@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
@@ -0,0 +1,29 @@
1
+ // <Link href> — client-side (SPA) navigation.
2
+ //
3
+ // A *pure JSX component*, shipped as source and compiled by the consuming app's
4
+ // pipeline (like a UI library ships components for the user's bundler). The
5
+ // framework dogfoods its own JSX → Custom Element model here instead of a
6
+ // hand-written HTMLElement class — so it gets reactive props, the children slot,
7
+ // and a matching SSG renderer for free, with no separate runtime build step.
8
+ //
9
+ // `class` is a declared prop, so it lands on the rendered <a> (the link), not on
10
+ // the <web-link> host — no duplicated styling.
11
+
12
+ import { navigate } from "../runtime/router.js";
13
+
14
+ export default function Link({ href, class: className, children }) {
15
+ const onclick = (e) => {
16
+ // Let the browser handle modified clicks (new tab, etc.) and non-primary buttons.
17
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
18
+ return;
19
+ }
20
+ e.preventDefault();
21
+ navigate(href);
22
+ };
23
+
24
+ return (
25
+ <a href={href} class={className} onclick={onclick}>
26
+ {children}
27
+ </a>
28
+ );
29
+ }
package/core/errors.js ADDED
@@ -0,0 +1,48 @@
1
+ //! Central error reporting for the OTF Web runtime.
2
+ //
3
+ // Render, effect, lifecycle, and event errors are funneled here instead of being
4
+ // thrown into the void. Userland (and the dev overlay) subscribe via `onError`;
5
+ // in the browser every report is also dispatched as an `otfw:error` window event
6
+ // so a fully decoupled listener (e.g. the dev server's injected overlay) can show
7
+ // it without importing the bundle.
8
+
9
+ const handlers = new Set();
10
+ const isBrowser = typeof window !== "undefined";
11
+
12
+ /**
13
+ * Subscribe to runtime errors. `handler(error, context)` is called for every
14
+ * `reportError`. Returns a disposer that unsubscribes.
15
+ */
16
+ export function onError(handler) {
17
+ handlers.add(handler);
18
+ return () => handlers.delete(handler);
19
+ }
20
+
21
+ /**
22
+ * Report a caught error. `context` carries where it happened
23
+ * (`{ phase: "render" | "effect" | "mount" | "event" | "route", … }`).
24
+ * Notifies subscribers, emits the `otfw:error` window event, and — if nobody is
25
+ * listening — logs to the console so errors are never silently swallowed.
26
+ */
27
+ export function reportError(error, context = {}) {
28
+ for (const handler of handlers) {
29
+ try {
30
+ handler(error, context);
31
+ } catch {
32
+ // A failing handler must not mask the original error.
33
+ }
34
+ }
35
+ if (isBrowser) {
36
+ window.dispatchEvent(new CustomEvent("otfw:error", { detail: { error, context } }));
37
+ }
38
+ if (handlers.size === 0) {
39
+ console.error(`[otfw${context.phase ? `:${context.phase}` : ""}]`, error);
40
+ }
41
+ }
42
+
43
+ /** Signal that the error condition has cleared (e.g. a successful navigation). */
44
+ export function clearError(context = {}) {
45
+ if (isBrowser) {
46
+ window.dispatchEvent(new CustomEvent("otfw:error-clear", { detail: context }));
47
+ }
48
+ }
@@ -0,0 +1,188 @@
1
+ //! Fine-grained reactivity — our own signals core.
2
+ //
3
+ // This replaces the previous `@preact/signals-core` dependency to remove a
4
+ // third-party supply-chain risk (SPEC §1.1). The public surface is the
5
+ // industry-standard `signal` / `computed` / `effect` with a `.value` accessor;
6
+ // more APIs (peek/untracked/subscribe) are added only when a consumer needs them.
7
+ //
8
+ // Model: producers (signals, computeds) hold a set of subscribers; consumers
9
+ // (effects, computeds) track the producers they read. Reads register a
10
+ // dependency on the active consumer; writes mark dependent computeds stale and
11
+ // queue dependent effects, which run once per change/batch (glitch-free: an
12
+ // effect that depends on a signal both directly and via a computed runs once).
13
+
14
+ import { reportError } from "./errors.js";
15
+
16
+ /** The consumer (effect or computed) currently executing, if any. */
17
+ let activeConsumer = null;
18
+ /** Depth of nested `batch()` calls; effects flush when this returns to 0. */
19
+ let batchDepth = 0;
20
+ /** Re-entrancy guard for the flush loop. */
21
+ let flushing = false;
22
+ /** Effects queued to run on the next flush. A Set so each runs at most once. */
23
+ const pendingEffects = new Set();
24
+
25
+ /** Register `producer` as a dependency of the consumer currently running. */
26
+ function track(producer) {
27
+ if (activeConsumer) {
28
+ activeConsumer.sources.add(producer);
29
+ producer.subscribers.add(activeConsumer);
30
+ }
31
+ }
32
+
33
+ /** Detach a consumer from all producers it previously read. */
34
+ function clearSources(consumer) {
35
+ for (const source of consumer.sources) {
36
+ source.subscribers.delete(consumer);
37
+ }
38
+ consumer.sources.clear();
39
+ }
40
+
41
+ /** Mark dependents stale: computeds recursively, effects into the run queue. */
42
+ function propagate(producer) {
43
+ for (const sub of producer.subscribers) {
44
+ if (sub.kind === "computed") {
45
+ if (!sub.stale) {
46
+ sub.stale = true;
47
+ propagate(sub);
48
+ }
49
+ } else {
50
+ pendingEffects.add(sub);
51
+ }
52
+ }
53
+ }
54
+
55
+ /** Run all queued effects, including any they queue in turn. */
56
+ function flush() {
57
+ if (flushing) return;
58
+ flushing = true;
59
+ try {
60
+ while (pendingEffects.size > 0) {
61
+ const batch = [...pendingEffects];
62
+ pendingEffects.clear();
63
+ for (const effectNode of batch) {
64
+ if (effectNode.disposed) continue;
65
+ // A throwing reactive update is reported but must not break the scheduler
66
+ // (other queued effects still run). Build-time (initial) runs propagate so
67
+ // the surrounding component/page catch can handle them.
68
+ try {
69
+ effectNode.run();
70
+ } catch (e) {
71
+ reportError(e, { phase: "effect" });
72
+ }
73
+ }
74
+ }
75
+ } finally {
76
+ flushing = false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * A writable reactive value. Reading `.value` inside an effect/computed
82
+ * subscribes to it; writing a changed `.value` notifies dependents.
83
+ */
84
+ export function signal(initial) {
85
+ const node = { kind: "signal", value: initial, subscribers: new Set() };
86
+ return {
87
+ get value() {
88
+ track(node);
89
+ return node.value;
90
+ },
91
+ set value(next) {
92
+ if (Object.is(next, node.value)) return;
93
+ node.value = next;
94
+ propagate(node);
95
+ if (batchDepth === 0) flush();
96
+ },
97
+ };
98
+ }
99
+
100
+ /**
101
+ * A derived, read-only reactive value. Lazy and cached: the compute function
102
+ * runs only when `.value` is read while stale, and recomputes only after a
103
+ * dependency changes.
104
+ */
105
+ export function computed(fn) {
106
+ const node = {
107
+ kind: "computed",
108
+ fn,
109
+ value: undefined,
110
+ stale: true,
111
+ sources: new Set(),
112
+ subscribers: new Set(),
113
+ };
114
+ return {
115
+ get value() {
116
+ if (node.stale) {
117
+ const prev = activeConsumer;
118
+ clearSources(node);
119
+ activeConsumer = node;
120
+ try {
121
+ node.value = node.fn();
122
+ node.stale = false;
123
+ } finally {
124
+ activeConsumer = prev;
125
+ }
126
+ }
127
+ track(node);
128
+ return node.value;
129
+ },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Run `fn` immediately, tracking the signals it reads, and re-run it whenever
135
+ * any of them change. `fn` may return a cleanup function, which runs before the
136
+ * next re-run and on dispose. Returns a disposer that stops the effect.
137
+ */
138
+ export function effect(fn) {
139
+ const node = {
140
+ kind: "effect",
141
+ fn,
142
+ cleanup: undefined,
143
+ sources: new Set(),
144
+ disposed: false,
145
+ run() {
146
+ if (node.disposed) return;
147
+ runCleanup(node);
148
+ clearSources(node);
149
+ const prev = activeConsumer;
150
+ activeConsumer = node;
151
+ try {
152
+ const result = node.fn();
153
+ if (typeof result === "function") node.cleanup = result;
154
+ } finally {
155
+ activeConsumer = prev;
156
+ }
157
+ },
158
+ };
159
+ node.run();
160
+ return function dispose() {
161
+ if (node.disposed) return;
162
+ node.disposed = true;
163
+ runCleanup(node);
164
+ clearSources(node);
165
+ };
166
+ }
167
+
168
+ function runCleanup(node) {
169
+ if (node.cleanup) {
170
+ const fn = node.cleanup;
171
+ node.cleanup = undefined;
172
+ fn();
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Group multiple writes so dependent effects run once after the outermost batch
178
+ * completes, instead of after each write.
179
+ */
180
+ export function batch(fn) {
181
+ batchDepth++;
182
+ try {
183
+ return fn();
184
+ } finally {
185
+ batchDepth--;
186
+ if (batchDepth === 0) flush();
187
+ }
188
+ }
package/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Public entry for the OTF Web runtime: reactivity + DOM operations.
2
+ // CSR-generated modules import their helpers (signal/computed/bindText/…) from here.
3
+ export * from "./core/signals.js";
4
+ export * from "./core/errors.js";
5
+ export * from "./runtime/index.js";
6
+ // `Link` is shipped as JSX source and compiled by the consuming app's pipeline
7
+ // (the `.jsx` lands in the module graph and otfwc transforms it). Its compiled
8
+ // module self-registers `web-link` (CSR `customElements.define` / SSG `defineSSG`).
9
+ // The bare import forces that registration to be retained — `<Link>` is used as a
10
+ // tag, so the `Link` binding alone would be tree-shaken.
11
+ import "./components/Link.jsx";
12
+ export { default as Link } from "./components/Link.jsx";
package/package.json CHANGED
@@ -1,29 +1,38 @@
1
1
  {
2
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",
3
+ "version": "0.5.0",
4
+ "description": "The native-first OTF Web runtime signal-based reactivity and zero-VDOM DOM operations, paired with the IR-based compiler.",
7
5
  "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
6
  "exports": {
13
- ".": "./dist/index.js",
14
- "./compiler": "./dist/compiler/babel-plugin.cjs",
15
- "./runtime": "./dist/runtime/index.js",
16
- "./router": "./dist/router/index.js"
7
+ ".": "./index.js",
8
+ "./signals": "./core/signals.js",
9
+ "./runtime": "./runtime/index.js",
10
+ "./server": "./server/index.js"
17
11
  },
18
12
  "files": [
19
- "dist"
13
+ "index.js",
14
+ "core",
15
+ "runtime",
16
+ "components",
17
+ "server",
18
+ "!**/*.test.js"
19
+ ],
20
+ "keywords": [
21
+ "opentf",
22
+ "web-components",
23
+ "signals",
24
+ "reactivity",
25
+ "runtime",
26
+ "ssg"
20
27
  ],
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"
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/Open-Tech-Foundation/Web-App-Framework.git",
31
+ "directory": "packages/web"
26
32
  },
33
+ "homepage": "https://github.com/Open-Tech-Foundation/Web-App-Framework#readme",
34
+ "bugs": "https://github.com/Open-Tech-Foundation/Web-App-Framework/issues",
35
+ "license": "MIT",
27
36
  "publishConfig": {
28
37
  "access": "public"
29
38
  }
@@ -0,0 +1,89 @@
1
+ //! Context API — scoped dependency injection over the component tree.
2
+ //
3
+ // Unlike a module-level signal (which is global) or a prop (which must be drilled),
4
+ // context provides a value to a *subtree* and lets a nested provider override it.
5
+ // Because components compile to Custom Elements, the component tree *is* the DOM
6
+ // tree, so we resolve a consumer's provider with `closest()` — no special graph.
7
+ //
8
+ // const ThemeContext = createContext("dark");
9
+ // <ContextProvider context={ThemeContext} value={theme}> … </ContextProvider>
10
+ // const theme = $context(ThemeContext); // compiler macro; use the bare name
11
+ //
12
+ // `value` flows through the provider's element as a signal, so a reactive `value`
13
+ // (e.g. backed by `$state`) updates every consumer fine-grained.
14
+
15
+ import { signal } from "../core/signals.js";
16
+
17
+ let nextId = 0;
18
+
19
+ // Stack of the elements whose component bodies are currently executing. A stack
20
+ // (not a single slot) because filling a component's children slot connects nested
21
+ // components synchronously inside the parent's connect — each push is balanced by
22
+ // a pop in the generated `finally`, so the parent's host is restored after.
23
+ const hostStack = [];
24
+
25
+ /** Push the current component host (compiler-emitted, paired with `exitHost`). */
26
+ export function enterHost(el) {
27
+ hostStack.push(el);
28
+ }
29
+
30
+ /** Pop the current component host (compiler-emitted, in a `finally`). */
31
+ export function exitHost() {
32
+ hostStack.pop();
33
+ }
34
+
35
+ /**
36
+ * Create a context with a default value. The returned token is passed to
37
+ * `<ContextProvider context={…}>` and `$context(…)`.
38
+ */
39
+ export function createContext(defaultValue) {
40
+ return { id: `otfw-ctx-${nextId++}`, fallback: signal(defaultValue) };
41
+ }
42
+
43
+ /**
44
+ * Resolve the value provided by the nearest ancestor `ContextProvider` for
45
+ * `context`, or the context's default if there is none. Returns a **signal**. This
46
+ * is what the `$context(Ctx)` compiler macro lowers to; the macro registers the
47
+ * binding as a signal so the bare name reads `.value` reactively.
48
+ */
49
+ export function readContext(context) {
50
+ let el = hostStack[hostStack.length - 1];
51
+ const sel = `[data-otfw-ctx~="${context.id}"]`;
52
+ // Walk up the DOM; when the search reaches a portal boundary without finding a
53
+ // provider, hop to the portal's logical location (`__portalHost`) and continue,
54
+ // so context resolves across a <Portal> as if the content were still in place.
55
+ while (el) {
56
+ const provider = el.closest(sel);
57
+ if (provider) return provider._signal;
58
+ const boundary = el.closest("[data-otfw-portal]");
59
+ el = boundary ? boundary.__portalHost : null;
60
+ }
61
+ return context.fallback;
62
+ }
63
+
64
+ // `<ContextProvider context={Ctx} value={v}>`: a host element that publishes `v`
65
+ // (as a signal) for `Ctx` to its subtree. `context`/`value` arrive as element
66
+ // properties (the compiler sets component props reactively), so a changing `value`
67
+ // propagates to consumers automatically.
68
+ export class ContextProviderElement extends HTMLElement {
69
+ constructor() {
70
+ super();
71
+ this._signal = signal(undefined);
72
+ }
73
+ set context(ctx) {
74
+ if (ctx && ctx.id) this.setAttribute("data-otfw-ctx", ctx.id);
75
+ }
76
+ set value(v) {
77
+ this._signal.value = v;
78
+ }
79
+ get value() {
80
+ return this._signal.value;
81
+ }
82
+ }
83
+
84
+ // Exported so `import { ContextProvider } from "@opentf/web"` resolves; the import
85
+ // side effect registers the element (JSX uses the `web-context-provider` tag).
86
+ export const ContextProvider = ContextProviderElement;
87
+ if (typeof customElements !== "undefined" && !customElements.get("web-context-provider")) {
88
+ customElements.define("web-context-provider", ContextProviderElement);
89
+ }