nodality 1.3.13 → 1.3.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 (213) hide show
  1. package/bin/nodality.mjs +1 -1
  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 +1 -1
  9. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  10. package/dist/beta-desktop-bar.esm.js +1 -1
  11. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  12. package/dist/beta-mobile-bar.cjs.js +1 -1
  13. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  14. package/dist/beta-mobile-bar.esm.js +1 -1
  15. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  16. package/dist/bundle.umd.js +1 -1
  17. package/dist/bundle.umd.js.LICENSE.txt +1 -1
  18. package/dist/button.cjs.js.LICENSE.txt +1 -1
  19. package/dist/button.esm.js.LICENSE.txt +1 -1
  20. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  21. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  22. package/dist/center.cjs.js.LICENSE.txt +1 -1
  23. package/dist/center.esm.js.LICENSE.txt +1 -1
  24. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  25. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  26. package/dist/code.cjs.js.LICENSE.txt +1 -1
  27. package/dist/code.esm.js.LICENSE.txt +1 -1
  28. package/dist/container.cjs.js.LICENSE.txt +1 -1
  29. package/dist/container.esm.js.LICENSE.txt +1 -1
  30. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  31. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  32. package/dist/designer.cjs.js +1 -1
  33. package/dist/designer.cjs.js.LICENSE.txt +1 -1
  34. package/dist/designer.esm.js +1 -1
  35. package/dist/designer.esm.js.LICENSE.txt +1 -1
  36. package/dist/element-mapper.cjs.js +1 -1
  37. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  38. package/dist/element-mapper.esm.js +1 -1
  39. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  40. package/dist/finalresult.esm.js +2 -2
  41. package/dist/finalresult.esm.js.LICENSE.txt +1 -1
  42. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  43. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  44. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  45. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  46. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  47. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  48. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  49. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  50. package/dist/free.cjs.js.LICENSE.txt +1 -1
  51. package/dist/free.esm.js.LICENSE.txt +1 -1
  52. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  53. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  54. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  55. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  56. package/dist/image.cjs.js.LICENSE.txt +1 -1
  57. package/dist/image.esm.js.LICENSE.txt +1 -1
  58. package/dist/index.cjs.js +1 -1
  59. package/dist/index.cjs.js.LICENSE.txt +1 -1
  60. package/dist/index.d.ts +20 -1
  61. package/dist/index.esm.js +2 -2
  62. package/dist/index.esm.js.LICENSE.txt +1 -1
  63. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  64. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  65. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  66. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  67. package/dist/link.cjs.js.LICENSE.txt +1 -1
  68. package/dist/link.esm.js.LICENSE.txt +1 -1
  69. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  70. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  71. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  72. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  73. package/dist/multiswitcher.cjs.js +1 -1
  74. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  75. package/dist/multiswitcher.esm.js +1 -1
  76. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  77. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  78. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  79. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  80. package/dist/picker.esm.js.LICENSE.txt +1 -1
  81. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  82. package/dist/progress.esm.js.LICENSE.txt +1 -1
  83. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  84. package/dist/radio.esm.js.LICENSE.txt +1 -1
  85. package/dist/range.cjs.js.LICENSE.txt +1 -1
  86. package/dist/range.esm.js.LICENSE.txt +1 -1
  87. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  88. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  89. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  90. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  91. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  92. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  93. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  94. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  95. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  96. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  97. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  98. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  99. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  100. package/dist/stack.esm.js.LICENSE.txt +1 -1
  101. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  102. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  103. package/dist/table.cjs.js.LICENSE.txt +1 -1
  104. package/dist/table.esm.js.LICENSE.txt +1 -1
  105. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  106. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  107. package/dist/text.cjs.js.LICENSE.txt +1 -1
  108. package/dist/text.esm.js.LICENSE.txt +1 -1
  109. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  110. package/dist/theme.esm.js.LICENSE.txt +1 -1
  111. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  112. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  113. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  114. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  115. package/dist/video.cjs.js.LICENSE.txt +1 -1
  116. package/dist/video.esm.js.LICENSE.txt +1 -1
  117. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  118. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  119. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  120. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  121. package/layout/animator.js +1 -1
  122. package/layout/audio.js +1 -1
  123. package/layout/audionew.js +1 -1
  124. package/layout/base.js +1 -1
  125. package/layout/beta-desktop-bar.js +4 -1
  126. package/layout/beta-mobile-bar.js +35 -13
  127. package/layout/button.js +1 -1
  128. package/layout/center.js +1 -1
  129. package/layout/checkbox.js +1 -1
  130. package/layout/circle.js +1 -1
  131. package/layout/code.js +1 -1
  132. package/layout/container.js +1 -1
  133. package/layout/dropdown-2025.js +1 -1
  134. package/layout/flex-card.js +1 -1
  135. package/layout/flex-grid.js +1 -1
  136. package/layout/flex-row.js +1 -1
  137. package/layout/form-components/custom.js +1 -1
  138. package/layout/form-components/data-list.js +1 -1
  139. package/layout/form-components/floating-input.js +1 -1
  140. package/layout/form-components/form-all.js +1 -1
  141. package/layout/form-components/form.js +1 -1
  142. package/layout/form-components/image-picker.js +1 -1
  143. package/layout/form-components/picker.js +1 -1
  144. package/layout/form-components/radio.js +1 -1
  145. package/layout/form-components/radiogroup.js +1 -1
  146. package/layout/form-components/range.js +1 -1
  147. package/layout/free.js +1 -1
  148. package/layout/grid-overlay.js +1 -1
  149. package/layout/grid-switcher.js +1 -1
  150. package/layout/grid.js +1 -1
  151. package/layout/horizontal-scroller.js +1 -1
  152. package/layout/image.js +1 -1
  153. package/layout/index.js +5 -1
  154. package/layout/link.js +1 -1
  155. package/layout/list.js +1 -1
  156. package/layout/meta-adder.js +1 -1
  157. package/layout/modal-2025.js +1 -1
  158. package/layout/morph.js +1 -1
  159. package/layout/multiswitcher.js +15 -2
  160. package/layout/nav-bar.js +1 -1
  161. package/layout/nav-factor/custom-div.js +1 -1
  162. package/layout/new-nav-bar.js +1 -1
  163. package/layout/polygon.js +1 -1
  164. package/layout/prerender-site.js +1 -1
  165. package/layout/prerender.js +14 -9
  166. package/layout/progress.js +1 -1
  167. package/layout/row.js +1 -1
  168. package/layout/scroll-video.js +1 -1
  169. package/layout/shop.js +261 -0
  170. package/layout/side-bar.js +1 -1
  171. package/layout/side-nav-bar.js +1 -1
  172. package/layout/simple-bar.js +1 -1
  173. package/layout/slider-2025.js +1 -1
  174. package/layout/spacer.js +1 -1
  175. package/layout/stack.js +1 -1
  176. package/layout/svg.js +1 -1
  177. package/layout/switcher.js +1 -1
  178. package/layout/table.js +1 -1
  179. package/layout/text-field.js +1 -1
  180. package/layout/text.js +1 -1
  181. package/layout/ulist.js +1 -1
  182. package/layout/video.js +1 -1
  183. package/layout/wrap.js +1 -1
  184. package/layout/zoom-card.js +1 -1
  185. package/lib/agent-surface.js +24 -2
  186. package/lib/card-getter.js +1 -1
  187. package/lib/check-page.js +1 -1
  188. package/lib/codegen.js +1 -1
  189. package/lib/data.js +1 -1
  190. package/lib/designer.js +3 -2
  191. package/lib/element-mapper.js +186 -1
  192. package/lib/element-params.generated.js +4 -4
  193. package/lib/grid-geometry.js +1 -1
  194. package/lib/keyframe-animation.js +1 -1
  195. package/lib/link-getter.js +1 -1
  196. package/lib/morph-node.js +1 -1
  197. package/lib/normalize-spec.js +1 -1
  198. package/lib/parse-html.js +1 -1
  199. package/lib/raster-inspect.js +1 -1
  200. package/lib/raster-ops.js +1 -1
  201. package/lib/raster-presets.js +1 -1
  202. package/lib/resolve-refs.js +1 -1
  203. package/lib/scroll-video.js +1 -1
  204. package/lib/seo.js +1 -1
  205. package/lib/stacker.js +1 -1
  206. package/lib/suggest.js +1 -1
  207. package/lib/theme.js +1 -1
  208. package/lib/transform-anim.js +1 -1
  209. package/lib/transition.js +1 -1
  210. package/lib/validate-nodes.js +42 -4
  211. package/lib/webmcp-adapter.js +36 -1
  212. package/package.json +1 -1
  213. package/schema.json +1028 -12
package/layout/shop.js ADDED
@@ -0,0 +1,261 @@
1
+ /*!
2
+ * nodality v1.3.15
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ // shop.js — commerce for a static page, on Shopify's Storefront Web Components.
8
+ //
9
+ // new Shop().set({ kind: "store", domain: "https://mock.shop" }).render("#mount");
10
+ // new Shop().set({ kind: "product", handle: "sweatpants" })
11
+ // .add([ new Shop().set({ kind: "price" }) ]) // live
12
+ // .fallback([ new Shop().set({ kind: "price", text: "€35", fallback: true }) ])
13
+ // .render("#mount");
14
+ //
15
+ // One class, one `kind` per element type: store, product, price, productData,
16
+ // productMedia, variantPicker, buy, cart. Each renders the matching Shopify
17
+ // custom element, so the cart, prices per market and checkout stay Shopify's
18
+ // and the page stays static.
19
+ //
20
+ // Every kind also has a STATIC form (`fallback: true`): plain text, an image
21
+ // or a link built from build-time data. A product's static children fill the
22
+ // placeholder Shopify shows until its data arrives — which is also what the
23
+ // prerenderer writes into the HTML, because jsdom never defines the custom
24
+ // elements. So crawlers and no-JS readers get the product, its price and a
25
+ // working link to buy it.
26
+
27
+ import { Animator } from "./animator.js";
28
+ import { toObjectSource } from "../lib/codegen.js";
29
+
30
+ const SCRIPT = "https://cdn.shopify.com/storefront/web-components.js";
31
+
32
+ // Ids end up inside inline handlers, so only id-shaped strings are accepted.
33
+ const safeId = (v, dflt) => {
34
+ const s = String(v ?? dflt).replace(/^#/, "");
35
+ return /^[A-Za-z][\w-]*$/.test(s) ? s : dflt;
36
+ };
37
+
38
+ const DEFAULT_QUERY = {
39
+ price: "product.selectedOrFirstAvailableVariant.price",
40
+ productData: "product.title",
41
+ productMedia: "product.selectedOrFirstAvailableVariant.image",
42
+ };
43
+
44
+ class Shop extends Animator {
45
+ constructor() {
46
+ super();
47
+ this.items = [];
48
+ this.fallbackItems = [];
49
+ }
50
+
51
+ set(obj = {}) {
52
+ this.options = obj;
53
+ const kind = obj.kind;
54
+ const builder = this[`build_${kind}`];
55
+ if (typeof builder !== "function") {
56
+ throw new Error(`nodality: Shop has no kind "${kind}"`);
57
+ }
58
+ this.res = builder.call(this, obj);
59
+
60
+ if (kind === "product") {
61
+ // A product is a box on the page, styled like a wrap. The
62
+ // custom element is inline by default.
63
+ this.res.style.display = "block";
64
+ this.commonMethods(obj);
65
+ }
66
+
67
+ obj.id && this.res.setAttribute("id", String(obj.id));
68
+ obj.font && (this.res.style.fontFamily = obj.font);
69
+ obj.color && (this.res.style.color = obj.color);
70
+ obj.keySet && this.keySet(obj.keySet);
71
+ return this;
72
+ }
73
+
74
+ // ── kinds ──────────────────────────────────────────────────────────
75
+
76
+ build_store(o) {
77
+ const el = document.createElement("shopify-store");
78
+ el.setAttribute("store-domain", o.domain);
79
+ o.token && el.setAttribute("public-access-token", o.token);
80
+ o.country && el.setAttribute("country", o.country);
81
+ o.language && el.setAttribute("language", o.language);
82
+ Shop.loadScript();
83
+ return el;
84
+ }
85
+
86
+ build_product(o) {
87
+ const ctx = document.createElement("shopify-context");
88
+ ctx.setAttribute("type", "product");
89
+ ctx.setAttribute("handle", o.handle);
90
+ this.template = document.createElement("template");
91
+ this.placeholder = document.createElement("div");
92
+ this.placeholder.setAttribute("shopify-loading-placeholder", "");
93
+ ctx.appendChild(this.template);
94
+ ctx.appendChild(this.placeholder);
95
+ return ctx;
96
+ }
97
+
98
+ build_price(o) {
99
+ if (o.fallback) return this.text(o.text);
100
+ const el = document.createElement("shopify-money");
101
+ el.setAttribute("query", o.query ?? DEFAULT_QUERY.price);
102
+ // The static value as initial content: shown until Shopify renders,
103
+ // so the price never flashes blank.
104
+ o.text != null && (el.textContent = o.text);
105
+ return el;
106
+ }
107
+
108
+ build_productData(o) {
109
+ if (o.fallback) return this.text(o.text);
110
+ const el = document.createElement("shopify-data");
111
+ el.setAttribute("query", o.query ?? DEFAULT_QUERY.productData);
112
+ o.text != null && (el.textContent = o.text);
113
+ return el;
114
+ }
115
+
116
+ build_productMedia(o) {
117
+ if (o.fallback) {
118
+ const img = document.createElement("img");
119
+ o.url && img.setAttribute("src", o.url);
120
+ img.setAttribute("alt", o.alt ?? "");
121
+ o.width && img.setAttribute("width", String(o.width));
122
+ o.height && img.setAttribute("height", String(o.height));
123
+ return img;
124
+ }
125
+ const el = document.createElement("shopify-media");
126
+ el.setAttribute("query", o.query ?? DEFAULT_QUERY.productMedia);
127
+ o.width && el.setAttribute("width", String(o.width));
128
+ o.height && el.setAttribute("height", String(o.height));
129
+ if (o.url) {
130
+ const img = document.createElement("img");
131
+ img.setAttribute("src", o.url);
132
+ img.setAttribute("alt", o.alt ?? "");
133
+ el.appendChild(img);
134
+ }
135
+ return el;
136
+ }
137
+
138
+ build_variantPicker(o) {
139
+ if (o.fallback) return this.text(o.text);
140
+ const el = document.createElement("shopify-variant-selector");
141
+ o.text != null && (el.textContent = o.text);
142
+ return el;
143
+ }
144
+
145
+ build_buy(o) {
146
+ if (o.fallback) {
147
+ // No JavaScript, or not yet: a real link to the product on the
148
+ // shop, so buying never depends on the script loading.
149
+ const a = document.createElement("a");
150
+ o.url && a.setAttribute("href", o.url);
151
+ a.textContent = o.text ?? "Buy";
152
+ return a;
153
+ }
154
+ const button = document.createElement("button");
155
+ button.setAttribute("type", "button");
156
+ button.textContent = o.text ?? "Add to cart";
157
+ const cart = safeId(o.cart, "cart");
158
+ const store = safeId(o.store, "store");
159
+ // An attribute, not a listener: a product's children are cloned out
160
+ // of its <template> by Shopify, and cloning drops listeners.
161
+ button.setAttribute("onclick", o.mode === "buyNow"
162
+ ? `document.getElementById('${store}').buyNow(event)`
163
+ : `document.getElementById('${cart}').addLine(event).showModal()`);
164
+ return button;
165
+ }
166
+
167
+ build_cart(o) {
168
+ const el = document.createElement("shopify-cart");
169
+ o.target && el.setAttribute("target", o.target);
170
+ o.discountCodes && el.setAttribute("discount-codes", o.discountCodes);
171
+ const id = safeId(o.id, "cart");
172
+ if (o.theme) Shop.themeCart(id, o.theme);
173
+ return el;
174
+ }
175
+
176
+ // ── helpers ────────────────────────────────────────────────────────
177
+
178
+ text(value) {
179
+ const span = document.createElement("span");
180
+ span.textContent = value ?? "";
181
+ return span;
182
+ }
183
+
184
+ /** Shopify's script, once per document. */
185
+ static loadScript() {
186
+ if (typeof document === "undefined" || !document.head) return;
187
+ if (document.querySelector(`script[src="${SCRIPT}"]`)) return;
188
+ const s = document.createElement("script");
189
+ s.setAttribute("type", "module");
190
+ s.setAttribute("src", SCRIPT);
191
+ document.head.appendChild(s);
192
+ }
193
+
194
+ /**
195
+ * The cart draws in its own shadow DOM, styled from outside only through
196
+ * the parts Shopify exposes. The theme becomes ::part() rules in one
197
+ * <style> per cart, replaced rather than duplicated on a re-render.
198
+ */
199
+ static themeCart(id, t) {
200
+ if (typeof document === "undefined" || !document.head) return;
201
+ const sel = `#${id}`;
202
+ const decl = (pairs) => Object.entries(pairs)
203
+ .filter(([, v]) => v != null && v !== "")
204
+ .map(([k, v]) => `${k}: ${String(v).replace(/[;{}<>]/g, "")}`).join("; ");
205
+ const css = [
206
+ `${sel} { ${decl({ "font-family": t.font })} }`,
207
+ `${sel}::part(dialog) { ${decl({ background: t.background, color: t.color, "font-family": t.font, "border-radius": t.radius })} }`,
208
+ `${sel}::part(primary-button) { ${decl({ background: t.accent, color: t.accentText, "font-family": t.font, "border-radius": t.buttonRadius ?? t.radius })} }`,
209
+ `${sel}::part(secondary-button) { ${decl({ color: t.color, "font-family": t.font })} }`,
210
+ `${sel}::part(line-heading) { ${decl({ "font-family": t.headingFont ?? t.font })} }`,
211
+ ].join("\n");
212
+ const key = `nodality-cart-${id}`;
213
+ let style = document.head.querySelector(`style[data-shop="${key}"]`);
214
+ if (!style) {
215
+ style = document.createElement("style");
216
+ style.setAttribute("data-shop", key);
217
+ document.head.appendChild(style);
218
+ }
219
+ style.textContent = css;
220
+ }
221
+
222
+ // ── children ───────────────────────────────────────────────────────
223
+
224
+ /** Live children. A product's go into its <template>. */
225
+ add(els = []) {
226
+ this.items = els;
227
+ const into = this.template ? this.template.content : this.res;
228
+ for (const el of els) if (el && typeof el.render === "function") into.appendChild(el.render());
229
+ return this;
230
+ }
231
+
232
+ /** Static children, shown until the product's data arrives. */
233
+ fallback(els = []) {
234
+ this.fallbackItems = els;
235
+ const into = this.placeholder || this.res;
236
+ for (const el of els) if (el && typeof el.render === "function") into.appendChild(el.render());
237
+ return this;
238
+ }
239
+
240
+ toCode() {
241
+ const opts = Object.fromEntries(Object.entries(this.options || {}).filter(([, v]) => v != null));
242
+ const kids = (list) => list
243
+ .filter((e) => e && typeof e.toCode === "function")
244
+ .map((e) => [].concat(e.toCode()).join(""))
245
+ .join(",\n");
246
+ let code = `new Shop().set(${toObjectSource(opts, 4)})`;
247
+ if (this.items.length) code += `.add([${kids(this.items)}])`;
248
+ if (this.fallbackItems.length) code += `.fallback([${kids(this.fallbackItems)}])`;
249
+ return [code];
250
+ }
251
+
252
+ render(selector) {
253
+ if (selector) {
254
+ const parent = document.querySelector(selector);
255
+ parent && parent.appendChild(this.res);
256
+ }
257
+ return this.res;
258
+ }
259
+ }
260
+
261
+ export { Shop, SCRIPT as SHOP_SCRIPT };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -315,6 +315,28 @@ export function deriveSurface(elements, nodes, { specDraft = null } = {}) {
315
315
  });
316
316
  }
317
317
 
318
+ // ── products, allow-listed by id ─────────────────────────────────
319
+ //
320
+ // Read-only. An agent can learn what is for sale, at what price and in
321
+ // which options; it cannot put anything in a cart. Paying stays a human
322
+ // act on the shop's own checkout.
323
+ const productIds = (Array.isArray(node.products) ? node.products : [])
324
+ .filter((s) => typeof s === "string").map((s) => s.replace(/^#/, ""));
325
+ for (const id of productIds) {
326
+ const product = byId.get(id) || byId.get(`#${id}`);
327
+ if (!product || product.type !== "product") continue;
328
+ tools.push({
329
+ name: named(`get_product_${id.replace(/[^a-zA-Z0-9_-]/g, "_")}`),
330
+ kind: "product",
331
+ productId: id,
332
+ description:
333
+ `Read the product "${product.handle}" as it is shown on this page: ` +
334
+ "name, price for the visitor's market, options and availability. " +
335
+ "Read-only; buying happens on the shop's checkout.",
336
+ inputSchema: { type: "object", properties: {} },
337
+ });
338
+ }
339
+
318
340
  // ── what is on screen ────────────────────────────────────────────
319
341
  tools.push({
320
342
  name: named("read_view"),
@@ -331,7 +353,7 @@ export function deriveSurface(elements, nodes, { specDraft = null } = {}) {
331
353
  // deciding whether to visit at all can actually read.
332
354
  const manifest = {
333
355
  spec: specDraft,
334
- tools: tools.map(({ kind, formId, ...decl }) => decl),
356
+ tools: tools.map(({ kind, formId, productId, ...decl }) => decl),
335
357
  ...(graph
336
358
  ? {
337
359
  views: {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
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.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/codegen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/data.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/designer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.3.13
2
+ * nodality v1.3.15
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -76,6 +76,7 @@ import { AreaSwitcher } from "../layout/grid-switcher.js";
76
76
  import { Polygon } from "../layout/polygon.js";
77
77
  import { Circle } from "../layout/circle.js";
78
78
  import { GridOverlay } from "../layout/grid-overlay.js";
79
+ import { Shop } from "../layout/shop.js";
79
80
 
80
81
 
81
82
 
@@ -1446,7 +1447,7 @@ new Slider(texts, null, { tintColor: "#e74c3c", inactiveColor: "#ccc" })
1446
1447
  Text, Image, Link, FlexRow, UINavBar, Free, Audio, Progress, Center, Code,
1447
1448
  Stack, Wrapper, MetaAdder, Table, Dropdown, Modal, TextField, Card,
1448
1449
  Wrap, FlexGrid, ZoomCard, Switcher, MobileBar, DesktopBar, SideNav, Spacer, HScroller, Polygon, Circle, UList,
1449
- GridOverlay
1450
+ GridOverlay, Shop
1450
1451
  };
1451
1452
 
1452
1453
  const formComponents = {