@jigx/core-sdk 1.0.0 → 1.2.0-rc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +2 -0
  2. package/dist/action/ja.generate-pdf.d.ts +17 -1
  3. package/dist/action/ja.generate-pdf.d.ts.map +1 -1
  4. package/dist/action/ja.generate-pdf.js +4 -1
  5. package/dist/action/ja.in-background.d.ts +3 -2
  6. package/dist/action/ja.in-background.d.ts.map +1 -1
  7. package/dist/action/ja.in-background.js +1 -1
  8. package/dist/assets/example-extraction-cache.json +3 -3
  9. package/dist/assets/extracted-core-sdk-examples.yaml +18 -0
  10. package/dist/assets/extracted-core-sdk-types.yaml +58 -0
  11. package/dist/assets/type-extraction-cache.json +3 -3
  12. package/docs/array-fields.md +371 -0
  13. package/docs/conditional-logic.md +178 -0
  14. package/docs/convention-naming.md +102 -0
  15. package/docs/date-field.md +92 -0
  16. package/docs/dropdown-fields.md +879 -0
  17. package/docs/field-state.md +131 -0
  18. package/docs/field-types-overview.md +132 -0
  19. package/docs/formatting.md +421 -0
  20. package/docs/icons.md +142 -0
  21. package/docs/index.md +23 -0
  22. package/docs/jsonata-expressions.md +200 -0
  23. package/docs/media-fields.md +107 -0
  24. package/docs/overview.md +467 -0
  25. package/docs/pattern-build-deploy.md +91 -0
  26. package/docs/pattern-datasources.md +459 -0
  27. package/docs/pattern-forms.md +528 -0
  28. package/docs/pattern-global-actions.md +92 -0
  29. package/docs/pattern-javascript-functions.md +452 -0
  30. package/docs/pattern-navigation.md +304 -0
  31. package/docs/pattern-pdf-generation.md +391 -0
  32. package/docs/pattern-rest-acumatica.md +660 -0
  33. package/docs/pattern-sync-progress.md +96 -0
  34. package/docs/pattern-sync.md +653 -0
  35. package/docs/pattern-tabs-form.md +293 -0
  36. package/docs/recipe-index.md +64 -0
  37. package/docs/runtime-variables.md +127 -0
  38. package/docs/sections.md +81 -0
  39. package/docs/validation-patterns.md +150 -0
  40. package/package.json +5 -4
  41. package/CHANGELOG.md +0 -95
@@ -0,0 +1,293 @@
1
+ # Pattern: Tabbed Forms
2
+
3
+ **Tab-specific nuances only.** For generic form patterns (required fields, `isDisabled`,
4
+ save action ordering, global actions, edit-existing-record), see
5
+ [pattern-forms.md](pattern-forms.md).
6
+
7
+ ## Tabs screen with header
8
+
9
+ ```typescript
10
+ const tabs = app.addScreen.tabs({
11
+ screenId: SCREEN.CUSTOMER_CREATE,
12
+ title: 'New Customer',
13
+ })
14
+
15
+ tabs.with({ state: { showDiscardAlert: { initialValue: true } } })
16
+
17
+ const header = tabs.header()
18
+ header.with({ height: 'tiny' })
19
+ header.addControl.image({
20
+ instanceId: 'header-image',
21
+ source: { uri: 'https://images.jigx.com/customers/sales/sales_header.png' },
22
+ })
23
+ ```
24
+
25
+ ## Registering tabs with inputs
26
+
27
+ Use `.input()` to pass state from the tabs screen to child screens:
28
+
29
+ ```typescript
30
+ const generalTab = tabs.addInstance.tab(SCREEN.CUSTOMER_GENERAL, TAB_INSTANCE.GENERAL)
31
+ generalTab.button({ instanceId: 'general-btn', title: 'General', icon: 'contact' })
32
+ generalTab.input('showDiscardAlert', '=@ctx.jig.state.showDiscardAlert')
33
+ ```
34
+
35
+ ## Conditional embedded jigs
36
+
37
+ When one tab or detail screen needs to host a variant-specific sub-flow, use
38
+ `component.jig` inside a `jig.default` screen. Render one embedded jig per variant and
39
+ guard each with `when` rather than trying to make `jigId` dynamic.
40
+
41
+ ```typescript
42
+ screen.addControl.jig({
43
+ instanceId: 'amarr-builder',
44
+ jigId: SCREEN.DOOR_AMARR,
45
+ when: '=@ctx.components.manufacturer.state.value = "AMARR"',
46
+ inputs: {
47
+ doorId: '=@ctx.jig.inputs.doorId',
48
+ lineNumber: '=@ctx.jig.inputs.lineNumber',
49
+ },
50
+ })
51
+ ```
52
+
53
+ The wrapper owns variant selection. The embedded jig owns its form, validation, save
54
+ action, and item-code construction. Pass stable ids as inputs; do not rely on the
55
+ embedded jig instance id as a local row id.
56
+
57
+ For manufacturer-specific attribute builders, prefer not to use `component.jig` at
58
+ all. Navigate from a chooser/list screen to the concrete manufacturer jig and put the
59
+ save action on that concrete jig. Use wrapper-owned saves only when the embedded
60
+ content is simple enough that cross-jig validation has been proven in the target
61
+ runtime.
62
+
63
+ If the UX requires one parent-level save button, keep the wrapper as the save owner
64
+ instead. In that variant:
65
+
66
+ 1. The wrapper owns the bottom-panel action list.
67
+ 2. Each embedded jig is field-only. The wrapper reads the active embedded jig's
68
+ component state in the save action, e.g.
69
+ `@ctx.jigs.amarr-builder.components.size.state.selected.valueID`.
70
+ 3. The wrapper save action has one conditional `executeEntity` branch per embedded
71
+ variant, guarded by the wrapper selection (`when: =@ctx.components.manufacturer.state.value = "AMARR"`).
72
+ 4. Each branch also checks the active embedded jig's form validity before saving,
73
+ e.g. `@ctx.jigs.amarr-builder.components.door-details-form.state.isValid`.
74
+ 5. The wrapper save button's disabled expression should depend only on wrapper-owned
75
+ state, such as the selected manufacturer. Do not depend on embedded jig outputs
76
+ for the button style, because parent bottom-panel `isDisabled` can remain stale
77
+ after fields change inside the embedded jig.
78
+ 6. Shared wrapper datasources needed by the save, such as the existing local row and
79
+ PDF stale marker row, must live on the wrapper screen because the parent action
80
+ executes in wrapper context.
81
+
82
+ ```typescript
83
+ const amarr = app.addScreen.default({
84
+ screenId: SCREEN.DOOR_AMARR,
85
+ title: 'Amarr Door Builder',
86
+ })
87
+
88
+ const form = amarr.addControl.form({ instanceId: 'door-details-form' })
89
+
90
+ screen.addControl.jig({
91
+ instanceId: 'amarr-builder',
92
+ jigId: SCREEN.DOOR_AMARR,
93
+ when: '=@ctx.components.manufacturer.state.value = "AMARR"',
94
+ })
95
+
96
+ screen.bottomPanel().add.list({
97
+ title: 'Save Door',
98
+ isDisabled: '=@ctx.components.manufacturer.state.value ? false:true',
99
+ })
100
+ ```
101
+
102
+ Use the embedded component state inside the action list at tap time. Add an invalid
103
+ alert before the save branches, guard the state changes and saves with the embedded
104
+ form validity, and read field values from the embedded component state.
105
+
106
+ Do not rely on `@ctx.jigs.<embedded>.outputs` for wrapper-owned save actions unless
107
+ you have verified the current runtime exposes those outputs in the action context.
108
+ In the DoorPro flow, debug logs showed `@ctx.jigs.amarr-builder.outputs` as `null`
109
+ inside the wrapper action, while direct embedded component state remained the safer
110
+ pattern for wrapper-owned saves.
111
+
112
+ If the product requirement is a visually disabled button until every embedded field
113
+ is valid, put the save button inside the same jig as the form state instead of on
114
+ the wrapper.
115
+
116
+ When the parent owns the save button, read embedded component state in the save
117
+ action itself:
118
+
119
+ ```typescript
120
+ saveActions.showAlert({
121
+ instanceId: 'missing-required-fields',
122
+ when: '=@ctx.components.manufacturer.state.value = "AMARR" ? (@ctx.jigs.amarr-builder.components.door-details-form.state.isValid = true ? false:true) : true',
123
+ title: 'Door Not Complete',
124
+ })
125
+
126
+ saveActions
127
+ .executeEntity({
128
+ when: '=@ctx.components.manufacturer.state.value = "AMARR" and @ctx.jigs.amarr-builder.components.door-details-form.state.isValid = true',
129
+ })
130
+ .dynamicData('default/doorDetails', 'save')
131
+ .data({
132
+ sizeValueID: '=@ctx.jigs.amarr-builder.components.size.state.selected.valueID',
133
+ })
134
+ ```
135
+
136
+ For wrapper-owned saves, propagate the wrapper upload/discard state into the embedded
137
+ jigs as an input and bind each embedded form's `isDiscardChangesAlertEnabled` to that
138
+ input. The save action still follows the generic ordering: set wrapper `upload`,
139
+ save, reset wrapper `upload`, then navigate.
140
+
141
+ Do not put critical save actions on a section only to work around embedded jig bottom
142
+ panels not rendering. Use either embedded-owned saves when each embedded jig is a
143
+ standalone interaction, or wrapper-owned saves when the parent screen should expose a
144
+ single consistent save action.
145
+
146
+ ## Local correlation id for new tabbed aggregates
147
+
148
+ When a tabs flow creates a new local aggregate, the preferred pattern is:
149
+
150
+ 1. Navigate to the new parent screen with a generated jig `instanceId`
151
+ 2. Generate one local correlation id and use it for both:
152
+ - the parent screen `instanceId`
153
+ - the parent input that child tabs read as the aggregate key
154
+ 3. Pass that same id down to child tabs and child create screens as the parent key
155
+ 4. Save the parent row with that id in the local table
156
+ 5. Save child rows with `parentId` set to that same local correlation id
157
+
158
+ This is the stable local key for the whole aggregate until remote sync happens.
159
+
160
+ Example:
161
+
162
+ ```typescript
163
+ const createActions = list.bottomPanel().add.list({
164
+ title: 'New Service Order',
165
+ isPrimary: true,
166
+ concurrency: 'sequential',
167
+ }).actions
168
+
169
+ createActions.setScreenState({
170
+ instanceId: 'set-new-id',
171
+ keys: { newParentId: '=$uuid()' },
172
+ })
173
+
174
+ createActions.goto({
175
+ linkTo: SCREEN.SERVICE_ORDER_CREATE,
176
+ behaviour: 'new',
177
+ instanceId: '=@ctx.jig.state.newParentId',
178
+ parameters: {
179
+ serviceOrderId: '=@ctx.jig.state.newParentId',
180
+ },
181
+ })
182
+ ```
183
+
184
+ Then inside the tabs flow:
185
+
186
+ ```typescript
187
+ detailsTab.input('serviceOrderId', '=@ctx.jig.inputs.serviceOrderId')
188
+ ```
189
+
190
+ And child rows:
191
+
192
+ ```typescript
193
+ .data({
194
+ id: '=@ctx.jig.inputs.detailId',
195
+ parentId: '=@ctx.jig.inputs.serviceOrderId',
196
+ })
197
+ ```
198
+
199
+ For child create screens, generate the new child id at the `go-to` call and pass it in as an input or parameter. Do not rely on the child jig `instanceId` as the row id.
200
+
201
+ ```typescript
202
+ addDetail.goto({
203
+ linkTo: SCREEN.SERVICE_ORDER_DETAIL_CREATE,
204
+ behaviour: 'new',
205
+ parameters: {
206
+ serviceOrderId: '=@ctx.jig.inputs.serviceOrderId',
207
+ detailId: '=$uuid()',
208
+ },
209
+ })
210
+ ```
211
+
212
+ Then the child save can always use the passed `detailId` directly.
213
+
214
+ Important distinction:
215
+
216
+ - the parent input is the authoritative local correlator child tabs should use
217
+ - the parent screen `instanceId` should match that correlator for new flows
218
+ - existing records opened from a list should pass their persisted row id in inputs
219
+ - child tables should keep `parentId` pointing at the stable local parent id
220
+ - if a remote API later needs a different id (for example Acumatica `id` / `NoteID` for file uploads),
221
+ store that in a separate remote-target field and update it during sync operations
222
+
223
+ Do not mix "input id" and fallback `@ctx.jig.instanceId` logic in the same tabbed aggregate flow.
224
+ Use one stable local correlation id end to end.
225
+
226
+ Do not introduce refresh-token workarounds when the real issue is parent-child key consistency.
227
+ If details, photos, or contacts are not showing after save, check the local correlation id flow first.
228
+
229
+ ## Tab content screen with form
230
+
231
+ Each tab screen declares the input and wraps fields in a form. See
232
+ [pattern-forms.md](pattern-forms.md) for the generic form rules (required fields,
233
+ button validation, etc.).
234
+
235
+ ```typescript
236
+ const screen = app.addScreen.default({ screenId: SCREEN.CUSTOMER_GENERAL, title: 'General' })
237
+ screen.addInput({ name: 'showDiscardAlert', type: 'boolean' })
238
+
239
+ const form = screen.addControl.form({
240
+ instanceId: 'general-form',
241
+ isDiscardChangesAlertEnabled: '=@ctx.jig.inputs.showDiscardAlert',
242
+ })
243
+
244
+ form.addControl.textField({ instanceId: 'CustomerName', label: 'Customer Name', isRequired: true })
245
+ ```
246
+
247
+ ## Cross-jig form validation
248
+
249
+ The one thing unique to tabs: combine `isValid` across all child forms from the
250
+ parent tabs screen:
251
+
252
+ ```typescript
253
+ const IS_VALID = [
254
+ '=@ctx.jigs.general-instance.components.general-form.state.isValid',
255
+ 'and @ctx.jigs.billing-instance.components.billing-form.state.isValid',
256
+ 'and @ctx.jigs.shipping-instance.components.shipping-form.state.isValid',
257
+ ].join(' ')
258
+ ```
259
+
260
+ Use as `isDisabled` (inverted) on the save button — see
261
+ [pattern-forms.md](pattern-forms.md#save-button--isdisabled-never-when) for why
262
+ `isDisabled` not `when`.
263
+
264
+ ```typescript
265
+ tabs.bottomPanel().add.executeAction({
266
+ title: 'Save',
267
+ action: 'act-customer-save',
268
+ isDisabled: `=(${IS_VALID}) = true ? false:true`,
269
+ })
270
+ ```
271
+
272
+ ## Discard alert across tabs
273
+
274
+ The discard alert pattern in [pattern-forms.md](pattern-forms.md#save-action-list--correct-ordering)
275
+ uses a single-screen local state key (`upload`). For tabs, the state lives on the
276
+ parent tabs screen and propagates to child tabs via inputs:
277
+
278
+ 1. **Tabs screen state**: `upload: { initialValue: null }`
279
+ 2. **Pass to child tabs** via `.input('upload', '=@ctx.jig.state.upload')`
280
+ 3. **Child screens declare** `addInput({ name: 'upload', type: 'boolean' })`
281
+ 4. **Form binds**: `isDiscardChangesAlertEnabled: '=@ctx.jig.inputs.upload = true ? false:true'`
282
+ 5. **Global action's first step**: `setScreenState({ keys: { upload: true } })` —
283
+ this targets the tabs screen's state (the *calling* screen), which propagates to
284
+ child forms via the input binding
285
+ 6. **Global action's last steps**:
286
+ - save-and-close flows: `resetScreenState` → `goBack`
287
+ - save-in-place flows: `resetScreenState`
288
+ See [pattern-forms.md](pattern-forms.md#save-action-list--correct-ordering).
289
+
290
+ **The nuance vs single-screen**: in the tabs pattern, the state lives on the PARENT
291
+ tabs screen, not the child form screens. `setScreenState` targets the calling jig,
292
+ which in this flow is the tabs screen (where the save button lives). Because child
293
+ tabs read the state via `.input()`, they pick up the change on the next render.
@@ -0,0 +1,64 @@
1
+ # Recipe: Core SDK App with Acumatica Integration
2
+
3
+ Build a multi-screen Jigx app using Core SDK with local data, tabbed forms, REST integration, and Acumatica-compatible nested JSON.
4
+
5
+ Reference implementation: `workspace/acumatica-apps/create-customer/`
6
+
7
+ ## Pattern files
8
+
9
+ | Pattern | Description |
10
+ | --- | --- |
11
+ | [convention-naming.md](convention-naming.md) | File, component, field, and constant naming conventions |
12
+ | [pattern-datasources.md](pattern-datasources.md) | SELECT id/data, jsonProperties, isDocument, config table, schemas folder |
13
+ | [pattern-forms.md](pattern-forms.md) | **Generic form patterns** — required fields, save button isDisabled, save action ordering, global action extraction, edit-existing-record |
14
+ | [pattern-tabs-form.md](pattern-tabs-form.md) | Tab-specific additions — cross-jig validation, tab inputs for discard-alert propagation |
15
+ | [pattern-global-actions.md](pattern-global-actions.md) | Parameter snapshotting, local save action, JSONata save expression, TypeScript helpers |
16
+ | [pattern-rest-acumatica.md](pattern-rest-acumatica.md) | REST functions, Acumatica conventions, functionCall, accessToken, remote sync flag |
17
+ | [pattern-navigation.md](pattern-navigation.md) | behaviour: 'new', $uuid(), bottom panel order, list-detail-child flow, screen-scoped datasources |
18
+ | [pattern-sync.md](pattern-sync.md) | Initial sync, continuation, diff sync, sync scopes, OData vs REST |
19
+ | [pattern-sync-progress.md](pattern-sync-progress.md) | Sync progress bar UI ideas — weights, step text, show/hide, cards |
20
+ | [pattern-javascript-functions.md](pattern-javascript-functions.md) | `app.script()` — extend JSONata with custom JS functions (math, formatting, HTML templates, validation, transformation) |
21
+ | [pattern-pdf-generation.md](pattern-pdf-generation.md) | `generate-pdf` action + `app.script()` + `jig.document` screen — generate, persist, preview, share PDFs |
22
+ | [pattern-build-deploy.md](pattern-build-deploy.md) | Build order, typecheck, JSON/YAML generation, deploy script, Jigx API |
23
+
24
+ ## Project structure
25
+
26
+ ```
27
+ src/
28
+ ├── app.ts # Assembly + build order
29
+ ├── common.ts # Screen/action/function/datasource/entity constants
30
+ ├── database.ts # Table definitions (config, customers, contacts)
31
+ ├── datasources.ts # App-scoped datasources
32
+ ├── schemas/
33
+ │ ├── customers.yaml # Schema for default/customers table
34
+ │ └── contacts.yaml # Schema for default/contacts table
35
+ ├── functions/
36
+ │ └── rest-put-customer.ts # REST PUT function for Acumatica
37
+ ├── actions/
38
+ │ ├── act-customer-save.ts # Save customer locally
39
+ │ └── act-customer-save-remote.ts # Save customer to Acumatica via REST
40
+ └── screens/
41
+ ├── jig-customer-list.ts # List screen with header sync action
42
+ ├── jig-customer-general.ts # Tab 1 content (default screen with form)
43
+ ├── jig-customer-billing.ts # Tab 2 content
44
+ ├── jig-customer-shipping.ts # Tab 3 content
45
+ ├── jig-customer-create.ts # Tabs screen (orchestrator)
46
+ ├── jig-customer-view.ts # Detail view
47
+ └── jig-contact-create.ts # Child entity form
48
+ ```
49
+
50
+ ## Schema-driven field design
51
+
52
+ Start with the external API schema (e.g., `acumatica schemas/Customers.yaml`) to drive field creation. The schema documents:
53
+
54
+ - Field paths (`data.CustomerName.value`, `data.MainContact.Email.value`)
55
+ - Nested objects (`MainContact`, `BillingContact`, `ShippingContact`, `PrimaryContact`)
56
+ - Field types and enumerations
57
+ - Relationships to other entities
58
+
59
+ Use the schema to:
60
+
61
+ 1. Name form field `instanceId` values to match schema field names
62
+ 2. Structure the save JSONata expression to match the schema's nesting
63
+ 3. Identify required fields from the UI design (orange-highlighted labels in mockups)
64
+ 4. Map sections to schema objects (Account Address → top-level, Primary Contact → `PrimaryContact`, etc.)
@@ -0,0 +1,127 @@
1
+ ## Import Statement
2
+
3
+ ```typescript
4
+ import { Auth, System, JsonataBuilder } from '@jigx/expert-sdk'
5
+ ```
6
+
7
+ ## System Variables
8
+
9
+ ### Device & Environment
10
+
11
+ | Variable | Type | Description |
12
+ | --- | --- | --- |
13
+ | `System.deviceType` | string | Device type ('ios', 'android') |
14
+ | `System.isOnline` | boolean | Network connected |
15
+ | `System.isOffline` | boolean | Network disconnected |
16
+ | `System.isPortrait` | boolean | Portrait orientation |
17
+ | `System.locale` | string | User's locale setting |
18
+ | `System.appVersion` | string | Current app version |
19
+
20
+ ### Location
21
+
22
+ | Variable | Type | Description |
23
+ | --- | --- | --- |
24
+ | `System.isLocationSharingEnabled` | boolean | Location permission granted |
25
+ | `System.geolocation.coords.latitude` | number | GPS latitude |
26
+ | `System.geolocation.coords.longitude` | number | GPS longitude |
27
+ | `System.geolocation.coords.accuracy` | number | GPS accuracy (meters) |
28
+ | `System.geolocation.coords.altitude` | number | GPS altitude |
29
+ | `System.geolocation.coords.speed` | number | Movement speed |
30
+ | `System.geolocation.coords.heading` | number | Movement direction |
31
+ | `System.geolocation.timestamp` | number | Location timestamp |
32
+
33
+ ### Timezone
34
+
35
+ | Variable | Type | Description |
36
+ | --- | --- | --- |
37
+ | `System.timezone.name` | string | Timezone name (e.g., 'America/New_York') |
38
+ | `System.timezone.offset` | number | UTC offset in minutes |
39
+
40
+ ## User Variables (Auth.user)
41
+
42
+ | Variable | Type | Description |
43
+ | --- | --- | --- |
44
+ | `Auth.user.id` | string | User's unique identifier |
45
+ | `Auth.user.displayName` | string | User's display name |
46
+ | `Auth.user.email` | string | User's email address |
47
+ | `Auth.user.phone` | string | User's phone number |
48
+ | `Auth.user.avatarUrl` | string | User's avatar image URL |
49
+ | `Auth.user.isVerified` | boolean | Account verification status |
50
+
51
+ ## Usage Examples
52
+
53
+ ### Pre-fill with User Data
54
+
55
+ ```typescript
56
+ step.addText({
57
+ name: 'name',
58
+ label: 'Name',
59
+ value: new JsonataBuilder('$userName', { userName: Auth.user.displayName }),
60
+ })
61
+ step.addEmail({
62
+ name: 'email',
63
+ label: 'Email',
64
+ value: new JsonataBuilder('$userEmail', { userEmail: Auth.user.email }),
65
+ })
66
+ ```
67
+
68
+ ### Conditional Based on Network
69
+
70
+ ```typescript
71
+ step.addText({
72
+ name: 'status',
73
+ label: 'Status',
74
+ value: new JsonataBuilder('$online ? "Connected" : "Offline"', { online: System.isOnline }),
75
+ })
76
+ ```
77
+
78
+ ### Device-Specific Logic
79
+
80
+ ```typescript
81
+ step.addDropdown({
82
+ name: 'selection',
83
+ label: 'Selection',
84
+ data: [
85
+ { label: 'Option A', value: 'a' },
86
+ { label: 'Option B', value: 'b' },
87
+ ],
88
+ helperText: new JsonataBuilder('$device = "ios" ? "Tap to select" : "Touch to select"', {
89
+ device: System.deviceType,
90
+ }),
91
+ })
92
+ ```
93
+
94
+ ### Location-Based Features
95
+
96
+ ```typescript
97
+ step.addLocation({ name: 'location', label: 'Current Location' })
98
+ // Location field auto-configures with System.geolocation
99
+ // Or manual access:
100
+ step.addText({
101
+ name: 'coords',
102
+ label: 'Coordinates',
103
+ value: new JsonataBuilder('$lat & "," & $lng', {
104
+ lat: System.geolocation.coords.latitude,
105
+ lng: System.geolocation.coords.longitude,
106
+ }),
107
+ })
108
+ ```
109
+
110
+ ### Timezone Display
111
+
112
+ ```typescript
113
+ step.addText({
114
+ name: 'timezone',
115
+ label: 'Timezone',
116
+ helperText: new JsonataBuilder('"Your timezone: " & $tz', { tz: System.timezone.name }),
117
+ })
118
+ ```
119
+
120
+ ## Availability
121
+
122
+ System and Auth variables are accessible in all `JsonataBuilder` contexts:
123
+
124
+ - Field properties (value, helperText, isVisible, etc.)
125
+ - Step properties (title, description, isVisible)
126
+ - Submission item (submissionItemTitle, submissionItemSubtitle)
127
+ - Form metadata (description)
@@ -0,0 +1,81 @@
1
+ ## Creating Sections
2
+
3
+ ```typescript
4
+ // Section for contact info
5
+ const contactSection = step.addSection({ title: 'Contact Information' })
6
+ contactSection.addEmail({ name: 'email', label: 'Email' })
7
+ contactSection.addPhone({ name: 'phone', label: 'Phone' })
8
+ // Section for address
9
+ const addressSection = step.addSection({ title: 'Address' })
10
+ addressSection.addText({ name: 'street', label: 'Street' })
11
+ addressSection.addText({ name: 'city', label: 'City' })
12
+ ```
13
+
14
+ **Important:** Add fields to the section, not the step directly:
15
+
16
+ ```typescript
17
+ // Correct
18
+ section.addText({ name: 'field', label: 'Field' })
19
+
20
+ // Wrong - adds to step, not section
21
+ step.addText({ name: 'field', label: 'Field' })
22
+ ```
23
+
24
+ ## Section Properties
25
+
26
+ Pass in constructor or use `.with()`:
27
+
28
+ ```typescript
29
+ const toggleField = step.addCheckbox({ name: 'show-section', label: 'Show Issues' })
30
+ const section = step.addSection({
31
+ title: 'Issues & Evidence',
32
+ subtitle: 'Describe any problems encountered',
33
+ })
34
+ // Or update later
35
+ section.with({
36
+ title: 'Updated Title',
37
+ isVisible: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
38
+ })
39
+ section.addText({ name: 'description', label: 'Description' })
40
+ ```
41
+
42
+ ## Best Practices
43
+
44
+ **Section titles:**
45
+
46
+ - Keep concise (under 20 characters)
47
+ - Use clear, descriptive names
48
+ - Examples: "Contact Info", "Preferences", "Billing Details"
49
+
50
+ **Grouping logic:**
51
+
52
+ - Contact info: email, phone, address
53
+ - Personal: name, date of birth, gender
54
+ - Professional: company, title, department
55
+ - Preferences: notification settings, language
56
+
57
+ ## Example: Multi-Section Step
58
+
59
+ ```typescript
60
+ form.addStep({ instanceId: 'profile', icon: 'single-neutral' }, (step) => {
61
+ // Personal section
62
+ const personal = step.addSection({ title: 'Personal' })
63
+ personal.addText({ name: 'full-name', label: 'Full Name' })
64
+ personal.addDate({ name: 'dob', label: 'Date of Birth' })
65
+ // Contact section
66
+ const contact = step.addSection({ title: 'Contact' })
67
+ contact.addEmail({ name: 'email', label: 'Email' })
68
+ contact.addPhone({ name: 'phone', label: 'Phone' })
69
+ // Preferences section
70
+ const prefs = step.addSection({ title: 'Preferences' })
71
+ prefs.addCheckbox({ name: 'newsletter', label: 'Subscribe to newsletter' })
72
+ prefs.addDropdown({
73
+ name: 'language',
74
+ label: 'Language',
75
+ data: [
76
+ { label: 'English', value: 'en' },
77
+ { label: 'Spanish', value: 'es' },
78
+ ],
79
+ })
80
+ })
81
+ ```