@opentf/web 0.4.0 → 0.5.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 (181) hide show
  1. package/components/Link.jsx +29 -0
  2. package/core/errors.js +48 -0
  3. package/core/signals.js +188 -0
  4. package/index.js +12 -0
  5. package/package.json +27 -18
  6. package/runtime/context.js +89 -0
  7. package/runtime/dom.js +280 -0
  8. package/runtime/error-boundary.js +109 -0
  9. package/runtime/events.js +17 -0
  10. package/runtime/index.js +16 -0
  11. package/runtime/lifecycle.js +9 -0
  12. package/runtime/mount.js +49 -0
  13. package/runtime/portal.js +59 -0
  14. package/runtime/router.js +246 -0
  15. package/server/builtins.js +11 -0
  16. package/server/index.js +7 -0
  17. package/server/render.js +76 -0
  18. package/server/ssg-runtime.js +109 -0
  19. package/README.md +0 -41
  20. package/dist/CHANGELOG.md +0 -49
  21. package/dist/README.md +0 -41
  22. package/dist/babel.config.cjs +0 -6
  23. package/dist/compiler/babel-plugin.cjs +0 -798
  24. package/dist/core/mount.js +0 -32
  25. package/dist/index.js +0 -7
  26. package/dist/node_modules/@babel/core/LICENSE +0 -22
  27. package/dist/node_modules/@babel/core/README.md +0 -19
  28. package/dist/node_modules/@babel/core/lib/config/cache-contexts.js +0 -5
  29. package/dist/node_modules/@babel/core/lib/config/cache-contexts.js.map +0 -1
  30. package/dist/node_modules/@babel/core/lib/config/caching.js +0 -261
  31. package/dist/node_modules/@babel/core/lib/config/caching.js.map +0 -1
  32. package/dist/node_modules/@babel/core/lib/config/config-chain.js +0 -469
  33. package/dist/node_modules/@babel/core/lib/config/config-chain.js.map +0 -1
  34. package/dist/node_modules/@babel/core/lib/config/config-descriptors.js +0 -190
  35. package/dist/node_modules/@babel/core/lib/config/config-descriptors.js.map +0 -1
  36. package/dist/node_modules/@babel/core/lib/config/files/configuration.js +0 -290
  37. package/dist/node_modules/@babel/core/lib/config/files/configuration.js.map +0 -1
  38. package/dist/node_modules/@babel/core/lib/config/files/import.cjs +0 -6
  39. package/dist/node_modules/@babel/core/lib/config/files/import.cjs.map +0 -1
  40. package/dist/node_modules/@babel/core/lib/config/files/index-browser.js +0 -58
  41. package/dist/node_modules/@babel/core/lib/config/files/index-browser.js.map +0 -1
  42. package/dist/node_modules/@babel/core/lib/config/files/index.js +0 -78
  43. package/dist/node_modules/@babel/core/lib/config/files/index.js.map +0 -1
  44. package/dist/node_modules/@babel/core/lib/config/files/module-types.js +0 -203
  45. package/dist/node_modules/@babel/core/lib/config/files/module-types.js.map +0 -1
  46. package/dist/node_modules/@babel/core/lib/config/files/package.js +0 -61
  47. package/dist/node_modules/@babel/core/lib/config/files/package.js.map +0 -1
  48. package/dist/node_modules/@babel/core/lib/config/files/plugins.js +0 -220
  49. package/dist/node_modules/@babel/core/lib/config/files/plugins.js.map +0 -1
  50. package/dist/node_modules/@babel/core/lib/config/files/types.js +0 -5
  51. package/dist/node_modules/@babel/core/lib/config/files/types.js.map +0 -1
  52. package/dist/node_modules/@babel/core/lib/config/files/utils.js +0 -36
  53. package/dist/node_modules/@babel/core/lib/config/files/utils.js.map +0 -1
  54. package/dist/node_modules/@babel/core/lib/config/full.js +0 -312
  55. package/dist/node_modules/@babel/core/lib/config/full.js.map +0 -1
  56. package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js +0 -85
  57. package/dist/node_modules/@babel/core/lib/config/helpers/config-api.js.map +0 -1
  58. package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js +0 -23
  59. package/dist/node_modules/@babel/core/lib/config/helpers/deep-array.js.map +0 -1
  60. package/dist/node_modules/@babel/core/lib/config/helpers/environment.js +0 -12
  61. package/dist/node_modules/@babel/core/lib/config/helpers/environment.js.map +0 -1
  62. package/dist/node_modules/@babel/core/lib/config/index.js +0 -87
  63. package/dist/node_modules/@babel/core/lib/config/index.js.map +0 -1
  64. package/dist/node_modules/@babel/core/lib/config/item.js +0 -67
  65. package/dist/node_modules/@babel/core/lib/config/item.js.map +0 -1
  66. package/dist/node_modules/@babel/core/lib/config/partial.js +0 -158
  67. package/dist/node_modules/@babel/core/lib/config/partial.js.map +0 -1
  68. package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js +0 -38
  69. package/dist/node_modules/@babel/core/lib/config/pattern-to-regex.js.map +0 -1
  70. package/dist/node_modules/@babel/core/lib/config/plugin.js +0 -33
  71. package/dist/node_modules/@babel/core/lib/config/plugin.js.map +0 -1
  72. package/dist/node_modules/@babel/core/lib/config/printer.js +0 -113
  73. package/dist/node_modules/@babel/core/lib/config/printer.js.map +0 -1
  74. package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js +0 -41
  75. package/dist/node_modules/@babel/core/lib/config/resolve-targets-browser.js.map +0 -1
  76. package/dist/node_modules/@babel/core/lib/config/resolve-targets.js +0 -61
  77. package/dist/node_modules/@babel/core/lib/config/resolve-targets.js.map +0 -1
  78. package/dist/node_modules/@babel/core/lib/config/util.js +0 -31
  79. package/dist/node_modules/@babel/core/lib/config/util.js.map +0 -1
  80. package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js +0 -277
  81. package/dist/node_modules/@babel/core/lib/config/validation/option-assertions.js.map +0 -1
  82. package/dist/node_modules/@babel/core/lib/config/validation/options.js +0 -187
  83. package/dist/node_modules/@babel/core/lib/config/validation/options.js.map +0 -1
  84. package/dist/node_modules/@babel/core/lib/config/validation/plugins.js +0 -67
  85. package/dist/node_modules/@babel/core/lib/config/validation/plugins.js.map +0 -1
  86. package/dist/node_modules/@babel/core/lib/config/validation/removed.js +0 -68
  87. package/dist/node_modules/@babel/core/lib/config/validation/removed.js.map +0 -1
  88. package/dist/node_modules/@babel/core/lib/errors/config-error.js +0 -18
  89. package/dist/node_modules/@babel/core/lib/errors/config-error.js.map +0 -1
  90. package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js +0 -98
  91. package/dist/node_modules/@babel/core/lib/errors/rewrite-stack-trace.js.map +0 -1
  92. package/dist/node_modules/@babel/core/lib/gensync-utils/async.js +0 -90
  93. package/dist/node_modules/@babel/core/lib/gensync-utils/async.js.map +0 -1
  94. package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js +0 -31
  95. package/dist/node_modules/@babel/core/lib/gensync-utils/fs.js.map +0 -1
  96. package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js +0 -58
  97. package/dist/node_modules/@babel/core/lib/gensync-utils/functional.js.map +0 -1
  98. package/dist/node_modules/@babel/core/lib/index.js +0 -230
  99. package/dist/node_modules/@babel/core/lib/index.js.map +0 -1
  100. package/dist/node_modules/@babel/core/lib/parse.js +0 -45
  101. package/dist/node_modules/@babel/core/lib/parse.js.map +0 -1
  102. package/dist/node_modules/@babel/core/lib/parser/index.js +0 -85
  103. package/dist/node_modules/@babel/core/lib/parser/index.js.map +0 -1
  104. package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js +0 -337
  105. package/dist/node_modules/@babel/core/lib/parser/util/missing-plugin-helper.js.map +0 -1
  106. package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js +0 -144
  107. package/dist/node_modules/@babel/core/lib/tools/build-external-helpers.js.map +0 -1
  108. package/dist/node_modules/@babel/core/lib/transform-ast.js +0 -48
  109. package/dist/node_modules/@babel/core/lib/transform-ast.js.map +0 -1
  110. package/dist/node_modules/@babel/core/lib/transform-file-browser.js +0 -23
  111. package/dist/node_modules/@babel/core/lib/transform-file-browser.js.map +0 -1
  112. package/dist/node_modules/@babel/core/lib/transform-file.js +0 -40
  113. package/dist/node_modules/@babel/core/lib/transform-file.js.map +0 -1
  114. package/dist/node_modules/@babel/core/lib/transform.js +0 -47
  115. package/dist/node_modules/@babel/core/lib/transform.js.map +0 -1
  116. package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js +0 -84
  117. package/dist/node_modules/@babel/core/lib/transformation/block-hoist-plugin.js.map +0 -1
  118. package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs +0 -4
  119. package/dist/node_modules/@babel/core/lib/transformation/file/babel-7-helpers.cjs.map +0 -1
  120. package/dist/node_modules/@babel/core/lib/transformation/file/file.js +0 -204
  121. package/dist/node_modules/@babel/core/lib/transformation/file/file.js.map +0 -1
  122. package/dist/node_modules/@babel/core/lib/transformation/file/generate.js +0 -84
  123. package/dist/node_modules/@babel/core/lib/transformation/file/generate.js.map +0 -1
  124. package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js +0 -37
  125. package/dist/node_modules/@babel/core/lib/transformation/file/merge-map.js.map +0 -1
  126. package/dist/node_modules/@babel/core/lib/transformation/index.js +0 -90
  127. package/dist/node_modules/@babel/core/lib/transformation/index.js.map +0 -1
  128. package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js +0 -127
  129. package/dist/node_modules/@babel/core/lib/transformation/normalize-file.js.map +0 -1
  130. package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js +0 -59
  131. package/dist/node_modules/@babel/core/lib/transformation/normalize-opts.js.map +0 -1
  132. package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js +0 -48
  133. package/dist/node_modules/@babel/core/lib/transformation/plugin-pass.js.map +0 -1
  134. package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js +0 -54
  135. package/dist/node_modules/@babel/core/lib/transformation/util/clone-deep.js.map +0 -1
  136. package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js +0 -1042
  137. package/dist/node_modules/@babel/core/lib/vendor/import-meta-resolve.js.map +0 -1
  138. package/dist/node_modules/@babel/core/package.json +0 -82
  139. package/dist/node_modules/@babel/core/src/config/files/index-browser.ts +0 -115
  140. package/dist/node_modules/@babel/core/src/config/files/index.ts +0 -30
  141. package/dist/node_modules/@babel/core/src/config/resolve-targets-browser.ts +0 -42
  142. package/dist/node_modules/@babel/core/src/config/resolve-targets.ts +0 -54
  143. package/dist/node_modules/@babel/core/src/transform-file-browser.ts +0 -33
  144. package/dist/node_modules/@babel/core/src/transform-file.ts +0 -56
  145. package/dist/node_modules/@babel/helper-module-imports/LICENSE +0 -22
  146. package/dist/node_modules/@babel/helper-module-imports/README.md +0 -19
  147. package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js +0 -122
  148. package/dist/node_modules/@babel/helper-module-imports/lib/import-builder.js.map +0 -1
  149. package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js +0 -304
  150. package/dist/node_modules/@babel/helper-module-imports/lib/import-injector.js.map +0 -1
  151. package/dist/node_modules/@babel/helper-module-imports/lib/index.js +0 -37
  152. package/dist/node_modules/@babel/helper-module-imports/lib/index.js.map +0 -1
  153. package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js +0 -11
  154. package/dist/node_modules/@babel/helper-module-imports/lib/is-module.js.map +0 -1
  155. package/dist/node_modules/@babel/helper-module-imports/package.json +0 -28
  156. package/dist/node_modules/@babel/plugin-syntax-jsx/LICENSE +0 -22
  157. package/dist/node_modules/@babel/plugin-syntax-jsx/README.md +0 -19
  158. package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js +0 -21
  159. package/dist/node_modules/@babel/plugin-syntax-jsx/lib/index.js.map +0 -1
  160. package/dist/node_modules/@babel/plugin-syntax-jsx/package.json +0 -33
  161. package/dist/node_modules/@preact/signals-core/CHANGELOG.md +0 -331
  162. package/dist/node_modules/@preact/signals-core/LICENSE +0 -21
  163. package/dist/node_modules/@preact/signals-core/README.md +0 -258
  164. package/dist/node_modules/@preact/signals-core/dist/signals-core.d.ts +0 -149
  165. package/dist/node_modules/@preact/signals-core/dist/signals-core.js +0 -1
  166. package/dist/node_modules/@preact/signals-core/dist/signals-core.js.map +0 -1
  167. package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js +0 -1
  168. package/dist/node_modules/@preact/signals-core/dist/signals-core.min.js.map +0 -1
  169. package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs +0 -1
  170. package/dist/node_modules/@preact/signals-core/dist/signals-core.mjs.map +0 -1
  171. package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js +0 -1
  172. package/dist/node_modules/@preact/signals-core/dist/signals-core.module.js.map +0 -1
  173. package/dist/node_modules/@preact/signals-core/package.json +0 -48
  174. package/dist/node_modules/@preact/signals-core/src/index.ts +0 -1121
  175. package/dist/package.json +0 -30
  176. package/dist/router/Link.js +0 -73
  177. package/dist/router/index.js +0 -216
  178. package/dist/runtime/For.js +0 -16
  179. package/dist/runtime/dom.js +0 -73
  180. package/dist/runtime/lifecycle.js +0 -25
  181. 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
- }