@plitzi/sdk-schema 0.37.2 → 0.37.4

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,4 +1,3 @@
1
- import { SchemaValidationResult } from './schemaValidator';
2
1
  import { MintElementId } from './elementId';
3
2
  import { Style, Element, Schema, DropPosition, SchemaVariable } from '@plitzi/sdk-shared';
4
3
  /** An element on its way in. Its id is the name it will answer to, and leaving it out asks this map to mint one. */
@@ -31,6 +30,12 @@ declare class FlatMap {
31
30
  /** Inserts an element, minting its id when the caller did not name it. Nothing else in the codebase mints an
32
31
  * element id: every writer goes through here, so "every element has a name" is true by construction. */
33
32
  addElement: (input: ElementInput, to: Element["id"], dropPosition?: DropPosition, initialItems?: Record<Element["id"], Element>) => boolean;
33
+ /**
34
+ * Where `ids` go when dropped at `dropPosition` of `to`: the parent that takes them and its items with them in place.
35
+ * Null when there is nowhere to put them — no such element, a sibling of an element with no parent, a parent that
36
+ * holds no items, or an anchor its parent does not list. Computed without writing, so a refusal leaves no trace.
37
+ */
38
+ private placement;
34
39
  /** Replaces a stored element with an edited copy of itself. The id is its identity, not a field an update may
35
40
  * carry a new value for — changing the name is `renameElement`, which is a document-wide operation. */
36
41
  updateElement: (element?: Element) => boolean;
@@ -46,15 +51,14 @@ declare class FlatMap {
46
51
  * change has to publish: the one element it renamed is almost never the whole of what moved.
47
52
  */
48
53
  renameElement: (from: Element["id"], to: Element["id"]) => Element["id"][] | false;
49
- /** Why this element cannot be renamed to `id` (charset or a clash), or null when the name is free. */
50
- renameConflict: (from: Element["id"], id: string) => string | null;
54
+ /**
55
+ * Moves an element — and everything in it — next to or inside `to`. Refused when `to` is the element itself or one
56
+ * of its own descendants: the subtree would end up holding itself. Crossing into another page or layout takes the
57
+ * subtree's `rootId` along, so the tree agrees with itself about where everything lives.
58
+ */
51
59
  moveElement: (from: Element["id"], to: Element["id"], elementId: Element["id"], dropPosition?: DropPosition) => boolean;
52
- getElement: (elementId: Element["id"]) => Element<Record<string, unknown>>;
53
- /** Every id currently in use, so a newly minted one stays unique across the document. */
54
- takenIds: () => Set<string>;
55
- /** Why an id cannot be used here (charset or a clash), or null when it is free. `ignoreElementId` exempts the
56
- * element being edited, so re-saving an element its own name is not a conflict. */
57
- elementIdConflict: (id: string, ignoreElementId?: Element["id"]) => string | null;
60
+ /** Points a subtree at the root it now lives under, when a move took it into another page or layout. */
61
+ private carryRoot;
58
62
  /**
59
63
  * Copies a subtree onto fresh names.
60
64
  *
@@ -74,13 +78,6 @@ declare class FlatMap {
74
78
  updateVariable: (variable: SchemaVariable) => boolean;
75
79
  removeVariables: (variables: string[]) => boolean;
76
80
  removeVariable: (variable: string) => boolean;
77
- /**
78
- * Everything an element is rendered inside, nearest first — its ancestors, and past a root the shell that root is
79
- * shown in. A layout names its own shell the way a page does, so the walk continues through as many as are nested;
80
- * `visited` keeps a document whose shells name each other from walking forever.
81
- */
82
- parentTree: (elementId: Element["id"], visited?: Set<Element["id"]>) => string[];
83
- childTree: (elementId: Element["id"]) => string[];
84
81
  isValidElement: (element?: Partial<Element>) => boolean;
85
82
  flatAsTemplate: (style: Style, elementId: Element["id"], excludeRoot?: boolean) => {
86
83
  elements: {
@@ -90,31 +87,21 @@ declare class FlatMap {
90
87
  elementsStyle: Style;
91
88
  variables: SchemaVariable[];
92
89
  };
93
- validate: () => SchemaValidationResult;
94
- isValid: () => boolean;
95
- assertValid: (context?: string) => void;
96
90
  getElementVariables: (style: Style, elementId: Element["id"], flat?: Record<string, Element<Record<string, unknown>>>, variables?: SchemaVariable[]) => SchemaVariable[];
97
91
  static getInstance: (props: FlatMapProps) => FlatMap;
98
92
  static addElement: (flat: Schema["flat"], data: ElementInput, to: Element["id"], dropPosition?: DropPosition, initialItems?: Record<Element["id"], Element>) => boolean;
99
93
  static updateElement: (flat: Schema["flat"], element: Element) => boolean;
100
94
  static renameElement: (schema: Pick<Schema, "flat" | "pages">, from: Element["id"], to: Element["id"]) => false | string[];
101
95
  static moveElement: (flat: Schema["flat"], from: Element["id"], to: Element["id"], elementId: Element["id"], dropPosition?: DropPosition) => boolean;
102
- static getElement: (flat: Schema["flat"], elementId: Element["id"]) => Element<Record<string, unknown>>;
103
96
  static cloneElements: (flat: Schema["flat"], elementId: Element["id"], parentId?: Element["id"], rootId?: Element["id"], excludeRoot?: boolean) => {
104
97
  acum: Record<Element["id"], Element>;
105
98
  item?: Element;
106
99
  };
107
100
  static removeElement: (flat: Schema["flat"], elementId: Element["id"], removePage?: boolean) => boolean;
108
- static takenIds: (flat: Schema["flat"]) => Set<string>;
109
- static elementIdConflict: (flat: Schema["flat"], id: string, ignoreElementId?: Element["id"]) => string | null;
110
101
  static addVariables: (schemaVariables: Schema["variables"], variables: Schema["variables"]) => boolean;
111
102
  static addVariable: (schemaVariables: Schema["variables"], variable: SchemaVariable) => boolean;
112
103
  static updateVariable: (schemaVariables: Schema["variables"], variable: SchemaVariable) => boolean;
113
- static removeVariables: (schemaVariables: Schema["variables"], variables: string[]) => boolean;
114
104
  static removeVariable: (schemaVariables: Schema["variables"], variable: string) => boolean;
115
- static parentTree: (flat: Schema["flat"], elementId: Element["id"]) => string[];
116
- static childTree: (flat: Schema["flat"], elementId: Element["id"]) => string[];
117
- static isValidElement: (flat: Schema["flat"], element: Element) => boolean;
118
105
  static flatAsTemplate: (schema: Schema, style: Style, elementId: Element["id"], excludeRoot?: boolean) => {
119
106
  elements: {
120
107
  acum: Record<Element["id"], Element>;
@@ -123,9 +110,5 @@ declare class FlatMap {
123
110
  elementsStyle: Style;
124
111
  variables: SchemaVariable[];
125
112
  };
126
- static getElementVariables: (schema: Schema, style: Style, elementId: Element["id"]) => SchemaVariable[];
127
- static validate: (schema: Schema) => SchemaValidationResult;
128
- static isValid: (schema: Schema) => boolean;
129
- static assertValid: (schema: Schema, context?: string) => void;
130
113
  }
131
114
  export default FlatMap;
@@ -1,11 +1,10 @@
1
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";
4
- import { EMPTY_SCHEMA as l, VARIABLE_REGEX as u } from "@plitzi/sdk-shared/schema/schemaConstants";
5
- import { EMPTY_STYLE_SCHEMA as d } from "@plitzi/sdk-shared/style/styleConstants";
6
- import f from "@plitzi/sdk-style/helpers/calculateInheriting";
2
+ import { descendants as o, parentChain as s } from "./elementTree.mjs";
3
+ import { VARIABLE_REGEX as c } from "@plitzi/sdk-shared/schema/schemaConstants";
4
+ import { EMPTY_STYLE_SCHEMA as l } from "@plitzi/sdk-shared/style/styleConstants";
5
+ import u from "@plitzi/sdk-style/helpers/calculateInheriting";
7
6
  //#region src/helpers/FlatMap.ts
8
- var p = class {
7
+ var d = class {
9
8
  flat;
10
9
  variables;
11
10
  pages;
@@ -16,97 +15,55 @@ var p = class {
16
15
  this.flat = t, this.variables = r ?? [], this.pages = i ?? [], this.mintId = a ?? n;
17
16
  }
18
17
  nextId = (e, t = () => !1) => {
19
- let n = this.takenIds();
18
+ let n = i(this.flat);
20
19
  return this.mintId(e, (e) => n.has(e) || t(e));
21
20
  };
22
21
  addElement = (e, n, r = "inside", i = {}) => {
23
22
  let a = e.id ? e : {
24
23
  ...e,
25
24
  id: this.nextId(e.definition.type)
26
- }, o;
25
+ };
26
+ if (!this.isValidElement(a) || !t(this.flat, [a, ...Object.values(i)])) return !1;
27
27
  if (r !== "custom") {
28
- if (r !== "inside") {
29
- let e = this.flat[n];
30
- if (!e) return !1;
31
- e.definition.parentId && (o = this.flat[e.definition.parentId]);
32
- } else o = this.flat[n];
33
- if (!o) return !1;
34
- }
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) {
37
- case "left":
38
- case "top": {
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);
42
- break;
43
- }
44
- case "right":
45
- case "bottom": {
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);
49
- break;
50
- }
51
- case "inside": {
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);
55
- break;
56
- }
57
- case "custom": break;
58
- default: return !1;
59
- }
60
- return Object.keys(i).length > 0 && Object.keys(i).forEach((e) => {
61
- this.flat[e] = i[e];
62
- }), !0;
63
- };
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);
67
- moveElement = (e, t, n, r = "inside") => {
68
- if (n === t || !this.flat[e]) return !1;
69
- let i = this.flat[t];
70
- if (!i) return !1;
71
- let a = this.flat[t];
72
- for (; a;) {
73
- let e = s(a, "definition.parentId");
74
- if (!e) break;
75
- if (a.id === n) return !1;
76
- a = this.flat[e];
28
+ let e = this.placement(n, r, [a.id]);
29
+ if (!e) return !1;
30
+ e.parent.definition.items = e.items, a.definition.parentId = e.parent.id, a.definition.rootId = e.parent.definition.rootId;
77
31
  }
78
- if (!a) return !1;
79
- let o = s(this.flat, `${e}.definition.items`, []).filter((e) => e !== n);
80
- if ([
32
+ return this.flat[a.id] = a, Object.assign(this.flat, i), !0;
33
+ };
34
+ placement = (e, t, n, r) => {
35
+ let i = this.flat[e], a = t === "inside" ? e : i?.definition.parentId, o = a ? this.flat[a] : void 0;
36
+ if (!i || !o?.definition.items) return null;
37
+ let s = o.definition.items.filter((e) => e !== r), c = t === "inside" ? s.length : s.indexOf(e);
38
+ return c < 0 || ![
39
+ "inside",
81
40
  "left",
82
41
  "top",
83
42
  "right",
84
43
  "bottom"
85
- ].includes(r)) {
86
- if (!i.definition.parentId) return !1;
87
- let a = this.flat[i.definition.parentId];
88
- if (!a) return !1;
89
- let l = s(a, "definition.items", []);
90
- a.id === e && (l = o);
91
- let u = l.findIndex((e) => e === t);
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);
93
- } else if (r === "inside") {
94
- if (!this.flat[t]) return !1;
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);
97
- }
98
- return !0;
44
+ ].includes(t) ? null : (s.splice(t === "right" || t === "bottom" ? c + 1 : c, 0, ...n), {
45
+ parent: o,
46
+ items: s
47
+ });
99
48
  };
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 = {
49
+ updateElement = (e) => !e || !this.flat[e.id] ? !1 : (this.flat[e.id] = e, !0);
50
+ renameElement = (t, n) => this.flat[t] ? t === n ? [] : !e(this.flat, n) && r(this.flat, { [t]: n }, this.pages) : !1;
51
+ moveElement = (e, t, n, r = "inside") => {
52
+ let i = this.flat[e], a = this.flat[n];
53
+ if (!i || !a || n === t || s(this.flat, t).includes(n)) return !1;
54
+ let o = this.placement(t, r, [n], n);
55
+ return o ? (i.definition.items = (i.definition.items ?? []).filter((e) => e !== n), o.parent.definition.items = o.items, a.definition.parentId = o.parent.id, this.carryRoot(n, o.parent.definition.rootId), !0) : !1;
56
+ };
57
+ carryRoot = (e, t) => {
58
+ if (this.flat[e].definition.rootId !== t) for (let n of [e, ...o(this.flat, e)]) this.flat[n].definition.rootId = t;
59
+ };
60
+ cloneElements = (e, t = "", n = "", s = !1) => {
61
+ let c = {
105
62
  acum: {},
106
63
  item: void 0
107
64
  }, l = {}, u = this.flat[e];
108
- if (!u) return o;
109
- let d = [e, ...this.childTree(e)].filter((e) => this.flat[e] !== void 0), f = this.takenIds();
65
+ if (!u) return c;
66
+ let d = [e, ...o(this.flat, e)], f = i(this.flat);
110
67
  for (let e of d) {
111
68
  let t = a(e, (e) => f.has(e));
112
69
  f.add(t), l[e] = t;
@@ -116,21 +73,16 @@ var p = class {
116
73
  let t = structuredClone(this.flat[e]);
117
74
  n && (t.definition.rootId = n), p[e] = t;
118
75
  }
119
- r(p, l), o.acum = p, o.item = p[l[e]], i && delete o.acum[l[e]];
76
+ r(p, l), c.acum = p, c.item = p[l[e]], s && delete c.acum[l[e]];
120
77
  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;
78
+ return m?.definition.items || (t = m?.definition.parentId ?? u.definition.parentId ?? ""), t && (c.item.definition.parentId = t), c;
122
79
  };
123
80
  removeElement = (e, t = !1) => {
124
81
  let n = this.flat[e];
125
- if (!n || n.definition.type === "page" && !t || t && s(n, "attributes.default", !1)) return !1;
126
- let r = s(n, "definition.items");
127
- r && r.length > 0 && r.forEach((e) => this.removeElement(e));
128
- let i = s(n, "definition.parentId"), a = i ? this.flat[i] : void 0;
129
- if (i && a) {
130
- let { definition: { items: t = [] } } = a;
131
- c(a, "definition.items", t.filter((t) => t !== e)), this.flat[i] = a;
132
- }
133
- return delete this.flat[e], !0;
82
+ if (!n || n.definition.type === "page" && !t || t && n.attributes.default) return !1;
83
+ for (let t of o(this.flat, e)) delete this.flat[t];
84
+ let r = n.definition.parentId ? this.flat[n.definition.parentId] : void 0;
85
+ return r?.definition.items && (r.definition.items = r.definition.items.filter((t) => t !== e)), delete this.flat[e], !0;
134
86
  };
135
87
  addVariables = (e) => {
136
88
  if (e && e.length > 0) {
@@ -151,26 +103,6 @@ var p = class {
151
103
  return this.variables = this.variables.filter((t) => e.includes(t.name)), t !== this.variables.length;
152
104
  };
153
105
  removeVariable = (e) => this.removeVariables([e]);
154
- parentTree = (e, t = /* @__PURE__ */ new Set()) => {
155
- let n = this.flat[e], r = [];
156
- if (!n) return r;
157
- do {
158
- let i = s(n, "definition.type");
159
- if ((i === "page" || i === "layoutContainer") && !t.has(n.id)) {
160
- t.add(n.id);
161
- let e = s(n, "attributes.layout"), i = s(n, "attributes.layoutContainer");
162
- e && i && r.push(i, ...this.parentTree(i, t));
163
- }
164
- e !== n.id && r.push(n.id), n = s(this.flat, s(n, "definition.parentId"), void 0);
165
- } while (n);
166
- return r;
167
- };
168
- childTree = (e) => {
169
- let t = this.flat[e];
170
- if (!t) return [];
171
- let n = [], r = s(t, "definition.items");
172
- return r && r.forEach((e) => n.push(e, ...this.childTree(e))), n;
173
- };
174
106
  isValidElement = (e) => {
175
107
  if (!e) return !1;
176
108
  let { id: t, attributes: n, definition: r } = e;
@@ -180,7 +112,7 @@ var p = class {
180
112
  };
181
113
  flatAsTemplate = (e, t, n = !1) => {
182
114
  let r = {
183
- ...d,
115
+ ...l,
184
116
  platform: {
185
117
  desktop: {},
186
118
  tablet: {},
@@ -195,7 +127,7 @@ var p = class {
195
127
  elementsStyle: r,
196
128
  variables: i
197
129
  };
198
- let a = s(this.flat, t);
130
+ let a = this.flat[t];
199
131
  if (!a) return {
200
132
  elements: {
201
133
  acum: {},
@@ -207,14 +139,14 @@ var p = class {
207
139
  let o = this.cloneElements(t, a.definition.parentId);
208
140
  return o.item ? (Object.values(o.acum).forEach((t) => {
209
141
  let { id: n } = t;
210
- if (c(o.acum, `${n}.definition.rootId`, o.item?.id), f(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
142
+ if (o.item && (t.definition.rootId = o.item.id), u(t, t.definition.type, this.flat, e.platform, {}, { includeSelf: !0 }).tree.forEach((t) => {
211
143
  let { displayMode: n, name: i } = t;
212
144
  !(i in r.platform[n]) && i in e.platform[n] && (r.platform[n][i] = e.platform[n][i]);
213
145
  }), this.variables.length > 0) {
214
146
  let t = this.getElementVariables(e, n, o.acum);
215
147
  i = [...i, ...t];
216
148
  }
217
- }), c(o.acum, `${o.item.id}.definition.parentId`, null), n && delete o.acum[o.item.id], i.length > 1 && (i = [...new Set(i)]), {
149
+ }), delete o.acum[o.item.id].definition.parentId, n && delete o.acum[o.item.id], i.length > 1 && (i = [...new Set(i)]), {
218
150
  elements: o,
219
151
  elementsStyle: r,
220
152
  variables: i
@@ -227,27 +159,10 @@ var p = class {
227
159
  variables: i
228
160
  };
229
161
  };
230
- validate = () => o({
231
- ...l.schema,
232
- flat: this.flat,
233
- variables: this.variables
234
- });
235
- isValid = () => this.validate().valid;
236
- assertValid = (e) => {
237
- let t = o({
238
- ...l.schema,
239
- flat: this.flat,
240
- variables: this.variables
241
- });
242
- if (!t.valid) {
243
- let n = `Invalid schema${e ? ` (${e})` : ""}: ${t.errors.map((e) => e.message).join("; ")}`;
244
- throw Error(n);
245
- }
246
- };
247
162
  getElementVariables = (e, t, n = this.flat, r = this.variables) => {
248
- let i = [], a = s(n, `${t}.definition.styleSelectors`);
163
+ let i = [], a = n[t]?.definition.styleSelectors;
249
164
  if (!a) return i;
250
- let o = new RegExp(u, "g");
165
+ let o = new RegExp(c, "g");
251
166
  return Object.values(a).filter(Boolean).forEach((t) => {
252
167
  Object.values(e.platform).forEach((e) => {
253
168
  let n = e[t];
@@ -266,19 +181,12 @@ var p = class {
266
181
  pages: e.pages
267
182
  }).renameElement(t, n);
268
183
  static moveElement = (e, t, n, r, i = "inside") => this.getInstance({ flat: e }).moveElement(t, n, r, i);
269
- static getElement = (e, t) => this.getInstance({ flat: e }).getElement(t);
270
184
  static cloneElements = (e, t, n = "", r = "", i = !1) => this.getInstance({ flat: e }).cloneElements(t, n, r, i);
271
185
  static removeElement = (e, t, n = !1) => this.getInstance({ flat: e }).removeElement(t, n);
272
- static takenIds = (e) => this.getInstance({ flat: e }).takenIds();
273
- static elementIdConflict = (e, t, n) => this.getInstance({ flat: e }).elementIdConflict(t, n);
274
186
  static addVariables = (e, t) => this.getInstance({ variables: e }).addVariables(t);
275
187
  static addVariable = (e, t) => this.getInstance({ variables: e }).addVariable(t);
276
188
  static updateVariable = (e, t) => this.getInstance({ variables: e }).updateVariable(t);
277
- static removeVariables = (e, t) => this.getInstance({ variables: e }).removeVariables(t);
278
189
  static removeVariable = (e, t) => this.getInstance({ variables: e }).removeVariable(t);
279
- static parentTree = (e, t) => this.getInstance({ flat: e }).parentTree(t);
280
- static childTree = (e, t) => this.getInstance({ flat: e }).childTree(t);
281
- static isValidElement = (e, t) => this.getInstance({ flat: e }).isValidElement(t);
282
190
  static flatAsTemplate = (e, t, n, r = !1) => {
283
191
  let { flat: i, variables: a } = e;
284
192
  return this.getInstance({
@@ -286,22 +194,6 @@ var p = class {
286
194
  variables: a
287
195
  }).flatAsTemplate(t, n, r);
288
196
  };
289
- static getElementVariables = (e, t, n) => {
290
- let { flat: r, variables: i } = e;
291
- return this.getInstance({
292
- flat: r,
293
- variables: i
294
- }).getElementVariables(t, n);
295
- };
296
- static validate = (e) => o(e);
297
- static isValid = (e) => o(e).valid;
298
- static assertValid = (e, t) => {
299
- let n = o(e);
300
- if (!n.valid) {
301
- let e = `Invalid schema${t ? ` (${t})` : ""}: ${n.errors.map((e) => e.message).join("; ")}`;
302
- throw Error(e);
303
- }
304
- };
305
197
  };
306
198
  //#endregion
307
- export { p as default };
199
+ export { d as default };
@@ -0,0 +1,44 @@
1
+ import { Element, Schema } from '@plitzi/sdk-shared';
2
+ /**
3
+ * The flat schema, typed as it actually behaves: a `parentId` or an item can name an element the document no longer
4
+ * has, and every walk here has to survive that. `Record<string, Element>` would call those checks dead code.
5
+ */
6
+ type Flat = Schema['flat'] | Record<string, Element | undefined>;
7
+ /**
8
+ * The elements `id` is nested in, nearest first, up to its page or layout root.
9
+ *
10
+ * The tree as it is stored — no layout crossed. What the builder opens to reveal an element, and what "inside a list"
11
+ * means for an author. A schema edited by two people at once can briefly describe a cycle, so every id is visited once.
12
+ */
13
+ export declare const parentChain: (flat: Flat, id: string) => string[];
14
+ /**
15
+ * Everything an element renders inside, nearest first: its ancestors and, past a page or a layout that is shown in a
16
+ * layout, the container that shell renders it in and that container's own ancestors — through as many layouts as are
17
+ * nested.
18
+ *
19
+ * What the runtime walks to decide which data sources an element sees, so it is what any check of that — the linter's
20
+ * scope rules — has to walk too. A provider elsewhere in the layout, beside the slot rather than around it, is not in
21
+ * it.
22
+ */
23
+ export declare const renderContext: (flat: Flat, id: string) => string[];
24
+ /** Every element under `id`, depth first in document order. Items that name nothing are skipped; a cycle is walked once. */
25
+ export declare const descendants: (flat: Flat, id: string) => string[];
26
+ /** What the author calls a page or layout: its `name`, else its label, else the id when the root is not in `flat`. */
27
+ export declare const rootName: (flat: Flat, rootId: string) => string;
28
+ /** One root of a schema — a page, or a layout several pages render inside — and how many elements it holds. */
29
+ export type RootElements = {
30
+ /** What the author called it (`rootName`). */
31
+ page: string;
32
+ elements: number;
33
+ };
34
+ /**
35
+ * The element count, grouped by the root each element names (`definition.rootId`), heaviest first. `only` narrows it
36
+ * to the elements it accepts — where a plugin is used, say — still named after the page or layout that holds them.
37
+ *
38
+ * Unnarrowed it is a grouping, not a second measurement: it always adds up to the number of elements in `flat`, so the
39
+ * builder's live meter and the API's usage panel agree with the count the element ceilings are judged on. Layouts are
40
+ * roots too and are listed as themselves rather than shared out over the pages that render inside them — their
41
+ * elements are authored once, and splitting them between pages would report a total that no page has.
42
+ */
43
+ export declare const elementsByRoot: (flat: Flat, only?: (element: Element) => boolean) => RootElements[];
44
+ export {};
@@ -0,0 +1,41 @@
1
+ //#region src/helpers/elementTree.ts
2
+ var e = (e, t) => t && Object.hasOwn(e, t) ? e[t] : void 0, t = (t, n) => {
3
+ let r = [], i = /* @__PURE__ */ new Set([n]);
4
+ for (let a = e(t, e(t, n)?.definition.parentId); a && !i.has(a.id);) i.add(a.id), r.push(a.id), a = e(t, a.definition.parentId);
5
+ return r;
6
+ }, n = (n, r) => {
7
+ let i = [], a = /* @__PURE__ */ new Set([r]), o = (s) => {
8
+ for (let c of [s, ...t(n, s)]) {
9
+ let t = e(n, c);
10
+ if (c !== s || s !== r) {
11
+ if (a.has(c)) continue;
12
+ a.add(c), i.push(c);
13
+ }
14
+ let l = t?.definition.type, { layout: u, layoutContainer: d } = t?.attributes ?? {};
15
+ (l === "page" || l === "layoutContainer") && u && typeof d == "string" && !a.has(d) && o(d);
16
+ }
17
+ };
18
+ return o(r), i;
19
+ }, r = (t, n) => {
20
+ let r = [], i = /* @__PURE__ */ new Set([n]), a = (n) => {
21
+ for (let o of e(t, n)?.definition.items ?? []) !i.has(o) && e(t, o) && (i.add(o), r.push(o), a(o));
22
+ };
23
+ return a(n), r;
24
+ }, i = (t, n) => {
25
+ let r = e(t, n);
26
+ if (!r) return n;
27
+ let { name: i } = r.attributes;
28
+ return typeof i == "string" && i ? i : r.definition.label;
29
+ }, a = (e, t = () => !0) => {
30
+ let n = /* @__PURE__ */ new Map();
31
+ for (let r of Object.values(e)) if (r && t(r)) {
32
+ let e = r.definition.rootId;
33
+ n.set(e, (n.get(e) ?? 0) + 1);
34
+ }
35
+ return [...n.entries()].map(([t, n]) => ({
36
+ page: i(e, t),
37
+ elements: n
38
+ })).sort((e, t) => t.elements - e.elements);
39
+ };
40
+ //#endregion
41
+ export { r as descendants, a as elementsByRoot, t as parentChain, n as renderContext, i as rootName };
@@ -21,6 +21,18 @@ export type SchemaValidationResult = {
21
21
  errors: SchemaValidationError[];
22
22
  warnings: SchemaValidationError[];
23
23
  };
24
+ /**
25
+ * The errors that are a reference to something no longer there, rather than the document being broken.
26
+ *
27
+ * An editor produces them in the ordinary course of editing: delete a provider and the binding that read it names
28
+ * nothing, delete an element and the step aimed at it has no target. The next edit is what settles them — rebind, or
29
+ * drop the step — so a writer that edits one change at a time reports them and refuses them only when the space is
30
+ * about to be served. Every other error is the tree itself broken — an orphan, a cycle, two ids for one element — and
31
+ * no sequence of edits should ever leave one behind.
32
+ */
33
+ export declare const REFERENCE_ERROR_CODES: ReadonlySet<string>;
34
+ /** Whether an error is the document itself being broken, as opposed to a reference an edit left dangling. */
35
+ export declare const isIntegrityError: (error: SchemaValidationError) => boolean;
24
36
  export declare const validateSchema: (schema: Schema, options?: SchemaValidationOptions) => SchemaValidationResult;
25
37
  export declare const assertSchemaValid: (schema: Schema, options?: SchemaValidationOptions, context?: string) => void;
26
38
  export declare const isSchemaValid: (schema: Schema, options?: SchemaValidationOptions) => boolean;
@@ -1,7 +1,11 @@
1
1
  import { isValidElementId as e } from "./elementId.mjs";
2
2
  import { findLayoutCycle as t } from "@plitzi/sdk-shared/schema/layoutChain";
3
3
  //#region src/helpers/schemaValidator.ts
4
- var n = (n) => {
4
+ var n = /* @__PURE__ */ new Set([
5
+ "UNRESOLVED_BINDING_SOURCE",
6
+ "MISMATCHED_BINDING_SOURCE",
7
+ "UNRESOLVED_INTERACTION_TARGET"
8
+ ]), r = (e) => !n.has(e.code), i = (n) => {
5
9
  let r = [], i = [], { flat: a, pages: o, pageFolders: s, variables: c } = n, l = (e) => !!a[e], u = (e) => a[e], d = () => Object.values(a).filter((e) => e?.definition?.type === "layoutContainer" && !e.definition.parentId).map((e) => e.id), f = () => [...o, ...d()], p = () => !a || typeof a != "object" ? (r.push({
6
10
  code: "INVALID_FLAT",
7
11
  message: "Schema.flat must be a valid Record<string, Element>"
@@ -389,12 +393,12 @@ var n = (n) => {
389
393
  warnings: i
390
394
  };
391
395
  } };
392
- }, r = (e, t) => n(e).validate(t), i = (e, t, n) => {
393
- let i = r(e, t);
394
- if (!i.valid) {
395
- let e = `Invalid schema${n ? ` (${n})` : ""}: ${i.errors.map((e) => e.message).join("; ")}`;
396
+ }, a = (e, t) => i(e).validate(t), o = (e, t, n) => {
397
+ let r = a(e, t);
398
+ if (!r.valid) {
399
+ let e = `Invalid schema${n ? ` (${n})` : ""}: ${r.errors.map((e) => e.message).join("; ")}`;
396
400
  throw Error(e);
397
401
  }
398
- }, a = (e, t) => r(e, t).valid;
402
+ }, s = (e, t) => a(e, t).valid;
399
403
  //#endregion
400
- export { i as assertSchemaValid, a as isSchemaValid, r as validateSchema };
404
+ export { n as REFERENCE_ERROR_CODES, o as assertSchemaValid, r as isIntegrityError, s as isSchemaValid, a as validateSchema };
@@ -1,4 +1,9 @@
1
1
  import { Element, Style } from '@plitzi/sdk-shared';
2
+ /**
3
+ * Whether an element renders no element of its own — an `apiContainer` with no `subType`, the builder's "Container
4
+ * Tag: None". Its children sit straight in its parent, so it has no box: nothing to style, and nothing a test can see.
5
+ */
6
+ export declare const rendersNoTag: (element: Pick<Element, "attributes" | "definition">) => boolean;
2
7
  /**
3
8
  * Why an element's style applies to nothing, if it does not.
4
9
  *
@@ -1,10 +1,11 @@
1
1
  //#region src/helpers/styleWithoutTag.ts
2
- var e = (e) => Object.keys(e ?? {}).length > 0, t = (t) => Object.values(t ?? {}).some(e), n = (n) => e(n.default) || t(n.states) || Object.values(n.variants ?? {}).some((n) => e(n.default) || t(n.states)), r = (e, t) => Object.values(e.platform).some((e) => Object.hasOwn(e, t) && Object.values(e[t].attributes).some(n)), i = (e, t) => {
3
- if (e.definition.type !== "apiContainer") return;
4
- let n = e.attributes.subType;
5
- if (typeof n == "string" && n !== "") return;
6
- let { styleSelectors: i, initialState: a, bindings: o } = e.definition, s = Object.values(i).some((e) => e.split(" ").some((e) => e && r(t, e))), c = Object.keys(a?.styleVariant ?? {}).length > 0, l = (o?.style?.length ?? 0) > 0;
2
+ var e = (e) => Object.keys(e ?? {}).length > 0, t = (t) => Object.values(t ?? {}).some(e), n = (n) => e(n.default) || t(n.states) || Object.values(n.variants ?? {}).some((n) => e(n.default) || t(n.states)), r = (e, t) => Object.values(e.platform).some((e) => Object.hasOwn(e, t) && Object.values(e[t].attributes).some(n)), i = (e) => {
3
+ let t = e.attributes.subType;
4
+ return e.definition.type === "apiContainer" && (typeof t != "string" || t === "");
5
+ }, a = (e, t) => {
6
+ if (!i(e)) return;
7
+ let { styleSelectors: n, initialState: a, bindings: o } = e.definition, s = Object.values(n).some((e) => e.split(" ").some((e) => e && r(t, e))), c = Object.keys(a?.styleVariant ?? {}).length > 0, l = (o?.style?.length ?? 0) > 0;
7
8
  if (s || c || l) return "has style but no `subType` (container tag), so it renders no element of its own and the style applies to nothing — its children lay out in its parent. Give it a tag (a `subType` of `div`, `section`, …) or move the style onto its parent or a child.";
8
9
  };
9
10
  //#endregion
10
- export { i as styleWithoutTag };
11
+ export { i as rendersNoTag, a as styleWithoutTag };
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ import { default as FlatMap } from './helpers/FlatMap';
2
2
  import { default as SchemaReducer } from './SchemaReducer';
3
3
  export * from './SchemaReducer';
4
4
  export * from './helpers/FlatMap';
5
+ export * from './helpers/elementTree';
5
6
  export * from './helpers/schemaValidator';
6
7
  export { FlatMap, SchemaReducer };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { assertSchemaValid as e, isSchemaValid as t, validateSchema as n } from "./helpers/schemaValidator.mjs";
2
- import r from "./helpers/FlatMap.mjs";
3
- import i, { SchemaActions as a } from "./SchemaReducer.mjs";
4
- export { r as FlatMap, a as SchemaActions, i as SchemaReducer, e as assertSchemaValid, t as isSchemaValid, n as validateSchema };
1
+ import { descendants as e, elementsByRoot as t, parentChain as n, renderContext as r, rootName as i } from "./helpers/elementTree.mjs";
2
+ import a from "./helpers/FlatMap.mjs";
3
+ import o, { SchemaActions as s } from "./SchemaReducer.mjs";
4
+ import { REFERENCE_ERROR_CODES as c, assertSchemaValid as l, isIntegrityError as u, isSchemaValid as d, validateSchema as f } from "./helpers/schemaValidator.mjs";
5
+ export { a as FlatMap, c as REFERENCE_ERROR_CODES, s as SchemaActions, o as SchemaReducer, l as assertSchemaValid, e as descendants, t as elementsByRoot, u as isIntegrityError, d as isSchemaValid, n as parentChain, r as renderContext, i as rootName, f as validateSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-schema",
3
- "version": "0.37.2",
3
+ "version": "0.37.4",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist"
@@ -22,6 +22,10 @@
22
22
  "types": "./dist/helpers/elementId.d.ts",
23
23
  "import": "./dist/helpers/elementId.mjs"
24
24
  },
25
+ "./helpers/elementTree": {
26
+ "types": "./dist/helpers/elementTree.d.ts",
27
+ "import": "./dist/helpers/elementTree.mjs"
28
+ },
25
29
  "./helpers/schemaValidator": {
26
30
  "types": "./dist/helpers/schemaValidator.d.ts",
27
31
  "import": "./dist/helpers/schemaValidator.mjs"
@@ -48,15 +52,15 @@
48
52
  "build:prod": "vite build && node ../sdk-shared/scripts/generate-exports.mjs"
49
53
  },
50
54
  "dependencies": {
51
- "@plitzi/plitzi-ui": "^1.6.22",
52
- "@plitzi/sdk-shared": "0.37.2",
53
- "@plitzi/sdk-style": "0.37.2",
55
+ "@plitzi/plitzi-ui": "^1.6.24",
56
+ "@plitzi/sdk-shared": "0.37.4",
57
+ "@plitzi/sdk-style": "0.37.4",
54
58
  "prop-types": "^15.8.1"
55
59
  },
56
60
  "devDependencies": {
57
61
  "eslint": "^9.39.5",
58
62
  "typescript": "^6.0.3",
59
- "vite": "^8.3.0",
63
+ "vite": "^8.3.1",
60
64
  "vitest": "^5.0.1"
61
65
  }
62
66
  }