@remix-run/ui 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -1
- package/dist/accordion/index.js +2 -2
- package/dist/accordion/primitives.d.ts +1 -1
- package/dist/accordion/primitives.js.map +1 -1
- package/dist/anchor/index.js.map +1 -1
- package/dist/animation/animate-layout-mixin.js +1 -1
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/animate-mixins.js +2 -2
- package/dist/animation/animate-mixins.js.map +1 -1
- package/dist/animation/demos/color-interpolation.js +1 -1
- package/dist/animation/demos/color-interpolation.js.map +1 -1
- package/dist/animation/demos/drag-release.d.ts +1 -1
- package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
- package/dist/animation/demos/material-ripple.js +1 -3
- package/dist/animation/demos/material-ripple.js.map +1 -1
- package/dist/animation/index.js +4 -4
- package/dist/animation/spring.js.map +1 -1
- package/dist/breadcrumbs/index.js +2 -2
- package/dist/button/index.js +25 -25
- package/dist/button/index.js.map +1 -1
- package/dist/checkbox/index.js +11 -9
- package/dist/checkbox/index.js.map +1 -1
- package/dist/combobox/index.js +4 -4
- package/dist/combobox/index.js.map +1 -1
- package/dist/combobox/primitives.d.ts +1 -1
- package/dist/combobox/primitives.js +5 -5
- package/dist/combobox/primitives.js.map +1 -1
- package/dist/dev/refresh.d.ts +1 -0
- package/dist/dev/refresh.js +2 -0
- package/dist/dev/refresh.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -14
- package/dist/input/index.js +9 -9
- package/dist/input/index.js.map +1 -1
- package/dist/jsx-runtime.js +2 -2
- package/dist/listbox/index.js +2 -2
- package/dist/menu/hover-aim.js.map +1 -1
- package/dist/menu/index.js +3 -3
- package/dist/menu/primitives.d.ts +1 -1
- package/dist/menu/primitives.js +6 -6
- package/dist/menu/primitives.js.map +1 -1
- package/dist/popover/index.js +3 -3
- package/dist/popover/scroll-lock.js.map +1 -1
- package/dist/radio/index.js +7 -7
- package/dist/radio/index.js.map +1 -1
- package/dist/runtime/client-entry-boundary.d.ts +14 -0
- package/dist/runtime/client-entry-boundary.js +29 -0
- package/dist/runtime/client-entry-boundary.js.map +1 -0
- package/dist/runtime/component.d.ts +19 -4
- package/dist/runtime/component.js +39 -9
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.js +36 -3
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/children.d.ts +2 -0
- package/dist/runtime/core/children.js +26 -2
- package/dist/runtime/core/children.js.map +1 -1
- package/dist/runtime/core/props.js +2 -2
- package/dist/runtime/core/vnode.d.ts +3 -3
- package/dist/runtime/core/vnode.js +22 -1
- package/dist/runtime/core/vnode.js.map +1 -1
- package/dist/runtime/create-element.d.ts +2 -2
- package/dist/runtime/create-element.js +3 -2
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +307 -220
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.js +3 -3
- package/dist/runtime/dom.d.ts +12 -0
- package/dist/runtime/element-function.d.ts +3 -0
- package/dist/runtime/element-function.js +2 -0
- package/dist/runtime/element-function.js.map +1 -0
- package/dist/runtime/form-navigation.d.ts +25 -0
- package/dist/runtime/form-navigation.js +132 -0
- package/dist/runtime/form-navigation.js.map +1 -0
- package/dist/runtime/frame-resolution.d.ts +5 -0
- package/dist/runtime/frame-resolution.js +10 -0
- package/dist/runtime/frame-resolution.js.map +1 -0
- package/dist/runtime/frame.d.ts +59 -15
- package/dist/runtime/frame.js +383 -114
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +1 -1
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/key.d.ts +1 -0
- package/dist/runtime/key.js +2 -0
- package/dist/runtime/key.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.js +2 -2
- package/dist/runtime/mixins/link-mixin.js +4 -3
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +15 -10
- package/dist/runtime/mixins/mixin.js +4 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.js +1 -1
- package/dist/runtime/mixins/ref-mixin.js +1 -1
- package/dist/runtime/module-preloader.d.ts +8 -0
- package/dist/runtime/module-preloader.js +95 -0
- package/dist/runtime/module-preloader.js.map +1 -0
- package/dist/runtime/navigation.d.ts +4 -1
- package/dist/runtime/navigation.js +157 -32
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +11 -10
- package/dist/runtime/reconcile.js +549 -421
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/refresh.d.ts +28 -0
- package/dist/runtime/refresh.js +31 -0
- package/dist/runtime/refresh.js.map +1 -0
- package/dist/runtime/render.js +1 -1
- package/dist/runtime/render.js.map +1 -1
- package/dist/runtime/run.d.ts +3 -1
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +8 -16
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/to-vnode.d.ts +2 -2
- package/dist/runtime/to-vnode.js +68 -20
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vdom.d.ts +4 -2
- package/dist/runtime/vdom.js +87 -42
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/runtime/vnode.d.ts +138 -67
- package/dist/runtime/vnode.js +17 -12
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/select/index.js +11 -11
- package/dist/select/index.js.map +1 -1
- package/dist/select/primitives.d.ts +1 -1
- package/dist/select/primitives.js +6 -6
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +74 -29
- package/dist/server/stream.js.map +1 -1
- package/dist/shared/focus-styles.d.ts +1 -1
- package/dist/shared/focus-styles.js +1 -1
- package/dist/shared/focus-styles.js.map +1 -1
- package/dist/shared/listbox-popover-styles.js +1 -1
- package/dist/shared/style-values.js +26 -26
- package/dist/shared/style-values.js.map +1 -1
- package/dist/style/css-mixin.js +3 -3
- package/dist/style/index.js +2 -2
- package/dist/style/style.js +25 -6
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +0 -1
- package/dist/style/stylesheet.js +13 -54
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/tabs/index.js +11 -11
- package/dist/tabs/index.js.map +1 -1
- package/dist/tabs/primitives.d.ts +1 -1
- package/dist/test.js +1 -1
- package/dist/toggle/index.js +9 -9
- package/dist/toggle/index.js.map +1 -1
- package/dist/toggle/primitives.d.ts +1 -1
- package/package.json +10 -7
- package/src/animation/animate-mixins.ts +3 -3
- package/src/animation/demos/color-interpolation.tsx +1 -1
- package/src/button/index.ts +24 -24
- package/src/checkbox/index.ts +10 -8
- package/src/combobox/index.tsx +1 -1
- package/src/dev/refresh.ts +1 -0
- package/src/index.ts +1 -1
- package/src/input/index.ts +8 -8
- package/src/radio/index.ts +6 -5
- package/src/runtime/client-entry-boundary.ts +54 -0
- package/src/runtime/component.ts +65 -16
- package/src/runtime/core/attributes.ts +35 -1
- package/src/runtime/core/children.ts +24 -2
- package/src/runtime/core/vnode.ts +25 -4
- package/src/runtime/create-element.ts +6 -5
- package/src/runtime/demos/readme.demo.tsx +4 -4
- package/src/runtime/diff-dom.ts +370 -244
- package/src/runtime/dom.ts +331 -214
- package/src/runtime/element-function.ts +1 -0
- package/src/runtime/event-listeners.ts +1 -1
- package/src/runtime/form-navigation.ts +184 -0
- package/src/runtime/frame-resolution.ts +13 -0
- package/src/runtime/frame.ts +500 -124
- package/src/runtime/jsx.ts +3 -5
- package/src/runtime/key.ts +1 -0
- package/src/runtime/mixins/link-mixin.ts +1 -0
- package/src/runtime/mixins/mixin.ts +21 -18
- package/src/runtime/module-preloader.ts +115 -0
- package/src/runtime/navigation.ts +204 -30
- package/src/runtime/reconcile.ts +762 -759
- package/src/runtime/refresh.ts +46 -0
- package/src/runtime/run.ts +12 -2
- package/src/runtime/scheduler.ts +9 -29
- package/src/runtime/to-vnode.ts +85 -23
- package/src/runtime/vdom.ts +89 -70
- package/src/runtime/vnode.ts +192 -88
- package/src/select/index.tsx +9 -9
- package/src/server/README.md +13 -0
- package/src/server/stream.ts +93 -30
- package/src/shared/focus-styles.ts +1 -1
- package/src/shared/style-values.ts +26 -26
- package/src/style/style.ts +25 -6
- package/src/style/stylesheet.ts +32 -57
- package/src/tabs/index.tsx +12 -11
- package/src/test/README.md +151 -60
- package/src/toggle/index.tsx +9 -7
package/src/runtime/frame.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { jsx } from './jsx.ts'
|
|
2
|
-
import { Frame, createFrameHandle, type FrameContent } from './component.ts'
|
|
2
|
+
import { Frame, createFrameHandle, type FrameContent, type FrameResolution } from './component.ts'
|
|
3
3
|
import { createComponentErrorEvent, getComponentError } from './error-event.ts'
|
|
4
4
|
import { invariant } from './invariant.ts'
|
|
5
5
|
import type { RemixElement, RemixNode } from './jsx.ts'
|
|
6
|
+
import type { ElementFunction } from './element-function.ts'
|
|
6
7
|
import type { FrameHandle } from './component.ts'
|
|
7
8
|
import type { Scheduler, VirtualRoot } from './vdom.ts'
|
|
8
9
|
import { createRangeRoot, createRoot } from './vdom.ts'
|
|
9
10
|
import { diffNodes } from './diff-dom.ts'
|
|
10
11
|
import { createStyleManager, type StyleManager } from '../style/index.ts'
|
|
11
12
|
import { findFlushMarker, type FlushKind } from './stream-protocol.ts'
|
|
13
|
+
import { getDocumentModulePreloader } from './module-preloader.ts'
|
|
14
|
+
import { unwrapFrameResolution } from './frame-resolution.ts'
|
|
15
|
+
import {
|
|
16
|
+
disposeClientEntryBoundary,
|
|
17
|
+
getClientEntryBoundaryOwner,
|
|
18
|
+
setClientEntryBoundaryOwner,
|
|
19
|
+
type ClientEntryIdentity,
|
|
20
|
+
} from './client-entry-boundary.ts'
|
|
12
21
|
|
|
13
22
|
type FrameRoot = [Comment, Comment] | Element | Document | DocumentFragment
|
|
14
23
|
|
|
@@ -62,24 +71,71 @@ export type LoadModule = (moduleUrl: string, exportName: string) => Promise<Func
|
|
|
62
71
|
* Resolves content for a browser-loaded frame.
|
|
63
72
|
*
|
|
64
73
|
* @param src Source string from the `<Frame src>` prop.
|
|
65
|
-
* @param
|
|
66
|
-
* @
|
|
67
|
-
* @returns HTML, a stream of HTML bytes, or Remix node content to render into the frame.
|
|
74
|
+
* @param options Information about the active frame load or form submission.
|
|
75
|
+
* @returns Frame content or a response whose body should be rendered into the frame.
|
|
68
76
|
*/
|
|
69
77
|
export type ResolveFrame = (
|
|
70
78
|
src: string,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
options?: ResolveFrameOptions,
|
|
80
|
+
) => Promise<FrameResolution> | FrameResolution
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Information available while resolving browser-loaded frame content.
|
|
84
|
+
*/
|
|
85
|
+
export interface ResolveFrameOptions {
|
|
86
|
+
/** Optional name of the frame being loaded or reloaded. */
|
|
87
|
+
target?: string
|
|
88
|
+
/** Form values submitted to the frame source for a non-GET submission. */
|
|
89
|
+
formData?: FormData
|
|
90
|
+
/** HTTP method selected by the form and its submitter. */
|
|
91
|
+
method?: string
|
|
92
|
+
/** Form encoding selected by the form and its submitter. */
|
|
93
|
+
encType?: string
|
|
94
|
+
/** Aborts the reload when the navigation that started it is cancelled. */
|
|
95
|
+
signal?: AbortSignal
|
|
96
|
+
}
|
|
74
97
|
|
|
75
98
|
type InternalFrameContent = FrameContent | DocumentFragment
|
|
76
99
|
|
|
100
|
+
type FrameReloadOptions = Omit<ResolveFrameOptions, 'target'>
|
|
101
|
+
|
|
102
|
+
type FrameReloadResult = {
|
|
103
|
+
signal: AbortSignal
|
|
104
|
+
redirectedTo?: string
|
|
105
|
+
}
|
|
106
|
+
|
|
77
107
|
type FrameTemplateListener = (fragment: DocumentFragment) => void
|
|
78
108
|
|
|
79
109
|
const bufferedFrameTemplates = new Map<string, DocumentFragment[]>()
|
|
80
110
|
const frameTemplateListeners = new Map<string, Set<FrameTemplateListener>>()
|
|
81
111
|
const DOCTYPE_PATTERN = /<!doctype(?:\s[^>]*)?>/gi
|
|
82
112
|
|
|
113
|
+
function createLinkedAbortController(
|
|
114
|
+
first: AbortSignal,
|
|
115
|
+
second?: AbortSignal,
|
|
116
|
+
): { controller: AbortController; disconnect: () => void } {
|
|
117
|
+
let controller = new AbortController()
|
|
118
|
+
let signals = second ? [first, second] : [first]
|
|
119
|
+
let abort = () => controller.abort()
|
|
120
|
+
|
|
121
|
+
for (let signal of signals) {
|
|
122
|
+
if (signal.aborted) {
|
|
123
|
+
controller.abort()
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
controller,
|
|
131
|
+
disconnect() {
|
|
132
|
+
for (let signal of signals) {
|
|
133
|
+
signal.removeEventListener('abort', abort)
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
83
139
|
function stripDoctypeMarkup(html: string): string {
|
|
84
140
|
return html.replace(DOCTYPE_PATTERN, '')
|
|
85
141
|
}
|
|
@@ -98,7 +154,11 @@ function syncElementAttributes(target: Element, source: Element) {
|
|
|
98
154
|
}
|
|
99
155
|
}
|
|
100
156
|
|
|
157
|
+
const FRAME_RUNTIME = Symbol('FrameRuntime')
|
|
158
|
+
|
|
101
159
|
export type FrameRuntime = {
|
|
160
|
+
[FRAME_RUNTIME]: true
|
|
161
|
+
canResolveFrames?: boolean
|
|
102
162
|
topFrame?: FrameHandle
|
|
103
163
|
errorTarget: EventTarget
|
|
104
164
|
loadModule: LoadModule
|
|
@@ -106,11 +166,34 @@ export type FrameRuntime = {
|
|
|
106
166
|
pendingClientEntries: PendingClientEntries
|
|
107
167
|
scheduler: Scheduler
|
|
108
168
|
styleManager: StyleManager
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
moduleLoads: Map<string, Promise<Function | undefined>>
|
|
169
|
+
moduleCache: Map<string, ElementFunction>
|
|
170
|
+
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
112
171
|
frameInstances: WeakMap<Comment, Frame>
|
|
113
172
|
namedFrames: Map<string, FrameHandle>
|
|
173
|
+
serverFrameReload: { signal: AbortSignal } | undefined
|
|
174
|
+
reloadForNavigation?: (options?: FrameReloadOptions) => Promise<FrameReloadResult>
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function isFrameRuntime(value: unknown): value is FrameRuntime {
|
|
178
|
+
return isRecord(value) && Reflect.get(value, FRAME_RUNTIME) === true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Reloads a frame and returns response metadata used by the navigation runtime.
|
|
183
|
+
*
|
|
184
|
+
* @param frame Frame handle to reload.
|
|
185
|
+
* @param options Form submission metadata and cancellation signal.
|
|
186
|
+
* @returns The reload signal and final response URL, when redirected.
|
|
187
|
+
*/
|
|
188
|
+
export function reloadFrameForNavigation(
|
|
189
|
+
frame: FrameHandle,
|
|
190
|
+
options?: FrameReloadOptions,
|
|
191
|
+
): Promise<FrameReloadResult> {
|
|
192
|
+
let runtime = frame.$runtime
|
|
193
|
+
invariant(isFrameRuntime(runtime), 'Expected a frame runtime')
|
|
194
|
+
let reload = runtime.reloadForNavigation
|
|
195
|
+
invariant(reload, 'Expected frame runtime to support navigation reloads')
|
|
196
|
+
return reload(options)
|
|
114
197
|
}
|
|
115
198
|
|
|
116
199
|
export type FrameContext = {
|
|
@@ -123,13 +206,15 @@ export type FrameContext = {
|
|
|
123
206
|
frame: FrameHandle
|
|
124
207
|
styleManager: StyleManager
|
|
125
208
|
data: RmxData
|
|
126
|
-
moduleCache: Map<string,
|
|
127
|
-
moduleLoads: Map<string, Promise<
|
|
209
|
+
moduleCache: Map<string, ElementFunction>
|
|
210
|
+
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
128
211
|
frameInstances: WeakMap<Comment, Frame>
|
|
129
212
|
namedFrames: Map<string, FrameHandle>
|
|
213
|
+
lifecycleSignal: AbortSignal
|
|
130
214
|
regionTailRef?: ChildNode | null
|
|
131
215
|
regionParent?: ParentNode | null
|
|
132
216
|
signal?: AbortSignal
|
|
217
|
+
isActiveModulePreload?: (node: Node) => boolean
|
|
133
218
|
}
|
|
134
219
|
|
|
135
220
|
type FrameInit = {
|
|
@@ -144,8 +229,8 @@ type FrameInit = {
|
|
|
144
229
|
styleManager?: StyleManager
|
|
145
230
|
marker?: FrameMarkerData
|
|
146
231
|
data: RmxData
|
|
147
|
-
moduleCache: Map<string,
|
|
148
|
-
moduleLoads: Map<string, Promise<
|
|
232
|
+
moduleCache: Map<string, ElementFunction>
|
|
233
|
+
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
149
234
|
frameInstances: WeakMap<Comment, Frame>
|
|
150
235
|
namedFrames: Map<string, FrameHandle>
|
|
151
236
|
}
|
|
@@ -156,6 +241,11 @@ export type Frame = {
|
|
|
156
241
|
flush: () => void
|
|
157
242
|
clearPendingTemplateWatch: () => void
|
|
158
243
|
isDisplayingResolvedContent: () => boolean
|
|
244
|
+
beginClientFrameReloadForAncestorReload: (signal: AbortSignal) => {
|
|
245
|
+
controller: AbortController
|
|
246
|
+
complete: () => void
|
|
247
|
+
}
|
|
248
|
+
cancelReload: () => void
|
|
159
249
|
startInheritedReload: (signal?: AbortSignal) => void
|
|
160
250
|
updateMarker: (marker: FrameMarkerData, options?: RenderOptions) => Promise<void>
|
|
161
251
|
renderMarkerContent: (
|
|
@@ -163,6 +253,7 @@ export type Frame = {
|
|
|
163
253
|
content: InternalFrameContent,
|
|
164
254
|
options?: RenderOptions,
|
|
165
255
|
) => Promise<void>
|
|
256
|
+
matchesIdentity: (src: string, name: string | undefined) => boolean
|
|
166
257
|
dispose: () => void
|
|
167
258
|
handle: FrameHandle
|
|
168
259
|
}
|
|
@@ -172,13 +263,24 @@ type RenderOptions = {
|
|
|
172
263
|
initialHydrationTracker?: InitialHydrationTracker
|
|
173
264
|
signal?: AbortSignal
|
|
174
265
|
contentStatus?: 'pending' | 'resolved'
|
|
266
|
+
data?: RmxData
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
type ActiveRenderOptions = RenderOptions & {
|
|
270
|
+
data: RmxData
|
|
175
271
|
}
|
|
176
272
|
|
|
177
273
|
export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
178
274
|
let container = createContainer(root)
|
|
179
275
|
let contentRoot: VirtualRoot | undefined
|
|
180
276
|
let reloadController: AbortController | undefined
|
|
277
|
+
// The style registry is document-level and shared by every frame; only the
|
|
278
|
+
// frame that created it (the runtime root) disposes it.
|
|
279
|
+
let ownsStyleManager = !init.styleManager
|
|
280
|
+
let reloadAbortUnsubscribe: (() => void) | undefined
|
|
281
|
+
let reloadKind: 'direct' | 'ancestor' | undefined
|
|
181
282
|
let styleManager = init.styleManager ?? createStyleManager()
|
|
283
|
+
let modulePreloader = getDocumentModulePreloader(container.doc)
|
|
182
284
|
let currentMarker = init.marker
|
|
183
285
|
let displayedContentStatus: 'pending' | 'resolved' = init.marker?.status ?? 'resolved'
|
|
184
286
|
let pendingTemplateMarkerId: string | undefined
|
|
@@ -186,36 +288,24 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
186
288
|
let pendingTemplateUnsubscribe: (() => void) | undefined
|
|
187
289
|
let inheritedReloadPending = false
|
|
188
290
|
let inheritedReloadAbortUnsubscribe: (() => void) | undefined
|
|
291
|
+
let disposed = false
|
|
292
|
+
let lifecycleController = new AbortController()
|
|
293
|
+
|
|
294
|
+
if (isDocumentNode(container.root)) {
|
|
295
|
+
modulePreloader.adoptInitialPreloadLinks(container.root)
|
|
296
|
+
} else {
|
|
297
|
+
modulePreloader.consumePreloadLinks(container.root)
|
|
298
|
+
}
|
|
189
299
|
|
|
190
300
|
// Merge any rmx-data found in the current document once at startup.
|
|
191
301
|
mergeRmxDataFromDocument(init.data, container.doc)
|
|
192
302
|
|
|
193
|
-
let runtime = createFrameRuntime({ ...init, styleManager })
|
|
303
|
+
let runtime = createFrameRuntime({ ...init, styleManager, reloadForNavigation: reload })
|
|
194
304
|
|
|
195
305
|
let frame = createFrameHandle({
|
|
196
306
|
src: init.src,
|
|
197
307
|
$runtime: runtime,
|
|
198
|
-
reload: async () =>
|
|
199
|
-
reloadController?.abort()
|
|
200
|
-
let controller = new AbortController()
|
|
201
|
-
reloadController = controller
|
|
202
|
-
frame.dispatchEvent(new Event('reloadStart'))
|
|
203
|
-
startSubFrameInheritedReloads(getContentNodes(), controller.signal)
|
|
204
|
-
try {
|
|
205
|
-
let content = await init.resolveFrame(frame.src, controller.signal, frameName)
|
|
206
|
-
if (reloadController !== controller || controller.signal.aborted) return controller.signal
|
|
207
|
-
await render(content, { signal: controller.signal })
|
|
208
|
-
return controller.signal
|
|
209
|
-
} catch (error) {
|
|
210
|
-
if (reloadController !== controller || controller.signal.aborted) return controller.signal
|
|
211
|
-
init.errorTarget.dispatchEvent(createComponentErrorEvent(error))
|
|
212
|
-
throw error
|
|
213
|
-
} finally {
|
|
214
|
-
if (reloadController === controller) {
|
|
215
|
-
frame.dispatchEvent(new Event('reloadComplete'))
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
},
|
|
308
|
+
reload: async () => (await reload()).signal,
|
|
219
309
|
replace: async (content: FrameContent) => {
|
|
220
310
|
await render(content)
|
|
221
311
|
},
|
|
@@ -241,18 +331,47 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
241
331
|
moduleLoads: init.moduleLoads,
|
|
242
332
|
frameInstances: init.frameInstances,
|
|
243
333
|
namedFrames: init.namedFrames,
|
|
334
|
+
lifecycleSignal: lifecycleController.signal,
|
|
244
335
|
regionTailRef: container.regionTailRef,
|
|
245
336
|
regionParent: container.regionParent,
|
|
246
337
|
}
|
|
247
338
|
|
|
248
339
|
async function render(content: InternalFrameContent, options?: RenderOptions): Promise<void> {
|
|
249
|
-
if (options?.signal?.aborted) return
|
|
340
|
+
if (disposed || lifecycleController.signal.aborted || options?.signal?.aborted) return
|
|
341
|
+
let ownsData = options?.data === undefined
|
|
342
|
+
let renderOptions: ActiveRenderOptions = {
|
|
343
|
+
...options,
|
|
344
|
+
data: options?.data ?? {},
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
await renderContent(content, renderOptions)
|
|
349
|
+
} finally {
|
|
350
|
+
if (ownsData) clearRmxData(renderOptions.data)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function renderContent(
|
|
355
|
+
content: InternalFrameContent,
|
|
356
|
+
options: ActiveRenderOptions,
|
|
357
|
+
): Promise<void> {
|
|
358
|
+
if (isRenderAborted(options.signal)) return
|
|
250
359
|
|
|
251
360
|
if (content instanceof ReadableStream) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
await
|
|
255
|
-
|
|
361
|
+
let linkedAbort = createLinkedAbortController(lifecycleController.signal, options.signal)
|
|
362
|
+
try {
|
|
363
|
+
await renderFrameStream(
|
|
364
|
+
content,
|
|
365
|
+
container.doc,
|
|
366
|
+
async (html, flushKind) => {
|
|
367
|
+
if (isRenderAborted(options.signal)) return
|
|
368
|
+
await render(html, { ...options, flushKind })
|
|
369
|
+
},
|
|
370
|
+
linkedAbort.controller.signal,
|
|
371
|
+
)
|
|
372
|
+
} finally {
|
|
373
|
+
linkedAbort.disconnect()
|
|
374
|
+
}
|
|
256
375
|
return
|
|
257
376
|
}
|
|
258
377
|
|
|
@@ -265,9 +384,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
265
384
|
contentRoot = createFrameContentRoot()
|
|
266
385
|
}
|
|
267
386
|
|
|
268
|
-
if (options
|
|
387
|
+
if (isRenderAborted(options.signal)) return
|
|
269
388
|
contentRoot.render(content)
|
|
270
|
-
displayedContentStatus = options
|
|
389
|
+
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
271
390
|
return
|
|
272
391
|
}
|
|
273
392
|
|
|
@@ -293,61 +412,86 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
293
412
|
let isFullDocumentReload =
|
|
294
413
|
container.root instanceof Document &&
|
|
295
414
|
htmlContent !== undefined &&
|
|
296
|
-
options
|
|
415
|
+
options.flushKind === 'document'
|
|
297
416
|
|
|
298
417
|
if (isFullDocumentReload && htmlContent !== undefined) {
|
|
299
418
|
let parsed = new DOMParser().parseFromString(htmlContent, 'text/html')
|
|
300
|
-
|
|
301
|
-
|
|
419
|
+
modulePreloader.consumePreloadLinks(parsed)
|
|
420
|
+
let responseData = options.data
|
|
421
|
+
mergeRmxDataFromDocument(responseData, parsed)
|
|
422
|
+
let responseContext = { ...context, data: responseData }
|
|
423
|
+
context.styleManager.adoptServerStyles(
|
|
302
424
|
collectFrameServerStyleTags(createElementContainer(parsed)),
|
|
303
425
|
)
|
|
304
426
|
|
|
305
427
|
syncElementAttributes(container.doc.documentElement, parsed.documentElement)
|
|
306
428
|
|
|
307
429
|
diffNodes([container.doc.head], [parsed.head], {
|
|
308
|
-
...
|
|
430
|
+
...responseContext,
|
|
309
431
|
regionParent: container.doc.documentElement,
|
|
310
432
|
regionTailRef: null,
|
|
311
|
-
signal: options
|
|
433
|
+
signal: options.signal,
|
|
434
|
+
isActiveModulePreload: modulePreloader.hasActivePreloads()
|
|
435
|
+
? modulePreloader.isActivePreload
|
|
436
|
+
: undefined,
|
|
312
437
|
})
|
|
313
438
|
diffNodes([container.doc.body], [parsed.body], {
|
|
314
|
-
...
|
|
439
|
+
...responseContext,
|
|
315
440
|
regionParent: container.doc.documentElement,
|
|
316
441
|
regionTailRef: null,
|
|
317
|
-
signal: options
|
|
442
|
+
signal: options.signal,
|
|
318
443
|
})
|
|
319
444
|
|
|
320
445
|
let bodyContainer = createElementContainer(container.doc.body)
|
|
321
|
-
if (options
|
|
322
|
-
scheduleHydrationInContainer(
|
|
323
|
-
|
|
324
|
-
|
|
446
|
+
if (isRenderAborted(options.signal)) return
|
|
447
|
+
scheduleHydrationInContainer(
|
|
448
|
+
bodyContainer,
|
|
449
|
+
responseContext,
|
|
450
|
+
options.initialHydrationTracker,
|
|
451
|
+
options.signal,
|
|
452
|
+
)
|
|
453
|
+
await createSubFrames(bodyContainer.childNodes, responseContext, options)
|
|
454
|
+
if (isRenderAborted(options.signal)) return
|
|
455
|
+
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
325
456
|
return
|
|
326
457
|
}
|
|
327
458
|
|
|
328
459
|
let fragment =
|
|
329
460
|
htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content
|
|
330
|
-
|
|
461
|
+
modulePreloader.consumePreloadLinks(fragment)
|
|
462
|
+
context.styleManager.adoptServerStyles(
|
|
331
463
|
collectFrameServerStyleTags(createElementContainer(fragment)),
|
|
332
464
|
)
|
|
333
465
|
removeEmptyHeads(fragment)
|
|
334
|
-
|
|
466
|
+
let responseData = options.data
|
|
467
|
+
mergeRmxDataFromFragment(responseData, fragment)
|
|
468
|
+
let responseContext = { ...context, data: responseData }
|
|
335
469
|
|
|
336
470
|
let nextContainer = createContainer(fragment)
|
|
337
471
|
|
|
338
|
-
if (options
|
|
472
|
+
if (isRenderAborted(options.signal)) return
|
|
339
473
|
|
|
340
474
|
diffNodes(container.childNodes, Array.from(nextContainer.childNodes), {
|
|
341
|
-
...
|
|
475
|
+
...responseContext,
|
|
342
476
|
regionTailRef: container.regionTailRef,
|
|
343
477
|
regionParent: container.regionParent,
|
|
344
|
-
signal: options
|
|
478
|
+
signal: options.signal,
|
|
345
479
|
})
|
|
346
480
|
|
|
347
|
-
if (options
|
|
348
|
-
scheduleHydrationInContainer(
|
|
349
|
-
|
|
350
|
-
|
|
481
|
+
if (isRenderAborted(options.signal)) return
|
|
482
|
+
scheduleHydrationInContainer(
|
|
483
|
+
container,
|
|
484
|
+
responseContext,
|
|
485
|
+
options.initialHydrationTracker,
|
|
486
|
+
options.signal,
|
|
487
|
+
)
|
|
488
|
+
await createSubFrames(container.childNodes, responseContext, options)
|
|
489
|
+
if (isRenderAborted(options.signal)) return
|
|
490
|
+
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function isRenderAborted(signal?: AbortSignal): boolean {
|
|
494
|
+
return disposed || lifecycleController.signal.aborted || signal?.aborted === true
|
|
351
495
|
}
|
|
352
496
|
|
|
353
497
|
function createFrameContentRoot(): VirtualRoot {
|
|
@@ -390,21 +534,36 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
390
534
|
async function hydrateInitial(): Promise<void> {
|
|
391
535
|
let initialHydrationTracker = createInitialHydrationTracker()
|
|
392
536
|
|
|
393
|
-
context.styleManager.
|
|
394
|
-
|
|
537
|
+
context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container))
|
|
538
|
+
let subFramesReady = createSubFrames(container.childNodes, context)
|
|
395
539
|
scheduleHydrationInContainer(container, context, initialHydrationTracker)
|
|
396
540
|
|
|
397
|
-
|
|
398
|
-
await
|
|
399
|
-
|
|
541
|
+
try {
|
|
542
|
+
await subFramesReady
|
|
543
|
+
|
|
544
|
+
if (disposed || context.lifecycleSignal.aborted) return
|
|
545
|
+
|
|
546
|
+
if (currentMarker?.status === 'pending') {
|
|
547
|
+
await watchPendingFrameTemplate(currentMarker, initialHydrationTracker)
|
|
548
|
+
}
|
|
400
549
|
|
|
401
|
-
|
|
402
|
-
|
|
550
|
+
initialHydrationTracker.finalize()
|
|
551
|
+
await initialHydrationTracker.ready()
|
|
552
|
+
} finally {
|
|
553
|
+
clearRmxData(context.data)
|
|
554
|
+
}
|
|
403
555
|
}
|
|
404
556
|
|
|
405
557
|
function dispose(): void {
|
|
558
|
+
if (disposed) return
|
|
559
|
+
disposed = true
|
|
560
|
+
lifecycleController.abort()
|
|
561
|
+
clearRmxData(context.data)
|
|
406
562
|
reloadController?.abort()
|
|
407
563
|
reloadController = undefined
|
|
564
|
+
reloadAbortUnsubscribe?.()
|
|
565
|
+
reloadAbortUnsubscribe = undefined
|
|
566
|
+
reloadKind = undefined
|
|
408
567
|
contentRoot?.dispose()
|
|
409
568
|
contentRoot = undefined
|
|
410
569
|
clearPendingFrameTemplateWatch()
|
|
@@ -414,7 +573,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
414
573
|
|
|
415
574
|
// Dispose sub-frames recursively.
|
|
416
575
|
disposeSubFrames(container.childNodes, context)
|
|
417
|
-
|
|
576
|
+
if (ownsStyleManager) {
|
|
577
|
+
context.styleManager.dispose()
|
|
578
|
+
}
|
|
418
579
|
|
|
419
580
|
if (frameName) {
|
|
420
581
|
if (init.namedFrames.get(frameName) === frame) {
|
|
@@ -431,15 +592,18 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
431
592
|
flush: () => context.scheduler.dequeue(),
|
|
432
593
|
clearPendingTemplateWatch: clearPendingFrameTemplateWatch,
|
|
433
594
|
isDisplayingResolvedContent: () => displayedContentStatus === 'resolved',
|
|
595
|
+
beginClientFrameReloadForAncestorReload,
|
|
596
|
+
cancelReload,
|
|
434
597
|
startInheritedReload,
|
|
435
598
|
updateMarker,
|
|
436
599
|
renderMarkerContent,
|
|
600
|
+
matchesIdentity: (src, name) => !disposed && frame.src === src && frameName === name,
|
|
437
601
|
dispose,
|
|
438
602
|
handle: frame,
|
|
439
603
|
}
|
|
440
604
|
|
|
441
605
|
async function updateMarker(marker: FrameMarkerData, options?: RenderOptions): Promise<void> {
|
|
442
|
-
if (options?.signal?.aborted) return
|
|
606
|
+
if (disposed || context.lifecycleSignal.aborted || options?.signal?.aborted) return
|
|
443
607
|
let previousMarker = currentMarker
|
|
444
608
|
let isInheritedReload = previousMarker !== undefined && previousMarker.id !== marker.id
|
|
445
609
|
currentMarker = marker
|
|
@@ -472,7 +636,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
472
636
|
content: InternalFrameContent,
|
|
473
637
|
options?: RenderOptions,
|
|
474
638
|
): Promise<void> {
|
|
475
|
-
if (options?.signal?.aborted) return
|
|
639
|
+
if (disposed || context.lifecycleSignal.aborted || options?.signal?.aborted) return
|
|
476
640
|
let previousMarker = currentMarker
|
|
477
641
|
let isInheritedReload = previousMarker !== undefined && previousMarker.id !== marker.id
|
|
478
642
|
currentMarker = marker
|
|
@@ -484,11 +648,119 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
484
648
|
clearPendingFrameTemplateWatch()
|
|
485
649
|
await render(content, { ...options, contentStatus: 'resolved' })
|
|
486
650
|
|
|
487
|
-
if (
|
|
651
|
+
if (
|
|
652
|
+
isInheritedReload &&
|
|
653
|
+
!disposed &&
|
|
654
|
+
!context.lifecycleSignal.aborted &&
|
|
655
|
+
!options?.signal?.aborted
|
|
656
|
+
) {
|
|
488
657
|
completeInheritedReload()
|
|
489
658
|
}
|
|
490
659
|
}
|
|
491
660
|
|
|
661
|
+
async function reload(options?: FrameReloadOptions): Promise<FrameReloadResult> {
|
|
662
|
+
let controller = startReload(options?.signal)
|
|
663
|
+
return await resolveAndRenderReload(controller, options)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function startReload(signal?: AbortSignal): AbortController {
|
|
667
|
+
let controller = replaceReloadController(signal)
|
|
668
|
+
reloadKind = 'direct'
|
|
669
|
+
frame.dispatchEvent(new Event('reloadStart'))
|
|
670
|
+
startSubFrameInheritedReloads(getContentNodes(), controller.signal)
|
|
671
|
+
return controller
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function beginClientFrameReloadForAncestorReload(signal: AbortSignal): {
|
|
675
|
+
controller: AbortController
|
|
676
|
+
complete: () => void
|
|
677
|
+
} {
|
|
678
|
+
let inheritedReloadStarted = reuseInheritedReloadStart()
|
|
679
|
+
let continuingAncestorReload = reloadKind === 'ancestor'
|
|
680
|
+
let controller = replaceReloadController(signal)
|
|
681
|
+
reloadKind = 'ancestor'
|
|
682
|
+
|
|
683
|
+
if (!inheritedReloadStarted && !continuingAncestorReload) {
|
|
684
|
+
frame.dispatchEvent(new Event('reloadStart'))
|
|
685
|
+
startSubFrameInheritedReloads(getContentNodes(), controller.signal)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return {
|
|
689
|
+
controller,
|
|
690
|
+
complete: () => completeReload(controller),
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function cancelReload(): void {
|
|
695
|
+
let controller = reloadController
|
|
696
|
+
if (!controller) return
|
|
697
|
+
controller.abort()
|
|
698
|
+
completeReload(controller)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function replaceReloadController(signal?: AbortSignal): AbortController {
|
|
702
|
+
reloadController?.abort()
|
|
703
|
+
reloadAbortUnsubscribe?.()
|
|
704
|
+
reloadAbortUnsubscribe = undefined
|
|
705
|
+
|
|
706
|
+
let controller = new AbortController()
|
|
707
|
+
reloadController = controller
|
|
708
|
+
|
|
709
|
+
if (signal) {
|
|
710
|
+
if (signal.aborted) {
|
|
711
|
+
controller.abort()
|
|
712
|
+
} else {
|
|
713
|
+
let abort = () => controller.abort()
|
|
714
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
715
|
+
reloadAbortUnsubscribe = () => signal.removeEventListener('abort', abort)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return controller
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function resolveAndRenderReload(
|
|
723
|
+
controller: AbortController,
|
|
724
|
+
options?: FrameReloadOptions,
|
|
725
|
+
): Promise<FrameReloadResult> {
|
|
726
|
+
try {
|
|
727
|
+
let resolution = await init.resolveFrame(frame.src, {
|
|
728
|
+
...options,
|
|
729
|
+
signal: controller.signal,
|
|
730
|
+
target: frameName,
|
|
731
|
+
})
|
|
732
|
+
if (reloadController !== controller || controller.signal.aborted) {
|
|
733
|
+
return { signal: controller.signal }
|
|
734
|
+
}
|
|
735
|
+
let { content, redirectedTo } = await unwrapFrameResolution(resolution)
|
|
736
|
+
if (reloadController !== controller || controller.signal.aborted) {
|
|
737
|
+
return { signal: controller.signal }
|
|
738
|
+
}
|
|
739
|
+
await render(content, { signal: controller.signal })
|
|
740
|
+
return {
|
|
741
|
+
signal: controller.signal,
|
|
742
|
+
redirectedTo:
|
|
743
|
+
reloadController === controller && !controller.signal.aborted ? redirectedTo : undefined,
|
|
744
|
+
}
|
|
745
|
+
} catch (error) {
|
|
746
|
+
if (reloadController !== controller || controller.signal.aborted) {
|
|
747
|
+
return { signal: controller.signal }
|
|
748
|
+
}
|
|
749
|
+
init.errorTarget.dispatchEvent(createComponentErrorEvent(error))
|
|
750
|
+
throw error
|
|
751
|
+
} finally {
|
|
752
|
+
completeReload(controller)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function completeReload(controller: AbortController): void {
|
|
757
|
+
if (reloadController !== controller || reloadKind === undefined) return
|
|
758
|
+
reloadAbortUnsubscribe?.()
|
|
759
|
+
reloadAbortUnsubscribe = undefined
|
|
760
|
+
reloadKind = undefined
|
|
761
|
+
frame.dispatchEvent(new Event('reloadComplete'))
|
|
762
|
+
}
|
|
763
|
+
|
|
492
764
|
function startInheritedReload(signal?: AbortSignal): void {
|
|
493
765
|
if (signal?.aborted) return
|
|
494
766
|
if (!inheritedReloadPending) {
|
|
@@ -508,6 +780,14 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
508
780
|
}
|
|
509
781
|
}
|
|
510
782
|
|
|
783
|
+
function reuseInheritedReloadStart(): boolean {
|
|
784
|
+
if (!inheritedReloadPending) return false
|
|
785
|
+
inheritedReloadPending = false
|
|
786
|
+
inheritedReloadAbortUnsubscribe?.()
|
|
787
|
+
inheritedReloadAbortUnsubscribe = undefined
|
|
788
|
+
return true
|
|
789
|
+
}
|
|
790
|
+
|
|
511
791
|
function completeInheritedReload(): void {
|
|
512
792
|
if (!inheritedReloadPending) return
|
|
513
793
|
inheritedReloadPending = false
|
|
@@ -525,7 +805,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
525
805
|
if (isFrameStart(node)) {
|
|
526
806
|
let end = findEndMarker(node, isFrameStart, isFrameEnd)
|
|
527
807
|
context.frameInstances.get(node)?.startInheritedReload(signal)
|
|
528
|
-
i = nodes
|
|
808
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
529
809
|
continue
|
|
530
810
|
}
|
|
531
811
|
|
|
@@ -549,7 +829,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
549
829
|
signal?: AbortSignal,
|
|
550
830
|
onResolved?: () => void,
|
|
551
831
|
): Promise<void> {
|
|
552
|
-
if (signal?.aborted) return
|
|
832
|
+
if (disposed || context.lifecycleSignal.aborted || signal?.aborted) return
|
|
553
833
|
if (pendingTemplateMarkerId === marker.id) return
|
|
554
834
|
|
|
555
835
|
clearPendingFrameTemplateWatch()
|
|
@@ -559,11 +839,11 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
559
839
|
if (early) {
|
|
560
840
|
clearPendingFrameTemplateWatch()
|
|
561
841
|
await render(early, { initialHydrationTracker, signal, contentStatus: 'resolved' })
|
|
562
|
-
if (!signal?.aborted) onResolved?.()
|
|
842
|
+
if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
|
|
563
843
|
return
|
|
564
844
|
}
|
|
565
845
|
|
|
566
|
-
if (signal?.aborted) {
|
|
846
|
+
if (disposed || context.lifecycleSignal.aborted || signal?.aborted) {
|
|
567
847
|
clearPendingFrameTemplateWatch()
|
|
568
848
|
return
|
|
569
849
|
}
|
|
@@ -571,11 +851,11 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
571
851
|
let observer = setupTemplateObserver()
|
|
572
852
|
pendingTemplateObserver = observer
|
|
573
853
|
let unsubscribe = subscribeFrameTemplate(marker.id, async (fragment) => {
|
|
574
|
-
if (signal?.aborted) return
|
|
854
|
+
if (disposed || context.lifecycleSignal.aborted || signal?.aborted) return
|
|
575
855
|
if (pendingTemplateMarkerId !== marker.id) return
|
|
576
856
|
clearPendingFrameTemplateWatch()
|
|
577
857
|
await render(fragment, { signal, contentStatus: 'resolved' })
|
|
578
|
-
if (!signal?.aborted) onResolved?.()
|
|
858
|
+
if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
|
|
579
859
|
})
|
|
580
860
|
pendingTemplateUnsubscribe = unsubscribe
|
|
581
861
|
|
|
@@ -593,7 +873,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
593
873
|
if (buffered) {
|
|
594
874
|
clearPendingFrameTemplateWatch()
|
|
595
875
|
await render(buffered, { initialHydrationTracker, signal, contentStatus: 'resolved' })
|
|
596
|
-
if (!signal?.aborted) onResolved?.()
|
|
876
|
+
if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
|
|
597
877
|
}
|
|
598
878
|
}
|
|
599
879
|
}
|
|
@@ -606,13 +886,14 @@ export function createFrameRuntime(init: {
|
|
|
606
886
|
pendingClientEntries: PendingClientEntries
|
|
607
887
|
scheduler: Scheduler
|
|
608
888
|
styleManager: StyleManager
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
moduleLoads: Map<string, Promise<Function | undefined>>
|
|
889
|
+
moduleCache: Map<string, ElementFunction>
|
|
890
|
+
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
612
891
|
frameInstances: WeakMap<Comment, Frame>
|
|
613
892
|
namedFrames: Map<string, FrameHandle>
|
|
893
|
+
reloadForNavigation?: (options?: FrameReloadOptions) => Promise<FrameReloadResult>
|
|
614
894
|
}): FrameRuntime {
|
|
615
895
|
return {
|
|
896
|
+
[FRAME_RUNTIME]: true,
|
|
616
897
|
topFrame: init.topFrame,
|
|
617
898
|
errorTarget: init.errorTarget,
|
|
618
899
|
loadModule: init.loadModule,
|
|
@@ -620,11 +901,12 @@ export function createFrameRuntime(init: {
|
|
|
620
901
|
pendingClientEntries: init.pendingClientEntries,
|
|
621
902
|
scheduler: init.scheduler,
|
|
622
903
|
styleManager: init.styleManager,
|
|
623
|
-
data: init.data,
|
|
624
904
|
moduleCache: init.moduleCache,
|
|
625
905
|
moduleLoads: init.moduleLoads,
|
|
626
906
|
frameInstances: init.frameInstances,
|
|
627
907
|
namedFrames: init.namedFrames,
|
|
908
|
+
serverFrameReload: undefined,
|
|
909
|
+
reloadForNavigation: init.reloadForNavigation,
|
|
628
910
|
}
|
|
629
911
|
}
|
|
630
912
|
|
|
@@ -689,6 +971,11 @@ function mergeRmxDataFromFragment(into: RmxData, fragment: DocumentFragment): vo
|
|
|
689
971
|
}
|
|
690
972
|
}
|
|
691
973
|
|
|
974
|
+
function clearRmxData(data: RmxData): void {
|
|
975
|
+
delete data.h
|
|
976
|
+
delete data.f
|
|
977
|
+
}
|
|
978
|
+
|
|
692
979
|
function removeEmptyHeads(fragment: DocumentFragment): void {
|
|
693
980
|
let heads = Array.from(fragment.querySelectorAll('head'))
|
|
694
981
|
for (let head of heads) {
|
|
@@ -715,7 +1002,7 @@ function collectOwnedServerStyleTags(nodes: Node[], styles: HTMLStyleElement[]):
|
|
|
715
1002
|
|
|
716
1003
|
if (isFrameStart(node)) {
|
|
717
1004
|
let end = findEndMarker(node, isFrameStart, isFrameEnd)
|
|
718
|
-
i = nodes
|
|
1005
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
719
1006
|
continue
|
|
720
1007
|
}
|
|
721
1008
|
|
|
@@ -724,7 +1011,7 @@ function collectOwnedServerStyleTags(nodes: Node[], styles: HTMLStyleElement[]):
|
|
|
724
1011
|
continue
|
|
725
1012
|
}
|
|
726
1013
|
|
|
727
|
-
if (node
|
|
1014
|
+
if (node.childNodes.length > 0) {
|
|
728
1015
|
collectOwnedServerStyleTags(Array.from(node.childNodes), styles)
|
|
729
1016
|
}
|
|
730
1017
|
}
|
|
@@ -763,6 +1050,7 @@ function scheduleHydrationInContainer(
|
|
|
763
1050
|
container: FrameContainer,
|
|
764
1051
|
context: FrameContext,
|
|
765
1052
|
initialHydrationTracker?: InitialHydrationTracker,
|
|
1053
|
+
signal?: AbortSignal,
|
|
766
1054
|
): void {
|
|
767
1055
|
let hydrationMarkers = findHydrationMarkers(container)
|
|
768
1056
|
if (hydrationMarkers.length === 0) return
|
|
@@ -773,7 +1061,7 @@ function scheduleHydrationInContainer(
|
|
|
773
1061
|
for (let marker of hydrationMarkers) {
|
|
774
1062
|
let entry = hydrationData[marker.id]
|
|
775
1063
|
if (!entry) continue
|
|
776
|
-
scheduleHydrationMarker(marker, entry, context, initialHydrationTracker)
|
|
1064
|
+
scheduleHydrationMarker(marker, entry, context, initialHydrationTracker, signal)
|
|
777
1065
|
}
|
|
778
1066
|
}
|
|
779
1067
|
|
|
@@ -782,49 +1070,74 @@ function scheduleHydrationMarker(
|
|
|
782
1070
|
entry: HydrationData,
|
|
783
1071
|
context: FrameContext,
|
|
784
1072
|
initialHydrationTracker?: InitialHydrationTracker,
|
|
1073
|
+
signal?: AbortSignal,
|
|
785
1074
|
): void {
|
|
1075
|
+
if (signal?.aborted || context.lifecycleSignal.aborted) return
|
|
1076
|
+
|
|
786
1077
|
let done = initialHydrationTracker?.track()
|
|
787
1078
|
let key = `${entry.moduleUrl}#${entry.exportName}`
|
|
1079
|
+
let identity: ClientEntryIdentity = {
|
|
1080
|
+
moduleUrl: entry.moduleUrl,
|
|
1081
|
+
exportName: entry.exportName,
|
|
1082
|
+
}
|
|
1083
|
+
let props: Record<string, unknown> | undefined = entry.props
|
|
1084
|
+
let completed = false
|
|
1085
|
+
|
|
1086
|
+
let complete = () => {
|
|
1087
|
+
if (completed) return
|
|
1088
|
+
completed = true
|
|
1089
|
+
props = undefined
|
|
1090
|
+
signal?.removeEventListener('abort', complete)
|
|
1091
|
+
context.lifecycleSignal.removeEventListener('abort', complete)
|
|
1092
|
+
done?.()
|
|
1093
|
+
}
|
|
788
1094
|
|
|
789
|
-
|
|
1095
|
+
signal?.addEventListener('abort', complete, { once: true })
|
|
1096
|
+
context.lifecycleSignal.addEventListener('abort', complete, { once: true })
|
|
1097
|
+
|
|
1098
|
+
let hydrateWithComponent = (component: ElementFunction) => {
|
|
1099
|
+
if (signal?.aborted || context.lifecycleSignal.aborted) return
|
|
790
1100
|
if (!isHydrationMarkerLive(marker, context)) return
|
|
791
|
-
|
|
1101
|
+
if (!props) return
|
|
1102
|
+
let vElement = createElement(component, props)
|
|
792
1103
|
context.pendingClientEntries.set(marker.start, [marker.end, vElement])
|
|
793
|
-
hydrateRegion(vElement, marker.start, marker.end, context)
|
|
1104
|
+
hydrateRegion(vElement, marker.start, marker.end, identity, context, signal)
|
|
794
1105
|
}
|
|
795
1106
|
|
|
796
1107
|
let cached = context.moduleCache.get(key)
|
|
797
1108
|
if (cached) {
|
|
798
1109
|
hydrateWithComponent(cached)
|
|
799
|
-
|
|
1110
|
+
complete()
|
|
800
1111
|
return
|
|
801
1112
|
}
|
|
802
1113
|
|
|
803
|
-
getOrStartModuleLoad(key,
|
|
1114
|
+
getOrStartModuleLoad(key, identity, marker.id, context)
|
|
804
1115
|
.then((component) => {
|
|
805
1116
|
if (component) {
|
|
806
1117
|
hydrateWithComponent(component)
|
|
807
1118
|
}
|
|
808
1119
|
})
|
|
809
1120
|
.finally(() => {
|
|
810
|
-
|
|
1121
|
+
complete()
|
|
811
1122
|
})
|
|
812
1123
|
}
|
|
813
1124
|
|
|
814
1125
|
function getOrStartModuleLoad(
|
|
815
1126
|
key: string,
|
|
816
|
-
|
|
1127
|
+
identity: ClientEntryIdentity,
|
|
817
1128
|
markerId: string,
|
|
818
1129
|
context: FrameContext,
|
|
819
|
-
): Promise<
|
|
1130
|
+
): Promise<ElementFunction | undefined> {
|
|
820
1131
|
let inFlight = context.moduleLoads.get(key)
|
|
821
1132
|
if (inFlight) return inFlight
|
|
822
1133
|
|
|
823
1134
|
let loadPromise = (async () => {
|
|
824
1135
|
try {
|
|
825
|
-
let mod = await context.loadModule(
|
|
826
|
-
if (
|
|
827
|
-
throw new Error(
|
|
1136
|
+
let mod = await context.loadModule(identity.moduleUrl, identity.exportName)
|
|
1137
|
+
if (!isElementFunction(mod)) {
|
|
1138
|
+
throw new Error(
|
|
1139
|
+
`Export "${identity.exportName}" from "${identity.moduleUrl}" is not a function`,
|
|
1140
|
+
)
|
|
828
1141
|
}
|
|
829
1142
|
context.moduleCache.set(key, mod)
|
|
830
1143
|
return mod
|
|
@@ -840,9 +1153,14 @@ function getOrStartModuleLoad(
|
|
|
840
1153
|
return loadPromise
|
|
841
1154
|
}
|
|
842
1155
|
|
|
843
|
-
function createElement(component:
|
|
1156
|
+
function createElement(component: ElementFunction, props: Record<string, unknown>): RemixElement {
|
|
844
1157
|
let revivedProps = reviveSerializedValue(props)
|
|
845
|
-
|
|
1158
|
+
invariant(isRecord(revivedProps), 'Expected revived component props to be an object')
|
|
1159
|
+
return jsx(component, revivedProps)
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function isElementFunction(value: unknown): value is ElementFunction {
|
|
1163
|
+
return typeof value === 'function'
|
|
846
1164
|
}
|
|
847
1165
|
|
|
848
1166
|
function reviveSerializedValue(value: unknown): unknown {
|
|
@@ -853,18 +1171,19 @@ function reviveSerializedValue(value: unknown): unknown {
|
|
|
853
1171
|
return value.map((item) => reviveSerializedValue(item))
|
|
854
1172
|
}
|
|
855
1173
|
|
|
856
|
-
|
|
1174
|
+
if (!isRecord(value)) return value
|
|
1175
|
+
let record = value
|
|
857
1176
|
|
|
858
1177
|
if (record.$rmxFrame === true) {
|
|
859
1178
|
let props = reviveSerializedObject(record.props)
|
|
860
1179
|
let key = reviveSerializedValue(record.key)
|
|
861
|
-
return jsx(Frame
|
|
1180
|
+
return jsx(Frame, props, key)
|
|
862
1181
|
}
|
|
863
1182
|
|
|
864
1183
|
if (record.$rmx === true && typeof record.type === 'string') {
|
|
865
1184
|
let props = reviveSerializedObject(record.props)
|
|
866
1185
|
let key = reviveSerializedValue(record.key)
|
|
867
|
-
return jsx(record.type
|
|
1186
|
+
return jsx(record.type, props, key)
|
|
868
1187
|
}
|
|
869
1188
|
|
|
870
1189
|
let revived: Record<string, unknown> = {}
|
|
@@ -878,22 +1197,49 @@ function reviveSerializedObject(value: unknown): Record<string, unknown> {
|
|
|
878
1197
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
879
1198
|
let revived = reviveSerializedValue(value)
|
|
880
1199
|
if (!revived || typeof revived !== 'object' || Array.isArray(revived)) return {}
|
|
881
|
-
return revived
|
|
1200
|
+
return isRecord(revived) ? revived : {}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1204
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
882
1205
|
}
|
|
883
1206
|
|
|
884
1207
|
function hydrateRegion(
|
|
885
1208
|
vElement: RemixElement,
|
|
886
1209
|
start: Comment,
|
|
887
1210
|
end: Comment,
|
|
1211
|
+
identity: ClientEntryIdentity,
|
|
888
1212
|
context: FrameContext,
|
|
1213
|
+
signal?: AbortSignal,
|
|
889
1214
|
): void {
|
|
1215
|
+
if (signal?.aborted) return
|
|
1216
|
+
|
|
890
1217
|
context.pendingClientEntries.delete(start)
|
|
891
1218
|
|
|
892
1219
|
// The same marker can be discovered by overlapping hydration passes
|
|
893
1220
|
// (for example, document root + nested frame root). Reuse the existing
|
|
894
1221
|
// virtual root instead of redefining the marker property.
|
|
895
|
-
|
|
896
|
-
|
|
1222
|
+
let owner = getClientEntryBoundaryOwner(start)
|
|
1223
|
+
if (owner) {
|
|
1224
|
+
if (!signal) {
|
|
1225
|
+
owner.root.render(vElement)
|
|
1226
|
+
return
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
let frameRuntime = context.frame.$runtime
|
|
1230
|
+
invariant(
|
|
1231
|
+
isFrameRuntime(frameRuntime),
|
|
1232
|
+
'Expected frame runtime while rendering a preserved client entry',
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
let previousServerFrameReload = frameRuntime.serverFrameReload
|
|
1236
|
+
|
|
1237
|
+
frameRuntime.serverFrameReload = { signal }
|
|
1238
|
+
try {
|
|
1239
|
+
owner.root.render(vElement)
|
|
1240
|
+
} finally {
|
|
1241
|
+
frameRuntime.serverFrameReload = previousServerFrameReload
|
|
1242
|
+
}
|
|
897
1243
|
return
|
|
898
1244
|
}
|
|
899
1245
|
|
|
@@ -907,7 +1253,7 @@ function hydrateRegion(
|
|
|
907
1253
|
context.errorTarget.dispatchEvent(createComponentErrorEvent(getComponentError(event)))
|
|
908
1254
|
})
|
|
909
1255
|
|
|
910
|
-
|
|
1256
|
+
setClientEntryBoundaryOwner(start, identity, root)
|
|
911
1257
|
root.render(vElement)
|
|
912
1258
|
}
|
|
913
1259
|
|
|
@@ -948,6 +1294,7 @@ async function createSubFrames(
|
|
|
948
1294
|
resolveFrame: context.resolveFrame,
|
|
949
1295
|
pendingClientEntries: context.pendingClientEntries,
|
|
950
1296
|
scheduler: context.scheduler,
|
|
1297
|
+
styleManager: context.styleManager,
|
|
951
1298
|
data: context.data,
|
|
952
1299
|
moduleCache: context.moduleCache,
|
|
953
1300
|
moduleLoads: context.moduleLoads,
|
|
@@ -958,7 +1305,7 @@ async function createSubFrames(
|
|
|
958
1305
|
}
|
|
959
1306
|
}
|
|
960
1307
|
|
|
961
|
-
i = nodes
|
|
1308
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
962
1309
|
continue
|
|
963
1310
|
}
|
|
964
1311
|
|
|
@@ -996,10 +1343,9 @@ function removeVirtualRoots(nodes: Node[]): void {
|
|
|
996
1343
|
for (let i = 0; i < nodes.length; i++) {
|
|
997
1344
|
let node = nodes[i]
|
|
998
1345
|
|
|
999
|
-
if (
|
|
1000
|
-
node.$rmx.dispose()
|
|
1346
|
+
if (isCommentNode(node) && isHydrationStart(node) && disposeClientEntryBoundary(node)) {
|
|
1001
1347
|
let end = findEndMarker(node, isHydrationStart, isHydrationEnd)
|
|
1002
|
-
i = nodes
|
|
1348
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
1003
1349
|
continue
|
|
1004
1350
|
}
|
|
1005
1351
|
|
|
@@ -1020,7 +1366,7 @@ function disposeSubFrames(nodes: Node[], context: FrameContext): void {
|
|
|
1020
1366
|
subFrame.dispose()
|
|
1021
1367
|
context.frameInstances.delete(node)
|
|
1022
1368
|
}
|
|
1023
|
-
i = nodes
|
|
1369
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
1024
1370
|
continue
|
|
1025
1371
|
}
|
|
1026
1372
|
|
|
@@ -1087,7 +1433,9 @@ export function publishFrameTemplate(id: string, fragment: DocumentFragment): vo
|
|
|
1087
1433
|
}
|
|
1088
1434
|
|
|
1089
1435
|
for (let listener of listeners) {
|
|
1090
|
-
|
|
1436
|
+
let clone = fragment.cloneNode(true)
|
|
1437
|
+
invariant(isDocumentFragmentNode(clone), 'Expected cloned frame template fragment')
|
|
1438
|
+
listener(clone)
|
|
1091
1439
|
}
|
|
1092
1440
|
}
|
|
1093
1441
|
|
|
@@ -1186,12 +1534,22 @@ async function renderFrameStream(
|
|
|
1186
1534
|
stream: ReadableStream<Uint8Array>,
|
|
1187
1535
|
doc: Document,
|
|
1188
1536
|
applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
|
|
1537
|
+
signal?: AbortSignal,
|
|
1189
1538
|
): Promise<void> {
|
|
1190
1539
|
let reader = stream.getReader()
|
|
1191
1540
|
let decoder = new TextDecoder()
|
|
1192
1541
|
let buffer = ''
|
|
1193
1542
|
let html = ''
|
|
1194
1543
|
let appliedOnce = false
|
|
1544
|
+
let abort = () => {
|
|
1545
|
+
void reader.cancel().catch(() => {})
|
|
1546
|
+
}
|
|
1547
|
+
if (signal?.aborted) {
|
|
1548
|
+
await reader.cancel()
|
|
1549
|
+
reader.releaseLock()
|
|
1550
|
+
return
|
|
1551
|
+
}
|
|
1552
|
+
signal?.addEventListener('abort', abort, { once: true })
|
|
1195
1553
|
|
|
1196
1554
|
try {
|
|
1197
1555
|
while (true) {
|
|
@@ -1230,6 +1588,7 @@ async function renderFrameStream(
|
|
|
1230
1588
|
await applyHtml('', 'fragment')
|
|
1231
1589
|
}
|
|
1232
1590
|
} finally {
|
|
1591
|
+
signal?.removeEventListener('abort', abort)
|
|
1233
1592
|
reader.releaseLock()
|
|
1234
1593
|
}
|
|
1235
1594
|
}
|
|
@@ -1312,7 +1671,7 @@ function createFragmentFromString(doc: Document, content: string): DocumentFragm
|
|
|
1312
1671
|
function isRemixNodeFrameContent(content: InternalFrameContent): content is RemixNode {
|
|
1313
1672
|
return !(
|
|
1314
1673
|
content instanceof ReadableStream ||
|
|
1315
|
-
content
|
|
1674
|
+
isDocumentFragmentNode(content) ||
|
|
1316
1675
|
typeof content === 'string'
|
|
1317
1676
|
)
|
|
1318
1677
|
}
|
|
@@ -1350,11 +1709,11 @@ function walkCommentsInNodes(nodes: Node[], cb: (comment: Comment) => void): voi
|
|
|
1350
1709
|
// are discovered and hydrated by the nested frame instance only.
|
|
1351
1710
|
if (isFrameStart(node)) {
|
|
1352
1711
|
let end = findEndMarker(node, isFrameStart, isFrameEnd)
|
|
1353
|
-
i = nodes
|
|
1712
|
+
i = findMarkerRangeEndIndex(nodes, end, i)
|
|
1354
1713
|
continue
|
|
1355
1714
|
}
|
|
1356
1715
|
|
|
1357
|
-
if (node
|
|
1716
|
+
if (isCommentNode(node)) cb(node)
|
|
1358
1717
|
if (node.childNodes && node.childNodes.length > 0) {
|
|
1359
1718
|
walkCommentsInNodes(Array.from(node.childNodes), cb)
|
|
1360
1719
|
}
|
|
@@ -1369,12 +1728,8 @@ function isHydrationEnd(node: Comment): boolean {
|
|
|
1369
1728
|
return node.data.trim() === '/rmx:h'
|
|
1370
1729
|
}
|
|
1371
1730
|
|
|
1372
|
-
function isHydratedVirtualRootMarker(node: Node): node is VirtualRootMarker {
|
|
1373
|
-
return node instanceof Comment && '$rmx' in node
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
1731
|
function isFrameStart(node: Node): node is Comment {
|
|
1377
|
-
return node
|
|
1732
|
+
return isCommentNode(node) && node.data.trim().startsWith('rmx:f:')
|
|
1378
1733
|
}
|
|
1379
1734
|
|
|
1380
1735
|
function isFrameEnd(node: Comment): boolean {
|
|
@@ -1387,6 +1742,11 @@ function getFrameId(start: Comment): string {
|
|
|
1387
1742
|
return trimmed.slice('rmx:f:'.length)
|
|
1388
1743
|
}
|
|
1389
1744
|
|
|
1745
|
+
function findMarkerRangeEndIndex(nodes: Node[], end: Comment, startIndex: number): number {
|
|
1746
|
+
// The snapshot may not contain an end marker moved by a DOM update.
|
|
1747
|
+
return Math.max(startIndex, nodes.indexOf(end))
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1390
1750
|
function findEndMarker(
|
|
1391
1751
|
start: Comment,
|
|
1392
1752
|
isStart: (node: Comment) => boolean,
|
|
@@ -1396,8 +1756,8 @@ function findEndMarker(
|
|
|
1396
1756
|
let depth = 1
|
|
1397
1757
|
|
|
1398
1758
|
while (node) {
|
|
1399
|
-
if (node
|
|
1400
|
-
let comment = node
|
|
1759
|
+
if (isCommentNode(node)) {
|
|
1760
|
+
let comment = node
|
|
1401
1761
|
if (isStart(comment)) depth++
|
|
1402
1762
|
else if (isEnd(comment)) {
|
|
1403
1763
|
depth--
|
|
@@ -1409,3 +1769,19 @@ function findEndMarker(
|
|
|
1409
1769
|
|
|
1410
1770
|
throw new Error('End marker not found')
|
|
1411
1771
|
}
|
|
1772
|
+
|
|
1773
|
+
function isCommentNode(node: Node | null | undefined): node is Comment {
|
|
1774
|
+
return node?.nodeType === Node.COMMENT_NODE
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function isDocumentNode(node: Node): node is Document {
|
|
1778
|
+
return node.nodeType === Node.DOCUMENT_NODE
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function isDocumentFragmentNode(value: unknown): value is DocumentFragment {
|
|
1782
|
+
return (
|
|
1783
|
+
typeof value === 'object' &&
|
|
1784
|
+
value !== null &&
|
|
1785
|
+
Reflect.get(value, 'nodeType') === Node.DOCUMENT_FRAGMENT_NODE
|
|
1786
|
+
)
|
|
1787
|
+
}
|