nodality 1.3.0 → 1.3.1

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 (206) hide show
  1. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  2. package/dist/animator.esm.js.LICENSE.txt +1 -1
  3. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  4. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  5. package/dist/base.cjs.js.LICENSE.txt +1 -1
  6. package/dist/base.esm.js.LICENSE.txt +1 -1
  7. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  8. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  9. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  10. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  11. package/dist/bundle.umd.js +1 -1
  12. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  13. package/dist/button.cjs.js.LICENSE.txt +1 -1
  14. package/dist/button.esm.js.LICENSE.txt +1 -1
  15. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  16. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  17. package/dist/center.cjs.js.LICENSE.txt +1 -1
  18. package/dist/center.esm.js.LICENSE.txt +1 -1
  19. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  20. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  21. package/dist/code.cjs.js.LICENSE.txt +1 -1
  22. package/dist/code.esm.js.LICENSE.txt +1 -1
  23. package/dist/container.cjs.js.LICENSE.txt +1 -1
  24. package/dist/container.esm.js.LICENSE.txt +1 -1
  25. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  26. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  27. package/dist/designer.cjs.js +1 -1
  28. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  29. package/dist/designer.esm.js +1 -1
  30. package/dist/designer.esm.js.LICENSE.txt +1 -1
  31. package/dist/element-mapper.cjs.js +1 -1
  32. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  33. package/dist/element-mapper.esm.js +1 -1
  34. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  35. package/dist/finalresult.esm.js +1 -1
  36. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  37. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  38. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  39. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  40. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  41. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  42. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  43. package/dist/floating-input.cjs.js +1 -1
  44. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  45. package/dist/floating-input.esm.js +1 -1
  46. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  47. package/dist/free.cjs.js.LICENSE.txt +1 -1
  48. package/dist/free.esm.js.LICENSE.txt +1 -1
  49. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  50. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  51. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  52. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  53. package/dist/image.cjs.js.LICENSE.txt +1 -1
  54. package/dist/image.esm.js.LICENSE.txt +1 -1
  55. package/dist/index.cjs.js +1 -1
  56. package/dist/index.cjs.js.LICENSE.txt +1 -1
  57. package/dist/index.d.ts +1 -1
  58. package/dist/index.esm.js +1 -1
  59. package/dist/index.esm.js.LICENSE.txt +1 -1
  60. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  61. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  62. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  63. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  64. package/dist/link.cjs.js.LICENSE.txt +1 -1
  65. package/dist/link.esm.js.LICENSE.txt +1 -1
  66. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  67. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  68. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  69. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  70. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  71. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  72. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  73. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  74. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  75. package/dist/picker.esm.js.LICENSE.txt +1 -1
  76. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  77. package/dist/progress.esm.js.LICENSE.txt +1 -1
  78. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  79. package/dist/radio.esm.js.LICENSE.txt +1 -1
  80. package/dist/range.cjs.js.LICENSE.txt +1 -1
  81. package/dist/range.esm.js.LICENSE.txt +1 -1
  82. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  83. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  84. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  85. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  86. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  87. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  88. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  89. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  90. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  91. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  92. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  93. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  94. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  95. package/dist/stack.esm.js.LICENSE.txt +1 -1
  96. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  97. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  98. package/dist/table.cjs.js.LICENSE.txt +1 -1
  99. package/dist/table.esm.js.LICENSE.txt +1 -1
  100. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  101. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  102. package/dist/text.cjs.js.LICENSE.txt +1 -1
  103. package/dist/text.esm.js.LICENSE.txt +1 -1
  104. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  105. package/dist/theme.esm.js.LICENSE.txt +1 -1
  106. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  107. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  108. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  109. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  110. package/dist/video.cjs.js.LICENSE.txt +1 -1
  111. package/dist/video.esm.js.LICENSE.txt +1 -1
  112. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  113. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  114. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  115. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  116. package/layout/animator.js +1 -1
  117. package/layout/audio.js +1 -1
  118. package/layout/audionew.js +1 -1
  119. package/layout/base.js +1 -1
  120. package/layout/beta-desktop-bar.js +1 -1
  121. package/layout/beta-mobile-bar.js +1 -1
  122. package/layout/button.js +1 -1
  123. package/layout/center.js +1 -1
  124. package/layout/checkbox.js +1 -1
  125. package/layout/circle.js +1 -1
  126. package/layout/code.js +1 -1
  127. package/layout/container.js +1 -1
  128. package/layout/dropdown-2025.js +1 -1
  129. package/layout/flex-card.js +1 -1
  130. package/layout/flex-grid.js +1 -1
  131. package/layout/flex-row.js +1 -1
  132. package/layout/form-components/custom.js +1 -1
  133. package/layout/form-components/data-list.js +1 -1
  134. package/layout/form-components/floating-input.js +12 -6
  135. package/layout/form-components/form-all.js +1 -1
  136. package/layout/form-components/form.js +1 -1
  137. package/layout/form-components/image-picker.js +1 -1
  138. package/layout/form-components/picker.js +1 -1
  139. package/layout/form-components/radio.js +1 -1
  140. package/layout/form-components/radiogroup.js +1 -1
  141. package/layout/form-components/range.js +1 -1
  142. package/layout/free.js +1 -1
  143. package/layout/grid-switcher.js +1 -1
  144. package/layout/grid.js +1 -1
  145. package/layout/horizontal-scroller.js +1 -1
  146. package/layout/image.js +1 -1
  147. package/layout/index.js +1 -1
  148. package/layout/link.js +1 -1
  149. package/layout/list.js +1 -1
  150. package/layout/meta-adder.js +1 -1
  151. package/layout/modal-2025.js +1 -1
  152. package/layout/morph.js +1 -1
  153. package/layout/multiswitcher.js +1 -1
  154. package/layout/nav-bar.js +1 -1
  155. package/layout/nav-factor/custom-div.js +1 -1
  156. package/layout/new-nav-bar.js +1 -1
  157. package/layout/polygon.js +1 -1
  158. package/layout/prerender-site.js +1 -1
  159. package/layout/prerender.js +1 -1
  160. package/layout/progress.js +1 -1
  161. package/layout/row.js +1 -1
  162. package/layout/scroll-video.js +1 -1
  163. package/layout/side-bar.js +1 -1
  164. package/layout/side-nav-bar.js +1 -1
  165. package/layout/simple-bar.js +1 -1
  166. package/layout/slider-2025.js +1 -1
  167. package/layout/spacer.js +1 -1
  168. package/layout/stack.js +1 -1
  169. package/layout/svg.js +1 -1
  170. package/layout/switcher.js +1 -1
  171. package/layout/table.js +1 -1
  172. package/layout/text-field.js +1 -1
  173. package/layout/text.js +1 -1
  174. package/layout/ulist.js +1 -1
  175. package/layout/video.js +1 -1
  176. package/layout/wrap.js +1 -1
  177. package/layout/zoom-card.js +1 -1
  178. package/lib/agent-surface.js +1 -1
  179. package/lib/card-getter.js +1 -1
  180. package/lib/check-page.js +130 -21
  181. package/lib/codegen.js +1 -1
  182. package/lib/data.js +1 -1
  183. package/lib/designer.js +1 -1
  184. package/lib/element-mapper.js +11 -3
  185. package/lib/element-params.generated.js +2 -2
  186. package/lib/keyframe-animation.js +1 -1
  187. package/lib/link-getter.js +1 -1
  188. package/lib/morph-node.js +1 -1
  189. package/lib/normalize-spec.js +1 -1
  190. package/lib/parse-html.js +1 -1
  191. package/lib/raster-inspect.js +1 -1
  192. package/lib/raster-ops.js +1 -1
  193. package/lib/raster-presets.js +1 -1
  194. package/lib/resolve-refs.js +1 -1
  195. package/lib/scroll-video.js +1 -1
  196. package/lib/seo.js +1 -1
  197. package/lib/stacker.js +1 -1
  198. package/lib/suggest.js +1 -1
  199. package/lib/theme.js +1 -1
  200. package/lib/transform-anim.js +1 -1
  201. package/lib/transition.js +1 -1
  202. package/lib/validate-nodes.js +52 -1
  203. package/lib/webmcp-adapter.js +1 -1
  204. package/package.json +1 -1
  205. package/schema.json +763 -1
  206. package/scripts/generate-schema.mjs +9 -1
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -23,7 +23,13 @@ let label = document.createElement("label");
23
23
  label.style.position = "relative";
24
24
  label.style.display = "block";
25
25
 
26
- let node = document.createTextNode(obj.title);
26
+ // `createTextNode(undefined)` is the STRING "undefined", and so is
27
+ // `placeholder = undefined`. A field given no title therefore rendered the
28
+ // word "undefined" as its visible label, its placeholder AND its accessible
29
+ // name — a page that says "undefined" to a visitor, with validate_nodes
30
+ // reporting ok. An absent title has to render as absent.
31
+ const title = obj.title == null ? "" : String(obj.title);
32
+ let node = document.createTextNode(title);
27
33
  label.appendChild(node);
28
34
  label.style.opacity = 0;
29
35
 
@@ -39,7 +45,7 @@ var input = document.createElement("input");
39
45
 
40
46
 
41
47
  input.style.position = "relative";
42
- input.placeholder = obj.title;
48
+ input.placeholder = title;
43
49
 
44
50
  // width:100% plus horizontal padding is 100% PLUS the padding under the
45
51
  // default content-box, so a full-width field pushed the page 20px wider
@@ -50,8 +56,8 @@ var input = document.createElement("input");
50
56
  // A placeholder is not an accessible name — it disappears the moment
51
57
  // anyone types, and assistive technology is not required to announce it.
52
58
  // The visible label is right there, so use it.
53
- if (obj.title && !input.getAttribute("aria-label")) {
54
- input.setAttribute("aria-label", obj.title);
59
+ if (title && !input.getAttribute("aria-label")) {
60
+ input.setAttribute("aria-label", title);
55
61
  }
56
62
 
57
63
 
@@ -94,7 +100,7 @@ input.addEventListener("focus", () => {
94
100
 
95
101
  input.addEventListener("focusout", () => {
96
102
  label.style.transform = "scale(1.0)";
97
- input.placeholder = obj.title;
103
+ input.placeholder = title;
98
104
  label.style.opacity = "0.0";
99
105
  });
100
106
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/free.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/grid.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/image.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/link.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/list.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/morph.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/nav-bar.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/polygon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/row.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/spacer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/stack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/svg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/table.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/text.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/ulist.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/video.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/wrap.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/check-page.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.0
2
+ * nodality v1.3.1
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -45,18 +45,59 @@ export const DEFAULT_VIEWPORTS = [
45
45
  */
46
46
  const IN_PAGE = () => {
47
47
  const out = [];
48
- const add = (code, path, got, detail) => out.push({ code, path, got, detail });
49
48
 
50
- /** A short, stable selector for a node, so a report can be acted on. */
49
+ /**
50
+ * A PAGE-UNIQUE selector, so a report can be acted on.
51
+ *
52
+ * The first version was parent-relative and returned `input` for any input
53
+ * that was the only one under its own div. A form with three unlabelled
54
+ * fields produced the string "input" three times, the merge at the bottom
55
+ * of this file keyed on it, and three findings collapsed into one row
56
+ * reading `viewport: "mobile, mobile, mobile"`. The agent was told neither
57
+ * which control nor that there were three — Tier 7 watched a model fail to
58
+ * repair exactly that. A path walks to the nearest id, or to the body.
59
+ */
51
60
  const sel = (el) => {
52
61
  if (!el || !el.tagName) return "?";
53
- if (el.id) return `#${CSS.escape(el.id)}`;
54
- const tag = el.tagName.toLowerCase();
55
- const cls = (el.getAttribute("class") || "").trim().split(/\s+/).filter(Boolean)[0];
56
- const base = cls ? `${tag}.${CSS.escape(cls)}` : tag;
57
- const sibs = el.parentElement
58
- ? [...el.parentElement.children].filter((n) => n.tagName === el.tagName) : [];
59
- return sibs.length > 1 ? `${base}:nth-of-type(${sibs.indexOf(el) + 1})` : base;
62
+ const seg = (n) => {
63
+ const tag = n.tagName.toLowerCase();
64
+ const sibs = n.parentElement
65
+ ? [...n.parentElement.children].filter((x) => x.tagName === n.tagName) : [];
66
+ return sibs.length > 1 ? `${tag}:nth-of-type(${sibs.indexOf(n) + 1})` : tag;
67
+ };
68
+ const parts = [];
69
+ for (let n = el; n && n !== document.body; n = n.parentElement) {
70
+ if (n.id) { parts.unshift(`#${CSS.escape(n.id)}`); break; }
71
+ parts.unshift(seg(n));
72
+ }
73
+ return parts.join(" > ") || el.tagName.toLowerCase();
74
+ };
75
+
76
+ /**
77
+ * The SPEC node an element came from, when the page carries annotation.
78
+ * A selector says where in the DOM; this says which descriptor to edit,
79
+ * which is the thing an agent repairing a spec actually needs. Free when
80
+ * the annotation is there, null when it is not — it is opt-in, and the
81
+ * attribute name is spelled out here because this function crosses into
82
+ * the browser as a string and cannot import `SPEC_ATTR`. A test pins the
83
+ * two together.
84
+ */
85
+ const nod = (el) => {
86
+ for (let n = el; n && n !== document.body; n = n.parentElement) {
87
+ const v = n.getAttribute && n.getAttribute("data-nod");
88
+ if (v) return v;
89
+ }
90
+ return null;
91
+ };
92
+
93
+ // `target` is the element itself, so a finding can carry both. A string is
94
+ // passed for findings about the document rather than about an element.
95
+ const add = (code, target, got, detail) => {
96
+ const isEl = !!(target && target.tagName);
97
+ out.push({
98
+ code, path: isEl ? sel(target) : String(target), got, detail,
99
+ nod: isEl ? nod(target) : null,
100
+ });
60
101
  };
61
102
 
62
103
  const all = [...document.body.querySelectorAll("*")];
@@ -78,7 +119,7 @@ const IN_PAGE = () => {
78
119
  // 2. An element sticking out past the right edge. Left/top are not
79
120
  // checked: off-canvas menus and slide-in panels park there legitimately.
80
121
  if (r.right > vw + 1 && cs.position !== "fixed") {
81
- add("ELEMENT_OVERFLOWS_VIEWPORT", sel(el), Math.round(r.right),
122
+ add("ELEMENT_OVERFLOWS_VIEWPORT", el, Math.round(r.right),
82
123
  `extends ${Math.round(r.right - vw)}px past the ${vw}px viewport`);
83
124
  }
84
125
 
@@ -87,7 +128,7 @@ const IN_PAGE = () => {
87
128
  if ((cs.overflow === "hidden" || cs.overflowY === "hidden") &&
88
129
  el.scrollHeight > el.clientHeight + 2 && el.clientHeight > 0 &&
89
130
  (el.textContent || "").trim().length > 0) {
90
- add("CONTENT_CLIPPED", sel(el), el.scrollHeight,
131
+ add("CONTENT_CLIPPED", el, el.scrollHeight,
91
132
  `content is ${el.scrollHeight}px in a ${el.clientHeight}px box with overflow hidden`);
92
133
  }
93
134
 
@@ -95,24 +136,24 @@ const IN_PAGE = () => {
95
136
  // too, which is what this library's cards emit.
96
137
  const tag = el.tagName.toLowerCase();
97
138
  if (tag === "img" && !el.hasAttribute("alt")) {
98
- add("IMAGE_WITHOUT_ALT", sel(el), el.getAttribute("src") || "", "no alt attribute");
139
+ add("IMAGE_WITHOUT_ALT", el, el.getAttribute("src") || "", "no alt attribute");
99
140
  }
100
141
  if (el.getAttribute("role") === "img" && !el.getAttribute("aria-label")) {
101
- add("IMAGE_WITHOUT_ALT", sel(el), "role=img", "role=img with no aria-label");
142
+ add("IMAGE_WITHOUT_ALT", el, "role=img", "role=img with no aria-label");
102
143
  }
103
144
 
104
145
  // 5. A target too small to hit. 24px is the WCAG 2.2 AA minimum.
105
146
  const interactive = tag === "a" || tag === "button" ||
106
147
  (tag === "input" && el.type !== "hidden") || el.getAttribute("role") === "button";
107
148
  if (interactive && r.width > 0 && (r.width < 24 || r.height < 24)) {
108
- add("TAP_TARGET_TOO_SMALL", sel(el), `${Math.round(r.width)}x${Math.round(r.height)}`,
149
+ add("TAP_TARGET_TOO_SMALL", el, `${Math.round(r.width)}x${Math.round(r.height)}`,
109
150
  "below the 24x24 minimum");
110
151
  }
111
152
 
112
153
  // 6. A control with nothing to announce.
113
154
  if (interactive && !(el.textContent || "").trim() &&
114
155
  !el.getAttribute("aria-label") && !el.querySelector("img[alt]:not([alt=''])")) {
115
- add("CONTROL_WITHOUT_LABEL", sel(el), tag, "no text and no accessible name");
156
+ add("CONTROL_WITHOUT_LABEL", el, tag, "no text and no accessible name");
116
157
  }
117
158
  }
118
159
 
@@ -123,7 +164,7 @@ const IN_PAGE = () => {
123
164
  for (const h of heads) {
124
165
  const lvl = Number(h.tagName[1]);
125
166
  if (prev && lvl > prev + 1) {
126
- add("HEADING_LEVEL_SKIPPED", sel(h), h.tagName,
167
+ add("HEADING_LEVEL_SKIPPED", h, h.tagName,
127
168
  `h${prev} is followed by ${h.tagName.toLowerCase()}`);
128
169
  }
129
170
  prev = lvl;
@@ -163,19 +204,83 @@ const IN_PAGE = () => {
163
204
  const large = size >= 24 || (size >= 18.66 && Number(cs.fontWeight) >= 700);
164
205
  const need = large ? 3 : 4.5;
165
206
  if (ratio < need) {
166
- add("LOW_CONTRAST", sel(el), `${ratio.toFixed(2)}:1`,
207
+ add("LOW_CONTRAST", el, `${ratio.toFixed(2)}:1`,
167
208
  `needs ${need}:1 at ${Math.round(size)}px`);
168
209
  }
169
210
  }
170
211
  return out;
171
212
  };
172
213
 
214
+ /**
215
+ * How to repair each finding, in THIS library's vocabulary.
216
+ *
217
+ * `suggestions` and `valid` are in the finding shape because `validate_nodes`
218
+ * puts repairs there, and check_page left both empty on every finding it has
219
+ * ever emitted — a field that is always blank is a field an agent learns to
220
+ * ignore. Tier 7 measured the cost. Told only "54x23, below the 24x24
221
+ * minimum", the strong model guessed `keySet: {key, value}` and was right, the
222
+ * weaker one invented `keySet: {"--tap-target-size": "44px"}` and was wrong,
223
+ * and the page stayed broken through the repair turn. Naming the element was
224
+ * not enough; the report has to name the edit.
225
+ *
226
+ * Kept deliberately short. These are the repair that fits the overwhelming
227
+ * majority of cases, not a treatment of every way a page can be wrong — an
228
+ * agent that needs more has `npx nodality schema <type>`.
229
+ */
230
+ const REPAIRS = {
231
+ TAP_TARGET_TOO_SMALL: {
232
+ suggestions: [`keySet: { key: "min-height", value: "44px" }`,
233
+ `keySet: { key: "min-width", value: "44px" }`,
234
+ `pad: [{ a: 12 }]`],
235
+ valid: ["at least 24x24 CSS pixels, per WCAG 2.2 AA target size (minimum)"],
236
+ },
237
+ CONTROL_WITHOUT_LABEL: {
238
+ suggestions: [`label: "<what the field is for>" (labelInput)`,
239
+ `text: "<what the control does>" (button, a)`],
240
+ valid: ["every control needs a name assistive technology can announce; " +
241
+ "a placeholder is not one"],
242
+ },
243
+ IMAGE_WITHOUT_ALT: {
244
+ suggestions: [`alt: "<what the image shows>"`,
245
+ `alt: "" if it is decorative and repeats nearby text`],
246
+ valid: ["img takes `alt`; a background image takes it too and becomes aria-label"],
247
+ },
248
+ LOW_CONTRAST: {
249
+ suggestions: [`color: "<a darker or lighter text colour>"`,
250
+ `background: "<a background with more separation>"`],
251
+ valid: ["4.5:1 for body text, 3:1 at 24px or at 18.66px bold"],
252
+ },
253
+ HEADING_LEVEL_SKIPPED: {
254
+ suggestions: [`tag: "h<the next level down>"`],
255
+ valid: ["`tag` sets the heading level independently of the size scale, " +
256
+ "so the outline can be correct without changing the design"],
257
+ },
258
+ ELEMENT_OVERFLOWS_VIEWPORT: {
259
+ suggestions: [`maxWidth: "100%"`, `width: "100%"`, `breakWord: true`],
260
+ valid: ["nothing may extend past the right edge of the viewport"],
261
+ },
262
+ HORIZONTAL_OVERFLOW: {
263
+ suggestions: [`maxWidth: "100%" on whichever element the other findings name`],
264
+ valid: ["the document may not be wider than the viewport"],
265
+ },
266
+ CONTENT_CLIPPED: {
267
+ suggestions: [`height: "auto"`, `overflow: "visible"`],
268
+ valid: ["a box with `overflow: hidden` must be tall enough for its text"],
269
+ },
270
+ };
271
+
173
272
  /**
174
273
  * Check rendered HTML and return a report in the shape everything else here
175
274
  * returns. `html` is a full document or a fragment; a fragment is wrapped.
176
275
  */
177
276
  export async function checkPage(html, opts = {}) {
178
277
  const viewports = opts.viewports || DEFAULT_VIEWPORTS;
278
+ // `load` is right for a self-contained page, which is what this library
279
+ // emits. A page whose styling arrives over the network — a CDN stylesheet,
280
+ // a webfont — has not been laid out yet at `load`, and measuring it there
281
+ // reports the unstyled document. Callers checking such a page pass
282
+ // "networkidle".
283
+ const waitUntil = opts.waitUntil || "load";
179
284
  let chromium;
180
285
  try {
181
286
  ({ chromium } = await import("playwright"));
@@ -208,16 +313,20 @@ export async function checkPage(html, opts = {}) {
208
313
  for (const vp of viewports) {
209
314
  const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } });
210
315
  try {
211
- await page.setContent(doc, { waitUntil: "load" });
316
+ await page.setContent(doc, { waitUntil });
212
317
  const found = await page.evaluate(IN_PAGE);
213
318
  for (const f of found) {
214
319
  errors.push({
215
320
  code: f.code,
216
321
  path: f.path,
217
322
  got: f.got,
218
- suggestions: [],
219
- valid: [],
323
+ suggestions: REPAIRS[f.code]?.suggestions ?? [],
324
+ valid: REPAIRS[f.code]?.valid ?? [],
220
325
  detail: f.detail,
326
+ // The spec node, when the page was annotated. Null
327
+ // otherwise, rather than absent, so the field's
328
+ // meaning does not depend on whether it is there.
329
+ nod: f.nod ?? null,
221
330
  viewport: vp.name,
222
331
  });
223
332
  }