diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..61a0262
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,186 @@
+name: CI
+
+on:
+ push:
+ branches: [main, develop, 'release/**', 'hotfix/**']
+ pull_request:
+ branches: [main, develop]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ lint-and-typecheck:
+ name: Lint & Type Check
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Type check
+ run: npm run type-check
+
+ test:
+ name: Test
+ runs-on: ubuntu-latest
+ needs: lint-and-typecheck
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run tests with coverage
+ run: npm run test:coverage
+
+ - name: Extract coverage data
+ id: coverage
+ run: |
+ if [ -f coverage/coverage-summary.json ]; then
+ LINES=$(jq -r '.total.lines.pct' coverage/coverage-summary.json)
+ BRANCHES=$(jq -r '.total.branches.pct' coverage/coverage-summary.json)
+ FUNCTIONS=$(jq -r '.total.functions.pct' coverage/coverage-summary.json)
+ STATEMENTS=$(jq -r '.total.statements.pct' coverage/coverage-summary.json)
+ else
+ LINES="N/A"
+ BRANCHES="N/A"
+ FUNCTIONS="N/A"
+ STATEMENTS="N/A"
+ fi
+
+ echo "lines=$LINES" >> $GITHUB_OUTPUT
+ echo "branches=$BRANCHES" >> $GITHUB_OUTPUT
+ echo "functions=$FUNCTIONS" >> $GITHUB_OUTPUT
+ echo "statements=$STATEMENTS" >> $GITHUB_OUTPUT
+
+ - name: Post coverage comment on PR
+ if: github.event_name == 'pull_request'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const lines = '${{ steps.coverage.outputs.lines }}';
+ const branches = '${{ steps.coverage.outputs.branches }}';
+ const functions = '${{ steps.coverage.outputs.functions }}';
+ const statements = '${{ steps.coverage.outputs.statements }}';
+
+ const getEmoji = (pct) => {
+ const num = parseFloat(pct);
+ if (isNaN(num)) return '❓';
+ if (num >= 80) return '✅';
+ if (num >= 60) return '🟡';
+ return '🔴';
+ };
+
+ const body = `## 📊 Test Coverage Report
+
+ | Metric | Coverage | Status |
+ |--------|----------|--------|
+ | Lines | ${lines}% | ${getEmoji(lines)} |
+ | Branches | ${branches}% | ${getEmoji(branches)} |
+ | Functions | ${functions}% | ${getEmoji(functions)} |
+ | Statements | ${statements}% | ${getEmoji(statements)} |
+
+ > Coverage thresholds: 🔴 < 60% | 🟡 60-79% | ✅ >= 80%
+ `;
+
+ // Find existing comment
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+
+ const botComment = comments.find(comment =>
+ comment.user.type === 'Bot' &&
+ comment.body.includes('📊 Test Coverage Report')
+ );
+
+ if (botComment) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: body,
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: body,
+ });
+ }
+
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ needs: test
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build library
+ run: npm run build
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist
+ path: dist/
+ retention-days: 7
+
+ test-matrix:
+ name: Test on Node ${{ matrix.node-version }}
+ runs-on: ubuntu-latest
+ needs: lint-and-typecheck
+
+ strategy:
+ matrix:
+ # Node 18 is not supported - Vite 7 and Vitest 4 require Node 20+
+ node-version: ['20', '22']
+ fail-fast: false
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run tests
+ run: npm run test:run
diff --git a/.gitignore b/.gitignore
index d781e91..14a7bde 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
node_modules
dist
+coverage
client/capi/data
client/cawi/data
src/data/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 874eb1f..3b3da3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,31 +1,49 @@
# Changelog
-## FormGear | Ver. 2.0.0 - FormGear is now running independently. 🎉
+## FormGear | Ver. 2.0.0 - Major Architecture Refactoring
-> September 02, 2022
+> December 31, 2025
+
+### Breaking Changes
+
+- **New Factory API**: Use `createFormGear()` instead of direct `FormGear` component
+- **Store Isolation**: Each form instance now has its own isolated store context
+- **TypeScript Strict Mode**: Full TypeScript strict mode enabled with proper types
+
+### Added
+
+- `createFormGear()` factory function for creating isolated form instances
+- `FormGearProvider` component for store context management
+- Native bridge abstraction layer supporting Android, iOS, Flutter, and Web platforms
+- TypeScript declaration files (`.d.ts`) included in package
+- Modular event handlers (`Focus`, `KeyDown`) with proper exports
+- Platform-specific bridge implementations with type-safe interfaces
### Changed
+
+- Migrated from single global store to per-instance isolated stores
+- Updated all dependencies to latest versions (Vite 7.3, TypeScript 5.9, SolidJS 1.9)
+- Build target changed to ES2015 for better mobile WebView compatibility
+- Utilities refactored into modular structure (`/utils` directory)
+- SolidJS components now use proper `class` attribute instead of `className`
+- Improved type safety across all components with strict TypeScript
+
+### Removed
+
+- Legacy global `FormGear` component export (use `createFormGear()` instead)
+- Deprecated utility functions replaced with modern implementations
+
+### Migration
+
+See [MIGRATION.md](./MIGRATION.md) for detailed upgrade instructions.
+
+---
+
+## FormGear | Ver. 1.1.2 - Legacy Release
+
+> September 02, 2022
+
- Reformat `sourceSelect` to `sourceAPI` for select option from API
- ```json
- {
- "label": "User",
- "dataKey": "user_jsonplaceholder",
- "typeOption": 2,
- "type": 27,
- "sourceAPI": [
- {
- "baseUrl": "https://jsonplaceholder.typicode.com/users",
- "headers": {
- "Content-Type": "application/json"
- },
- "data": "",
- "value": "id",
- "label": "name"
- }
- ]
- }
- ```
-### Fixed
- Fix summary calculation filtration for undefined answer component
diff --git a/MIGRATION.md b/MIGRATION.md
new file mode 100644
index 0000000..cfde734
--- /dev/null
+++ b/MIGRATION.md
@@ -0,0 +1,416 @@
+# Migration Guide: FormGear 1.x to 2.0
+
+This guide helps you migrate from FormGear 1.x to the new 2.0 API.
+
+## Overview of Changes
+
+FormGear 2.0 introduces a modern, type-safe API while maintaining functionality:
+
+| Feature | v1.x | v2.0 |
+|---------|------|------|
+| API Style | 16 positional parameters | Options object pattern |
+| TypeScript | Partial types | Full strict TypeScript |
+| Instance Control | Callbacks only | Programmatic methods |
+| Platform Bridge | Hardcoded checks | Modular bridge abstraction |
+
+## Quick Migration
+
+### Before (v1.x)
+
+```typescript
+import { FormGear } from 'form-gear';
+
+FormGear(
+ referenceJson,
+ templateJson,
+ presetJson,
+ responseJson,
+ validationJson,
+ mediaJson,
+ remarkJson,
+ {
+ clientMode: 1,
+ formMode: 1,
+ initialMode: 2,
+ lookupMode: 1,
+ username: 'user123',
+ token: 'abc123',
+ baseUrl: 'https://api.example.com',
+ },
+ uploadHandler,
+ gpsHandler,
+ offlineSearch,
+ onlineSearch,
+ exitHandler,
+ saveCallback,
+ submitCallback,
+ openMapHandler
+);
+```
+
+### After (v2.0)
+
+```typescript
+import { createFormGear, ClientMode, FormMode, InitialMode, LookupMode } from 'form-gear';
+
+const form = createFormGear({
+ data: {
+ reference: referenceJson,
+ template: templateJson,
+ preset: presetJson,
+ response: responseJson,
+ validation: validationJson,
+ media: mediaJson,
+ remark: remarkJson,
+ },
+ config: {
+ clientMode: ClientMode.CAWI,
+ formMode: FormMode.OPEN,
+ initialMode: InitialMode.ASSIGN,
+ lookupMode: LookupMode.ONLINE,
+ username: 'user123',
+ token: 'abc123',
+ baseUrl: 'https://api.example.com',
+ },
+ mobileHandlers: {
+ uploadHandler,
+ gpsHandler,
+ offlineSearch,
+ onlineSearch,
+ exitHandler,
+ openMap: openMapHandler,
+ },
+ callbacks: {
+ onSave: saveCallback,
+ onSubmit: submitCallback,
+ },
+});
+```
+
+## Enums Reference
+
+### ClientMode
+
+| v1.x Value | v2.0 Enum |
+|------------|-----------|
+| `1` | `ClientMode.CAWI` |
+| `2` | `ClientMode.CAPI` |
+
+### FormMode
+
+| v1.x Value | v2.0 Enum |
+|------------|-----------|
+| `1` | `FormMode.OPEN` |
+| `2` | `FormMode.REVIEW` |
+| `3` | `FormMode.CLOSE` |
+
+### InitialMode
+
+| v1.x Value | v2.0 Enum |
+|------------|-----------|
+| `1` | `InitialMode.INITIAL` |
+| `2` | `InitialMode.ASSIGN` |
+
+### LookupMode
+
+| v1.x Value | v2.0 Enum |
+|------------|-----------|
+| `1` | `LookupMode.ONLINE` |
+| `2` | `LookupMode.OFFLINE` |
+
+## New Instance Methods
+
+v2.0 returns a form instance with programmatic methods:
+
+```typescript
+const form = createFormGear({ ... });
+
+// Get current form data
+const responses = form.getResponse();
+const media = form.getMedia();
+const remarks = form.getRemarks();
+const principal = form.getPrincipal();
+const summary = form.getSummary();
+
+// Validate form
+const isValid = form.validate();
+
+// Get/Set values
+const value = form.getValue('dataKey');
+form.setValue('dataKey', newValue);
+
+// Trigger save/submit programmatically
+form.save();
+form.submit();
+
+// Cleanup when done
+form.destroy();
+```
+
+## Platform Bridge
+
+v2.0 includes a modular native bridge for platform detection and communication:
+
+```typescript
+import {
+ createBridge,
+ detectPlatform,
+ isNativeApp,
+ isMobile,
+ getPlatformName,
+} from 'form-gear';
+
+// Auto-detect and create appropriate bridge
+const bridge = createBridge();
+
+// Check platform
+console.log(getPlatformName()); // 'android' | 'ios' | 'flutter' | 'web'
+console.log(isNativeApp()); // true if running in native app
+console.log(isMobile()); // true if mobile device
+
+// Use bridge for native communication
+bridge.getGpsPhoto('location', (result) => {
+ console.log(result.latitude, result.longitude);
+});
+```
+
+### Platform-Specific Bridges
+
+```typescript
+import {
+ createAndroidBridge,
+ createIOSBridge,
+ createFlutterInAppWebViewBridge,
+ createWebBridge,
+} from 'form-gear';
+
+// Create specific bridge when you know the platform
+const bridge = createAndroidBridge();
+```
+
+## TypeScript Types
+
+All types are now exported for full TypeScript support:
+
+```typescript
+import type {
+ // Configuration
+ FormGearConfig,
+ FormGearOptions,
+ FormGearInstance,
+ FormGearData,
+ FormGearCallbacks,
+
+ // Handlers
+ UploadHandler,
+ GpsHandler,
+ OfflineSearchHandler,
+ OnlineSearchHandler,
+
+ // Components
+ FormComponentProps,
+ Option,
+ RangeInput,
+ LengthInput,
+
+ // Store types
+ ResponseState,
+ ReferenceState,
+ ValidationState,
+
+ // Bridge types
+ NativeBridge,
+ Platform,
+ GpsPhotoResult,
+} from 'form-gear';
+```
+
+## CSS Import
+
+Import the styles in your application:
+
+```typescript
+// ES Modules
+import 'form-gear/style.css';
+
+// Or use the dist path
+import 'form-gear/dist/style.css';
+```
+
+## Breaking Changes Summary
+
+1. **`FormGear` export removed** - Use `createFormGear()` instead
+2. **Numeric config values** - Use enums (`ClientMode.CAWI` instead of `1`)
+3. **Parameter ordering** - Use named options object instead of 16 positional params
+4. **Handler naming** - `exitHandler` instead of `mobileExit`, `openMap` instead of `openMapHandler`
+
+## Example: Complete Setup
+
+```typescript
+import { createFormGear, ClientMode, FormMode, LookupMode } from 'form-gear';
+import 'form-gear/style.css';
+
+// Load your JSON data
+const template = await fetch('/data/template.json').then(r => r.json());
+const validation = await fetch('/data/validation.json').then(r => r.json());
+const preset = await fetch('/data/preset.json').then(r => r.json());
+
+// Create form instance
+const form = createFormGear({
+ data: {
+ template,
+ validation,
+ preset,
+ response: {}, // Empty for new form
+ reference: {}, // Will be generated
+ media: {},
+ remark: {},
+ },
+ config: {
+ clientMode: ClientMode.CAWI,
+ formMode: FormMode.OPEN,
+ lookupMode: LookupMode.ONLINE,
+ username: 'surveyor01',
+ baseUrl: 'https://api.example.com',
+ token: localStorage.getItem('authToken') || '',
+ },
+ mobileHandlers: {
+ uploadHandler: (type, dataKey, callback) => {
+ // Handle file uploads
+ },
+ gpsHandler: (dataKey, callback) => {
+ // Handle GPS requests
+ },
+ onlineSearch: async (id, version, params) => {
+ // Fetch lookup data from API
+ return await fetch(`/lookup/${id}`).then(r => r.json());
+ },
+ },
+ callbacks: {
+ onSave: (response, media, remark, principal, reference) => {
+ console.log('Form saved:', response);
+ // Save to local storage or send to server
+ },
+ onSubmit: (response, media, remark, principal, reference) => {
+ console.log('Form submitted:', response);
+ // Submit to server
+ },
+ },
+});
+
+// Use instance methods
+document.getElementById('validateBtn')?.addEventListener('click', () => {
+ if (form.validate()) {
+ console.log('Form is valid!');
+ } else {
+ console.log('Form has errors');
+ }
+});
+
+document.getElementById('submitBtn')?.addEventListener('click', () => {
+ form.submit();
+});
+```
+
+## FormGear Builder
+
+FormGear now has a visual **Template Builder** - a drag-and-drop interface for creating form templates without writing JSON manually.
+
+**The builder is available in a separate repository:** [formgear-builder](https://github.com/AdityyaX/formgear-builder)
+
+### Builder Overview
+
+The builder is a Vue 3 application that generates `template.json` and `validation.json` files compatible with all FormGear versions.
+
+| Output File | Description |
+|-------------|-------------|
+| `template.json` | Form structure, components, metadata |
+| `validation.json` | Validation rules and test functions |
+
+### Builder Features
+
+- **Drag-and-drop** component palette with all 38 component types
+- **Visual form canvas** with nested component support (Section, Nested)
+- **Properties panel** for editing component attributes
+- **Expression builder** for `enableCondition` and validation `test` expressions
+- **Live preview** using the actual FormGear engine
+- **Import/Export** existing JSON templates
+
+### Form Metadata
+
+The builder supports all form metadata fields:
+
+| Field | Description |
+|-------|-------------|
+| `title` | Form title displayed at the top |
+| `description` | Form description/subtitle |
+| `dataKey` | Unique form identifier |
+| `acronym` | Short form acronym |
+| `version` | Semantic version (e.g., "1.0.0") |
+
+### Export Structure
+
+The builder exports files in the same format as the FormGear engine expects:
+
+**template.json:**
+```json
+{
+ "description": "My Survey",
+ "dataKey": "my_survey",
+ "title": "Survey Title",
+ "acronym": "MS",
+ "version": "1.0.0",
+ "components": [
+ [
+ {
+ "label": "Section 1",
+ "dataKey": "section_1",
+ "type": 1,
+ "components": [
+ [
+ { "label": "Name", "dataKey": "name", "type": 25 }
+ ]
+ ]
+ }
+ ]
+ ]
+}
+```
+
+**validation.json:**
+```json
+{
+ "dataKey": "my_survey",
+ "version": "1.0.0",
+ "testFunctions": [
+ {
+ "dataKey": "name",
+ "componentValidation": ["name"],
+ "validations": [
+ {
+ "test": "getValue('name') == ''",
+ "message": "Name is required",
+ "type": 1
+ }
+ ]
+ }
+ ]
+}
+```
+
+### Backward Compatibility
+
+The builder's JSON output is **fully backward compatible** with:
+- FormGear 1.x ✅
+- FormGear 2.x ✅
+
+The builder uses Vue 3 and Tailwind CSS v4 internally, but this doesn't affect compatibility since it only outputs standard JSON files.
+
+### Note on reference.json
+
+`reference.json` is **NOT** generated by the builder. It's a runtime state file created by the FormGear engine when initializing a form. The builder only exports `template.json` and `validation.json`.
+
+## Need Help?
+
+- Check the [CHANGELOG.md](./CHANGELOG.md) for all changes
+- Open an issue on [GitHub](https://github.com/AdityaSetyadi/form-gear/issues)
diff --git a/README.md b/README.md
index ac59c64..9a4488f 100644
--- a/README.md
+++ b/README.md
@@ -35,8 +35,8 @@ FormGear uses a defined JSON object template, thus is easy to build, use, and ef
- [About](#about)
- [Features](#features)
- [Usage](#usage)
- * [Online examples](#online-examples)
- * [Develop on JS Framework examples](#develop-on-js-framework-examples)
+ * [Online examples](#online-examples)
+ * [Develop on JS Framework examples](#develop-on-js-framework-examples)
* [Installation](#installation)
* [Template](#template)
+ [Control Type](#control-type)
@@ -48,8 +48,10 @@ FormGear uses a defined JSON object template, thus is easy to build, use, and ef
* [Response](#response)
* [Validation](#validation)
* [Remark](#remark)
+- [Template Builder](#template-builder)
+- [Migration Guide](#migration-guide)
- [Contributing](#contributing)
- * [Further development ideas](#further-development-ideas)
+ * [Further development ideas](#further-development-ideas)
- [License](#license)
- [Our Team](#our-team)
@@ -399,9 +401,17 @@ FormGear uses numbers as code to define each control type. Below is a table of c
| `CheckboxInput` | 29 | Checkbox input field, lets user choose one or more options of limited choices. |
| `TextAreaInput` | 30 | Adjustable text area input field. |
| `EmailInput` | 31 | Email address input field. |
-| `PhotoInput` | 32 | Photo input, lets user add picture with .`jpg`, `.jpeg`, `.png`, and `.gif` format. |
-| `GpsInput` | 33 | GPS input. |
-| `CsvInput` | 34 | CSV input, lets user upload `.csv` file to be stored as `.json` format in the Response. The `.csv` file can later be downloaded again in the same format. |
+| `PhotoInput` | 32 | Photo input, lets user add picture with `.jpg`, `.jpeg`, `.png`, and `.gif` format. |
+| `GpsInput` | 33 | GPS input, captures device coordinates with optional map view. |
+| `CsvInput` | 34 | CSV input, lets user upload `.csv` file stored as `.json` in the Response. The file can be downloaded again later. |
+| `NowInput` | 35 | Captures the current date and time at the moment the field is filled. |
+| `SignatureInput` | 36 | Signature pad input, lets user draw a signature that is stored as an image. |
+| `UnitInput` | 37 | Numeric input with a selectable unit label (e.g. kg, cm). |
+| `DecimalInput` | 38 | Decimal number input field. |
+| `AudioInput` | 39 | Audio recording input, lets user record audio directly from the device microphone. |
+| `BarcodeInput` | 40 | Barcode / QR code scanner input via device camera with manual fallback. |
+| `VideoInput` | 41 | Video recording input, lets user record or upload a video file. |
+| `FileInput` | 42 | General file picker with configurable allowed extensions and size limits. |
### Component Type
@@ -781,12 +791,62 @@ remark.json
}
```
+# Template Builder
+
+FormGear now has a visual **Template Builder** - a drag-and-drop interface for creating form templates without writing JSON manually.
+
+**The builder is available in a separate repository:** [formgear-builder](https://github.com/AdityyaX/formgear-builder)
+
+## Builder Features
+
+- **Drag-and-drop** component palette with all 42 component types
+- **Visual form canvas** with nested component support (Section, Nested)
+- **Properties panel** for editing component attributes
+- **Expression builder** for `enableCondition` and validation `test` expressions
+- **Live preview** using the actual FormGear engine
+- **Import/Export** existing JSON templates
+
+## Builder Output
+
+The builder exports two JSON files:
+
+| Output File | Description |
+|-------------|-------------|
+| `template.json` | Form structure, components, metadata |
+| `validation.json` | Validation rules and test functions |
+
+These files are **fully backward compatible** with FormGear 1.x and 2.x.
+
+## Form Metadata
+
+The builder supports all form metadata fields:
+
+| Field | Description |
+|-------|-------------|
+| `title` | Form title displayed at the top |
+| `description` | Form description/subtitle |
+| `dataKey` | Unique form identifier |
+| `acronym` | Short form acronym |
+| `version` | Semantic version (e.g., "1.0.0") |
+
+# Migration Guide
+
+If you're upgrading from FormGear 1.x to 2.0, please refer to the [Migration Guide](./MIGRATION.md) for detailed instructions on:
+
+- API changes (positional parameters → options object)
+- New TypeScript enums (`ClientMode`, `FormMode`, etc.)
+- Instance methods for programmatic control
+- Platform bridge abstraction
+- Using the new Template Builder
+
# Contributing
Your assistance is greatly appreciated if you want to contribute and make it better.
+
### Further development ideas:
-- FormGear templates design platform
-- FormGear validation creator platform
+
+- ~~FormGear templates design platform~~ ✅ (Now available as Template Builder!)
+- ~~FormGear validation creator platform~~ ✅ (Included in Template Builder!)
# License
diff --git a/client/capi/index.html b/client/capi/index.html
index c478107..2654c3a 100644
--- a/client/capi/index.html
+++ b/client/capi/index.html
@@ -126,243 +126,371 @@