@jigx/core-sdk 1.1.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 (39) hide show
  1. package/dist/action/ja.generate-pdf.d.ts +17 -1
  2. package/dist/action/ja.generate-pdf.d.ts.map +1 -1
  3. package/dist/action/ja.generate-pdf.js +4 -1
  4. package/dist/action/ja.in-background.d.ts +3 -2
  5. package/dist/action/ja.in-background.d.ts.map +1 -1
  6. package/dist/action/ja.in-background.js +1 -1
  7. package/dist/assets/example-extraction-cache.json +3 -3
  8. package/dist/assets/extracted-core-sdk-examples.yaml +18 -0
  9. package/dist/assets/extracted-core-sdk-types.yaml +58 -0
  10. package/dist/assets/type-extraction-cache.json +3 -3
  11. package/docs/array-fields.md +371 -0
  12. package/docs/conditional-logic.md +178 -0
  13. package/docs/convention-naming.md +102 -0
  14. package/docs/date-field.md +92 -0
  15. package/docs/dropdown-fields.md +879 -0
  16. package/docs/field-state.md +131 -0
  17. package/docs/field-types-overview.md +132 -0
  18. package/docs/formatting.md +421 -0
  19. package/docs/icons.md +142 -0
  20. package/docs/index.md +23 -0
  21. package/docs/jsonata-expressions.md +200 -0
  22. package/docs/media-fields.md +107 -0
  23. package/docs/overview.md +467 -0
  24. package/docs/pattern-build-deploy.md +91 -0
  25. package/docs/pattern-datasources.md +459 -0
  26. package/docs/pattern-forms.md +528 -0
  27. package/docs/pattern-global-actions.md +92 -0
  28. package/docs/pattern-javascript-functions.md +452 -0
  29. package/docs/pattern-navigation.md +304 -0
  30. package/docs/pattern-pdf-generation.md +391 -0
  31. package/docs/pattern-rest-acumatica.md +660 -0
  32. package/docs/pattern-sync-progress.md +96 -0
  33. package/docs/pattern-sync.md +653 -0
  34. package/docs/pattern-tabs-form.md +293 -0
  35. package/docs/recipe-index.md +64 -0
  36. package/docs/runtime-variables.md +127 -0
  37. package/docs/sections.md +81 -0
  38. package/docs/validation-patterns.md +150 -0
  39. package/package.json +3 -2
@@ -2617,6 +2617,8 @@ types:
2617
2617
  fileName: Expression|string
2618
2618
  /** HTML content to convert to PDF */
2619
2619
  html: Expression|string
2620
+ /** Optional page size, orientation, and margins. Defaults to letter portrait with no margins. */
2621
+ page?: PdfPageOptions
2620
2622
  }
2621
2623
  GoBackActionInput:
2622
2624
  domain: action
@@ -3707,11 +3709,13 @@ types:
3707
3709
  * @param options.fileName - PDF file name
3708
3710
  * @param options.html - HTML content
3709
3711
  * @param options.instanceId - Instance ID for tracking
3712
+ * @param options.page - Optional page size, orientation, and margins
3710
3713
  */
3711
3714
  generatePdf(options: {
3712
3715
  fileName: Expression|string
3713
3716
  html: Expression|string
3714
3717
  instanceId?: KegInstanceId|string
3718
+ page?: PdfPageOptions
3715
3719
  }): JaGeneratePdfBuilder
3716
3720
  /**
3717
3721
  * Create an open-app-settings action builder
@@ -9529,6 +9533,60 @@ types:
9529
9533
  type PathsData = Expression|string | {
9530
9534
  data: Expression|string
9531
9535
  }
9536
+ PdfPageMargin:
9537
+ domain: action
9538
+ code: |-
9539
+ /**
9540
+ * PDF page margin specification.
9541
+ *
9542
+ * - `'none'` — zero margins on all sides (default).
9543
+ * - `number` — uniform margin in PDF points on all sides (72pt = 1in).
9544
+ * - object — independent per-side margins in PDF points; omitted sides default to 0.
9545
+ */
9546
+ type PdfPageMargin = 'none' | number | {
9547
+ top?: number
9548
+ right?: number
9549
+ bottom?: number
9550
+ left?: number
9551
+ }
9552
+ PdfPageOptions:
9553
+ domain: action
9554
+ code: |-
9555
+ /**
9556
+ * PDF page configuration.
9557
+ *
9558
+ * The runtime constructs the physical page bounds from these values before the
9559
+ * HTML is rendered. CSS `@page` rules do not control the PDF page size — to
9560
+ * avoid overflow, match the body dimensions to the configured size in points.
9561
+ *
9562
+ * When `width` and `height` are both provided they override `size`/`orientation`.
9563
+ *
9564
+ * @see PdfPageSize
9565
+ * @see PdfPageOrientation
9566
+ * @see PdfPageMargin
9567
+ */
9568
+ interface PdfPageOptions {
9569
+ /** Named paper size. Default: `'letter'`. */
9570
+ size?: PdfPageSize
9571
+ /** Page orientation. Default: `'portrait'`. */
9572
+ orientation?: PdfPageOrientation
9573
+ /** Custom page width in PDF points (72pt = 1in). When set with `height`, overrides `size`/`orientation`. */
9574
+ width?: number
9575
+ /** Custom page height in PDF points (72pt = 1in). When set with `width`, overrides `size`/`orientation`. */
9576
+ height?: number
9577
+ /** Page margins. Default: `'none'`. */
9578
+ margin?: PdfPageMargin
9579
+ }
9580
+ PdfPageOrientation:
9581
+ domain: action
9582
+ code: |-
9583
+ /** PDF page orientation. */
9584
+ type PdfPageOrientation = 'portrait' | 'landscape'
9585
+ PdfPageSize:
9586
+ domain: action
9587
+ code: |-
9588
+ /** Named PDF paper sizes. */
9589
+ type PdfPageSize = 'letter' | 'legal' | 'tabloid' | 'a4' | 'a3'
9532
9590
  PieChartLayout:
9533
9591
  domain: component
9534
9592
  code: type PieChartLayout = 'pie' | 'arch'
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "sourceHash": "8b7a1f45e39c7460275edc85ed96a0ef",
4
- "timestamp": 1778660398642,
3
+ "sourceHash": "4054b27eb8116771508a3e3fbe66d3cf",
4
+ "timestamp": 1778854916954,
5
5
  "stats": {
6
6
  "inputFiles": 300,
7
- "extractionTimeMs": 10955
7
+ "extractionTimeMs": 11351
8
8
  }
9
9
  }
@@ -0,0 +1,371 @@
1
+ ## Basic Setup
2
+
3
+ Create an array field and set its required title:
4
+
5
+ ```typescript
6
+ const members = step.addArrayField({ name: 'members', label: 'Team Members' })
7
+ // Title is required - identifies items in list
8
+ members.with({
9
+ title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('name') }),
10
+ })
11
+ // Add fields to each item
12
+ members.addText({ name: 'name', label: 'Name' })
13
+ members.addEmail({ name: 'email', label: 'Email' })
14
+ ```
15
+
16
+ ## getCurrentItemRef() - Item Field References
17
+
18
+ Reference fields within the current array item using `getCurrentItemRef()`:
19
+
20
+ ```typescript
21
+ const members = step.addArrayField({ name: 'members', label: 'Members' })
22
+ // Single field reference
23
+ members.with({
24
+ title: new JsonataBuilder('$n', { n: members.getCurrentItemRef('name') }),
25
+ })
26
+ // Combine multiple fields
27
+ members.with({
28
+ subtitle: new JsonataBuilder('$first & " " & $last', {
29
+ first: members.getCurrentItemRef('firstName'),
30
+ last: members.getCurrentItemRef('lastName'),
31
+ }),
32
+ })
33
+ members.addText({ name: 'name', label: 'Name' })
34
+ members.addText({ name: 'firstName', label: 'First Name' })
35
+ members.addText({ name: 'lastName', label: 'Last Name' })
36
+ ```
37
+
38
+ ## getCurrentItemLabelRef() - Dropdown Labels
39
+
40
+ For dropdown fields inside arrayField, use `getCurrentItemLabelRef()` to display the human-readable label instead of the value id:
41
+
42
+ ```typescript
43
+ const items = step.addArrayField({
44
+ name: 'orderItems',
45
+ label: 'Order Items',
46
+ })
47
+ // Display dropdown LABEL (e.g., "Electronics") in item title
48
+ // instead of dropdown VALUE (e.g., "electronics")
49
+ items.with({
50
+ title: new JsonataBuilder('$product & " - " & $categoryLabel', {
51
+ product: items.getCurrentItemRef('productName'),
52
+ categoryLabel: items.getCurrentItemLabelRef('category'),
53
+ }),
54
+ })
55
+ items.addText({ name: 'productName', label: 'Product Name' })
56
+ items.addDropdown({
57
+ name: 'category',
58
+ label: 'Category',
59
+ data: [
60
+ { label: 'Electronics', value: 'electronics' },
61
+ { label: 'Clothing', value: 'clothing' },
62
+ { label: 'Home & Garden', value: 'home' },
63
+ ],
64
+ })
65
+ items.addNumber({ name: 'quantity', label: 'Quantity' })
66
+ ```
67
+
68
+ | Method | Returns | Use Case |
69
+ | --- | --- | --- |
70
+ | `arrayField.getCurrentItemRef('dropdown')` | Value (id) | Business logic |
71
+ | `arrayField.getCurrentItemLabelRef('dropdown')` | Label (text) | Display in title/subtitle |
72
+
73
+ ## Item Display
74
+
75
+ Customize how items appear in the list:
76
+
77
+ ```typescript
78
+ const items = step.addArrayField({ name: 'items', label: 'Order Items' })
79
+ // Required title
80
+ items.with({
81
+ title: new JsonataBuilder('$name', { name: items.getCurrentItemRef('productName') }),
82
+ })
83
+ // Optional subtitle with formatted currency
84
+ items.with({
85
+ subtitle: new I18nBuilder('item.subtitle', 'Qty: {qty} × {price}', {
86
+ qty: new JsonataBuilder('$qty', {
87
+ qty: items.getCurrentItemRef('quantity'),
88
+ }),
89
+ price: new FormatBuilder(
90
+ new JsonataBuilder('$price', {
91
+ price: items.getCurrentItemRef('unitPrice'),
92
+ }),
93
+ { numberStyle: 'currency', currency: 'USD' },
94
+ ),
95
+ }),
96
+ })
97
+ // Optional description
98
+ items.with({
99
+ description: new JsonataBuilder('$notes', { notes: items.getCurrentItemRef('notes') }),
100
+ })
101
+ items.addText({ name: 'productName', label: 'Product Name' })
102
+ items.addNumber({ name: 'quantity', label: 'Quantity' })
103
+ items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
104
+ items.addText({ name: 'notes', label: 'Notes' })
105
+ ```
106
+
107
+ ## Nested Fields
108
+
109
+ Array fields support all standard field types:
110
+
111
+ ```typescript
112
+ const items = step.addArrayField({ name: 'items', label: 'Line Items' })
113
+ items.with({
114
+ title: new JsonataBuilder('$p', { p: items.getCurrentItemRef('product') }),
115
+ })
116
+ items.addText({ name: 'product', label: 'Product Name' })
117
+ items.addNumber({ name: 'quantity', label: 'Quantity' })
118
+ items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
119
+ items.addDropdown({
120
+ name: 'category',
121
+ label: 'Category',
122
+ data: [
123
+ { label: 'Electronics', value: 'electronics' },
124
+ { label: 'Clothing', value: 'clothing' },
125
+ ],
126
+ })
127
+ ```
128
+
129
+ ## Sections Within Array Fields
130
+
131
+ Group related fields within array items:
132
+
133
+ ```typescript
134
+ const items = step.addArrayField({
135
+ name: 'items',
136
+ label: 'Order Items',
137
+ addButtonLabel: 'Add Item',
138
+ })
139
+ // Set title for array items - need to use .with() since we need the variable declared first
140
+ items.with({
141
+ title: new JsonataBuilder('$productName', {
142
+ productName: items.getCurrentItemRef('product-name'),
143
+ }),
144
+ })
145
+ const section = items.addSection()
146
+ section.with({ title: 'Product Details' })
147
+ section.addText({ name: 'product-name', label: 'Product' })
148
+ section.addNumber({ name: 'quantity', label: 'Quantity' })
149
+ ```
150
+
151
+ ## Configuration Options
152
+
153
+ Customize array behavior:
154
+
155
+ ```typescript
156
+ const tasks = step.addArrayField({
157
+ name: 'tasks',
158
+ label: 'Tasks',
159
+ addButtonLabel: 'Add Task', // Custom button text
160
+ })
161
+ tasks.with({
162
+ title: new JsonataBuilder('$t', { t: tasks.getCurrentItemRef('taskName') }),
163
+ })
164
+ tasks.addText({ name: 'taskName', label: 'Task Name' })
165
+ ```
166
+
167
+ ## Conditional Visibility
168
+
169
+ Show/hide array field based on conditions:
170
+
171
+ ```typescript
172
+ const hasTeam = step.addCheckbox({
173
+ name: 'has-team',
174
+ label: 'This project has a team',
175
+ })
176
+ const teamMembers = step.addArrayField({
177
+ name: 'team-members',
178
+ label: 'Team Members',
179
+ addButtonLabel: 'Add Team Member',
180
+ // Option 1: Set title in constructor
181
+ title: 'Team Member',
182
+ })
183
+ // Option 2: Set title via .with()
184
+ teamMembers.with({
185
+ // Can't use in constructor because the variable doesn't exist yet
186
+ title: new JsonataBuilder('$name', { name: teamMembers.getCurrentItemRef('member-name') }),
187
+ // Use Jsonata expression to show array field only when checkbox is checked
188
+ isVisible: new JsonataBuilder('$hasTeam = true', { hasTeam: hasTeam.state.value }),
189
+ })
190
+ teamMembers.addText({
191
+ name: 'member-name',
192
+ label: 'Name',
193
+ })
194
+ teamMembers.addEmail({
195
+ name: 'member-email',
196
+ label: 'Email',
197
+ })
198
+ ```
199
+
200
+ ## Formatting in Array Item title / subtitle / description
201
+
202
+ **Date fields MUST use `FormatBuilder`** in arrayField `title`, `subtitle`, and `description`. Without it, unreadable ISO 8601 strings like `"2024-01-15T14:30:00Z"` display instead of human-readable dates.
203
+
204
+ **Number/currency fields** should also use `FormatBuilder` for locale-aware display instead of JSONata string concatenation.
205
+
206
+ **Ref:** `./formatting.md` for `FormatBuilder` usage, date format codes, and common mistakes
207
+
208
+ ## Retrieving Array Data
209
+
210
+ Access array items from other steps using `getArrayFieldData()`:
211
+
212
+ ```typescript
213
+ // Step 1: Collect team members
214
+ const step1 = form.addStep({ instanceId: 'team' }, (step) => {
215
+ const members = step.addArrayField({
216
+ name: 'members',
217
+ label: 'Team Members',
218
+ })
219
+ members.with({
220
+ title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('name') }),
221
+ })
222
+ members.addText({ name: 'name', label: 'Name' })
223
+ members.addText({ name: 'role', label: 'Role' })
224
+ })
225
+ step1.with({ icon: 'multiple-users-3', title: 'Team Members' })
226
+ // Step 2: Select team lead from collected members
227
+ const step2 = form.addStep({ instanceId: 'lead' }, (step) => {
228
+ step.addDropdown({
229
+ name: 'teamLead',
230
+ label: 'Select Team Lead',
231
+ // IMPORTANT: Use getArrayFieldData to map arrayField items to dropdown options
232
+ data: step1.data.getArrayFieldData('members'),
233
+ })
234
+ })
235
+ step2.with({ icon: 'messages-people-user-check', title: 'Select Lead' })
236
+ ```
237
+
238
+ **Custom mapping:**
239
+
240
+ ```typescript
241
+ const step1 = form.addStep({ instanceId: 'step1' }, (step) => {
242
+ const items = step.addArrayField({ name: 'items', label: 'Items', title: 'Item' })
243
+ items.addText({ name: 'code', label: 'Code' })
244
+ items.addText({ name: 'description', label: 'Description' })
245
+ })
246
+ step1.with({ icon: 'list', title: 'Items' })
247
+ const step2 = form.addStep({ instanceId: 'step2' }, (step) => {
248
+ step.addDropdown({
249
+ name: 'selectedItem',
250
+ label: 'Select Item',
251
+ // IMPORTANT: Use getArrayFieldData to map arrayField items to dropdown options
252
+ data: step1.data.getArrayFieldData('items', {
253
+ valueKey: 'code',
254
+ labelKey: 'description',
255
+ }),
256
+ })
257
+ })
258
+ step2.with({ icon: 'check', title: 'Select' })
259
+ ```
260
+
261
+ ## Data Structure
262
+
263
+ Each array item has this structure:
264
+
265
+ | Property | Description |
266
+ | --- | --- |
267
+ | `id` | Auto-generated UUID (used as default dropdown value) |
268
+ | `item_title` | Captured from title expression (used as default dropdown label) |
269
+ | `<fieldName>` | Values from each nested field |
270
+
271
+ **item_title mechanism:** A hidden field automatically captures the evaluated title expression when saving. This enables dropdown compatibility - when you use `getArrayFieldData()`, the `item_title` is available as the default label without re-evaluating the expression.
272
+
273
+ ```typescript
274
+ // Example data structure after user adds items:
275
+ [
276
+ { id: "uuid-1", item_title: "John Doe", name: "John Doe", email: "john@example.com" },
277
+ { id: "uuid-2", item_title: "Jane Smith", name: "Jane Smith", email: "jane@example.com" }
278
+ ]
279
+ ```
280
+
281
+ ## Use Case: Team Members
282
+
283
+ Classic pattern for collecting people with name/email:
284
+
285
+ ```typescript
286
+ const members = step.addArrayField({
287
+ name: 'teamMembers',
288
+ label: 'Team Members',
289
+ addButtonLabel: 'Add Member',
290
+ })
291
+ members.with({
292
+ title: new JsonataBuilder('$name', { name: members.getCurrentItemRef('fullName') }),
293
+ })
294
+ members.with({
295
+ subtitle: new JsonataBuilder('$role', { role: members.getCurrentItemRef('role') }),
296
+ })
297
+ members.addText({ name: 'fullName', label: 'Full Name' })
298
+ members.addEmail({ name: 'email', label: 'Email' })
299
+ members.addDropdown({
300
+ name: 'role',
301
+ label: 'Role',
302
+ data: [
303
+ { label: 'Developer', value: 'developer' },
304
+ { label: 'Designer', value: 'designer' },
305
+ { label: 'Manager', value: 'manager' },
306
+ ],
307
+ })
308
+ ```
309
+
310
+ ## Use Case: Line Items
311
+
312
+ Invoice or order items with quantities:
313
+
314
+ ```typescript
315
+ const items = step.addArrayField({
316
+ name: 'lineItems',
317
+ label: 'Order Items',
318
+ })
319
+ items.with({
320
+ title: new JsonataBuilder('$product', {
321
+ product: items.getCurrentItemRef('productName'),
322
+ }),
323
+ })
324
+ items.with({
325
+ subtitle: new I18nBuilder('line-item.subtitle', 'Qty: {qty} × {price}', {
326
+ qty: new JsonataBuilder('$qty', {
327
+ qty: items.getCurrentItemRef('quantity'),
328
+ }),
329
+ price: new FormatBuilder(
330
+ new JsonataBuilder('$price', {
331
+ price: items.getCurrentItemRef('unitPrice'),
332
+ }),
333
+ { numberStyle: 'currency', currency: 'USD' },
334
+ ),
335
+ }),
336
+ })
337
+ items.addText({ name: 'productName', label: 'Product' })
338
+ items.addNumber({ name: 'quantity', label: 'Quantity' })
339
+ items.addNumber({ name: 'unitPrice', label: 'Unit Price' })
340
+ ```
341
+
342
+ ## Use Case: Inspection Checklist
343
+
344
+ Status tracking with notes:
345
+
346
+ ```typescript
347
+ const checks = step.addArrayField({
348
+ name: 'inspectionItems',
349
+ label: 'Inspection Checklist',
350
+ })
351
+ checks.with({
352
+ title: new JsonataBuilder('$item', { item: checks.getCurrentItemRef('checkItem') }),
353
+ })
354
+ checks.with({
355
+ subtitle: new JsonataBuilder('$uppercase($status)', {
356
+ status: checks.getCurrentItemRef('status'),
357
+ }),
358
+ })
359
+ checks.addText({ name: 'checkItem', label: 'Item to Check' })
360
+ checks.addDropdown({
361
+ name: 'status',
362
+ label: 'Status',
363
+ data: [
364
+ { label: 'Pass', value: 'pass', icon: 'check-circle-1' },
365
+ { label: 'Fail', value: 'fail', icon: 'remove-circle' },
366
+ { label: 'N/A', value: 'na', icon: 'remove-circle-1' },
367
+ ],
368
+ })
369
+ checks.addText({ name: 'notes', label: 'Notes', isMultiline: true })
370
+ checks.addMedia({ name: 'photo', label: 'Photo Evidence' })
371
+ ```
@@ -0,0 +1,178 @@
1
+ ## Basic Conditional Visibility
2
+
3
+ ```typescript
4
+ const attendanceType = step.addDropdown({
5
+ name: 'attendance',
6
+ label: 'Attendance Type',
7
+ data: [
8
+ { label: 'In Person', value: 'in-person' },
9
+ { label: 'Virtual', value: 'virtual' },
10
+ ],
11
+ })
12
+ // Show only for in-person attendance
13
+ step.addText({
14
+ name: 'hotel-preferences',
15
+ label: 'Hotel Preferences',
16
+ isVisible: new JsonataBuilder('$attendance = "in-person"', {
17
+ attendance: attendanceType.state.value,
18
+ }),
19
+ })
20
+ ```
21
+
22
+ ## Conditional Required
23
+
24
+ Often combine with visibility - if a field is shown, it should be required:
25
+
26
+ ```typescript
27
+ const attendanceType = step.addDropdown({
28
+ name: 'attendance',
29
+ label: 'Attendance Type',
30
+ data: [
31
+ { label: 'In Person', value: 'in-person' },
32
+ { label: 'Virtual', value: 'virtual' },
33
+ ],
34
+ })
35
+ const visibilityCondition = new JsonataBuilder('$attendance = "in-person"', {
36
+ attendance: attendanceType.state.value,
37
+ })
38
+ // Show and require together using same condition
39
+ step.addText({
40
+ name: 'hotel-preferences',
41
+ label: 'Hotel Preferences',
42
+ isVisible: visibilityCondition,
43
+ isRequired: new JsonataBuilder('$attendance = "in-person"', {
44
+ attendance: attendanceType.state.value,
45
+ }),
46
+ })
47
+ ```
48
+
49
+ **Best practice:** Apply same condition to both `isVisible` and `isRequired`.
50
+
51
+ ## Multiple Conditions
52
+
53
+ Combine conditions with `and`/`or`:
54
+
55
+ ```typescript
56
+ const attendance = step.addDropdown({
57
+ name: 'attendance',
58
+ label: 'Attendance',
59
+ data: [
60
+ { label: 'In Person', value: 'in-person' },
61
+ { label: 'Virtual', value: 'virtual' },
62
+ ],
63
+ })
64
+ const needsAccommodation = step.addCheckbox({
65
+ name: 'needs-accommodation',
66
+ label: 'I need accommodation',
67
+ })
68
+ // Show when BOTH conditions are true
69
+ step.addText({
70
+ name: 'hotel-preferences',
71
+ label: 'Hotel Preferences',
72
+ isVisible: new JsonataBuilder('$attendance = "in-person" and $needsAccommodation = true', {
73
+ attendance: attendance.state.value,
74
+ needsAccommodation: needsAccommodation.state.value,
75
+ }),
76
+ })
77
+ ```
78
+
79
+ ## Either/Or Conditions
80
+
81
+ ```typescript
82
+ const email = step.addEmail({
83
+ name: 'email',
84
+ label: 'Email',
85
+ })
86
+ const phone = step.addPhone({
87
+ name: 'phone',
88
+ label: 'Phone',
89
+ isRequired: new JsonataBuilder('$email = null or $email = ""', {
90
+ email: email.state.value,
91
+ }),
92
+ })
93
+ email.with({
94
+ isRequired: new JsonataBuilder('$phone = null or $phone = ""', {
95
+ phone: phone.state.value,
96
+ }),
97
+ })
98
+ ```
99
+
100
+ ## Cross-Step Conditions
101
+
102
+ For conditions based on other steps, use `step.data.getFieldData()`:
103
+
104
+ ```typescript
105
+ const step1 = form.addStep({ instanceId: 'type' }, (step) => {
106
+ step.addDropdown({
107
+ name: 'account-type',
108
+ label: 'Account Type',
109
+ data: [
110
+ { label: 'Personal', value: 'personal' },
111
+ { label: 'Business', value: 'business' },
112
+ ],
113
+ })
114
+ })
115
+ step1.with({ icon: 'monitor-user', title: 'Account Type' })
116
+ form.addStep({ instanceId: 'business', icon: 'small-office-building' }, (step) => {
117
+ // Required only for business accounts
118
+ step.addText({
119
+ name: 'tax-id',
120
+ label: 'Tax ID',
121
+ isRequired: new JsonataBuilder('$type = "business"', {
122
+ type: step1.data.getFieldData('account-type'),
123
+ }),
124
+ })
125
+ })
126
+ ```
127
+
128
+ **Ref:** `./cross-step-data.md` for more on `getFieldData()`
129
+
130
+ ## Conditional Disabled
131
+
132
+ Make fields read-only based on conditions:
133
+
134
+ ```typescript
135
+ const status = step.addDropdown({
136
+ name: 'status',
137
+ label: 'Status',
138
+ data: [
139
+ { label: 'Draft', value: 'draft' },
140
+ { label: 'Submitted', value: 'submitted' },
141
+ ],
142
+ })
143
+ // Can't edit title after submission
144
+ step.addText({
145
+ name: 'title',
146
+ label: 'Title',
147
+ isDisabled: new JsonataBuilder('$status = "submitted"', {
148
+ status: status.state.value,
149
+ }),
150
+ })
151
+ ```
152
+
153
+ ## Updating Conditions with .with()
154
+
155
+ ```typescript
156
+ const toggleField = step.addCheckbox({
157
+ name: 'show-notes',
158
+ label: 'Add Notes',
159
+ })
160
+ const field = step.addText({ name: 'notes', label: 'Notes' })
161
+ // Add conditions after construction
162
+ field.with({
163
+ isVisible: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
164
+ isRequired: new JsonataBuilder('$show = true', { show: toggleField.state.value }),
165
+ })
166
+ ```
167
+
168
+ ## JSONata Operators
169
+
170
+ | Operator | Meaning | Example |
171
+ | --- | --- | --- |
172
+ | `=` | Equals | `$status = "active"` |
173
+ | `!=` | Not equals | `$status != "inactive"` |
174
+ | `and` | Both true | `$a = 1 and $b = 2` |
175
+ | `or` | Either true | `$a = 1 or $b = 2` |
176
+ | `>`, `<`, `>=`, `<=` | Comparison | `$age >= 18` |
177
+
178
+ **Note:** Use single `=` for equality (not `==`).