@vectoriox/iox-ui 5.82.2 → 5.83.1
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 +200 -0
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +215 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs +733 -56
- package/fesm2022/vectoriox-iox-ui-forms-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +40 -4
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +1559 -135
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/forms-core/index.d.ts +366 -18
- package/generation-core/index.d.ts +3 -0
- package/index.d.ts +307 -25
- package/package.json +1 -1
- package/src/design-core/contrast-audit.ts +4 -0
- package/src/design-core/flavor-spec.generated.ts +209 -1
- package/src/forms-core/channel.ts +85 -0
- package/src/forms-core/collect.ts +63 -30
- package/src/forms-core/dates.ts +299 -0
- package/src/forms-core/field-markup.ts +159 -0
- package/src/forms-core/form-presets.ts +51 -2
- package/src/forms-core/form.model.ts +34 -2
- package/src/forms-core/messages.ts +31 -0
- package/src/forms-core/public-api.ts +3 -0
- package/src/forms-core/rule-catalog.ts +3 -2
- package/src/forms-core/submit-lifecycle.ts +42 -12
- package/src/forms-core/validate.ts +90 -4
- package/src/generation-core/audit-site.ts +9 -1
- package/src/generation-core/compose-site.ts +22 -3
- package/src/generation-core/node-baselines.generated.ts +10 -0
- package/src/generation-core/plan-wire.ts +2 -2
- package/styles/iox-design.css +10 -0
- package/styles/iox-forms.css +358 -10
package/forms-core/index.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ declare enum ControlType {
|
|
|
16
16
|
/** A slider over a numeric range — min/max/step ride the field. */
|
|
17
17
|
Range = "range",
|
|
18
18
|
Radio = "radio",
|
|
19
|
+
/** One or several of a few visible choices — chips (the default) or a list of radios / checkboxes (premium N1).
|
|
20
|
+
* Its `multiple` trait picks the value's shape: a string, or a list of strings. */
|
|
21
|
+
Choices = "choices",
|
|
22
|
+
/** A date the visitor picks from the look's calendar (premium N8) — a button that reads the date in words and a
|
|
23
|
+
* hidden input carrying the ISO `YYYY-MM-DD` value. Never the browser's own date box. */
|
|
19
24
|
Date = "date",
|
|
20
25
|
Time = "time",
|
|
21
26
|
File = "file",
|
|
@@ -35,7 +40,19 @@ declare enum RuleName {
|
|
|
35
40
|
/** A checkbox that must be ticked — terms, consent. */
|
|
36
41
|
Accepted = "accepted",
|
|
37
42
|
FileType = "fileType",
|
|
38
|
-
FileSize = "fileSize"
|
|
43
|
+
FileSize = "fileSize",
|
|
44
|
+
/** IMPLIED, never authored: an answer of a Choices / Radio / Dropdown must be one of the field's own options
|
|
45
|
+
* (premium N1) — a hand-crafted POST cannot file a value the form never offered. */
|
|
46
|
+
OneOf = "oneOf",
|
|
47
|
+
/** IMPLIED, never authored: a Date's answer is a real `YYYY-MM-DD` day (premium N8). */
|
|
48
|
+
ValidDate = "validDate"
|
|
49
|
+
}
|
|
50
|
+
/** How a Choices field draws its options (premium N1). */
|
|
51
|
+
declare enum ChoiceAppearance {
|
|
52
|
+
/** Each option a chip — the label is the chip; the native input inside it is visually hidden. */
|
|
53
|
+
Chips = "chips",
|
|
54
|
+
/** The options as rows of the look's own radio / checkbox (DS9.1). */
|
|
55
|
+
List = "list"
|
|
39
56
|
}
|
|
40
57
|
/** Named regexes so an author picks "Email" rather than writing one. */
|
|
41
58
|
declare enum PatternPreset {
|
|
@@ -81,6 +98,8 @@ interface FieldSpec {
|
|
|
81
98
|
options?: FieldOption[];
|
|
82
99
|
optionsSource?: FieldOptionSource;
|
|
83
100
|
defaultValue?: any;
|
|
101
|
+
/** A Choices field that takes several answers: its value is a list (premium N1). */
|
|
102
|
+
multiple?: boolean;
|
|
84
103
|
}
|
|
85
104
|
/** When a field is validated, once the author has stopped deciding it per field. */
|
|
86
105
|
declare enum FeedbackTiming {
|
|
@@ -117,8 +136,15 @@ interface FormSpec {
|
|
|
117
136
|
* validation — the single biggest reason the panel can stay empty and still validate well.
|
|
118
137
|
*/
|
|
119
138
|
declare const IMPLIED_PATTERN: Readonly<Partial<Record<ControlType, PatternPreset>>>;
|
|
120
|
-
/**
|
|
121
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Whether this field's answer is a LIST rather than one value — a file list, or a Choices field that takes several
|
|
141
|
+
* (premium N1). Per FIELD, not per control: the same Choices control is single or multiple by its `multiple` trait.
|
|
142
|
+
* ONE reading for collect, validate, the runtimes' transport and the server (replaced the control-only
|
|
143
|
+
* `MULTI_VALUE_CONTROLS`, which nothing read).
|
|
144
|
+
*/
|
|
145
|
+
declare function isMultiValueField(field: Pick<FieldSpec, 'control' | 'multiple'> | null | undefined): boolean;
|
|
146
|
+
/** The controls whose answers must be one of the field's own options (the implied `oneOf` rule). */
|
|
147
|
+
declare const OPTION_CONTROLS: ReadonlySet<ControlType>;
|
|
122
148
|
|
|
123
149
|
/** Namespace for every forms validation key. Keep in sync with `en.json` / `he.json`. */
|
|
124
150
|
declare const MESSAGE_NAMESPACE = "forms.validation";
|
|
@@ -127,6 +153,24 @@ declare const MESSAGE_NAMESPACE = "forms.validation";
|
|
|
127
153
|
* can place the number wherever its grammar needs it, rather than having it baked into the string.
|
|
128
154
|
*/
|
|
129
155
|
declare const RULE_MESSAGE_KEYS: Readonly<Record<RuleName, string>>;
|
|
156
|
+
/**
|
|
157
|
+
* What "required" says on a field you CHOOSE rather than type into (premium N1 / N8): "Choose an option", "Choose at
|
|
158
|
+
* least one", "Choose a date" — never "This field is required" beside a row of chips.
|
|
159
|
+
*/
|
|
160
|
+
declare const CHOICE_MESSAGE_KEYS: Readonly<{
|
|
161
|
+
One: "forms.validation.choose";
|
|
162
|
+
Some: "forms.validation.chooseSome";
|
|
163
|
+
Date: "forms.validation.chooseDate";
|
|
164
|
+
}>;
|
|
165
|
+
/** A Date outside its Earliest / Latest. The calendar refuses those days, so this is a stale page or a hand POST. */
|
|
166
|
+
declare const DATE_MESSAGE_KEYS: Readonly<{
|
|
167
|
+
Min: "forms.validation.dateMin";
|
|
168
|
+
Max: "forms.validation.dateMax";
|
|
169
|
+
}>;
|
|
170
|
+
/** The submit button's own words (premium N2): what it says once its form has sent, when the author wrote none. */
|
|
171
|
+
declare const SUBMIT_MESSAGE_KEYS: Readonly<{
|
|
172
|
+
Sent: "forms.submit.sent";
|
|
173
|
+
}>;
|
|
130
174
|
/**
|
|
131
175
|
* A pattern failure deserves better than "Invalid format". Each preset gets its own key so the
|
|
132
176
|
* message can say what a valid value looks like, which is the difference between a visitor fixing
|
|
@@ -158,6 +202,15 @@ interface FormResult {
|
|
|
158
202
|
/** Keyed by field name. Empty when valid. */
|
|
159
203
|
errors: Record<string, FieldError>;
|
|
160
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* When "today" is, for a Date's relative bounds (premium N8). A browser leaves it out: its own local day is the
|
|
207
|
+
* visitor's. The SERVER passes `dateSlackDays: 1` — it cannot know the visitor's time zone, so a day either side of
|
|
208
|
+
* its own today is still accepted.
|
|
209
|
+
*/
|
|
210
|
+
interface ValidateOptions {
|
|
211
|
+
today?: string;
|
|
212
|
+
dateSlackDays?: number;
|
|
213
|
+
}
|
|
161
214
|
/**
|
|
162
215
|
* Validate one field's value.
|
|
163
216
|
*
|
|
@@ -169,12 +222,19 @@ interface FormResult {
|
|
|
169
222
|
* number" at someone who simply left it blank — the single most common validation-UX bug.
|
|
170
223
|
* The FIRST failure wins: a visitor fixes one thing at a time.
|
|
171
224
|
*/
|
|
172
|
-
declare function validateField(value: unknown, spec: FieldSpec, form?: Record<string, unknown
|
|
225
|
+
declare function validateField(value: unknown, spec: FieldSpec, form?: Record<string, unknown>, options?: ValidateOptions): FieldResult;
|
|
173
226
|
/**
|
|
174
227
|
* Validate a whole submission. Every field is checked (not short-circuited at the first bad one) so
|
|
175
228
|
* the visitor sees every problem at once instead of fixing them one reload at a time.
|
|
176
229
|
*/
|
|
177
|
-
declare function validateForm(values: Record<string, unknown>, spec: FormSpec): FormResult;
|
|
230
|
+
declare function validateForm(values: Record<string, unknown>, spec: FormSpec, options?: ValidateOptions): FormResult;
|
|
231
|
+
/**
|
|
232
|
+
* The answers in the shape each field takes (premium N1): a field that takes several answers gets a LIST (one answer
|
|
233
|
+
* posted on its own is a list of one; nothing is an empty list). ONE reading for a host's transport and the server,
|
|
234
|
+
* before validation — `isMultiValueField` decides. A single-answer field is left as posted: two answers to it is the
|
|
235
|
+
* validator's to refuse, never quietly cut to one.
|
|
236
|
+
*/
|
|
237
|
+
declare function normaliseAnswers(values: Record<string, unknown> | null | undefined, spec: Pick<FormSpec, 'fields'>): Record<string, unknown>;
|
|
178
238
|
/** The name of the first field with an error, in spec order — what the browser should focus. */
|
|
179
239
|
declare function firstInvalidField(spec: FormSpec, errors: Record<string, FieldError>): string | null;
|
|
180
240
|
|
|
@@ -198,6 +258,27 @@ declare const STORE_ACTION: SubmitAction;
|
|
|
198
258
|
declare function deriveFieldName(label: string): string;
|
|
199
259
|
/** Make `name` unique within `taken`, appending 2, 3, … — two "Email" fields must not collide. */
|
|
200
260
|
declare function uniqueFieldName(name: string, taken: ReadonlySet<string>): string;
|
|
261
|
+
/** A Field's own key, before the form makes it unique: the author's override, else derived from the label. */
|
|
262
|
+
declare function baseFieldName(props: {
|
|
263
|
+
name?: unknown;
|
|
264
|
+
label?: unknown;
|
|
265
|
+
} | null | undefined): string;
|
|
266
|
+
/**
|
|
267
|
+
* THE naming rule (vectoriox/iox-ui#43): a form's fields, in document order, each its base name made unique against
|
|
268
|
+
* the names before it — `email`, `email2`. The spec (`collectFormSpec`), the component path's runtime and the engine's
|
|
269
|
+
* string renderer all name a field from here, so the input's `name` and the saved spec's key can never differ.
|
|
270
|
+
*/
|
|
271
|
+
declare function uniqueFieldNames(baseNames: ReadonlyArray<string>): string[];
|
|
272
|
+
/** The submitted name of every Field of a Form, in document order — `uniqueFieldNames` over its fields. */
|
|
273
|
+
declare function formFieldNames(node: FormNodeLike | null | undefined): string[];
|
|
274
|
+
/** Each Field node of a Form → the name it submits under (`formFieldNames`, by node). */
|
|
275
|
+
declare function formFieldNameMap<N extends FormNodeLike>(node: N | null | undefined): Map<FormNodeLike, string>;
|
|
276
|
+
/**
|
|
277
|
+
* A Field's spec from its props and the name its form gave it — ONE reading for the saved spec and the component
|
|
278
|
+
* path's runtime (it was written twice: `IoxFieldComponent.toSpec` never carried the options source, the default or a
|
|
279
|
+
* Date's bounds). A Date's Earliest / Latest (`earliest` / `latest`) become its `min` / `max` rules (premium N8).
|
|
280
|
+
*/
|
|
281
|
+
declare function fieldSpecOf(props: Record<string, any> | null | undefined, name: string): FieldSpec;
|
|
201
282
|
/**
|
|
202
283
|
* Walk a Form subtree and produce its spec.
|
|
203
284
|
*
|
|
@@ -428,28 +509,45 @@ declare const FORM_FIELDS_ATTRIBUTE = "data-iox-form-fields";
|
|
|
428
509
|
declare function clientFormSpec(spec: Pick<FormSpec, 'feedback' | 'fields'>): ClientFormSpec;
|
|
429
510
|
/** The attribute's value back into a spec, or null when it is missing or not one. */
|
|
430
511
|
declare function parseClientFormSpec(raw: string | null | undefined): ClientFormSpec | null;
|
|
431
|
-
/** How a submit button wears its form's state (vectoriox/iox-ui#42). */
|
|
512
|
+
/** How a submit button wears its form's state (vectoriox/iox-ui#42, premium N2). */
|
|
432
513
|
interface SubmitButtonView {
|
|
433
514
|
busy: boolean;
|
|
434
515
|
disabled: boolean;
|
|
435
516
|
}
|
|
436
|
-
/**
|
|
437
|
-
*
|
|
438
|
-
*
|
|
517
|
+
/**
|
|
518
|
+
* While its form sends, a submit button is busy, and disabled unless its author turned that off; once the form has
|
|
519
|
+
* SENT it stays disabled, reading "Sent" with its check — nothing sends a lead twice (premium N2, the success in place).
|
|
520
|
+
* ONE rule for the canvas's Sending / Success states, the component path and the string path. Its labels are the shared
|
|
521
|
+
* CSS's, keyed on the same `data-iox-state`.
|
|
522
|
+
*/
|
|
439
523
|
declare function submitButtonView(state: FormRenderState | string | null | undefined, disableWhileSubmitting?: boolean): SubmitButtonView;
|
|
440
524
|
/** On a submit button whose author turned "Disable while sending" off. */
|
|
441
525
|
declare const SUBMIT_KEEP_ENABLED_ATTRIBUTE = "data-iox-keep-enabled";
|
|
442
526
|
declare enum SubmitLabelClass {
|
|
443
527
|
Labels = "iox-submit__labels",
|
|
444
528
|
Idle = "iox-submit__idle",
|
|
445
|
-
Busy = "iox-submit__busy"
|
|
529
|
+
Busy = "iox-submit__busy",
|
|
530
|
+
Sent = "iox-submit__sent",
|
|
531
|
+
Check = "iox-submit__check"
|
|
532
|
+
}
|
|
533
|
+
/** The check the Sent label draws once (premium N2 — the rare-tier beat, Emil 135/136): one stroke, `pathLength` 100 so
|
|
534
|
+
* the shared CSS draws it by `stroke-dashoffset` whatever its real length. */
|
|
535
|
+
declare const SUBMIT_CHECK_SVG: string;
|
|
536
|
+
declare function sentWord(lang: string | null | undefined): string;
|
|
537
|
+
/** A submit button's three labels: what it says at rest, while its form sends, and once it has sent. */
|
|
538
|
+
interface SubmitLabels {
|
|
539
|
+
idle: string;
|
|
540
|
+
/** "While sending" — none: the button keeps its idle words while sending. */
|
|
541
|
+
busy?: string | null;
|
|
542
|
+
/** "Sent" — none: the catalog's word, translated by the host (`SUBMIT_MESSAGE_KEYS.Sent`). */
|
|
543
|
+
sent: string;
|
|
446
544
|
}
|
|
447
545
|
/**
|
|
448
|
-
* A submit button's words, as markup: its
|
|
449
|
-
*
|
|
450
|
-
* `IoxSubmitButtonComponent`'s template; `escape` is the renderer's.
|
|
546
|
+
* A submit button's words, as markup: its three labels stacked in one grid cell (the button keeps the longest one's
|
|
547
|
+
* width and never jumps), each shown by the form's state through the shared CSS — swapped in place behind a 2px blur
|
|
548
|
+
* (Emil 87/139). The string renderer's copy of `IoxSubmitButtonComponent`'s template; `escape` is the renderer's.
|
|
451
549
|
*/
|
|
452
|
-
declare function submitButtonLabelsHtml(
|
|
550
|
+
declare function submitButtonLabelsHtml(labels: SubmitLabels, escape: (v: string) => string): string;
|
|
453
551
|
declare const FORM_HONEYPOT_CLASS = "iox-form__hp";
|
|
454
552
|
declare const FORM_ANNOUNCER_ATTRIBUTE = "data-iox-form-announcer";
|
|
455
553
|
/**
|
|
@@ -460,6 +558,44 @@ declare const FORM_ANNOUNCER_ATTRIBUTE = "data-iox-form-announcer";
|
|
|
460
558
|
*/
|
|
461
559
|
declare function formChromeHtml(honeypotField: string): string;
|
|
462
560
|
|
|
561
|
+
/**
|
|
562
|
+
* THE SECOND CHANNEL (premium components N2) — "or message us on WhatsApp", the button a form keeps beside its submit,
|
|
563
|
+
* before AND after it sends.
|
|
564
|
+
*
|
|
565
|
+
* It is an ordinary node in the form's actions row (a ButtonBlock that links) carrying the ROLE `form.channel`, so the
|
|
566
|
+
* shared CSS keeps it on screen when the form succeeds and the Publishable gate can find it. Its address is the
|
|
567
|
+
* OWNER's fact: the builder builds it from what the author types (a number, an address, a link) and never invents one —
|
|
568
|
+
* inserted empty, it blocks publishing until it is filled (generation-core `auditSite`, `empty-channel`).
|
|
569
|
+
*
|
|
570
|
+
* Framework-free, like the rest of forms-core.
|
|
571
|
+
*/
|
|
572
|
+
/** The role the channel's node carries (`iox-role-form-channel`). */
|
|
573
|
+
declare const FORM_CHANNEL_ROLE = "form.channel";
|
|
574
|
+
/** The role of the row that holds the submit button and the channel (`iox-role-form-actions`). */
|
|
575
|
+
declare const FORM_ACTIONS_ROLE = "form.actions";
|
|
576
|
+
declare enum ChannelKind {
|
|
577
|
+
WhatsApp = "whatsapp",
|
|
578
|
+
Phone = "phone",
|
|
579
|
+
Email = "email",
|
|
580
|
+
Link = "link"
|
|
581
|
+
}
|
|
582
|
+
declare const CHANNEL_KINDS: ReadonlyArray<ChannelKind>;
|
|
583
|
+
/**
|
|
584
|
+
* The address a channel links to, built from what the author typed — or `''` when it gives nothing a visitor could
|
|
585
|
+
* use (no digits, not an email). Never a guess: a WhatsApp number keeps exactly the digits typed (the panel asks for
|
|
586
|
+
* the country code), a link without a scheme gets `https://`.
|
|
587
|
+
*/
|
|
588
|
+
declare function channelHref(kind: ChannelKind | string, input: unknown): string;
|
|
589
|
+
/** A channel's address back into what the panel shows: its kind and the value the author typed. */
|
|
590
|
+
declare function parseChannelHref(href: unknown): {
|
|
591
|
+
kind: ChannelKind;
|
|
592
|
+
value: string;
|
|
593
|
+
};
|
|
594
|
+
/** True when the channel goes somewhere a visitor can use — the gate's "filled". */
|
|
595
|
+
declare function isChannelFilled(href: unknown): boolean;
|
|
596
|
+
/** A WhatsApp or web channel opens in a new tab; a call or an email hands over to the device. */
|
|
597
|
+
declare function channelOpensNewTab(kind: ChannelKind | string): boolean;
|
|
598
|
+
|
|
463
599
|
/**
|
|
464
600
|
* FORM PRESETS — a complete, working form as one insert: its fields, its submit label and the words of
|
|
465
601
|
* its Success and Error blocks. ONE definition (W1.1): the builder's Forms tray builds a preset from
|
|
@@ -467,11 +603,15 @@ declare function formChromeHtml(honeypotField: string): string;
|
|
|
467
603
|
* into a composed page, so a generated contact form is the real one — never a second, drifting copy.
|
|
468
604
|
* Data only: framework-free, like the rest of forms-core.
|
|
469
605
|
*/
|
|
606
|
+
|
|
470
607
|
declare enum FormPresetId {
|
|
471
608
|
Contact = "contact",
|
|
472
609
|
Newsletter = "newsletter",
|
|
473
610
|
Application = "application",
|
|
474
|
-
Booking = "booking"
|
|
611
|
+
Booking = "booking",
|
|
612
|
+
/** Premium N2: modelled on Doron's iox.studio brief — name, a phone that is also WhatsApp, the business, what they
|
|
613
|
+
* need and a budget as chips, Send with WhatsApp beside it. */
|
|
614
|
+
Lead = "lead"
|
|
475
615
|
}
|
|
476
616
|
interface FormPresetField {
|
|
477
617
|
label: string;
|
|
@@ -479,9 +619,21 @@ interface FormPresetField {
|
|
|
479
619
|
required?: boolean;
|
|
480
620
|
help?: string;
|
|
481
621
|
placeholder?: string;
|
|
482
|
-
/** Choices for select/radio, `value|Label` per line — the Field's `optionsText` trait. */
|
|
622
|
+
/** Choices for select/radio/choices, `value|Label` per line — the Field's `optionsText` trait. */
|
|
483
623
|
optionsText?: string;
|
|
484
624
|
rows?: number;
|
|
625
|
+
/** A Choices field that takes several answers (N1). */
|
|
626
|
+
multiple?: boolean;
|
|
627
|
+
/** A Choices field's appearance — `chips` (the default) or `list` (N1). */
|
|
628
|
+
appearance?: string;
|
|
629
|
+
/** A Date's first and last day — '' any · an integer: days from today · an ISO day (N8, `dates.ts`). */
|
|
630
|
+
earliest?: string;
|
|
631
|
+
latest?: string;
|
|
632
|
+
}
|
|
633
|
+
/** The second way to reach the studio, beside Send (N2). Its address is the studio's own and starts EMPTY. */
|
|
634
|
+
interface FormPresetChannel {
|
|
635
|
+
kind: `${ChannelKind}`;
|
|
636
|
+
label: string;
|
|
485
637
|
}
|
|
486
638
|
interface FormPresetDef {
|
|
487
639
|
id: `${FormPresetId}`;
|
|
@@ -495,7 +647,13 @@ interface FormPresetDef {
|
|
|
495
647
|
submitLabel: string;
|
|
496
648
|
successText: string;
|
|
497
649
|
errorText: string;
|
|
650
|
+
channel?: FormPresetChannel;
|
|
651
|
+
/** `false`: the AI plan wire does not offer it yet (the lead preset ships in the builder in M3; generation takes it
|
|
652
|
+
* up in premium M8 — the wire's enum is model-facing and does not change before then). */
|
|
653
|
+
inPlan?: false;
|
|
498
654
|
}
|
|
655
|
+
/** The presets a generated plan may name (the plan wire's `form` enum) — every preset not held back by `inPlan`. */
|
|
656
|
+
declare function planFormPresetIds(): string[];
|
|
499
657
|
declare const FORM_PRESETS: readonly FormPresetDef[];
|
|
500
658
|
/** The preset with this id, or undefined. */
|
|
501
659
|
declare function formPresetById(id: string | null | undefined): FormPresetDef | undefined;
|
|
@@ -543,5 +701,195 @@ interface MisplacedFormContent<N extends NestingNode = NestingNode> {
|
|
|
543
701
|
/** Every form-content subtree a tree holds inside an interactive host, in document order. */
|
|
544
702
|
declare function findFormContentInHosts<N extends NestingNode>(roots: readonly N[] | null | undefined): MisplacedFormContent<N>[];
|
|
545
703
|
|
|
546
|
-
|
|
547
|
-
|
|
704
|
+
/**
|
|
705
|
+
* DATES — the date picker's pure half (premium components N8) and the date rules' one reading.
|
|
706
|
+
*
|
|
707
|
+
* A Date field's value is an ISO day, `YYYY-MM-DD`, compared as a string (the format sorts as the calendar does).
|
|
708
|
+
* Its `min` / `max` rules take an ISO day OR an offset in days from today (`{ offsetDays: 0 }` = today), so "from
|
|
709
|
+
* today on" stays true tomorrow without a re-save. ONE reading for the validator (client AND iox-cms), the calendar
|
|
710
|
+
* (which days it refuses) and the builder's Earliest / Latest controls.
|
|
711
|
+
*
|
|
712
|
+
* The calendar model (six rows of seven, roving focus, the APG keys) lives here too, so the DOM runtime only draws
|
|
713
|
+
* what this decides — the canvas, the engine's component path and its string path draw the SAME month.
|
|
714
|
+
*
|
|
715
|
+
* ⚠️ Framework-free, like the rest of forms-core. Local-time `Date` objects are used only to walk the calendar; a
|
|
716
|
+
* day leaves this file as an ISO string.
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
/** Always six rows, so the panel never changes size between months (APG; Emil 74 — one object, one size). */
|
|
720
|
+
declare const CALENDAR_ROWS = 6;
|
|
721
|
+
/** The ISO day of a local `Date`. */
|
|
722
|
+
declare function isoOfDate(d: Date): string;
|
|
723
|
+
/** A local `Date` at midnight for an ISO day, or null when it is not one (2026-02-30 is not). */
|
|
724
|
+
declare function dateOfIso(iso: unknown): Date | null;
|
|
725
|
+
/** True for a real calendar day written `YYYY-MM-DD`. */
|
|
726
|
+
declare function isIsoDate(value: unknown): boolean;
|
|
727
|
+
/** Today, as the device that runs this sees it (the visitor's local day in a browser; the server's on iox-cms). */
|
|
728
|
+
declare function todayIso(now?: Date): string;
|
|
729
|
+
/** `iso` moved by `days` (negative goes back). */
|
|
730
|
+
declare function addDaysIso(iso: string, days: number): string;
|
|
731
|
+
/** `iso` moved by `months`, its day clamped to the target month's last (31 Jan + 1 month = 28/29 Feb). */
|
|
732
|
+
declare function addMonthsIso(iso: string, months: number): string;
|
|
733
|
+
/** `iso` held inside [min, max] (either may be absent). */
|
|
734
|
+
declare function clampIso(iso: string, min?: string | null, max?: string | null): string;
|
|
735
|
+
/** A date rule's parameter: an ISO day, or a number of days from today. */
|
|
736
|
+
interface DateBoundParams {
|
|
737
|
+
date?: string;
|
|
738
|
+
offsetDays?: number;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* A Field's `earliest` / `latest` trait → the rule parameter. The trait is one string so the panel and a saved page
|
|
742
|
+
* stay simple: `''` = any day · an integer = that many days from today (`'0'` = today, `'30'` = a month ahead) · an
|
|
743
|
+
* ISO day. Anything else reads as no bound.
|
|
744
|
+
*/
|
|
745
|
+
declare function parseDateBound(raw: unknown): DateBoundParams | null;
|
|
746
|
+
/** The `min` / `max` rules a Date field's Earliest / Latest traits give it (none for "any"). */
|
|
747
|
+
declare function dateBoundRules(earliest: unknown, latest: unknown): ValidationRule[];
|
|
748
|
+
/** True when a rule's params are a DATE bound (an ISO day or an offset), never a number bound. */
|
|
749
|
+
declare function isDateBoundParams(params: Record<string, any> | null | undefined): boolean;
|
|
750
|
+
/** The ISO day a bound names today, or null when it names none. */
|
|
751
|
+
declare function resolveDateBound(params: Record<string, any> | null | undefined, today: string): string | null;
|
|
752
|
+
/** The first and last day a Date field accepts today — the calendar's refused days and the validator's range. */
|
|
753
|
+
declare function fieldDateRange(field: Pick<FieldSpec, 'rules'> | null | undefined, today: string): {
|
|
754
|
+
min: string | null;
|
|
755
|
+
max: string | null;
|
|
756
|
+
};
|
|
757
|
+
/** True when the field is a date picker (the control that reads these rules as days). */
|
|
758
|
+
declare function isDateField(field: Pick<FieldSpec, 'control'> | null | undefined): boolean;
|
|
759
|
+
/**
|
|
760
|
+
* The first day of the week for a locale, 0 = Sunday … 6 = Saturday. `Intl.Locale(…).getWeekInfo()` (or the older
|
|
761
|
+
* `weekInfo` getter) when the runtime has it, else a small region table; a bare `en` reads as the US.
|
|
762
|
+
*/
|
|
763
|
+
declare function weekStartFor(locale: string | null | undefined): number;
|
|
764
|
+
/** One cell of the month grid. */
|
|
765
|
+
interface CalendarDay {
|
|
766
|
+
iso: string;
|
|
767
|
+
day: number;
|
|
768
|
+
today: boolean;
|
|
769
|
+
selected: boolean;
|
|
770
|
+
/** Outside Earliest / Latest: shown, announced as unavailable, never picked. */
|
|
771
|
+
disabled: boolean;
|
|
772
|
+
/** The roving tab stop — exactly one day of the month has it. */
|
|
773
|
+
focus: boolean;
|
|
774
|
+
}
|
|
775
|
+
interface CalendarMonth {
|
|
776
|
+
year: number;
|
|
777
|
+
/** 0-based, as `Date` counts. */
|
|
778
|
+
month: number;
|
|
779
|
+
/** `CALENDAR_ROWS` × 7; null where the month has no day. */
|
|
780
|
+
weeks: Array<Array<CalendarDay | null>>;
|
|
781
|
+
/** The weekday of each column, 0 = Sunday — the header row reads these. */
|
|
782
|
+
weekdays: number[];
|
|
783
|
+
canPrev: boolean;
|
|
784
|
+
canNext: boolean;
|
|
785
|
+
}
|
|
786
|
+
interface CalendarOptions {
|
|
787
|
+
weekStart: number;
|
|
788
|
+
today: string;
|
|
789
|
+
min?: string | null;
|
|
790
|
+
max?: string | null;
|
|
791
|
+
selected?: string | null;
|
|
792
|
+
}
|
|
793
|
+
/** The month `focus` sits in, as six rows of seven — the ONE grid every renderer draws. */
|
|
794
|
+
declare function calendarMonth(focus: string, options: CalendarOptions): CalendarMonth;
|
|
795
|
+
/** The keys the grid answers (APG date picker dialog). */
|
|
796
|
+
declare enum CalendarKey {
|
|
797
|
+
Left = "ArrowLeft",
|
|
798
|
+
Right = "ArrowRight",
|
|
799
|
+
Up = "ArrowUp",
|
|
800
|
+
Down = "ArrowDown",
|
|
801
|
+
Home = "Home",
|
|
802
|
+
End = "End",
|
|
803
|
+
PageUp = "PageUp",
|
|
804
|
+
PageDown = "PageDown"
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Where a key moves the focused day, held inside [min, max] — or null for a key the grid does not answer (Enter,
|
|
808
|
+
* Space and Escape are the runtime's: they pick or close, they never move).
|
|
809
|
+
*/
|
|
810
|
+
declare function calendarKeyTarget(key: string, focus: string, options: {
|
|
811
|
+
weekStart: number;
|
|
812
|
+
rtl?: boolean;
|
|
813
|
+
shift?: boolean;
|
|
814
|
+
min?: string | null;
|
|
815
|
+
max?: string | null;
|
|
816
|
+
}): string | null;
|
|
817
|
+
/** The date in words, in the site's language: "Thu, 20 November" — the year only when it is not this year's. */
|
|
818
|
+
declare function formatDateInWords(iso: string, locale: string | null | undefined, today: string): string;
|
|
819
|
+
/** The calendar's own words, by the site's language — the media-core `videoWords` precedent (a string-rendered
|
|
820
|
+
* site has no translation service; the document's `lang` picks). */
|
|
821
|
+
interface DatePickerWords {
|
|
822
|
+
choose: string;
|
|
823
|
+
previousMonth: string;
|
|
824
|
+
nextMonth: string;
|
|
825
|
+
close: string;
|
|
826
|
+
}
|
|
827
|
+
declare const DATE_PICKER_WORDS: Readonly<Record<'en' | 'he', DatePickerWords>>;
|
|
828
|
+
/** The words for a language tag (`he-IL` → Hebrew); English for anything else. */
|
|
829
|
+
declare function datePickerWords(lang: string | null | undefined): DatePickerWords;
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* A Field as MARKUP — the string renderer's copy of `IoxFieldComponent`'s template, in ONE place (it lived in the
|
|
833
|
+
* engine's `layout-renderer.service.ts`, a second copy beside the component). The engine's string path writes it; the
|
|
834
|
+
* component mirrors it element for element, so hydration replaces like with like and the shared CSS (iox-forms.css,
|
|
835
|
+
* the contract's field baseline) reaches both.
|
|
836
|
+
*
|
|
837
|
+
* Choices (premium N1): a `<fieldset>` whose `<legend>` is the label, its options native radios (one answer — the
|
|
838
|
+
* arrow keys move and pick, with no script) or checkboxes (several), each inside a `<label>` that IS the chip; the
|
|
839
|
+
* input is visually hidden, never removed. Date (N8): a `<button>` that says the date in words and opens the look's
|
|
840
|
+
* calendar, with a hidden input carrying the ISO day — the raw SSR is the final markup (nothing shifts on hydrate).
|
|
841
|
+
*
|
|
842
|
+
* Framework-free, like the rest of forms-core: `escape` is the caller's.
|
|
843
|
+
*/
|
|
844
|
+
|
|
845
|
+
/** The classes the markup carries — the runtimes and the shared CSS key on these, never on a literal. */
|
|
846
|
+
declare enum FieldClass {
|
|
847
|
+
Root = "iox-field",
|
|
848
|
+
Label = "iox-field__label",
|
|
849
|
+
LabelHidden = "iox-field__label--hidden",
|
|
850
|
+
Required = "iox-field__required",
|
|
851
|
+
Control = "iox-field__control",
|
|
852
|
+
Help = "iox-field__help",
|
|
853
|
+
Error = "iox-field__error",
|
|
854
|
+
Group = "iox-field__group",
|
|
855
|
+
Option = "iox-field__option",
|
|
856
|
+
Choices = "iox-field__choices",
|
|
857
|
+
ChoicesChips = "iox-field__choices--chips",
|
|
858
|
+
ChoicesList = "iox-field__choices--list",
|
|
859
|
+
Chips = "iox-field__chips",
|
|
860
|
+
Chip = "iox-field__chip",
|
|
861
|
+
ChipInput = "iox-field__control--chip",
|
|
862
|
+
DateButton = "iox-field__control--date",
|
|
863
|
+
DateValue = "iox-field__date-value",
|
|
864
|
+
DateIcon = "iox-field__date-icon",
|
|
865
|
+
DateInput = "iox-field__date-input"
|
|
866
|
+
}
|
|
867
|
+
/** On the date button: the raw Earliest / Latest (`'0'`, an ISO day), resolved by the visitor's own today. */
|
|
868
|
+
declare const DATE_MIN_ATTRIBUTE = "data-iox-date-min";
|
|
869
|
+
declare const DATE_MAX_ATTRIBUTE = "data-iox-date-max";
|
|
870
|
+
/** On the date's words while no day is chosen (the placeholder's colour keys on it). */
|
|
871
|
+
declare const DATE_EMPTY_ATTRIBUTE = "data-iox-empty";
|
|
872
|
+
/** The calendar glyph inside the date button: one path set, drawn in currentColor, never announced. */
|
|
873
|
+
declare const DATE_ICON_SVG: string;
|
|
874
|
+
/** The `<input type>` of a control that renders as a plain input. */
|
|
875
|
+
declare function inputTypeOf(control: string): string;
|
|
876
|
+
/** A Choices field's appearance; anything unknown reads as chips (the default). */
|
|
877
|
+
declare function choiceAppearanceOf(props: Record<string, any> | null | undefined): ChoiceAppearance;
|
|
878
|
+
interface FieldMarkupOptions {
|
|
879
|
+
/** The name the field submits under — its FORM's unique name for it (`formFieldNames`, vectoriox/iox-ui#43). */
|
|
880
|
+
name: string;
|
|
881
|
+
/** The control's id; the label, help and error ids derive from it. */
|
|
882
|
+
id: string;
|
|
883
|
+
/** The node's classes (`iox-node-…`, the element-style marker…). */
|
|
884
|
+
cls: string;
|
|
885
|
+
/** Anything else the host element carries, written verbatim (an entrance's style). */
|
|
886
|
+
attrs?: string;
|
|
887
|
+
/** The site's language (the date's words). */
|
|
888
|
+
lang?: string | null;
|
|
889
|
+
escape: (value: string) => string;
|
|
890
|
+
}
|
|
891
|
+
/** A Field's whole element, as the string renderer writes it. */
|
|
892
|
+
declare function fieldHtml(props: Record<string, any> | null | undefined, options: FieldMarkupOptions): string;
|
|
893
|
+
|
|
894
|
+
export { CALENDAR_ROWS, CHANNEL_KINDS, CHOICE_MESSAGE_KEYS, CalendarKey, ChannelKind, ChoiceAppearance, ControlType, DATE_EMPTY_ATTRIBUTE, DATE_ICON_SVG, DATE_MAX_ATTRIBUTE, DATE_MESSAGE_KEYS, DATE_MIN_ATTRIBUTE, DATE_PICKER_WORDS, DEFAULT_EN_MESSAGES, FIELD_GROUP_NODE_TYPE, FIELD_NODE_TYPE, FORM_ACTIONS_ROLE, FORM_ANNOUNCER_ATTRIBUTE, FORM_CHANNEL_ROLE, FORM_CONTENT_TYPES, FORM_ERROR_KEYS, FORM_FIELDS_ATTRIBUTE, FORM_HONEYPOT_CLASS, FORM_NODE_TYPE, FORM_PRESETS, FORM_STATE_ATTRIBUTE, FeedbackTiming, FieldClass, FormPresetId, FormRenderState, HONEYPOT_FIELD, IMPLIED_PATTERN, INITIAL_FORM_STATE, INTERACTIVE_HOST_TAGS, InteractiveTag, MESSAGE_NAMESPACE, OPTION_CONTROLS, PATTERN_MESSAGE_KEYS, PATTERN_PRESETS, PATTERN_PRESET_OPTIONS, PatternPreset, RENDERED_AT_FIELD, RULE_DESCRIPTORS, RULE_MESSAGE_KEYS, RuleName, RuleParamKind, STORE_ACTION, SUBMIT_BUTTON_NODE_TYPE, SUBMIT_CHECK_SVG, SUBMIT_KEEP_ENABLED_ATTRIBUTE, SUBMIT_MESSAGE_KEYS, SubmitLabelClass, addDaysIso, addMonthsIso, addableRules, applyServerErrors, baseFieldName, calendarKeyTarget, calendarMonth, channelHref, channelOpensNewTab, choiceAppearanceOf, clampIso, clientFormSpec, collectFieldNodes, collectFormSpec, dateBoundRules, dateOfIso, datePickerWords, deriveFieldName, failureAnnouncement, fieldDateRange, fieldHtml, fieldOptionsOf, fieldSpecOf, findFormContentInHosts, firstInvalidField, formChromeHtml, formContentInHost, formFieldNameMap, formFieldNames, formPresetById, formatDateInWords, inputTypeOf, interpretSubmitResponse, isChannelFilled, isDateBoundParams, isDateField, isFormContent, isInteractiveHost, isIsoDate, isMultiValueField, isoOfDate, normaliseAnswers, onFieldBlur, onFieldChange, onSubmitAttempt, parseChannelHref, parseClientFormSpec, parseDateBound, parseFieldOptions, planFormPresetIds, resetFormState, resolveDateBound, rulesFor, sentWord, shouldShowError, submitButtonLabelsHtml, submitButtonView, submitEndpointPath, submitOutcome, todayIso, uniqueFieldName, uniqueFieldNames, validateField, validateForm, weekStartFor, withAntiSpamFields };
|
|
895
|
+
export type { CalendarDay, CalendarMonth, CalendarOptions, ClientFormSpec, DateBoundParams, DatePickerWords, FieldError, FieldMarkupOptions, FieldOption, FieldOptionSource, FieldResult, FieldSpec, FormNodeLike, FormPresetChannel, FormPresetDef, FormPresetField, FormResult, FormRuntimeState, FormSpec, FormTransport, MisplacedFormContent, NestingNode, RuleDescriptor, SubmitAction, SubmitButtonView, SubmitLabels, SubmitOutcome, SubmitRequest, SubmitResponse, ValidateOptions, ValidationRule };
|
|
@@ -6929,6 +6929,9 @@ declare enum SiteFindingCode {
|
|
|
6929
6929
|
NoHeader = "no-header",
|
|
6930
6930
|
NoPhoneMenu = "no-phone-menu",
|
|
6931
6931
|
FormWithoutId = "form-without-id",
|
|
6932
|
+
/** Premium N2: a form's second way to reach you (WhatsApp, a phone, an email) has no address yet — the studio's own
|
|
6933
|
+
* fact, never written for it. */
|
|
6934
|
+
EmptyChannel = "empty-channel",
|
|
6932
6935
|
EmailNeedsMailer = "email-needs-mailer",
|
|
6933
6936
|
TrackerWithoutBanner = "tracker-without-banner",
|
|
6934
6937
|
NoTitle = "no-title",
|