@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.
- package/CHANGELOG.md +52 -0
- package/README.md +1 -1
- package/dist/actions.d.ts +32 -30
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +251 -266
- package/dist/actions.js.map +1 -1
- package/dist/async.d.ts +20 -17
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +18 -17
- package/dist/async.js.map +1 -1
- package/dist/compiled/async-resource.d.ts +9 -6
- package/dist/compiled/async-resource.d.ts.map +1 -1
- package/dist/compiled/async-resource.js +69 -77
- package/dist/compiled/async-resource.js.map +1 -1
- package/dist/compiled/core.d.ts +136 -147
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +2672 -2985
- package/dist/compiled/core.js.map +1 -1
- package/dist/compiled/types.d.ts +44 -56
- package/dist/compiled/types.d.ts.map +1 -1
- package/dist/compiled/types.js +10 -6
- package/dist/compiled/types.js.map +1 -1
- package/dist/compiled/validation.d.ts +4 -1
- package/dist/compiled/validation.d.ts.map +1 -1
- package/dist/compiled/validation.js +17 -20
- package/dist/compiled/validation.js.map +1 -1
- package/dist/component-spread.d.ts +5 -3
- package/dist/component-spread.d.ts.map +1 -1
- package/dist/component-spread.js +49 -67
- package/dist/component-spread.js.map +1 -1
- package/dist/concurrent.d.ts +20 -18
- package/dist/concurrent.d.ts.map +1 -1
- package/dist/concurrent.js +191 -210
- package/dist/concurrent.js.map +1 -1
- package/dist/direct-dom.d.ts +17 -12
- package/dist/direct-dom.d.ts.map +1 -1
- package/dist/direct-dom.js +187 -221
- package/dist/direct-dom.js.map +1 -1
- package/dist/dom/attributes.d.ts +9 -6
- package/dist/dom/attributes.d.ts.map +1 -1
- package/dist/dom/attributes.js +65 -47
- package/dist/dom/attributes.js.map +1 -1
- package/dist/dom/events.d.ts +5 -2
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +53 -65
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/forms.d.ts +9 -6
- package/dist/dom/forms.d.ts.map +1 -1
- package/dist/dom/forms.js +152 -198
- package/dist/dom/forms.js.map +1 -1
- package/dist/dom/intrinsic.d.ts +17 -15
- package/dist/dom/intrinsic.d.ts.map +1 -1
- package/dist/dom/intrinsic.js +48 -50
- package/dist/dom/intrinsic.js.map +1 -1
- package/dist/dom/namespace.d.ts +4 -1
- package/dist/dom/namespace.d.ts.map +1 -1
- package/dist/dom/namespace.js +25 -28
- package/dist/dom/namespace.js.map +1 -1
- package/dist/dom/namespaces.d.ts +11 -8
- package/dist/dom/namespaces.d.ts.map +1 -1
- package/dist/dom/namespaces.js +14 -12
- package/dist/dom/namespaces.js.map +1 -1
- package/dist/dom/properties.d.ts +18 -16
- package/dist/dom/properties.d.ts.map +1 -1
- package/dist/dom/properties.js +273 -296
- package/dist/dom/properties.js.map +1 -1
- package/dist/dom/style-properties.d.ts +4 -1
- package/dist/dom/style-properties.d.ts.map +1 -1
- package/dist/dom/style-properties.js +49 -45
- package/dist/dom/style-properties.js.map +1 -1
- package/dist/dom/styles.d.ts +5 -2
- package/dist/dom/styles.d.ts.map +1 -1
- package/dist/dom/styles.js +47 -67
- package/dist/dom/styles.js.map +1 -1
- package/dist/dom-forms.d.ts +4 -1
- package/dist/dom-forms.d.ts.map +1 -1
- package/dist/dom-forms.js +16 -13
- package/dist/dom-forms.js.map +1 -1
- package/dist/dom-styles.d.ts +4 -1
- package/dist/dom-styles.d.ts.map +1 -1
- package/dist/dom-styles.js +10 -7
- package/dist/dom-styles.js.map +1 -1
- package/dist/framework-hydrate.d.ts +20 -17
- package/dist/framework-hydrate.d.ts.map +1 -1
- package/dist/framework-hydrate.js +128 -146
- package/dist/framework-hydrate.js.map +1 -1
- package/dist/framework-protocol.d.ts +27 -25
- package/dist/framework-protocol.d.ts.map +1 -1
- package/dist/framework-protocol.js +173 -211
- package/dist/framework-protocol.js.map +1 -1
- package/dist/framework.d.ts +34 -32
- package/dist/framework.d.ts.map +1 -1
- package/dist/framework.js +74 -93
- package/dist/framework.js.map +1 -1
- package/dist/hydrate.d.ts +11 -8
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +13 -9
- package/dist/hydrate.js.map +1 -1
- package/dist/hydration-bridge.d.ts +70 -58
- package/dist/hydration-bridge.d.ts.map +1 -1
- package/dist/hydration-bridge.js +77 -66
- package/dist/hydration-bridge.js.map +1 -1
- package/dist/hydration.d.ts +54 -28
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +450 -463
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +11 -13
- package/dist/indexed-list.d.ts +8 -5
- package/dist/indexed-list.d.ts.map +1 -1
- package/dist/indexed-list.js +14 -7
- package/dist/indexed-list.js.map +1 -1
- package/dist/jsx-runtime.d.ts +10 -8
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +28 -23
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/keyed-list.d.ts +13 -14
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +132 -160
- package/dist/keyed-list.js.map +1 -1
- package/dist/metadata.d.ts +6 -3
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +131 -164
- package/dist/metadata.js.map +1 -1
- package/dist/nested-prop.d.ts +4 -2
- package/dist/nested-prop.d.ts.map +1 -1
- package/dist/nested-prop.js +17 -16
- package/dist/nested-prop.js.map +1 -1
- package/dist/profiling.d.ts +11 -8
- package/dist/profiling.d.ts.map +1 -1
- package/dist/profiling.js +17 -17
- package/dist/profiling.js.map +1 -1
- package/dist/protocol.d.ts +5 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +8 -5
- package/dist/protocol.js.map +1 -1
- package/dist/raw-html.d.ts +5 -2
- package/dist/raw-html.d.ts.map +1 -1
- package/dist/raw-html.js +86 -146
- package/dist/raw-html.js.map +1 -1
- package/dist/ref.d.ts +8 -3
- package/dist/ref.d.ts.map +1 -1
- package/dist/ref.js +10 -2
- package/dist/ref.js.map +1 -1
- package/dist/renderable-protocol.d.ts +26 -8
- package/dist/renderable-protocol.d.ts.map +1 -1
- package/dist/renderable-protocol.js +30 -9
- package/dist/renderable-protocol.js.map +1 -1
- package/dist/renderable.d.ts +27 -22
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +141 -133
- package/dist/renderable.js.map +1 -1
- package/dist/retained-ui.d.ts +9 -6
- package/dist/retained-ui.d.ts.map +1 -1
- package/dist/retained-ui.js +12 -10
- package/dist/retained-ui.js.map +1 -1
- package/dist/root.d.ts +18 -15
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +73 -85
- package/dist/root.js.map +1 -1
- package/dist/scheduler.d.ts +10 -8
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +97 -105
- package/dist/scheduler.js.map +1 -1
- package/dist/server-framework.d.ts +45 -42
- package/dist/server-framework.d.ts.map +1 -1
- package/dist/server-framework.js +209 -217
- package/dist/server-framework.js.map +1 -1
- package/dist/server.d.ts +130 -128
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +603 -710
- package/dist/server.js.map +1 -1
- package/dist/shared/promise.d.ts +6 -3
- package/dist/shared/promise.d.ts.map +1 -1
- package/dist/shared/promise.js +21 -19
- package/dist/shared/promise.js.map +1 -1
- package/dist/source-mask.d.ts +9 -6
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +45 -63
- package/dist/source-mask.js.map +1 -1
- package/dist/spread.d.ts +5 -2
- package/dist/spread.d.ts.map +1 -1
- package/dist/spread.js +79 -99
- package/dist/spread.js.map +1 -1
- package/dist/state-slot.d.ts +18 -15
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +43 -42
- package/dist/state-slot.js.map +1 -1
- package/dist/testing.d.ts +9 -6
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +16 -12
- package/dist/testing.js.map +1 -1
- package/package.json +8 -7
- package/src/actions.ts +415 -0
- package/src/async.ts +78 -0
- package/src/compiled/async-resource.ts +113 -0
- package/src/compiled/core.ts +4041 -0
- package/src/compiled/types.ts +96 -0
- package/src/compiled/validation.ts +23 -0
- package/src/component-spread.ts +86 -0
- package/src/concurrent.ts +310 -0
- package/src/direct-dom.ts +365 -0
- package/src/dom/attributes.ts +55 -0
- package/src/dom/events.ts +93 -0
- package/src/dom/forms.ts +220 -0
- package/src/dom/intrinsic.ts +94 -0
- package/src/dom/namespace.ts +30 -0
- package/src/dom/namespaces.ts +14 -0
- package/src/dom/properties.ts +355 -0
- package/src/dom/style-properties.ts +46 -0
- package/src/dom/styles.ts +86 -0
- package/src/dom-forms.ts +23 -0
- package/src/dom-styles.ts +11 -0
- package/src/env.d.ts +6 -0
- package/src/framework-hydrate.ts +224 -0
- package/src/framework-protocol.ts +335 -0
- package/src/framework.ts +161 -0
- package/src/hydrate.ts +38 -0
- package/src/hydration-bridge.ts +196 -0
- package/src/hydration.ts +642 -0
- package/src/index.ts +92 -0
- package/src/indexed-list.ts +20 -0
- package/src/jsx-runtime.ts +55 -0
- package/src/keyed-list.ts +233 -0
- package/src/metadata.ts +212 -0
- package/src/nested-prop.ts +27 -0
- package/src/profiling.ts +43 -0
- package/src/protocol.ts +8 -0
- package/src/raw-html.ts +206 -0
- package/src/ref.ts +13 -0
- package/src/renderable-protocol.ts +63 -0
- package/src/renderable.ts +283 -0
- package/src/retained-ui.ts +19 -0
- package/src/root.ts +122 -0
- package/src/scheduler.ts +136 -0
- package/src/server-framework.ts +397 -0
- package/src/server.ts +1160 -0
- package/src/shared/promise.ts +28 -0
- package/src/source-mask.ts +83 -0
- package/src/spread.ts +131 -0
- package/src/state-slot.ts +76 -0
- package/src/testing.ts +27 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/src/raw-html.ts
ADDED
|
@@ -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
|
+
}
|