@vidact/runtime 0.2.0-beta.0 → 0.2.0-beta.11
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 +73 -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 +148 -147
- package/dist/compiled/core.d.ts.map +1 -1
- package/dist/compiled/core.js +3064 -2987
- 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 +279 -222
- 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 +8 -2
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +118 -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 +17 -14
- package/dist/keyed-list.d.ts.map +1 -1
- package/dist/keyed-list.js +244 -161
- 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 +28 -22
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +152 -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 +10 -6
- package/dist/source-mask.d.ts.map +1 -1
- package/dist/source-mask.js +60 -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 +29 -15
- package/dist/state-slot.d.ts.map +1 -1
- package/dist/state-slot.js +73 -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 +4647 -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 +491 -0
- package/src/dom/attributes.ts +55 -0
- package/src/dom/events.ts +179 -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 +202 -0
- package/src/hydration.ts +644 -0
- package/src/index.ts +94 -0
- package/src/indexed-list.ts +20 -0
- package/src/jsx-runtime.ts +55 -0
- package/src/keyed-list.ts +379 -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 +313 -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 +103 -0
- package/src/spread.ts +131 -0
- package/src/state-slot.ts +132 -0
- package/src/testing.ts +27 -0
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/src/index.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// oxlint-disable-next-line typescript/triple-slash-reference -- Include the build define in consuming TypeScript programs.
|
|
2
|
+
/// <reference path="./env.d.ts" />
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
binding,
|
|
6
|
+
choose,
|
|
7
|
+
compiledEvent,
|
|
8
|
+
compiledInlineEvent,
|
|
9
|
+
compiledEffect,
|
|
10
|
+
compiledImperativeHandle,
|
|
11
|
+
compiledInsertionEffect,
|
|
12
|
+
compiledLayoutEffect,
|
|
13
|
+
compiledRoot,
|
|
14
|
+
createCompiledContext,
|
|
15
|
+
createCompiledEffectEvent,
|
|
16
|
+
createCompiledExternalStore,
|
|
17
|
+
createCompiledId,
|
|
18
|
+
createCompiledMemo,
|
|
19
|
+
createCompiledProp,
|
|
20
|
+
createCompiledReducer,
|
|
21
|
+
createCompiledRestProp,
|
|
22
|
+
createCompiledScope,
|
|
23
|
+
createCompiledState,
|
|
24
|
+
createContext,
|
|
25
|
+
createNarrowCompiledScope,
|
|
26
|
+
createPortal,
|
|
27
|
+
deferred,
|
|
28
|
+
dispatch,
|
|
29
|
+
errorBoundary,
|
|
30
|
+
indexed,
|
|
31
|
+
keyed,
|
|
32
|
+
mountCompiled,
|
|
33
|
+
mountCompiledRef,
|
|
34
|
+
objectRest,
|
|
35
|
+
runWithCompiledContext,
|
|
36
|
+
use,
|
|
37
|
+
useCallback,
|
|
38
|
+
useContext,
|
|
39
|
+
useEffect,
|
|
40
|
+
useEffectEvent,
|
|
41
|
+
useId,
|
|
42
|
+
useImperativeHandle,
|
|
43
|
+
useInsertionEffect,
|
|
44
|
+
useLayoutEffect,
|
|
45
|
+
useMemo,
|
|
46
|
+
useSyncExternalStore,
|
|
47
|
+
when,
|
|
48
|
+
type ChoiceMode,
|
|
49
|
+
type MountCompiledOptions,
|
|
50
|
+
} from './compiled/core.ts'
|
|
51
|
+
export type {
|
|
52
|
+
CompiledBinding,
|
|
53
|
+
CompiledComponentResult,
|
|
54
|
+
CompiledContext,
|
|
55
|
+
CompiledErrorHandler,
|
|
56
|
+
CompiledRenderValue,
|
|
57
|
+
CompiledScope,
|
|
58
|
+
OwnedBlock,
|
|
59
|
+
StructuralBinding,
|
|
60
|
+
} from './compiled/types.ts'
|
|
61
|
+
export { compiledComponentSpread } from './component-spread.ts'
|
|
62
|
+
export {
|
|
63
|
+
Fragment,
|
|
64
|
+
h,
|
|
65
|
+
type DirectChild,
|
|
66
|
+
type DirectComponent,
|
|
67
|
+
type DirectProps,
|
|
68
|
+
} from './direct-dom.ts'
|
|
69
|
+
export { nestedProp } from './nested-prop.ts'
|
|
70
|
+
export { assertRuntimeProtocol, VIDACT_RUNTIME_PROTOCOL } from './protocol.ts'
|
|
71
|
+
export { createRef, useRef, type MutableRef } from './ref.ts'
|
|
72
|
+
export {
|
|
73
|
+
cloneRenderable,
|
|
74
|
+
cloneRenderableComponent,
|
|
75
|
+
createComponentRenderable,
|
|
76
|
+
createReactElement,
|
|
77
|
+
createReactElement as createElement,
|
|
78
|
+
createRenderable,
|
|
79
|
+
dynamicIntrinsicComponent,
|
|
80
|
+
forwardedRef,
|
|
81
|
+
isRenderable,
|
|
82
|
+
keyedFragmentComponent,
|
|
83
|
+
renderableChildren,
|
|
84
|
+
renderableMarker,
|
|
85
|
+
renderableProps,
|
|
86
|
+
renderableRef,
|
|
87
|
+
renderableToArray,
|
|
88
|
+
type CompiledRenderable,
|
|
89
|
+
type RenderablePropsInput,
|
|
90
|
+
} from './renderable.ts'
|
|
91
|
+
export { createRoot, mountHotRoot, type CompiledRoot, type HotContext } from './root.ts'
|
|
92
|
+
export { combineSources, intersectsSources, source, type SourceMask } from './source-mask.ts'
|
|
93
|
+
export { compiledSpread } from './spread.ts'
|
|
94
|
+
export type { StateUpdate } from './state-slot.ts'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createKeyedList, type KeyedList, type KeyedRenderResult } from './keyed-list.ts'
|
|
2
|
+
|
|
3
|
+
export interface IndexedListOptions<T> {
|
|
4
|
+
readonly render: (value: T, index: number) => KeyedRenderResult<T>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type IndexedList<T> = KeyedList<T>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reconciles records by their current position. Existing owners are retained at
|
|
11
|
+
* an index and receive the new value at that index, matching React's unkeyed
|
|
12
|
+
* collection identity semantics.
|
|
13
|
+
*/
|
|
14
|
+
export function createIndexedList<T>(
|
|
15
|
+
parent: Node,
|
|
16
|
+
options: IndexedListOptions<T>,
|
|
17
|
+
before: Node | null = null,
|
|
18
|
+
): IndexedList<T> {
|
|
19
|
+
return createKeyedList(parent, { key: (_value, index) => index, render: options.render }, before)
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// oxlint-disable-next-line typescript/triple-slash-reference -- Include the build define in consuming TypeScript programs.
|
|
2
|
+
/// <reference path="./env.d.ts" />
|
|
3
|
+
|
|
4
|
+
import { h, type DirectChild, type DirectComponent } from './direct-dom.ts'
|
|
5
|
+
import {
|
|
6
|
+
isRenderableProtocol,
|
|
7
|
+
renderableCloneHandler,
|
|
8
|
+
type RenderableProtocol,
|
|
9
|
+
} from './renderable-protocol.ts'
|
|
10
|
+
import type { CompiledRenderable } from './renderable.ts'
|
|
11
|
+
|
|
12
|
+
export const Fragment: symbol = Symbol.for('vidact.v1.Fragment')
|
|
13
|
+
|
|
14
|
+
type ElementType = string | typeof Fragment | DirectComponent | CompiledRenderable
|
|
15
|
+
|
|
16
|
+
interface JsxProps extends Record<string, unknown> {
|
|
17
|
+
readonly children?: DirectChild | readonly DirectChild[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function jsx(type: ElementType, props: JsxProps | null, _key?: unknown): DirectChild {
|
|
21
|
+
if (isRenderableProtocol(type)) return cloneElementType(type, props)
|
|
22
|
+
const children = props?.children
|
|
23
|
+
const hasChildren = props !== null && Object.hasOwn(props, 'children')
|
|
24
|
+
if (!hasChildren) return h(type, props)
|
|
25
|
+
return h(type, props, children)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function jsxs(type: ElementType, props: JsxProps | null, _key?: unknown): DirectChild {
|
|
29
|
+
if (isRenderableProtocol(type)) return cloneElementType(type, props)
|
|
30
|
+
const children = props?.children
|
|
31
|
+
const hasChildren = props !== null && Object.hasOwn(props, 'children')
|
|
32
|
+
if (!hasChildren) return h(type, props)
|
|
33
|
+
return Array.isArray(children) ? h(type, props, ...children) : h(type, props, children)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function jsxDEV(
|
|
37
|
+
type: ElementType,
|
|
38
|
+
props: JsxProps | null,
|
|
39
|
+
key?: unknown,
|
|
40
|
+
isStaticChildren = false,
|
|
41
|
+
): DirectChild {
|
|
42
|
+
return isStaticChildren ? jsxs(type, props, key) : jsx(type, props, key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A renderable can only reach `jsx` as an element type if something already
|
|
47
|
+
* built one, which is what registers the clone.
|
|
48
|
+
*/
|
|
49
|
+
function cloneElementType(type: RenderableProtocol, props: JsxProps | null): DirectChild {
|
|
50
|
+
const clone = renderableCloneHandler()
|
|
51
|
+
if (clone === undefined) {
|
|
52
|
+
throw new TypeError('V107')
|
|
53
|
+
}
|
|
54
|
+
return clone(type, props ?? {}) as DirectChild
|
|
55
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
export interface KeyedDisposeTarget {
|
|
2
|
+
readonly [3]: () => void
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type KeyedDisposer = (() => void) | KeyedDisposeTarget
|
|
6
|
+
|
|
7
|
+
export type KeyedItem<T> = readonly [
|
|
8
|
+
nodes: readonly Node[],
|
|
9
|
+
update?: ((value: T, index: number) => void) | undefined,
|
|
10
|
+
dispose?: KeyedDisposer | undefined,
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export type KeyedRenderResult<T> = Node | readonly Node[] | KeyedItem<T>
|
|
14
|
+
|
|
15
|
+
export interface KeyedListOptions<T, K> {
|
|
16
|
+
readonly key: (value: T, index: number) => K
|
|
17
|
+
readonly render: (value: T, index: number) => KeyedRenderResult<T>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface KeyedList<T> {
|
|
21
|
+
readonly dispose: () => void
|
|
22
|
+
readonly parent: () => Node | null
|
|
23
|
+
readonly update: (values: readonly T[]) => readonly Node[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type RecordState<T, K> = readonly [
|
|
27
|
+
key: K,
|
|
28
|
+
nodes: readonly Node[],
|
|
29
|
+
update: ((value: T, index: number) => void) | undefined,
|
|
30
|
+
dispose: KeyedDisposer | undefined,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const DEV = typeof __VIDACT_DEV__ === 'undefined' || __VIDACT_DEV__
|
|
34
|
+
|
|
35
|
+
export function createKeyedList<T, K>(
|
|
36
|
+
parent: Node,
|
|
37
|
+
options: KeyedListOptions<T, K>,
|
|
38
|
+
before: Node | null = null,
|
|
39
|
+
): KeyedList<T> {
|
|
40
|
+
const hydratedRange = claimHydrationArrayRange(parent)
|
|
41
|
+
const start = hydratedRange?.[0] ?? document.createComment(DEV ? 'vidact:list' : '')
|
|
42
|
+
const end = hydratedRange?.[1] ?? document.createComment(DEV ? '/vidact:list' : '')
|
|
43
|
+
if (hydratedRange === undefined) {
|
|
44
|
+
parent.insertBefore(start, before)
|
|
45
|
+
parent.insertBefore(end, before)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let disposed = false
|
|
49
|
+
let hydrationPending = hydratedRange !== undefined
|
|
50
|
+
let records: readonly RecordState<T, K>[] = []
|
|
51
|
+
|
|
52
|
+
const update = (values: readonly T[]): readonly Node[] => {
|
|
53
|
+
if (disposed) throw new Error(DEV ? 'cannot update a disposed keyed list' : 'V801')
|
|
54
|
+
const currentParent = end.parentNode
|
|
55
|
+
if (currentParent === null || start.parentNode !== currentParent) {
|
|
56
|
+
throw new Error(DEV ? 'cannot update a detached keyed list' : 'V802')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const keys = values.map(options.key)
|
|
60
|
+
assertValidKeys(keys)
|
|
61
|
+
assertUniqueKeys(keys)
|
|
62
|
+
|
|
63
|
+
if (!hydrationPending && values.length === 0 && records.length > 0) {
|
|
64
|
+
const removed = records
|
|
65
|
+
records = []
|
|
66
|
+
deleteRangeContents(start, end)
|
|
67
|
+
const cleanup = disposeRecordOwners(removed)
|
|
68
|
+
if (cleanup[1]) throw cleanup[0]
|
|
69
|
+
return []
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!hydrationPending && hasRetainedPrefix(records, keys)) {
|
|
73
|
+
const retainedCount = Math.min(records.length, values.length)
|
|
74
|
+
const created: RecordState<T, K>[] = []
|
|
75
|
+
try {
|
|
76
|
+
for (let index = records.length; index < values.length; index += 1) {
|
|
77
|
+
const value = values[index] as T
|
|
78
|
+
const item = normalizeRenderResult(
|
|
79
|
+
withHydrationInsertion(parent, end, () => options.render(value, index)),
|
|
80
|
+
)
|
|
81
|
+
created.push([keys[index] as K, item[0], item[1], item[2]])
|
|
82
|
+
}
|
|
83
|
+
for (let index = 0; index < retainedCount; index += 1) {
|
|
84
|
+
records[index]![2]?.(values[index] as T, index)
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
disposeRecords(currentParent, created)
|
|
88
|
+
throw error
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const cleanup = disposeRecords(currentParent, records.slice(values.length))
|
|
92
|
+
const fragment = document.createDocumentFragment()
|
|
93
|
+
for (const record of created) {
|
|
94
|
+
for (const node of record[1]) fragment.append(node)
|
|
95
|
+
}
|
|
96
|
+
currentParent.insertBefore(fragment, end)
|
|
97
|
+
if (records.length !== values.length) {
|
|
98
|
+
records = retainedCount === 0 ? created : [...records.slice(0, retainedCount), ...created]
|
|
99
|
+
}
|
|
100
|
+
if (cleanup[1]) throw cleanup[0]
|
|
101
|
+
return collectRecordNodes(created)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const previousByKey = new Map<K, readonly [record: RecordState<T, K>, index: number]>()
|
|
105
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
106
|
+
const record = records[index] as RecordState<T, K>
|
|
107
|
+
previousByKey.set(record[0], [record, index])
|
|
108
|
+
}
|
|
109
|
+
const created: RecordState<T, K>[] = []
|
|
110
|
+
const retained: Array<readonly [RecordState<T, K>, T, number]> = []
|
|
111
|
+
const previousIndexes: number[] = []
|
|
112
|
+
let nextRecords: RecordState<T, K>[]
|
|
113
|
+
try {
|
|
114
|
+
nextRecords = values.map((value, index): RecordState<T, K> => {
|
|
115
|
+
const key = keys[index] as K
|
|
116
|
+
const previous = previousByKey.get(key)
|
|
117
|
+
if (previous !== undefined) {
|
|
118
|
+
previousByKey.delete(key)
|
|
119
|
+
retained.push([previous[0], value, index])
|
|
120
|
+
previousIndexes.push(previous[1])
|
|
121
|
+
return previous[0]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const item = normalizeRenderResult(
|
|
125
|
+
withHydrationInsertion(parent, end, () => options.render(value, index)),
|
|
126
|
+
)
|
|
127
|
+
const record: RecordState<T, K> = [key, item[0], item[1], item[2]]
|
|
128
|
+
created.push(record)
|
|
129
|
+
previousIndexes.push(-1)
|
|
130
|
+
return record
|
|
131
|
+
})
|
|
132
|
+
for (const [record, value, index] of retained) record[2]?.(value, index)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
disposeRecords(currentParent, created)
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cleanup = disposeRecords(currentParent, mapRecords(previousByKey.values()))
|
|
139
|
+
|
|
140
|
+
const stableRecords = longestIncreasingSubsequence(previousIndexes)
|
|
141
|
+
const hasRetainedMoves = previousIndexes.some(
|
|
142
|
+
(previousIndex, index) => previousIndex >= 0 && stableRecords[index] === 0,
|
|
143
|
+
)
|
|
144
|
+
const activeElement = hasRetainedMoves
|
|
145
|
+
? document.activeElement instanceof HTMLElement &&
|
|
146
|
+
currentParent.contains(document.activeElement)
|
|
147
|
+
? document.activeElement
|
|
148
|
+
: null
|
|
149
|
+
: null
|
|
150
|
+
const selectionControl =
|
|
151
|
+
activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement
|
|
152
|
+
? activeElement
|
|
153
|
+
: null
|
|
154
|
+
const selection =
|
|
155
|
+
selectionControl === null
|
|
156
|
+
? null
|
|
157
|
+
: ([selectionControl.selectionStart, selectionControl.selectionEnd] as const)
|
|
158
|
+
let cursor: Node = end
|
|
159
|
+
for (let recordIndex = nextRecords.length - 1; recordIndex >= 0; recordIndex -= 1) {
|
|
160
|
+
const record = nextRecords[recordIndex] as RecordState<T, K>
|
|
161
|
+
if (previousIndexes[recordIndex] === -1 || stableRecords[recordIndex] === 0) {
|
|
162
|
+
for (let nodeIndex = record[1].length - 1; nodeIndex >= 0; nodeIndex -= 1) {
|
|
163
|
+
const node = record[1][nodeIndex] as Node
|
|
164
|
+
if (node.nextSibling !== cursor) moveBefore(currentParent, node, cursor)
|
|
165
|
+
cursor = node
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
cursor = record[1][0] as Node
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
activeElement !== null &&
|
|
173
|
+
document.activeElement !== activeElement &&
|
|
174
|
+
currentParent.contains(activeElement)
|
|
175
|
+
) {
|
|
176
|
+
activeElement.focus({ preventScroll: true })
|
|
177
|
+
if (
|
|
178
|
+
selectionControl !== null &&
|
|
179
|
+
selection !== null &&
|
|
180
|
+
selection[0] !== null &&
|
|
181
|
+
selection[1] !== null
|
|
182
|
+
) {
|
|
183
|
+
selectionControl.setSelectionRange(selection[0], selection[1])
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
records = nextRecords
|
|
188
|
+
if (hydrationPending) {
|
|
189
|
+
hydrationPending = false
|
|
190
|
+
finishHydrationArrayRange(currentParent, end)
|
|
191
|
+
}
|
|
192
|
+
if (cleanup[1]) throw cleanup[0]
|
|
193
|
+
return collectRecordNodes(created)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const dispose = (): void => {
|
|
197
|
+
if (disposed) return
|
|
198
|
+
disposed = true
|
|
199
|
+
const currentParent = end.parentNode
|
|
200
|
+
const cleanup =
|
|
201
|
+
currentParent === null
|
|
202
|
+
? ([undefined, false] as const)
|
|
203
|
+
: disposeRecords(currentParent, records)
|
|
204
|
+
records = []
|
|
205
|
+
start.remove()
|
|
206
|
+
end.remove()
|
|
207
|
+
if (cleanup[1]) throw cleanup[0]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { dispose, parent: () => end.parentNode, update }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function* mapRecords<T, K>(
|
|
214
|
+
entries: Iterable<readonly [record: RecordState<T, K>, index: number]>,
|
|
215
|
+
): Iterable<RecordState<T, K>> {
|
|
216
|
+
for (const [record] of entries) yield record
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function collectRecordNodes<T, K>(records: readonly RecordState<T, K>[]): Node[] {
|
|
220
|
+
const nodes: Node[] = []
|
|
221
|
+
for (const record of records) nodes.push(...record[1])
|
|
222
|
+
return nodes
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function longestIncreasingSubsequence(indexes: readonly number[]): Uint8Array {
|
|
226
|
+
const predecessors = new Int32Array(indexes.length).fill(-1)
|
|
227
|
+
const tails: number[] = []
|
|
228
|
+
for (let index = 0; index < indexes.length; index += 1) {
|
|
229
|
+
const value = indexes[index] as number
|
|
230
|
+
if (value < 0) continue
|
|
231
|
+
let low = 0
|
|
232
|
+
let high = tails.length
|
|
233
|
+
while (low < high) {
|
|
234
|
+
const middle = (low + high) >>> 1
|
|
235
|
+
if ((indexes[tails[middle] as number] as number) < value) low = middle + 1
|
|
236
|
+
else high = middle
|
|
237
|
+
}
|
|
238
|
+
if (low > 0) predecessors[index] = tails[low - 1] as number
|
|
239
|
+
tails[low] = index
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const stable = new Uint8Array(indexes.length)
|
|
243
|
+
let index = tails.at(-1) ?? -1
|
|
244
|
+
while (index >= 0) {
|
|
245
|
+
stable[index] = 1
|
|
246
|
+
index = predecessors[index] as number
|
|
247
|
+
}
|
|
248
|
+
return stable
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function hasRetainedPrefix<T, K>(
|
|
252
|
+
records: readonly RecordState<T, K>[],
|
|
253
|
+
keys: readonly K[],
|
|
254
|
+
): boolean {
|
|
255
|
+
const retainedCount = Math.min(records.length, keys.length)
|
|
256
|
+
for (let index = 0; index < retainedCount; index += 1) {
|
|
257
|
+
if (records[index]![0] !== keys[index]) return false
|
|
258
|
+
}
|
|
259
|
+
return true
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function moveBefore(parent: Node, node: Node, before: Node): void {
|
|
263
|
+
const statePreservingParent = parent as Node & {
|
|
264
|
+
moveBefore?: (node: Node, before: Node | null) => void
|
|
265
|
+
}
|
|
266
|
+
if (statePreservingParent.moveBefore !== undefined && node.parentNode === parent) {
|
|
267
|
+
statePreservingParent.moveBefore(node, before)
|
|
268
|
+
} else {
|
|
269
|
+
parent.insertBefore(node, before)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function assertValidKeys<K>(keys: readonly K[]): void {
|
|
274
|
+
for (const key of keys) {
|
|
275
|
+
if (typeof key !== 'string' && typeof key !== 'number' && typeof key !== 'bigint') {
|
|
276
|
+
throw new Error(DEV ? `invalid key in keyed list: ${String(key)}` : 'V803')
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function assertUniqueKeys<K>(keys: readonly K[]): void {
|
|
282
|
+
const seen = new Set<K>()
|
|
283
|
+
for (const key of keys) {
|
|
284
|
+
if (seen.has(key)) {
|
|
285
|
+
throw new Error(DEV ? `duplicate key in keyed list: ${String(key)}` : 'V804')
|
|
286
|
+
}
|
|
287
|
+
seen.add(key)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function normalizeRenderResult<T>(result: KeyedRenderResult<T>): KeyedItem<T> {
|
|
292
|
+
if (isNode(result)) return [normalizeNodes([result])]
|
|
293
|
+
if (isNodeArray(result)) return [normalizeNodes(result)]
|
|
294
|
+
return [normalizeNodes(result[0]), result[1], result[2]]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeNodes(nodes: readonly Node[]): readonly Node[] {
|
|
298
|
+
if (nodes.length === 0) return [document.createComment(DEV ? 'vidact:empty' : '')]
|
|
299
|
+
let firstFragment = -1
|
|
300
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
301
|
+
if (nodes[index]!.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
302
|
+
firstFragment = index
|
|
303
|
+
break
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (firstFragment === -1) return nodes
|
|
307
|
+
const normalized = nodes.slice(0, firstFragment) as Node[]
|
|
308
|
+
for (let index = firstFragment; index < nodes.length; index += 1) {
|
|
309
|
+
const node = nodes[index]!
|
|
310
|
+
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) normalized.push(...node.childNodes)
|
|
311
|
+
else normalized.push(node)
|
|
312
|
+
}
|
|
313
|
+
return normalized.length === 0 ? [document.createComment(DEV ? 'vidact:empty' : '')] : normalized
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function isNode(value: unknown): value is Node {
|
|
317
|
+
return typeof value === 'object' && value !== null && 'nodeType' in value
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function isNodeArray<T>(value: KeyedRenderResult<T>): value is readonly Node[] {
|
|
321
|
+
return Array.isArray(value) && (value.length === 0 || isNode(value[0]))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function removeNodes(parent: Node, nodes: readonly Node[]): void {
|
|
325
|
+
for (const node of nodes) {
|
|
326
|
+
if (node.parentNode === parent) parent.removeChild(node)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function disposeRecords<T, K>(
|
|
331
|
+
parent: Node,
|
|
332
|
+
records: Iterable<RecordState<T, K>>,
|
|
333
|
+
): readonly [error: unknown, failed: boolean] {
|
|
334
|
+
let firstError: unknown
|
|
335
|
+
let failed = false
|
|
336
|
+
for (const record of records) {
|
|
337
|
+
removeNodes(parent, record[1])
|
|
338
|
+
try {
|
|
339
|
+
disposeRecord(record[3])
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (!failed) firstError = error
|
|
342
|
+
failed = true
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return [firstError, failed]
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function disposeRecordOwners<T, K>(
|
|
349
|
+
records: Iterable<RecordState<T, K>>,
|
|
350
|
+
): readonly [error: unknown, failed: boolean] {
|
|
351
|
+
let firstError: unknown
|
|
352
|
+
let failed = false
|
|
353
|
+
for (const record of records) {
|
|
354
|
+
try {
|
|
355
|
+
disposeRecord(record[3])
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (!failed) firstError = error
|
|
358
|
+
failed = true
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return [firstError, failed]
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function disposeRecord(disposer: KeyedDisposer | undefined): void {
|
|
365
|
+
if (typeof disposer === 'function') disposer()
|
|
366
|
+
else disposer?.[3]()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function deleteRangeContents(start: Node, end: Node): void {
|
|
370
|
+
const range = start.ownerDocument?.createRange() ?? document.createRange()
|
|
371
|
+
range.setStartAfter(start)
|
|
372
|
+
range.setEndBefore(end)
|
|
373
|
+
range.deleteContents()
|
|
374
|
+
}
|
|
375
|
+
import {
|
|
376
|
+
claimHydrationArrayRange,
|
|
377
|
+
finishHydrationArrayRange,
|
|
378
|
+
withHydrationInsertion,
|
|
379
|
+
} from './hydration-bridge.ts'
|