@plitzi/sdk-schema 0.36.1 → 0.37.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # @plitzi/sdk-schema
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - - A `button` takes a `title`: shown as a tooltip, and its accessible name when it has no text of its own — an
8
+ icon-only button without one was announced as nothing. The builder's settings offer it as "Tooltip". The JSON export
9
+ is two files, `schema.json` and `style.json`, shown as tabs; each group of changes in the export folds away.
10
+
11
+ - `BindingTransformer.params` is `Record<string, string | number | boolean>`: a param the builder draws as a checkbox
12
+ (`styleVariant`'s `append`) is stored as the boolean it is and read as one, so the type now says so. The MCP compares
13
+ such a param with its catalog options as text. A `container` may be an `li`, for the rows of a list. The export
14
+ keeps every element id with `keepIds`, and the header's Publish dialog opens wider.
15
+
16
+ - **The builder exports the space on screen — on paid plans.** **Export** in the header opens a wide dialog: the
17
+ format as tabs (TypeScript in one file, TypeScript with a file per page as a `.zip`, or JSON), generated as soon as
18
+ it is picked, shown read-only in CodeMirror — beside a file list when there are several — with one row of actions,
19
+ **Copy** and **Download**. What the export tidied is a one-line note that unfolds into readable groups. On the free
20
+ plan the dialog explains what export is instead; the server refuses the code with a 402 (`limit: 'spaceExport'`)
21
+ whatever the builder shows. `useSpaceExport` is the one way into it, for anything else that needs the space as files.
22
+
23
+ - **A space document can be read back into code.** `specFromSpace(documents)` turns a `{ schema, style }` pair — a
24
+ builder export, a seed checked in as JSON — into the `SpaceSpec` that authors it, and `specToSource(spec, { exportName })`
25
+ writes that spec out as the TypeScript a person would write: one factory call per element, the attributes a type
26
+ already defaults to left out, the longhands the document stores written back as `padding: 10px 20px`, and every class
27
+ something names as a `styles()` declaration held in a variable. `split: true` writes one file per page and per layout.
28
+ `compareSpaces(expected, actual)` proves the round trip: it lists every way two pairs render differently — the tree,
29
+ the attributes, the rules that apply to each element whatever its selector is called, bindings, flows, pages, layouts
30
+ and settings — and nothing else. A selector used by one element becomes that element's own `css`; one shared, or
31
+ spelled out anywhere in the document (a `customCss` rule, a template), stays a class under its own name; an element id
32
+ nothing refers to is left out and derived again.
33
+
34
+ What an older builder left behind is repaired on the way, and every repair is reported in `corrections` rather than
35
+ made silently: element types that no longer exist (`navbar` → a `list` laid out in a row, `navbarItem` → `listItem`),
36
+ a hover stored as a class of its own (`card:hover`) folded into the class, fields and attributes no component reads,
37
+ a setting nothing reads, a link `target` of `_blank` (the component adds the underscore, so it rendered as `__blank`),
38
+ `null` or an empty list where an attribute is unset, a binding to a source nothing publishes, a flow with a step that
39
+ runs nothing, and a global callback registered on the wrong module. A CSS property the style editor cannot hold is
40
+ kept in `customCss` under the same selector, so the page still renders it.
41
+
42
+ - **What a spec can say grew to what a document holds.** A selector carries its states and variants beside its rules —
43
+ `styles('card', { css, states: { hover }, variants: { active } })`, and `states` on an element's own `css` — and the
44
+ selector cache is now written by `processSelector`, the function the style editor writes it with. An element type's
45
+ defaults (`elements`) take states, variants, per-breakpoint rules and `slots` for its other selectors (a modal's
46
+ `rootContainer`). A space declares `layouts` — shells that pages render inside, named by `layout: { id, slot }` on a
47
+ page or on another layout, with the slot checked to be inside the shell — and a page takes `keepState` and
48
+ `stateStorage`. `visible: false` starts an element hidden for a flow to reveal, and `loadStrategy` is carried through.
49
+ An element's own selector never takes the name of a declared class any more, however that class happens to be called.
50
+
51
+ - `elementAttributeNames` lists, as data, the attributes each built-in element can be authored with — `null` for one
52
+ that takes any. It is generated from the elements' attribute types (`yarn generate:attribute-names`) and a type test
53
+ fails when the two disagree. `defaultAttributes(type)` and `defaultLabel(type)` expose what a factory fills in.
54
+
55
+ - `CustomAttributes` accepts any attribute, because the component a custom element's `renderType` names reads its own
56
+ attributes off it; and `FormControlAttributes` declares `previewError`, which `withFieldValue` reads.
57
+
58
+ ### Patch Changes
59
+
60
+ - Updated dependencies
61
+ - @plitzi/sdk-shared@0.37.0
62
+ - @plitzi/sdk-style@0.37.0
63
+
64
+ ## 0.36.2
65
+
66
+ ### Patch Changes
67
+
68
+ - v0.36.2
69
+ - Updated dependencies
70
+ - @plitzi/sdk-shared@0.36.2
71
+ - @plitzi/sdk-style@0.36.2
72
+
3
73
  ## 0.36.1
4
74
 
5
75
  ### Patch Changes
@@ -74,7 +74,12 @@ declare class FlatMap {
74
74
  updateVariable: (variable: SchemaVariable) => boolean;
75
75
  removeVariables: (variables: string[]) => boolean;
76
76
  removeVariable: (variable: string) => boolean;
77
- parentTree: (elementId: Element["id"]) => string[];
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[];
78
83
  childTree: (elementId: Element["id"]) => string[];
79
84
  isValidElement: (element?: Partial<Element>) => boolean;
80
85
  flatAsTemplate: (style: Style, elementId: Element["id"], excludeRoot?: boolean) => {
@@ -151,17 +151,19 @@ var p = class {
151
151
  return this.variables = this.variables.filter((t) => e.includes(t.name)), t !== this.variables.length;
152
152
  };
153
153
  removeVariable = (e) => this.removeVariables([e]);
154
- parentTree = (e) => {
155
- let t = this.flat[e], n = [];
156
- if (!t) return n;
154
+ parentTree = (e, t = /* @__PURE__ */ new Set()) => {
155
+ let n = this.flat[e], r = [];
156
+ if (!n) return r;
157
157
  do {
158
- if (s(t, "definition.type") === "page") {
159
- let e = s(t, "attributes.layout"), r = s(t, "attributes.layoutContainer");
160
- e && r && n.push(r, ...this.parentTree(r));
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));
161
163
  }
162
- e !== t.id && n.push(t.id), t = s(this.flat, s(t, "definition.parentId"), void 0);
163
- } while (t);
164
- return n;
164
+ e !== n.id && r.push(n.id), n = s(this.flat, s(n, "definition.parentId"), void 0);
165
+ } while (n);
166
+ return r;
165
167
  };
166
168
  childTree = (e) => {
167
169
  let t = this.flat[e];
@@ -1,22 +1,23 @@
1
1
  import { isValidElementId as e } from "./elementId.mjs";
2
+ import { findLayoutCycle as t } from "@plitzi/sdk-shared/schema/layoutChain";
2
3
  //#region src/helpers/schemaValidator.ts
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 = () => Object.values(i).filter((e) => e?.definition?.type === "layoutContainer" && !e.definition.parentId).map((e) => e.id), d = () => [...a, ...u()], f = () => !i || typeof i != "object" ? (n.push({
4
+ var n = (n) => {
5
+ 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({
5
6
  code: "INVALID_FLAT",
6
7
  message: "Schema.flat must be a valid Record<string, Element>"
7
- }), !1) : Array.isArray(a) ? Array.isArray(o) ? Array.isArray(s) ? !0 : (n.push({
8
+ }), !1) : Array.isArray(o) ? Array.isArray(s) ? Array.isArray(c) ? !0 : (r.push({
8
9
  code: "INVALID_VARIABLES",
9
10
  message: "Schema.variables must be an array"
10
- }), !1) : (n.push({
11
+ }), !1) : (r.push({
11
12
  code: "INVALID_PAGE_FOLDERS",
12
13
  message: "Schema.pageFolders must be an array"
13
- }), !1) : (n.push({
14
+ }), !1) : (r.push({
14
15
  code: "INVALID_PAGES",
15
16
  message: "Schema.pages must be an array of element IDs"
16
- }), !1), p = () => {
17
- Object.entries(i).forEach(([e, t]) => {
17
+ }), !1), m = () => {
18
+ Object.entries(a).forEach(([e, t]) => {
18
19
  if (!t) {
19
- n.push({
20
+ r.push({
20
21
  code: "NULL_ELEMENT",
21
22
  message: `Element with id "${e}" is null or undefined`,
22
23
  elementId: e
@@ -24,163 +25,163 @@ var t = (t) => {
24
25
  return;
25
26
  }
26
27
  if (!t.id) {
27
- n.push({
28
+ r.push({
28
29
  code: "MISSING_ID",
29
30
  message: `Element at key "${e}" has no id property`
30
31
  });
31
32
  return;
32
33
  }
33
- if (t.id !== e && n.push({
34
+ if (t.id !== e && r.push({
34
35
  code: "ID_MISMATCH",
35
36
  message: `Element id "${t.id}" doesn't match flat key "${e}"`,
36
37
  elementId: e
37
38
  }), !t.definition) {
38
- n.push({
39
+ r.push({
39
40
  code: "MISSING_DEFINITION",
40
41
  message: "Element missing definition",
41
42
  elementId: e
42
43
  });
43
44
  return;
44
45
  }
45
- let { definition: i, attributes: a } = t;
46
- i.type || n.push({
46
+ let { definition: n, attributes: a } = t;
47
+ n.type || r.push({
47
48
  code: "MISSING_TYPE",
48
49
  message: "Element missing definition.type",
49
50
  elementId: e
50
- }), i.label === void 0 && n.push({
51
+ }), n.label === void 0 && r.push({
51
52
  code: "MISSING_LABEL",
52
53
  message: "Element missing definition.label",
53
54
  elementId: e
54
- }), i.rootId || n.push({
55
+ }), n.rootId || r.push({
55
56
  code: "MISSING_ROOT_ID",
56
57
  message: "Element missing definition.rootId",
57
58
  elementId: e
58
- }), (!i.styleSelectors || typeof i.styleSelectors != "object") && n.push({
59
+ }), (!n.styleSelectors || typeof n.styleSelectors != "object") && r.push({
59
60
  code: "MISSING_STYLE_SELECTORS",
60
61
  message: "Element missing or invalid definition.styleSelectors",
61
62
  elementId: e
62
- }), a || r.push({
63
+ }), a || i.push({
63
64
  code: "MISSING_ATTRIBUTES",
64
65
  message: "Element has no attributes",
65
66
  elementId: e
66
- }), i.items && (Array.isArray(i.items) ? i.items.forEach((t, r) => {
67
- c(t) || n.push({
67
+ }), n.items && (Array.isArray(n.items) ? n.items.forEach((t, n) => {
68
+ l(t) || r.push({
68
69
  code: "ORPHANED_ITEM_REFERENCE",
69
- message: `Element "${e}" references non-existent child "${t}" at items[${r}]`,
70
+ message: `Element "${e}" references non-existent child "${t}" at items[${n}]`,
70
71
  elementId: e,
71
72
  details: {
72
73
  childId: t,
73
- index: r
74
+ index: n
74
75
  }
75
76
  });
76
- }) : n.push({
77
+ }) : r.push({
77
78
  code: "INVALID_ITEMS",
78
79
  message: "Element.definition.items must be an array",
79
80
  elementId: e
80
- })), i.parentId && !c(i.parentId) && n.push({
81
+ })), n.parentId && !l(n.parentId) && r.push({
81
82
  code: "ORPHANED_PARENT_REFERENCE",
82
- message: `Element "${e}" has parentId "${i.parentId}" but parent doesn't exist in flat`,
83
+ message: `Element "${e}" has parentId "${n.parentId}" but parent doesn't exist in flat`,
83
84
  elementId: e,
84
- details: { parentId: i.parentId }
85
+ details: { parentId: n.parentId }
85
86
  });
86
87
  });
87
- }, m = () => {
88
- Object.values(i).forEach((e) => {
88
+ }, h = () => {
89
+ Object.values(a).forEach((e) => {
89
90
  if (!e?.definition) return;
90
- let { id: t, definition: r } = e;
91
- if (r.items && r.items.forEach((e) => {
92
- let r = l(e);
93
- r?.definition && r.definition.parentId !== t && n.push({
91
+ let { id: t, definition: n } = e;
92
+ if (n.items && n.items.forEach((e) => {
93
+ let n = u(e);
94
+ n?.definition && n.definition.parentId !== t && r.push({
94
95
  code: "PARENT_CHILD_MISMATCH",
95
- message: `Element "${e}" is in "${t}".items but has parentId "${r.definition.parentId}"`,
96
+ message: `Element "${e}" is in "${t}".items but has parentId "${n.definition.parentId}"`,
96
97
  elementId: e,
97
98
  details: {
98
99
  expectedParent: t,
99
- actualParent: r.definition.parentId
100
+ actualParent: n.definition.parentId
100
101
  }
101
102
  });
102
- }), r.parentId) {
103
- let e = l(r.parentId);
103
+ }), n.parentId) {
104
+ let e = u(n.parentId);
104
105
  if (!e?.definition) return;
105
106
  let i = e.definition.items || [];
106
- i.includes(t) || n.push({
107
+ i.includes(t) || r.push({
107
108
  code: "PARENT_CHILD_MISMATCH",
108
- message: `Element "${t}" has parentId "${r.parentId}" but parent doesn't have it in items`,
109
+ message: `Element "${t}" has parentId "${n.parentId}" but parent doesn't have it in items`,
109
110
  elementId: t,
110
111
  details: {
111
- parentId: r.parentId,
112
+ parentId: n.parentId,
112
113
  parentItems: i
113
114
  }
114
115
  });
115
116
  }
116
117
  });
117
- }, h = () => {
118
- let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), r = (i) => {
119
- if (t.has(i)) return n.push({
118
+ }, g = () => {
119
+ let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), n = (i) => {
120
+ if (t.has(i)) return r.push({
120
121
  code: "CIRCULAR_REFERENCE",
121
122
  message: `Circular reference detected involving element "${i}"`,
122
123
  elementId: i
123
124
  }), !0;
124
125
  if (e.has(i)) return !1;
125
126
  e.add(i), t.add(i);
126
- let a = l(i);
127
+ let a = u(i);
127
128
  if (a?.definition?.items) {
128
- for (let e of a.definition.items) if (r(e)) return t.delete(i), !0;
129
+ for (let e of a.definition.items) if (n(e)) return t.delete(i), !0;
129
130
  }
130
131
  let o = /* @__PURE__ */ new Set(), s = a;
131
132
  for (; s?.definition?.parentId;) {
132
- if (o.has(s.id)) return n.push({
133
+ if (o.has(s.id)) return r.push({
133
134
  code: "CIRCULAR_PARENT_REFERENCE",
134
135
  message: `Circular parent reference detected involving element "${s.id}"`,
135
136
  elementId: s.id
136
137
  }), !0;
137
- o.add(s.id), s = l(s.definition.parentId);
138
+ o.add(s.id), s = u(s.definition.parentId);
138
139
  }
139
140
  return t.delete(i), !1;
140
141
  };
141
- Object.keys(i).forEach((t) => {
142
- e.has(t) || r(t);
142
+ Object.keys(a).forEach((t) => {
143
+ e.has(t) || n(t);
143
144
  });
144
- }, g = () => {
145
+ }, _ = () => {
145
146
  let e = /* @__PURE__ */ new Set(), t = 0;
146
- a.forEach((r, i) => {
147
- e.has(r) && n.push({
147
+ o.forEach((n, i) => {
148
+ e.has(n) && r.push({
148
149
  code: "DUPLICATE_PAGE",
149
- message: `Duplicate page ID "${r}" in pages array at index ${i}`,
150
- elementId: r
151
- }), e.add(r);
152
- let a = l(r);
150
+ message: `Duplicate page ID "${n}" in pages array at index ${i}`,
151
+ elementId: n
152
+ }), e.add(n);
153
+ let a = u(n);
153
154
  if (!a) {
154
- n.push({
155
+ r.push({
155
156
  code: "INVALID_PAGE_REFERENCE",
156
- message: `Page ID "${r}" in pages array doesn't exist in flat`,
157
- elementId: r
157
+ message: `Page ID "${n}" in pages array doesn't exist in flat`,
158
+ elementId: n
158
159
  });
159
160
  return;
160
161
  }
161
- a.definition.type !== "page" && n.push({
162
+ a.definition.type !== "page" && r.push({
162
163
  code: "INVALID_PAGE_TYPE",
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({
164
+ message: `Element "${n}" in pages array has type "${a.definition.type}" instead of "page"`,
165
+ elementId: n
166
+ }), a.definition.rootId !== n && r.push({
166
167
  code: "PAGE_ROOT_ID_MISMATCH",
167
- message: `Page "${r}" has rootId "${a.definition.rootId}" instead of its own ID`,
168
- elementId: r
168
+ message: `Page "${n}" has rootId "${a.definition.rootId}" instead of its own ID`,
169
+ elementId: n
169
170
  }), a.attributes?.default && t++;
170
- }), t === 0 && a.length > 0 ? r.push({
171
+ }), t === 0 && o.length > 0 ? i.push({
171
172
  code: "NO_DEFAULT_PAGE",
172
173
  message: "No default page found in schema"
173
- }) : t > 1 && r.push({
174
+ }) : t > 1 && i.push({
174
175
  code: "MULTIPLE_DEFAULT_PAGES",
175
176
  message: `Found ${t} default pages, expected 1`
176
177
  });
177
- }, _ = () => {
178
- d().forEach((e) => {
179
- let t = l(e);
178
+ }, v = () => {
179
+ f().forEach((e) => {
180
+ let t = u(e);
180
181
  if (!t) return;
181
- let r = (t) => {
182
- let i = l(t);
183
- i?.definition && (i.definition.rootId !== e && n.push({
182
+ let n = (t) => {
183
+ let i = u(t);
184
+ i?.definition && (i.definition.rootId !== e && r.push({
184
185
  code: "ROOT_ID_MISMATCH",
185
186
  message: `Element "${t}" has rootId "${i.definition.rootId}" but should be "${e}" (its root's ID)`,
186
187
  elementId: t,
@@ -188,98 +189,110 @@ var t = (t) => {
188
189
  expectedRootId: e,
189
190
  actualRootId: i.definition.rootId
190
191
  }
191
- }), i.definition.items && i.definition.items.forEach((e) => r(e)));
192
+ }), i.definition.items && i.definition.items.forEach((e) => n(e)));
192
193
  };
193
- t.definition.items && t.definition.items.forEach((e) => r(e));
194
+ t.definition.items && t.definition.items.forEach((e) => n(e));
194
195
  });
195
- }, v = () => {
196
+ }, y = () => {
196
197
  let e = /* @__PURE__ */ new Set();
197
- o.forEach((t, r) => {
198
+ s.forEach((t, n) => {
198
199
  if (!t.id) {
199
- n.push({
200
+ r.push({
200
201
  code: "INVALID_FOLDER",
201
- message: `Page folder at index ${r} has no id`
202
+ message: `Page folder at index ${n} has no id`
202
203
  });
203
204
  return;
204
205
  }
205
- e.has(t.id) && n.push({
206
+ e.has(t.id) && r.push({
206
207
  code: "DUPLICATE_FOLDER",
207
208
  message: `Duplicate folder ID "${t.id}"`,
208
209
  elementId: t.id
209
- }), e.add(t.id), t.parentId && (e.has(t.parentId) || n.push({
210
+ }), e.add(t.id), t.parentId && (e.has(t.parentId) || r.push({
210
211
  code: "ORPHANED_FOLDER_PARENT",
211
212
  message: `Folder "${t.id}" has non-existent parent "${t.parentId}"`,
212
213
  elementId: t.id,
213
214
  details: { parentId: t.parentId }
214
215
  }));
215
216
  });
216
- }, y = (e) => {
217
+ }, b = () => {
218
+ let e = /* @__PURE__ */ new Set();
219
+ d().forEach((n) => {
220
+ let i = t(u, n);
221
+ i.length === 0 || e.has(i[0]) || (i.forEach((t) => e.add(t)), r.push({
222
+ code: "LAYOUT_CYCLE",
223
+ message: `Layouts sit inside each other in a circle: ${i.join(" → ")}`,
224
+ elementId: n,
225
+ details: { cycle: i }
226
+ }));
227
+ });
228
+ }, x = (e) => {
217
229
  let t = /* @__PURE__ */ new Set(), n = (e) => {
218
230
  if (t.has(e)) return;
219
231
  t.add(e);
220
- let r = l(e);
232
+ let r = u(e);
221
233
  r?.definition?.items && r.definition.items.forEach((e) => n(e));
222
- }, a = (e) => {
223
- n(e);
224
- let t = l(e)?.attributes;
225
- t?.layout && n(t.layout), t?.layoutContainer && n(t.layoutContainer);
234
+ }, r = /* @__PURE__ */ new Set(), o = (e) => {
235
+ if (r.has(e)) return;
236
+ r.add(e), n(e);
237
+ let t = u(e)?.attributes;
238
+ t?.layout && o(t.layout), t?.layoutContainer && n(t.layoutContainer);
226
239
  };
227
- e ? a(e) : d().forEach(a);
228
- let o = new Set(d());
229
- Object.keys(i).forEach((e) => {
230
- let n = l(e);
231
- n && !t.has(e) && n.definition.type !== "page" && !o.has(e) && r.push({
240
+ e ? o(e) : f().forEach(o);
241
+ let s = new Set(f());
242
+ Object.keys(a).forEach((e) => {
243
+ let n = u(e);
244
+ n && !t.has(e) && n.definition.type !== "page" && !s.has(e) && i.push({
232
245
  code: "ORPHANED_ELEMENT",
233
246
  message: `Element "${e}" is not reachable from any page or layout`,
234
247
  elementId: e
235
248
  });
236
249
  });
237
- }, b = () => {
250
+ }, S = () => {
238
251
  let e = /* @__PURE__ */ new Set();
239
- s.forEach((t, i) => {
252
+ c.forEach((t, n) => {
240
253
  if (!t.name) {
241
- n.push({
254
+ r.push({
242
255
  code: "INVALID_VARIABLE",
243
- message: `Variable at index ${i} has no name`
256
+ message: `Variable at index ${n} has no name`
244
257
  });
245
258
  return;
246
259
  }
247
- e.has(t.name) && n.push({
260
+ e.has(t.name) && r.push({
248
261
  code: "DUPLICATE_VARIABLE",
249
262
  message: `Duplicate variable name "${t.name}"`,
250
263
  details: { variableName: t.name }
251
- }), e.add(t.name), t.type || r.push({
264
+ }), e.add(t.name), t.type || i.push({
252
265
  code: "MISSING_VARIABLE_TYPE",
253
266
  message: `Variable "${t.name}" has no type`,
254
267
  details: { variableName: t.name }
255
268
  });
256
269
  });
257
- }, x = () => {
258
- Object.values(i).forEach((t) => {
259
- t && (e(t.id) || n.push({
270
+ }, C = () => {
271
+ Object.values(a).forEach((t) => {
272
+ t && (e(t.id) || r.push({
260
273
  code: "INVALID_ELEMENT_ID",
261
274
  message: `Element "${t.id}" has a name that must start with a letter, then letters, numbers, hyphens and underscores (no hyphen or underscore at the start, and no hyphen at the end)`,
262
275
  elementId: t.id
263
276
  }));
264
277
  });
265
- }, S = (e) => {
266
- let t = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map();
267
- Object.values(i).forEach((n) => {
268
- if (!n) return;
269
- t.add(n.id);
270
- let i = e?.[n.definition.type];
271
- i && a.set(n.id, i), r.add(n.definition.type);
272
- }), Object.values(i).forEach((i) => {
273
- i && i.definition.bindings && Object.entries(i.definition.bindings).forEach(([o, s]) => {
278
+ }, w = (e) => {
279
+ let t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Map();
280
+ Object.values(a).forEach((r) => {
281
+ if (!r) return;
282
+ t.add(r.id);
283
+ let a = e?.[r.definition.type];
284
+ a && i.set(r.id, a), n.add(r.definition.type);
285
+ }), Object.values(a).forEach((a) => {
286
+ a && a.definition.bindings && Object.entries(a.definition.bindings).forEach(([o, s]) => {
274
287
  (s ?? []).forEach((s) => {
275
288
  let c = s.source.split(".")[0], l = c.indexOf("_");
276
289
  if (l === -1) return;
277
- let u = c.slice(0, l), d = c.slice(l + 1), f = `Element "${i.id}" binds ${o}.${s.to} to "${s.source}"`;
290
+ let u = c.slice(0, l), d = c.slice(l + 1), f = `Element "${a.id}" binds ${o}.${s.to} to "${s.source}"`;
278
291
  if (!t.has(d)) {
279
- (e || r.has(u)) && n.push({
292
+ (e || n.has(u)) && r.push({
280
293
  code: "UNRESOLVED_BINDING_SOURCE",
281
294
  message: `${f}, but no element answers to the name "${d}"`,
282
- elementId: i.id,
295
+ elementId: a.id,
283
296
  details: {
284
297
  source: s.source,
285
298
  elementId: d
@@ -287,11 +300,11 @@ var t = (t) => {
287
300
  });
288
301
  return;
289
302
  }
290
- let p = a.get(d);
291
- p && p !== u && n.push({
303
+ let p = i.get(d);
304
+ p && p !== u && r.push({
292
305
  code: "MISMATCHED_BINDING_SOURCE",
293
306
  message: `${f}, but "${d}" publishes its source as "${p}_${d}"`,
294
- elementId: i.id,
307
+ elementId: a.id,
295
308
  details: {
296
309
  source: s.source,
297
310
  elementId: d,
@@ -301,54 +314,54 @@ var t = (t) => {
301
314
  });
302
315
  });
303
316
  });
304
- }, C = () => {
305
- let t = new Set(Object.keys(i));
306
- Object.values(i).forEach((r) => {
307
- let i = r?.definition.interactions;
317
+ }, T = () => {
318
+ let t = new Set(Object.keys(a));
319
+ Object.values(a).forEach((n) => {
320
+ let i = n?.definition.interactions;
308
321
  if (!i) return;
309
322
  let a = Object.keys(i);
310
323
  Object.entries(i).forEach(([i, o]) => {
311
- e(i) || n.push({
324
+ e(i) || r.push({
312
325
  code: "INVALID_INTERACTION_ID",
313
- message: `Interaction "${i}" on element "${r.id}" has a name that must start with a letter, then letters, numbers, hyphens and underscores — a later step reads it as {{ ${i}.field }}`,
314
- elementId: r.id
315
- }), o.id !== i && n.push({
326
+ message: `Interaction "${i}" on element "${n.id}" has a name that must start with a letter, then letters, numbers, hyphens and underscores — a later step reads it as {{ ${i}.field }}`,
327
+ elementId: n.id
328
+ }), o.id !== i && r.push({
316
329
  code: "INTERACTION_ID_MISMATCH",
317
- message: `Interaction id "${o.id}" on element "${r.id}" doesn't match its key "${i}"`,
318
- elementId: r.id
330
+ message: `Interaction id "${o.id}" on element "${n.id}" doesn't match its key "${i}"`,
331
+ elementId: n.id
319
332
  }), ["beforeNode", "afterNode"].forEach((e) => {
320
333
  let t = o[e];
321
- t && !a.includes(t) && n.push({
334
+ t && !a.includes(t) && r.push({
322
335
  code: "BROKEN_FLOW_LINK",
323
- message: `Interaction "${o.id}" on element "${r.id}" names ${e} "${t}", which is not a node of this flow`,
324
- elementId: r.id
336
+ message: `Interaction "${o.id}" on element "${n.id}" names ${e} "${t}", which is not a node of this flow`,
337
+ elementId: n.id
325
338
  });
326
- }), o.flowId && !a.includes(o.flowId) && n.push({
339
+ }), o.flowId && !a.includes(o.flowId) && r.push({
327
340
  code: "BROKEN_FLOW_ID",
328
- message: `Interaction "${o.id}" on element "${r.id}" belongs to flow "${o.flowId}", which is not a node of this flow`,
329
- elementId: r.id
330
- }), o.type === "callback" && o.elementId && !t.has(o.elementId) && n.push({
341
+ message: `Interaction "${o.id}" on element "${n.id}" belongs to flow "${o.flowId}", which is not a node of this flow`,
342
+ elementId: n.id
343
+ }), o.type === "callback" && o.elementId && !t.has(o.elementId) && r.push({
331
344
  code: "UNRESOLVED_INTERACTION_TARGET",
332
- message: `Interaction "${o.id}" on element "${r.id}" runs against "${o.elementId}", but no element answers to that name`,
333
- elementId: r.id
345
+ message: `Interaction "${o.id}" on element "${n.id}" runs against "${o.elementId}", but no element answers to that name`,
346
+ elementId: n.id
334
347
  });
335
348
  });
336
349
  });
337
- }, w = () => {
338
- Object.values(i).forEach((e) => {
350
+ }, E = () => {
351
+ Object.values(a).forEach((e) => {
339
352
  if (!e || !e.definition.bindings) return;
340
353
  let t = /* @__PURE__ */ new Set();
341
- Object.entries(e.definition.bindings).forEach(([r, i]) => {
354
+ Object.entries(e.definition.bindings).forEach(([n, i]) => {
342
355
  (i ?? []).forEach((i) => {
343
356
  if (!i.id) {
344
- n.push({
357
+ r.push({
345
358
  code: "MISSING_BINDING_ID",
346
- message: `Element "${e.id}" has a ${r} binding to "${i.to}" with no id`,
359
+ message: `Element "${e.id}" has a ${n} binding to "${i.to}" with no id`,
347
360
  elementId: e.id
348
361
  });
349
362
  return;
350
363
  }
351
- t.has(i.id) && n.push({
364
+ t.has(i.id) && r.push({
352
365
  code: "DUPLICATE_BINDING_ID",
353
366
  message: `Element "${e.id}" has two bindings called "${i.id}"`,
354
367
  elementId: e.id,
@@ -360,22 +373,22 @@ var t = (t) => {
360
373
  };
361
374
  return { validate: (e) => {
362
375
  let { baseElementId: t } = e ?? {};
363
- return f() ? (p(), m(), h(), g(), _(), v(), y(t), b(), x(), S(e?.sourceTypes), w(), C(), {
364
- valid: n.length === 0,
365
- errors: n,
366
- warnings: r
376
+ return p() ? (m(), h(), g(), _(), v(), y(), b(), x(t), S(), C(), w(e?.sourceTypes), E(), T(), {
377
+ valid: r.length === 0,
378
+ errors: r,
379
+ warnings: i
367
380
  }) : {
368
381
  valid: !1,
369
- errors: n,
370
- warnings: r
382
+ errors: r,
383
+ warnings: i
371
384
  };
372
385
  } };
373
- }, n = (e, n) => t(e).validate(n), r = (e, t, r) => {
374
- let i = n(e, t);
386
+ }, r = (e, t) => n(e).validate(t), i = (e, t, n) => {
387
+ let i = r(e, t);
375
388
  if (!i.valid) {
376
- let e = `Invalid schema${r ? ` (${r})` : ""}: ${i.errors.map((e) => e.message).join("; ")}`;
389
+ let e = `Invalid schema${n ? ` (${n})` : ""}: ${i.errors.map((e) => e.message).join("; ")}`;
377
390
  throw Error(e);
378
391
  }
379
- }, i = (e, t) => n(e, t).valid;
392
+ }, a = (e, t) => r(e, t).valid;
380
393
  //#endregion
381
- export { r as assertSchemaValid, i as isSchemaValid, n as validateSchema };
394
+ export { i as assertSchemaValid, a as isSchemaValid, r as validateSchema };
@@ -0,0 +1,14 @@
1
+ import { Element, Style } from '@plitzi/sdk-shared';
2
+ /**
3
+ * Why an element's style applies to nothing, if it does not.
4
+ *
5
+ * An `apiContainer` whose `subType` is empty — its default, the builder's "Container Tag: None" — renders its
6
+ * children and no element of its own, so a class with rules, a variant or a style binding on it styles nothing: its
7
+ * children sit straight in the parent's layout, and the gap or padding written on the provider is not there. Found on
8
+ * real spaces as a counter card with no card, a sticky sidebar that did not stick, and page sections that lost the
9
+ * gap between them.
10
+ *
11
+ * Needs the style document: the builder and the authoring package both give every element a class of its own, rules
12
+ * or not, so a class name on its own says nothing. A provider with no tag is legal and often right.
13
+ */
14
+ export declare const styleWithoutTag: (element: Element, style: Pick<Style, "platform">) => string | undefined;
@@ -0,0 +1,10 @@
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;
7
+ 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
+ //#endregion
10
+ export { i as styleWithoutTag };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-schema",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist"
@@ -26,6 +26,10 @@
26
26
  "types": "./dist/helpers/schemaValidator.d.ts",
27
27
  "import": "./dist/helpers/schemaValidator.mjs"
28
28
  },
29
+ "./helpers/styleWithoutTag": {
30
+ "types": "./dist/helpers/styleWithoutTag.d.ts",
31
+ "import": "./dist/helpers/styleWithoutTag.mjs"
32
+ },
29
33
  "./index": {
30
34
  "types": "./dist/index.d.ts",
31
35
  "import": "./dist/index.mjs"
@@ -45,14 +49,14 @@
45
49
  },
46
50
  "dependencies": {
47
51
  "@plitzi/plitzi-ui": "^1.6.21",
48
- "@plitzi/sdk-shared": "0.36.1",
49
- "@plitzi/sdk-style": "0.36.1",
52
+ "@plitzi/sdk-shared": "0.37.0",
53
+ "@plitzi/sdk-style": "0.37.0",
50
54
  "prop-types": "^15.8.1"
51
55
  },
52
56
  "devDependencies": {
53
57
  "eslint": "^9.39.5",
54
58
  "typescript": "^6.0.3",
55
59
  "vite": "^8.3.0",
56
- "vitest": "^5.0.0"
60
+ "vitest": "^5.0.1"
57
61
  }
58
62
  }