@vectoriox/iox-builder 4.12.0 → 4.13.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.
|
@@ -701,15 +701,42 @@ const freshId = () => Math.random().toString(36).substr(2, 9);
|
|
|
701
701
|
* A reference pointing OUTSIDE the cloned subtree (id not in the map) is left untouched — the copy
|
|
702
702
|
* intentionally still points at the external node.
|
|
703
703
|
*
|
|
704
|
+
* TRAIT values are NOT ids and are copied verbatim — with one exception. A trait that carries an
|
|
705
|
+
* IDENTITY rather than a setting must be reissued, or the copy silently shares the original's
|
|
706
|
+
* identity. `formId` is the first: two forms with the same id share one submissions stream, and the
|
|
707
|
+
* server resolves a submission against whichever page it reaches first. See REGENERATED_TRAITS.
|
|
708
|
+
*
|
|
704
709
|
* `styleId` strategy:
|
|
705
710
|
* - `'clear'` (default) — drop styleId so the clone OWNS its own CSS rule (independent copy).
|
|
706
711
|
* - `'regenerate'` — keep a styleId but give it a fresh value (legacy builder.component path).
|
|
707
712
|
*/
|
|
713
|
+
/**
|
|
714
|
+
* Trait names whose value is an IDENTITY, not a setting, and must therefore be reissued on every
|
|
715
|
+
* clone. Add a name here the moment a trait starts identifying something rather than describing it.
|
|
716
|
+
*
|
|
717
|
+
* `formId` — a form's submissions stream. Duplicating a page, pasting a form or dropping a saved
|
|
718
|
+
* block twice used to produce two forms claiming the same stream.
|
|
719
|
+
*/
|
|
720
|
+
const REGENERATED_TRAITS = new Set(['formId']);
|
|
721
|
+
/** Reissue identity-carrying traits on a node, preserving the original's prefix where it had one. */
|
|
722
|
+
function reissueIdentityTraits(node) {
|
|
723
|
+
for (const trait of (node.traits ?? [])) {
|
|
724
|
+
if (!trait?.name || !REGENERATED_TRAITS.has(trait.name))
|
|
725
|
+
continue;
|
|
726
|
+
if (trait.default === undefined || trait.default === '')
|
|
727
|
+
continue;
|
|
728
|
+
// Keep the human-readable prefix a preset gave it (`contact-a1b2c3` → `contact-<new>`), so a
|
|
729
|
+
// duplicated contact form is still recognisably a contact form in the inbox.
|
|
730
|
+
const prefix = String(trait.default).split('-')[0];
|
|
731
|
+
trait.default = `${prefix}-${freshId()}`;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
708
734
|
function regenerateNodeIds(root, opts = {}) {
|
|
709
735
|
const idMap = new Map();
|
|
710
736
|
const assign = (node) => {
|
|
711
737
|
const oldId = node.id;
|
|
712
738
|
node.id = freshId();
|
|
739
|
+
reissueIdentityTraits(node);
|
|
713
740
|
if (opts.styleId === 'regenerate') {
|
|
714
741
|
if (node.styleId)
|
|
715
742
|
node.styleId = freshId();
|
|
@@ -5907,9 +5934,14 @@ class BuilderComponent {
|
|
|
5907
5934
|
return;
|
|
5908
5935
|
// Straight onto the live instance: this is a VIEW state, not page data — it must never be
|
|
5909
5936
|
// persisted, or a page would ship showing its own success message.
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5937
|
+
//
|
|
5938
|
+
// Two input names on purpose: the CANVAS renders the container twin (`formState`) while the
|
|
5939
|
+
// ENGINE renders IoxFormComponent (`renderState`). safeSetInput drops whichever the rendered
|
|
5940
|
+
// component does not declare, so this works on either path without branching on type.
|
|
5941
|
+
const ref = this.overlayService.getNodeRef(form)?.componentRef;
|
|
5942
|
+
if (ref) {
|
|
5943
|
+
safeSetInput(ref, 'formState', state);
|
|
5944
|
+
safeSetInput(ref, 'renderState', state);
|
|
5913
5945
|
}
|
|
5914
5946
|
this.cdr.detectChanges();
|
|
5915
5947
|
}
|
|
@@ -9705,13 +9737,16 @@ class FieldComponentConfig extends ComponentConfig {
|
|
|
9705
9737
|
{ label: 'Radio group', value: 'radio' },
|
|
9706
9738
|
{ label: 'Date', value: 'date' },
|
|
9707
9739
|
{ label: 'Time', value: 'time' },
|
|
9708
|
-
|
|
9740
|
+
// ⚠️ 'file' is DELIBERATELY ABSENT until uploads are implemented. A file input's
|
|
9741
|
+
// `value` is the browser's fake path (`C:\fakepath\cv.pdf`), and `fileType`/
|
|
9742
|
+
// `fileSize` operate on a file list — so on a string they pass vacuously. Offering
|
|
9743
|
+
// it meant an author could set "PDF only, 5MB" and silently get neither the
|
|
9744
|
+
// enforcement nor the file. See forms-and-inputs-plan.md → Remaining work P0/1.
|
|
9709
9745
|
{ label: 'Hidden', value: 'hidden' },
|
|
9710
9746
|
], 'text'),
|
|
9711
9747
|
new TraitConfig('placeholder', 'Placeholder', TraitInputType.Text, undefined, '', false, { trait: 'control', values: ['text', 'email', 'tel', 'url', 'number', 'password', 'textarea', 'select'] }),
|
|
9712
9748
|
new TraitConfig('help', 'Help text', TraitInputType.Text, undefined, ''),
|
|
9713
9749
|
new TraitConfig('rows', 'Rows', TraitInputType.Number, undefined, 4, false, { trait: 'control', values: 'textarea' }),
|
|
9714
|
-
new TraitConfig('accept', 'Accepted file types', TraitInputType.Text, undefined, '', false, { trait: 'control', values: 'file' }),
|
|
9715
9750
|
new TraitConfig('labelHidden', 'Hide label (screen readers still read it)', TraitInputType.Checkbox, undefined, false),
|
|
9716
9751
|
// The submitted key. Derived from the label; editable for external integrations.
|
|
9717
9752
|
new TraitConfig('name', 'Submitted key', TraitInputType.Text, undefined, ''),
|