nodality 1.0.144 → 1.0.145

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 (94) hide show
  1. package/layout/animator.js +1 -1
  2. package/layout/audio.js +1 -1
  3. package/layout/audionew.js +1 -1
  4. package/layout/base-2.js +1 -1
  5. package/layout/base.js +1 -1
  6. package/layout/beta-desktop-bar.js +1 -1
  7. package/layout/beta-mobile-bar.js +1 -1
  8. package/layout/box.js +1 -1
  9. package/layout/button.js +1 -1
  10. package/layout/cards.js +1 -1
  11. package/layout/center.js +1 -1
  12. package/layout/checkbox.js +1 -1
  13. package/layout/circle.js +1 -1
  14. package/layout/clean-row.js +1 -1
  15. package/layout/code.js +1 -1
  16. package/layout/container.js +1 -1
  17. package/layout/custom.js +1 -1
  18. package/layout/div-image.js +1 -1
  19. package/layout/dropdown-2025.js +1 -1
  20. package/layout/dropdown.js +1 -1
  21. package/layout/empty-element.js +1 -1
  22. package/layout/external-stylesheet.js +1 -1
  23. package/layout/flex-card.js +1 -1
  24. package/layout/flex-grid.js +1 -1
  25. package/layout/flex-row.js +1 -1
  26. package/layout/footer.js +1 -1
  27. package/layout/form-components/custom.js +1 -1
  28. package/layout/form-components/data-list.js +1 -1
  29. package/layout/form-components/floating-input.js +1 -1
  30. package/layout/form-components/form-all.js +1 -1
  31. package/layout/form-components/form.js +1 -1
  32. package/layout/form-components/image-picker.js +1 -1
  33. package/layout/form-components/picker.js +1 -1
  34. package/layout/form-components/radio.js +1 -1
  35. package/layout/form-components/radiogroup.js +1 -1
  36. package/layout/form-components/range.js +1 -1
  37. package/layout/free.js +1 -1
  38. package/layout/grid-new.js +1 -1
  39. package/layout/grid-switcher.js +1 -1
  40. package/layout/grid.js +1 -1
  41. package/layout/group.js +1 -1
  42. package/layout/header.js +1 -1
  43. package/layout/horizontal-scroller.js +1 -1
  44. package/layout/image-old.js +1 -1
  45. package/layout/image.js +1 -1
  46. package/layout/index.js +1 -1
  47. package/layout/label.js +1 -1
  48. package/layout/link.js +1 -1
  49. package/layout/list-OLD.js +1 -1
  50. package/layout/list.js +1 -1
  51. package/layout/meta-adder.js +1 -1
  52. package/layout/modal-2025.js +1 -1
  53. package/layout/modernwrap.js +1 -1
  54. package/layout/multiswitcher.js +1 -1
  55. package/layout/multiswitcherBeta.js +1 -1
  56. package/layout/nav-bar.js +1 -1
  57. package/layout/nav-factor/custom-div.js +1 -1
  58. package/layout/navBar-OLD.js +1 -1
  59. package/layout/new-flat-adder.js +1 -1
  60. package/layout/new-nav-bar.js +1 -1
  61. package/layout/offset-container.js +1 -1
  62. package/layout/polygon.js +1 -1
  63. package/layout/prerender.js +343 -0
  64. package/layout/progress.js +1 -1
  65. package/layout/row.js +1 -1
  66. package/layout/saved-new-nav-bar.js +1 -1
  67. package/layout/scroll-video.js +1 -1
  68. package/layout/side-bar.js +1 -1
  69. package/layout/side-nav-bar.js +1 -1
  70. package/layout/simple-bar.js +1 -1
  71. package/layout/slider-2025.js +1 -1
  72. package/layout/spacer.js +1 -1
  73. package/layout/stack.js +1 -1
  74. package/layout/styler.js +1 -1
  75. package/layout/svg.js +1 -1
  76. package/layout/switcher.js +1 -1
  77. package/layout/table.js +1 -1
  78. package/layout/text-field.js +1 -1
  79. package/layout/text.js +1 -1
  80. package/layout/ulist.js +1 -1
  81. package/layout/video.js +1 -1
  82. package/layout/without-new.js +1 -1
  83. package/layout/wrap.js +1 -1
  84. package/layout/zoom-card.js +1 -1
  85. package/lib/card-getter.js +1 -1
  86. package/lib/designer.js +1 -1
  87. package/lib/element-mapper.js +1 -1
  88. package/lib/keyframe-animation.js +1 -1
  89. package/lib/link-getter.js +1 -1
  90. package/lib/scroll-video.js +1 -1
  91. package/lib/stacker.js +1 -1
  92. package/lib/theme.js +1 -1
  93. package/lib/transform-anim.js +1 -1
  94. package/package.json +3 -2
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/audio.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base-2.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/base.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/box.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/button.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/cards.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/center.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/circle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/code.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/custom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/footer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/group.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/header.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/label.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -0,0 +1,343 @@
1
+ /*!
2
+ * nodality v1.0.145
3
+ * (c) 2026 Filip Vabrousek
4
+ * License: MIT
5
+ */
6
+
7
+ // ════════════════════════════════════════════════════════════════════
8
+ // Nodality — static-site prerender helper
9
+ // ════════════════════════════════════════════════════════════════════
10
+ //
11
+ // Adds the ability to capture the HTML produced by Nodality primitives
12
+ // and write it to a file at BUILD TIME, without changing any other
13
+ // library file or the runtime mount path. The caller's existing
14
+ // `app.js` (or per-page script) keeps mounting dynamically in the
15
+ // browser — this module just runs the same builder once in Node,
16
+ // inside a jsdom-simulated DOM, and saves the resulting HTML so
17
+ // crawlers / AI bots / social-unfurl bots see the rendered page
18
+ // before any JavaScript executes.
19
+ //
20
+ // ─── Why this exists ────────────────────────────────────────────────
21
+ // Nodality apps construct the DOM imperatively in the browser
22
+ // (`new Text().set({...}).render("#mount")`). That works for users
23
+ // but leaves the raw HTML body essentially empty — `<div id="mount">
24
+ // </div>` plus a script tag. Most AI crawlers (GPTBot, ClaudeBot,
25
+ // PerplexityBot, CCBot) and every social-unfurl bot (Slack, LinkedIn,
26
+ // WhatsApp, iMessage, X) don't execute JavaScript, so they see nothing.
27
+ // Pre-rendering the same builder in Node gives those crawlers the
28
+ // real content. Users still get the JS-hydrated experience on top.
29
+ //
30
+ // ─── Design constraint ──────────────────────────────────────────────
31
+ // This file is the ONLY new module in the library; index.js,
32
+ // package.json, webpack.config.js, and every existing primitive are
33
+ // untouched. The caller is expected to import from this file path
34
+ // directly, e.g.:
35
+ //
36
+ // import { prerender } from "nodality/layout/prerender.js";
37
+ //
38
+ // If the caller wants to add a top-level export later, that's a
39
+ // one-line addition to `index.js` they can make on their own.
40
+ //
41
+ // ─── Usage ──────────────────────────────────────────────────────────
42
+ //
43
+ // import { prerender } from "nodality/layout/prerender.js";
44
+ //
45
+ // await prerender({
46
+ // template: "./page-template.html", // HTML shell (head + empty body)
47
+ // mount: "#mount", // selector inside the template
48
+ // locale: "de", // optional: pre-seeded into localStorage
49
+ // url: "https://h7active.com/", // jsdom base URL (used for relative paths)
50
+ // build: async (window) => {
51
+ // // Runs inside the jsdom-simulated browser. `window.document`,
52
+ // // `window.localStorage`, etc. are available globally for the
53
+ // // duration of the call. Import or invoke your page builder
54
+ // // here; it can use Nodality primitives exactly as in the
55
+ // // browser.
56
+ // await import("./app.js");
57
+ // },
58
+ // output: "./upload/de/index.html", // file to write
59
+ // });
60
+ //
61
+ // The function spins up jsdom, runs your builder, captures the full
62
+ // `<!DOCTYPE html>…</html>` document (preserving the template's
63
+ // <head>, the <script> tags pointing at runtime JS, AND the now-
64
+ // populated body), and writes it to `output`. When the live browser
65
+ // later loads that file, the runtime `app.js` still executes and
66
+ // remounts — the navigation, dropdowns, language switcher, scroll
67
+ // handlers, etc. all continue to work dynamically.
68
+ //
69
+ // ─── Peer dependency ────────────────────────────────────────────────
70
+ // `jsdom` is imported only by this file. It's listed as an optional
71
+ // peer dependency so the browser bundle stays small. Callers using
72
+ // prerender must `npm install jsdom` in their build environment.
73
+
74
+ import { promises as fs } from "node:fs";
75
+ import path from "node:path";
76
+
77
+ /**
78
+ * Render a Nodality-built page to a static HTML file.
79
+ *
80
+ * @param {object} opts
81
+ * @param {string} opts.template — Path to the HTML shell file. Must contain
82
+ * `<head>` and an empty mount container (e.g. `<div id="mount"></div>`).
83
+ * @param {string} [opts.mount="#mount"] — CSS selector for the mount point
84
+ * inside the template. The function verifies it exists before running
85
+ * the builder; missing selector throws (catches typos early).
86
+ * @param {string} [opts.locale] — If set, written to `window.localStorage`
87
+ * under the key `h7lang` before the builder runs. Lets locale-aware
88
+ * builders (e.g. those calling `detectLang()`) produce the correct
89
+ * translation without further plumbing.
90
+ * @param {string} [opts.localStorageKey="h7lang"] — Override the
91
+ * localStorage key used for the locale handshake if your app uses a
92
+ * different name.
93
+ * @param {string} [opts.url="http://localhost/"] — Base URL for the jsdom
94
+ * window. Affects `window.location`, relative-URL resolution, and the
95
+ * `Document.URL` your code may read.
96
+ * @param {(window: Window) => (void | Promise<void>)} opts.build —
97
+ * Async function that runs inside the simulated browser. Receives the
98
+ * jsdom `window` object. Should construct the page via Nodality
99
+ * primitives and call `.render(mount)`. Awaited so dynamic imports
100
+ * and async setup complete before serialization.
101
+ * @param {string} opts.output — Path where the rendered HTML is written.
102
+ * Parent directories are created automatically.
103
+ *
104
+ * @returns {Promise<{ bytes: number, output: string }>} — Resolves once
105
+ * the file is on disk. `bytes` is the rendered document size.
106
+ */
107
+ export async function prerender({
108
+ template,
109
+ mount = "#mount",
110
+ locale,
111
+ localStorageKey = "h7lang",
112
+ url = "http://localhost/",
113
+ build,
114
+ output,
115
+ }) {
116
+ if (!template) throw new Error("prerender: `template` is required");
117
+ if (!output) throw new Error("prerender: `output` is required");
118
+ if (typeof build !== "function") {
119
+ throw new Error("prerender: `build` must be an async function");
120
+ }
121
+
122
+ // Lazy import jsdom so this file can be imported in environments that
123
+ // don't have it installed (it's a peer dep). The error message is
124
+ // explicit because the default "Cannot find package 'jsdom'" stack
125
+ // trace is opaque to users who haven't seen it before.
126
+ let JSDOM;
127
+ try {
128
+ ({ JSDOM } = await import("jsdom"));
129
+ } catch (err) {
130
+ throw new Error(
131
+ "prerender: jsdom is required at build time. " +
132
+ "Install it in the consuming project: `npm install --save-dev jsdom`. " +
133
+ "Original error: " + err.message
134
+ );
135
+ }
136
+
137
+ // Read the HTML shell. Use absolute path resolution so relative
138
+ // `template` arguments (the common case from a build script) work
139
+ // regardless of process.cwd().
140
+ const templatePath = path.resolve(template);
141
+ const html = await fs.readFile(templatePath, "utf8");
142
+
143
+ // ─── jsdom setup ─────────────────────────────────────────────────
144
+ //
145
+ // `runScripts: "outside-only"` is the critical flag: it gives the
146
+ // window a working JS execution context (so `new Text()` etc. work
147
+ // inside the builder) BUT does NOT auto-execute `<script>` tags
148
+ // from the template. We deliberately don't want the template's
149
+ // `<script src="./app.js">` to run inside jsdom — paths there are
150
+ // production-relative and would 404; instead the caller's `build`
151
+ // function imports the same modules through Node's module system,
152
+ // which resolves them correctly.
153
+ //
154
+ // `pretendToBeVisual: true` enables requestAnimationFrame and a few
155
+ // related visual APIs. Cheap and prevents subtle "rAF is not a
156
+ // function" errors from libraries that schedule layout work.
157
+ const dom = new JSDOM(html, {
158
+ url,
159
+ runScripts: "outside-only",
160
+ pretendToBeVisual: true,
161
+ });
162
+
163
+ const { window } = dom;
164
+
165
+ // Optional locale seed — written before any global proxying so the
166
+ // builder sees it as soon as the proxy is in place.
167
+ if (locale) {
168
+ window.localStorage.setItem(localStorageKey, locale);
169
+ }
170
+
171
+ // ─── Browser API shims jsdom doesn't ship ───────────────────────
172
+ //
173
+ // These are no-ops; their job is to prevent ReferenceErrors when
174
+ // the builder constructs objects that reference observation APIs.
175
+ // Real observation isn't meaningful at build time (no scrolling,
176
+ // no resizing, no layout) and SSG doesn't need it.
177
+ if (!window.IntersectionObserver) {
178
+ window.IntersectionObserver = class {
179
+ constructor() {}
180
+ observe() {}
181
+ unobserve() {}
182
+ disconnect() {}
183
+ takeRecords() { return []; }
184
+ };
185
+ }
186
+ if (!window.ResizeObserver) {
187
+ window.ResizeObserver = class {
188
+ constructor() {}
189
+ observe() {}
190
+ unobserve() {}
191
+ disconnect() {}
192
+ };
193
+ }
194
+ if (!window.matchMedia) {
195
+ window.matchMedia = () => ({
196
+ matches: false,
197
+ media: "",
198
+ onchange: null,
199
+ addListener() {},
200
+ removeListener() {},
201
+ addEventListener() {},
202
+ removeEventListener() {},
203
+ dispatchEvent() { return false; },
204
+ });
205
+ }
206
+ // Some libraries probe these for code-splitting / analytics. Stub
207
+ // them as harmless no-ops so the builder doesn't crash; the live
208
+ // browser still uses the real implementations.
209
+ if (!window.fetch) {
210
+ window.fetch = () =>
211
+ Promise.resolve({ ok: true, status: 200, json: async () => ({}), text: async () => "" });
212
+ }
213
+ if (!window.dataLayer) window.dataLayer = [];
214
+ if (!window.gtag) window.gtag = () => {};
215
+
216
+ // ─── Global proxying ────────────────────────────────────────────
217
+ //
218
+ // Nodality primitives reach for `document`, `window`, etc. as bare
219
+ // globals — they were written for the browser, where those are
220
+ // already present. To run them in Node we hoist the jsdom-provided
221
+ // versions onto `globalThis` for the duration of the build call.
222
+ //
223
+ // We snapshot any prior values and restore them in the `finally`
224
+ // block so the parent process isn't polluted between successive
225
+ // prerender() invocations (e.g. a build loop over all locales).
226
+ const PROXIED = [
227
+ "window", "document", "localStorage", "sessionStorage", "navigator",
228
+ "location", "HTMLElement", "Element", "Node", "DocumentFragment",
229
+ "Event", "CustomEvent", "MouseEvent", "KeyboardEvent", "PointerEvent",
230
+ "IntersectionObserver", "ResizeObserver", "matchMedia",
231
+ "requestAnimationFrame", "cancelAnimationFrame", "getComputedStyle",
232
+ "fetch", "dataLayer", "gtag",
233
+ ];
234
+ const originalGlobals = {};
235
+ for (const key of PROXIED) {
236
+ if (key in globalThis) originalGlobals[key] = globalThis[key];
237
+ if (key in window) globalThis[key] = window[key];
238
+ }
239
+
240
+ try {
241
+ // Sanity-check the mount point exists. Catches the common typo of
242
+ // changing the mount selector in the template without updating it
243
+ // here (or vice versa). Without this guard the builder runs but
244
+ // silently writes nothing useful.
245
+ if (mount && !window.document.querySelector(mount)) {
246
+ throw new Error(
247
+ `prerender: mount selector "${mount}" not found in template "${templatePath}". ` +
248
+ `Check that the template's <body> contains <div id="${mount.replace(/^#/, "")}"></div>.`
249
+ );
250
+ }
251
+
252
+ // Run the builder. Errors propagate so the caller knows the
253
+ // page didn't render — better than writing a half-built file.
254
+ await build(window);
255
+
256
+ // Drain the microtask queue so any queued promise.then handlers
257
+ // (e.g. dynamic imports inside the builder, fetch().then chains)
258
+ // finish before we serialize. jsdom's macrotask scheduling means
259
+ // setTimeout(0) callbacks may NOT fire — builders that rely on
260
+ // those won't have their output captured. Keep builders sync or
261
+ // promise-based for full coverage.
262
+ await new Promise((resolve) => setImmediate(resolve));
263
+
264
+ // Serialize the full document. `dom.serialize()` returns
265
+ // `<!DOCTYPE html><html>...</html>` including the template's
266
+ // head + the now-populated body. The runtime <script> tags from
267
+ // the template are preserved verbatim, so the live browser will
268
+ // re-execute app.js and re-mount on top of the static DOM.
269
+ const rendered = dom.serialize();
270
+
271
+ // Ensure the output directory exists. Writing to a path like
272
+ // `./upload/de/index.html` would otherwise fail if `de/` doesn't
273
+ // exist yet.
274
+ const outputPath = path.resolve(output);
275
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
276
+ await fs.writeFile(outputPath, rendered, "utf8");
277
+
278
+ return { bytes: rendered.length, output: outputPath };
279
+ } finally {
280
+ // Restore globalThis to its pre-call state. Critical when the
281
+ // caller runs prerender() in a loop — without this, the second
282
+ // invocation would inherit the FIRST jsdom's window, which has
283
+ // already been closed.
284
+ for (const key of PROXIED) {
285
+ if (key in originalGlobals) {
286
+ globalThis[key] = originalGlobals[key];
287
+ } else {
288
+ delete globalThis[key];
289
+ }
290
+ }
291
+ // Release jsdom's internal resources (timers, parser state).
292
+ // Without this the Node process can keep ticking long after
293
+ // the build script "finishes" because jsdom's setInterval
294
+ // callbacks are still alive.
295
+ window.close();
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Convenience wrapper: prerender the same page once per locale and
301
+ * write each rendered file to a different output path.
302
+ *
303
+ * @param {object} opts — Same as `prerender()`, but `output` is a
304
+ * function `(locale) => string` and `locale` is a list.
305
+ * @param {string[]} opts.locales — Locale codes to render, e.g.
306
+ * `["cs", "en", "sk", "de", "fr"]`. Each value is passed to both
307
+ * the builder (via localStorage) and the `outputFor(locale)` fn.
308
+ * @param {(locale: string) => string} opts.outputFor — Returns the
309
+ * destination path for each locale.
310
+ *
311
+ * @returns {Promise<Array<{ locale: string, bytes: number, output: string }>>}
312
+ */
313
+ export async function prerenderEachLocale({
314
+ template,
315
+ mount,
316
+ url,
317
+ locales,
318
+ outputFor,
319
+ build,
320
+ localStorageKey,
321
+ }) {
322
+ if (!Array.isArray(locales) || locales.length === 0) {
323
+ throw new Error("prerenderEachLocale: `locales` must be a non-empty array");
324
+ }
325
+ if (typeof outputFor !== "function") {
326
+ throw new Error("prerenderEachLocale: `outputFor` must be (locale) => string");
327
+ }
328
+
329
+ const results = [];
330
+ for (const locale of locales) {
331
+ const result = await prerender({
332
+ template,
333
+ mount,
334
+ url,
335
+ locale,
336
+ localStorageKey,
337
+ build,
338
+ output: outputFor(locale),
339
+ });
340
+ results.push({ locale, ...result });
341
+ }
342
+ return results;
343
+ }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/layout/styler.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
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.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/stacker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/lib/theme.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * nodality v1.0.144
2
+ * nodality v1.0.145
3
3
  * (c) 2026 Filip Vabrousek
4
4
  * License: MIT
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodality",
3
- "version": "1.0.144",
3
+ "version": "1.0.145",
4
4
  "description": "A lightweight library for declarative UI elements.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -13,7 +13,8 @@
13
13
  ".": {
14
14
  "require": "./dist/index.js",
15
15
  "import": "./dist/index.esm.js"
16
- }
16
+ },
17
+ "./ssg": "./layout/prerender.js"
17
18
  },
18
19
  "scripts": {
19
20
  "build": "webpack --config webpack.config.js",