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