@remix-run/ui 0.1.0 → 0.1.2
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 +24 -3
- package/dist/animation/animate-layout-mixin.js +66 -66
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +3 -3
- package/dist/components/accordion/accordion.js +0 -2
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/demos/card.demo.d.ts +6 -0
- package/dist/components/accordion/demos/card.demo.js +58 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -0
- package/dist/components/accordion/demos/multiple.demo.d.ts +6 -0
- package/dist/components/accordion/demos/multiple.demo.js +19 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -0
- package/dist/components/accordion/demos/overview.demo.d.ts +6 -0
- package/dist/components/accordion/demos/overview.demo.js +19 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.js +0 -2
- package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -1
- package/dist/components/breadcrumbs/demos/basic.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/demos/separator.demo.d.ts +5 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js +14 -0
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +1 -0
- package/dist/components/button/button.d.ts +82 -0
- package/dist/components/button/button.js +60 -2
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/demos/aliases.demo.d.ts +6 -0
- package/dist/components/button/demos/aliases.demo.js +18 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -0
- package/dist/components/button/demos/basic.demo.d.ts +6 -0
- package/dist/components/button/demos/basic.demo.js +19 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -0
- package/dist/components/button/demos/states.demo.d.ts +6 -0
- package/dist/components/button/demos/states.demo.js +29 -0
- package/dist/components/button/demos/states.demo.js.map +1 -0
- package/dist/components/combobox/combobox.d.ts +3 -3
- package/dist/components/combobox/combobox.js +0 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/combobox/demos/overview.demo.d.ts +6 -0
- package/dist/components/combobox/demos/overview.demo.js +153 -0
- package/dist/components/combobox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/demos/overview.demo.d.ts +6 -0
- package/dist/components/listbox/demos/overview.demo.js +64 -0
- package/dist/components/listbox/demos/overview.demo.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +1 -6
- package/dist/components/listbox/listbox.js +21 -33
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/menu/demos/bubbling.demo.d.ts +5 -0
- package/dist/components/menu/demos/bubbling.demo.js +15 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +6 -0
- package/dist/components/menu/demos/overview.demo.js +32 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -0
- package/dist/components/menu/menu.d.ts +5 -14
- package/dist/components/menu/menu.js +23 -38
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/demos/overview.demo.d.ts +6 -0
- package/dist/components/popover/demos/overview.demo.js +64 -0
- package/dist/components/popover/demos/overview.demo.js.map +1 -0
- package/dist/components/select/demos/deconstructed.demo.d.ts +6 -0
- package/dist/components/select/demos/deconstructed.demo.js +53 -0
- package/dist/components/select/demos/deconstructed.demo.js.map +1 -0
- package/dist/components/select/demos/overview.demo.d.ts +5 -0
- package/dist/components/select/demos/overview.demo.js +27 -0
- package/dist/components/select/demos/overview.demo.js.map +1 -0
- package/dist/components/select/select.d.ts +3 -3
- package/dist/components/select/select.js +17 -26
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +3 -3
- package/dist/components/tabs/tabs.js +0 -2
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/component.d.ts +8 -5
- package/dist/runtime/component.js +84 -80
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +12 -0
- package/dist/runtime/core/attributes.js +85 -0
- package/dist/runtime/core/attributes.js.map +1 -0
- package/dist/runtime/core/children.d.ts +7 -0
- package/dist/runtime/core/children.js +30 -0
- package/dist/runtime/core/children.js.map +1 -0
- package/dist/runtime/core/props.d.ts +9 -0
- package/dist/runtime/core/props.js +169 -0
- package/dist/runtime/core/props.js.map +1 -0
- package/dist/runtime/core/vnode.d.ts +3 -0
- package/dist/runtime/core/vnode.js +39 -0
- package/dist/runtime/core/vnode.js.map +1 -0
- package/dist/runtime/create-element.js +2 -1
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +47 -14
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.d.ts +1 -2
- package/dist/runtime/diff-props.js +2 -181
- package/dist/runtime/diff-props.js.map +1 -1
- package/dist/runtime/dom.d.ts +4 -0
- package/dist/runtime/frame.d.ts +23 -2
- package/dist/runtime/frame.js +51 -52
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.js +2 -27
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/mixins/mixin.js +9 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.d.ts +15 -4
- package/dist/runtime/mixins/on-mixin.js +9 -2
- package/dist/runtime/mixins/on-mixin.js.map +1 -1
- package/dist/runtime/navigation.d.ts +6 -0
- package/dist/runtime/navigation.js +16 -3
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.js +444 -181
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/run.d.ts +17 -2
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +10 -0
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/stream-protocol.d.ts +8 -0
- package/dist/runtime/stream-protocol.js +20 -0
- package/dist/runtime/stream-protocol.js.map +1 -0
- package/dist/runtime/to-vnode.js +17 -18
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +5 -4
- package/dist/runtime/vnode.js +2 -3
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.js +80 -106
- package/dist/server/stream.js.map +1 -1
- package/dist/style/layers.d.ts +2 -0
- package/dist/style/layers.js +3 -0
- package/dist/style/layers.js.map +1 -0
- package/dist/style/stylesheet.d.ts +3 -1
- package/dist/style/stylesheet.js +69 -8
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/theme/glyph-contract.d.ts +1 -1
- package/dist/theme/runtime.js +4 -1
- package/dist/theme/runtime.js.map +1 -1
- package/package.json +6 -7
- package/src/animation/README.md +314 -0
- package/src/animation/animate-layout-mixin.ts +79 -71
- package/src/components/accordion/README.md +166 -0
- package/src/components/accordion/accordion.tsx +8 -10
- package/src/components/accordion/demos/card.demo.tsx +113 -0
- package/src/components/accordion/demos/multiple.demo.tsx +56 -0
- package/src/components/accordion/demos/overview.demo.tsx +56 -0
- package/src/components/anchor/README.md +153 -0
- package/src/components/breadcrumbs/README.md +55 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +0 -2
- package/src/components/breadcrumbs/demos/basic.demo.tsx +17 -0
- package/src/components/breadcrumbs/demos/separator.demo.tsx +18 -0
- package/src/components/button/README.md +4 -4
- package/src/components/button/button.tsx +88 -2
- package/src/components/button/demos/aliases.demo.tsx +27 -0
- package/src/components/button/demos/basic.demo.tsx +30 -0
- package/src/components/button/demos/states.demo.tsx +52 -0
- package/src/components/combobox/combobox.tsx +8 -10
- package/src/components/combobox/demos/overview.demo.tsx +199 -0
- package/src/components/glyph/README.md +72 -0
- package/src/components/listbox/README.md +115 -0
- package/src/components/listbox/demos/overview.demo.tsx +101 -0
- package/src/components/listbox/listbox.ts +23 -32
- package/src/components/menu/README.md +96 -0
- package/src/components/menu/demos/bubbling.demo.tsx +34 -0
- package/src/components/menu/demos/overview.demo.tsx +76 -0
- package/src/components/menu/menu.tsx +35 -41
- package/src/components/popover/README.md +3 -3
- package/src/components/popover/demos/overview.demo.tsx +143 -0
- package/src/components/select/README.md +107 -0
- package/src/components/select/demos/deconstructed.demo.tsx +95 -0
- package/src/components/select/demos/overview.demo.tsx +51 -0
- package/src/components/select/select.tsx +28 -28
- package/src/components/tabs/tabs.tsx +10 -5
- package/src/index.ts +1 -1
- package/src/runtime/component.ts +94 -82
- package/src/runtime/core/attributes.ts +97 -0
- package/src/runtime/core/children.ts +42 -0
- package/src/runtime/core/props.ts +185 -0
- package/src/runtime/core/vnode.ts +48 -0
- package/src/runtime/create-element.ts +2 -1
- package/src/runtime/diff-dom.ts +59 -12
- package/src/runtime/diff-props.ts +2 -187
- package/src/runtime/dom.ts +4 -0
- package/src/runtime/frame.ts +84 -61
- package/src/runtime/jsx.ts +2 -30
- package/src/runtime/mixins/mixin.ts +9 -4
- package/src/runtime/mixins/on-mixin.ts +20 -12
- package/src/runtime/navigation.ts +24 -3
- package/src/runtime/reconcile.ts +552 -186
- package/src/runtime/run.ts +17 -1
- package/src/runtime/scheduler.ts +7 -0
- package/src/runtime/stream-protocol.ts +27 -0
- package/src/runtime/to-vnode.ts +15 -17
- package/src/runtime/vnode.ts +6 -9
- package/src/server/README.md +88 -0
- package/src/server/stream.ts +105 -117
- package/src/style/layers.ts +2 -0
- package/src/style/stylesheet.ts +75 -10
- package/src/test/README.md +107 -0
- package/src/test/utils.ts +3 -1
- package/src/theme/README.md +103 -0
- package/src/theme/runtime.ts +4 -1
- package/src/utils/scroll-lock/README.md +33 -0
- package/src/test/setup.ts +0 -7
package/src/runtime/diff-dom.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { invariant } from './invariant.ts'
|
|
2
2
|
import type { FrameContext } from './frame.ts'
|
|
3
3
|
|
|
4
|
+
type HydratedVirtualRootStartMarker = Comment & {
|
|
5
|
+
$rmx: {
|
|
6
|
+
dispose(): void
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
|
|
5
11
|
let parent = curr[0]?.parentNode ?? context.regionParent ?? null
|
|
6
12
|
invariant(parent, 'Parent node not found')
|
|
@@ -23,8 +29,7 @@ export function diffNodes(curr: Node[], next: Node[], context: FrameContext) {
|
|
|
23
29
|
parent.appendChild(n)
|
|
24
30
|
}
|
|
25
31
|
} else if (c && !n) {
|
|
26
|
-
|
|
27
|
-
parent.removeChild(c)
|
|
32
|
+
removeNode(c, parent, context)
|
|
28
33
|
} else if (c && n) {
|
|
29
34
|
// Skip hydrated client-entry boundary ranges; hydration pass re-renders
|
|
30
35
|
// roots with new props from incoming payload
|
|
@@ -71,7 +76,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
71
76
|
// Comment -> Comment
|
|
72
77
|
if (isCommentNode(current) && isCommentNode(next)) {
|
|
73
78
|
let newData = next.data
|
|
74
|
-
if (current.data !== newData)
|
|
79
|
+
if (current.data !== newData) {
|
|
80
|
+
if (isFrameStartMarker(current)) disposeFrameStartMarker(current, context)
|
|
81
|
+
current.data = newData
|
|
82
|
+
}
|
|
75
83
|
return
|
|
76
84
|
}
|
|
77
85
|
|
|
@@ -80,7 +88,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
80
88
|
// Different tags: replace
|
|
81
89
|
if (current.tagName !== next.tagName) {
|
|
82
90
|
let parent = current.parentNode
|
|
83
|
-
if (parent)
|
|
91
|
+
if (parent) {
|
|
92
|
+
parent.insertBefore(next, current)
|
|
93
|
+
removeNode(current, parent, context)
|
|
94
|
+
}
|
|
84
95
|
return
|
|
85
96
|
}
|
|
86
97
|
|
|
@@ -93,7 +104,10 @@ function diffNode(current: Node, next: Node, context: FrameContext): ChildNode |
|
|
|
93
104
|
|
|
94
105
|
// Type mismatch: replace
|
|
95
106
|
let parent = current.parentNode
|
|
96
|
-
if (parent)
|
|
107
|
+
if (parent) {
|
|
108
|
+
parent.insertBefore(next, current)
|
|
109
|
+
removeNode(current, parent, context)
|
|
110
|
+
}
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
function diffElementAttributes(current: Element, next: Element): void {
|
|
@@ -315,8 +329,7 @@ function diffElementChildren(current: Element, next: Element, context: FrameCont
|
|
|
315
329
|
for (let i = 0; i < currentChildren.length; i++) {
|
|
316
330
|
if (!used[i]) {
|
|
317
331
|
let nodeToRemove = currentChildren[i]
|
|
318
|
-
|
|
319
|
-
current.removeChild(currentChildren[i])
|
|
332
|
+
removeNode(nodeToRemove, current, context)
|
|
320
333
|
}
|
|
321
334
|
}
|
|
322
335
|
}
|
|
@@ -375,6 +388,32 @@ function isFrameStartMarker(node: Node): node is Comment {
|
|
|
375
388
|
return node instanceof Comment && node.data.trim().startsWith('rmx:f:')
|
|
376
389
|
}
|
|
377
390
|
|
|
391
|
+
function removeNode(node: Node, parent: ParentNode, context: FrameContext): void {
|
|
392
|
+
disposeRemovedVirtualRoots(node)
|
|
393
|
+
disposeRemovedSubFrames(node, context)
|
|
394
|
+
|
|
395
|
+
if (node.parentNode === parent) {
|
|
396
|
+
parent.removeChild(node)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function disposeRemovedVirtualRoots(node: Node): void {
|
|
401
|
+
let stack: Node[] = [node]
|
|
402
|
+
while (stack.length > 0) {
|
|
403
|
+
let next = stack.pop()
|
|
404
|
+
if (!next) continue
|
|
405
|
+
|
|
406
|
+
if (isHydratedVirtualRootStartMarker(next)) {
|
|
407
|
+
next.$rmx.dispose()
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (let child of Array.from(next.childNodes)) {
|
|
412
|
+
stack.push(child)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
378
417
|
function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
379
418
|
let stack: Node[] = [node]
|
|
380
419
|
while (stack.length > 0) {
|
|
@@ -382,11 +421,7 @@ function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
|
382
421
|
if (!next) continue
|
|
383
422
|
|
|
384
423
|
if (isFrameStartMarker(next)) {
|
|
385
|
-
|
|
386
|
-
if (subFrame) {
|
|
387
|
-
subFrame.dispose()
|
|
388
|
-
context.frameInstances.delete(next)
|
|
389
|
-
}
|
|
424
|
+
disposeFrameStartMarker(next, context)
|
|
390
425
|
}
|
|
391
426
|
|
|
392
427
|
for (let child of Array.from(next.childNodes)) {
|
|
@@ -395,10 +430,22 @@ function disposeRemovedSubFrames(node: Node, context: FrameContext): void {
|
|
|
395
430
|
}
|
|
396
431
|
}
|
|
397
432
|
|
|
433
|
+
function disposeFrameStartMarker(marker: Comment, context: FrameContext): void {
|
|
434
|
+
let subFrame = context.frameInstances.get(marker)
|
|
435
|
+
if (subFrame) {
|
|
436
|
+
subFrame.dispose()
|
|
437
|
+
context.frameInstances.delete(marker)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
398
441
|
function isVirtualRootStartMarker(node: Node): node is Comment {
|
|
399
442
|
return isCommentNode(node) && node.data.trim().startsWith('rmx:h:')
|
|
400
443
|
}
|
|
401
444
|
|
|
445
|
+
function isHydratedVirtualRootStartMarker(node: Node): node is HydratedVirtualRootStartMarker {
|
|
446
|
+
return isVirtualRootStartMarker(node) && '$rmx' in node
|
|
447
|
+
}
|
|
448
|
+
|
|
402
449
|
function isVirtualRootEndMarker(node: Node): node is Comment {
|
|
403
450
|
return isCommentNode(node) && node.data.trim() === '/rmx:h'
|
|
404
451
|
}
|
|
@@ -1,200 +1,15 @@
|
|
|
1
1
|
import { invariant } from './invariant.ts'
|
|
2
|
-
import { createStyleManager
|
|
2
|
+
import { createStyleManager } from '../style/index.ts'
|
|
3
3
|
import type { StyleManager } from '../style/index.ts'
|
|
4
|
-
import type { ElementProps } from './jsx.ts'
|
|
5
|
-
import { normalizeSvgAttribute } from './svg-attributes.ts'
|
|
6
|
-
|
|
7
|
-
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
8
4
|
|
|
9
5
|
let globalStyleManager =
|
|
10
6
|
typeof window !== 'undefined' ? createStyleManager() : (null as unknown as StyleManager)
|
|
11
7
|
|
|
12
8
|
export { type StyleManager }
|
|
9
|
+
export { patchHostProps as diffHostProps } from './core/props.ts'
|
|
13
10
|
|
|
14
11
|
export let defaultStyleManager: StyleManager = globalStyleManager
|
|
15
12
|
|
|
16
|
-
// Preact excludes certain attributes from the property path due to browser quirks
|
|
17
|
-
const ATTRIBUTE_FALLBACK_NAMES = new Set([
|
|
18
|
-
'width',
|
|
19
|
-
'height',
|
|
20
|
-
'href',
|
|
21
|
-
'list',
|
|
22
|
-
'form',
|
|
23
|
-
'tabIndex',
|
|
24
|
-
'download',
|
|
25
|
-
'rowSpan',
|
|
26
|
-
'colSpan',
|
|
27
|
-
'role',
|
|
28
|
-
'popover',
|
|
29
|
-
])
|
|
30
|
-
|
|
31
|
-
// Determine if we should use the property path for a given name.
|
|
32
|
-
// Also acts as a type guard to allow bracket assignment without casts.
|
|
33
|
-
function canUseProperty(
|
|
34
|
-
dom: Element,
|
|
35
|
-
name: string,
|
|
36
|
-
isSvg: boolean,
|
|
37
|
-
): dom is Element & Record<string, unknown> {
|
|
38
|
-
if (isSvg) return false
|
|
39
|
-
if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
|
|
40
|
-
return name in dom
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isFrameworkProp(name: string): boolean {
|
|
44
|
-
return (
|
|
45
|
-
name === 'children' ||
|
|
46
|
-
name === 'mix' ||
|
|
47
|
-
name === 'key' ||
|
|
48
|
-
name === 'animate' ||
|
|
49
|
-
name === 'innerHTML'
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// TODO: would rather actually diff el.style object directly instead of writing
|
|
54
|
-
// to the style attribute
|
|
55
|
-
function serializeStyleObject(style: Record<string, unknown>): string {
|
|
56
|
-
let parts: string[] = []
|
|
57
|
-
for (let [key, value] of Object.entries(style)) {
|
|
58
|
-
if (value == null) continue
|
|
59
|
-
if (typeof value === 'boolean') continue
|
|
60
|
-
if (typeof value === 'number' && !Number.isFinite(value)) continue
|
|
61
|
-
|
|
62
|
-
let cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
|
|
63
|
-
|
|
64
|
-
let cssValue = Array.isArray(value)
|
|
65
|
-
? (value as unknown[]).join(', ')
|
|
66
|
-
: normalizeCssValue(key, value)
|
|
67
|
-
|
|
68
|
-
parts.push(`${cssKey}: ${cssValue};`)
|
|
69
|
-
}
|
|
70
|
-
return parts.join(' ')
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function normalizePropName(name: string, isSvg: boolean): { ns?: string; attr: string } {
|
|
74
|
-
// aria-/data- pass through
|
|
75
|
-
if (name.startsWith('aria-') || name.startsWith('data-')) return { attr: name }
|
|
76
|
-
|
|
77
|
-
// DOM property -> HTML mappings
|
|
78
|
-
if (name === 'className') return { attr: 'class' }
|
|
79
|
-
if (!isSvg) {
|
|
80
|
-
if (name === 'htmlFor') return { attr: 'for' }
|
|
81
|
-
if (name === 'tabIndex') return { attr: 'tabindex' }
|
|
82
|
-
if (name === 'acceptCharset') return { attr: 'accept-charset' }
|
|
83
|
-
if (name === 'httpEquiv') return { attr: 'http-equiv' }
|
|
84
|
-
return { attr: name.toLowerCase() }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return normalizeSvgAttribute(name)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function toLocalName(attrName: string): string {
|
|
91
|
-
let separatorIndex = attrName.indexOf(':')
|
|
92
|
-
if (separatorIndex === -1) return attrName
|
|
93
|
-
return attrName.slice(separatorIndex + 1)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function clearRuntimePropertyOnRemoval(dom: Element & Record<string, unknown>, name: string): void {
|
|
97
|
-
try {
|
|
98
|
-
if (name === 'value' || name === 'defaultValue') {
|
|
99
|
-
dom[name] = ''
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
if (name === 'checked' || name === 'defaultChecked' || name === 'selected') {
|
|
103
|
-
dom[name] = false
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
if (name === 'selectedIndex') {
|
|
107
|
-
dom[name] = -1
|
|
108
|
-
}
|
|
109
|
-
} catch {}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getMergedClassName(props: ElementProps): string | undefined {
|
|
113
|
-
let classAttr = typeof props.class === 'string' ? props.class : ''
|
|
114
|
-
let className = typeof props.className === 'string' ? props.className : ''
|
|
115
|
-
let mergedClassName =
|
|
116
|
-
classAttr && className ? `${classAttr} ${className}` : classAttr || className
|
|
117
|
-
return mergedClassName || undefined
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function diffHostProps(curr: ElementProps, next: ElementProps, dom: Element) {
|
|
121
|
-
let isSvg = dom.namespaceURI === SVG_NS
|
|
122
|
-
let currClassName = getMergedClassName(curr)
|
|
123
|
-
let nextClassName = getMergedClassName(next)
|
|
124
|
-
|
|
125
|
-
if (currClassName !== nextClassName) {
|
|
126
|
-
if (nextClassName) {
|
|
127
|
-
dom.setAttribute('class', nextClassName)
|
|
128
|
-
} else {
|
|
129
|
-
dom.removeAttribute('class')
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Removals
|
|
134
|
-
for (let name in curr) {
|
|
135
|
-
if (isFrameworkProp(name)) continue
|
|
136
|
-
if (name === 'class' || name === 'className') continue
|
|
137
|
-
if (!(name in next) || next[name] == null) {
|
|
138
|
-
// Clear runtime state for form-like props where removing the attribute is not enough.
|
|
139
|
-
if (canUseProperty(dom, name, isSvg)) {
|
|
140
|
-
clearRuntimePropertyOnRemoval(dom, name)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
let { ns, attr } = normalizePropName(name, isSvg)
|
|
144
|
-
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
145
|
-
else dom.removeAttribute(attr)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Additions/updates
|
|
150
|
-
for (let name in next) {
|
|
151
|
-
if (isFrameworkProp(name)) continue
|
|
152
|
-
if (name === 'class' || name === 'className') continue
|
|
153
|
-
let nextValue = next[name]
|
|
154
|
-
if (nextValue == null) continue
|
|
155
|
-
let prevValue = curr[name]
|
|
156
|
-
if (prevValue !== nextValue) {
|
|
157
|
-
let { ns, attr } = normalizePropName(name, isSvg)
|
|
158
|
-
|
|
159
|
-
// Object style: serialize to attribute for now
|
|
160
|
-
if (
|
|
161
|
-
attr === 'style' &&
|
|
162
|
-
typeof nextValue === 'object' &&
|
|
163
|
-
nextValue &&
|
|
164
|
-
!Array.isArray(nextValue)
|
|
165
|
-
) {
|
|
166
|
-
dom.setAttribute('style', serializeStyleObject(nextValue))
|
|
167
|
-
continue
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Prefer property assignment when possible (HTML only, not SVG)
|
|
171
|
-
if (canUseProperty(dom, name, isSvg)) {
|
|
172
|
-
try {
|
|
173
|
-
dom[name] = nextValue == null ? '' : nextValue
|
|
174
|
-
continue
|
|
175
|
-
} catch {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Attribute path
|
|
179
|
-
if (typeof nextValue === 'function') {
|
|
180
|
-
// Never serialize functions as attribute values
|
|
181
|
-
continue
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
|
|
185
|
-
if (nextValue != null && (nextValue !== false || isAriaOrData)) {
|
|
186
|
-
// Special-case popover: true => presence only
|
|
187
|
-
let attrValue = name === 'popover' && nextValue === true ? '' : String(nextValue)
|
|
188
|
-
if (ns) dom.setAttributeNS(ns, attr, attrValue)
|
|
189
|
-
else dom.setAttribute(attr, attrValue)
|
|
190
|
-
} else {
|
|
191
|
-
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
192
|
-
else dom.removeAttribute(attr)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
13
|
/**
|
|
199
14
|
* Reset the global style state. For testing only - not exported from index.ts.
|
|
200
15
|
*/
|
package/src/runtime/dom.ts
CHANGED
|
@@ -3418,6 +3418,10 @@ export interface TemplateHTMLProps<eventTarget extends EventTarget = HTMLTemplat
|
|
|
3418
3418
|
*/
|
|
3419
3419
|
export interface TextareaHTMLProps<eventTarget extends EventTarget = HTMLTextAreaElement>
|
|
3420
3420
|
extends HTMLProps<eventTarget> {
|
|
3421
|
+
/** Textarea content comes from `value` or `defaultValue`. */
|
|
3422
|
+
children?: never
|
|
3423
|
+
/** Textarea content comes from `value` or `defaultValue`. */
|
|
3424
|
+
innerHTML?: never
|
|
3421
3425
|
/** The `autocomplete` HTML attribute. */
|
|
3422
3426
|
autocomplete?: Trackable<string | undefined>
|
|
3423
3427
|
/** The `autoComplete` HTML attribute. */
|
package/src/runtime/frame.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Scheduler, VirtualRoot } from './vdom.ts'
|
|
|
8
8
|
import { createRangeRoot, createRoot } from './vdom.ts'
|
|
9
9
|
import { diffNodes } from './diff-dom.ts'
|
|
10
10
|
import { createStyleManager, type StyleManager } from '../style/index.ts'
|
|
11
|
+
import { findFlushMarker, type FlushKind } from './stream-protocol.ts'
|
|
11
12
|
|
|
12
13
|
type FrameRoot = [Comment, Comment] | Element | Document | DocumentFragment
|
|
13
14
|
|
|
@@ -39,12 +40,31 @@ type FrameMarkerData = FrameData & {
|
|
|
39
40
|
type PendingClientEntries = Map<Comment, [Comment, RemixElement]>
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
* Loads a client
|
|
43
|
+
* Loads a named client-entry export for hydration.
|
|
44
|
+
*
|
|
45
|
+
* @param moduleUrl Browser-resolvable URL for the module that contains the client entry.
|
|
46
|
+
* @param exportName Named export to read from the loaded module.
|
|
47
|
+
* @returns The exported component function, or a promise for it.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* run({
|
|
52
|
+
* async loadModule(moduleUrl, exportName) {
|
|
53
|
+
* let mod = await import(moduleUrl)
|
|
54
|
+
* return mod[exportName]
|
|
55
|
+
* },
|
|
56
|
+
* })
|
|
57
|
+
* ```
|
|
43
58
|
*/
|
|
44
59
|
export type LoadModule = (moduleUrl: string, exportName: string) => Promise<Function> | Function
|
|
45
60
|
|
|
46
61
|
/**
|
|
47
|
-
* Resolves
|
|
62
|
+
* Resolves content for a browser-loaded frame.
|
|
63
|
+
*
|
|
64
|
+
* @param src Source string from the `<Frame src>` prop.
|
|
65
|
+
* @param signal Abort signal for the active frame load or reload.
|
|
66
|
+
* @param target Optional name of the frame being reloaded.
|
|
67
|
+
* @returns HTML, a stream of HTML bytes, or Remix node content to render into the frame.
|
|
48
68
|
*/
|
|
49
69
|
export type ResolveFrame = (
|
|
50
70
|
src: string,
|
|
@@ -58,6 +78,11 @@ type FrameTemplateListener = (fragment: DocumentFragment) => void
|
|
|
58
78
|
|
|
59
79
|
const bufferedFrameTemplates = new Map<string, DocumentFragment[]>()
|
|
60
80
|
const frameTemplateListeners = new Map<string, Set<FrameTemplateListener>>()
|
|
81
|
+
const DOCTYPE_PATTERN = /<!doctype(?:\s[^>]*)?>/gi
|
|
82
|
+
|
|
83
|
+
function stripDoctypeMarkup(html: string): string {
|
|
84
|
+
return html.replace(DOCTYPE_PATTERN, '')
|
|
85
|
+
}
|
|
61
86
|
|
|
62
87
|
function syncElementAttributes(target: Element, source: Element) {
|
|
63
88
|
for (let attribute of Array.from(target.attributes)) {
|
|
@@ -133,6 +158,7 @@ export type Frame = {
|
|
|
133
158
|
}
|
|
134
159
|
|
|
135
160
|
type RenderOptions = {
|
|
161
|
+
flushKind?: FlushKind
|
|
136
162
|
initialHydrationTracker?: InitialHydrationTracker
|
|
137
163
|
signal?: AbortSignal
|
|
138
164
|
}
|
|
@@ -206,9 +232,9 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
206
232
|
if (options?.signal?.aborted) return
|
|
207
233
|
|
|
208
234
|
if (content instanceof ReadableStream) {
|
|
209
|
-
await renderFrameStream(content, container.doc, async (html) => {
|
|
235
|
+
await renderFrameStream(content, container.doc, async (html, flushKind) => {
|
|
210
236
|
if (options?.signal?.aborted) return
|
|
211
|
-
await render(html, options)
|
|
237
|
+
await render(html, { ...options, flushKind })
|
|
212
238
|
})
|
|
213
239
|
return
|
|
214
240
|
}
|
|
@@ -232,37 +258,42 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
232
258
|
contentRoot = undefined
|
|
233
259
|
}
|
|
234
260
|
|
|
261
|
+
if (typeof content === 'string') {
|
|
262
|
+
let flushed = await consumeFlushBatches(content, async (html, flushKind) => {
|
|
263
|
+
await render(html, { ...options, flushKind })
|
|
264
|
+
})
|
|
265
|
+
if (flushed.applied) {
|
|
266
|
+
if (flushed.remainder !== '') {
|
|
267
|
+
await render(flushed.remainder, { ...options, flushKind: 'fragment' })
|
|
268
|
+
}
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let htmlContent = typeof content === 'string' ? stripDoctypeMarkup(content) : undefined
|
|
274
|
+
|
|
235
275
|
let isFullDocumentReload =
|
|
236
276
|
container.root instanceof Document &&
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (isFullDocumentReload &&
|
|
241
|
-
|
|
242
|
-
// before diffing fresh HTML, otherwise stale component instances can survive
|
|
243
|
-
// on detached DOM nodes.
|
|
244
|
-
let previousBodyNodes = Array.from(container.doc.body.childNodes)
|
|
245
|
-
removeVirtualRoots(previousBodyNodes)
|
|
246
|
-
disposeSubFrames(previousBodyNodes, context)
|
|
247
|
-
let parsed = new DOMParser().parseFromString(content, 'text/html')
|
|
277
|
+
htmlContent !== undefined &&
|
|
278
|
+
options?.flushKind === 'document'
|
|
279
|
+
|
|
280
|
+
if (isFullDocumentReload && htmlContent !== undefined) {
|
|
281
|
+
let parsed = new DOMParser().parseFromString(htmlContent, 'text/html')
|
|
248
282
|
mergeRmxDataFromDocument(context.data, parsed)
|
|
249
|
-
context.styleManager.
|
|
250
|
-
context.styleManager.adoptServerStyles(
|
|
283
|
+
context.styleManager.replaceServerStyles(
|
|
251
284
|
collectFrameServerStyleTags(createElementContainer(parsed)),
|
|
252
285
|
)
|
|
253
286
|
|
|
254
287
|
syncElementAttributes(container.doc.documentElement, parsed.documentElement)
|
|
255
|
-
syncElementAttributes(container.doc.head, parsed.head)
|
|
256
|
-
syncElementAttributes(container.doc.body, parsed.body)
|
|
257
288
|
|
|
258
|
-
diffNodes(
|
|
289
|
+
diffNodes([container.doc.head], [parsed.head], {
|
|
259
290
|
...context,
|
|
260
|
-
regionParent: container.doc.
|
|
291
|
+
regionParent: container.doc.documentElement,
|
|
261
292
|
regionTailRef: null,
|
|
262
293
|
})
|
|
263
|
-
diffNodes(
|
|
294
|
+
diffNodes([container.doc.body], [parsed.body], {
|
|
264
295
|
...context,
|
|
265
|
-
regionParent: container.doc.
|
|
296
|
+
regionParent: container.doc.documentElement,
|
|
266
297
|
regionTailRef: null,
|
|
267
298
|
})
|
|
268
299
|
|
|
@@ -274,9 +305,8 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
274
305
|
}
|
|
275
306
|
|
|
276
307
|
let fragment =
|
|
277
|
-
|
|
278
|
-
context.styleManager.
|
|
279
|
-
context.styleManager.adoptServerStyles(
|
|
308
|
+
htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content
|
|
309
|
+
context.styleManager.replaceServerStyles(
|
|
280
310
|
collectFrameServerStyleTags(createElementContainer(fragment)),
|
|
281
311
|
)
|
|
282
312
|
removeEmptyHeads(fragment)
|
|
@@ -337,8 +367,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
337
367
|
async function hydrateInitial(): Promise<void> {
|
|
338
368
|
let initialHydrationTracker = createInitialHydrationTracker()
|
|
339
369
|
|
|
340
|
-
context.styleManager.
|
|
341
|
-
context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container))
|
|
370
|
+
context.styleManager.replaceServerStyles(collectFrameServerStyleTags(container))
|
|
342
371
|
createSubFrames(container.childNodes, context)
|
|
343
372
|
scheduleHydrationInContainer(container, context, initialHydrationTracker)
|
|
344
373
|
|
|
@@ -979,13 +1008,12 @@ function extractTemplatesFromBuffer(
|
|
|
979
1008
|
async function renderFrameStream(
|
|
980
1009
|
stream: ReadableStream<Uint8Array>,
|
|
981
1010
|
doc: Document,
|
|
982
|
-
applyHtml: (html: string) => Promise<void>,
|
|
1011
|
+
applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
|
|
983
1012
|
): Promise<void> {
|
|
984
1013
|
let reader = stream.getReader()
|
|
985
1014
|
let decoder = new TextDecoder()
|
|
986
1015
|
let buffer = ''
|
|
987
1016
|
let html = ''
|
|
988
|
-
let appliedLength = 0
|
|
989
1017
|
let appliedOnce = false
|
|
990
1018
|
|
|
991
1019
|
try {
|
|
@@ -999,13 +1027,9 @@ async function renderFrameStream(
|
|
|
999
1027
|
|
|
1000
1028
|
if (parsed.html !== '') {
|
|
1001
1029
|
html += parsed.html
|
|
1002
|
-
let
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
await applyHtml(html)
|
|
1007
|
-
appliedLength = html.length
|
|
1008
|
-
appliedOnce = true
|
|
1030
|
+
let flushed = await consumeFlushBatches(html, applyHtml)
|
|
1031
|
+
appliedOnce = flushed.applied || appliedOnce
|
|
1032
|
+
html = flushed.remainder
|
|
1009
1033
|
}
|
|
1010
1034
|
}
|
|
1011
1035
|
|
|
@@ -1018,21 +1042,40 @@ async function renderFrameStream(
|
|
|
1018
1042
|
buffer = ''
|
|
1019
1043
|
}
|
|
1020
1044
|
|
|
1021
|
-
if (html !== ''
|
|
1022
|
-
await applyHtml(html)
|
|
1045
|
+
if (html !== '') {
|
|
1046
|
+
await applyHtml(html, 'fragment')
|
|
1023
1047
|
appliedOnce = true
|
|
1024
1048
|
}
|
|
1025
1049
|
|
|
1026
1050
|
// A frame stream can legitimately resolve to empty content. Ensure the
|
|
1027
1051
|
// existing frame region is cleared instead of treated as a no-op.
|
|
1028
1052
|
if (html === '' && !appliedOnce) {
|
|
1029
|
-
await applyHtml('')
|
|
1053
|
+
await applyHtml('', 'fragment')
|
|
1030
1054
|
}
|
|
1031
1055
|
} finally {
|
|
1032
1056
|
reader.releaseLock()
|
|
1033
1057
|
}
|
|
1034
1058
|
}
|
|
1035
1059
|
|
|
1060
|
+
async function consumeFlushBatches(
|
|
1061
|
+
html: string,
|
|
1062
|
+
applyHtml: (html: string, flushKind: FlushKind) => Promise<void>,
|
|
1063
|
+
): Promise<{ applied: boolean; remainder: string }> {
|
|
1064
|
+
let applied = false
|
|
1065
|
+
let cursor = 0
|
|
1066
|
+
let marker = findFlushMarker(html, cursor)
|
|
1067
|
+
|
|
1068
|
+
while (marker) {
|
|
1069
|
+
let batch = html.slice(cursor, marker.index)
|
|
1070
|
+
await applyHtml(batch, marker.kind)
|
|
1071
|
+
applied = true
|
|
1072
|
+
cursor = marker.endIndex
|
|
1073
|
+
marker = findFlushMarker(html, cursor)
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return { applied, remainder: html.slice(cursor) }
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1036
1079
|
type FrameContainer = {
|
|
1037
1080
|
doc: Document
|
|
1038
1081
|
root: ParentNode
|
|
@@ -1085,7 +1128,7 @@ function createCommentContainer([start, end]: [Comment, Comment]): FrameContaine
|
|
|
1085
1128
|
|
|
1086
1129
|
function createFragmentFromString(doc: Document, content: string): DocumentFragment {
|
|
1087
1130
|
let template = doc.createElement('template')
|
|
1088
|
-
template.innerHTML = content.trim()
|
|
1131
|
+
template.innerHTML = stripDoctypeMarkup(content).trim()
|
|
1089
1132
|
return template.content
|
|
1090
1133
|
}
|
|
1091
1134
|
|
|
@@ -1097,11 +1140,6 @@ function isRemixNodeFrameContent(content: InternalFrameContent): content is Remi
|
|
|
1097
1140
|
)
|
|
1098
1141
|
}
|
|
1099
1142
|
|
|
1100
|
-
function isFullDocumentHtml(content: string): boolean {
|
|
1101
|
-
let trimmed = content.trimStart()
|
|
1102
|
-
return /^<!doctype html\b/i.test(trimmed) || /^<html[\s>]/i.test(trimmed)
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
1143
|
type HydrationMarker = {
|
|
1106
1144
|
id: string
|
|
1107
1145
|
start: Comment
|
|
@@ -1194,18 +1232,3 @@ function findEndMarker(
|
|
|
1194
1232
|
|
|
1195
1233
|
throw new Error('End marker not found')
|
|
1196
1234
|
}
|
|
1197
|
-
|
|
1198
|
-
function collectHtmlMarkerSummary(html: string): Record<string, number> {
|
|
1199
|
-
return {
|
|
1200
|
-
frameStarts: html.match(/<!--\s*rmx:f:/g)?.length ?? 0,
|
|
1201
|
-
frameEnds: html.match(/<!--\s*\/rmx:f\s*-->/g)?.length ?? 0,
|
|
1202
|
-
hydrationStarts: html.match(/<!--\s*rmx:h:/g)?.length ?? 0,
|
|
1203
|
-
hydrationEnds: html.match(/<!--\s*\/rmx:h\s*-->/g)?.length ?? 0,
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
function hasBalancedMarkerSummary(summary: Record<string, number>): boolean {
|
|
1208
|
-
return (
|
|
1209
|
-
summary.frameStarts === summary.frameEnds && summary.hydrationStarts === summary.hydrationEnds
|
|
1210
|
-
)
|
|
1211
|
-
}
|
package/src/runtime/jsx.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as dom from './dom.d.ts'
|
|
2
2
|
import type { Handle, RenderFn } from './component.ts'
|
|
3
|
+
import { createRemixElement } from './core/vnode.ts'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Any valid element type accepted by JSX or {@link import('./create-element.ts').createElement}.
|
|
@@ -102,7 +103,7 @@ export type Props<T extends keyof JSX.IntrinsicElements> = NormalizeMixProp<
|
|
|
102
103
|
*/
|
|
103
104
|
export function jsx(type: ElementType, props: ElementProps, key?: string): RemixElement
|
|
104
105
|
export function jsx(type: ElementType, props: ElementProps, key?: string): RemixElement {
|
|
105
|
-
return
|
|
106
|
+
return createRemixElement(type, props, key)
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
export { jsx as jsxDEV, jsx as jsxs }
|
|
@@ -367,32 +368,3 @@ declare global {
|
|
|
367
368
|
IntrinsicHTMLElements {}
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
|
-
|
|
371
|
-
function normalizeElementProps(props: ElementProps | null | undefined): ElementProps {
|
|
372
|
-
if (!props) return {}
|
|
373
|
-
if (!('mix' in props)) return props
|
|
374
|
-
|
|
375
|
-
let { mix, ...rest } = props
|
|
376
|
-
let normalizedMix = normalizeMixValue(mix)
|
|
377
|
-
return normalizedMix === undefined ? rest : { ...rest, mix: normalizedMix }
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function normalizeMixValue(mix: unknown): unknown[] | undefined {
|
|
381
|
-
if (!mix) return undefined
|
|
382
|
-
|
|
383
|
-
let normalizedMix = flattenMixValue(mix)
|
|
384
|
-
return normalizedMix.length === 0 ? undefined : normalizedMix
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function flattenMixValue(mix: unknown): unknown[] {
|
|
388
|
-
if (!mix) return []
|
|
389
|
-
if (!Array.isArray(mix)) return [mix]
|
|
390
|
-
|
|
391
|
-
let flattened: unknown[] = []
|
|
392
|
-
|
|
393
|
-
for (let item of mix) {
|
|
394
|
-
flattened.push(...flattenMixValue(item))
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return flattened
|
|
398
|
-
}
|