@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.5

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 (244) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +1 -1
  3. package/dist/actions.d.ts +32 -30
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +251 -266
  6. package/dist/actions.js.map +1 -1
  7. package/dist/async.d.ts +20 -17
  8. package/dist/async.d.ts.map +1 -1
  9. package/dist/async.js +18 -17
  10. package/dist/async.js.map +1 -1
  11. package/dist/compiled/async-resource.d.ts +9 -6
  12. package/dist/compiled/async-resource.d.ts.map +1 -1
  13. package/dist/compiled/async-resource.js +69 -77
  14. package/dist/compiled/async-resource.js.map +1 -1
  15. package/dist/compiled/core.d.ts +136 -147
  16. package/dist/compiled/core.d.ts.map +1 -1
  17. package/dist/compiled/core.js +2672 -2985
  18. package/dist/compiled/core.js.map +1 -1
  19. package/dist/compiled/types.d.ts +44 -56
  20. package/dist/compiled/types.d.ts.map +1 -1
  21. package/dist/compiled/types.js +10 -6
  22. package/dist/compiled/types.js.map +1 -1
  23. package/dist/compiled/validation.d.ts +4 -1
  24. package/dist/compiled/validation.d.ts.map +1 -1
  25. package/dist/compiled/validation.js +17 -20
  26. package/dist/compiled/validation.js.map +1 -1
  27. package/dist/component-spread.d.ts +5 -3
  28. package/dist/component-spread.d.ts.map +1 -1
  29. package/dist/component-spread.js +49 -67
  30. package/dist/component-spread.js.map +1 -1
  31. package/dist/concurrent.d.ts +20 -18
  32. package/dist/concurrent.d.ts.map +1 -1
  33. package/dist/concurrent.js +191 -210
  34. package/dist/concurrent.js.map +1 -1
  35. package/dist/direct-dom.d.ts +17 -12
  36. package/dist/direct-dom.d.ts.map +1 -1
  37. package/dist/direct-dom.js +187 -221
  38. package/dist/direct-dom.js.map +1 -1
  39. package/dist/dom/attributes.d.ts +9 -6
  40. package/dist/dom/attributes.d.ts.map +1 -1
  41. package/dist/dom/attributes.js +65 -47
  42. package/dist/dom/attributes.js.map +1 -1
  43. package/dist/dom/events.d.ts +5 -2
  44. package/dist/dom/events.d.ts.map +1 -1
  45. package/dist/dom/events.js +53 -65
  46. package/dist/dom/events.js.map +1 -1
  47. package/dist/dom/forms.d.ts +9 -6
  48. package/dist/dom/forms.d.ts.map +1 -1
  49. package/dist/dom/forms.js +152 -198
  50. package/dist/dom/forms.js.map +1 -1
  51. package/dist/dom/intrinsic.d.ts +17 -15
  52. package/dist/dom/intrinsic.d.ts.map +1 -1
  53. package/dist/dom/intrinsic.js +48 -50
  54. package/dist/dom/intrinsic.js.map +1 -1
  55. package/dist/dom/namespace.d.ts +4 -1
  56. package/dist/dom/namespace.d.ts.map +1 -1
  57. package/dist/dom/namespace.js +25 -28
  58. package/dist/dom/namespace.js.map +1 -1
  59. package/dist/dom/namespaces.d.ts +11 -8
  60. package/dist/dom/namespaces.d.ts.map +1 -1
  61. package/dist/dom/namespaces.js +14 -12
  62. package/dist/dom/namespaces.js.map +1 -1
  63. package/dist/dom/properties.d.ts +18 -16
  64. package/dist/dom/properties.d.ts.map +1 -1
  65. package/dist/dom/properties.js +273 -296
  66. package/dist/dom/properties.js.map +1 -1
  67. package/dist/dom/style-properties.d.ts +4 -1
  68. package/dist/dom/style-properties.d.ts.map +1 -1
  69. package/dist/dom/style-properties.js +49 -45
  70. package/dist/dom/style-properties.js.map +1 -1
  71. package/dist/dom/styles.d.ts +5 -2
  72. package/dist/dom/styles.d.ts.map +1 -1
  73. package/dist/dom/styles.js +47 -67
  74. package/dist/dom/styles.js.map +1 -1
  75. package/dist/dom-forms.d.ts +4 -1
  76. package/dist/dom-forms.d.ts.map +1 -1
  77. package/dist/dom-forms.js +16 -13
  78. package/dist/dom-forms.js.map +1 -1
  79. package/dist/dom-styles.d.ts +4 -1
  80. package/dist/dom-styles.d.ts.map +1 -1
  81. package/dist/dom-styles.js +10 -7
  82. package/dist/dom-styles.js.map +1 -1
  83. package/dist/framework-hydrate.d.ts +20 -17
  84. package/dist/framework-hydrate.d.ts.map +1 -1
  85. package/dist/framework-hydrate.js +128 -146
  86. package/dist/framework-hydrate.js.map +1 -1
  87. package/dist/framework-protocol.d.ts +27 -25
  88. package/dist/framework-protocol.d.ts.map +1 -1
  89. package/dist/framework-protocol.js +173 -211
  90. package/dist/framework-protocol.js.map +1 -1
  91. package/dist/framework.d.ts +34 -32
  92. package/dist/framework.d.ts.map +1 -1
  93. package/dist/framework.js +74 -93
  94. package/dist/framework.js.map +1 -1
  95. package/dist/hydrate.d.ts +11 -8
  96. package/dist/hydrate.d.ts.map +1 -1
  97. package/dist/hydrate.js +13 -9
  98. package/dist/hydrate.js.map +1 -1
  99. package/dist/hydration-bridge.d.ts +70 -58
  100. package/dist/hydration-bridge.d.ts.map +1 -1
  101. package/dist/hydration-bridge.js +77 -66
  102. package/dist/hydration-bridge.js.map +1 -1
  103. package/dist/hydration.d.ts +54 -28
  104. package/dist/hydration.d.ts.map +1 -1
  105. package/dist/hydration.js +450 -463
  106. package/dist/hydration.js.map +1 -1
  107. package/dist/index.d.ts +13 -13
  108. package/dist/index.js +11 -13
  109. package/dist/indexed-list.d.ts +8 -5
  110. package/dist/indexed-list.d.ts.map +1 -1
  111. package/dist/indexed-list.js +14 -7
  112. package/dist/indexed-list.js.map +1 -1
  113. package/dist/jsx-runtime.d.ts +10 -8
  114. package/dist/jsx-runtime.d.ts.map +1 -1
  115. package/dist/jsx-runtime.js +28 -23
  116. package/dist/jsx-runtime.js.map +1 -1
  117. package/dist/keyed-list.d.ts +13 -14
  118. package/dist/keyed-list.d.ts.map +1 -1
  119. package/dist/keyed-list.js +132 -160
  120. package/dist/keyed-list.js.map +1 -1
  121. package/dist/metadata.d.ts +6 -3
  122. package/dist/metadata.d.ts.map +1 -1
  123. package/dist/metadata.js +131 -164
  124. package/dist/metadata.js.map +1 -1
  125. package/dist/nested-prop.d.ts +4 -2
  126. package/dist/nested-prop.d.ts.map +1 -1
  127. package/dist/nested-prop.js +17 -16
  128. package/dist/nested-prop.js.map +1 -1
  129. package/dist/profiling.d.ts +11 -8
  130. package/dist/profiling.d.ts.map +1 -1
  131. package/dist/profiling.js +17 -17
  132. package/dist/profiling.js.map +1 -1
  133. package/dist/protocol.d.ts +5 -2
  134. package/dist/protocol.d.ts.map +1 -1
  135. package/dist/protocol.js +8 -5
  136. package/dist/protocol.js.map +1 -1
  137. package/dist/raw-html.d.ts +5 -2
  138. package/dist/raw-html.d.ts.map +1 -1
  139. package/dist/raw-html.js +86 -146
  140. package/dist/raw-html.js.map +1 -1
  141. package/dist/ref.d.ts +8 -3
  142. package/dist/ref.d.ts.map +1 -1
  143. package/dist/ref.js +10 -2
  144. package/dist/ref.js.map +1 -1
  145. package/dist/renderable-protocol.d.ts +26 -8
  146. package/dist/renderable-protocol.d.ts.map +1 -1
  147. package/dist/renderable-protocol.js +30 -9
  148. package/dist/renderable-protocol.js.map +1 -1
  149. package/dist/renderable.d.ts +27 -22
  150. package/dist/renderable.d.ts.map +1 -1
  151. package/dist/renderable.js +141 -133
  152. package/dist/renderable.js.map +1 -1
  153. package/dist/retained-ui.d.ts +9 -6
  154. package/dist/retained-ui.d.ts.map +1 -1
  155. package/dist/retained-ui.js +12 -10
  156. package/dist/retained-ui.js.map +1 -1
  157. package/dist/root.d.ts +18 -15
  158. package/dist/root.d.ts.map +1 -1
  159. package/dist/root.js +73 -85
  160. package/dist/root.js.map +1 -1
  161. package/dist/scheduler.d.ts +10 -8
  162. package/dist/scheduler.d.ts.map +1 -1
  163. package/dist/scheduler.js +97 -105
  164. package/dist/scheduler.js.map +1 -1
  165. package/dist/server-framework.d.ts +45 -42
  166. package/dist/server-framework.d.ts.map +1 -1
  167. package/dist/server-framework.js +209 -217
  168. package/dist/server-framework.js.map +1 -1
  169. package/dist/server.d.ts +130 -128
  170. package/dist/server.d.ts.map +1 -1
  171. package/dist/server.js +603 -710
  172. package/dist/server.js.map +1 -1
  173. package/dist/shared/promise.d.ts +6 -3
  174. package/dist/shared/promise.d.ts.map +1 -1
  175. package/dist/shared/promise.js +21 -19
  176. package/dist/shared/promise.js.map +1 -1
  177. package/dist/source-mask.d.ts +9 -6
  178. package/dist/source-mask.d.ts.map +1 -1
  179. package/dist/source-mask.js +45 -63
  180. package/dist/source-mask.js.map +1 -1
  181. package/dist/spread.d.ts +5 -2
  182. package/dist/spread.d.ts.map +1 -1
  183. package/dist/spread.js +79 -99
  184. package/dist/spread.js.map +1 -1
  185. package/dist/state-slot.d.ts +18 -15
  186. package/dist/state-slot.d.ts.map +1 -1
  187. package/dist/state-slot.js +43 -42
  188. package/dist/state-slot.js.map +1 -1
  189. package/dist/testing.d.ts +9 -6
  190. package/dist/testing.d.ts.map +1 -1
  191. package/dist/testing.js +16 -12
  192. package/dist/testing.js.map +1 -1
  193. package/package.json +8 -7
  194. package/src/actions.ts +415 -0
  195. package/src/async.ts +78 -0
  196. package/src/compiled/async-resource.ts +113 -0
  197. package/src/compiled/core.ts +4041 -0
  198. package/src/compiled/types.ts +96 -0
  199. package/src/compiled/validation.ts +23 -0
  200. package/src/component-spread.ts +86 -0
  201. package/src/concurrent.ts +310 -0
  202. package/src/direct-dom.ts +365 -0
  203. package/src/dom/attributes.ts +55 -0
  204. package/src/dom/events.ts +93 -0
  205. package/src/dom/forms.ts +220 -0
  206. package/src/dom/intrinsic.ts +94 -0
  207. package/src/dom/namespace.ts +30 -0
  208. package/src/dom/namespaces.ts +14 -0
  209. package/src/dom/properties.ts +355 -0
  210. package/src/dom/style-properties.ts +46 -0
  211. package/src/dom/styles.ts +86 -0
  212. package/src/dom-forms.ts +23 -0
  213. package/src/dom-styles.ts +11 -0
  214. package/src/env.d.ts +6 -0
  215. package/src/framework-hydrate.ts +224 -0
  216. package/src/framework-protocol.ts +335 -0
  217. package/src/framework.ts +161 -0
  218. package/src/hydrate.ts +38 -0
  219. package/src/hydration-bridge.ts +196 -0
  220. package/src/hydration.ts +642 -0
  221. package/src/index.ts +92 -0
  222. package/src/indexed-list.ts +20 -0
  223. package/src/jsx-runtime.ts +55 -0
  224. package/src/keyed-list.ts +233 -0
  225. package/src/metadata.ts +212 -0
  226. package/src/nested-prop.ts +27 -0
  227. package/src/profiling.ts +43 -0
  228. package/src/protocol.ts +8 -0
  229. package/src/raw-html.ts +206 -0
  230. package/src/ref.ts +13 -0
  231. package/src/renderable-protocol.ts +63 -0
  232. package/src/renderable.ts +283 -0
  233. package/src/retained-ui.ts +19 -0
  234. package/src/root.ts +122 -0
  235. package/src/scheduler.ts +136 -0
  236. package/src/server-framework.ts +397 -0
  237. package/src/server.ts +1160 -0
  238. package/src/shared/promise.ts +28 -0
  239. package/src/source-mask.ts +83 -0
  240. package/src/spread.ts +131 -0
  241. package/src/state-slot.ts +76 -0
  242. package/src/testing.ts +27 -0
  243. package/dist/index.d.ts.map +0 -1
  244. package/dist/index.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ import {
2
+ isCompiledBinding,
3
+ mountCompiledPropTransition,
4
+ type CompiledPropTransition,
5
+ } from './compiled/core.ts'
6
+ import { HYDRATION_PREFIX, HydrationMismatch, isHydrating } from './hydration-bridge.ts'
7
+
8
+ const RAW_HTML_PUBLICATION_PRIORITY = 100
9
+ const rawHtmlHosts = new WeakSet<HTMLElement>()
10
+ const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
11
+
12
+ export function mountRawHtmlProp(
13
+ element: HTMLElement,
14
+ value: unknown,
15
+ children: readonly unknown[],
16
+ ): void {
17
+ if (value === null || value === undefined) return
18
+ if (isCompiledBinding(value)) {
19
+ mountCompiledPropTransition(
20
+ value,
21
+ (initial) => mountRawHtml(element, initial, children),
22
+ (next, previous) => prepareRawHtml(element, next, previous, children),
23
+ )
24
+ return
25
+ }
26
+ mountRawHtml(element, value, children)
27
+ }
28
+
29
+ function mountRawHtml(element: HTMLElement, value: unknown, children: readonly unknown[]): void {
30
+ const html = readRawHtml(element, value, children)
31
+ if (html === null || html === undefined) return
32
+ const container = rawHtmlContainer(element)
33
+ const staged = parseRawHtml(element, html)
34
+ if (isHydrating()) {
35
+ if (!hasEqualHydrationRawChildren(container, staged)) {
36
+ throw new HydrationMismatch('server raw HTML does not match the client value')
37
+ }
38
+ } else {
39
+ container.replaceChildren(adoptRawHtml(element, staged))
40
+ }
41
+ rawHtmlHosts.add(element)
42
+ }
43
+
44
+ function hasEqualHydrationRawChildren(container: Node, expected: DocumentFragment): boolean {
45
+ const start = container.firstChild
46
+ const end = container.lastChild
47
+ if (
48
+ !(start instanceof Comment) ||
49
+ start.data !== `${HYDRATION_PREFIX}:h` ||
50
+ !(end instanceof Comment) ||
51
+ end.data !== `/${HYDRATION_PREFIX}:h`
52
+ ) {
53
+ return false
54
+ }
55
+ const actual: Node[] = []
56
+ for (let node = start.nextSibling; node !== null && node !== end; node = node.nextSibling) {
57
+ actual.push(node)
58
+ }
59
+ if (actual.length !== expected.childNodes.length) return false
60
+ for (let index = 0; index < actual.length; index += 1) {
61
+ if (!actual[index]?.isEqualNode(expected.childNodes[index] ?? null)) return false
62
+ }
63
+ return true
64
+ }
65
+
66
+ function prepareRawHtml(
67
+ element: HTMLElement,
68
+ nextValue: unknown,
69
+ previousValue: unknown,
70
+ children: readonly unknown[],
71
+ ): CompiledPropTransition | undefined {
72
+ const nextHtml = readRawHtml(element, nextValue, children)
73
+ const previousHtml = readRawHtml(element, previousValue, children)
74
+ if (nextHtml === null || nextHtml === undefined || nextHtml === previousHtml) return undefined
75
+
76
+ const staged = parseRawHtml(element, nextHtml)
77
+ const container = rawHtmlContainer(element)
78
+ const wasRawHtmlHost = rawHtmlHosts.has(element)
79
+ let previousNodes: Node[] | undefined
80
+ return [
81
+ () => {
82
+ // A reactive script `type` prop commits before the raw subtree.
83
+ assertExecutableRawHtmlTarget(element)
84
+ const adopted = adoptRawHtml(element, staged)
85
+ previousNodes = [...container.childNodes]
86
+ container.replaceChildren(adopted)
87
+ rawHtmlHosts.add(element)
88
+ },
89
+ () => {
90
+ if (previousNodes === undefined) return
91
+ container.replaceChildren(...previousNodes)
92
+ if (!wasRawHtmlHost) rawHtmlHosts.delete(element)
93
+ previousNodes = undefined
94
+ },
95
+ undefined,
96
+ undefined,
97
+ RAW_HTML_PUBLICATION_PRIORITY,
98
+ ]
99
+ }
100
+
101
+ export function validateRawHtmlRelatedProp(element: HTMLElement, name: string): void {
102
+ if (name === 'type' && rawHtmlHosts.has(element)) assertExecutableRawHtmlTarget(element)
103
+ }
104
+
105
+ function readRawHtml(element: HTMLElement, value: unknown, children: readonly unknown[]): unknown {
106
+ if (value === null || value === undefined) return undefined
107
+ assertRawHtmlPropTarget(element)
108
+ if (typeof value !== 'object' || !('__html' in value)) {
109
+ throw new Error(
110
+ DEV
111
+ ? '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. Please visit https://react.dev/link/dangerously-set-inner-html for more information.'
112
+ : 'V601',
113
+ )
114
+ }
115
+ const html = value['__html']
116
+ if (html === null || html === undefined) return html
117
+ if (children.some((child) => child !== null && child !== undefined)) {
118
+ throw new Error(
119
+ DEV ? 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' : 'V602',
120
+ )
121
+ }
122
+ assertExecutableRawHtmlTarget(element)
123
+ return html
124
+ }
125
+
126
+ function assertRawHtmlPropTarget(element: HTMLElement): void {
127
+ const tag = element.localName
128
+ if (isVoidHtmlElement(tag)) {
129
+ throw new Error(
130
+ DEV
131
+ ? `${tag} is a void element tag and must neither have \`children\` nor use \`dangerouslySetInnerHTML\`.`
132
+ : 'V603',
133
+ )
134
+ }
135
+ if (tag === 'textarea') {
136
+ throw new Error(DEV ? '`dangerouslySetInnerHTML` does not make sense on <textarea>.' : 'V604')
137
+ }
138
+ }
139
+
140
+ function assertExecutableRawHtmlTarget(element: HTMLElement): void {
141
+ const tag = element.localName
142
+ if (tag === 'script' && isExecutableScript(element as HTMLScriptElement)) {
143
+ throw new Error(
144
+ DEV
145
+ ? 'dangerouslySetInnerHTML on an executable <script> is unsupported because direct-created scripts execute when connected; use a non-executable data MIME type'
146
+ : 'V605',
147
+ )
148
+ }
149
+ }
150
+
151
+ function isVoidHtmlElement(tag: string): boolean {
152
+ return (
153
+ tag === 'area' ||
154
+ tag === 'base' ||
155
+ tag === 'br' ||
156
+ tag === 'col' ||
157
+ tag === 'embed' ||
158
+ tag === 'hr' ||
159
+ tag === 'img' ||
160
+ tag === 'input' ||
161
+ tag === 'keygen' ||
162
+ tag === 'link' ||
163
+ tag === 'menuitem' ||
164
+ tag === 'meta' ||
165
+ tag === 'param' ||
166
+ tag === 'source' ||
167
+ tag === 'track' ||
168
+ tag === 'wbr'
169
+ )
170
+ }
171
+
172
+ function isExecutableScript(element: HTMLScriptElement): boolean {
173
+ const type = element.type.trim().toLowerCase().split(';', 1)[0] ?? ''
174
+ if (type === '' || type === 'module' || type === 'importmap' || type === 'speculationrules') {
175
+ return true
176
+ }
177
+ return (
178
+ /^(?:application|text)\/(?:x-)?(?:java|ecma)script(?:1\.[0-5])?$/.test(type) ||
179
+ type === 'text/jscript' ||
180
+ type === 'text/livescript'
181
+ )
182
+ }
183
+
184
+ function parseRawHtml(element: HTMLElement, html: unknown): DocumentFragment {
185
+ const stagingDocument = element.ownerDocument.implementation.createHTMLDocument()
186
+ const staging =
187
+ element.localName === 'noscript'
188
+ ? (element.cloneNode(false) as HTMLElement)
189
+ : stagingDocument.createElement(element.localName.includes('-') ? 'div' : element.localName)
190
+ Reflect.set(staging, 'innerHTML', html)
191
+ const fragment = staging.ownerDocument.createDocumentFragment()
192
+ fragment.append(...rawHtmlContainer(staging).childNodes)
193
+ return fragment
194
+ }
195
+
196
+ function rawHtmlContainer(element: HTMLElement): HTMLElement | DocumentFragment {
197
+ return element instanceof HTMLTemplateElement ? element.content : element
198
+ }
199
+
200
+ function adoptRawHtml(element: HTMLElement, staged: DocumentFragment): DocumentFragment {
201
+ if (staged.ownerDocument === element.ownerDocument) return staged
202
+ const adopted = element.ownerDocument.createDocumentFragment()
203
+ adopted.append(...staged.childNodes)
204
+ element.ownerDocument.defaultView?.customElements.upgrade(adopted)
205
+ return adopted
206
+ }
package/src/ref.ts ADDED
@@ -0,0 +1,13 @@
1
+ export interface MutableRef<T> {
2
+ current: T
3
+ }
4
+
5
+ /** Creates the plain ref object used by dependency initialization outside component hooks. */
6
+ export function createRef<T>(): MutableRef<T | null> {
7
+ return { current: null }
8
+ }
9
+
10
+ /** Creates the stable ref cell captured by a component's one-time execution. */
11
+ export function useRef<T>(initialValue: T): MutableRef<T> {
12
+ return { current: initialValue }
13
+ }
@@ -0,0 +1,63 @@
1
+ export const RENDERABLE = Symbol.for('vidact.v1.Renderable')
2
+
3
+ export interface RenderableProtocol {
4
+ readonly [RENDERABLE]: {
5
+ readonly identity: unknown
6
+ readonly input: unknown
7
+ readonly reconcile: boolean
8
+ readonly construct: (input: never) => unknown
9
+ }
10
+ }
11
+
12
+ /**
13
+ * Cloning a renderable pulls in the element-construction module, which most
14
+ * compiled apps never touch. The renderable module registers its clone the
15
+ * first time an element capability is created, so `jsx` can reach it without
16
+ * importing it, and bundles that never build elements leave it out.
17
+ */
18
+ export type RenderableClone = (
19
+ value: RenderableProtocol,
20
+ overrides: Record<string, unknown>,
21
+ ) => unknown
22
+
23
+ let renderableClone: RenderableClone | undefined
24
+
25
+ export function installRenderableClone(clone: RenderableClone): void {
26
+ renderableClone = clone
27
+ }
28
+
29
+ export function renderableCloneHandler(): RenderableClone | undefined {
30
+ return renderableClone
31
+ }
32
+
33
+ export function isRenderableProtocol(value: unknown): value is RenderableProtocol {
34
+ return (
35
+ typeof value === 'object' &&
36
+ value !== null &&
37
+ Object.hasOwn(value, RENDERABLE) &&
38
+ typeof (value as RenderableProtocol)[RENDERABLE]?.construct === 'function'
39
+ )
40
+ }
41
+
42
+ export function materializeRenderable(value: RenderableProtocol): unknown {
43
+ const internals = value[RENDERABLE]
44
+ return internals.construct(internals.input as never)
45
+ }
46
+
47
+ export function materializeRenderableWithInput(value: RenderableProtocol, input: unknown): unknown {
48
+ return value[RENDERABLE].construct(input as never)
49
+ }
50
+
51
+ export function renderableIdentity(value: RenderableProtocol): unknown {
52
+ return value[RENDERABLE].identity
53
+ }
54
+
55
+ export function canReconcileRenderable(value: RenderableProtocol): boolean {
56
+ return value[RENDERABLE].reconcile
57
+ }
58
+
59
+ export function renderablePropsSnapshot(value: RenderableProtocol): Record<PropertyKey, unknown> {
60
+ const props = (value as RenderableProtocol & { readonly props: Record<PropertyKey, unknown> })
61
+ .props
62
+ return Object.fromEntries(Reflect.ownKeys(props).map((name) => [name, Reflect.get(props, name)]))
63
+ }
@@ -0,0 +1,283 @@
1
+ import { binding, isCompiledBinding } from './compiled/core.ts'
2
+ import type { CompiledBinding, CompiledRenderValue, CompiledScope } from './compiled/types.ts'
3
+ import { Fragment, h, type DirectComponent } from './direct-dom.ts'
4
+ import {
5
+ installRenderableClone,
6
+ isRenderableProtocol,
7
+ RENDERABLE,
8
+ type RenderableClone,
9
+ } from './renderable-protocol.ts'
10
+ import type { SourceMask } from './source-mask.ts'
11
+ import { unionSources } from './source-mask.ts'
12
+ import { compiledSpread } from './spread.ts'
13
+
14
+ const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
15
+ const SPECIAL_PROPS = new Set(['children', 'key', 'ref'])
16
+
17
+ type Props = Record<string, unknown>
18
+ export type RenderablePropsInput = Props | CompiledBinding<Props>
19
+
20
+ type RenderableInternals = {
21
+ readonly identity: unknown
22
+ readonly input: RenderablePropsInput
23
+ readonly reconcile: boolean
24
+ readonly construct: (input: RenderablePropsInput) => CompiledRenderValue
25
+ }
26
+
27
+ export interface CompiledRenderable {
28
+ readonly props: Props
29
+ readonly [RENDERABLE]: RenderableInternals
30
+ }
31
+
32
+ export function createRenderable(
33
+ input: RenderablePropsInput,
34
+ construct: (input: RenderablePropsInput) => CompiledRenderValue,
35
+ identity: unknown = construct,
36
+ reconcile = false,
37
+ ): CompiledRenderable {
38
+ installRenderableClone(cloneRenderable as RenderableClone)
39
+ readPropsInput(input)
40
+ const capability = {
41
+ props: propsView(input),
42
+ } as CompiledRenderable
43
+ Object.defineProperty(capability, RENDERABLE, {
44
+ configurable: false,
45
+ enumerable: false,
46
+ value: { identity, input, reconcile, construct } satisfies RenderableInternals,
47
+ })
48
+ return capability
49
+ }
50
+
51
+ export function isRenderable(value: unknown): value is CompiledRenderable {
52
+ return isRenderableProtocol(value)
53
+ }
54
+
55
+ export function createReactElement(
56
+ type: string | typeof Fragment | DirectComponent | CompiledRenderable,
57
+ props: Props | null,
58
+ ...children: CompiledRenderValue[]
59
+ ): CompiledRenderable {
60
+ const input: Props = { ...props }
61
+ if (children.length === 1) input.children = children[0]
62
+ else if (children.length > 1) input.children = children
63
+
64
+ if (typeof type === 'string') return createIntrinsicRenderable(type, input)
65
+
66
+ return createRenderable(
67
+ input,
68
+ (currentInput) => {
69
+ if (isRenderable(type)) return cloneRenderable(type, currentInput)
70
+ const resolved = readPropsInput(currentInput)
71
+ const { children: currentChildren, key: _key, ...currentProps } = resolved
72
+ return Object.hasOwn(resolved, 'children')
73
+ ? h(type, currentProps, currentChildren as CompiledRenderValue)
74
+ : h(type, currentProps)
75
+ },
76
+ type,
77
+ false,
78
+ )
79
+ }
80
+
81
+ export function renderableToArray(value: unknown): [CompiledRenderable] {
82
+ if (!isRenderable(value)) {
83
+ throw new TypeError(
84
+ DEV ? 'Children.toArray requires one compiled renderable capability' : 'V107',
85
+ )
86
+ }
87
+ return [value]
88
+ }
89
+
90
+ export function renderableMarker(value: unknown): undefined {
91
+ if (!isRenderable(value)) {
92
+ throw new TypeError(
93
+ DEV ? 'React element metadata requires a compiled renderable capability' : 'V107',
94
+ )
95
+ }
96
+ return undefined
97
+ }
98
+
99
+ export function cloneRenderable(
100
+ value: unknown,
101
+ overrides?: RenderablePropsInput | null,
102
+ childrenOverride?: CompiledRenderValue | CompiledBinding<CompiledRenderValue>,
103
+ ): CompiledRenderValue {
104
+ if (!isRenderable(value)) {
105
+ throw new TypeError(DEV ? 'cloneRenderable requires a compiled renderable capability' : 'V107')
106
+ }
107
+ const internals = value[RENDERABLE]
108
+ let input = mergeInputs(internals.input, overrides)
109
+ if (arguments.length >= 3) input = mergeInputs(input, childrenInput(childrenOverride))
110
+ return createRenderable(
111
+ input,
112
+ internals.construct,
113
+ internals.identity,
114
+ internals.reconcile,
115
+ ) as unknown as CompiledRenderValue
116
+ }
117
+
118
+ export function cloneRenderableComponent(props: Record<string, unknown>): CompiledRenderValue {
119
+ const value = isCompiledBinding(props.value) ? props.value[1]() : props.value
120
+ if (Object.hasOwn(props, 'childrenOverride')) {
121
+ return cloneRenderable(
122
+ value,
123
+ props.overrides as RenderablePropsInput | null | undefined,
124
+ props.childrenOverride as CompiledRenderValue | CompiledBinding<CompiledRenderValue>,
125
+ )
126
+ }
127
+ return cloneRenderable(value, props.overrides as RenderablePropsInput | null | undefined)
128
+ }
129
+
130
+ export function keyedFragmentComponent(props: Record<string, unknown>): CompiledRenderValue {
131
+ return props.children as CompiledRenderValue
132
+ }
133
+
134
+ export function renderableProps(input: RenderablePropsInput): Record<string, unknown> {
135
+ const ordinary = projectInput(input, (props) =>
136
+ Object.fromEntries(
137
+ Reflect.ownKeys(props)
138
+ .filter((name) => typeof name !== 'string' || !SPECIAL_PROPS.has(name))
139
+ .map((name) => [name, Reflect.get(props, name)]),
140
+ ),
141
+ )
142
+ return isCompiledBinding(ordinary) ? compiledSpread(ordinary, []) : ordinary
143
+ }
144
+
145
+ export function renderableChildren(input: RenderablePropsInput): CompiledRenderValue {
146
+ return projectInput(input, (props) => props.children) as CompiledRenderValue
147
+ }
148
+
149
+ export function renderableRef(input: RenderablePropsInput): unknown {
150
+ return projectInput(input, (props) => props.ref)
151
+ }
152
+
153
+ export function forwardedRef(props: Record<string, unknown>): unknown {
154
+ return props.ref
155
+ }
156
+
157
+ export function dynamicIntrinsicComponent(props: Record<string, unknown>): CompiledRenderValue {
158
+ const tag = isCompiledBinding(props.tag) ? props.tag[1]() : props.tag
159
+ if (typeof tag !== 'string') {
160
+ throw new TypeError(DEV ? 'dynamic intrinsic construction requires a string tag' : 'V107')
161
+ }
162
+ let input = props.props as RenderablePropsInput
163
+ if (Object.hasOwn(props, 'childrenOverride')) {
164
+ input = mergeInputs(
165
+ input,
166
+ childrenInput(
167
+ props.childrenOverride as CompiledRenderValue | CompiledBinding<CompiledRenderValue>,
168
+ ),
169
+ )
170
+ }
171
+ return createIntrinsicRenderable(tag, input) as unknown as CompiledRenderValue
172
+ }
173
+
174
+ function createIntrinsicRenderable(tag: string, input: RenderablePropsInput): CompiledRenderable {
175
+ return createRenderable(
176
+ input,
177
+ (currentInput) =>
178
+ h(
179
+ tag,
180
+ { ...renderableProps(currentInput), ref: renderableRef(currentInput) },
181
+ renderableChildren(currentInput),
182
+ ),
183
+ tag,
184
+ true,
185
+ )
186
+ }
187
+
188
+ function propsView(input: RenderablePropsInput): Props {
189
+ return new Proxy(
190
+ {},
191
+ {
192
+ get(_target, property) {
193
+ const value = Reflect.get(readPropsInput(input), property)
194
+ return isCompiledBinding(value) ? value[1]() : value
195
+ },
196
+ getOwnPropertyDescriptor(_target, property) {
197
+ return Object.hasOwn(readPropsInput(input), property)
198
+ ? { configurable: true, enumerable: true }
199
+ : undefined
200
+ },
201
+ has(_target, property) {
202
+ return Reflect.has(readPropsInput(input), property)
203
+ },
204
+ ownKeys() {
205
+ return Reflect.ownKeys(readPropsInput(input))
206
+ },
207
+ },
208
+ )
209
+ }
210
+
211
+ function childrenInput(
212
+ value: CompiledRenderValue | CompiledBinding<CompiledRenderValue> | undefined,
213
+ ): RenderablePropsInput {
214
+ if (!isCompiledBinding(value)) return { children: value }
215
+ return binding(value[2], value[3], () => ({ children: value[1]() }), value[4], value[5])
216
+ }
217
+
218
+ function mergeInputs(
219
+ authored: RenderablePropsInput,
220
+ overrides: RenderablePropsInput | null | undefined,
221
+ ): RenderablePropsInput {
222
+ if (overrides == null) return authored
223
+ if (!isCompiledBinding(authored) && !isCompiledBinding(overrides)) {
224
+ return { ...readPropsInput(authored), ...readPropsInput(overrides) }
225
+ }
226
+ const inputs = [authored, overrides]
227
+ const subscriptions = collectSubscriptions(inputs)
228
+ const evaluate = (): Props => ({
229
+ ...readPropsInput(authored),
230
+ ...readPropsInput(overrides),
231
+ })
232
+ return binding(
233
+ subscriptions[0]![0],
234
+ subscriptions[0]![1],
235
+ evaluate,
236
+ subscriptions[1]?.[0],
237
+ subscriptions[1]?.[1],
238
+ )
239
+ }
240
+
241
+ function projectInput<Value>(
242
+ input: RenderablePropsInput,
243
+ project: (props: Props) => Value,
244
+ ): Value | CompiledBinding<Value> {
245
+ if (!isCompiledBinding(input)) return project(readPropsInput(input))
246
+ return binding(input[2], input[3], () => project(readPropsInput(input)), input[4], input[5])
247
+ }
248
+
249
+ function collectSubscriptions(
250
+ inputs: readonly RenderablePropsInput[],
251
+ ): Array<[CompiledScope, SourceMask]> {
252
+ const subscriptions = new Map<CompiledScope, SourceMask>()
253
+ for (const input of inputs) {
254
+ if (!isCompiledBinding(input)) continue
255
+ addSubscription(subscriptions, input[2], input[3])
256
+ if (input[4] !== undefined && input[5] !== undefined) {
257
+ addSubscription(subscriptions, input[4], input[5])
258
+ }
259
+ }
260
+ if (subscriptions.size > 2) {
261
+ throw new TypeError(
262
+ DEV ? 'a compiled renderable can combine reactive props from at most two scopes' : 'V107',
263
+ )
264
+ }
265
+ return [...subscriptions]
266
+ }
267
+
268
+ function addSubscription(
269
+ subscriptions: Map<CompiledScope, SourceMask>,
270
+ scope: CompiledScope,
271
+ reads: SourceMask,
272
+ ): void {
273
+ const current = subscriptions.get(scope)
274
+ subscriptions.set(scope, current === undefined ? reads : unionSources(current, reads))
275
+ }
276
+
277
+ function readPropsInput(input: RenderablePropsInput): Props {
278
+ const value = isCompiledBinding(input) ? input[1]() : input
279
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
280
+ throw new TypeError(DEV ? 'compiled renderable props must evaluate to an object' : 'V107')
281
+ }
282
+ return value
283
+ }
@@ -0,0 +1,19 @@
1
+ import { enableRetainedUi, retainedActivity } from './compiled/core.ts'
2
+ import type { CompiledBinding, CompiledRenderValue, StructuralBinding } from './compiled/types.ts'
3
+ import { enableRetainedUiStyles } from './dom/styles.ts'
4
+
5
+ export interface ActivityProps {
6
+ readonly children?: (() => CompiledRenderValue) | readonly [() => CompiledRenderValue]
7
+ readonly mode: 'visible' | 'hidden' | CompiledBinding<'visible' | 'hidden'>
8
+ readonly name?: string
9
+ }
10
+
11
+ export function Activity(props: ActivityProps): StructuralBinding {
12
+ const render = Array.isArray(props.children) ? props.children[0] : props.children
13
+ if (typeof render !== 'function') {
14
+ throw new TypeError('Activity children must be a compiler-generated render function')
15
+ }
16
+ enableRetainedUi()
17
+ enableRetainedUiStyles()
18
+ return retainedActivity(props.mode, render)
19
+ }