@vectoriox/iox-ui 5.27.0 → 5.28.0
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/design-core/index.d.ts +144 -4
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +153 -5
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs +40 -5
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +72 -14
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/forms-core/index.d.ts +18 -2
- package/index.d.ts +13 -2
- package/package.json +1 -1
- package/src/design-core/flavor-spec.generated.ts +153 -5
- package/src/forms-core/collect.ts +3 -1
- package/src/forms-core/options.ts +29 -0
- package/src/forms-core/public-api.ts +1 -0
- package/src/forms-core/transport.ts +6 -2
- package/styles/iox-forms.css +136 -8
package/forms-core/index.d.ts
CHANGED
|
@@ -208,6 +208,18 @@ declare function collectFormSpec(node: FormNodeLike | null | undefined): FormSpe
|
|
|
208
208
|
/** Every Field in this subtree, in document order, not descending into a nested Form. */
|
|
209
209
|
declare function collectFieldNodes(nodes: readonly FormNodeLike[]): FormNodeLike[];
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* A Field's `optionsText` trait — the author's choices, one per line — as options. ONE parser for the
|
|
213
|
+
* builder canvas, both engine paths and iox-cms: until 2026-10-19 nothing parsed the trait, so a Radio
|
|
214
|
+
* group or a Dropdown from the tray rendered no choices at all on the canvas and on the live site.
|
|
215
|
+
*/
|
|
216
|
+
declare function parseFieldOptions(text: unknown): FieldOption[];
|
|
217
|
+
/** The choices a saved Field offers: an explicit `options` list wins, else its `optionsText` parsed. */
|
|
218
|
+
declare function fieldOptionsOf(props: {
|
|
219
|
+
options?: unknown;
|
|
220
|
+
optionsText?: unknown;
|
|
221
|
+
} | null | undefined): FieldOption[];
|
|
222
|
+
|
|
211
223
|
/** Everything the runtime needs to remember. Immutable — each transition returns a new state. */
|
|
212
224
|
interface FormRuntimeState {
|
|
213
225
|
/** Field names the visitor has left (blurred) at least once. */
|
|
@@ -343,7 +355,11 @@ type FormTransport = (request: SubmitRequest) => Promise<SubmitResponse>;
|
|
|
343
355
|
* The public submit path. Built here rather than interpolated at each call site, so the engine and
|
|
344
356
|
* any future host cannot disagree about it — and so changing it is one edit.
|
|
345
357
|
*
|
|
346
|
-
*
|
|
358
|
+
* ⚠️ ROOT-relative (leading slash), never base-relative. A live site is served under a per-org
|
|
359
|
+
* `<base href="/<label>/">`, so a relative `api/…` resolved to `/<label>/api/…` — a path the reverse
|
|
360
|
+
* proxy (`/api` → iox-cms, everything else → the engine) hands to the ENGINE, which answers a POST with
|
|
361
|
+
* 404: no visitor submission ever reached iox-cms from an org path (found 2026-10-19 by
|
|
362
|
+
* e2e:forminsert:real). The org is already in the path segment after `site/`.
|
|
347
363
|
*/
|
|
348
364
|
declare function submitEndpointPath(orgId: string, formId: string): string;
|
|
349
365
|
/**
|
|
@@ -363,5 +379,5 @@ declare function withAntiSpamFields(values: Record<string, any>, renderedAt: num
|
|
|
363
379
|
*/
|
|
364
380
|
declare function interpretSubmitResponse(body: any): SubmitResponse;
|
|
365
381
|
|
|
366
|
-
export { ControlType, DEFAULT_EN_MESSAGES, FIELD_NODE_TYPE, FORM_ERROR_KEYS, FORM_NODE_TYPE, FeedbackTiming, HONEYPOT_FIELD, IMPLIED_PATTERN, INITIAL_FORM_STATE, MESSAGE_NAMESPACE, MULTI_VALUE_CONTROLS, PATTERN_MESSAGE_KEYS, PATTERN_PRESETS, PATTERN_PRESET_OPTIONS, PatternPreset, RENDERED_AT_FIELD, RULE_DESCRIPTORS, RULE_MESSAGE_KEYS, RuleName, RuleParamKind, STORE_ACTION, addableRules, applyServerErrors, collectFieldNodes, collectFormSpec, deriveFieldName, firstInvalidField, interpretSubmitResponse, onFieldBlur, onFieldChange, onSubmitAttempt, resetFormState, rulesFor, shouldShowError, submitEndpointPath, uniqueFieldName, validateField, validateForm, withAntiSpamFields };
|
|
382
|
+
export { ControlType, DEFAULT_EN_MESSAGES, FIELD_NODE_TYPE, FORM_ERROR_KEYS, FORM_NODE_TYPE, FeedbackTiming, HONEYPOT_FIELD, IMPLIED_PATTERN, INITIAL_FORM_STATE, MESSAGE_NAMESPACE, MULTI_VALUE_CONTROLS, PATTERN_MESSAGE_KEYS, PATTERN_PRESETS, PATTERN_PRESET_OPTIONS, PatternPreset, RENDERED_AT_FIELD, RULE_DESCRIPTORS, RULE_MESSAGE_KEYS, RuleName, RuleParamKind, STORE_ACTION, addableRules, applyServerErrors, collectFieldNodes, collectFormSpec, deriveFieldName, fieldOptionsOf, firstInvalidField, interpretSubmitResponse, onFieldBlur, onFieldChange, onSubmitAttempt, parseFieldOptions, resetFormState, rulesFor, shouldShowError, submitEndpointPath, uniqueFieldName, validateField, validateForm, withAntiSpamFields };
|
|
367
383
|
export type { FieldError, FieldOption, FieldOptionSource, FieldResult, FieldSpec, FormNodeLike, FormResult, FormRuntimeState, FormSpec, FormTransport, RuleDescriptor, SubmitAction, SubmitRequest, SubmitResponse, ValidationRule };
|
package/index.d.ts
CHANGED
|
@@ -1189,7 +1189,17 @@ declare class IoxFieldComponent implements OnInit, OnDestroy {
|
|
|
1189
1189
|
rangeMax: number | string;
|
|
1190
1190
|
rangeStep: number | string;
|
|
1191
1191
|
accept: string;
|
|
1192
|
-
|
|
1192
|
+
/** Explicit choices (bound data, a hand-built node). Wins over `optionsText`. */
|
|
1193
|
+
set options(value: FieldOption[] | null | undefined);
|
|
1194
|
+
get options(): FieldOption[];
|
|
1195
|
+
/** The author's choices, one per line (`value|Label`) — the Field's trait. A SETTER: the builder
|
|
1196
|
+
* assigns traits directly, with no ngOnChanges. */
|
|
1197
|
+
set optionsText(value: string | null | undefined);
|
|
1198
|
+
get optionsText(): string;
|
|
1199
|
+
/** What the radio group / dropdown renders: the shared forms-core reading of the two inputs above. */
|
|
1200
|
+
choices: FieldOption[];
|
|
1201
|
+
private _options;
|
|
1202
|
+
private _optionsText;
|
|
1193
1203
|
value: any;
|
|
1194
1204
|
/** Rules beyond `required` — carried on the node, interpreted by forms-core. */
|
|
1195
1205
|
rules: ValidationRule[];
|
|
@@ -1225,6 +1235,7 @@ declare class IoxFieldComponent implements OnInit, OnDestroy {
|
|
|
1225
1235
|
/** The message a visitor reads. Translation is the consumer's, via IOX_FORM_MESSAGES. */
|
|
1226
1236
|
get errorText(): string;
|
|
1227
1237
|
private toSpec;
|
|
1238
|
+
private refreshChoices;
|
|
1228
1239
|
/** `file`, `date`, `number`… map straight through; everything else is a text input. */
|
|
1229
1240
|
get inputType(): string;
|
|
1230
1241
|
/** Help and error are BOTH announced, in that order, when both are present. */
|
|
@@ -1232,7 +1243,7 @@ declare class IoxFieldComponent implements OnInit, OnDestroy {
|
|
|
1232
1243
|
emit(value: any): void;
|
|
1233
1244
|
touch(): void;
|
|
1234
1245
|
static ɵfac: i0.ɵɵFactoryDeclaration<IoxFieldComponent, [{ optional: true; }, null]>;
|
|
1235
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<IoxFieldComponent, "div[iox-field]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; "name": { "alias": "name"; "required": false; }; "label": { "alias": "label"; "required": false; }; "control": { "alias": "control"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "help": { "alias": "help"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "labelHidden": { "alias": "labelHidden"; "required": false; }; "requiredMark": { "alias": "requiredMark"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "rangeMin": { "alias": "rangeMin"; "required": false; }; "rangeMax": { "alias": "rangeMax"; "required": false; }; "rangeStep": { "alias": "rangeStep"; "required": false; }; "accept": { "alias": "accept"; "required": false; }; "options": { "alias": "options"; "required": false; }; "value": { "alias": "value"; "required": false; }; "rules": { "alias": "rules"; "required": false; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; }; "designErrorText": { "alias": "designErrorText"; "required": false; }; "showValid": { "alias": "showValid"; "required": false; }; }, { "valueChange": "valueChange"; "touched": "touched"; }, never, never, false, never>;
|
|
1246
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<IoxFieldComponent, "div[iox-field]", never, { "nodeId": { "alias": "nodeId"; "required": false; }; "name": { "alias": "name"; "required": false; }; "label": { "alias": "label"; "required": false; }; "control": { "alias": "control"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "help": { "alias": "help"; "required": false; }; "required": { "alias": "required"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "labelHidden": { "alias": "labelHidden"; "required": false; }; "requiredMark": { "alias": "requiredMark"; "required": false; }; "rows": { "alias": "rows"; "required": false; }; "rangeMin": { "alias": "rangeMin"; "required": false; }; "rangeMax": { "alias": "rangeMax"; "required": false; }; "rangeStep": { "alias": "rangeStep"; "required": false; }; "accept": { "alias": "accept"; "required": false; }; "options": { "alias": "options"; "required": false; }; "optionsText": { "alias": "optionsText"; "required": false; }; "value": { "alias": "value"; "required": false; }; "rules": { "alias": "rules"; "required": false; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; }; "designErrorText": { "alias": "designErrorText"; "required": false; }; "showValid": { "alias": "showValid"; "required": false; }; }, { "valueChange": "valueChange"; "touched": "touched"; }, never, never, false, never>;
|
|
1236
1247
|
}
|
|
1237
1248
|
|
|
1238
1249
|
/**
|
package/package.json
CHANGED
|
@@ -1811,7 +1811,8 @@ export const SPEC_FIELD_CLASS_BASELINE = {
|
|
|
1811
1811
|
".iox-es-field .iox-field__control:focus-visible": {
|
|
1812
1812
|
"border-color": "var(--iox-accent-text)",
|
|
1813
1813
|
"box-shadow": "var(--iox-field-focus)",
|
|
1814
|
-
"outline": "
|
|
1814
|
+
"outline": "2px solid transparent",
|
|
1815
|
+
"outline-offset": "2px"
|
|
1815
1816
|
},
|
|
1816
1817
|
".iox-es-field .iox-field__label": {
|
|
1817
1818
|
"font-family": "var(--iox-font-body)",
|
|
@@ -1819,6 +1820,12 @@ export const SPEC_FIELD_CLASS_BASELINE = {
|
|
|
1819
1820
|
"font-weight": "var(--iox-field-label-weight)",
|
|
1820
1821
|
"color": "var(--iox-text)"
|
|
1821
1822
|
},
|
|
1823
|
+
".iox-es-field .iox-field__option": {
|
|
1824
|
+
"font-family": "var(--iox-font-body)",
|
|
1825
|
+
"font-size": "var(--iox-size-body)",
|
|
1826
|
+
"color": "var(--iox-text)",
|
|
1827
|
+
"cursor": "pointer"
|
|
1828
|
+
},
|
|
1822
1829
|
".iox-es-field .iox-field__help": {
|
|
1823
1830
|
"font-size": "var(--iox-size-small)",
|
|
1824
1831
|
"color": "var(--iox-text-muted)"
|
|
@@ -1834,11 +1841,121 @@ export const SPEC_FIELD_CLASS_BASELINE = {
|
|
|
1834
1841
|
"color": "var(--iox-text-muted)",
|
|
1835
1842
|
"opacity": "1"
|
|
1836
1843
|
},
|
|
1837
|
-
".iox-es-field .iox-field__control:
|
|
1844
|
+
".iox-es-field .iox-field__control:disabled": {
|
|
1845
|
+
"opacity": "0.55",
|
|
1846
|
+
"cursor": "not-allowed"
|
|
1847
|
+
},
|
|
1848
|
+
".iox-es-field .iox-field__control:where(.iox-field__control--switch, [type='range'])": {
|
|
1838
1849
|
"border": "none",
|
|
1839
1850
|
"padding": "0",
|
|
1840
|
-
"background": "transparent"
|
|
1841
|
-
|
|
1851
|
+
"background": "transparent"
|
|
1852
|
+
},
|
|
1853
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio'])": {
|
|
1854
|
+
"appearance": "none",
|
|
1855
|
+
"-webkit-appearance": "none",
|
|
1856
|
+
"display": "inline-grid",
|
|
1857
|
+
"place-content": "center",
|
|
1858
|
+
"flex-shrink": "0",
|
|
1859
|
+
"margin": "0",
|
|
1860
|
+
"padding": "0",
|
|
1861
|
+
"inline-size": "1.25em",
|
|
1862
|
+
"block-size": "1.25em",
|
|
1863
|
+
"vertical-align": "middle",
|
|
1864
|
+
"cursor": "pointer",
|
|
1865
|
+
"background": "var(--iox-surface)",
|
|
1866
|
+
"border-color": "var(--iox-line-strong)",
|
|
1867
|
+
"border-radius": "var(--iox-radius-xs)",
|
|
1868
|
+
"transition": "background var(--iox-dur-fast) var(--iox-ease-standard), border-color var(--iox-dur-fast) var(--iox-ease-standard), box-shadow var(--iox-dur-fast) var(--iox-ease-standard)"
|
|
1869
|
+
},
|
|
1870
|
+
".iox-es-field .iox-field__control[type='radio']": {
|
|
1871
|
+
"border-radius": "50%"
|
|
1872
|
+
},
|
|
1873
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio'])::before": {
|
|
1874
|
+
"content": "''",
|
|
1875
|
+
"inline-size": "0.28em",
|
|
1876
|
+
"block-size": "0.56em",
|
|
1877
|
+
"border-style": "solid",
|
|
1878
|
+
"border-color": "var(--iox-accent-ink)",
|
|
1879
|
+
"border-width": "0 0.14em 0.14em 0",
|
|
1880
|
+
"transform": "translateY(-0.08em) rotate(45deg) scale(0)",
|
|
1881
|
+
"transition": "transform var(--iox-dur-fast) var(--iox-ease-standard)"
|
|
1882
|
+
},
|
|
1883
|
+
".iox-es-field .iox-field__control[type='radio']::before": {
|
|
1884
|
+
"inline-size": "0",
|
|
1885
|
+
"block-size": "0",
|
|
1886
|
+
"border-width": "0.25em",
|
|
1887
|
+
"border-radius": "50%",
|
|
1888
|
+
"transform": "scale(0)"
|
|
1889
|
+
},
|
|
1890
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):hover": {
|
|
1891
|
+
"border-color": "var(--iox-accent-text)"
|
|
1892
|
+
},
|
|
1893
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked": {
|
|
1894
|
+
"background": "var(--iox-accent)",
|
|
1895
|
+
"border-color": "var(--iox-accent-text)"
|
|
1896
|
+
},
|
|
1897
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked:hover": {
|
|
1898
|
+
"background": "var(--iox-accent-hover)"
|
|
1899
|
+
},
|
|
1900
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked::before": {
|
|
1901
|
+
"transform": "translateY(-0.08em) rotate(45deg) scale(1)"
|
|
1902
|
+
},
|
|
1903
|
+
".iox-es-field .iox-field__control[type='radio']:checked::before": {
|
|
1904
|
+
"transform": "scale(1)"
|
|
1905
|
+
},
|
|
1906
|
+
".iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):focus-visible": {
|
|
1907
|
+
"box-shadow": "var(--iox-focus-ring)"
|
|
1908
|
+
},
|
|
1909
|
+
".iox-es-field .iox-field__control:where([type='range'])": {
|
|
1910
|
+
"appearance": "none",
|
|
1911
|
+
"-webkit-appearance": "none",
|
|
1912
|
+
"inline-size": "100%",
|
|
1913
|
+
"block-size": "1.25em",
|
|
1914
|
+
"cursor": "pointer"
|
|
1915
|
+
},
|
|
1916
|
+
".iox-es-field .iox-field__control:where([type='range'])::-webkit-slider-runnable-track": {
|
|
1917
|
+
"block-size": "0.25em",
|
|
1918
|
+
"border-radius": "var(--iox-radius-pill)",
|
|
1919
|
+
"background": "var(--iox-line-strong)",
|
|
1920
|
+
"outline": "1px solid transparent"
|
|
1921
|
+
},
|
|
1922
|
+
".iox-es-field .iox-field__control:where([type='range'])::-moz-range-track": {
|
|
1923
|
+
"block-size": "0.25em",
|
|
1924
|
+
"border-radius": "var(--iox-radius-pill)",
|
|
1925
|
+
"background": "var(--iox-line-strong)",
|
|
1926
|
+
"outline": "1px solid transparent"
|
|
1927
|
+
},
|
|
1928
|
+
".iox-es-field .iox-field__control:where([type='range'])::-webkit-slider-thumb": {
|
|
1929
|
+
"appearance": "none",
|
|
1930
|
+
"-webkit-appearance": "none",
|
|
1931
|
+
"inline-size": "1.25em",
|
|
1932
|
+
"block-size": "1.25em",
|
|
1933
|
+
"border-radius": "50%",
|
|
1934
|
+
"background": "var(--iox-accent-text)",
|
|
1935
|
+
"border": "2px solid var(--iox-bg)",
|
|
1936
|
+
"box-sizing": "border-box",
|
|
1937
|
+
"cursor": "pointer",
|
|
1938
|
+
"margin-block-start": "-0.5em"
|
|
1939
|
+
},
|
|
1940
|
+
".iox-es-field .iox-field__control:where([type='range'])::-moz-range-thumb": {
|
|
1941
|
+
"appearance": "none",
|
|
1942
|
+
"-webkit-appearance": "none",
|
|
1943
|
+
"inline-size": "1.25em",
|
|
1944
|
+
"block-size": "1.25em",
|
|
1945
|
+
"border-radius": "50%",
|
|
1946
|
+
"background": "var(--iox-accent-text)",
|
|
1947
|
+
"border": "2px solid var(--iox-bg)",
|
|
1948
|
+
"box-sizing": "border-box",
|
|
1949
|
+
"cursor": "pointer"
|
|
1950
|
+
},
|
|
1951
|
+
".iox-es-field .iox-field__control:where([type='range']):focus-visible": {
|
|
1952
|
+
"box-shadow": "none"
|
|
1953
|
+
},
|
|
1954
|
+
".iox-es-field .iox-field__control:where([type='range']):focus-visible::-webkit-slider-thumb": {
|
|
1955
|
+
"box-shadow": "var(--iox-focus-ring)"
|
|
1956
|
+
},
|
|
1957
|
+
".iox-es-field .iox-field__control:where([type='range']):focus-visible::-moz-range-thumb": {
|
|
1958
|
+
"box-shadow": "var(--iox-focus-ring)"
|
|
1842
1959
|
},
|
|
1843
1960
|
".iox-es-field .iox-field__control--switch": {
|
|
1844
1961
|
"background": "var(--iox-text-muted)",
|
|
@@ -2044,9 +2161,40 @@ export const SPEC_CONTRAST_AUDIT = {
|
|
|
2044
2161
|
"min": 3
|
|
2045
2162
|
},
|
|
2046
2163
|
{
|
|
2047
|
-
"part": "checkbox · radio
|
|
2164
|
+
"part": "checkbox · radio edge",
|
|
2165
|
+
"fg": "var(--iox-line-strong)",
|
|
2166
|
+
"min": 3
|
|
2167
|
+
},
|
|
2168
|
+
{
|
|
2169
|
+
"part": "checkbox · radio edge on its fill",
|
|
2170
|
+
"fg": "var(--iox-line-strong)",
|
|
2171
|
+
"on": "var(--iox-surface)",
|
|
2172
|
+
"min": 3
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
"part": "checkbox · radio checked",
|
|
2176
|
+
"fg": [
|
|
2177
|
+
"var(--iox-accent-text)",
|
|
2178
|
+
"var(--iox-accent)"
|
|
2179
|
+
],
|
|
2180
|
+
"any": true,
|
|
2181
|
+
"min": 3
|
|
2182
|
+
},
|
|
2183
|
+
{
|
|
2184
|
+
"part": "check mark · radio dot",
|
|
2185
|
+
"fg": "var(--iox-accent-ink)",
|
|
2186
|
+
"on": "var(--iox-accent)",
|
|
2187
|
+
"min": 3
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
"part": "hover edge · range thumb",
|
|
2048
2191
|
"fg": "var(--iox-accent-text)",
|
|
2049
2192
|
"min": 3
|
|
2193
|
+
},
|
|
2194
|
+
{
|
|
2195
|
+
"part": "range track",
|
|
2196
|
+
"fg": "var(--iox-line-strong)",
|
|
2197
|
+
"min": 3
|
|
2050
2198
|
}
|
|
2051
2199
|
]
|
|
2052
2200
|
} as const;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
SubmitAction,
|
|
16
16
|
ValidationRule,
|
|
17
17
|
} from './form.model';
|
|
18
|
+
import { fieldOptionsOf } from './options';
|
|
18
19
|
|
|
19
20
|
/** The minimum a node must look like. Structural on purpose: this file knows no renderer. */
|
|
20
21
|
export interface FormNodeLike {
|
|
@@ -117,7 +118,8 @@ function toFieldSpec(node: FormNodeLike, taken: ReadonlySet<string>): FieldSpec
|
|
|
117
118
|
if (props['required']) spec.required = true;
|
|
118
119
|
const rules = normaliseRules(props['rules']);
|
|
119
120
|
if (rules.length) spec.rules = rules;
|
|
120
|
-
|
|
121
|
+
const options = fieldOptionsOf(props);
|
|
122
|
+
if (options.length) spec.options = options;
|
|
121
123
|
if (props['optionsSource']) spec.optionsSource = props['optionsSource'];
|
|
122
124
|
if (props['defaultValue'] !== undefined && props['defaultValue'] !== '') {
|
|
123
125
|
spec.defaultValue = props['defaultValue'];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FieldOption } from './form.model';
|
|
2
|
+
|
|
3
|
+
/** `value|Label` separates a submitted value from what the visitor reads; a bare line is both. */
|
|
4
|
+
const OPTION_SEPARATOR = '|';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Field's `optionsText` trait — the author's choices, one per line — as options. ONE parser for the
|
|
8
|
+
* builder canvas, both engine paths and iox-cms: until 2026-10-19 nothing parsed the trait, so a Radio
|
|
9
|
+
* group or a Dropdown from the tray rendered no choices at all on the canvas and on the live site.
|
|
10
|
+
*/
|
|
11
|
+
export function parseFieldOptions(text: unknown): FieldOption[] {
|
|
12
|
+
if (typeof text !== 'string') return [];
|
|
13
|
+
const out: FieldOption[] = [];
|
|
14
|
+
for (const line of text.split(/\r?\n/)) {
|
|
15
|
+
const raw = line.trim();
|
|
16
|
+
if (!raw) continue;
|
|
17
|
+
const at = raw.indexOf(OPTION_SEPARATOR);
|
|
18
|
+
const value = (at === -1 ? raw : raw.slice(0, at)).trim();
|
|
19
|
+
const label = (at === -1 ? raw : raw.slice(at + 1)).trim() || value;
|
|
20
|
+
if (value) out.push({ value, label });
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The choices a saved Field offers: an explicit `options` list wins, else its `optionsText` parsed. */
|
|
26
|
+
export function fieldOptionsOf(props: { options?: unknown; optionsText?: unknown } | null | undefined): FieldOption[] {
|
|
27
|
+
if (Array.isArray(props?.options) && props!.options.length) return props!.options as FieldOption[];
|
|
28
|
+
return parseFieldOptions(props?.optionsText);
|
|
29
|
+
}
|
|
@@ -49,10 +49,14 @@ export type FormTransport = (request: SubmitRequest) => Promise<SubmitResponse>;
|
|
|
49
49
|
* The public submit path. Built here rather than interpolated at each call site, so the engine and
|
|
50
50
|
* any future host cannot disagree about it — and so changing it is one edit.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
52
|
+
* ⚠️ ROOT-relative (leading slash), never base-relative. A live site is served under a per-org
|
|
53
|
+
* `<base href="/<label>/">`, so a relative `api/…` resolved to `/<label>/api/…` — a path the reverse
|
|
54
|
+
* proxy (`/api` → iox-cms, everything else → the engine) hands to the ENGINE, which answers a POST with
|
|
55
|
+
* 404: no visitor submission ever reached iox-cms from an org path (found 2026-10-19 by
|
|
56
|
+
* e2e:forminsert:real). The org is already in the path segment after `site/`.
|
|
53
57
|
*/
|
|
54
58
|
export function submitEndpointPath(orgId: string, formId: string): string {
|
|
55
|
-
return
|
|
59
|
+
return `/api/site/${encodeURIComponent(orgId)}/forms/${encodeURIComponent(formId)}/submit`;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
/**
|
package/styles/iox-forms.css
CHANGED
|
@@ -119,7 +119,8 @@
|
|
|
119
119
|
.iox-es-field .iox-field__control:focus-visible {
|
|
120
120
|
border-color: var(--iox-accent-text);
|
|
121
121
|
box-shadow: var(--iox-field-focus);
|
|
122
|
-
outline:
|
|
122
|
+
outline: 2px solid transparent;
|
|
123
|
+
outline-offset: 2px;
|
|
123
124
|
}
|
|
124
125
|
.iox-es-field .iox-field__label {
|
|
125
126
|
font-family: var(--iox-font-body);
|
|
@@ -127,6 +128,13 @@
|
|
|
127
128
|
font-weight: var(--iox-field-label-weight);
|
|
128
129
|
color: var(--iox-text);
|
|
129
130
|
}
|
|
131
|
+
/* A radio's choice reads like the label beside it: the look's body type in the look's text. */
|
|
132
|
+
.iox-es-field .iox-field__option {
|
|
133
|
+
font-family: var(--iox-font-body);
|
|
134
|
+
font-size: var(--iox-size-body);
|
|
135
|
+
color: var(--iox-text);
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
}
|
|
130
138
|
.iox-es-field .iox-field__help {
|
|
131
139
|
font-size: var(--iox-size-small);
|
|
132
140
|
color: var(--iox-text-muted);
|
|
@@ -139,21 +147,141 @@
|
|
|
139
147
|
border-color: var(--iox-danger);
|
|
140
148
|
}
|
|
141
149
|
/* DS9 (contrast audit): the placeholder is themed (the UA's #757575 read 1:1 in a Brand section); the
|
|
142
|
-
|
|
150
|
+
focus edge, the switch and the range thumb read `accent-text` — the accent GATED as text on every
|
|
143
151
|
ground — never the `accent` fill (Pop's orange on its yellow panel: 2.13:1); the off track is
|
|
144
|
-
`text-muted` under a `bg` thumb (the decorative `line` it wore read 1.06:1). WCAG 1.4.11: 3:1.
|
|
152
|
+
`text-muted` under a `bg` thumb (the decorative `line` it wore read 1.06:1). WCAG 1.4.11: 3:1.
|
|
153
|
+
Focus: the transparent outline is what forced-colors mode paints once it drops the box-shadow ring. */
|
|
145
154
|
.iox-es-field .iox-field__control::placeholder {
|
|
146
155
|
color: var(--iox-text-muted);
|
|
147
156
|
opacity: 1;
|
|
148
157
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
.iox-es-field .iox-field__control:disabled {
|
|
159
|
+
opacity: 0.55;
|
|
160
|
+
cursor: not-allowed;
|
|
161
|
+
}
|
|
162
|
+
/* The switch and the range draw themselves: the box baseline must not frame them. `:where()` keeps this
|
|
163
|
+
at the baseline's specificity, so the switch rules below still paint the track. */
|
|
164
|
+
.iox-es-field .iox-field__control:where(.iox-field__control--switch, [type='range']) {
|
|
153
165
|
border: none;
|
|
154
166
|
padding: 0;
|
|
155
167
|
background: transparent;
|
|
156
|
-
|
|
168
|
+
}
|
|
169
|
+
/* Checkbox and radio, drawn by the look (2026-10-19 — `accent-color` only TINTED the browser's own box,
|
|
170
|
+
so every flavor showed a browser default). 1.25em of the control's own size (the body step) · the
|
|
171
|
+
field's edge width in `line-strong` (≥ 3:1 on every ground) · the surface fill · `radius-xs` (a radio
|
|
172
|
+
is round) · checked: the `accent` fill, an `accent-text` edge and a mark in `accent-ink` (≥ 4.5:1 on
|
|
173
|
+
the fill). The mark is a BORDER — a rotated corner, a radio's dot a zero-size circle of border — so
|
|
174
|
+
forced-colors mode repaints it in the system ink (a background mark would vanish). Not mirrored in
|
|
175
|
+
RTL: a check mark is not a direction. Every motion reads dur-fast, which reduced motion sets to 0ms. */
|
|
176
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']) {
|
|
177
|
+
appearance: none;
|
|
178
|
+
-webkit-appearance: none;
|
|
179
|
+
display: inline-grid;
|
|
180
|
+
place-content: center;
|
|
181
|
+
flex-shrink: 0;
|
|
182
|
+
margin: 0;
|
|
183
|
+
padding: 0;
|
|
184
|
+
inline-size: 1.25em;
|
|
185
|
+
block-size: 1.25em;
|
|
186
|
+
vertical-align: middle;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
background: var(--iox-surface);
|
|
189
|
+
border-color: var(--iox-line-strong);
|
|
190
|
+
border-radius: var(--iox-radius-xs);
|
|
191
|
+
transition: background var(--iox-dur-fast) var(--iox-ease-standard), border-color var(--iox-dur-fast) var(--iox-ease-standard), box-shadow var(--iox-dur-fast) var(--iox-ease-standard);
|
|
192
|
+
}
|
|
193
|
+
.iox-es-field .iox-field__control[type='radio'] {
|
|
194
|
+
border-radius: 50%;
|
|
195
|
+
}
|
|
196
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio'])::before {
|
|
197
|
+
content: '';
|
|
198
|
+
inline-size: 0.28em;
|
|
199
|
+
block-size: 0.56em;
|
|
200
|
+
border-style: solid;
|
|
201
|
+
border-color: var(--iox-accent-ink);
|
|
202
|
+
border-width: 0 0.14em 0.14em 0;
|
|
203
|
+
transform: translateY(-0.08em) rotate(45deg) scale(0);
|
|
204
|
+
transition: transform var(--iox-dur-fast) var(--iox-ease-standard);
|
|
205
|
+
}
|
|
206
|
+
.iox-es-field .iox-field__control[type='radio']::before {
|
|
207
|
+
inline-size: 0;
|
|
208
|
+
block-size: 0;
|
|
209
|
+
border-width: 0.25em;
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
transform: scale(0);
|
|
212
|
+
}
|
|
213
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):hover {
|
|
214
|
+
border-color: var(--iox-accent-text);
|
|
215
|
+
}
|
|
216
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked {
|
|
217
|
+
background: var(--iox-accent);
|
|
218
|
+
border-color: var(--iox-accent-text);
|
|
219
|
+
}
|
|
220
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked:hover {
|
|
221
|
+
background: var(--iox-accent-hover);
|
|
222
|
+
}
|
|
223
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):checked::before {
|
|
224
|
+
transform: translateY(-0.08em) rotate(45deg) scale(1);
|
|
225
|
+
}
|
|
226
|
+
.iox-es-field .iox-field__control[type='radio']:checked::before {
|
|
227
|
+
transform: scale(1);
|
|
228
|
+
}
|
|
229
|
+
.iox-es-field .iox-field__control:where([type='checkbox']:not(.iox-field__control--switch), [type='radio']):focus-visible {
|
|
230
|
+
box-shadow: var(--iox-focus-ring);
|
|
231
|
+
}
|
|
232
|
+
/* Range: the track is `line-strong`, the thumb `accent-text` on a `bg` ring, the focus ring on the THUMB.
|
|
233
|
+
One rule per vendor pseudo-element — a selector list mixing -webkit- and -moz- is dropped whole by
|
|
234
|
+
the engine that does not know the other. */
|
|
235
|
+
.iox-es-field .iox-field__control:where([type='range']) {
|
|
236
|
+
appearance: none;
|
|
237
|
+
-webkit-appearance: none;
|
|
238
|
+
inline-size: 100%;
|
|
239
|
+
block-size: 1.25em;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
.iox-es-field .iox-field__control:where([type='range'])::-webkit-slider-runnable-track {
|
|
243
|
+
block-size: 0.25em;
|
|
244
|
+
border-radius: var(--iox-radius-pill);
|
|
245
|
+
background: var(--iox-line-strong);
|
|
246
|
+
outline: 1px solid transparent;
|
|
247
|
+
}
|
|
248
|
+
.iox-es-field .iox-field__control:where([type='range'])::-moz-range-track {
|
|
249
|
+
block-size: 0.25em;
|
|
250
|
+
border-radius: var(--iox-radius-pill);
|
|
251
|
+
background: var(--iox-line-strong);
|
|
252
|
+
outline: 1px solid transparent;
|
|
253
|
+
}
|
|
254
|
+
.iox-es-field .iox-field__control:where([type='range'])::-webkit-slider-thumb {
|
|
255
|
+
appearance: none;
|
|
256
|
+
-webkit-appearance: none;
|
|
257
|
+
inline-size: 1.25em;
|
|
258
|
+
block-size: 1.25em;
|
|
259
|
+
border-radius: 50%;
|
|
260
|
+
background: var(--iox-accent-text);
|
|
261
|
+
border: 2px solid var(--iox-bg);
|
|
262
|
+
box-sizing: border-box;
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
margin-block-start: -0.5em;
|
|
265
|
+
}
|
|
266
|
+
.iox-es-field .iox-field__control:where([type='range'])::-moz-range-thumb {
|
|
267
|
+
appearance: none;
|
|
268
|
+
-webkit-appearance: none;
|
|
269
|
+
inline-size: 1.25em;
|
|
270
|
+
block-size: 1.25em;
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
background: var(--iox-accent-text);
|
|
273
|
+
border: 2px solid var(--iox-bg);
|
|
274
|
+
box-sizing: border-box;
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
}
|
|
277
|
+
.iox-es-field .iox-field__control:where([type='range']):focus-visible {
|
|
278
|
+
box-shadow: none;
|
|
279
|
+
}
|
|
280
|
+
.iox-es-field .iox-field__control:where([type='range']):focus-visible::-webkit-slider-thumb {
|
|
281
|
+
box-shadow: var(--iox-focus-ring);
|
|
282
|
+
}
|
|
283
|
+
.iox-es-field .iox-field__control:where([type='range']):focus-visible::-moz-range-thumb {
|
|
284
|
+
box-shadow: var(--iox-focus-ring);
|
|
157
285
|
}
|
|
158
286
|
.iox-es-field .iox-field__control--switch {
|
|
159
287
|
background: var(--iox-text-muted);
|