@vectoriox/iox-builder 4.12.1 → 4.14.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.
|
@@ -180,6 +180,12 @@ var TraitInputType;
|
|
|
180
180
|
/** Ordered list of images (URL / one-by-one / filebox; per-item binding in a later phase).
|
|
181
181
|
* Controller registered in the consumer (iox-cms-client) as `mediaList`. Value: GalleryImage[]. */
|
|
182
182
|
TraitInputType["MediaList"] = "mediaList";
|
|
183
|
+
/** Additive validation rules for a Field (`ValidationRule[]` from forms-core). Controller
|
|
184
|
+
* registered in the consumer (iox-cms-client) as `ruleList`. */
|
|
185
|
+
TraitInputType["RuleList"] = "ruleList";
|
|
186
|
+
/** The ordered "After submit" action list on a Form (`SubmitAction[]`). Controller registered
|
|
187
|
+
* in the consumer as `actionList`. */
|
|
188
|
+
TraitInputType["ActionList"] = "actionList";
|
|
183
189
|
TraitInputType["FontFamily"] = "fontFamily";
|
|
184
190
|
TraitInputType["GridTemplate"] = "gridTemplate";
|
|
185
191
|
TraitInputType["SectionLabel"] = "sectionLabel";
|
|
@@ -701,15 +707,42 @@ const freshId = () => Math.random().toString(36).substr(2, 9);
|
|
|
701
707
|
* A reference pointing OUTSIDE the cloned subtree (id not in the map) is left untouched — the copy
|
|
702
708
|
* intentionally still points at the external node.
|
|
703
709
|
*
|
|
710
|
+
* TRAIT values are NOT ids and are copied verbatim — with one exception. A trait that carries an
|
|
711
|
+
* IDENTITY rather than a setting must be reissued, or the copy silently shares the original's
|
|
712
|
+
* identity. `formId` is the first: two forms with the same id share one submissions stream, and the
|
|
713
|
+
* server resolves a submission against whichever page it reaches first. See REGENERATED_TRAITS.
|
|
714
|
+
*
|
|
704
715
|
* `styleId` strategy:
|
|
705
716
|
* - `'clear'` (default) — drop styleId so the clone OWNS its own CSS rule (independent copy).
|
|
706
717
|
* - `'regenerate'` — keep a styleId but give it a fresh value (legacy builder.component path).
|
|
707
718
|
*/
|
|
719
|
+
/**
|
|
720
|
+
* Trait names whose value is an IDENTITY, not a setting, and must therefore be reissued on every
|
|
721
|
+
* clone. Add a name here the moment a trait starts identifying something rather than describing it.
|
|
722
|
+
*
|
|
723
|
+
* `formId` — a form's submissions stream. Duplicating a page, pasting a form or dropping a saved
|
|
724
|
+
* block twice used to produce two forms claiming the same stream.
|
|
725
|
+
*/
|
|
726
|
+
const REGENERATED_TRAITS = new Set(['formId']);
|
|
727
|
+
/** Reissue identity-carrying traits on a node, preserving the original's prefix where it had one. */
|
|
728
|
+
function reissueIdentityTraits(node) {
|
|
729
|
+
for (const trait of (node.traits ?? [])) {
|
|
730
|
+
if (!trait?.name || !REGENERATED_TRAITS.has(trait.name))
|
|
731
|
+
continue;
|
|
732
|
+
if (trait.default === undefined || trait.default === '')
|
|
733
|
+
continue;
|
|
734
|
+
// Keep the human-readable prefix a preset gave it (`contact-a1b2c3` → `contact-<new>`), so a
|
|
735
|
+
// duplicated contact form is still recognisably a contact form in the inbox.
|
|
736
|
+
const prefix = String(trait.default).split('-')[0];
|
|
737
|
+
trait.default = `${prefix}-${freshId()}`;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
708
740
|
function regenerateNodeIds(root, opts = {}) {
|
|
709
741
|
const idMap = new Map();
|
|
710
742
|
const assign = (node) => {
|
|
711
743
|
const oldId = node.id;
|
|
712
744
|
node.id = freshId();
|
|
745
|
+
reissueIdentityTraits(node);
|
|
713
746
|
if (opts.styleId === 'regenerate') {
|
|
714
747
|
if (node.styleId)
|
|
715
748
|
node.styleId = freshId();
|
|
@@ -9663,6 +9696,10 @@ class FormComponentConfig extends ComponentConfig {
|
|
|
9663
9696
|
// On by default and invisible to the visitor (decision D12).
|
|
9664
9697
|
new TraitConfig('honeypot', 'Honeypot field', TraitInputType.Checkbox, undefined, true),
|
|
9665
9698
|
new TraitConfig('minFillMs', 'Minimum fill time (ms)', TraitInputType.Number, undefined, 1200),
|
|
9699
|
+
new TraitConfig('__afterSubmitLabel', 'After submit', TraitInputType.SectionLabel),
|
|
9700
|
+
// The ordered destination list. `store` is always first and cannot be removed — it is
|
|
9701
|
+
// the safety net the whole store-first design rests on.
|
|
9702
|
+
new TraitConfig('actions', 'Destinations', TraitInputType.ActionList, undefined, [{ type: 'store' }]),
|
|
9666
9703
|
new TraitConfig('__dataLabel', 'Data', TraitInputType.SectionLabel),
|
|
9667
9704
|
// Absent means keep forever — the confirmed default. A number opts into purging.
|
|
9668
9705
|
new TraitConfig('retentionDays', 'Delete submissions after (days)', TraitInputType.Number, undefined, ''),
|
|
@@ -9710,13 +9747,16 @@ class FieldComponentConfig extends ComponentConfig {
|
|
|
9710
9747
|
{ label: 'Radio group', value: 'radio' },
|
|
9711
9748
|
{ label: 'Date', value: 'date' },
|
|
9712
9749
|
{ label: 'Time', value: 'time' },
|
|
9713
|
-
|
|
9750
|
+
// ⚠️ 'file' is DELIBERATELY ABSENT until uploads are implemented. A file input's
|
|
9751
|
+
// `value` is the browser's fake path (`C:\fakepath\cv.pdf`), and `fileType`/
|
|
9752
|
+
// `fileSize` operate on a file list — so on a string they pass vacuously. Offering
|
|
9753
|
+
// it meant an author could set "PDF only, 5MB" and silently get neither the
|
|
9754
|
+
// enforcement nor the file. See forms-and-inputs-plan.md → Remaining work P0/1.
|
|
9714
9755
|
{ label: 'Hidden', value: 'hidden' },
|
|
9715
9756
|
], 'text'),
|
|
9716
9757
|
new TraitConfig('placeholder', 'Placeholder', TraitInputType.Text, undefined, '', false, { trait: 'control', values: ['text', 'email', 'tel', 'url', 'number', 'password', 'textarea', 'select'] }),
|
|
9717
9758
|
new TraitConfig('help', 'Help text', TraitInputType.Text, undefined, ''),
|
|
9718
9759
|
new TraitConfig('rows', 'Rows', TraitInputType.Number, undefined, 4, false, { trait: 'control', values: 'textarea' }),
|
|
9719
|
-
new TraitConfig('accept', 'Accepted file types', TraitInputType.Text, undefined, '', false, { trait: 'control', values: 'file' }),
|
|
9720
9760
|
new TraitConfig('labelHidden', 'Hide label (screen readers still read it)', TraitInputType.Checkbox, undefined, false),
|
|
9721
9761
|
// The submitted key. Derived from the label; editable for external integrations.
|
|
9722
9762
|
new TraitConfig('name', 'Submitted key', TraitInputType.Text, undefined, ''),
|
|
@@ -9727,9 +9767,9 @@ class FieldComponentConfig extends ComponentConfig {
|
|
|
9727
9767
|
new TraitConfig('optionsText', 'Choices (one per line)', TraitInputType.Textarea, undefined, '', false, { trait: 'control', values: ['select', 'radio'] }),
|
|
9728
9768
|
new TraitConfig('__validationLabel', 'Validation', TraitInputType.SectionLabel),
|
|
9729
9769
|
new TraitConfig('required', 'Required', TraitInputType.Checkbox, undefined, false),
|
|
9730
|
-
//
|
|
9731
|
-
//
|
|
9732
|
-
new TraitConfig('rules', 'Rules', TraitInputType.
|
|
9770
|
+
// The additive rule list (minLength, pattern, matches, …). Unused rules are invisible
|
|
9771
|
+
// rather than a wall of empty controls — the Effects-group pattern.
|
|
9772
|
+
new TraitConfig('rules', 'Rules', TraitInputType.RuleList, undefined, []),
|
|
9733
9773
|
];
|
|
9734
9774
|
this.styleTraits = fullStyles();
|
|
9735
9775
|
this.applyStyleDefaults({
|