@vectoriox/iox-ui 4.31.1 → 4.32.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.
@@ -242,5 +242,70 @@ declare function applyServerErrors(state: FormRuntimeState, errors: Record<strin
242
242
  /** A clean slate after a successful submit, for a form configured to clear itself. */
243
243
  declare function resetFormState(): FormRuntimeState;
244
244
 
245
- export { ControlType, DEFAULT_EN_MESSAGES, FIELD_NODE_TYPE, FORM_ERROR_KEYS, FORM_NODE_TYPE, FeedbackTiming, IMPLIED_PATTERN, INITIAL_FORM_STATE, MESSAGE_NAMESPACE, MULTI_VALUE_CONTROLS, PATTERN_MESSAGE_KEYS, PATTERN_PRESETS, PatternPreset, RULE_MESSAGE_KEYS, RuleName, STORE_ACTION, applyServerErrors, collectFieldNodes, collectFormSpec, deriveFieldName, firstInvalidField, onFieldBlur, onFieldChange, onSubmitAttempt, resetFormState, shouldShowError, uniqueFieldName, validateField, validateForm };
246
- export type { FieldError, FieldOption, FieldOptionSource, FieldResult, FieldSpec, FormNodeLike, FormResult, FormRuntimeState, FormSpec, SubmitAction, ValidationRule };
245
+ /**
246
+ * Which rules exist, and which controls they make sense on.
247
+ *
248
+ * The panel must not offer "must be ticked" on a text field, or a character limit on a date. That
249
+ * knowledge belongs with the validator, not duplicated into the editor — otherwise the two drift and
250
+ * an author configures a rule that silently never runs.
251
+ *
252
+ * Pure data, in forms-core, so the builder panel, the engine and the backend all read one catalog.
253
+ */
254
+
255
+ /** How a rule's parameter is edited. The panel maps these to its own controls. */
256
+ declare enum RuleParamKind {
257
+ /** No parameter — the rule is its own statement (`required`, `accepted`). */
258
+ None = "none",
259
+ /** A single number (`{ n }`) — lengths and bounds. */
260
+ Number = "number",
261
+ /** A preset regex, or a custom one (`{ preset }` / `{ regex }`). */
262
+ Pattern = "pattern",
263
+ /** Another field in the same form (`{ field }`). */
264
+ Field = "field"
265
+ }
266
+ interface RuleDescriptor {
267
+ name: RuleName;
268
+ /** i18n key for the rule's name in the Add-rule menu. */
269
+ labelKey: string;
270
+ param: RuleParamKind;
271
+ /** Controls this rule can apply to. Empty means every control. */
272
+ controls: readonly ControlType[];
273
+ /** Suggested starting value for the parameter, so a freshly added rule is already valid. */
274
+ defaultParams?: Record<string, any>;
275
+ }
276
+ declare const RULE_DESCRIPTORS: readonly RuleDescriptor[];
277
+ /** The rules that can be added to a field of this control type. */
278
+ declare function rulesFor(control: ControlType | string): RuleDescriptor[];
279
+ /**
280
+ * The rules an author may still ADD — the catalog minus what is already on the field.
281
+ *
282
+ * Each rule appears once: two `maxLength` rules on one field is not a configuration anyone means,
283
+ * and the validator would apply whichever came first. `pattern` is the exception a caller might
284
+ * expect to repeat, and it deliberately does not: a second pattern can only contradict the first.
285
+ */
286
+ declare function addableRules(control: ControlType | string, existing?: readonly {
287
+ name: RuleName;
288
+ }[]): RuleDescriptor[];
289
+ /** The preset options a Pattern rule offers, in the order the panel should list them. */
290
+ declare const PATTERN_PRESET_OPTIONS: readonly {
291
+ value: PatternPreset;
292
+ labelKey: string;
293
+ }[];
294
+ /** Destinations an author can add to a form, for the After-submit list. */
295
+ interface ActionDescriptor {
296
+ type: string;
297
+ labelKey: string;
298
+ /** Always present and not removable. */
299
+ locked?: boolean;
300
+ /** Not offered yet — shown greyed with a "coming soon" note rather than hidden, so the roadmap
301
+ * is visible rather than the feature looking absent. */
302
+ planned?: boolean;
303
+ }
304
+ declare const ACTION_DESCRIPTORS: readonly ActionDescriptor[];
305
+ /** Destinations that can still be added — each at most once, and never a planned one. */
306
+ declare function addableActions(existing?: readonly {
307
+ type: string;
308
+ }[]): ActionDescriptor[];
309
+
310
+ export { ACTION_DESCRIPTORS, ControlType, DEFAULT_EN_MESSAGES, FIELD_NODE_TYPE, FORM_ERROR_KEYS, FORM_NODE_TYPE, FeedbackTiming, IMPLIED_PATTERN, INITIAL_FORM_STATE, MESSAGE_NAMESPACE, MULTI_VALUE_CONTROLS, PATTERN_MESSAGE_KEYS, PATTERN_PRESETS, PATTERN_PRESET_OPTIONS, PatternPreset, RULE_DESCRIPTORS, RULE_MESSAGE_KEYS, RuleName, RuleParamKind, STORE_ACTION, addableActions, addableRules, applyServerErrors, collectFieldNodes, collectFormSpec, deriveFieldName, firstInvalidField, onFieldBlur, onFieldChange, onSubmitAttempt, resetFormState, rulesFor, shouldShowError, uniqueFieldName, validateField, validateForm };
311
+ export type { ActionDescriptor, FieldError, FieldOption, FieldOptionSource, FieldResult, FieldSpec, FormNodeLike, FormResult, FormRuntimeState, FormSpec, RuleDescriptor, SubmitAction, ValidationRule };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectoriox/iox-ui",
3
- "version": "4.31.1",
3
+ "version": "4.32.0",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=20.0.0",
6
6
  "@angular/core": ">=20.0.0",
@@ -15,3 +15,4 @@ export * from './messages';
15
15
  export * from './validate';
16
16
  export * from './collect';
17
17
  export * from './runtime';
18
+ export * from './rule-catalog';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Which rules exist, and which controls they make sense on.
3
+ *
4
+ * The panel must not offer "must be ticked" on a text field, or a character limit on a date. That
5
+ * knowledge belongs with the validator, not duplicated into the editor — otherwise the two drift and
6
+ * an author configures a rule that silently never runs.
7
+ *
8
+ * Pure data, in forms-core, so the builder panel, the engine and the backend all read one catalog.
9
+ */
10
+
11
+ import { ControlType, PatternPreset, RuleName } from './form.model';
12
+
13
+ /** How a rule's parameter is edited. The panel maps these to its own controls. */
14
+ export enum RuleParamKind {
15
+ /** No parameter — the rule is its own statement (`required`, `accepted`). */
16
+ None = 'none',
17
+ /** A single number (`{ n }`) — lengths and bounds. */
18
+ Number = 'number',
19
+ /** A preset regex, or a custom one (`{ preset }` / `{ regex }`). */
20
+ Pattern = 'pattern',
21
+ /** Another field in the same form (`{ field }`). */
22
+ Field = 'field',
23
+ }
24
+
25
+ export interface RuleDescriptor {
26
+ name: RuleName;
27
+ /** i18n key for the rule's name in the Add-rule menu. */
28
+ labelKey: string;
29
+ param: RuleParamKind;
30
+ /** Controls this rule can apply to. Empty means every control. */
31
+ controls: readonly ControlType[];
32
+ /** Suggested starting value for the parameter, so a freshly added rule is already valid. */
33
+ defaultParams?: Record<string, any>;
34
+ }
35
+
36
+ /** Text-ish controls: the ones whose value is a string a person typed. */
37
+ const TEXTUAL: readonly ControlType[] = [
38
+ ControlType.Text, ControlType.Email, ControlType.Tel, ControlType.Url,
39
+ ControlType.Password, ControlType.Textarea,
40
+ ];
41
+
42
+ /** Controls with a comparable magnitude. */
43
+ const NUMERIC: readonly ControlType[] = [ControlType.Number, ControlType.Date, ControlType.Time];
44
+
45
+ export const RULE_DESCRIPTORS: readonly RuleDescriptor[] = Object.freeze([
46
+ {
47
+ name: RuleName.MinLength, labelKey: 'forms.rules.minLength',
48
+ param: RuleParamKind.Number, controls: TEXTUAL, defaultParams: { n: 2 },
49
+ },
50
+ {
51
+ name: RuleName.MaxLength, labelKey: 'forms.rules.maxLength',
52
+ param: RuleParamKind.Number, controls: TEXTUAL, defaultParams: { n: 200 },
53
+ },
54
+ {
55
+ name: RuleName.Min, labelKey: 'forms.rules.min',
56
+ param: RuleParamKind.Number, controls: NUMERIC, defaultParams: { n: 0 },
57
+ },
58
+ {
59
+ name: RuleName.Max, labelKey: 'forms.rules.max',
60
+ param: RuleParamKind.Number, controls: NUMERIC, defaultParams: { n: 100 },
61
+ },
62
+ {
63
+ name: RuleName.Pattern, labelKey: 'forms.rules.pattern',
64
+ param: RuleParamKind.Pattern, controls: TEXTUAL,
65
+ defaultParams: { preset: PatternPreset.Email },
66
+ },
67
+ {
68
+ // Cross-field: confirm-email, confirm-password.
69
+ name: RuleName.Matches, labelKey: 'forms.rules.matches',
70
+ param: RuleParamKind.Field, controls: [],
71
+ },
72
+ {
73
+ // A checkbox's `required` already becomes `accepted`, so offering it again would be a
74
+ // duplicate — it is here for a checkbox the author wants ticked WITHOUT marking it required.
75
+ name: RuleName.Accepted, labelKey: 'forms.rules.accepted',
76
+ param: RuleParamKind.None, controls: [ControlType.Checkbox],
77
+ },
78
+ ]);
79
+
80
+ /** The rules that can be added to a field of this control type. */
81
+ export function rulesFor(control: ControlType | string): RuleDescriptor[] {
82
+ return RULE_DESCRIPTORS.filter(rule =>
83
+ rule.controls.length === 0 || rule.controls.includes(control as ControlType));
84
+ }
85
+
86
+ /**
87
+ * The rules an author may still ADD — the catalog minus what is already on the field.
88
+ *
89
+ * Each rule appears once: two `maxLength` rules on one field is not a configuration anyone means,
90
+ * and the validator would apply whichever came first. `pattern` is the exception a caller might
91
+ * expect to repeat, and it deliberately does not: a second pattern can only contradict the first.
92
+ */
93
+ export function addableRules(
94
+ control: ControlType | string,
95
+ existing: readonly { name: RuleName }[] = [],
96
+ ): RuleDescriptor[] {
97
+ const used = new Set(existing.map(rule => rule.name));
98
+ return rulesFor(control).filter(rule => !used.has(rule.name));
99
+ }
100
+
101
+ /** The preset options a Pattern rule offers, in the order the panel should list them. */
102
+ export const PATTERN_PRESET_OPTIONS: readonly { value: PatternPreset; labelKey: string }[] = Object.freeze([
103
+ { value: PatternPreset.Email, labelKey: 'forms.rules.preset.email' },
104
+ { value: PatternPreset.Phone, labelKey: 'forms.rules.preset.phone' },
105
+ { value: PatternPreset.Url, labelKey: 'forms.rules.preset.url' },
106
+ { value: PatternPreset.Digits, labelKey: 'forms.rules.preset.digits' },
107
+ { value: PatternPreset.Letters, labelKey: 'forms.rules.preset.letters' },
108
+ { value: PatternPreset.Slug, labelKey: 'forms.rules.preset.slug' },
109
+ ]);
110
+
111
+ /** Destinations an author can add to a form, for the After-submit list. */
112
+ export interface ActionDescriptor {
113
+ type: string;
114
+ labelKey: string;
115
+ /** Always present and not removable. */
116
+ locked?: boolean;
117
+ /** Not offered yet — shown greyed with a "coming soon" note rather than hidden, so the roadmap
118
+ * is visible rather than the feature looking absent. */
119
+ planned?: boolean;
120
+ }
121
+
122
+ export const ACTION_DESCRIPTORS: readonly ActionDescriptor[] = Object.freeze([
123
+ { type: 'store', labelKey: 'forms.actions.store', locked: true },
124
+ { type: 'email', labelKey: 'forms.actions.email' },
125
+ { type: 'redirect', labelKey: 'forms.actions.redirect' },
126
+ { type: 'createItem', labelKey: 'forms.actions.createItem', planned: true },
127
+ { type: 'webhook', labelKey: 'forms.actions.webhook', planned: true },
128
+ { type: 'autoReply', labelKey: 'forms.actions.autoReply', planned: true },
129
+ ]);
130
+
131
+ /** Destinations that can still be added — each at most once, and never a planned one. */
132
+ export function addableActions(existing: readonly { type: string }[] = []): ActionDescriptor[] {
133
+ const used = new Set(existing.map(action => action.type));
134
+ return ACTION_DESCRIPTORS.filter(action => !action.locked && !action.planned && !used.has(action.type));
135
+ }