@plitzi/sdk-schema 0.33.1 → 0.34.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.
@@ -1,7 +1,6 @@
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";
1
+ import { elementIdConflict as e, elementIdsFree as t, randomElementId as n, repointIds as r, takenIds as i, uniqueElementId as a } from "./elementId.mjs";
2
+ import { validateSchema as o } from "./schemaValidator.mjs";
3
+ import { get as s, set as c } from "@plitzi/plitzi-ui/helpers";
5
4
  import { EMPTY_SCHEMA as l, VARIABLE_REGEX as u } from "@plitzi/sdk-shared/schema/schemaConstants";
6
5
  import { EMPTY_STYLE_SCHEMA as d } from "@plitzi/sdk-shared/style/styleConstants";
7
6
  import f from "@plitzi/sdk-style/helpers/calculateInheriting";
@@ -9,41 +8,50 @@ import f from "@plitzi/sdk-style/helpers/calculateInheriting";
9
8
  var p = class {
10
9
  flat;
11
10
  variables;
11
+ pages;
12
+ mintId;
12
13
  constructor(e = {}) {
13
- let { flat: t, variables: n } = e;
14
+ let { flat: t, variables: r, pages: i, mintId: a } = e;
14
15
  if (!t) throw Error("Flat is required");
15
- this.flat = t, this.variables = n ?? [];
16
+ this.flat = t, this.variables = r ?? [], this.pages = i ?? [], this.mintId = a ?? n;
16
17
  }
18
+ nextId = (e, t = () => !1) => {
19
+ let n = this.takenIds();
20
+ return this.mintId(e, (e) => n.has(e) || t(e));
21
+ };
17
22
  addElement = (e, n, r = "inside", i = {}) => {
18
- let a;
23
+ let a = e.id ? e : {
24
+ ...e,
25
+ id: this.nextId(e.definition.type)
26
+ }, o;
19
27
  if (r !== "custom") {
20
28
  if (r !== "inside") {
21
29
  let e = this.flat[n];
22
30
  if (!e) return !1;
23
- e.definition.parentId && (a = this.flat[e.definition.parentId]);
24
- } else a = this.flat[n];
25
- if (!a) return !1;
31
+ e.definition.parentId && (o = this.flat[e.definition.parentId]);
32
+ } else o = this.flat[n];
33
+ if (!o) return !1;
26
34
  }
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) {
35
+ if (r !== "custom" && !Array.isArray(s(o, "definition.items")) || !this.isValidElement(a) || !t(this.flat, [a, ...Object.values(i)])) return !1;
36
+ switch (c(this.flat, a.id, a), r) {
29
37
  case "left":
30
38
  case "top": {
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);
39
+ let e = s(o, "definition.items", []);
40
+ if (e.splice(e.findIndex((e) => e === n), 0, a.id), !o) return !1;
41
+ c(this.flat, `${o.id}.definition.items`, e), c(this.flat, `${a.id}.definition.parentId`, o.id), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
34
42
  break;
35
43
  }
36
44
  case "right":
37
45
  case "bottom": {
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);
46
+ let e = s(o, "definition.items", []);
47
+ if (e.splice(e.findIndex((e) => e === n) + 1, 0, a.id), !o) return !1;
48
+ c(this.flat, `${o.id}.definition.items`, e), c(this.flat, `${a.id}.definition.parentId`, o.id), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
41
49
  break;
42
50
  }
43
51
  case "inside": {
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);
52
+ let e = s(o, "definition.items", []);
53
+ if (!o) return !1;
54
+ c(this.flat, `${n}.definition.items`, [...e, a.id]), c(this.flat, `${a.id}.definition.parentId`, n), c(this.flat, `${a.id}.definition.rootId`, o.definition.rootId);
47
55
  break;
48
56
  }
49
57
  case "custom": break;
@@ -53,24 +61,22 @@ var p = class {
53
61
  this.flat[e] = i[e];
54
62
  }), !0;
55
63
  };
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
- };
64
+ updateElement = (e) => !e || !this.flat[e.id] ? !1 : (this.flat[e.id] = e, !0);
65
+ renameElement = (t, n) => this.flat[t] ? t === n ? [] : !e(this.flat, n) && r(this.flat, { [t]: n }, this.pages) : !1;
66
+ renameConflict = (t, n) => e(this.flat, n, t);
61
67
  moveElement = (e, t, n, r = "inside") => {
62
68
  if (n === t || !this.flat[e]) return !1;
63
69
  let i = this.flat[t];
64
70
  if (!i) return !1;
65
71
  let a = this.flat[t];
66
72
  for (; a;) {
67
- let e = o(a, "definition.parentId");
73
+ let e = s(a, "definition.parentId");
68
74
  if (!e) break;
69
75
  if (a.id === n) return !1;
70
76
  a = this.flat[e];
71
77
  }
72
78
  if (!a) return !1;
73
- let c = o(this.flat, `${e}.definition.items`, []).filter((e) => e !== n);
79
+ let o = s(this.flat, `${e}.definition.items`, []).filter((e) => e !== n);
74
80
  if ([
75
81
  "left",
76
82
  "top",
@@ -80,64 +86,49 @@ var p = class {
80
86
  if (!i.definition.parentId) return !1;
81
87
  let a = this.flat[i.definition.parentId];
82
88
  if (!a) return !1;
83
- let l = o(a, "definition.items", []);
84
- a.id === e && (l = c);
89
+ let l = s(a, "definition.items", []);
90
+ a.id === e && (l = o);
85
91
  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);
92
+ ["right", "bottom"].includes(r) && u++, l.splice(u, 0, n), c(this.flat, `${e}.definition.items`, o), c(this.flat, `${a.id}.definition.items`, l), c(this.flat, `${n}.definition.parentId`, a.id);
87
93
  } else if (r === "inside") {
88
94
  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);
95
+ let r = s(this.flat, `${t}.definition.items`, []);
96
+ e === t && (r = o), r = [...r, n], c(this.flat, `${e}.definition.items`, o), c(this.flat, `${t}.definition.items`, r), c(this.flat, `${n}.definition.parentId`, t);
91
97
  }
92
98
  return !0;
93
99
  };
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 = {
100
+ getElement = (e) => s(this.flat, e);
101
+ takenIds = () => i(this.flat);
102
+ elementIdConflict = (t, n) => e(this.flat, t, n);
103
+ cloneElements = (e, t = "", n = "", i = !1) => {
104
+ let o = {
99
105
  acum: {},
100
106
  item: void 0
101
107
  }, l = {}, u = this.flat[e];
102
- if (!u) return a;
103
- let d = this.takenIdRefs(), f = [e, ...this.childTree(e)].reduce((e, t) => {
104
- let n = this.flat[t];
105
- return n ? (l[n.id] = c(n.id), r ? {
106
- ...e,
107
- [n.id]: {
108
- ...n,
109
- definition: {
110
- ...n.definition,
111
- rootId: r
112
- }
113
- }
114
- } : {
115
- ...e,
116
- [n.id]: n
117
- }) : e;
118
- }, {});
119
- try {
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));
122
- } catch (e) {
123
- return console.error("Error parsing elements", e), {
124
- acum: {},
125
- item: void 0
126
- };
108
+ if (!u) return o;
109
+ let d = [e, ...this.childTree(e)].filter((e) => this.flat[e] !== void 0), f = this.takenIds();
110
+ for (let e of d) {
111
+ let t = a(e, (e) => f.has(e));
112
+ f.add(t), l[e] = t;
113
+ }
114
+ let p = {};
115
+ for (let e of d) {
116
+ let t = structuredClone(this.flat[e]);
117
+ n && (t.definition.rootId = n), p[e] = t;
127
118
  }
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;
119
+ r(p, l), o.acum = p, o.item = p[l[e]], i && delete o.acum[l[e]];
120
+ let m = this.flat[t];
121
+ return (!m || !Array.isArray(s(m, "definition.items"))) && (t = s(m, "definition.parentId", s(u, "definition.parentId"))), t && c(o, "item.definition.parentId", t), o;
131
122
  };
132
123
  removeElement = (e, t = !1) => {
133
124
  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");
125
+ if (!n || n.definition.type === "page" && !t || t && s(n, "attributes.default", !1)) return !1;
126
+ let r = s(n, "definition.items");
136
127
  r && r.length > 0 && r.forEach((e) => this.removeElement(e));
137
- let i = o(n, "definition.parentId"), a = i ? this.flat[i] : void 0;
128
+ let i = s(n, "definition.parentId"), a = i ? this.flat[i] : void 0;
138
129
  if (i && a) {
139
130
  let { definition: { items: t = [] } } = a;
140
- s(a, "definition.items", t.filter((t) => t !== e)), this.flat[i] = a;
131
+ c(a, "definition.items", t.filter((t) => t !== e)), this.flat[i] = a;
141
132
  }
142
133
  return delete this.flat[e], !0;
143
134
  };
@@ -164,18 +155,18 @@ var p = class {
164
155
  let t = this.flat[e], n = [];
165
156
  if (!t) return n;
166
157
  do {
167
- if (o(t, "definition.type") === "page") {
168
- let e = o(t, "attributes.layout"), r = o(t, "attributes.layoutContainer");
158
+ if (s(t, "definition.type") === "page") {
159
+ let e = s(t, "attributes.layout"), r = s(t, "attributes.layoutContainer");
169
160
  e && r && n.push(r, ...this.parentTree(r));
170
161
  }
171
- e !== t.id && n.push(t.id), t = o(this.flat, o(t, "definition.parentId"), void 0);
162
+ e !== t.id && n.push(t.id), t = s(this.flat, s(t, "definition.parentId"), void 0);
172
163
  } while (t);
173
164
  return n;
174
165
  };
175
166
  childTree = (e) => {
176
167
  let t = this.flat[e];
177
168
  if (!t) return [];
178
- let n = [], r = o(t, "definition.items");
169
+ let n = [], r = s(t, "definition.items");
179
170
  return r && r.forEach((e) => n.push(e, ...this.childTree(e))), n;
180
171
  };
181
172
  isValidElement = (e) => {
@@ -202,7 +193,7 @@ var p = class {
202
193
  elementsStyle: r,
203
194
  variables: i
204
195
  };
205
- let a = o(this.flat, t);
196
+ let a = s(this.flat, t);
206
197
  if (!a) return {
207
198
  elements: {
208
199
  acum: {},
@@ -211,18 +202,18 @@ var p = class {
211
202
  elementsStyle: r,
212
203
  variables: i
213
204
  };
214
- let c = this.cloneElements(t, a.definition.parentId);
215
- return c.item ? (Object.values(c.acum).forEach((t) => {
205
+ let o = this.cloneElements(t, a.definition.parentId);
206
+ return o.item ? (Object.values(o.acum).forEach((t) => {
216
207
  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) => {
208
+ if (c(o.acum, `${n}.definition.rootId`, o.item?.id), f(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
218
209
  let { displayMode: n, name: i } = t;
219
210
  !(i in r.platform[n]) && i in e.platform[n] && (r.platform[n][i] = e.platform[n][i]);
220
211
  }), this.variables.length > 0) {
221
- let t = this.getElementVariables(e, n, c.acum);
212
+ let t = this.getElementVariables(e, n, o.acum);
222
213
  i = [...i, ...t];
223
214
  }
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,
215
+ }), c(o.acum, `${o.item.id}.definition.parentId`, null), n && delete o.acum[o.item.id], i.length > 1 && (i = [...new Set(i)]), {
216
+ elements: o,
226
217
  elementsStyle: r,
227
218
  variables: i
228
219
  }) : {
@@ -234,14 +225,14 @@ var p = class {
234
225
  variables: i
235
226
  };
236
227
  };
237
- validate = () => a({
228
+ validate = () => o({
238
229
  ...l.schema,
239
230
  flat: this.flat,
240
231
  variables: this.variables
241
232
  });
242
233
  isValid = () => this.validate().valid;
243
234
  assertValid = (e) => {
244
- let t = a({
235
+ let t = o({
245
236
  ...l.schema,
246
237
  flat: this.flat,
247
238
  variables: this.variables
@@ -252,13 +243,13 @@ var p = class {
252
243
  }
253
244
  };
254
245
  getElementVariables = (e, t, n = this.flat, r = this.variables) => {
255
- let i = [], a = o(n, `${t}.definition.styleSelectors`);
246
+ let i = [], a = s(n, `${t}.definition.styleSelectors`);
256
247
  if (!a) return i;
257
- let s = new RegExp(u, "g");
248
+ let o = new RegExp(u, "g");
258
249
  return Object.values(a).filter(Boolean).forEach((t) => {
259
250
  Object.values(e.platform).forEach((e) => {
260
251
  let n = e[t];
261
- n && [...JSON.stringify(n.attributes).matchAll(s)].forEach((e) => {
252
+ n && [...JSON.stringify(n.attributes).matchAll(o)].forEach((e) => {
262
253
  let t = r.find((t) => t.name === e[1] || t.name === e[2]);
263
254
  t && !i.find((e) => e.name === t.name) && i.push(t);
264
255
  });
@@ -268,12 +259,16 @@ var p = class {
268
259
  static getInstance = (e) => new this(e);
269
260
  static addElement = (e, t, n, r = "inside", i = {}) => this.getInstance({ flat: e }).addElement(t, n, r, i);
270
261
  static updateElement = (e, t) => this.getInstance({ flat: e }).updateElement(t);
262
+ static renameElement = (e, t, n) => this.getInstance({
263
+ flat: e.flat,
264
+ pages: e.pages
265
+ }).renameElement(t, n);
271
266
  static moveElement = (e, t, n, r, i = "inside") => this.getInstance({ flat: e }).moveElement(t, n, r, i);
272
267
  static getElement = (e, t) => this.getInstance({ flat: e }).getElement(t);
273
268
  static cloneElements = (e, t, n = "", r = "", i = !1) => this.getInstance({ flat: e }).cloneElements(t, n, r, i);
274
269
  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);
270
+ static takenIds = (e) => this.getInstance({ flat: e }).takenIds();
271
+ static elementIdConflict = (e, t, n) => this.getInstance({ flat: e }).elementIdConflict(t, n);
277
272
  static addVariables = (e, t) => this.getInstance({ variables: e }).addVariables(t);
278
273
  static addVariable = (e, t) => this.getInstance({ variables: e }).addVariable(t);
279
274
  static updateVariable = (e, t) => this.getInstance({ variables: e }).updateVariable(t);
@@ -296,10 +291,10 @@ var p = class {
296
291
  variables: i
297
292
  }).getElementVariables(t, n);
298
293
  };
299
- static validate = (e) => a(e);
300
- static isValid = (e) => a(e).valid;
294
+ static validate = (e) => o(e);
295
+ static isValid = (e) => o(e).valid;
301
296
  static assertValid = (e, t) => {
302
- let n = a(e);
297
+ let n = o(e);
303
298
  if (!n.valid) {
304
299
  let e = `Invalid schema${t ? ` (${t})` : ""}: ${n.errors.map((e) => e.message).join("; ")}`;
305
300
  throw Error(e);
@@ -0,0 +1,73 @@
1
+ import { Element, Schema } from '@plitzi/sdk-shared';
2
+ /** True when an element id is well formed: it starts with a letter and then carries letters, numbers, hyphens and
3
+ * underscores. Leading letter keeps a source `<type>_<id>` a valid twig identifier. A '.' would split the
4
+ * `<type>_<id>.<field>` source path, an interaction target lookup, and the lodash paths `flat` is read and written
5
+ * through (`get(flat, id)`, `set(flat, \`${id}.definition.rootId\`)`) — so it is not allowed. A source name uses
6
+ * the first '_' as separator between `<type>` and `<id>` (element types are camelCase with no underscore), so
7
+ * underscores inside the id are unambiguous. A hyphen is allowed too: a source doubles as a twig token, and
8
+ * `processTwig` resolves a hyphenated `<type>_<id>` through `_context` subscript access. */
9
+ export declare const isValidElementId: (id: string) => boolean;
10
+ /** What a person typed, as an id this document can hold. Anything outside the charset becomes a hyphen, runs
11
+ * collapse, and a leading non-letter is dropped — so a name is editable as prose in the builder's tree and still
12
+ * lands as a valid key. Empty when nothing usable survives, which the caller reports rather than storing. */
13
+ export declare const slugifyElementId: (value: string) => string;
14
+ /** Every id currently in use, so a new one can be checked or minted against it. Uniqueness is per DOCUMENT — a
15
+ * space schema, and each segment separately — which is what makes a lookup a `flat` key access. A segment used on
16
+ * several pages repeats its ids in the rendered tree; `rootId`/`referenceId` are what tell those apart. */
17
+ export declare const takenIds: (flat: Record<Element["id"], Element>) => Set<string>;
18
+ /** Why `id` cannot be used in this document, phrased for a person, or null when it is free and well formed.
19
+ * `ignoreElementId` exempts the element being edited, so re-saving an element its own id is not a conflict. */
20
+ export declare const elementIdConflict: (flat: Record<Element["id"], Element>, id: string, ignoreElementId?: Element["id"]) => string | null;
21
+ /** Whether every id a write brings INTO a document is free: well formed, not already stored, and not repeated
22
+ * inside the incoming set — which `elementIdConflict` cannot see on its own, because those elements are not in
23
+ * `flat` yet. For a rename, where the element IS stored, call `elementIdConflict` with the new name directly. */
24
+ export declare const elementIdsFree: (flat: Record<Element["id"], Element>, elements: Element[]) => boolean;
25
+ /** How a document mints an id for an element nobody named. Injected rather than chosen here: a live document wants
26
+ * uniqueness with no coordination between writers, an authored one wants byte-identical output when re-run, and
27
+ * the difference is the caller's to state. See `randomElementId` and `positionalElementId`. */
28
+ export type MintElementId = (type: string, isTaken: (candidate: string) => boolean) => string;
29
+ /** The live-editing minter: `<type>-<4 random chars>`. Random, not positional, because the builder and the MCP
30
+ * write concurrently — a counter has two collaborators mint `heading-3` and one of the two writes refused at the
31
+ * merge, which is the worst moment to discover it. At the scale of a document four characters are plenty, and the
32
+ * name is a default the author renames. */
33
+ export declare const randomElementId: MintElementId;
34
+ /** The offline-authoring minter: `<type>-<n>`, incrementing until `isTaken` says it is free. Deterministic, so a
35
+ * seed or a transformer re-run writes the same document it wrote last time and a diff against a committed copy
36
+ * stays empty. The hyphen sits between two alphanumeric runs, so it is valid and readable. */
37
+ export declare const positionalElementId: MintElementId;
38
+ /**
39
+ * `desired` when it is free, else the next number after it — the readable answer to a name collision.
40
+ *
41
+ * A copy of `hero` is `hero-2`, not `container-7`: the whole value of a name is that the copy still says what it
42
+ * is. A name that ALREADY ends in a number counts on from it rather than stacking another one, so a derived
43
+ * `container-1` meeting a `container-1` becomes `container-2` and not `container-1-2`. Names degrading this way is
44
+ * the one cost of a readable unique key, and it is cheaper than the alternative.
45
+ */
46
+ export declare const uniqueElementId: (desired: string, isTaken: (candidate: string) => boolean) => string;
47
+ /**
48
+ * Apply a renaming (old id → new id) across a set of elements: every reference written against the old name, and
49
+ * the elements' own identity.
50
+ *
51
+ * This is what makes an id both readable and safe to change. It is a field-by-field pass, never a string replace
52
+ * over the serialized tree: an id is short and human-readable, so replacing "card-1" everywhere would rewrite it
53
+ * inside labels, prose, class names and content. So each kind of reference is rewritten where it lives —
54
+ * structure (`flat` key, `parentId`, `items`, `rootId`, `pages`), the attributes that point at another element,
55
+ * a binding's `source` and the tokens embedded in its transformers and conditions, and an interaction's target
56
+ * and params.
57
+ *
58
+ * An id absent from the map is left alone, so references leaving this set keep pointing outward, which is what
59
+ * callers want. `pages` is rewritten in place when given — a page rename is not complete without it.
60
+ *
61
+ * Returns the ids, under their new names, of every element it actually wrote to. A caller broadcasting the rename
62
+ * publishes that set: the element being renamed is almost never the whole of what moved.
63
+ */
64
+ export declare const repointIds: (flat: Record<Element["id"], Element>, map: Record<Element["id"], Element["id"]>, pages?: Schema["pages"]) => Element["id"][];
65
+ /**
66
+ * Renames only the elements of an incoming set whose names this document has already taken, and repoints the set's
67
+ * own references onto the new names.
68
+ *
69
+ * What a template drop and a cross-document paste both need. Renaming everything would be simpler and worse: the
70
+ * names an author gave the template are the reason it is readable, and a document that had no `hero` should get
71
+ * one called `hero`. Returns the renaming it applied, so a caller can report what it had to change.
72
+ */
73
+ export declare const remapCollidingIds: (elements: Record<Element["id"], Element>, isTaken: (candidate: string) => boolean) => Record<Element["id"], Element["id"]>;
@@ -0,0 +1,114 @@
1
+ //#region src/helpers/elementId.ts
2
+ var e = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?$/, t = (t) => e.test(t), n = (e) => e.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[^A-Za-z]+/, "").replace(/-+$/, ""), r = (e) => new Set(Object.keys(e)), i = (e, n, r) => t(n) ? e[n] && n !== r ? `"${n}" is already used by another element here` : null : `"${n}" is not a valid name: start with a letter, then letters, numbers, hyphens and underscores`, a = (e, t) => {
3
+ let n = /* @__PURE__ */ new Set();
4
+ for (let r of t) {
5
+ if (n.has(r.id) || i(e, r.id)) return !1;
6
+ n.add(r.id);
7
+ }
8
+ return !0;
9
+ }, o = (e) => e.replace(/[^A-Za-z0-9]/g, "") || "element", s = "abcdefghijklmnopqrstuvwxyz0123456789", c = (e, t) => {
10
+ let n = o(e);
11
+ for (let e = 0; e < 100; e += 1) {
12
+ let e = "";
13
+ for (let t = 0; t < 4; t += 1) e += s[Math.floor(Math.random() * s.length)];
14
+ if (!t(`${n}-${e}`)) return `${n}-${e}`;
15
+ }
16
+ return l(e, t);
17
+ }, l = (e, t) => {
18
+ let n = o(e), r = 1;
19
+ for (; t(`${n}-${r}`);) r += 1;
20
+ return `${n}-${r}`;
21
+ }, u = (e, t) => {
22
+ if (!t(e)) return e;
23
+ let n = /^(.*[A-Za-z0-9])-(\d+)$/.exec(e), r = n ? n[1] : e, i = n ? Number(n[2]) + 1 : 2;
24
+ for (; t(`${r}-${i}`);) i += 1;
25
+ return `${r}-${i}`;
26
+ }, d = {
27
+ page: [{ attribute: "layout" }, { attribute: "layoutContainer" }],
28
+ reference: [{
29
+ attribute: "referenceId",
30
+ when: (e) => e.referenceType === "element"
31
+ }],
32
+ link: [{
33
+ attribute: "href",
34
+ when: (e) => e.mode === "page"
35
+ }]
36
+ }, f = { navigate: [{
37
+ param: "url",
38
+ when: (e) => e.urlType === "page"
39
+ }] }, p = (e, t) => {
40
+ let n = e.indexOf("."), r = n === -1 ? e : e.slice(0, n), i = r.indexOf("_");
41
+ if (i === -1) return e;
42
+ let a = t[r.slice(i + 1)];
43
+ return a ? `${r.slice(0, i + 1)}${a}${n === -1 ? "" : e.slice(n)}` : e;
44
+ }, m = /([A-Za-z][A-Za-z0-9]*)_([A-Za-z][A-Za-z0-9_-]*)/g, h = (e, t) => e.replace(m, (e, n, r) => {
45
+ let i = t[r];
46
+ return i ? `${n}_${i}` : e;
47
+ }), g = (e, t) => {
48
+ let n = !1;
49
+ if (Array.isArray(e)) return e.forEach((r, i) => {
50
+ if (typeof r == "string") {
51
+ let a = h(r, t);
52
+ a !== r && (e[i] = a, n = !0);
53
+ return;
54
+ }
55
+ n = g(r, t) || n;
56
+ }), n;
57
+ if (!e || typeof e != "object") return !1;
58
+ let r = e;
59
+ for (let [e, i] of Object.entries(r)) {
60
+ if (typeof i == "string") {
61
+ let a = h(i, t);
62
+ a !== i && (r[e] = a, n = !0);
63
+ continue;
64
+ }
65
+ n = g(i, t) || n;
66
+ }
67
+ return n;
68
+ }, _ = (e, t, n) => {
69
+ let r = Object.entries(t).filter(([e, t]) => e !== t);
70
+ if (r.length === 0) return [];
71
+ let i = (e) => t[e] ?? e, a = /* @__PURE__ */ new Set();
72
+ for (let n of Object.values(e)) {
73
+ let e = !1;
74
+ t[n.id] && (n.id = t[n.id], e = !0);
75
+ let { definition: r, attributes: o } = n;
76
+ t[r.rootId] && (r.rootId = t[r.rootId], e = !0), r.parentId && t[r.parentId] && (r.parentId = t[r.parentId], e = !0), r.items?.some((e) => t[e]) && (r.items = r.items.map(i), e = !0);
77
+ for (let { attribute: n, when: i } of d[r.type] ?? []) {
78
+ let r = o[n];
79
+ typeof r == "string" && t[r] && (!i || i(o)) && (o[n] = t[r], e = !0);
80
+ }
81
+ for (let n of Object.values(r.bindings ?? {})) for (let r of n) {
82
+ let n = p(r.source, t);
83
+ n !== r.source && (r.source = n, e = !0), r.transformers && g(r.transformers, t) && (e = !0), r.when && g(r.when, t) && (e = !0);
84
+ }
85
+ for (let n of Object.values(r.interactions ?? {})) {
86
+ n.elementId && t[n.elementId] && (n.elementId = t[n.elementId], e = !0);
87
+ for (let { param: r, when: i } of f[n.action] ?? []) {
88
+ let a = n.params[r];
89
+ typeof a == "string" && t[a] && (!i || i(n.params)) && (n.params[r] = t[a], e = !0);
90
+ }
91
+ g(n.params, t) && (e = !0), n.when && g(n.when, t) && (e = !0);
92
+ }
93
+ e && a.add(n.id);
94
+ }
95
+ let o = r.filter(([t]) => e[t] !== void 0), s = o.map(([t, n]) => [n, e[t]]);
96
+ for (let [t] of o) delete e[t];
97
+ for (let [t, n] of s) e[t] = n;
98
+ return n && n.forEach((e, r) => {
99
+ t[e] && (n[r] = t[e]);
100
+ }), [...a];
101
+ }, v = (e, t) => {
102
+ let n = {}, r = /* @__PURE__ */ new Set();
103
+ for (let i of Object.values(e)) {
104
+ if (!t(i.id)) {
105
+ r.add(i.id);
106
+ continue;
107
+ }
108
+ let a = u(i.id, (n) => t(n) || r.has(n) || e[n] !== void 0);
109
+ r.add(a), n[i.id] = a;
110
+ }
111
+ return _(e, n), n;
112
+ };
113
+ //#endregion
114
+ export { i as elementIdConflict, a as elementIdsFree, t as isValidElementId, l as positionalElementId, c as randomElementId, v as remapCollidingIds, _ as repointIds, n as slugifyElementId, r as takenIds, u as uniqueElementId };
@@ -1,6 +1,14 @@
1
1
  import { Schema } from '@plitzi/sdk-shared';
2
2
  export type SchemaValidationOptions = {
3
3
  baseElementId?: string;
4
+ /**
5
+ * Element type → the source name it publishes under.
6
+ *
7
+ * Without it a binding's source can only be half-checked: the type half is compared against the types that
8
+ * happen to be in this document, so a typo'd id is caught only when some element of that same type is present,
9
+ * and a prefix that is simply the wrong word for the element it names is never caught at all.
10
+ */
11
+ sourceTypes?: Record<string, string>;
4
12
  };
5
13
  export type SchemaValidationError = {
6
14
  code: string;