@cxtms/cx-schema 1.9.67 → 1.9.241

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 (81) hide show
  1. package/dist/cli.js +43 -9
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
  4. package/schemas/actions/all.json +5 -1
  5. package/schemas/actions/reload.json +17 -0
  6. package/schemas/actions/setSessionStorage.json +38 -0
  7. package/schemas/actions/sound.json +39 -0
  8. package/schemas/actions/vibrate.json +48 -0
  9. package/schemas/components/avatar.json +1 -0
  10. package/schemas/components/badge.json +79 -0
  11. package/schemas/components/barcodeScanner.json +28 -0
  12. package/schemas/components/camera.json +177 -0
  13. package/schemas/components/card.json +78 -0
  14. package/schemas/components/dashboard-widget.json +39 -0
  15. package/schemas/components/dashboard.json +46 -0
  16. package/schemas/components/dataGrid.json +15 -0
  17. package/schemas/components/field-collection.json +86 -2
  18. package/schemas/components/field.json +39 -4
  19. package/schemas/components/form.json +4 -0
  20. package/schemas/components/index.json +17 -1
  21. package/schemas/components/infoLine.json +1 -0
  22. package/schemas/components/map.json +167 -0
  23. package/schemas/components/module.json +32 -0
  24. package/schemas/components/planner.json +1 -0
  25. package/schemas/components/progressBar.json +1 -0
  26. package/schemas/components/timeline.json +216 -74
  27. package/schemas/components/timelineGrid.json +5 -1
  28. package/schemas/fields/autocomplete-googleplaces.json +2 -2
  29. package/schemas/fields/datetime.json +2 -2
  30. package/schemas/fields/index.json +4 -0
  31. package/schemas/fields/number-select.json +88 -0
  32. package/schemas/fields/rangedatetime.json +10 -2
  33. package/schemas/fields/select-async.json +2 -2
  34. package/schemas/fields/toggle.json +91 -0
  35. package/schemas/schema.graphql +11016 -12171
  36. package/schemas/schemas.json +131 -4
  37. package/schemas/workflows/flow/entity.json +3 -1
  38. package/schemas/workflows/input.json +1 -1
  39. package/schemas/workflows/output.json +1 -1
  40. package/schemas/workflows/tasks/all.json +9 -0
  41. package/schemas/workflows/tasks/commodity.json +8 -0
  42. package/schemas/workflows/tasks/contact-address.json +25 -0
  43. package/schemas/workflows/tasks/dispatch-routes.json +64 -0
  44. package/schemas/workflows/tasks/edi.json +15 -0
  45. package/schemas/workflows/tasks/export.json +21 -0
  46. package/schemas/workflows/tasks/httpRequest.json +4 -0
  47. package/schemas/workflows/tasks/import.json +5 -0
  48. package/schemas/workflows/tasks/order-move.json +38 -0
  49. package/schemas/workflows/tasks/order.json +6 -0
  50. package/schemas/workflows/tasks/organization-config.json +101 -0
  51. package/schemas/workflows/tasks/tracking-event.json +11 -3
  52. package/schemas/workflows/tasks/workflow-execute.json +4 -0
  53. package/schemas/workflows/variable.json +2 -2
  54. package/schemas/workflows/workflow.json +39 -2
  55. package/skills/cxtms-developer/SKILL.md +5 -2
  56. package/skills/cxtms-developer/ref-cli-auth.md +2 -0
  57. package/skills/cxtms-developer/ref-entity-commodity.md +41 -5
  58. package/skills/cxtms-developer/ref-entity-contact.md +46 -0
  59. package/skills/cxtms-developer/ref-entity-dispatch-routing.md +195 -0
  60. package/skills/cxtms-developer/ref-entity-geography.md +26 -1
  61. package/skills/cxtms-developer/ref-entity-job.md +6 -2
  62. package/skills/cxtms-developer/ref-entity-order-sub.md +1 -1
  63. package/skills/cxtms-developer/ref-entity-order.md +36 -3
  64. package/skills/cxtms-developer/ref-entity-organization.md +4 -2
  65. package/skills/cxtms-developer/ref-entity-shared.md +79 -3
  66. package/skills/cxtms-developer/ref-graphql-query.md +246 -4
  67. package/skills/cxtms-module-builder/SKILL.md +33 -6
  68. package/skills/cxtms-module-builder/ref-components-data.md +85 -0
  69. package/skills/cxtms-module-builder/ref-components-display.md +282 -8
  70. package/skills/cxtms-module-builder/ref-components-forms.md +386 -21
  71. package/skills/cxtms-module-builder/ref-components-interactive.md +2 -0
  72. package/skills/cxtms-module-builder/ref-components-layout.md +288 -29
  73. package/skills/cxtms-module-builder/ref-components-specialized.md +168 -20
  74. package/skills/cxtms-workflow-builder/SKILL.md +45 -6
  75. package/skills/cxtms-workflow-builder/ref-entity.md +100 -4
  76. package/skills/cxtms-workflow-builder/ref-expressions-ncalc.md +35 -2
  77. package/skills/cxtms-workflow-builder/ref-expressions-template.md +15 -1
  78. package/skills/cxtms-workflow-builder/ref-flow.md +6 -1
  79. package/skills/cxtms-workflow-builder/ref-query.md +16 -0
  80. package/skills/cxtms-workflow-builder/ref-utilities.md +32 -2
  81. package/templates/workflow-public-api.yaml +1 -0
@@ -1,11 +1,217 @@
1
1
  # Form & Input Components
2
2
 
3
3
  ## Contents
4
+ - Mobile-friendly form layout (adaptive design)
4
5
  - Form component
5
6
  - Field component
6
7
  - FieldCollection component
7
8
  - BarcodeScanner component
8
9
 
10
+ ---
11
+
12
+ ## Mobile-Friendly Form Layout
13
+
14
+ > See **`ref-components-layout.md` → Adaptive / Responsive Design** for breakpoint definitions and the full responsive prop reference. This section covers form-specific patterns.
15
+
16
+ `form` itself is a flexbox column — children stack top-to-bottom with no horizontal flow. To create responsive multi-column forms (one column on mobile, two on tablet, etc.), wrap fields in a `layout` child and use **modern MUI Grid v2 spacing** (`columnSpacing` + `rowSpacing`, both responsive) plus `itemDefaults.size`.
17
+
18
+ > **Do NOT** use `cols: N`, the `row` component, or CSS `className`s for form layout. Use `layout` + `size` + `columnSpacing`/`rowSpacing` + `sx` only.
19
+
20
+ ### Pattern 1 — Responsive 2-column form (1 column on mobile)
21
+
22
+ ```yaml
23
+ component: form
24
+ name: contactForm
25
+ props:
26
+ validationSchema:
27
+ firstName: { type: string, required: true }
28
+ lastName: { type: string, required: true }
29
+ email: { type: string, required: true }
30
+ children:
31
+ - component: layout
32
+ name: contactFields
33
+ props:
34
+ columnSpacing: { xs: 1, md: 2 } # tighter horizontal gap on mobile
35
+ rowSpacing: { xs: 2, md: 3 } # comfortable vertical breathing room
36
+ itemDefaults:
37
+ size: { xs: 12, md: 6 } # 1 col mobile, 2 col tablet+
38
+ children:
39
+ - component: field
40
+ name: firstName
41
+ props: { type: text, label: { en-US: "First Name" } }
42
+ - component: field
43
+ name: lastName
44
+ props: { type: text, label: { en-US: "Last Name" } }
45
+ - component: field
46
+ name: email
47
+ props:
48
+ type: email
49
+ label: { en-US: "Email" }
50
+ size: { xs: 12 } # always full width
51
+ - component: field
52
+ name: notes
53
+ props:
54
+ type: textarea
55
+ label: { en-US: "Notes" }
56
+ rows: 4
57
+ size: { xs: 12 } # textareas always full width
58
+ ```
59
+
60
+ ### Pattern 2 — Three-tier form (1 / 2 / 3 columns)
61
+
62
+ ```yaml
63
+ component: form
64
+ name: productForm
65
+ props:
66
+ validationSchema:
67
+ sku: { type: string, required: true }
68
+ children:
69
+ - component: layout
70
+ props:
71
+ columnSpacing: { xs: 1, sm: 2, md: 3 }
72
+ rowSpacing: { xs: 2, md: 3 }
73
+ itemDefaults:
74
+ size: { xs: 12, sm: 6, lg: 4 } # phone: 1 col, tablet: 2 col, desktop: 3 col
75
+ children:
76
+ - component: field
77
+ name: sku
78
+ props: { type: text, label: { en-US: "SKU" } }
79
+ - component: field
80
+ name: name
81
+ props: { type: text, label: { en-US: "Name" } }
82
+ - component: field
83
+ name: price
84
+ props: { type: number, label: { en-US: "Price" } }
85
+ - component: field
86
+ name: weight
87
+ props: { type: number, label: { en-US: "Weight" } }
88
+ - component: field
89
+ name: status
90
+ props: { type: select, label: { en-US: "Status" } }
91
+ - component: field
92
+ name: category
93
+ props: { type: select, label: { en-US: "Category" } }
94
+ ```
95
+
96
+ ### Pattern 3 — Section cards that reflow
97
+
98
+ Group related fields into cards. The cards stack on mobile and sit side-by-side on desktop.
99
+
100
+ ```yaml
101
+ component: form
102
+ name: orderForm
103
+ props:
104
+ validationSchema: { orderNumber: { type: string, required: true } }
105
+ children:
106
+ - component: layout
107
+ props:
108
+ spacing: { xs: 2, md: 3 } # uniform gap between cards
109
+ itemDefaults:
110
+ size: { xs: 12, lg: 6 } # cards stack on phone/tablet, split on desktop
111
+ children:
112
+ - component: card
113
+ name: customerCard
114
+ props:
115
+ options:
116
+ variant: outlined
117
+ header: { title: "Customer" }
118
+ children:
119
+ - component: layout
120
+ props:
121
+ columnSpacing: { xs: 1, md: 2 }
122
+ rowSpacing: 2
123
+ itemDefaults:
124
+ size: { xs: 12, sm: 6 }
125
+ children:
126
+ - component: field
127
+ name: customerId
128
+ props: { type: select-async, label: { en-US: "Customer" } }
129
+ - component: field
130
+ name: poNumber
131
+ props: { type: text, label: { en-US: "PO #" } }
132
+ - component: card
133
+ name: shippingCard
134
+ props:
135
+ options:
136
+ variant: outlined
137
+ header: { title: "Shipping" }
138
+ children:
139
+ - component: layout
140
+ props:
141
+ columnSpacing: { xs: 1, md: 2 }
142
+ rowSpacing: 2
143
+ itemDefaults:
144
+ size: { xs: 12, sm: 6 }
145
+ children:
146
+ - component: field
147
+ name: shipDate
148
+ props: { type: date, label: { en-US: "Ship Date" } }
149
+ - component: field
150
+ name: carrier
151
+ props: { type: select, label: { en-US: "Carrier" } }
152
+ ```
153
+
154
+ ### Pattern 4 — Paired fields that wrap together
155
+
156
+ Fields that belong together (qty + uom, from + to dates, address city/state/zip) should share breakpoints so they always wrap as a unit. Use a responsive `columns` total to pack 3 short fields into a single mobile row.
157
+
158
+ ```yaml
159
+ - component: layout
160
+ props:
161
+ columns: { xs: 12, md: 12 }
162
+ columnSpacing: { xs: 1, md: 2 }
163
+ rowSpacing: 2
164
+ children:
165
+ - component: field
166
+ name: quantity
167
+ props: { type: number, label: { en-US: "Qty" }, size: { xs: 4 } }
168
+ - component: field
169
+ name: uom
170
+ props: { type: select, label: { en-US: "UOM" }, size: { xs: 4 } }
171
+ - component: field
172
+ name: weight
173
+ props: { type: number, label: { en-US: "Weight" }, size: { xs: 4 } }
174
+ ```
175
+
176
+ ### Pattern 5 — Toolbar that adapts
177
+
178
+ ```yaml
179
+ - component: layout
180
+ name: formActions
181
+ props:
182
+ columnSpacing: { xs: 1, md: 2 }
183
+ sx: { justifyContent: { xs: 'space-between', md: 'flex-end' } }
184
+ children:
185
+ - component: button
186
+ name: cancel
187
+ props: { label: { en-US: "Cancel" }, variant: outlined }
188
+ - component: button
189
+ name: save
190
+ props: { label: { en-US: "Save" }, options: { type: submit, variant: primary } }
191
+ ```
192
+
193
+ ### Form mobile checklist
194
+
195
+ - **Default to 1 column on `xs`** — never assume horizontal space. Use `itemDefaults.size: { xs: 12, ... }`.
196
+ - **2 columns on `md+`** for paired short fields (firstName/lastName, qty/uom, from/to).
197
+ - **Long fields full-width**: `textarea`, `quill`, `attachment`, `notes`, multi-line inputs → `size: { xs: 12 }`.
198
+ - **Cap visible columns**: max 2 on `sm`, max 3 on `md`, max 4 on `lg`. More than that overcrowds.
199
+ - **Group with cards** when a form has 3+ logical sections — gives clear visual breaks on mobile and reflow targets on desktop.
200
+ - **Use the modern Grid spacing pattern**: `columnSpacing` + `rowSpacing` (responsive maps) over the legacy single-number `spacing`. Tighter columns on phones, comfortable rows always.
201
+ - **Toolbar on phones**: prefer `space-between` (cancel left, save right) so primary action stays in the thumb zone. Switch to `flex-end` on `md+`.
202
+ - **Validation messages** appear under the field — make sure `helperText` length doesn't break tight 2-col `sm` layouts.
203
+ - **Tap targets ≥ 44px**: don't set `size: small` on primary buttons / interactive fields on `xs`.
204
+
205
+ ### What NOT to do in form layouts
206
+
207
+ - ❌ `cols: N` on `form` (or anywhere) — non-responsive, ignored on mobile.
208
+ - ❌ `row` component for new layouts — use `layout`.
209
+ - ❌ `className: "row"` / `className: "col-md-6"` / any Bootstrap-style class — these don't affect MUI Grid sizing. Use `size` + `columns` instead.
210
+ - ❌ Custom CSS classes on `layout` / `row` / `col` for sizing or spacing — those props belong on `size`, `spacing`, `rowSpacing`, `columnSpacing`, `sx`, or `containerSx`.
211
+ - ❌ Mixing `spacing` with explicit `rowSpacing` AND `columnSpacing` (the explicit ones override).
212
+
213
+ ---
214
+
9
215
  ## form
10
216
 
11
217
  Data entry form with validation, queries, and submission. Wraps React Hook Form's FormProvider.
@@ -21,6 +227,7 @@ Data entry form with validation, queries, and submission. Wraps React Hook Form'
21
227
  | `queries` | `QueryDef[]` | GraphQL queries for data loading |
22
228
  | `prefix` | `string` | Field name namespace prefix |
23
229
  | `refreshHandler` | `string` | Remount on refresh event |
230
+ | `reloadHandler` | `string` | Refetch target; falls back to `refreshHandler`, then form name |
24
231
  | `dirtyGuard` | `DirtyGuardProps` | Unsaved changes protection |
25
232
  | `dirtyGuard.enabled` | `boolean` | Enable guard |
26
233
  | `dirtyGuard.title` | `ILocalizeString` | Dialog title |
@@ -31,7 +238,7 @@ Data entry form with validation, queries, and submission. Wraps React Hook Form'
31
238
  | `resetOnSubmit` | `boolean` | Reset form after submit |
32
239
  | `preventDefault` | `boolean` | Prevent default form submit |
33
240
  | `toolbar` | `component[]` | Toolbar components |
34
- | `cols` | `number` | Column layout for children |
241
+ | ~~`cols: N`~~ | _legacy_ | **Do not use.** Non-responsive. Wrap children in a `layout` with `itemDefaults.size: { xs: 12, md: 6 }` instead. See "Mobile-Friendly Form Layout" above. |
35
242
  | `orientation` | `string` | Layout orientation |
36
243
 
37
244
  **Events:**
@@ -46,6 +253,10 @@ Data entry form with validation, queries, and submission. Wraps React Hook Form'
46
253
 
47
254
  **Children:** Yes — typically `field` components. Provides `formName` and `createMode` in variables.
48
255
 
256
+ Named forms expose `{{<formName>.dirty}}` in action templates. The value compares current fields with the last loaded `initialValues`, treats `null`, `undefined`, and `''` as equivalent empty values, and resets after reload. It is not available to component visibility or disabled templates.
257
+
258
+ Use `- reload: orderForm` to refetch and replace a mounted form's `initialValues` without remounting its children. Reload discards unsaved edits; `refresh` keeps current values and only recomputes the component. Multiple reload targets are supported as an array. Dialog `onClose` actions can read values written inside the dialog as well as the caller store, allowing reloads to be conditional.
259
+
49
260
  ```yaml
50
261
  component: form
51
262
  name: orderForm
@@ -98,17 +309,25 @@ props:
98
309
  - notification: { message: { en-US: "Saved!" }, type: success }
99
310
  - navigateBack: { fallback: "/orders" }
100
311
  children:
101
- - component: field
102
- name: orderNumber
103
- props: { type: text, label: { en-US: "Order Number" }, required: true }
104
- - component: field
105
- name: status
312
+ - component: layout
313
+ name: orderFormFields
106
314
  props:
107
- type: select
108
- label: { en-US: "Status" }
109
- items:
110
- - { label: "Draft", value: "Draft" }
111
- - { label: "Active", value: "Active" }
315
+ columnSpacing: { xs: 1, md: 2 } # tighter on mobile, breathing room on desktop
316
+ rowSpacing: { xs: 2, md: 3 }
317
+ itemDefaults:
318
+ size: { xs: 12, md: 6 } # 1 col on phone, 2 col on tablet+
319
+ children:
320
+ - component: field
321
+ name: orderNumber
322
+ props: { type: text, label: { en-US: "Order Number" }, required: true }
323
+ - component: field
324
+ name: status
325
+ props:
326
+ type: select
327
+ label: { en-US: "Status" }
328
+ items:
329
+ - { label: "Draft", value: "Draft" }
330
+ - { label: "Active", value: "Active" }
112
331
  ```
113
332
 
114
333
  ---
@@ -134,22 +353,33 @@ Polymorphic form field — renders different input types based on `type` prop.
134
353
  | `defaultCountry` | `string` | Default country for phone type |
135
354
  | `isClearable` | `boolean` | Allow clearing the value |
136
355
  | `InputProps` | `object` | Passed to underlying MUI TextField |
356
+ | `size` | `number \| {xs,sm,md,lg,xl}` | When the field is a child of a `layout`, controls the Grid item width per breakpoint. Use `{ xs: 12, md: 6 }` for 1-col mobile / 2-col tablet+. Omit to inherit `itemDefaults.size` from parent layout. |
357
+ | `offset` | `number \| {xs,sm,md,lg,xl}` | Leading offset (push field right) when inside a `layout` |
358
+ | `order` | `number \| {xs,sm,md,lg,xl}` | Visual order per breakpoint when inside a `layout` |
359
+
360
+ Password fields receive a stable input id from the field name unless an explicit `id` is provided. This keeps multiple password fields in the same form isolated for browser autocomplete. Use `autoComplete` when a form needs a specific password-manager hint.
137
361
 
138
362
  **Field Types:**
139
363
  | Type | Description |
140
364
  |------|-------------|
141
365
  | `text` | Standard text input. `multiple: true` → multi-value tags |
142
366
  | `number` | Numeric input |
367
+ | `number-select` | Numeric input with up/down controls and long-press repeat; supports `min`, `max`, `step`, and `options.decimalScale` / `fixedDecimalScale` / `allowNegative` |
143
368
  | `email` | Email input |
144
369
  | `password` | Password input |
145
370
  | `tel` / `phone` | Phone number with country selector |
146
371
  | `textarea` | Multi-line text with `rows` prop |
147
372
  | `checkbox` | Boolean checkbox |
148
373
  | `radio` | Radio button (use `value` prop) |
374
+ | `toggle` | Segmented toggle button group. `allowMultiple: true` gives array values; MUI props (size, color, orientation) live under `options`. Supports icons, disabled items, `enforceValue`, and `defaultValue`. |
149
375
  | `date` | Date picker |
150
376
  | `datetime` | Date + time picker |
151
377
  | `rangedatetime` | Date range picker |
152
378
  | `enhanced-rangedatetime` | Enhanced date range picker |
379
+
380
+ **Timezone-aware datetime fields:** For appointment or terminal times that must be stored as UTC but displayed as a fixed local wall clock, set `options.useTimezone: true`, `options.storeAsUTC: true`, and an IANA `options.timezone` such as `America/New_York`. The picker treats the selected time as being in that configured timezone, not the user's browser timezone, so saving `11:00 PM` in New York stores the matching UTC instant and reads back as `11:00 PM` on machines in any timezone.
381
+
382
+ **Enhanced rangedatetime filter behavior:** `more_than` emits bracketed Lucene ranges such as `field:[* TO NOW-7DAYS]`; boolean/number falsy values (`false`, `0`) are preserved as real filter values; and switching away from Empty/Not Empty clears the previous `NULL` term instead of latching it.
153
383
  | `time` | Time picker |
154
384
  | `select` | Dropdown select from `items[]` |
155
385
  | `select-async` | Async search select (GraphQL-backed) |
@@ -162,7 +392,7 @@ Polymorphic form field — renders different input types based on `type` prop.
162
392
  | `quill` | Rich text editor (Quill) |
163
393
  | `object` | JSON object editor |
164
394
  | `yaml` | YAML editor |
165
- | `secret` | Encrypted secret input — stores `${secret:qualifiedName}` reference. Requires `secretPath` prop for naming. |
395
+ | `secret` | Encrypted secret input — stores `${secret:qualifiedName}` reference. Requires `secretPath` prop for naming and sends `setSecret(input: { organizationId, secretName, secretValue })` / `deleteSecret(input: { organizationId, secretName })` using the current organization. |
166
396
 
167
397
  **Select/Async options (under `options`):**
168
398
  | Prop | Type | Description |
@@ -179,6 +409,43 @@ Polymorphic form field — renders different input types based on `type` prop.
179
409
  | `valueQuery` | `{name, path, params}` | Single-value lookup query ref |
180
410
  | `variant` | `string` | Select variant |
181
411
 
412
+ For an inline-create-only `select-async`, omit `valueQuery` and enable
413
+ `allowCreate`. Existing primitive values are rendered directly (as chips when
414
+ `allowMultiple` is true), so this pattern works for free-form arrays without a
415
+ backing entity lookup. Optional `itemLabelTemplate` and `itemValueTemplate`
416
+ still control the displayed label and stored value.
417
+
418
+ ```yaml
419
+ - component: field
420
+ name: aliasScacCodes
421
+ props:
422
+ type: select-async
423
+ label: { en-US: "Alias SCAC codes" }
424
+ options:
425
+ allowCreate: true
426
+ allowMultiple: true
427
+ ```
428
+
429
+ **Google Places autocomplete:** `autocomplete-googleplaces` uses the organization-level Google Maps API key from `apps.google.googleMapsApiKey`. Do not put API keys in module YAML. Configure `searchQuery.params` and `valueQuery.params` only:
430
+
431
+ ```yaml
432
+ - component: field
433
+ name: pickupAddress
434
+ props:
435
+ type: autocomplete-googleplaces
436
+ label: { en-US: "Pickup address" }
437
+ options:
438
+ itemLabelTemplate: "{{ description }}"
439
+ searchQuery:
440
+ params:
441
+ input: "{{ search }}"
442
+ types: ["address"]
443
+ language: "en-US"
444
+ valueQuery:
445
+ params:
446
+ fields: ["name", "address_components", "formatted_address", "geometry", "place_id"]
447
+ ```
448
+
182
449
  **Events:**
183
450
  | Event | Description |
184
451
  |-------|-------------|
@@ -188,7 +455,7 @@ Polymorphic form field — renders different input types based on `type` prop.
188
455
  | `onFocus` | Fires on focus |
189
456
  | `onKeyPress` | Fires on keypress (data: `key`, `keyCode`) |
190
457
  | `onSelectValue` | Fires on select-async value selection |
191
- | `onEditClick` | Fires when edit icon clicked. Supported on text and select-async fields. Passes current form values (with optional `valueFieldName`) to the action context |
458
+ | `onEditClick` | Fires when edit icon clicked. Supported on `text`, `select-async`, and `autocomplete-async` fields when a single value is set. Passes current form values (with optional `valueFieldName`) to the action context |
192
459
 
193
460
  ```yaml
194
461
  # Text field
@@ -200,6 +467,19 @@ Polymorphic form field — renders different input types based on `type` prop.
200
467
  required: true
201
468
  placeholder: "Enter company name"
202
469
 
470
+
471
+ # Number select with arrow controls
472
+ - component: field
473
+ name: pieces
474
+ props:
475
+ type: number-select
476
+ label: { en-US: "Pieces" }
477
+ min: 0
478
+ max: 999
479
+ step: 1
480
+ options:
481
+ decimalScale: 0
482
+
203
483
  # Select field
204
484
  - component: field
205
485
  name: status
@@ -210,6 +490,23 @@ Polymorphic form field — renders different input types based on `type` prop.
210
490
  - { label: "Active", value: "active" }
211
491
  - { label: "Inactive", value: "inactive" }
212
492
 
493
+ # Toggle field
494
+ - component: field
495
+ name: priority
496
+ props:
497
+ type: toggle
498
+ label: { en-US: "Priority" }
499
+ allowMultiple: false
500
+ enforceValue: true
501
+ defaultValue: normal
502
+ options:
503
+ size: small
504
+ color: primary
505
+ orientation: horizontal
506
+ items:
507
+ - { label: "Normal", value: normal }
508
+ - { label: "High", value: high }
509
+
213
510
  # Async select with search
214
511
  - component: field
215
512
  name: customerId
@@ -275,7 +572,9 @@ Polymorphic form field — renders different input types based on `type` prop.
275
572
 
276
573
  ## field-collection
277
574
 
278
- Dynamic array/list editor for repeating field groups. Supports add/remove/reorder with drag-and-drop.
575
+ Dynamic array/list editor for repeating field groups. Supports add/remove/reorder with drag-and-drop and optional up/down move buttons. Reorder UI adds stable internal `_uuid` keys to object rows only when drag handles or move buttons are shown, so non-reorder payloads stay untouched.
576
+
577
+ When `options.allowRemove` is enabled and the item count is above `minItems`, removal is exposed through a theme-colored three-dots menu with a **Delete** action. The same menu is used for list/grid items and accordion headers; selecting it still follows `options.removeType` (`soft` or `hard`).
279
578
 
280
579
  **Props:**
281
580
  | Prop | Type | Default | Description |
@@ -289,20 +588,41 @@ Dynamic array/list editor for repeating field groups. Supports add/remove/reorde
289
588
  | `options.allowReorder` | `boolean` | `true` | Enable drag-and-drop |
290
589
  | `options.minItems` | `number` | `0` | Minimum items (auto-created) |
291
590
  | `options.maxItems` | `number` | `∞` | Maximum items |
591
+ | `options.removeType` | `soft \| hard` | `soft` | Individual remove behavior. `soft` marks object rows with `isDeleted: true`; `hard` splices the row from the array |
292
592
  | `addButton.label` | `ILocalizeString` | `Add Item` | Add button label |
293
593
  | `addButton.icon` | `string` | `plus` | Add button icon |
294
594
  | `addButton.position` | `top \| bottom \| both` | `bottom` | Add button placement |
295
595
  | `defaultItem` | `any` | — | Template for new items |
296
596
  | `layout` | `list \| grid \| accordion` | `list` | Layout mode |
297
- | `cols` | `number` | `1` | Grid columns |
597
+ | `cols` | `number` | `1` | Grid columns (non-responsive). For adaptive item layouts, set `cols: 1` and use a `layout` inside `itemTemplate` with `itemDefaults.size: { xs, md }`. |
598
+ | `groupCols` | `number \| {xs,sm,md,lg,xl}` | `1` | Group columns when `groupMode` is true |
599
+ | `groupSpacing` | `number` | `0` | Spacing between group columns |
298
600
  | `groupMode` | `boolean` | `false` | Enable grouping |
299
601
  | `groupBy` | `string` | — | Field path to group by |
300
602
  | `groups` | `{key, label, icon?}[]` | — | Group definitions |
301
603
  | `showIndex` | `boolean` | `false` | Show item index |
302
604
  | `showDragHandle` | `boolean` | `false` | Show drag handles |
605
+ | `showMoveButtons` | `boolean` | `false` | Show up/down move buttons for accessible reordering; requires `options.allowReorder` |
606
+ | `label` | `ILocalizeString` | — | Enables a collection header; nested collections use a compact section header |
607
+ | `itemTitle` | `string` | derived | Field path or template for the accordion row title |
608
+ | `itemIcon` | `string` | — | Icon name or per-item template rendered before the row title |
609
+ | `itemSubtitle` | `string` | — | Per-item template for muted row metadata |
610
+ | `itemBadges` | `{label,color?,when?}[]` | — | Inline semantic badges; colors: `danger`, `warning`, `info`, `success`, `primary`, `neutral` |
611
+ | `itemNestedCount` | `string` | — | Template resolving to a nested-record count on collapsed rows |
612
+ | `summaryTemplate` | `string` | item count | Header summary template with `count` and `items` variables |
613
+ | `showCount` | `boolean` | `true` | Show the header count chip |
614
+ | `showExpandAll` | `boolean` | `true` | Show bulk expansion controls for multi-row accordions |
615
+ | `collapseNested` | `boolean` | `true` | Render nested collections as collapsed accordion rows unless `layout` is explicit |
616
+ | `autoItemTitle` | `boolean` | `true` | Derive missing titles from label-like scalar record fields |
617
+ | `density` | `compact \| comfortable` | `compact` | Accordion row padding preset |
303
618
 
304
619
  **Children:** Uses `itemTemplate` (not `children`). Each item gets `item`, `index`, `collection` variables.
305
620
 
621
+ For compact record lists, use `layout: accordion`. If `label` is set, the add action moves into
622
+ the header unless its position is explicit. Nested collections collapse by default; blank new
623
+ records expand automatically. `itemIcon`, `itemSubtitle`, and badge/count templates receive the
624
+ parent variables plus `item`, `index`, and the item's own fields.
625
+
306
626
  ```yaml
307
627
  component: field-collection
308
628
  name: lineItems
@@ -312,6 +632,7 @@ props:
312
632
  allowAdd: true
313
633
  allowRemove: true
314
634
  allowReorder: true
635
+ removeType: soft
315
636
  minItems: 1
316
637
  maxItems: 50
317
638
  addButton:
@@ -324,19 +645,57 @@ props:
324
645
  unitPrice: 0
325
646
  layout: list
326
647
  itemTemplate:
327
- component: row
648
+ component: layout
328
649
  name: lineItemRow
329
- props: { spacing: 2 }
650
+ props:
651
+ columnSpacing: { xs: 1, md: 2 }
652
+ rowSpacing: 2
330
653
  children:
331
654
  - component: field
332
655
  name: description
333
- props: { type: text, label: { en-US: "Description" } }
656
+ props:
657
+ type: text
658
+ label: { en-US: "Description" }
659
+ size: { xs: 12, md: 6 }
334
660
  - component: field
335
661
  name: quantity
336
- props: { type: number, label: { en-US: "Qty" } }
662
+ props:
663
+ type: number
664
+ label: { en-US: "Qty" }
665
+ size: { xs: 6, md: 3 }
337
666
  - component: field
338
667
  name: unitPrice
339
- props: { type: number, label: { en-US: "Unit Price" } }
668
+ props:
669
+ type: number
670
+ label: { en-US: "Unit Price" }
671
+ size: { xs: 6, md: 3 }
672
+ ```
673
+
674
+ Grouped collections can render groups in multiple columns. Use a fixed `groupCols` count or responsive breakpoint counts; each count is converted to Material UI's 12-column grid.
675
+
676
+ ```yaml
677
+ component: field-collection
678
+ name: businessHours
679
+ props:
680
+ fieldName: businessHours
681
+ groupMode: true
682
+ groupBy: dayOfWeek
683
+ groupCols: { xs: 1, md: 2, lg: 4 }
684
+ groupSpacing: 2
685
+ groups:
686
+ - { key: 1, label: { en-US: Monday } }
687
+ - { key: 2, label: { en-US: Tuesday } }
688
+ layout: accordion
689
+ itemTemplate:
690
+ component: layout
691
+ props: { cols: 2 }
692
+ children:
693
+ - component: field
694
+ name: startTime
695
+ props: { type: time, label: { en-US: Start } }
696
+ - component: field
697
+ name: endTime
698
+ props: { type: time, label: { en-US: End } }
340
699
  ```
341
700
 
342
701
  ---
@@ -349,9 +708,15 @@ Headless barcode/keyboard scanner listener. Captures rapid keystrokes and fires
349
708
  | Prop | Type | Default | Description |
350
709
  |------|------|---------|-------------|
351
710
  | `minBarcodeLength` | `number` | `4` | Min chars to qualify as scan |
711
+ | `deduplicate` | `boolean` | `true` | Mobile: suppress repeat reads per barcode |
712
+ | `scanDelayMs` | `number` | `1000` | Mobile: per-code dedup window in milliseconds |
713
+ | `minScanIntervalMs` | `number` | `0` | Mobile: minimum time between accepted scans; `0` disables |
714
+ | `continuous` | `boolean` | `true` | Mobile: accept more than one scan |
715
+ | `maxBarcodes` | `number` | — | Mobile: maximum accepted scans in continuous mode |
352
716
  | `onScan` | `action[]` | — | **Required.** Action on scan detection |
353
717
 
354
- **Renders:** Nothing (empty fragment). Listens on `document` keypress.
718
+ **Renders:** Nothing (empty fragment). Web listens on `document` keypress. Mobile supports
719
+ native HID scanners with a hidden-input fallback and shares camera-scanner deduplication rules.
355
720
 
356
721
  **Scan data:** `result: { data: string, format: 'input' }`
357
722
 
@@ -43,6 +43,8 @@ MUI Button with icon, label, loading state, and action dispatch.
43
43
 
44
44
  **Events:** `onClick` — dispatches action chain with loading spinner while executing.
45
45
 
46
+ **Theme note:** MUI toggle button groups use the primary color by default, and medium toggle buttons are fixed at 39px height to align segmented controls with neighboring inputs.
47
+
46
48
  ```yaml
47
49
  # Primary submit button
48
50
  - component: button