nodality 1.1.13 → 1.1.15

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.
Files changed (194) hide show
  1. package/bin/mcp-server.mjs +13 -3
  2. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  3. package/dist/animator.esm.js.LICENSE.txt +1 -1
  4. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  5. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  6. package/dist/base.cjs.js.LICENSE.txt +1 -1
  7. package/dist/base.esm.js.LICENSE.txt +1 -1
  8. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  9. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  10. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  11. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  12. package/dist/bundle.umd.js +1 -1
  13. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  14. package/dist/button.cjs.js.LICENSE.txt +1 -1
  15. package/dist/button.esm.js.LICENSE.txt +1 -1
  16. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  17. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  18. package/dist/center.cjs.js.LICENSE.txt +1 -1
  19. package/dist/center.esm.js.LICENSE.txt +1 -1
  20. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  21. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  22. package/dist/code.cjs.js.LICENSE.txt +1 -1
  23. package/dist/code.esm.js.LICENSE.txt +1 -1
  24. package/dist/container.cjs.js.LICENSE.txt +1 -1
  25. package/dist/container.esm.js.LICENSE.txt +1 -1
  26. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  27. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  28. package/dist/designer.cjs.js +1 -1
  29. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  30. package/dist/designer.esm.js +1 -1
  31. package/dist/designer.esm.js.LICENSE.txt +1 -1
  32. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  33. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  34. package/dist/finalresult.esm.js +1 -1
  35. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  36. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  37. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  38. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  39. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  40. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  41. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  42. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  43. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  44. package/dist/free.cjs.js.LICENSE.txt +1 -1
  45. package/dist/free.esm.js.LICENSE.txt +1 -1
  46. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  47. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  48. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  49. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  50. package/dist/image.cjs.js.LICENSE.txt +1 -1
  51. package/dist/image.esm.js.LICENSE.txt +1 -1
  52. package/dist/index.cjs.js +1 -1
  53. package/dist/index.cjs.js.LICENSE.txt +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.esm.js +1 -1
  56. package/dist/index.esm.js.LICENSE.txt +1 -1
  57. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  58. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  59. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  60. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  61. package/dist/link.cjs.js.LICENSE.txt +1 -1
  62. package/dist/link.esm.js.LICENSE.txt +1 -1
  63. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  64. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  65. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  66. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  67. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  68. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  69. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  70. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  71. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  72. package/dist/picker.esm.js.LICENSE.txt +1 -1
  73. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  74. package/dist/progress.esm.js.LICENSE.txt +1 -1
  75. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  76. package/dist/radio.esm.js.LICENSE.txt +1 -1
  77. package/dist/range.cjs.js.LICENSE.txt +1 -1
  78. package/dist/range.esm.js.LICENSE.txt +1 -1
  79. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  80. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  81. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  82. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  83. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  84. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  85. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  86. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  87. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  88. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  89. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  90. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  91. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  92. package/dist/stack.esm.js.LICENSE.txt +1 -1
  93. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  94. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  95. package/dist/table.cjs.js.LICENSE.txt +1 -1
  96. package/dist/table.esm.js.LICENSE.txt +1 -1
  97. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  98. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  99. package/dist/text.cjs.js.LICENSE.txt +1 -1
  100. package/dist/text.esm.js.LICENSE.txt +1 -1
  101. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  102. package/dist/theme.esm.js.LICENSE.txt +1 -1
  103. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  104. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  105. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  106. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  107. package/dist/video.cjs.js.LICENSE.txt +1 -1
  108. package/dist/video.esm.js.LICENSE.txt +1 -1
  109. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  110. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  111. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  112. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  113. package/layout/animator.js +1 -1
  114. package/layout/audio.js +1 -1
  115. package/layout/audionew.js +1 -1
  116. package/layout/base.js +1 -1
  117. package/layout/beta-desktop-bar.js +1 -1
  118. package/layout/beta-mobile-bar.js +1 -1
  119. package/layout/button.js +1 -1
  120. package/layout/center.js +1 -1
  121. package/layout/checkbox.js +1 -1
  122. package/layout/circle.js +1 -1
  123. package/layout/code.js +1 -1
  124. package/layout/container.js +1 -1
  125. package/layout/dropdown-2025.js +1 -1
  126. package/layout/flex-card.js +1 -1
  127. package/layout/flex-grid.js +1 -1
  128. package/layout/flex-row.js +1 -1
  129. package/layout/form-components/custom.js +1 -1
  130. package/layout/form-components/data-list.js +1 -1
  131. package/layout/form-components/floating-input.js +1 -1
  132. package/layout/form-components/form-all.js +1 -1
  133. package/layout/form-components/form.js +1 -1
  134. package/layout/form-components/image-picker.js +1 -1
  135. package/layout/form-components/picker.js +1 -1
  136. package/layout/form-components/radio.js +1 -1
  137. package/layout/form-components/radiogroup.js +1 -1
  138. package/layout/form-components/range.js +1 -1
  139. package/layout/free.js +1 -1
  140. package/layout/grid-switcher.js +1 -1
  141. package/layout/grid.js +1 -1
  142. package/layout/horizontal-scroller.js +1 -1
  143. package/layout/image.js +1 -1
  144. package/layout/index.js +1 -1
  145. package/layout/link.js +1 -1
  146. package/layout/list.js +1 -1
  147. package/layout/meta-adder.js +1 -1
  148. package/layout/modal-2025.js +1 -1
  149. package/layout/morph.js +1 -1
  150. package/layout/multiswitcher.js +1 -1
  151. package/layout/nav-bar.js +1 -1
  152. package/layout/nav-factor/custom-div.js +1 -1
  153. package/layout/new-nav-bar.js +1 -1
  154. package/layout/polygon.js +1 -1
  155. package/layout/prerender-site.js +1 -1
  156. package/layout/prerender.js +1 -1
  157. package/layout/progress.js +1 -1
  158. package/layout/row.js +1 -1
  159. package/layout/scroll-video.js +1 -1
  160. package/layout/side-bar.js +1 -1
  161. package/layout/side-nav-bar.js +1 -1
  162. package/layout/simple-bar.js +1 -1
  163. package/layout/slider-2025.js +1 -1
  164. package/layout/spacer.js +1 -1
  165. package/layout/stack.js +1 -1
  166. package/layout/svg.js +1 -1
  167. package/layout/switcher.js +1 -1
  168. package/layout/table.js +1 -1
  169. package/layout/text-field.js +1 -1
  170. package/layout/text.js +1 -1
  171. package/layout/ulist.js +1 -1
  172. package/layout/video.js +1 -1
  173. package/layout/wrap.js +1 -1
  174. package/layout/zoom-card.js +1 -1
  175. package/lib/card-getter.js +1 -1
  176. package/lib/codegen.js +1 -1
  177. package/lib/data.js +1 -1
  178. package/lib/designer.js +1 -1
  179. package/lib/element-mapper.js +1 -1
  180. package/lib/keyframe-animation.js +1 -1
  181. package/lib/link-getter.js +1 -1
  182. package/lib/morph-node.js +53 -7
  183. package/lib/raster-inspect.js +1 -1
  184. package/lib/raster-ops.js +1 -1
  185. package/lib/raster-presets.js +1 -1
  186. package/lib/scroll-video.js +1 -1
  187. package/lib/seo.js +1 -1
  188. package/lib/stacker.js +1 -1
  189. package/lib/suggest.js +1 -1
  190. package/lib/theme.js +1 -1
  191. package/lib/transform-anim.js +1 -1
  192. package/lib/transition.js +1 -1
  193. package/lib/validate-nodes.js +118 -31
  194. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.1.13
2
+ * nodality v1.1.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -36,10 +36,20 @@ import { didYouMean, suggest } from "./suggest.js";
36
36
  * the runtime, which is why it has to be reported here.
37
37
  */
38
38
  const MORPH_FIELDS = [
39
- "op", "target", "from", "to",
39
+ "op", "target", "from", "to", "chain",
40
40
  "effect", "duration", "back", "live", "fade",
41
41
  ];
42
42
 
43
+ /**
44
+ * Every key an entry in `chain` reads. `from` and `to` make the edge; the
45
+ * rest are the node's own settings, overridable per edge — which is why
46
+ * this list is MORPH_FIELDS without `op`, `target` or `chain`, an edge
47
+ * being able to carry neither an op nor a nested chain of its own.
48
+ */
49
+ const EDGE_FIELDS = [
50
+ "from", "to", "effect", "duration", "back", "live", "fade",
51
+ ];
52
+
43
53
  /** Node families, told apart exactly the way the runtime tells them apart. */
44
54
  const familyOf = (node) => {
45
55
  if (!node || typeof node !== "object") return "invalid";
@@ -96,8 +106,13 @@ export function validateNodes(nodes, elements) {
96
106
  push("BAD_TARGET", at, t, [], ["an element id"]);
97
107
  return;
98
108
  }
99
- // Ids are written bare in E and may be written with a leading
100
- // "#" in a node; accept both rather than inventing a rule.
109
+ // An id may be written bare ("home") or in selector form
110
+ // ("#home"). Both are accepted here because both are accepted
111
+ // by the runtime — `bareId` in morph-node.js reduces them to
112
+ // one key before anything is resolved. The two must agree: for
113
+ // a while this check tolerated "#home" while the runtime did
114
+ // not, so the validator passed a node that silently never
115
+ // morphed, which is the exact failure it exists to prevent.
101
116
  const bare = t.startsWith("#") ? t.slice(1) : t;
102
117
  if (idsKnown && !ids.includes(bare)) {
103
118
  push("UNKNOWN_TARGET", at, t, suggest(bare, ids), ids);
@@ -121,37 +136,109 @@ export function validateNodes(nodes, elements) {
121
136
  if (family === "design") return; // design nodes carry their own vocabulary
122
137
 
123
138
  if (family === "morph") {
124
- // The morph node's own contract. `from` and `to` are what make
125
- // it a morph; without them the node is inert.
126
- if (typeof node.from !== "string") {
127
- push("MISSING_FIELD", `${path}.from`, node.from, [],
128
- ["the id of the element that morphs"]);
129
- } else {
130
- checkTargets(node.from, `${path}.from`);
139
+ // `from` and `to` are what make a hop; without them it is
140
+ // inert. Factored out because a chain edge has exactly the
141
+ // same two ends as a single-hop node, and the diagnostics
142
+ // should not differ by where the edge was written.
143
+ const checkEnds = (o, at) => {
144
+ if (typeof o.from !== "string") {
145
+ push("MISSING_FIELD", `${at}.from`, o.from, [],
146
+ ["the id of the element that morphs"]);
147
+ } else {
148
+ checkTargets(o.from, `${at}.from`);
149
+ }
150
+ if (o.to === undefined) {
151
+ push("MISSING_FIELD", `${at}.to`, undefined, [],
152
+ ["[\"id\", ...]", "{ \"Link label\": \"id\" }"]);
153
+ } else if (Array.isArray(o.to)) {
154
+ checkTargets(o.to, `${at}.to`);
155
+ } else if (o.to && typeof o.to === "object") {
156
+ for (const label in o.to) {
157
+ checkTargets(o.to[label], `${at}.to.${label}`);
158
+ }
159
+ } else {
160
+ push("BAD_FIELD", `${at}.to`, o.to, [],
161
+ ["an array of ids, or a map of link label to id"]);
162
+ }
163
+ };
164
+
165
+ // The settings, which are hoisted defaults on the node and
166
+ // overrides on an edge, so both are checked the same way.
167
+ // `back` and `live` are compared against `false`/`true` by
168
+ // identity in the runtime, which makes a STRING the dangerous
169
+ // mistake: `live: "true"` is not `=== true`, so the live
170
+ // backend silently stays off and the page still works, on the
171
+ // other backend, looking as though the flag took.
172
+ const checkOpts = (o, at) => {
173
+ if (o.duration !== undefined &&
174
+ (typeof o.duration !== "number" || !(o.duration > 0))) {
175
+ push("BAD_VALUE", `${at}.duration`, o.duration, [],
176
+ ["a positive number of milliseconds"]);
177
+ }
178
+ if (o.effect !== undefined &&
179
+ typeof o.effect !== "string" && !Array.isArray(o.effect)) {
180
+ push("BAD_FIELD", `${at}.effect`, o.effect, [],
181
+ ["a preset name, or an inline array of raster nodes"]);
182
+ }
183
+ for (const flag of ["back", "live"]) {
184
+ if (o[flag] !== undefined && typeof o[flag] !== "boolean") {
185
+ push("BAD_VALUE", `${at}.${flag}`, o[flag], [],
186
+ ["true", "false"]);
187
+ }
188
+ }
189
+ if (o.fade !== undefined && typeof o.fade !== "string") {
190
+ push("BAD_FIELD", `${at}.fade`, o.fade, [],
191
+ ["a fade mode, as a string"]);
192
+ }
193
+ };
194
+
195
+ // A node is either one hop or a chain, and the runtime decides
196
+ // by looking at `chain` FIRST: `normalizeEdges` takes a
197
+ // non-empty chain and never reads node-level `from`/`to`. The
198
+ // check mirrors that order, so what passes here is what runs.
199
+ const chained = Array.isArray(node.chain) && node.chain.length > 0;
200
+
201
+ if (node.chain !== undefined && !Array.isArray(node.chain)) {
202
+ push("BAD_FIELD", `${path}.chain`, node.chain, [],
203
+ ["an array of edges: [{ from, to }, ...]"]);
204
+ } else if (Array.isArray(node.chain) && !node.chain.length) {
205
+ // The runtime does not reject this — it falls back to the
206
+ // single-hop fields — but it is never what was meant.
207
+ push("BAD_VALUE", `${path}.chain`, node.chain, [],
208
+ ["at least one edge, or no `chain` key at all"]);
131
209
  }
132
- if (node.to === undefined) {
133
- push("MISSING_FIELD", `${path}.to`, undefined, [],
134
- ["[\"id\", ...]", "{ \"Link label\": \"id\" }"]);
135
- } else if (Array.isArray(node.to)) {
136
- checkTargets(node.to, `${path}.to`);
137
- } else if (node.to && typeof node.to === "object") {
138
- for (const label in node.to) {
139
- checkTargets(node.to[label], `${path}.to.${label}`);
210
+
211
+ checkOpts(node, path);
212
+
213
+ if (chained) {
214
+ // `from`/`to` written BESIDE a chain are dead, not merged.
215
+ // Worth reporting precisely because the page still works,
216
+ // using the chain, so the ignored pair looks as though it
217
+ // took effect.
218
+ for (const key of ["from", "to"]) {
219
+ if (node[key] !== undefined) {
220
+ push("IGNORED_FIELD", `${path}.${key}`, node[key], [],
221
+ ["remove it, or drop `chain` and keep the single hop"]);
222
+ }
140
223
  }
224
+ node.chain.forEach((edge, j) => {
225
+ const at = `${path}.chain[${j}]`;
226
+ if (!edge || typeof edge !== "object" || Array.isArray(edge)) {
227
+ push("BAD_FIELD", at, edge, [], ["{ from, to }"]);
228
+ return;
229
+ }
230
+ checkEnds(edge, at);
231
+ checkOpts(edge, at);
232
+ for (const key in edge) {
233
+ if (EDGE_FIELDS.includes(key)) continue;
234
+ push("UNKNOWN_PARAM", `${at}.${key}`, key,
235
+ suggest(key, EDGE_FIELDS), EDGE_FIELDS);
236
+ }
237
+ });
141
238
  } else {
142
- push("BAD_FIELD", `${path}.to`, node.to, [],
143
- ["an array of ids, or a map of link label to id"]);
144
- }
145
- if (node.duration !== undefined &&
146
- (typeof node.duration !== "number" || !(node.duration > 0))) {
147
- push("BAD_VALUE", `${path}.duration`, node.duration, [],
148
- ["a positive number of milliseconds"]);
149
- }
150
- if (node.effect !== undefined &&
151
- typeof node.effect !== "string" && !Array.isArray(node.effect)) {
152
- push("BAD_FIELD", `${path}.effect`, node.effect, [],
153
- ["a preset name, or an inline array of raster nodes"]);
239
+ checkEnds(node, path);
154
240
  }
241
+
155
242
  // Unknown keys, checked here for the same reason they are
156
243
  // checked on a raster node: a morph silently ignores what it
157
244
  // does not recognise, so `duraton: 900` produces a transition
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",