@plitzi/sdk-schema 0.32.5 → 0.32.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @plitzi/sdk-schema
2
2
 
3
+ ## 0.32.6
4
+
5
+ ### Patch Changes
6
+
7
+ - v0.32.6
8
+ - Updated dependencies
9
+ - @plitzi/sdk-shared@0.32.6
10
+ - @plitzi/sdk-style@0.32.6
11
+
3
12
  ## 0.32.5
4
13
 
5
14
  ### Patch Changes
@@ -16,6 +16,7 @@ export declare const SchemaActions: {
16
16
  readonly SCHEMA_MOVE_ELEMENT: "SCHEMA_MOVE_ELEMENT";
17
17
  readonly SCHEMA_CLONE_ELEMENT: "SCHEMA_CLONE_ELEMENT";
18
18
  readonly SCHEMA_UPDATE_ELEMENT: "SCHEMA_UPDATE_ELEMENT";
19
+ readonly SCHEMA_UPDATE_ELEMENTS: "SCHEMA_UPDATE_ELEMENTS";
19
20
  readonly SCHEMA_ADD_TEMPLATE: "SCHEMA_ADD_TEMPLATE";
20
21
  readonly SCHEMA_UPDATE_SETTINGS: "SCHEMA_UPDATE_SETTINGS";
21
22
  };
@@ -81,6 +82,9 @@ export type SchemaReducerActions = SchemaReducerActionsBase & ({
81
82
  } | {
82
83
  type: 'SCHEMA_UPDATE_ELEMENT';
83
84
  element: Element;
85
+ } | {
86
+ type: 'SCHEMA_UPDATE_ELEMENTS';
87
+ elements: Element[];
84
88
  } | {
85
89
  type: 'SCHEMA_UPDATE_SETTINGS';
86
90
  path: string;
@@ -19,6 +19,7 @@ var a = {
19
19
  SCHEMA_MOVE_ELEMENT: "SCHEMA_MOVE_ELEMENT",
20
20
  SCHEMA_CLONE_ELEMENT: "SCHEMA_CLONE_ELEMENT",
21
21
  SCHEMA_UPDATE_ELEMENT: "SCHEMA_UPDATE_ELEMENT",
22
+ SCHEMA_UPDATE_ELEMENTS: "SCHEMA_UPDATE_ELEMENTS",
22
23
  SCHEMA_ADD_TEMPLATE: "SCHEMA_ADD_TEMPLATE",
23
24
  SCHEMA_UPDATE_SETTINGS: "SCHEMA_UPDATE_SETTINGS"
24
25
  }, o = (o, s) => {
@@ -127,9 +128,15 @@ var a = {
127
128
  });
128
129
  }
129
130
  case a.SCHEMA_UPDATE_ELEMENT: {
130
- let { element: e } = s;
131
- return i(o, (t) => {
132
- r(t.flat, e.id, e);
131
+ let { element: t } = s;
132
+ return i(o, (n) => {
133
+ e.updateElement(n.flat, t);
134
+ });
135
+ }
136
+ case a.SCHEMA_UPDATE_ELEMENTS: {
137
+ let { elements: t } = s;
138
+ return i(o, (n) => {
139
+ t.forEach((t) => e.updateElement(n.flat, t));
133
140
  });
134
141
  }
135
142
  case a.SCHEMA_UPDATE_SETTINGS: {
@@ -9,9 +9,21 @@ declare class FlatMap {
9
9
  variables: Schema['variables'];
10
10
  constructor(props?: FlatMapProps);
11
11
  addElement: (data: Element, to: Element["id"], dropPosition?: DropPosition, initialItems?: Record<Element["id"], Element>) => boolean;
12
+ /** Refuses a rename onto an unusable idRef: two elements answering to the same name make every binding and
13
+ * interaction written against it resolve to whichever one is found first. Only a change is checked, so an
14
+ * element already stored with a bad ref stays editable.
15
+ *
16
+ * A rename that IS accepted carries its wiring with it: the idRef is the key bindings and interactions target,
17
+ * so every reference to the old name across the space is repointed here. Doing it at the single point every
18
+ * writer goes through is what keeps a rename from silently unwiring the element. */
12
19
  updateElement: (element?: Element) => boolean;
13
20
  moveElement: (from: Element["id"], to: Element["id"], elementId: Element["id"], dropPosition?: DropPosition) => boolean;
14
21
  getElement: (elementId: Element["id"]) => Element<Record<string, unknown>>;
22
+ /** Every idRef currently in use, so a newly minted one stays unique across the space. */
23
+ takenIdRefs: () => Set<string>;
24
+ /** Why an idRef cannot be used here (charset or a clash), or null when it is free. `ignoreElementId` exempts the
25
+ * element being edited, so re-saving an element its own ref is not a conflict. */
26
+ idRefConflict: (idRef: string, ignoreElementId?: Element["id"]) => string | null;
15
27
  cloneElements: (elementId: Element["id"], parentId?: Element["id"], rootId?: Element["id"], excludeRoot?: boolean) => {
16
28
  acum: Record<Element["id"], Element>;
17
29
  item?: Element;
@@ -47,6 +59,8 @@ declare class FlatMap {
47
59
  item?: Element;
48
60
  };
49
61
  static removeElement: (flat: Schema["flat"], elementId: Element["id"], removePage?: boolean) => boolean;
62
+ static takenIdRefs: (flat: Schema["flat"]) => Set<string>;
63
+ static idRefConflict: (flat: Schema["flat"], idRef: string, ignoreElementId?: Element["id"]) => string | null;
50
64
  static addVariables: (schemaVariables: Schema["variables"], variables: Schema["variables"]) => boolean;
51
65
  static addVariable: (schemaVariables: Schema["variables"], variable: SchemaVariable) => boolean;
52
66
  static updateVariable: (schemaVariables: Schema["variables"], variable: SchemaVariable) => boolean;
@@ -1,11 +1,12 @@
1
- import { validateSchema as e } from "./schemaValidator.mjs";
2
- import { get as t, set as n } from "@plitzi/plitzi-ui/helpers";
3
- import { generateID as r } from "@plitzi/sdk-shared/helpers/utils";
4
- import { EMPTY_SCHEMA as i, VARIABLE_REGEX as a } from "@plitzi/sdk-shared/schema/schemaConstants";
5
- import { EMPTY_STYLE_SCHEMA as o } from "@plitzi/sdk-shared/style/styleConstants";
6
- import s from "@plitzi/sdk-style/helpers/calculateInheriting";
1
+ import { idRefConflict as e, idRefsUsable as t, remapClonedRefs as n, repointIdRefs as r, takenIdRefs as i } from "./idRef.mjs";
2
+ import { validateSchema as a } from "./schemaValidator.mjs";
3
+ import { get as o, set as s } from "@plitzi/plitzi-ui/helpers";
4
+ import { generateID as c } from "@plitzi/sdk-shared/helpers/utils";
5
+ import { EMPTY_SCHEMA as l, VARIABLE_REGEX as u } from "@plitzi/sdk-shared/schema/schemaConstants";
6
+ import { EMPTY_STYLE_SCHEMA as d } from "@plitzi/sdk-shared/style/styleConstants";
7
+ import f from "@plitzi/sdk-style/helpers/calculateInheriting";
7
8
  //#region src/helpers/FlatMap.ts
8
- var c = class {
9
+ var p = class {
9
10
  flat;
10
11
  variables;
11
12
  constructor(e = {}) {
@@ -13,95 +14,101 @@ var c = class {
13
14
  if (!t) throw Error("Flat is required");
14
15
  this.flat = t, this.variables = n ?? [];
15
16
  }
16
- addElement = (e, r, i = "inside", a = {}) => {
17
- let o;
18
- if (i !== "custom") {
19
- if (i !== "inside") {
20
- let e = this.flat[r];
17
+ addElement = (e, n, r = "inside", i = {}) => {
18
+ let a;
19
+ if (r !== "custom") {
20
+ if (r !== "inside") {
21
+ let e = this.flat[n];
21
22
  if (!e) return !1;
22
- e.definition.parentId && (o = this.flat[e.definition.parentId]);
23
- } else o = this.flat[r];
24
- if (!o) return !1;
23
+ e.definition.parentId && (a = this.flat[e.definition.parentId]);
24
+ } else a = this.flat[n];
25
+ if (!a) return !1;
25
26
  }
26
- if (i !== "custom" && (this.flat[e.id] || !Array.isArray(t(o, "definition.items"))) || !this.isValidElement(e)) return !1;
27
- switch (n(this.flat, e.id, e), i) {
27
+ if (r !== "custom" && (this.flat[e.id] || !Array.isArray(o(a, "definition.items"))) || !this.isValidElement(e) || !t(this.flat, [e, ...Object.values(i)])) return !1;
28
+ switch (s(this.flat, e.id, e), r) {
28
29
  case "left":
29
30
  case "top": {
30
- let i = t(o, "definition.items", []);
31
- if (i.splice(i.findIndex((e) => e === r), 0, e.id), !o) return !1;
32
- n(this.flat, `${o.id}.definition.items`, i), n(this.flat, `${e.id}.definition.parentId`, o.id), n(this.flat, `${e.id}.definition.rootId`, o.definition.rootId);
31
+ let t = o(a, "definition.items", []);
32
+ if (t.splice(t.findIndex((e) => e === n), 0, e.id), !a) return !1;
33
+ s(this.flat, `${a.id}.definition.items`, t), s(this.flat, `${e.id}.definition.parentId`, a.id), s(this.flat, `${e.id}.definition.rootId`, a.definition.rootId);
33
34
  break;
34
35
  }
35
36
  case "right":
36
37
  case "bottom": {
37
- let i = t(o, "definition.items", []);
38
- if (i.splice(i.findIndex((e) => e === r) + 1, 0, e.id), !o) return !1;
39
- n(this.flat, `${o.id}.definition.items`, i), n(this.flat, `${e.id}.definition.parentId`, o.id), n(this.flat, `${e.id}.definition.rootId`, o.definition.rootId);
38
+ let t = o(a, "definition.items", []);
39
+ if (t.splice(t.findIndex((e) => e === n) + 1, 0, e.id), !a) return !1;
40
+ s(this.flat, `${a.id}.definition.items`, t), s(this.flat, `${e.id}.definition.parentId`, a.id), s(this.flat, `${e.id}.definition.rootId`, a.definition.rootId);
40
41
  break;
41
42
  }
42
43
  case "inside": {
43
- let i = t(o, "definition.items", []);
44
- if (!o) return !1;
45
- n(this.flat, `${r}.definition.items`, [...i, e.id]), n(this.flat, `${e.id}.definition.parentId`, r), n(this.flat, `${e.id}.definition.rootId`, o.definition.rootId);
44
+ let t = o(a, "definition.items", []);
45
+ if (!a) return !1;
46
+ s(this.flat, `${n}.definition.items`, [...t, e.id]), s(this.flat, `${e.id}.definition.parentId`, n), s(this.flat, `${e.id}.definition.rootId`, a.definition.rootId);
46
47
  break;
47
48
  }
48
49
  case "custom": break;
49
50
  default: return !1;
50
51
  }
51
- return Object.keys(a).length > 0 && Object.keys(a).forEach((e) => {
52
- this.flat[e] = a[e];
52
+ return Object.keys(i).length > 0 && Object.keys(i).forEach((e) => {
53
+ this.flat[e] = i[e];
53
54
  }), !0;
54
55
  };
55
- updateElement = (e) => !e || !this.flat[e.id] ? !1 : (this.flat[e.id] = e, !0);
56
- moveElement = (e, r, i, a = "inside") => {
57
- if (i === r || !this.flat[e]) return !1;
58
- let o = this.flat[r];
59
- if (!o) return !1;
60
- let s = this.flat[r];
61
- for (; s;) {
62
- let e = t(s, "definition.parentId");
56
+ updateElement = (e) => {
57
+ if (!e || !this.flat[e.id]) return !1;
58
+ let n = this.flat[e.id].idRef, i = e.idRef !== n;
59
+ return i && !t(this.flat, [e]) ? !1 : i && n && e.idRef ? (this.flat[e.id] = structuredClone(e), r(this.flat, { [n]: e.idRef }), !0) : (this.flat[e.id] = e, !0);
60
+ };
61
+ moveElement = (e, t, n, r = "inside") => {
62
+ if (n === t || !this.flat[e]) return !1;
63
+ let i = this.flat[t];
64
+ if (!i) return !1;
65
+ let a = this.flat[t];
66
+ for (; a;) {
67
+ let e = o(a, "definition.parentId");
63
68
  if (!e) break;
64
- if (s.id === i) return !1;
65
- s = this.flat[e];
69
+ if (a.id === n) return !1;
70
+ a = this.flat[e];
66
71
  }
67
- if (!s) return !1;
68
- let c = t(this.flat, `${e}.definition.items`, []).filter((e) => e !== i);
72
+ if (!a) return !1;
73
+ let c = o(this.flat, `${e}.definition.items`, []).filter((e) => e !== n);
69
74
  if ([
70
75
  "left",
71
76
  "top",
72
77
  "right",
73
78
  "bottom"
74
- ].includes(a)) {
75
- if (!o.definition.parentId) return !1;
76
- let s = this.flat[o.definition.parentId];
77
- if (!s) return !1;
78
- let l = t(s, "definition.items", []);
79
- s.id === e && (l = c);
80
- let u = l.findIndex((e) => e === r);
81
- ["right", "bottom"].includes(a) && u++, l.splice(u, 0, i), n(this.flat, `${e}.definition.items`, c), n(this.flat, `${s.id}.definition.items`, l), n(this.flat, `${i}.definition.parentId`, s.id);
82
- } else if (a === "inside") {
83
- if (!this.flat[r]) return !1;
84
- let a = t(this.flat, `${r}.definition.items`, []);
85
- e === r && (a = c), a = [...a, i], n(this.flat, `${e}.definition.items`, c), n(this.flat, `${r}.definition.items`, a), n(this.flat, `${i}.definition.parentId`, r);
79
+ ].includes(r)) {
80
+ if (!i.definition.parentId) return !1;
81
+ let a = this.flat[i.definition.parentId];
82
+ if (!a) return !1;
83
+ let l = o(a, "definition.items", []);
84
+ a.id === e && (l = c);
85
+ let u = l.findIndex((e) => e === t);
86
+ ["right", "bottom"].includes(r) && u++, l.splice(u, 0, n), s(this.flat, `${e}.definition.items`, c), s(this.flat, `${a.id}.definition.items`, l), s(this.flat, `${n}.definition.parentId`, a.id);
87
+ } else if (r === "inside") {
88
+ if (!this.flat[t]) return !1;
89
+ let r = o(this.flat, `${t}.definition.items`, []);
90
+ e === t && (r = c), r = [...r, n], s(this.flat, `${e}.definition.items`, c), s(this.flat, `${t}.definition.items`, r), s(this.flat, `${n}.definition.parentId`, t);
86
91
  }
87
92
  return !0;
88
93
  };
89
- getElement = (e) => t(this.flat, e);
90
- cloneElements = (e, i = "", a = "", o = !1) => {
91
- let s = {
94
+ getElement = (e) => o(this.flat, e);
95
+ takenIdRefs = () => i(this.flat);
96
+ idRefConflict = (t, n) => e(this.flat, t, n);
97
+ cloneElements = (e, t = "", r = "", i = !1) => {
98
+ let a = {
92
99
  acum: {},
93
100
  item: void 0
94
- }, c = {}, l = this.flat[e];
95
- if (!l) return s;
96
- let u = [e, ...this.childTree(e)].reduce((e, t) => {
101
+ }, l = {}, u = this.flat[e];
102
+ if (!u) return a;
103
+ let d = this.takenIdRefs(), f = [e, ...this.childTree(e)].reduce((e, t) => {
97
104
  let n = this.flat[t];
98
- return n ? (c[n.id] = r(n.id), a ? {
105
+ return n ? (l[n.id] = c(n.id), r ? {
99
106
  ...e,
100
107
  [n.id]: {
101
108
  ...n,
102
109
  definition: {
103
110
  ...n.definition,
104
- rootId: a
111
+ rootId: r
105
112
  }
106
113
  }
107
114
  } : {
@@ -110,27 +117,27 @@ var c = class {
110
117
  }) : e;
111
118
  }, {});
112
119
  try {
113
- let t = JSON.stringify(u);
114
- t = Object.keys(c).reduce((e, t) => e.replace(new RegExp(t, "g"), c[t]), t), s.acum = JSON.parse(t), s.item = s.acum[c[e]];
120
+ let t = JSON.stringify(f);
121
+ t = Object.keys(l).reduce((e, t) => e.replace(new RegExp(t, "g"), l[t]), t), a.acum = JSON.parse(t), a.item = a.acum[l[e]], n(a.acum, (e) => d.has(e));
115
122
  } catch (e) {
116
123
  return console.error("Error parsing elements", e), {
117
124
  acum: {},
118
125
  item: void 0
119
126
  };
120
127
  }
121
- o && delete s.acum[c[e]];
122
- let d = this.flat[i];
123
- return (!d || !Array.isArray(t(d, "definition.items"))) && (i = t(d, "definition.parentId", t(l, "definition.parentId"))), i && n(s, "item.definition.parentId", i), s;
128
+ i && delete a.acum[l[e]];
129
+ let p = this.flat[t];
130
+ return (!p || !Array.isArray(o(p, "definition.items"))) && (t = o(p, "definition.parentId", o(u, "definition.parentId"))), t && s(a, "item.definition.parentId", t), a;
124
131
  };
125
- removeElement = (e, r = !1) => {
126
- let i = this.flat[e];
127
- if (!i || i.definition.type === "page" && !r || r && t(i, "attributes.default", !1)) return !1;
128
- let a = t(i, "definition.items");
129
- a && a.length > 0 && a.forEach((e) => this.removeElement(e));
130
- let o = t(i, "definition.parentId"), s = o ? this.flat[o] : void 0;
131
- if (o && s) {
132
- let { definition: { items: t = [] } } = s;
133
- n(s, "definition.items", t.filter((t) => t !== e)), this.flat[o] = s;
132
+ removeElement = (e, t = !1) => {
133
+ let n = this.flat[e];
134
+ if (!n || n.definition.type === "page" && !t || t && o(n, "attributes.default", !1)) return !1;
135
+ let r = o(n, "definition.items");
136
+ r && r.length > 0 && r.forEach((e) => this.removeElement(e));
137
+ let i = o(n, "definition.parentId"), a = i ? this.flat[i] : void 0;
138
+ if (i && a) {
139
+ let { definition: { items: t = [] } } = a;
140
+ s(a, "definition.items", t.filter((t) => t !== e)), this.flat[i] = a;
134
141
  }
135
142
  return delete this.flat[e], !0;
136
143
  };
@@ -154,22 +161,22 @@ var c = class {
154
161
  };
155
162
  removeVariable = (e) => this.removeVariables([e]);
156
163
  parentTree = (e) => {
157
- let n = this.flat[e], r = [];
158
- if (!n) return r;
164
+ let t = this.flat[e], n = [];
165
+ if (!t) return n;
159
166
  do {
160
- if (t(n, "definition.type") === "page") {
161
- let e = t(n, "attributes.layout"), i = t(n, "attributes.layoutContainer");
162
- e && i && r.push(i, ...this.parentTree(i));
167
+ if (o(t, "definition.type") === "page") {
168
+ let e = o(t, "attributes.layout"), r = o(t, "attributes.layoutContainer");
169
+ e && r && n.push(r, ...this.parentTree(r));
163
170
  }
164
- e !== n.id && r.push(n.id), n = t(this.flat, t(n, "definition.parentId"), void 0);
165
- } while (n);
166
- return r;
171
+ e !== t.id && n.push(t.id), t = o(this.flat, o(t, "definition.parentId"), void 0);
172
+ } while (t);
173
+ return n;
167
174
  };
168
175
  childTree = (e) => {
169
- let n = this.flat[e];
170
- if (!n) return [];
171
- let r = [], i = t(n, "definition.items");
172
- return i && i.forEach((e) => r.push(e, ...this.childTree(e))), r;
176
+ let t = this.flat[e];
177
+ if (!t) return [];
178
+ let n = [], r = o(t, "definition.items");
179
+ return r && r.forEach((e) => n.push(e, ...this.childTree(e))), n;
173
180
  };
174
181
  isValidElement = (e) => {
175
182
  if (!e) return !1;
@@ -178,85 +185,85 @@ var c = class {
178
185
  let { type: i, label: a, styleSelectors: o, rootId: s } = r;
179
186
  return !(!i || a === void 0 || typeof o != "object" || s === void 0);
180
187
  };
181
- flatAsTemplate = (e, r, i = !1) => {
182
- let a = {
183
- ...o,
188
+ flatAsTemplate = (e, t, n = !1) => {
189
+ let r = {
190
+ ...d,
184
191
  platform: {
185
192
  desktop: {},
186
193
  tablet: {},
187
194
  mobile: {}
188
195
  }
189
- }, c = [];
190
- if (!r) return {
196
+ }, i = [];
197
+ if (!t) return {
191
198
  elements: {
192
199
  acum: {},
193
200
  item: void 0
194
201
  },
195
- elementsStyle: a,
196
- variables: c
202
+ elementsStyle: r,
203
+ variables: i
197
204
  };
198
- let l = t(this.flat, r);
199
- if (!l) return {
205
+ let a = o(this.flat, t);
206
+ if (!a) return {
200
207
  elements: {
201
208
  acum: {},
202
209
  item: void 0
203
210
  },
204
- elementsStyle: a,
205
- variables: c
211
+ elementsStyle: r,
212
+ variables: i
206
213
  };
207
- let u = this.cloneElements(r, l.definition.parentId);
208
- return u.item ? (Object.values(u.acum).forEach((t) => {
209
- let { id: r } = t;
210
- if (n(u.acum, `${r}.definition.rootId`, u.item?.id), s(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
211
- let { displayMode: n, name: r } = t;
212
- !(r in a.platform[n]) && r in e.platform[n] && (a.platform[n][r] = e.platform[n][r]);
214
+ let c = this.cloneElements(t, a.definition.parentId);
215
+ return c.item ? (Object.values(c.acum).forEach((t) => {
216
+ let { id: n } = t;
217
+ if (s(c.acum, `${n}.definition.rootId`, c.item?.id), f(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
218
+ let { displayMode: n, name: i } = t;
219
+ !(i in r.platform[n]) && i in e.platform[n] && (r.platform[n][i] = e.platform[n][i]);
213
220
  }), this.variables.length > 0) {
214
- let t = this.getElementVariables(e, r, u.acum);
215
- c = [...c, ...t];
221
+ let t = this.getElementVariables(e, n, c.acum);
222
+ i = [...i, ...t];
216
223
  }
217
- }), n(u.acum, `${u.item.id}.definition.parentId`, null), i && delete u.acum[u.item.id], c.length > 1 && (c = [...new Set(c)]), {
218
- elements: u,
219
- elementsStyle: a,
220
- variables: c
224
+ }), s(c.acum, `${c.item.id}.definition.parentId`, null), n && delete c.acum[c.item.id], i.length > 1 && (i = [...new Set(i)]), {
225
+ elements: c,
226
+ elementsStyle: r,
227
+ variables: i
221
228
  }) : {
222
229
  elements: {
223
230
  acum: {},
224
231
  item: void 0
225
232
  },
226
- elementsStyle: a,
227
- variables: c
233
+ elementsStyle: r,
234
+ variables: i
228
235
  };
229
236
  };
230
- validate = () => e({
231
- ...i.schema,
237
+ validate = () => a({
238
+ ...l.schema,
232
239
  flat: this.flat,
233
240
  variables: this.variables
234
241
  });
235
242
  isValid = () => this.validate().valid;
236
- assertValid = (t) => {
237
- let n = e({
238
- ...i.schema,
243
+ assertValid = (e) => {
244
+ let t = a({
245
+ ...l.schema,
239
246
  flat: this.flat,
240
247
  variables: this.variables
241
248
  });
242
- if (!n.valid) {
243
- let e = `Invalid schema${t ? ` (${t})` : ""}: ${n.errors.map((e) => e.message).join("; ")}`;
244
- throw Error(e);
249
+ if (!t.valid) {
250
+ let n = `Invalid schema${e ? ` (${e})` : ""}: ${t.errors.map((e) => e.message).join("; ")}`;
251
+ throw Error(n);
245
252
  }
246
253
  };
247
- getElementVariables = (e, n, r = this.flat, i = this.variables) => {
248
- let o = [], s = t(r, `${n}.definition.styleSelectors`);
249
- if (!s) return o;
250
- let c = new RegExp(a, "g");
251
- return Object.values(s).filter(Boolean).forEach((t) => {
254
+ getElementVariables = (e, t, n = this.flat, r = this.variables) => {
255
+ let i = [], a = o(n, `${t}.definition.styleSelectors`);
256
+ if (!a) return i;
257
+ let s = new RegExp(u, "g");
258
+ return Object.values(a).filter(Boolean).forEach((t) => {
252
259
  Object.values(e.platform).forEach((e) => {
253
260
  let n = e[t];
254
- n && [...JSON.stringify(n.attributes).matchAll(c)].forEach((e) => {
255
- let t = i.find((t) => t.name === e[1] || t.name === e[2]);
256
- t && !o.find((e) => e.name === t.name) && o.push(t);
261
+ n && [...JSON.stringify(n.attributes).matchAll(s)].forEach((e) => {
262
+ let t = r.find((t) => t.name === e[1] || t.name === e[2]);
263
+ t && !i.find((e) => e.name === t.name) && i.push(t);
257
264
  });
258
265
  });
259
- }), o;
266
+ }), i;
260
267
  };
261
268
  static getInstance = (e) => new this(e);
262
269
  static addElement = (e, t, n, r = "inside", i = {}) => this.getInstance({ flat: e }).addElement(t, n, r, i);
@@ -265,6 +272,8 @@ var c = class {
265
272
  static getElement = (e, t) => this.getInstance({ flat: e }).getElement(t);
266
273
  static cloneElements = (e, t, n = "", r = "", i = !1) => this.getInstance({ flat: e }).cloneElements(t, n, r, i);
267
274
  static removeElement = (e, t, n = !1) => this.getInstance({ flat: e }).removeElement(t, n);
275
+ static takenIdRefs = (e) => this.getInstance({ flat: e }).takenIdRefs();
276
+ static idRefConflict = (e, t, n) => this.getInstance({ flat: e }).idRefConflict(t, n);
268
277
  static addVariables = (e, t) => this.getInstance({ variables: e }).addVariables(t);
269
278
  static addVariable = (e, t) => this.getInstance({ variables: e }).addVariable(t);
270
279
  static updateVariable = (e, t) => this.getInstance({ variables: e }).updateVariable(t);
@@ -287,15 +296,15 @@ var c = class {
287
296
  variables: i
288
297
  }).getElementVariables(t, n);
289
298
  };
290
- static validate = (t) => e(t);
291
- static isValid = (t) => e(t).valid;
292
- static assertValid = (t, n) => {
293
- let r = e(t);
294
- if (!r.valid) {
295
- let e = `Invalid schema${n ? ` (${n})` : ""}: ${r.errors.map((e) => e.message).join("; ")}`;
299
+ static validate = (e) => a(e);
300
+ static isValid = (e) => a(e).valid;
301
+ static assertValid = (e, t) => {
302
+ let n = a(e);
303
+ if (!n.valid) {
304
+ let e = `Invalid schema${t ? ` (${t})` : ""}: ${n.errors.map((e) => e.message).join("; ")}`;
296
305
  throw Error(e);
297
306
  }
298
307
  };
299
308
  };
300
309
  //#endregion
301
- export { c as default };
310
+ export { p as default };
@@ -0,0 +1,34 @@
1
+ import { Element } from '@plitzi/sdk-shared';
2
+ /** True when an idRef is well formed: it starts with a letter and then carries letters, numbers, hyphens and
3
+ * underscores. Leading letter keeps a source `<type>_<idRef>` a valid twig identifier. A '.' would split the
4
+ * `<type>_<idRef>.<field>` source path and an interaction target lookup — so it is not allowed. A source name
5
+ * uses the first '_' as separator between `<type>` and `<idRef>` (element types are camelCase with no underscore),
6
+ * so underscores inside the idRef are unambiguous. A hyphen is allowed too: a source doubles as a twig token, and
7
+ * `processTwig` resolves a hyphenated `<type>_<idRef>` through `_context` subscript access. */
8
+ export declare const isValidIdRef: (idRef: string) => boolean;
9
+ /** Every idRef currently in use, so a new one can be checked or minted against it. */
10
+ export declare const takenIdRefs: (flat: Record<Element["id"], Element>) => Set<string>;
11
+ /** Why `idRef` cannot be used in this schema, phrased for a person, or null when it is free and well formed.
12
+ * `ignoreElementId` exempts the element being edited, so re-saving an element its own ref is not a conflict. */
13
+ export declare const idRefConflict: (flat: Record<Element["id"], Element>, idRef: string, ignoreElementId?: Element["id"]) => string | null;
14
+ /** Whether every idRef a write brings into a schema is usable: well formed, free — an element already stored never
15
+ * clashes with itself — and not repeated inside the incoming set, which `idRefConflict` cannot see on its own
16
+ * because those elements are not in `flat` yet. */
17
+ export declare const idRefsUsable: (flat: Record<Element["id"], Element>, elements: Element[]) => boolean;
18
+ /** A unique, charset-safe idRef for a new element: `<type>-<n>`, incrementing until `isTaken` says it is free.
19
+ * The hyphen sits between two alphanumeric runs, so it is valid (never leading or trailing) and readable. */
20
+ export declare const makeIdRef: (type: string, isTaken: (candidate: string) => boolean) => string;
21
+ /** Apply an idRef renaming (old → new) across a set of elements: the idRef itself plus every reference written
22
+ * against it — a binding's data `source`, its transformer params and `when`, and an interaction's target
23
+ * `elementId`, params and `when`. A ref absent from the map is left alone, so references leaving this set keep
24
+ * pointing outward, which is what callers want.
25
+ *
26
+ * Ids are remapped by a blind string replace over the serialized tree (see `FlatMap.cloneElements`); that is safe
27
+ * for a 24-char hex id but not for an idRef, which is short and human-readable — replacing "card-1" everywhere
28
+ * would corrupt any label containing it. Hence this field-by-field pass: a plain `source` goes through
29
+ * `remapSource`, embedded tokens (twig, transformer params, query-builder operands) through `remapTokensDeep`,
30
+ * which only ever rewrites a full `<type>_<idRef>` token. */
31
+ export declare const repointIdRefs: (elements: Record<Element["id"], Element>, mapRefs: Record<string, string>) => void;
32
+ /** Give every element in a freshly cloned subtree a new idRef, so the copy wires to itself instead of to the
33
+ * original it was cloned from. An element that had no idRef stays without one — a clone copies what is there. */
34
+ export declare const remapClonedRefs: (elements: Record<Element["id"], Element>, isTaken: (ref: string) => boolean) => void;
@@ -0,0 +1,72 @@
1
+ //#region src/helpers/idRef.ts
2
+ var e = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?$/, t = (t) => e.test(t), n = (e) => {
3
+ let t = /* @__PURE__ */ new Set();
4
+ for (let n of Object.values(e)) n.idRef && t.add(n.idRef);
5
+ return t;
6
+ }, r = (e, n, r) => t(n) ? Object.values(e).find((e) => e.idRef === n && e.id !== r) ? `"${n}" is already used by another element in this space` : null : `"${n}" is not a valid reference: start with a letter, then letters, numbers, hyphens and underscores`, i = (e, t) => {
7
+ let n = /* @__PURE__ */ new Set();
8
+ for (let i of t) if (i.idRef) {
9
+ if (n.has(i.idRef) || r(e, i.idRef, i.id)) return !1;
10
+ n.add(i.idRef);
11
+ }
12
+ return !0;
13
+ }, a = (e, t) => {
14
+ let n = e.replace(/[^A-Za-z0-9]/g, "") || "element", r = 1;
15
+ for (; t(`${n}-${r}`);) r += 1;
16
+ return `${n}-${r}`;
17
+ }, o = (e, t) => {
18
+ let n = e.indexOf("."), r = n === -1 ? e : e.slice(0, n), i = r.indexOf("_");
19
+ if (i === -1) return e;
20
+ let a = t[r.slice(i + 1)];
21
+ return a ? `${r.slice(0, i + 1)}${a}${n === -1 ? "" : e.slice(n)}` : e;
22
+ }, s = /([A-Za-z][A-Za-z0-9]*)_([A-Za-z][A-Za-z0-9_-]*)/g, c = (e, t) => e.replace(s, (e, n, r) => {
23
+ let i = t[r];
24
+ return i ? `${n}_${i}` : e;
25
+ }), l = (e, t) => {
26
+ if (Array.isArray(e)) {
27
+ e.forEach((n, r) => {
28
+ if (typeof n == "string") {
29
+ let i = c(n, t);
30
+ i !== n && (e[r] = i);
31
+ return;
32
+ }
33
+ l(n, t);
34
+ });
35
+ return;
36
+ }
37
+ if (!e || typeof e != "object") return;
38
+ let n = e;
39
+ for (let [e, r] of Object.entries(n)) {
40
+ if (typeof r == "string") {
41
+ let i = c(r, t);
42
+ i !== r && (n[e] = i);
43
+ continue;
44
+ }
45
+ l(r, t);
46
+ }
47
+ }, u = (e, t) => {
48
+ for (let n of Object.values(e)) {
49
+ n.idRef && t[n.idRef] && (n.idRef = t[n.idRef]);
50
+ for (let e of Object.values(n.definition.bindings ?? {})) for (let n of e) {
51
+ let e = o(n.source, t);
52
+ e !== n.source && (n.source = e), n.transformers && l(n.transformers, t), n.when && l(n.when, t);
53
+ }
54
+ for (let e of Object.values(n.definition.interactions ?? {})) {
55
+ if (e.elementId) {
56
+ let n = t[e.elementId];
57
+ n && (e.elementId = n);
58
+ }
59
+ l(e.params, t), e.when && l(e.when, t);
60
+ }
61
+ }
62
+ }, d = (e, t) => {
63
+ let n = {}, r = /* @__PURE__ */ new Set();
64
+ for (let i of Object.values(e)) {
65
+ if (!i.idRef) continue;
66
+ let e = a(i.definition.type, (e) => t(e) || r.has(e));
67
+ r.add(e), n[i.idRef] = e;
68
+ }
69
+ u(e, n);
70
+ };
71
+ //#endregion
72
+ export { r as idRefConflict, i as idRefsUsable, t as isValidIdRef, a as makeIdRef, d as remapClonedRefs, u as repointIdRefs, n as takenIdRefs };
@@ -1,111 +1,112 @@
1
+ import { isValidIdRef as e } from "./idRef.mjs";
1
2
  //#region src/helpers/schemaValidator.ts
2
- var e = (e) => {
3
- let t = [], n = [], { flat: r, pages: i, pageFolders: a, variables: o } = e, s = (e) => !!r[e], c = (e) => r[e], l = () => !r || typeof r != "object" ? (t.push({
3
+ var t = (t) => {
4
+ let n = [], r = [], { flat: i, pages: a, pageFolders: o, variables: s } = t, c = (e) => !!i[e], l = (e) => i[e], u = () => !i || typeof i != "object" ? (n.push({
4
5
  code: "INVALID_FLAT",
5
6
  message: "Schema.flat must be a valid Record<string, Element>"
6
- }), !1) : Array.isArray(i) ? Array.isArray(a) ? Array.isArray(o) ? !0 : (t.push({
7
+ }), !1) : Array.isArray(a) ? Array.isArray(o) ? Array.isArray(s) ? !0 : (n.push({
7
8
  code: "INVALID_VARIABLES",
8
9
  message: "Schema.variables must be an array"
9
- }), !1) : (t.push({
10
+ }), !1) : (n.push({
10
11
  code: "INVALID_PAGE_FOLDERS",
11
12
  message: "Schema.pageFolders must be an array"
12
- }), !1) : (t.push({
13
+ }), !1) : (n.push({
13
14
  code: "INVALID_PAGES",
14
15
  message: "Schema.pages must be an array of element IDs"
15
- }), !1), u = () => {
16
- Object.entries(r).forEach(([e, r]) => {
17
- if (!r) {
18
- t.push({
16
+ }), !1), d = () => {
17
+ Object.entries(i).forEach(([e, t]) => {
18
+ if (!t) {
19
+ n.push({
19
20
  code: "NULL_ELEMENT",
20
21
  message: `Element with id "${e}" is null or undefined`,
21
22
  elementId: e
22
23
  });
23
24
  return;
24
25
  }
25
- if (!r.id) {
26
- t.push({
26
+ if (!t.id) {
27
+ n.push({
27
28
  code: "MISSING_ID",
28
29
  message: `Element at key "${e}" has no id property`
29
30
  });
30
31
  return;
31
32
  }
32
- if (r.id !== e && t.push({
33
+ if (t.id !== e && n.push({
33
34
  code: "ID_MISMATCH",
34
- message: `Element id "${r.id}" doesn't match flat key "${e}"`,
35
+ message: `Element id "${t.id}" doesn't match flat key "${e}"`,
35
36
  elementId: e
36
- }), !r.definition) {
37
- t.push({
37
+ }), !t.definition) {
38
+ n.push({
38
39
  code: "MISSING_DEFINITION",
39
40
  message: "Element missing definition",
40
41
  elementId: e
41
42
  });
42
43
  return;
43
44
  }
44
- let { definition: i, attributes: a } = r;
45
- i.type || t.push({
45
+ let { definition: i, attributes: a } = t;
46
+ i.type || n.push({
46
47
  code: "MISSING_TYPE",
47
48
  message: "Element missing definition.type",
48
49
  elementId: e
49
- }), i.label === void 0 && t.push({
50
+ }), i.label === void 0 && n.push({
50
51
  code: "MISSING_LABEL",
51
52
  message: "Element missing definition.label",
52
53
  elementId: e
53
- }), i.rootId || t.push({
54
+ }), i.rootId || n.push({
54
55
  code: "MISSING_ROOT_ID",
55
56
  message: "Element missing definition.rootId",
56
57
  elementId: e
57
- }), (!i.styleSelectors || typeof i.styleSelectors != "object") && t.push({
58
+ }), (!i.styleSelectors || typeof i.styleSelectors != "object") && n.push({
58
59
  code: "MISSING_STYLE_SELECTORS",
59
60
  message: "Element missing or invalid definition.styleSelectors",
60
61
  elementId: e
61
- }), a || n.push({
62
+ }), a || r.push({
62
63
  code: "MISSING_ATTRIBUTES",
63
64
  message: "Element has no attributes",
64
65
  elementId: e
65
- }), i.items && (Array.isArray(i.items) ? i.items.forEach((n, r) => {
66
- s(n) || t.push({
66
+ }), i.items && (Array.isArray(i.items) ? i.items.forEach((t, r) => {
67
+ c(t) || n.push({
67
68
  code: "ORPHANED_ITEM_REFERENCE",
68
- message: `Element "${e}" references non-existent child "${n}" at items[${r}]`,
69
+ message: `Element "${e}" references non-existent child "${t}" at items[${r}]`,
69
70
  elementId: e,
70
71
  details: {
71
- childId: n,
72
+ childId: t,
72
73
  index: r
73
74
  }
74
75
  });
75
- }) : t.push({
76
+ }) : n.push({
76
77
  code: "INVALID_ITEMS",
77
78
  message: "Element.definition.items must be an array",
78
79
  elementId: e
79
- })), i.parentId && !s(i.parentId) && t.push({
80
+ })), i.parentId && !c(i.parentId) && n.push({
80
81
  code: "ORPHANED_PARENT_REFERENCE",
81
82
  message: `Element "${e}" has parentId "${i.parentId}" but parent doesn't exist in flat`,
82
83
  elementId: e,
83
84
  details: { parentId: i.parentId }
84
85
  });
85
86
  });
86
- }, d = () => {
87
- Object.values(r).forEach((e) => {
87
+ }, f = () => {
88
+ Object.values(i).forEach((e) => {
88
89
  if (!e?.definition) return;
89
- let { id: n, definition: r } = e;
90
+ let { id: t, definition: r } = e;
90
91
  if (r.items && r.items.forEach((e) => {
91
- let r = c(e);
92
- r?.definition && r.definition.parentId !== n && t.push({
92
+ let r = l(e);
93
+ r?.definition && r.definition.parentId !== t && n.push({
93
94
  code: "PARENT_CHILD_MISMATCH",
94
- message: `Element "${e}" is in "${n}".items but has parentId "${r.definition.parentId}"`,
95
+ message: `Element "${e}" is in "${t}".items but has parentId "${r.definition.parentId}"`,
95
96
  elementId: e,
96
97
  details: {
97
- expectedParent: n,
98
+ expectedParent: t,
98
99
  actualParent: r.definition.parentId
99
100
  }
100
101
  });
101
102
  }), r.parentId) {
102
- let e = c(r.parentId);
103
+ let e = l(r.parentId);
103
104
  if (!e?.definition) return;
104
105
  let i = e.definition.items || [];
105
- i.includes(n) || t.push({
106
+ i.includes(t) || n.push({
106
107
  code: "PARENT_CHILD_MISMATCH",
107
- message: `Element "${n}" has parentId "${r.parentId}" but parent doesn't have it in items`,
108
- elementId: n,
108
+ message: `Element "${t}" has parentId "${r.parentId}" but parent doesn't have it in items`,
109
+ elementId: t,
109
110
  details: {
110
111
  parentId: r.parentId,
111
112
  parentItems: i
@@ -113,169 +114,202 @@ var e = (e) => {
113
114
  });
114
115
  }
115
116
  });
116
- }, f = () => {
117
- let e = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set(), i = (r) => {
118
- if (n.has(r)) return t.push({
117
+ }, p = () => {
118
+ let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), r = (i) => {
119
+ if (t.has(i)) return n.push({
119
120
  code: "CIRCULAR_REFERENCE",
120
- message: `Circular reference detected involving element "${r}"`,
121
- elementId: r
121
+ message: `Circular reference detected involving element "${i}"`,
122
+ elementId: i
122
123
  }), !0;
123
- if (e.has(r)) return !1;
124
- e.add(r), n.add(r);
125
- let a = c(r);
124
+ if (e.has(i)) return !1;
125
+ e.add(i), t.add(i);
126
+ let a = l(i);
126
127
  if (a?.definition?.items) {
127
- for (let e of a.definition.items) if (i(e)) return n.delete(r), !0;
128
+ for (let e of a.definition.items) if (r(e)) return t.delete(i), !0;
128
129
  }
129
130
  let o = /* @__PURE__ */ new Set(), s = a;
130
131
  for (; s?.definition?.parentId;) {
131
- if (o.has(s.id)) return t.push({
132
+ if (o.has(s.id)) return n.push({
132
133
  code: "CIRCULAR_PARENT_REFERENCE",
133
134
  message: `Circular parent reference detected involving element "${s.id}"`,
134
135
  elementId: s.id
135
136
  }), !0;
136
- o.add(s.id), s = c(s.definition.parentId);
137
+ o.add(s.id), s = l(s.definition.parentId);
137
138
  }
138
- return n.delete(r), !1;
139
+ return t.delete(i), !1;
139
140
  };
140
- Object.keys(r).forEach((t) => {
141
- e.has(t) || i(t);
141
+ Object.keys(i).forEach((t) => {
142
+ e.has(t) || r(t);
142
143
  });
143
- }, p = () => {
144
- let e = /* @__PURE__ */ new Set(), r = 0;
145
- i.forEach((n, i) => {
146
- e.has(n) && t.push({
144
+ }, m = () => {
145
+ let e = /* @__PURE__ */ new Set(), t = 0;
146
+ a.forEach((r, i) => {
147
+ e.has(r) && n.push({
147
148
  code: "DUPLICATE_PAGE",
148
- message: `Duplicate page ID "${n}" in pages array at index ${i}`,
149
- elementId: n
150
- }), e.add(n);
151
- let a = c(n);
149
+ message: `Duplicate page ID "${r}" in pages array at index ${i}`,
150
+ elementId: r
151
+ }), e.add(r);
152
+ let a = l(r);
152
153
  if (!a) {
153
- t.push({
154
+ n.push({
154
155
  code: "INVALID_PAGE_REFERENCE",
155
- message: `Page ID "${n}" in pages array doesn't exist in flat`,
156
- elementId: n
156
+ message: `Page ID "${r}" in pages array doesn't exist in flat`,
157
+ elementId: r
157
158
  });
158
159
  return;
159
160
  }
160
- a.definition.type !== "page" && t.push({
161
+ a.definition.type !== "page" && n.push({
161
162
  code: "INVALID_PAGE_TYPE",
162
- message: `Element "${n}" in pages array has type "${a.definition.type}" instead of "page"`,
163
- elementId: n
164
- }), a.definition.rootId !== n && t.push({
163
+ message: `Element "${r}" in pages array has type "${a.definition.type}" instead of "page"`,
164
+ elementId: r
165
+ }), a.definition.rootId !== r && n.push({
165
166
  code: "PAGE_ROOT_ID_MISMATCH",
166
- message: `Page "${n}" has rootId "${a.definition.rootId}" instead of its own ID`,
167
- elementId: n
168
- }), a.attributes?.default && r++;
169
- }), r === 0 && i.length > 0 ? n.push({
167
+ message: `Page "${r}" has rootId "${a.definition.rootId}" instead of its own ID`,
168
+ elementId: r
169
+ }), a.attributes?.default && t++;
170
+ }), t === 0 && a.length > 0 ? r.push({
170
171
  code: "NO_DEFAULT_PAGE",
171
172
  message: "No default page found in schema"
172
- }) : r > 1 && n.push({
173
+ }) : t > 1 && r.push({
173
174
  code: "MULTIPLE_DEFAULT_PAGES",
174
- message: `Found ${r} default pages, expected 1`
175
+ message: `Found ${t} default pages, expected 1`
175
176
  });
176
- }, m = () => {
177
- i.forEach((e) => {
178
- let n = c(e);
179
- if (!n) return;
180
- let r = (n) => {
181
- let i = c(n);
182
- i?.definition && (i.definition.rootId !== e && t.push({
177
+ }, h = () => {
178
+ a.forEach((e) => {
179
+ let t = l(e);
180
+ if (!t) return;
181
+ let r = (t) => {
182
+ let i = l(t);
183
+ i?.definition && (i.definition.rootId !== e && n.push({
183
184
  code: "ROOT_ID_MISMATCH",
184
- message: `Element "${n}" has rootId "${i.definition.rootId}" but should be "${e}" (page's ID)`,
185
- elementId: n,
185
+ message: `Element "${t}" has rootId "${i.definition.rootId}" but should be "${e}" (page's ID)`,
186
+ elementId: t,
186
187
  details: {
187
188
  expectedRootId: e,
188
189
  actualRootId: i.definition.rootId
189
190
  }
190
191
  }), i.definition.items && i.definition.items.forEach((e) => r(e)));
191
192
  };
192
- n.definition.items && n.definition.items.forEach((e) => r(e));
193
+ t.definition.items && t.definition.items.forEach((e) => r(e));
193
194
  });
194
- }, h = () => {
195
+ }, g = () => {
195
196
  let e = /* @__PURE__ */ new Set();
196
- a.forEach((n, r) => {
197
- if (!n.id) {
198
- t.push({
197
+ o.forEach((t, r) => {
198
+ if (!t.id) {
199
+ n.push({
199
200
  code: "INVALID_FOLDER",
200
201
  message: `Page folder at index ${r} has no id`
201
202
  });
202
203
  return;
203
204
  }
204
- e.has(n.id) && t.push({
205
+ e.has(t.id) && n.push({
205
206
  code: "DUPLICATE_FOLDER",
206
- message: `Duplicate folder ID "${n.id}"`,
207
- elementId: n.id
208
- }), e.add(n.id), n.parentId && (e.has(n.parentId) || t.push({
207
+ message: `Duplicate folder ID "${t.id}"`,
208
+ elementId: t.id
209
+ }), e.add(t.id), t.parentId && (e.has(t.parentId) || n.push({
209
210
  code: "ORPHANED_FOLDER_PARENT",
210
- message: `Folder "${n.id}" has non-existent parent "${n.parentId}"`,
211
- elementId: n.id,
212
- details: { parentId: n.parentId }
211
+ message: `Folder "${t.id}" has non-existent parent "${t.parentId}"`,
212
+ elementId: t.id,
213
+ details: { parentId: t.parentId }
213
214
  }));
214
215
  });
215
- }, g = (e) => {
216
- let t = /* @__PURE__ */ new Set(), a = (e) => {
216
+ }, _ = (e) => {
217
+ let t = /* @__PURE__ */ new Set(), n = (e) => {
217
218
  if (t.has(e)) return;
218
219
  t.add(e);
219
- let n = c(e);
220
- n?.definition?.items && n.definition.items.forEach((e) => a(e));
220
+ let r = l(e);
221
+ r?.definition?.items && r.definition.items.forEach((e) => n(e));
221
222
  };
222
- if (!e) i.forEach((e) => {
223
- a(e);
224
- let t = c(e);
225
- t?.attributes?.layoutContainer && a(t.attributes.layoutContainer);
223
+ if (!e) a.forEach((e) => {
224
+ n(e);
225
+ let t = l(e);
226
+ t?.attributes?.layoutContainer && n(t.attributes.layoutContainer);
226
227
  });
227
228
  else if (e) {
228
- a(e);
229
- let t = c(e);
230
- t?.attributes?.layoutContainer && a(t.attributes.layoutContainer);
229
+ n(e);
230
+ let t = l(e);
231
+ t?.attributes?.layoutContainer && n(t.attributes.layoutContainer);
231
232
  }
232
- Object.keys(r).forEach((e) => {
233
- let r = c(e);
234
- r && !t.has(e) && r.definition.type !== "page" && n.push({
233
+ Object.keys(i).forEach((e) => {
234
+ let n = l(e);
235
+ n && !t.has(e) && n.definition.type !== "page" && r.push({
235
236
  code: "ORPHANED_ELEMENT",
236
237
  message: `Element "${e}" is not reachable from any page`,
237
238
  elementId: e
238
239
  });
239
240
  });
240
- }, _ = () => {
241
+ }, v = () => {
241
242
  let e = /* @__PURE__ */ new Set();
242
- o.forEach((r, i) => {
243
- if (!r.name) {
244
- t.push({
243
+ s.forEach((t, i) => {
244
+ if (!t.name) {
245
+ n.push({
245
246
  code: "INVALID_VARIABLE",
246
247
  message: `Variable at index ${i} has no name`
247
248
  });
248
249
  return;
249
250
  }
250
- e.has(r.name) && t.push({
251
+ e.has(t.name) && n.push({
251
252
  code: "DUPLICATE_VARIABLE",
252
- message: `Duplicate variable name "${r.name}"`,
253
- details: { variableName: r.name }
254
- }), e.add(r.name), r.type || n.push({
253
+ message: `Duplicate variable name "${t.name}"`,
254
+ details: { variableName: t.name }
255
+ }), e.add(t.name), t.type || r.push({
255
256
  code: "MISSING_VARIABLE_TYPE",
256
- message: `Variable "${r.name}" has no type`,
257
- details: { variableName: r.name }
257
+ message: `Variable "${t.name}" has no type`,
258
+ details: { variableName: t.name }
259
+ });
260
+ });
261
+ }, y = () => {
262
+ let t = /* @__PURE__ */ new Map();
263
+ Object.values(i).forEach((r) => {
264
+ if (!r) return;
265
+ let { idRef: i } = r;
266
+ if (!i) {
267
+ r.definition.interactions && Object.keys(r.definition.interactions).length > 0 && n.push({
268
+ code: "INTERACTIONS_WITHOUT_ID_REF",
269
+ message: `Element "${r.id}" has interactions but no idRef, so none of them can fire`,
270
+ elementId: r.id
271
+ });
272
+ return;
273
+ }
274
+ e(i) || n.push({
275
+ code: "INVALID_ID_REF",
276
+ message: `Element "${r.id}" has idRef "${i}", which must start with a letter, then letters, numbers, hyphens and underscores (no hyphen or underscore at the start, and no hyphen at the end)`,
277
+ elementId: r.id
258
278
  });
279
+ let a = t.get(i);
280
+ if (a) {
281
+ n.push({
282
+ code: "DUPLICATE_ID_REF",
283
+ message: `Elements "${a}" and "${r.id}" share the idRef "${i}"`,
284
+ elementId: r.id,
285
+ details: {
286
+ idRef: i,
287
+ otherElementId: a
288
+ }
289
+ });
290
+ return;
291
+ }
292
+ t.set(i, r.id);
259
293
  });
260
294
  };
261
295
  return { validate: (e) => {
262
- let { baseElementId: r } = e ?? {};
263
- return l() ? (u(), d(), f(), p(), m(), h(), g(r), _(), {
264
- valid: t.length === 0,
265
- errors: t,
266
- warnings: n
296
+ let { baseElementId: t } = e ?? {};
297
+ return u() ? (d(), f(), p(), m(), h(), g(), _(t), v(), y(), {
298
+ valid: n.length === 0,
299
+ errors: n,
300
+ warnings: r
267
301
  }) : {
268
302
  valid: !1,
269
- errors: t,
270
- warnings: n
303
+ errors: n,
304
+ warnings: r
271
305
  };
272
306
  } };
273
- }, t = (t, n) => e(t).validate(n), n = (e, n, r) => {
274
- let i = t(e, n);
307
+ }, n = (e, n) => t(e).validate(n), r = (e, t, r) => {
308
+ let i = n(e, t);
275
309
  if (!i.valid) {
276
310
  let e = `Invalid schema${r ? ` (${r})` : ""}: ${i.errors.map((e) => e.message).join("; ")}`;
277
311
  throw Error(e);
278
312
  }
279
- }, r = (e, n) => t(e, n).valid;
313
+ }, i = (e, t) => n(e, t).valid;
280
314
  //#endregion
281
- export { n as assertSchemaValid, r as isSchemaValid, t as validateSchema };
315
+ export { r as assertSchemaValid, i as isSchemaValid, n as validateSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-schema",
3
- "version": "0.32.5",
3
+ "version": "0.32.6",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist"
@@ -18,6 +18,10 @@
18
18
  "types": "./dist/helpers/FlatMap.d.ts",
19
19
  "import": "./dist/helpers/FlatMap.mjs"
20
20
  },
21
+ "./helpers/idRef": {
22
+ "types": "./dist/helpers/idRef.d.ts",
23
+ "import": "./dist/helpers/idRef.mjs"
24
+ },
21
25
  "./helpers/schemaValidator": {
22
26
  "types": "./dist/helpers/schemaValidator.d.ts",
23
27
  "import": "./dist/helpers/schemaValidator.mjs"
@@ -40,15 +44,15 @@
40
44
  "build:prod": "vite build && node ../sdk-shared/scripts/generate-exports.mjs"
41
45
  },
42
46
  "dependencies": {
43
- "@plitzi/plitzi-ui": "^1.6.13",
44
- "@plitzi/sdk-shared": "0.32.5",
45
- "@plitzi/sdk-style": "0.32.5",
47
+ "@plitzi/plitzi-ui": "^1.6.17",
48
+ "@plitzi/sdk-shared": "0.32.6",
49
+ "@plitzi/sdk-style": "0.32.6",
46
50
  "prop-types": "^15.8.1"
47
51
  },
48
52
  "devDependencies": {
49
- "eslint": "^9.39.4",
53
+ "eslint": "^9.39.5",
50
54
  "typescript": "^6.0.3",
51
- "vite": "^8.1.4",
55
+ "vite": "^8.1.5",
52
56
  "vitest": "^4.1.10"
53
57
  }
54
58
  }