@opentf/web 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +30 -28
  2. package/components/Link.jsx +43 -0
  3. package/core/errors.js +48 -0
  4. package/core/reactive.js +190 -0
  5. package/core/signals.js +207 -0
  6. package/index.js +13 -0
  7. package/package.json +27 -18
  8. package/runtime/clipboard.js +47 -0
  9. package/runtime/code-block.js +47 -0
  10. package/runtime/context.js +99 -0
  11. package/runtime/dom.js +368 -0
  12. package/runtime/error-boundary.js +109 -0
  13. package/runtime/events.js +17 -0
  14. package/runtime/hydrate.js +121 -0
  15. package/runtime/index.js +22 -0
  16. package/runtime/lifecycle.js +9 -0
  17. package/runtime/mount.js +49 -0
  18. package/runtime/portal.js +59 -0
  19. package/runtime/raw-html.js +33 -0
  20. package/runtime/router.js +392 -0
  21. package/server/builtins.js +16 -0
  22. package/server/head.js +191 -0
  23. package/server/index.js +8 -0
  24. package/server/render.js +98 -0
  25. package/server/ssg-runtime.js +112 -0
  26. package/dist/CHANGELOG.md +0 -49
  27. package/dist/README.md +0 -41
  28. package/dist/babel.config.cjs +0 -6
  29. package/dist/compiler/babel-plugin.cjs +0 -798
  30. package/dist/core/mount.js +0 -32
  31. package/dist/index.js +0 -7
  32. package/dist/node_modules/@babel/core/LICENSE +0 -22
  33. package/dist/node_modules/@babel/core/README.md +0 -19
  34. package/dist/node_modules/@babel/core/lib/config/cache-contexts.js +0 -5
  35. package/dist/node_modules/@babel/core/lib/config/cache-contexts.js.map +0 -1
  36. package/dist/node_modules/@babel/core/lib/config/caching.js +0 -261
  37. package/dist/node_modules/@babel/core/lib/config/caching.js.map +0 -1
  38. package/dist/node_modules/@babel/core/lib/config/config-chain.js +0 -469
  39. package/dist/node_modules/@babel/core/lib/config/config-chain.js.map +0 -1
  40. package/dist/node_modules/@babel/core/lib/config/config-descriptors.js +0 -190
  41. package/dist/node_modules/@babel/core/lib/config/config-descriptors.js.map +0 -1
  42. package/dist/node_modules/@babel/core/lib/config/files/configuration.js +0 -290
  43. package/dist/node_modules/@babel/core/lib/config/files/configuration.js.map +0 -1
  44. package/dist/node_modules/@babel/core/lib/config/files/import.cjs +0 -6
  45. package/dist/node_modules/@babel/core/lib/config/files/import.cjs.map +0 -1
  46. package/dist/node_modules/@babel/core/lib/config/files/index-browser.js +0 -58
  47. package/dist/node_modules/@babel/core/lib/config/files/index-browser.js.map +0 -1
  48. package/dist/node_modules/@babel/core/lib/config/files/index.js +0 -78
  49. package/dist/node_modules/@babel/core/lib/config/files/index.js.map +0 -1
  50. package/dist/node_modules/@babel/core/lib/config/files/module-types.js +0 -203
  51. package/dist/node_modules/@babel/core/lib/config/files/module-types.js.map +0 -1
  52. package/dist/node_modules/@babel/core/lib/config/files/package.js +0 -61
  53. package/dist/node_modules/@babel/core/lib/config/files/package.js.map +0 -1
  54. package/dist/node_modules/@babel/core/lib/config/files/plugins.js +0 -220
  55. package/dist/node_modules/@babel/core/lib/config/files/plugins.js.map +0 -1
  56. package/dist/node_modules/@babel/core/lib/config/files/types.js +0 -5
  57. package/dist/node_modules/@babel/core/lib/config/files/types.js.map +0 -1
  58. package/dist/node_modules/@babel/core/lib/config/files/utils.js +0 -36
  59. package/dist/node_modules/@babel/core/lib/config/files/utils.js.map +0 -1
  60. package/dist/node_modules/@babel/core/lib/config/full.js +0 -312
  61. package/dist/node_modules/@babel/core/lib/config/full.js.map +0 -1
  62. package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js +0 -85
  63. package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js.map +0 -1
  64. package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js +0 -23
  65. package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js.map +0 -1
  66. package/dist/node_modules/@babel/core/lib/config/helpers/environment.js +0 -12
  67. package/dist/node_modules/@babel/core/lib/config/helpers/environment.js.map +0 -1
  68. package/dist/node_modules/@babel/core/lib/config/index.js +0 -87
  69. package/dist/node_modules/@babel/core/lib/config/index.js.map +0 -1
  70. package/dist/node_modules/@babel/core/lib/config/item.js +0 -67
  71. package/dist/node_modules/@babel/core/lib/config/item.js.map +0 -1
  72. package/dist/node_modules/@babel/core/lib/config/partial.js +0 -158
  73. package/dist/node_modules/@babel/core/lib/config/partial.js.map +0 -1
  74. package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js +0 -38
  75. package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js.map +0 -1
  76. package/dist/node_modules/@babel/core/lib/config/plugin.js +0 -33
  77. package/dist/node_modules/@babel/core/lib/config/plugin.js.map +0 -1
  78. package/dist/node_modules/@babel/core/lib/config/printer.js +0 -113
  79. package/dist/node_modules/@babel/core/lib/config/printer.js.map +0 -1
  80. package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js +0 -41
  81. package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js.map +0 -1
  82. package/dist/node_modules/@babel/core/lib/config/resolve-targets.js +0 -61
  83. package/dist/node_modules/@babel/core/lib/config/resolve-targets.js.map +0 -1
  84. package/dist/node_modules/@babel/core/lib/config/util.js +0 -31
  85. package/dist/node_modules/@babel/core/lib/config/util.js.map +0 -1
  86. package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js +0 -277
  87. package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js.map +0 -1
  88. package/dist/node_modules/@babel/core/lib/config/validation/options.js +0 -187
  89. package/dist/node_modules/@babel/core/lib/config/validation/options.js.map +0 -1
  90. package/dist/node_modules/@babel/core/lib/config/validation/plugins.js +0 -67
  91. package/dist/node_modules/@babel/core/lib/config/validation/plugins.js.map +0 -1
  92. package/dist/node_modules/@babel/core/lib/config/validation/removed.js +0 -68
  93. package/dist/node_modules/@babel/core/lib/config/validation/removed.js.map +0 -1
  94. package/dist/node_modules/@babel/core/lib/errors/config-error.js +0 -18
  95. package/dist/node_modules/@babel/core/lib/errors/config-error.js.map +0 -1
  96. package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js +0 -98
  97. package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js.map +0 -1
  98. package/dist/node_modules/@babel/core/lib/gensync-utils/async.js +0 -90
  99. package/dist/node_modules/@babel/core/lib/gensync-utils/async.js.map +0 -1
  100. package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js +0 -31
  101. package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js.map +0 -1
  102. package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js +0 -58
  103. package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js.map +0 -1
  104. package/dist/node_modules/@babel/core/lib/index.js +0 -230
  105. package/dist/node_modules/@babel/core/lib/index.js.map +0 -1
  106. package/dist/node_modules/@babel/core/lib/parse.js +0 -45
  107. package/dist/node_modules/@babel/core/lib/parse.js.map +0 -1
  108. package/dist/node_modules/@babel/core/lib/parser/index.js +0 -85
  109. package/dist/node_modules/@babel/core/lib/parser/index.js.map +0 -1
  110. package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js +0 -337
  111. package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js.map +0 -1
  112. package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js +0 -144
  113. package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js.map +0 -1
  114. package/dist/node_modules/@babel/core/lib/transform-ast.js +0 -48
  115. package/dist/node_modules/@babel/core/lib/transform-ast.js.map +0 -1
  116. package/dist/node_modules/@babel/core/lib/transform-file-browser.js +0 -23
  117. package/dist/node_modules/@babel/core/lib/transform-file-browser.js.map +0 -1
  118. package/dist/node_modules/@babel/core/lib/transform-file.js +0 -40
  119. package/dist/node_modules/@babel/core/lib/transform-file.js.map +0 -1
  120. package/dist/node_modules/@babel/core/lib/transform.js +0 -47
  121. package/dist/node_modules/@babel/core/lib/transform.js.map +0 -1
  122. package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js +0 -84
  123. package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js.map +0 -1
  124. package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs +0 -4
  125. package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs.map +0 -1
  126. package/dist/node_modules/@babel/core/lib/transformation/file/file.js +0 -204
  127. package/dist/node_modules/@babel/core/lib/transformation/file/file.js.map +0 -1
  128. package/dist/node_modules/@babel/core/lib/transformation/file/generate.js +0 -84
  129. package/dist/node_modules/@babel/core/lib/transformation/file/generate.js.map +0 -1
  130. package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js +0 -37
  131. package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js.map +0 -1
  132. package/dist/node_modules/@babel/core/lib/transformation/index.js +0 -90
  133. package/dist/node_modules/@babel/core/lib/transformation/index.js.map +0 -1
  134. package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js +0 -127
  135. package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js.map +0 -1
  136. package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js +0 -59
  137. package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js.map +0 -1
  138. package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js +0 -48
  139. package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js.map +0 -1
  140. package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js +0 -54
  141. package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js.map +0 -1
  142. package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js +0 -1042
  143. package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js.map +0 -1
  144. package/dist/node_modules/@babel/core/package.json +0 -82
  145. package/dist/node_modules/@babel/core/src/config/files/index-browser.ts +0 -115
  146. package/dist/node_modules/@babel/core/src/config/files/index.ts +0 -30
  147. package/dist/node_modules/@babel/core/src/config/resolve-targets-browser.ts +0 -42
  148. package/dist/node_modules/@babel/core/src/config/resolve-targets.ts +0 -54
  149. package/dist/node_modules/@babel/core/src/transform-file-browser.ts +0 -33
  150. package/dist/node_modules/@babel/core/src/transform-file.ts +0 -56
  151. package/dist/node_modules/@babel/helper-module-imports/LICENSE +0 -22
  152. package/dist/node_modules/@babel/helper-module-imports/README.md +0 -19
  153. package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js +0 -122
  154. package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js.map +0 -1
  155. package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js +0 -304
  156. package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js.map +0 -1
  157. package/dist/node_modules/@babel/helper-module-imports/lib/index.js +0 -37
  158. package/dist/node_modules/@babel/helper-module-imports/lib/index.js.map +0 -1
  159. package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js +0 -11
  160. package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js.map +0 -1
  161. package/dist/node_modules/@babel/helper-module-imports/package.json +0 -28
  162. package/dist/node_modules/@babel/plugin-syntax-jsx/LICENSE +0 -22
  163. package/dist/node_modules/@babel/plugin-syntax-jsx/README.md +0 -19
  164. package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js +0 -21
  165. package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js.map +0 -1
  166. package/dist/node_modules/@babel/plugin-syntax-jsx/package.json +0 -33
  167. package/dist/node_modules/@preact/signals-core/CHANGELOG.md +0 -331
  168. package/dist/node_modules/@preact/signals-core/LICENSE +0 -21
  169. package/dist/node_modules/@preact/signals-core/README.md +0 -258
  170. package/dist/node_modules/@preact/signals-core/dist/signals-core.d.ts +0 -149
  171. package/dist/node_modules/@preact/signals-core/dist/signals-core.js +0 -1
  172. package/dist/node_modules/@preact/signals-core/dist/signals-core.js.map +0 -1
  173. package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js +0 -1
  174. package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js.map +0 -1
  175. package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs +0 -1
  176. package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs.map +0 -1
  177. package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js +0 -1
  178. package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js.map +0 -1
  179. package/dist/node_modules/@preact/signals-core/package.json +0 -48
  180. package/dist/node_modules/@preact/signals-core/src/index.ts +0 -1121
  181. package/dist/package.json +0 -30
  182. package/dist/router/Link.js +0 -73
  183. package/dist/router/index.js +0 -216
  184. package/dist/runtime/For.js +0 -16
  185. package/dist/runtime/dom.js +0 -73
  186. package/dist/runtime/lifecycle.js +0 -25
  187. package/dist/runtime/props.js +0 -103
package/dist/package.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "name": "@opentf/web",
3
- "version": "0.4.0",
4
- "description": "The core engine of the Web App Framework, including the zero-VDOM compiler and signal-based runtime.",
5
- "main": "dist/index.js",
6
- "module": "dist/index.js",
7
- "type": "module",
8
- "scripts": {
9
- "build": "babel . --out-dir dist --extensions .js,.jsx --copy-files --config-file ./babel.config.cjs --ignore \"node_modules,dist,package.json,bun.lock,*.md\"",
10
- "prepublishOnly": "npm run build"
11
- },
12
- "exports": {
13
- ".": "./dist/index.js",
14
- "./compiler": "./dist/compiler/babel-plugin.cjs",
15
- "./runtime": "./dist/runtime/index.js",
16
- "./router": "./dist/router/index.js"
17
- },
18
- "files": [
19
- "dist"
20
- ],
21
- "dependencies": {
22
- "@preact/signals-core": "^1.14.1",
23
- "@babel/core": "^7.29.0",
24
- "@babel/helper-module-imports": "^7.28.6",
25
- "@babel/plugin-syntax-jsx": "^7.28.6"
26
- },
27
- "publishConfig": {
28
- "access": "public"
29
- }
30
- }
@@ -1,73 +0,0 @@
1
- import { effect as _effect, renderDynamic as _renderDynamic, signal as _signal, createPropsProxy as _createPropsProxy, withInstance as _withInstance } from "../index.js";
2
- import { navigate } from "./index";
3
- class LinkElement extends HTMLElement {
4
- static observedAttributes = ["href", "className", "style", "children"];
5
- set href(val) {
6
- this._propsSignals["href"].value = val;
7
- }
8
- set className(val) {
9
- this._propsSignals["className"].value = val;
10
- this.setAttribute("class", val);
11
- }
12
- set style(val) {
13
- this._propsSignals["style"].value = val;
14
- if (val && typeof val === "object") Object.assign(this.style, val);
15
- }
16
- set children(val) {
17
- this._propsSignals["children"].value = val;
18
- }
19
- get href() {
20
- return this._propsSignals["href"].value;
21
- }
22
- get className() {
23
- return this._propsSignals["className"].value;
24
- }
25
- get style() {
26
- return this._propsSignals["style"].value;
27
- }
28
- get children() {
29
- return this._propsSignals["children"].value;
30
- }
31
- constructor() {
32
- super();
33
- this._propsSignals = {
34
- href: _signal(null),
35
- className: _signal(null),
36
- style: _signal(null),
37
- children: _signal(null)
38
- };
39
- }
40
- attributeChangedCallback(name, _, value) {
41
- this._propsSignals[name].value = value;
42
- }
43
- connectedCallback() {
44
- this._onMounts = [];
45
- this._onCleanups = [];
46
- const props = _createPropsProxy(this);
47
- this._children = Array.from(this.childNodes);
48
- while (this.firstChild) this.removeChild(this.firstChild);
49
- _withInstance(this, () => {
50
- const rootElement = (() => {
51
- const el0 = document.createElement("a");
52
- _effect(() => el0.href = props.href);
53
- _effect(() => el0.className = props.className);
54
- _effect(() => Object.assign(el0.style, props.style));
55
- el0.onclick = e => {
56
- if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;
57
- e.preventDefault();
58
- navigate(props.href);
59
- window.scrollTo(0, 0);
60
- };
61
- _renderDynamic(el0, () => props.children);
62
- return el0;
63
- })();
64
- this.appendChild(rootElement);
65
- });
66
- this._onMounts.forEach(fn => fn());
67
- }
68
- disconnectedCallback() {
69
- this._onCleanups.forEach(fn => fn());
70
- }
71
- }
72
- customElements.define("web-link", LinkElement);
73
- export default LinkElement;
@@ -1,216 +0,0 @@
1
- import { withInstance } from '../runtime/lifecycle.js';
2
- import { signal } from '@preact/signals-core';
3
- export const routes = {
4
- pages: {},
5
- layouts: {},
6
- notFound: null
7
- };
8
- const routerSignals = {
9
- pathname: signal(window.location.pathname),
10
- searchParams: signal(new URLSearchParams(window.location.search)),
11
- hash: signal(window.location.hash),
12
- isGuarding: signal(false),
13
- guard: null
14
- };
15
- export const router = new Proxy(routerSignals, {
16
- get: (target, key) => {
17
- if (key === "push") return path => navigate(path);
18
- if (key === "replace") return path => navigate(path, undefined, true);
19
- const s = target[key];
20
- if (s && typeof s === 'object' && 'value' in s && typeof s.subscribe === 'function') {
21
- return s.value;
22
- }
23
- return s;
24
- },
25
- set: (target, key, val) => {
26
- const s = target[key];
27
- if (s && typeof s === 'object' && 'value' in s && typeof s.subscribe === 'function') {
28
- s.value = val;
29
- } else {
30
- target[key] = val;
31
- }
32
- return true;
33
- }
34
- });
35
- let currentPageInstance = null;
36
- export function registerRoutes(pages) {
37
- for (const path in pages) {
38
- const isLayout = path.endsWith('layout.jsx') || path.endsWith('layout.tsx');
39
- const isNotFound = path.endsWith('404.jsx') || path.endsWith('404.tsx');
40
- let route = path.replace(/^.*\/app/, '').replace(/\/(page|layout|404)\.(jsx|tsx)$/, '');
41
- if (route === '') route = '/';
42
- if (isNotFound) {
43
- routes.notFound = pages[path];
44
- } else if (isLayout) {
45
- routes.layouts[route] = pages[path];
46
- } else {
47
- routes.pages[route] = pages[path];
48
- }
49
- }
50
- }
51
- function matchRoute(path) {
52
- for (const route in routes.pages) {
53
- const pattern = route.replace(/\[\.\.\.([^\]]+)\]/g, '(?<$1>.+)').replace(/\[([^\]]+)\]/g, '(?<$1>[^/]+)');
54
- const regex = new RegExp(`^${pattern}/?$`);
55
- const match = path.match(regex);
56
- if (match) {
57
- const params = {
58
- ...(match.groups || {})
59
- };
60
- for (const key in params) {
61
- if (route.includes(`[...${key}]`)) {
62
- params[key] = params[key].split('/');
63
- }
64
- }
65
- return {
66
- page: routes.pages[route],
67
- params,
68
- route
69
- };
70
- }
71
- }
72
- return null;
73
- }
74
- function scrollToHash(hash) {
75
- if (!hash) return;
76
- const id = hash.replace('#', '');
77
- const el = document.getElementById(id);
78
- if (el) {
79
- el.scrollIntoView({
80
- behavior: 'smooth'
81
- });
82
- }
83
- }
84
- export async function navigate(path, root = document.getElementById("app"), replace = false, isPopState = false) {
85
- if (!path) return;
86
- const url = new URL(path, window.location.origin);
87
- const pathname = url.pathname;
88
- const oldPathname = window.location.pathname;
89
- const oldSearch = window.location.search;
90
- const oldHash = window.location.hash;
91
-
92
- // 1. Run Route Guard if registered
93
- if (router.guard) {
94
- const match = matchRoute(pathname);
95
- const to = {
96
- path: pathname,
97
- fullPath: pathname + url.search + url.hash,
98
- params: match?.params || {},
99
- query: Object.fromEntries(new URLSearchParams(url.search))
100
- };
101
- routerSignals.isGuarding.value = true;
102
- try {
103
- await new Promise((resolve, reject) => {
104
- const tools = {
105
- next: () => resolve(),
106
- redirect: p => {
107
- navigate(p, root, false);
108
- reject('redirected');
109
- },
110
- replace: p => {
111
- navigate(p, root, true);
112
- reject('redirected');
113
- }
114
- };
115
-
116
- // Execute guard
117
- Promise.resolve(routerSignals.guard(to, tools)).catch(reject);
118
- });
119
- } catch (e) {
120
- routerSignals.isGuarding.value = false;
121
- if (e === 'redirected') return;
122
- console.error('Route Guard Error:', e);
123
- return;
124
- }
125
- routerSignals.isGuarding.value = false;
126
- }
127
- if (!isPopState) {
128
- routerSignals.pathname.value = pathname;
129
- routerSignals.searchParams.value = new URLSearchParams(url.search);
130
- routerSignals.hash.value = url.hash;
131
- }
132
-
133
- // If only hash changed, don't re-render everything
134
- if (pathname === oldPathname && url.search === oldSearch && url.hash !== oldHash) {
135
- if (!isPopState) {
136
- if (replace) window.history.replaceState({}, '', path);else window.history.pushState({}, '', path);
137
- }
138
- scrollToHash(url.hash);
139
- return;
140
- }
141
- const match = matchRoute(pathname) || (routes.notFound ? {
142
- page: routes.notFound,
143
- params: {},
144
- route: null
145
- } : null);
146
- if (match) {
147
- const {
148
- page,
149
- params,
150
- route
151
- } = match;
152
- if (currentPageInstance && currentPageInstance._onCleanups) {
153
- currentPageInstance._onCleanups.forEach(fn => fn());
154
- }
155
- const instance = {
156
- _onMounts: [],
157
- _onCleanups: []
158
- };
159
- currentPageInstance = instance;
160
- const layoutChain = [];
161
- if (route) {
162
- let currentPath = route;
163
- while (true) {
164
- if (routes.layouts[currentPath]) {
165
- layoutChain.unshift(routes.layouts[currentPath]);
166
- }
167
- if (currentPath === '/') break;
168
- currentPath = currentPath.substring(0, currentPath.lastIndexOf('/')) || '/';
169
- }
170
- }
171
- let content = document.createDocumentFragment();
172
- withInstance(instance, () => {
173
- page.render(content, {
174
- params
175
- });
176
- for (let i = layoutChain.length - 1; i >= 0; i--) {
177
- const layout = layoutChain[i];
178
- const nextContent = document.createDocumentFragment();
179
- layout.render(nextContent, {
180
- children: content,
181
- params
182
- });
183
- content = nextContent;
184
- }
185
- });
186
- if (!root) root = document.getElementById("app");
187
- if (root) {
188
- root.innerHTML = '';
189
- root.appendChild(content);
190
- }
191
- if (instance._onMounts) {
192
- instance._onMounts.forEach(fn => fn());
193
- }
194
- if (!isPopState) {
195
- if (replace) window.history.replaceState({}, '', path);else window.history.pushState({}, '', path);
196
- }
197
-
198
- // Scroll to hash after render
199
- setTimeout(() => scrollToHash(url.hash), 100);
200
- } else {
201
- if (root) root.innerHTML = '<h1>404 Not Found</h1>';
202
- }
203
- }
204
- if (typeof window !== 'undefined') {
205
- window.addEventListener('popstate', () => {
206
- const fullPath = window.location.pathname + window.location.search + window.location.hash;
207
- routerSignals.pathname.value = window.location.pathname;
208
- routerSignals.searchParams.value = new URLSearchParams(window.location.search);
209
- routerSignals.hash.value = window.location.hash;
210
- navigate(fullPath, undefined, false, true);
211
- });
212
- window.addEventListener('hashchange', () => {
213
- routerSignals.hash.value = window.location.hash;
214
- scrollToHash(window.location.hash);
215
- });
216
- }
@@ -1,16 +0,0 @@
1
- import { mapped, renderDynamic } from "./dom.js";
2
- import { withInstance } from "./lifecycle.js";
3
- export function For(props) {
4
- // We return a "marker" that renderDynamic will handle
5
- // But wait, if we are called as a component, we need to render something.
6
- return mapped(props.each, props.children[0]);
7
- }
8
-
9
- // Register as a Web Component to support <For> syntax
10
- class ForElement extends HTMLElement {
11
- connectedCallback() {
12
- const each = this._propsSignals?.each;
13
- const renderFn = this.childNodes[0]; // Wait, this is tricky
14
- // ...
15
- }
16
- }
@@ -1,73 +0,0 @@
1
- import { effect } from "@preact/signals-core";
2
- export function renderDynamic(parent, fn) {
3
- const anchor = document.createComment("dynamic");
4
- parent.appendChild(anchor);
5
- let currentNodes = [];
6
- effect(() => {
7
- let value = fn();
8
- // If the value is a function (e.g., from mapped), execute it to get the nodes
9
- if (typeof value === "function") value = value();
10
- if (value === null || value === undefined || value === false) value = [];
11
- if (!Array.isArray(value)) value = [value];
12
- const nextNodes = value.map(v => {
13
- if (v instanceof Node) return v;
14
- return document.createTextNode(String(v));
15
- });
16
- reconcile(anchor.parentNode, anchor, currentNodes, nextNodes);
17
- currentNodes = nextNodes;
18
- });
19
- }
20
- export function mapped(source, fn) {
21
- let cache = new Map(); // key -> { node }
22
-
23
- return () => {
24
- const list = (typeof source === "function" ? source() : source.value) || [];
25
- const nextNodes = [];
26
- const nextCache = new Map();
27
- list.forEach((item, index) => {
28
- const key = item.key ?? item.id ?? index;
29
- let cached = cache.get(key);
30
-
31
- // If the item exists and the reference is identical, reuse the node
32
- if (cached && cached.item === item) {
33
- nextNodes.push(cached.node);
34
- nextCache.set(key, cached);
35
- } else {
36
- // If it's a new item OR the data changed (new reference), re-render
37
- const node = fn(item, index);
38
- node._key = key;
39
- nextNodes.push(node);
40
- nextCache.set(key, {
41
- node,
42
- item
43
- });
44
- }
45
- });
46
-
47
- // Cleanup removed items
48
- cache.forEach((cached, key) => {
49
- if (!nextCache.has(key)) {
50
- if (cached.node.remove) cached.node.remove();
51
- }
52
- });
53
- cache = nextCache;
54
- return nextNodes;
55
- };
56
- }
57
- function reconcile(parent, anchor, oldNodes, nextNodes) {
58
- // 1. Remove nodes that are no longer present
59
- const nextSet = new Set(nextNodes);
60
- oldNodes.forEach(n => {
61
- if (!nextSet.has(n)) n.remove();
62
- });
63
-
64
- // 2. Insert or move nodes from right to left to minimize DOM operations
65
- let current = anchor;
66
- for (let i = nextNodes.length - 1; i >= 0; i--) {
67
- const node = nextNodes[i];
68
- if (node.nextSibling !== current) {
69
- parent.insertBefore(node, current);
70
- }
71
- current = node;
72
- }
73
- }
@@ -1,25 +0,0 @@
1
- let currentInstance = null;
2
- export function withInstance(inst, fn) {
3
- const prev = currentInstance;
4
- currentInstance = inst;
5
- try {
6
- return fn();
7
- } finally {
8
- currentInstance = prev;
9
- }
10
- }
11
- export function onMount(fn) {
12
- if (currentInstance) {
13
- if (!currentInstance._onMounts) currentInstance._onMounts = [];
14
- currentInstance._onMounts.push(fn);
15
- } else {
16
- // If no instance (like in a Page render), run immediately
17
- fn();
18
- }
19
- }
20
- export function onCleanup(fn) {
21
- if (currentInstance) {
22
- if (!currentInstance._onCleanups) currentInstance._onCleanups = [];
23
- currentInstance._onCleanups.push(fn);
24
- }
25
- }
@@ -1,103 +0,0 @@
1
- import { effect, signal } from "@preact/signals-core";
2
- function toKebabCase(str) {
3
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
4
- }
5
- export function createPropsProxy(el) {
6
- if (!el._propsSignals) el._propsSignals = {};
7
- return new Proxy(el, {
8
- get: (target, key) => {
9
- if (key === "__signals") return target._propsSignals;
10
- if (key === "children") return target._children;
11
- if (typeof key === "symbol") return target[key];
12
- if (key === "then") return undefined; // Promise check
13
-
14
- if (!target._propsSignals[key]) {
15
- const initialValue = target.getAttribute(toKebabCase(key)) || target[key];
16
- target._propsSignals[key] = signal(initialValue);
17
- }
18
- return target._propsSignals[key].value;
19
- },
20
- set: (target, key, value) => {
21
- const isSignal = value && typeof value === "object" && "value" in value && typeof value.subscribe === "function";
22
- if (!target._propsSignals[key]) {
23
- target._propsSignals[key] = signal(isSignal ? value.value : value);
24
- }
25
- if (isSignal) {
26
- // Link the internal signal to the external one
27
- if (target._propsSignals[key]._link) target._propsSignals[key]._link(); // Cleanup old link
28
- target._propsSignals[key]._link = effect(() => {
29
- target._propsSignals[key].value = value.value;
30
- });
31
- } else {
32
- target._propsSignals[key].value = value;
33
- }
34
- target[key] = value;
35
- return true;
36
- },
37
- ownKeys: target => {
38
- const keys = new Set(Object.keys(target));
39
- Object.keys(target._propsSignals).forEach(k => keys.add(k));
40
- keys.add("children");
41
- return Array.from(keys).filter(k => !k.startsWith("_"));
42
- },
43
- getOwnPropertyDescriptor: (target, key) => {
44
- return {
45
- enumerable: true,
46
- configurable: true
47
- };
48
- }
49
- });
50
- }
51
- export function applySpread(el, props) {
52
- if (!props) return;
53
- const signals = props?.__signals;
54
- Object.keys(props).forEach(key => {
55
- let value = props[key];
56
- if (signals && signals[key]) value = signals[key];
57
- const name = key.toLowerCase();
58
- const attrProp = name === "class" || name === "classname" ? "className" : key;
59
- const isProperty = ["className", "style", "value", "checked", "id", "title", "href", "src", "key"].includes(attrProp);
60
- const isEvent = name.startsWith("on");
61
-
62
- // Simple heuristic for signal detection: object with .value and .subscribe
63
- const isSignal = value && typeof value === "object" && "value" in value && typeof value.subscribe === "function";
64
- if (isSignal) {
65
- effect(() => {
66
- const val = value.value;
67
- if (isProperty) {
68
- if (attrProp === "style" && val && typeof val === "object") {
69
- Object.assign(el.style, val);
70
- } else if (attrProp === "className" && el instanceof SVGElement) {
71
- el.setAttribute("class", val);
72
- } else {
73
- el[attrProp] = val;
74
- }
75
- } else {
76
- if (val === null || val === undefined || val === false) {
77
- el.removeAttribute(key);
78
- } else {
79
- el.setAttribute(key, val);
80
- }
81
- }
82
- });
83
- } else {
84
- if (isEvent) {
85
- el[name] = value;
86
- } else if (isProperty) {
87
- if (attrProp === "style" && value && typeof value === "object") {
88
- Object.assign(el.style, value);
89
- } else if (attrProp === "className" && el instanceof SVGElement) {
90
- el.setAttribute("class", value);
91
- } else {
92
- el[attrProp] = value;
93
- }
94
- } else {
95
- if (value === null || value === undefined || value === false) {
96
- el.removeAttribute(key);
97
- } else {
98
- el.setAttribute(key, value);
99
- }
100
- }
101
- }
102
- });
103
- }