@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.
- package/README.md +2 -0
- package/dist/action/ja.generate-pdf.d.ts +17 -1
- package/dist/action/ja.generate-pdf.d.ts.map +1 -1
- package/dist/action/ja.generate-pdf.js +4 -1
- package/dist/action/ja.in-background.d.ts +3 -2
- package/dist/action/ja.in-background.d.ts.map +1 -1
- package/dist/action/ja.in-background.js +1 -1
- package/dist/assets/example-extraction-cache.json +3 -3
- package/dist/assets/extracted-core-sdk-examples.yaml +18 -0
- package/dist/assets/extracted-core-sdk-types.yaml +58 -0
- package/dist/assets/type-extraction-cache.json +3 -3
- package/docs/array-fields.md +371 -0
- package/docs/conditional-logic.md +178 -0
- package/docs/convention-naming.md +102 -0
- package/docs/date-field.md +92 -0
- package/docs/dropdown-fields.md +879 -0
- package/docs/field-state.md +131 -0
- package/docs/field-types-overview.md +132 -0
- package/docs/formatting.md +421 -0
- package/docs/icons.md +142 -0
- package/docs/index.md +23 -0
- package/docs/jsonata-expressions.md +200 -0
- package/docs/media-fields.md +107 -0
- package/docs/overview.md +467 -0
- package/docs/pattern-build-deploy.md +91 -0
- package/docs/pattern-datasources.md +459 -0
- package/docs/pattern-forms.md +528 -0
- package/docs/pattern-global-actions.md +92 -0
- package/docs/pattern-javascript-functions.md +452 -0
- package/docs/pattern-navigation.md +304 -0
- package/docs/pattern-pdf-generation.md +391 -0
- package/docs/pattern-rest-acumatica.md +660 -0
- package/docs/pattern-sync-progress.md +96 -0
- package/docs/pattern-sync.md +653 -0
- package/docs/pattern-tabs-form.md +293 -0
- package/docs/recipe-index.md +64 -0
- package/docs/runtime-variables.md +127 -0
- package/docs/sections.md +81 -0
- package/docs/validation-patterns.md +150 -0
- package/package.json +5 -4
- package/CHANGELOG.md +0 -95
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
## Basic Usage
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
form.addStep({ instanceId: 'details', icon: 'pencil-edit-info' }, (step) => {
|
|
5
|
+
const emailField = step.addEmail({ name: 'email', label: 'Email' })
|
|
6
|
+
// Phone required only if email is empty
|
|
7
|
+
step.addText({
|
|
8
|
+
name: 'phone',
|
|
9
|
+
label: 'Phone',
|
|
10
|
+
isRequired: new JsonataBuilder('$email = null or $email = ""', {
|
|
11
|
+
email: emailField.state.value,
|
|
12
|
+
}),
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
`field.state.value` is a template variable replaced with actual state reference at runtime:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
step.addDropdown({
|
|
23
|
+
name: 'status',
|
|
24
|
+
label: 'Status',
|
|
25
|
+
data: [
|
|
26
|
+
{ label: 'Active', value: 'active' },
|
|
27
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
// When user selects "active":
|
|
31
|
+
// status.state.value → "active" (immediately)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Characteristics
|
|
35
|
+
|
|
36
|
+
| Property | Behavior |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| Updates | Immediate on user input |
|
|
39
|
+
| Scope | Current step only |
|
|
40
|
+
| Persistence | Not persisted until step saved |
|
|
41
|
+
| Use case | Local validation, conditional fields |
|
|
42
|
+
|
|
43
|
+
## Important Limitations
|
|
44
|
+
|
|
45
|
+
**Self-reference in `value` creates a cycle (not allowed):**
|
|
46
|
+
|
|
47
|
+
A field's `value` cannot reference its own `.state.value`
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
**ArrayField does not expose `.state`:**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const step1 = form.addStep({ instanceId: 'collect-items' }, (step) => {
|
|
54
|
+
// Wrong - arrayField has no .state
|
|
55
|
+
const items = step.addArrayField({ name: 'items', label: 'Items', title: 'Item' })
|
|
56
|
+
items.addText({ name: 'name', label: 'Name' })
|
|
57
|
+
// items.state.value // Error - doesn't exist
|
|
58
|
+
})
|
|
59
|
+
step1.with({ icon: 'list', title: 'Items' })
|
|
60
|
+
// Correct - use step.data.getArrayFieldData()
|
|
61
|
+
form.addStep({ instanceId: 'select-item', icon: 'cursor-select-1' }, (otherStep) => {
|
|
62
|
+
otherStep.addDropdown({
|
|
63
|
+
name: 'selected',
|
|
64
|
+
label: 'Select Item',
|
|
65
|
+
data: step1.data.getArrayFieldData('items'),
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Cross-step access not available:**
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const step1 = form.addStep({ instanceId: 'type' }, (step) => {
|
|
74
|
+
step.addDropdown({
|
|
75
|
+
name: 'status',
|
|
76
|
+
label: 'Status',
|
|
77
|
+
data: [
|
|
78
|
+
{ label: 'Active', value: 'active' },
|
|
79
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
80
|
+
],
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
step1.with({ icon: 'settings', title: 'Type' })
|
|
84
|
+
// Wrong - field.state.value only works within same step
|
|
85
|
+
// step1Field.state.value // Won't work cross-step
|
|
86
|
+
// Correct - use step.data.getFieldData() for cross-step
|
|
87
|
+
form.addStep({ instanceId: 'details', icon: 'pencil-edit-info' }, (step2) => {
|
|
88
|
+
step2.addText({
|
|
89
|
+
name: 'field',
|
|
90
|
+
label: 'Field',
|
|
91
|
+
isVisible: new JsonataBuilder('$status = "active"', {
|
|
92
|
+
status: step1.data.getFieldData('status'),
|
|
93
|
+
}),
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Dropdown state.selected
|
|
99
|
+
|
|
100
|
+
Dropdowns expose an additional `state.selected` property for accessing the full selected object in the same step:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const productField = step.addDropdown({
|
|
104
|
+
name: 'product',
|
|
105
|
+
label: 'Product',
|
|
106
|
+
data: [
|
|
107
|
+
{ label: 'Widget A', value: 'widget-a', icon: 'cube-shape' },
|
|
108
|
+
{ label: 'Widget B', value: 'widget-b', icon: 'cube-shape' },
|
|
109
|
+
],
|
|
110
|
+
})
|
|
111
|
+
// state.value → "widget-a" (just the value)
|
|
112
|
+
// state.selected → { label: "Widget A", value: "widget-a", icon: "cube" }
|
|
113
|
+
// Access full object to show selected label in read-only field
|
|
114
|
+
step.addText({
|
|
115
|
+
name: 'selectedLabel',
|
|
116
|
+
label: 'Selected Product Name',
|
|
117
|
+
value: new JsonataBuilder('$product.label', {
|
|
118
|
+
product: productField.state.selected,
|
|
119
|
+
}),
|
|
120
|
+
isDisabled: true,
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Property | Returns | Use Case |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `state.value` | `string \| number` | Selected value for conditions |
|
|
127
|
+
| `state.selected` | Full object | Access label, icon, or nested properties |
|
|
128
|
+
|
|
129
|
+
**Ref:** `./dropdown-fields.md` for more on dropdown state
|
|
130
|
+
|
|
131
|
+
**Ref:** `./cross-step-data.md` for cross-step data access
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
## Adding Fields
|
|
2
|
+
|
|
3
|
+
Fields are added to steps or sections:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// Add to step
|
|
7
|
+
step.addText({ name: 'username', label: 'Username' })
|
|
8
|
+
// Add to section
|
|
9
|
+
const section = step.addSection({ title: 'Contact Info' })
|
|
10
|
+
section.addEmail({ name: 'email', label: 'Email' })
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Important:** Call field methods on step or section only - NOT on field references.
|
|
14
|
+
|
|
15
|
+
## Common Properties
|
|
16
|
+
|
|
17
|
+
All fields support these via constructor or `.with()`:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const otherField = step.addCheckbox({ name: 'showNotes', label: 'Show Notes' })
|
|
21
|
+
// Pass all properties in constructor
|
|
22
|
+
step.addText({
|
|
23
|
+
name: 'notes',
|
|
24
|
+
label: 'Notes',
|
|
25
|
+
icon: 'common-file-text',
|
|
26
|
+
helperText: 'Enter additional details',
|
|
27
|
+
isRequired: false, // Make optional
|
|
28
|
+
isDisabled: true, // Read-only
|
|
29
|
+
isVisible: new JsonataBuilder('$showNotes = true', { showNotes: otherField.state.value }),
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or use `.with()` to update after construction:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const field = step.addText({ name: 'notes', label: 'Notes' })
|
|
37
|
+
field.with({
|
|
38
|
+
icon: 'common-file-text',
|
|
39
|
+
helperText: 'Enter additional details',
|
|
40
|
+
isRequired: false,
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Required vs Optional
|
|
45
|
+
|
|
46
|
+
- **Most fields required by default** - no need to set `isRequired: true`
|
|
47
|
+
- **Checkbox is optional by default** - set `isRequired: true` if needed
|
|
48
|
+
- To make any field optional: `isRequired: false`
|
|
49
|
+
|
|
50
|
+
## Text Fields
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Single-line
|
|
54
|
+
step.addText({ name: 'title', label: 'Title' })
|
|
55
|
+
// Multi-line textarea
|
|
56
|
+
step.addText({
|
|
57
|
+
name: 'notes',
|
|
58
|
+
label: 'Notes',
|
|
59
|
+
isMultiline: true,
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Validated Input Fields
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// Email - built-in format validation
|
|
67
|
+
step.addEmail({ name: 'email', label: 'Email' })
|
|
68
|
+
// Phone - phone-pad keyboard
|
|
69
|
+
step.addPhone({ name: 'phone', label: 'Phone' })
|
|
70
|
+
// Number - numeric input
|
|
71
|
+
step.addNumber({ name: 'quantity', label: 'Quantity' })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Date/Time Fields
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
step.addDate({
|
|
78
|
+
mode: 'datetime',
|
|
79
|
+
name: 'eventDateTime',
|
|
80
|
+
label: 'Event Date & Time',
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Checkbox Fields
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
step.addCheckbox({
|
|
88
|
+
name: 'consent',
|
|
89
|
+
label: 'I agree to terms',
|
|
90
|
+
isRequired: true, // Checkbox optional by default, set true to require
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Choice vs Checkbox for yes/no:**
|
|
95
|
+
|
|
96
|
+
- Required yes/no question: Use choice field (checkbox fails validation when unchecked)
|
|
97
|
+
- Optional toggle: Use checkbox
|
|
98
|
+
|
|
99
|
+
## Rating Fields
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
step.addRating({ name: 'satisfaction', label: 'Satisfaction Rating' })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Rating vs Number:**
|
|
106
|
+
|
|
107
|
+
- Rating: Subjective feedback on fixed scale (1-5 stars, quality)
|
|
108
|
+
- Number: Objective/precise values (counts, measurements)
|
|
109
|
+
|
|
110
|
+
## Slider Fields
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
step.addSlider({ name: 'price', label: 'Price', minimum: 0, maximum: 1000, step: 50 })
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Slider vs Number:**
|
|
117
|
+
|
|
118
|
+
- Slider: Bounded range with known min/max, coarse precision
|
|
119
|
+
- Number: High precision, large ranges, exact values
|
|
120
|
+
|
|
121
|
+
## Helper Text Guidelines
|
|
122
|
+
|
|
123
|
+
- Use only when providing NEW information (formatting hints, constraints, examples)
|
|
124
|
+
- Never repeat the label
|
|
125
|
+
- Omit entirely if label is self-explanatory
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Good - adds information
|
|
129
|
+
step.addEmail({ name: 'email', label: 'Email', helperText: 'We will never share your email' })
|
|
130
|
+
// Bad - repeats label
|
|
131
|
+
step.addEmail({ name: 'email2', label: 'Email', helperText: 'Enter your email address' }) // Don't do this
|
|
132
|
+
```
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
Field `format` and `FormatBuilder` use [ECMA-402](https://tc39.es/ecma402/) `Intl.NumberFormat` for numbers (currency, percent, units) and [Moment.js](https://momentjs.com/docs/#/displaying/) `dateFormat` presets/patterns for dates (`LL`, `fromNow`, etc.). Both are locale-aware — numbers adapt decimal separators, thousands grouping, and currency symbols; dates adapt month names, orderings, and relative-time wording automatically.
|
|
2
|
+
|
|
3
|
+
**Important:** Field `format` only applies when field is disabled. When editable, raw value displays.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Where FormatBuilder applies
|
|
7
|
+
|
|
8
|
+
Use `FormatBuilder` to format values in these specific properties:
|
|
9
|
+
|
|
10
|
+
- `submissionItemTitle` — form-level submission title
|
|
11
|
+
- `submissionItemSubtitle` — form-level submission subtitle
|
|
12
|
+
- ArrayField `title` — item title in the array list
|
|
13
|
+
- ArrayField `subtitle` — item subtitle in the array list
|
|
14
|
+
- ArrayField `description` — item description in the array list
|
|
15
|
+
- Step `title` — step header text
|
|
16
|
+
|
|
17
|
+
Wrap `FormatBuilder` around a `JsonataBuilder` expression, then pass to `I18nBuilder` if combining with text.
|
|
18
|
+
|
|
19
|
+
## ECMA-402 Standard
|
|
20
|
+
|
|
21
|
+
Format options follow two systems:
|
|
22
|
+
|
|
23
|
+
- **Number formatting**: [ECMA-402](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) `Intl.NumberFormat` — currency, percent, units, scientific notation. Locale-aware, adapts to user's device locale automatically.
|
|
24
|
+
- **Date formatting**: [Moment.js](https://momentjs.com/docs/#/displaying/) `dateFormat` presets (`LL`, `fromNow`, etc.). See [Date Format Codes](#date-format-codes) below.
|
|
25
|
+
|
|
26
|
+
No manual locale configuration needed - the SDK handles it.
|
|
27
|
+
|
|
28
|
+
## Field Format Property
|
|
29
|
+
|
|
30
|
+
### When Format Applies
|
|
31
|
+
|
|
32
|
+
- **Disabled/read-only fields**: Format is applied, formatted value displays
|
|
33
|
+
- **Editable fields**: Raw value from `value` property displays, format ignored
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Format applied - field is disabled
|
|
37
|
+
step.addNumber({
|
|
38
|
+
name: 'total',
|
|
39
|
+
label: 'Total',
|
|
40
|
+
value: 1234.56,
|
|
41
|
+
isDisabled: true,
|
|
42
|
+
format: { numberStyle: 'currency', currency: 'USD' },
|
|
43
|
+
})
|
|
44
|
+
// Displays: $1,234.56
|
|
45
|
+
|
|
46
|
+
// Format NOT applied - field is editable
|
|
47
|
+
step.addNumber({
|
|
48
|
+
name: 'amount',
|
|
49
|
+
label: 'Amount',
|
|
50
|
+
value: 1234.56,
|
|
51
|
+
format: { numberStyle: 'currency', currency: 'USD' },
|
|
52
|
+
})
|
|
53
|
+
// Displays: 1234.56 (raw value)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Currency Formatting
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
step.addNumber({
|
|
60
|
+
name: 'price',
|
|
61
|
+
label: 'Price',
|
|
62
|
+
format: { currency: 'USD', numberStyle: 'currency' },
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Currency Options
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
step.addNumber({
|
|
70
|
+
name: 'price',
|
|
71
|
+
label: 'Price',
|
|
72
|
+
format: {
|
|
73
|
+
numberStyle: 'currency',
|
|
74
|
+
currency: 'EUR',
|
|
75
|
+
currencyDisplay: 'symbol', // €, $, £ (options: 'symbol', 'code', 'name', 'narrowSymbol')
|
|
76
|
+
currencySign: 'accounting', // (100) for negative (options: 'standard', 'accounting')
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Percentage Formatting
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Format as percentage
|
|
85
|
+
step.addNumber({
|
|
86
|
+
name: 'completion-rate',
|
|
87
|
+
label: 'Completion Rate',
|
|
88
|
+
format: { numberStyle: 'percent' },
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Decimal Formatting
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Fixed decimal places (123.45, 0.50)
|
|
96
|
+
step.addNumber({
|
|
97
|
+
name: 'average-score',
|
|
98
|
+
label: 'Average Score',
|
|
99
|
+
format: { numberStyle: 'decimal', minimumFractionDigits: 2, maximumFractionDigits: 2 },
|
|
100
|
+
})
|
|
101
|
+
// Significant digits (123.4, 0.5, 0.001234)
|
|
102
|
+
step.addNumber({
|
|
103
|
+
name: 'precision-value',
|
|
104
|
+
label: 'Precision Value',
|
|
105
|
+
format: { minimumSignificantDigits: 3, maximumSignificantDigits: 4 },
|
|
106
|
+
})
|
|
107
|
+
// Combining both: min/max fraction digits
|
|
108
|
+
step.addNumber({
|
|
109
|
+
name: 'detailed-score',
|
|
110
|
+
label: 'Detailed Score',
|
|
111
|
+
format: { minimumFractionDigits: 1, maximumFractionDigits: 3 },
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Decimal Options
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
format: {
|
|
119
|
+
minimumFractionDigits: 2, // Min decimal places
|
|
120
|
+
maximumFractionDigits: 4, // Max decimal places
|
|
121
|
+
minimumSignificantDigits: 3, // Min significant digits
|
|
122
|
+
maximumSignificantDigits: 5 // Max significant digits
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Unit Formatting
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Distance in kilometers with 2 decimal precision
|
|
130
|
+
step.addNumber({
|
|
131
|
+
name: 'distance-km',
|
|
132
|
+
label: 'Distance (km)',
|
|
133
|
+
format: {
|
|
134
|
+
numberStyle: 'unit',
|
|
135
|
+
unit: 'kilometer',
|
|
136
|
+
unitDisplay: 'long',
|
|
137
|
+
minimumFractionDigits: 2,
|
|
138
|
+
maximumFractionDigits: 2,
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
// Height in meters with 1 decimal place
|
|
142
|
+
step.addNumber({
|
|
143
|
+
name: 'height-m',
|
|
144
|
+
label: 'Height (m)',
|
|
145
|
+
format: {
|
|
146
|
+
numberStyle: 'unit',
|
|
147
|
+
unit: 'meter',
|
|
148
|
+
unitDisplay: 'short',
|
|
149
|
+
minimumFractionDigits: 1,
|
|
150
|
+
maximumFractionDigits: 1,
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
// Width in millimeters (no decimals for precision)
|
|
154
|
+
step.addNumber({
|
|
155
|
+
name: 'width-mm',
|
|
156
|
+
label: 'Width (mm)',
|
|
157
|
+
format: {
|
|
158
|
+
numberStyle: 'unit',
|
|
159
|
+
unit: 'millimeter',
|
|
160
|
+
unitDisplay: 'narrow',
|
|
161
|
+
minimumFractionDigits: 0,
|
|
162
|
+
maximumFractionDigits: 0,
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
// Weight in kilograms with sign display
|
|
166
|
+
step.addNumber({
|
|
167
|
+
name: 'weight-kg',
|
|
168
|
+
label: 'Weight Change (kg)',
|
|
169
|
+
format: {
|
|
170
|
+
numberStyle: 'unit',
|
|
171
|
+
unit: 'kilogram',
|
|
172
|
+
unitDisplay: 'short',
|
|
173
|
+
signDisplay: 'always',
|
|
174
|
+
minimumFractionDigits: 1,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
// Temperature in celsius with minimum integer digits
|
|
178
|
+
step.addNumber({
|
|
179
|
+
name: 'temperature',
|
|
180
|
+
label: 'Temperature',
|
|
181
|
+
format: {
|
|
182
|
+
numberStyle: 'unit',
|
|
183
|
+
unit: 'celsius',
|
|
184
|
+
minimumIntegerDigits: 2,
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Unit Display Options
|
|
190
|
+
|
|
191
|
+
| Display | Example |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `'short'` | 150 lb |
|
|
194
|
+
| `'long'` | 150 pounds |
|
|
195
|
+
| `'narrow'` | 150# |
|
|
196
|
+
|
|
197
|
+
### Common Units
|
|
198
|
+
|
|
199
|
+
- Length: `meter`, `kilometer`, `mile`, `foot`, `inch`
|
|
200
|
+
- Weight: `kilogram`, `pound`, `ounce`
|
|
201
|
+
- Temperature: `celsius`, `fahrenheit`
|
|
202
|
+
- Volume: `liter`, `gallon`
|
|
203
|
+
- Time: `hour`, `minute`, `second`
|
|
204
|
+
|
|
205
|
+
## Scientific Notation
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// Set multiple format properties
|
|
209
|
+
step.addNumber({
|
|
210
|
+
name: 'result',
|
|
211
|
+
label: 'Result',
|
|
212
|
+
format: {
|
|
213
|
+
notation: 'scientific',
|
|
214
|
+
minimumSignificantDigits: 3,
|
|
215
|
+
maximumSignificantDigits: 5,
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Dynamic Formatting
|
|
221
|
+
|
|
222
|
+
Format properties support `JsonataBuilder` for dynamic values:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Let user select currency
|
|
226
|
+
const currencyField = step.addDropdown({
|
|
227
|
+
name: 'currency',
|
|
228
|
+
label: 'Currency',
|
|
229
|
+
data: [
|
|
230
|
+
{ label: 'USD', value: 'USD' },
|
|
231
|
+
{ label: 'EUR', value: 'EUR' },
|
|
232
|
+
{ label: 'GBP', value: 'GBP' },
|
|
233
|
+
],
|
|
234
|
+
})
|
|
235
|
+
// Set currency dynamically based on selection
|
|
236
|
+
step.addNumber({
|
|
237
|
+
name: 'amount',
|
|
238
|
+
label: 'Amount',
|
|
239
|
+
format: {
|
|
240
|
+
currency: new JsonataBuilder('$selectedCurrency', {
|
|
241
|
+
selectedCurrency: currencyField.state.value,
|
|
242
|
+
}),
|
|
243
|
+
numberStyle: 'currency',
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Updating Format with .with()
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const price = step.addNumber({ name: 'price', label: 'Price' })
|
|
252
|
+
// Update format after construction
|
|
253
|
+
price.with({
|
|
254
|
+
format: { numberStyle: 'currency', currency: 'USD', minimumFractionDigits: 2 },
|
|
255
|
+
})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## FormatBuilder Class
|
|
259
|
+
|
|
260
|
+
Use `FormatBuilder` to format values for text display in `submissionItemTitle`, `submissionItemSubtitle`, arrayField item `title`/`subtitle`/`description`, and step `title`. Supports same number options as field format, plus date formatting.
|
|
261
|
+
|
|
262
|
+
### Number Formatting with FormatBuilder
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Step 1: Collect balance
|
|
266
|
+
const step1 = form.addStep(
|
|
267
|
+
{ instanceId: 'input', title: 'Enter Balance', icon: 'accounting-calculator' },
|
|
268
|
+
(step) => {
|
|
269
|
+
step.addNumber({ name: 'balance', label: 'Balance' })
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
// Step 2: Display formatted balance in title
|
|
273
|
+
form.addStep(
|
|
274
|
+
{
|
|
275
|
+
instanceId: 'summary',
|
|
276
|
+
title: new I18nBuilder('balance.display', 'Balance: {amount}', {
|
|
277
|
+
amount: new FormatBuilder(
|
|
278
|
+
new JsonataBuilder('$balance', { balance: step1.data.getFieldData('balance') }),
|
|
279
|
+
{ numberStyle: 'currency', currency: 'USD' },
|
|
280
|
+
),
|
|
281
|
+
}),
|
|
282
|
+
icon: 'cash-payment-wallet',
|
|
283
|
+
},
|
|
284
|
+
(step) => {
|
|
285
|
+
step.addText({ name: 'note', label: 'Note' })
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Date Formatting with FormatBuilder
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// Step 1: Collect date
|
|
294
|
+
const step1 = form.addStep(
|
|
295
|
+
{ instanceId: 'input', title: 'Enter Date', icon: 'calendar-date' },
|
|
296
|
+
(step) => {
|
|
297
|
+
step.addDate({ name: 'updatedAt', label: 'Last Updated' })
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
// Step 2: Display formatted date in title
|
|
301
|
+
form.addStep(
|
|
302
|
+
{
|
|
303
|
+
instanceId: 'activity',
|
|
304
|
+
title: new I18nBuilder('activity.updated', 'Updated {when}', {
|
|
305
|
+
when: new FormatBuilder(
|
|
306
|
+
new JsonataBuilder('$date', { date: step1.data.getFieldData('updatedAt') }),
|
|
307
|
+
{ dateFormat: 'fromNow' },
|
|
308
|
+
),
|
|
309
|
+
}),
|
|
310
|
+
icon: 'stopwatch',
|
|
311
|
+
},
|
|
312
|
+
(step) => {
|
|
313
|
+
step.addText({ name: 'note', label: 'Note' })
|
|
314
|
+
},
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Date Format Codes
|
|
319
|
+
|
|
320
|
+
Use these format codes with `FormatBuilder` for date display:
|
|
321
|
+
|
|
322
|
+
| Format | Example Output | Description |
|
|
323
|
+
| --- | --- | --- |
|
|
324
|
+
| `'fromNow'` | "2 hours ago", "3 days ago" | Relative time (past) |
|
|
325
|
+
| `'toNow'` | "in 23 hours" | Relative time (future) |
|
|
326
|
+
| `'LT'` | "3:28 PM" | Time only |
|
|
327
|
+
| `'LTS'` | "3:28:57 PM" | Time with seconds |
|
|
328
|
+
| `'L'` | "03/03/2022" | Short date |
|
|
329
|
+
| `'l'` | "3/3/2022" | Short date (no padding) |
|
|
330
|
+
| `'LL'` | "March 3, 2022" | Long date (default) |
|
|
331
|
+
| `'ll'` | "Mar 3, 2022" | Abbreviated date |
|
|
332
|
+
| `'LLL'` | "March 3, 2022 3:28 PM" | Long date + time |
|
|
333
|
+
| `'lll'` | "Mar 3, 2022 3:28 PM" | Abbreviated date + time |
|
|
334
|
+
| `'LLLL'` | "Thursday, March 3, 2022 3:28 PM" | Full date + time |
|
|
335
|
+
| `'llll'` | "Thu, Mar 3, 2022 3:28 PM" | Abbreviated full |
|
|
336
|
+
| `'HH:mm'` | "15:28" | 24-hour format |
|
|
337
|
+
|
|
338
|
+
## Common Mistakes
|
|
339
|
+
|
|
340
|
+
### Don't use date fields without FormatBuilder
|
|
341
|
+
|
|
342
|
+
Date fields store ISO 8601 strings. Using a raw date reference displays unformatted text like `"2024-01-15T14:30:00Z"`. This applies to `submissionItemTitle`, `submissionItemSubtitle`, arrayField `title`/`subtitle`/`description`, and step `title`. Always wrap in `FormatBuilder` with a `dateFormat` option.
|
|
343
|
+
|
|
344
|
+
**In submission title/subtitle:**
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const step1 = form.addStep({ instanceId: 'booking', icon: 'calendar' }, (step) => {
|
|
348
|
+
step.addDate({ name: 'appointment', label: 'Appointment Date' })
|
|
349
|
+
})
|
|
350
|
+
// WRONG: using JsonataBuilder alone displays raw ISO string like "2024-01-15T14:30:00Z"
|
|
351
|
+
// submissionItemTitle: new JsonataBuilder('$date', { date: step1.data.getFieldData('appointment') })
|
|
352
|
+
// CORRECT: always wrap date fields in FormatBuilder
|
|
353
|
+
// Displays: "January 15, 2024"
|
|
354
|
+
form.with({
|
|
355
|
+
submissionItemTitle: new FormatBuilder(
|
|
356
|
+
new JsonataBuilder('$date', { date: step1.data.getFieldData('appointment') }),
|
|
357
|
+
{ dateFormat: 'LL' },
|
|
358
|
+
),
|
|
359
|
+
submissionItemSubtitle: 'Booking',
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**In arrayField item title/subtitle:**
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const events = step.addArrayField({ name: 'events', label: 'Events' })
|
|
367
|
+
// WRONG: raw ISO string in title
|
|
368
|
+
// events.with({
|
|
369
|
+
// title: new JsonataBuilder('$date', { date: events.getCurrentItemRef('eventDate') }),
|
|
370
|
+
// })
|
|
371
|
+
// CORRECT: wrap in FormatBuilder
|
|
372
|
+
events.with({
|
|
373
|
+
title: new FormatBuilder(
|
|
374
|
+
new JsonataBuilder('$date', { date: events.getCurrentItemRef('eventDate') }),
|
|
375
|
+
{ dateFormat: 'LL' },
|
|
376
|
+
),
|
|
377
|
+
})
|
|
378
|
+
events.addText({ name: 'eventName', label: 'Event Name' })
|
|
379
|
+
events.addDate({ name: 'eventDate', label: 'Event Date' })
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Don't use JSONata formatting functions
|
|
383
|
+
|
|
384
|
+
**Wrong:**
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// DON'T DO THIS - use format property instead of JSONata functions
|
|
388
|
+
value: new JsonataBuilder('$formatNumber($price, "#,##0.00")', {
|
|
389
|
+
price: step1.data.getFieldData('price')
|
|
390
|
+
}) // JSONata formatting is not locale-aware
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Correct:**
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
step.addNumber({
|
|
397
|
+
name: 'price',
|
|
398
|
+
label: 'Price',
|
|
399
|
+
format: { numberStyle: 'currency', currency: 'USD' },
|
|
400
|
+
})
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Benefits of format property / FormatBuilder:**
|
|
404
|
+
|
|
405
|
+
- Validates options at build time
|
|
406
|
+
- Type-safe configuration
|
|
407
|
+
- Integrates with field rendering
|
|
408
|
+
- Handles locale automatically
|
|
409
|
+
|
|
410
|
+
## Best Practices
|
|
411
|
+
|
|
412
|
+
| Scenario | Approach |
|
|
413
|
+
| --- | --- |
|
|
414
|
+
| Disabled number field | Field `format` property |
|
|
415
|
+
| `submissionItemTitle` / `submissionItemSubtitle` with formatting | `I18nBuilder` + `FormatBuilder` |
|
|
416
|
+
| ArrayField `title` / `subtitle` / `description` with formatting | `I18nBuilder` + `FormatBuilder` |
|
|
417
|
+
| Step `title` with formatting | `I18nBuilder` + `FormatBuilder` |
|
|
418
|
+
| Date values in any display property | Always `FormatBuilder` (even without `I18nBuilder`) |
|
|
419
|
+
| Never | JSONata `$formatNumber()` / `$formatBase()` functions |
|
|
420
|
+
|
|
421
|
+
**Ref:** `./i18n.md` for internationalization patterns
|