@remix-run/ui 0.1.2 → 0.3.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 +9 -7
- package/dist/animation/demos/animation.demo.d.ts +5 -0
- package/dist/animation/demos/animation.demo.js +92 -0
- package/dist/animation/demos/animation.demo.js.map +1 -0
- package/dist/animation/demos/aspect-ratio.d.ts +2 -0
- package/dist/animation/demos/aspect-ratio.js +69 -0
- package/dist/animation/demos/aspect-ratio.js.map +1 -0
- package/dist/animation/demos/bouncy-switch.d.ts +2 -0
- package/dist/animation/demos/bouncy-switch.js +36 -0
- package/dist/animation/demos/bouncy-switch.js.map +1 -0
- package/dist/animation/demos/color-interpolation.d.ts +1 -0
- package/dist/animation/demos/color-interpolation.js +53 -0
- package/dist/animation/demos/color-interpolation.js.map +1 -0
- package/dist/animation/demos/cube.d.ts +2 -0
- package/dist/animation/demos/cube.js +86 -0
- package/dist/animation/demos/cube.js.map +1 -0
- package/dist/animation/demos/default-animate.d.ts +2 -0
- package/dist/animation/demos/default-animate.js +89 -0
- package/dist/animation/demos/default-animate.js.map +1 -0
- package/dist/animation/demos/drag-release.d.ts +24 -0
- package/dist/animation/demos/drag-release.js +96 -0
- package/dist/animation/demos/drag-release.js.map +1 -0
- package/dist/animation/demos/enter.d.ts +1 -0
- package/dist/animation/demos/enter.js +19 -0
- package/dist/animation/demos/enter.js.map +1 -0
- package/dist/animation/demos/exit.d.ts +2 -0
- package/dist/animation/demos/exit.js +58 -0
- package/dist/animation/demos/exit.js.map +1 -0
- package/dist/animation/demos/flip-toggle.d.ts +2 -0
- package/dist/animation/demos/flip-toggle.js +37 -0
- package/dist/animation/demos/flip-toggle.js.map +1 -0
- package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
- package/dist/animation/demos/hold-to-confirm.js +211 -0
- package/dist/animation/demos/hold-to-confirm.js.map +1 -0
- package/dist/animation/demos/html-content.d.ts +2 -0
- package/dist/animation/demos/html-content.js +30 -0
- package/dist/animation/demos/html-content.js.map +1 -0
- package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
- package/dist/animation/demos/interruptible-keyframes.js +48 -0
- package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
- package/dist/animation/demos/keyframes.d.ts +1 -0
- package/dist/animation/demos/keyframes.js +24 -0
- package/dist/animation/demos/keyframes.js.map +1 -0
- package/dist/animation/demos/material-ripple.d.ts +2 -0
- package/dist/animation/demos/material-ripple.js +127 -0
- package/dist/animation/demos/material-ripple.js.map +1 -0
- package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
- package/dist/animation/demos/mixin-presence-list.js +88 -0
- package/dist/animation/demos/mixin-presence-list.js.map +1 -0
- package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
- package/dist/animation/demos/mixin-reclaim.js +90 -0
- package/dist/animation/demos/mixin-reclaim.js.map +1 -0
- package/dist/animation/demos/multi-state-badge.d.ts +2 -0
- package/dist/animation/demos/multi-state-badge.js +231 -0
- package/dist/animation/demos/multi-state-badge.js.map +1 -0
- package/dist/animation/demos/press.d.ts +2 -0
- package/dist/animation/demos/press.js +63 -0
- package/dist/animation/demos/press.js.map +1 -0
- package/dist/animation/demos/reordering.d.ts +2 -0
- package/dist/animation/demos/reordering.js +50 -0
- package/dist/animation/demos/reordering.js.map +1 -0
- package/dist/animation/demos/rolling-square.d.ts +2 -0
- package/dist/animation/demos/rolling-square.js +42 -0
- package/dist/animation/demos/rolling-square.js.map +1 -0
- package/dist/animation/demos/rotate.d.ts +1 -0
- package/dist/animation/demos/rotate.js +18 -0
- package/dist/animation/demos/rotate.js.map +1 -0
- package/dist/animation/demos/shared-layout.d.ts +2 -0
- package/dist/animation/demos/shared-layout.js +114 -0
- package/dist/animation/demos/shared-layout.js.map +1 -0
- package/dist/animation/demos/spring.demo.d.ts +6 -0
- package/dist/animation/demos/spring.demo.js +304 -0
- package/dist/animation/demos/spring.demo.js.map +1 -0
- package/dist/animation/demos/transition-options.d.ts +1 -0
- package/dist/animation/demos/transition-options.js +21 -0
- package/dist/animation/demos/transition-options.js.map +1 -0
- package/dist/animation/spring.d.ts +24 -31
- package/dist/animation/spring.js +31 -8
- package/dist/animation/spring.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +6 -6
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/accordion/demos/card.demo.d.ts +1 -0
- package/dist/components/accordion/demos/card.demo.js +1 -0
- package/dist/components/accordion/demos/card.demo.js.map +1 -1
- package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
- package/dist/components/accordion/demos/multiple.demo.js +1 -0
- package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
- package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
- package/dist/components/accordion/demos/overview.demo.js +1 -0
- package/dist/components/accordion/demos/overview.demo.js.map +1 -1
- package/dist/components/anchor/anchor.d.ts +8 -1
- package/dist/components/anchor/anchor.js +30 -11
- package/dist/components/anchor/anchor.js.map +1 -1
- package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
- package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
- package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
- package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
- package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
- package/dist/components/button/button.d.ts +4 -4
- package/dist/components/button/button.js +3 -1
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/button/demos/aliases.demo.d.ts +1 -0
- package/dist/components/button/demos/aliases.demo.js +1 -0
- package/dist/components/button/demos/aliases.demo.js.map +1 -1
- package/dist/components/button/demos/basic.demo.d.ts +1 -0
- package/dist/components/button/demos/basic.demo.js +1 -0
- package/dist/components/button/demos/basic.demo.js.map +1 -1
- package/dist/components/button/demos/states.demo.d.ts +1 -0
- package/dist/components/button/demos/states.demo.js +1 -0
- package/dist/components/button/demos/states.demo.js.map +1 -1
- package/dist/components/combobox/combobox.d.ts +10 -10
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/combobox/overview.demo.js.map +1 -0
- package/dist/components/listbox/listbox.d.ts +3 -3
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/listbox/overview.demo.js.map +1 -0
- package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
- package/dist/components/menu/demos/bubbling.demo.js +1 -0
- package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
- package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
- package/dist/components/menu/demos/context-trigger.demo.js +87 -0
- package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
- package/dist/components/menu/demos/overview.demo.d.ts +1 -0
- package/dist/components/menu/demos/overview.demo.js +1 -0
- package/dist/components/menu/demos/overview.demo.js.map +1 -1
- package/dist/components/menu/menu.d.ts +15 -12
- package/dist/components/menu/menu.js +55 -0
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/popover/overview.demo.js.map +1 -0
- package/dist/components/popover/popover.d.ts +7 -7
- package/dist/components/popover/popover.js +11 -4
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
- package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
- package/dist/components/select/deconstructed.demo.js.map +1 -0
- package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
- package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
- package/dist/components/select/overview.demo.js.map +1 -0
- package/dist/components/select/select.d.ts +10 -10
- package/dist/components/select/select.js +1 -1
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactions/keydown/keydown.d.ts +1 -1
- package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
- package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
- package/dist/runtime/client-entries.d.ts +1 -2
- package/dist/runtime/client-entries.js.map +1 -1
- package/dist/runtime/component.d.ts +20 -17
- package/dist/runtime/component.js +15 -6
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +3 -1
- package/dist/runtime/core/attributes.js +19 -1
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/props.js +6 -5
- package/dist/runtime/core/props.js.map +1 -1
- package/dist/runtime/demos/basic.demo.d.ts +6 -0
- package/dist/runtime/demos/basic.demo.js +16 -0
- package/dist/runtime/demos/basic.demo.js.map +1 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
- package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
- package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
- package/dist/runtime/demos/keyed-list.demo.js +62 -0
- package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
- package/dist/runtime/demos/readme.demo.d.ts +5 -0
- package/dist/runtime/demos/readme.demo.js +396 -0
- package/dist/runtime/demos/readme.demo.js.map +1 -0
- package/dist/runtime/diff-dom.js +194 -21
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/dom.d.ts +16 -9
- package/dist/runtime/frame.d.ts +7 -0
- package/dist/runtime/frame.js +176 -41
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +3 -4
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/mixins/link-mixin.d.ts +5 -1
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +2 -1
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
- package/dist/runtime/mixins/ref-mixin.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +1 -1
- package/dist/runtime/reconcile.js +19 -5
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/scheduler.d.ts +10 -10
- package/dist/runtime/scheduler.js +1 -1
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/svg-attributes.js +1 -0
- package/dist/runtime/svg-attributes.js.map +1 -1
- package/dist/runtime/to-vnode.js +2 -2
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +6 -1
- package/dist/runtime/vnode.js +4 -0
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +82 -11
- package/dist/server/stream.js.map +1 -1
- package/dist/style/css-mixin.d.ts +2 -1
- package/dist/style/css-mixin.js.map +1 -1
- package/dist/style/index.d.ts +1 -1
- package/dist/style/style.js +27 -10
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +13 -13
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/theme/contract.d.ts +24 -222
- package/dist/theme/contract.js +2 -2
- package/dist/theme/contract.js.map +1 -1
- package/dist/theme/glyph-contract.d.ts +1 -3
- package/dist/theme/glyph-contract.js.map +1 -1
- package/dist/theme/presets/rmx-01/index.d.ts +4 -2
- package/dist/theme/presets/rmx-01/index.js.map +1 -1
- package/dist/utils/scroll-lock.d.ts +3 -3
- package/dist/utils/scroll-lock.js.map +1 -1
- package/package.json +4 -4
- package/src/animation/README.md +7 -5
- package/src/animation/demos/animation.demo.tsx +194 -0
- package/src/animation/demos/aspect-ratio.tsx +120 -0
- package/src/animation/demos/bouncy-switch.tsx +47 -0
- package/src/animation/demos/color-interpolation.tsx +73 -0
- package/src/animation/demos/cube.tsx +116 -0
- package/src/animation/demos/default-animate.tsx +129 -0
- package/src/animation/demos/drag-release.ts +118 -0
- package/src/animation/demos/enter.tsx +22 -0
- package/src/animation/demos/exit.tsx +79 -0
- package/src/animation/demos/flip-toggle.tsx +46 -0
- package/src/animation/demos/hold-to-confirm.tsx +317 -0
- package/src/animation/demos/html-content.tsx +41 -0
- package/src/animation/demos/interruptible-keyframes.tsx +62 -0
- package/src/animation/demos/keyframes.tsx +29 -0
- package/src/animation/demos/material-ripple.tsx +158 -0
- package/src/animation/demos/mixin-presence-list.tsx +121 -0
- package/src/animation/demos/mixin-reclaim.tsx +125 -0
- package/src/animation/demos/multi-state-badge.tsx +426 -0
- package/src/animation/demos/press.tsx +67 -0
- package/src/animation/demos/reordering.tsx +65 -0
- package/src/animation/demos/rolling-square.tsx +57 -0
- package/src/animation/demos/rotate.tsx +21 -0
- package/src/animation/demos/shared-layout.tsx +211 -0
- package/src/animation/demos/spring.demo.tsx +385 -0
- package/src/animation/demos/transition-options.tsx +24 -0
- package/src/animation/spring.ts +43 -9
- package/src/components/accordion/accordion.tsx +7 -5
- package/src/components/accordion/demos/card.demo.tsx +1 -0
- package/src/components/accordion/demos/multiple.demo.tsx +1 -0
- package/src/components/accordion/demos/overview.demo.tsx +1 -0
- package/src/components/anchor/README.md +11 -3
- package/src/components/anchor/anchor.ts +54 -13
- package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +1 -1
- package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
- package/src/components/button/button.tsx +30 -18
- package/src/components/button/demos/aliases.demo.tsx +1 -0
- package/src/components/button/demos/basic.demo.tsx +1 -0
- package/src/components/button/demos/states.demo.tsx +1 -0
- package/src/components/combobox/README.md +1 -1
- package/src/components/combobox/combobox.tsx +30 -12
- package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/glyph/glyph.tsx +2 -2
- package/src/components/listbox/listbox.ts +88 -83
- package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/menu/README.md +26 -2
- package/src/components/menu/demos/bubbling.demo.tsx +1 -0
- package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
- package/src/components/menu/demos/overview.demo.tsx +1 -0
- package/src/components/menu/menu.tsx +100 -16
- package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/popover/popover.ts +101 -81
- package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
- package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
- package/src/components/select/select.tsx +27 -10
- package/src/index.ts +1 -0
- package/src/runtime/client-entries.ts +1 -5
- package/src/runtime/component.ts +37 -15
- package/src/runtime/core/attributes.ts +21 -0
- package/src/runtime/core/props.ts +6 -4
- package/src/runtime/demos/basic.demo.tsx +21 -0
- package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
- package/src/runtime/demos/keyed-list.demo.tsx +94 -0
- package/src/runtime/demos/readme.demo.tsx +702 -0
- package/src/runtime/diff-dom.ts +270 -20
- package/src/runtime/dom.ts +16 -9
- package/src/runtime/frame.ts +218 -41
- package/src/runtime/jsx.ts +7 -11
- package/src/runtime/mixins/link-mixin.ts +88 -86
- package/src/runtime/mixins/mixin.ts +9 -1
- package/src/runtime/mixins/ref-mixin.ts +4 -5
- package/src/runtime/reconcile.ts +23 -14
- package/src/runtime/scheduler.ts +20 -20
- package/src/runtime/svg-attributes.ts +1 -0
- package/src/runtime/to-vnode.ts +2 -2
- package/src/runtime/vnode.ts +10 -0
- package/src/server/README.md +4 -2
- package/src/server/stream.ts +84 -10
- package/src/style/css-mixin.ts +6 -1
- package/src/style/index.ts +1 -2
- package/src/style/style.ts +29 -10
- package/src/style/stylesheet.ts +14 -2
- package/src/theme/contract.ts +37 -5
- package/src/theme/glyph-contract.ts +3 -1
- package/src/theme/presets/rmx-01/index.ts +4 -2
- package/src/utils/scroll-lock.ts +7 -3
- package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
- package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
- package/dist/components/combobox/demos/overview.demo.js.map +0 -1
- package/dist/components/listbox/demos/overview.demo.js.map +0 -1
- package/dist/components/popover/demos/overview.demo.js.map +0 -1
- package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
- package/dist/components/select/demos/overview.demo.js.map +0 -1
package/src/runtime/reconcile.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isCommittedTextNode,
|
|
21
21
|
isFragmentNode,
|
|
22
22
|
isHostNode,
|
|
23
|
+
isNonRenderNode,
|
|
23
24
|
isTextNode,
|
|
24
25
|
findContextFromAncestry,
|
|
25
26
|
} from './vnode.ts'
|
|
@@ -421,6 +422,10 @@ export function diffVNodes(
|
|
|
421
422
|
return rootCursor
|
|
422
423
|
}
|
|
423
424
|
|
|
425
|
+
if (isNonRenderNode(curr) && isNonRenderNode(next)) {
|
|
426
|
+
return rootCursor
|
|
427
|
+
}
|
|
428
|
+
|
|
424
429
|
if (isCommittedTextNode(curr) && isTextNode(next)) {
|
|
425
430
|
diffText(curr, next, vParent)
|
|
426
431
|
return rootCursor
|
|
@@ -444,7 +449,7 @@ export function diffVNodes(
|
|
|
444
449
|
frame,
|
|
445
450
|
scheduler,
|
|
446
451
|
styles,
|
|
447
|
-
|
|
452
|
+
next,
|
|
448
453
|
rootTarget,
|
|
449
454
|
undefined,
|
|
450
455
|
anchor,
|
|
@@ -484,7 +489,7 @@ function replace(
|
|
|
484
489
|
return
|
|
485
490
|
}
|
|
486
491
|
|
|
487
|
-
let replacementAnchor = findNextSiblingDomAnchor(curr
|
|
492
|
+
let replacementAnchor = findNextSiblingDomAnchor(curr) ?? anchor
|
|
488
493
|
remove(curr, domParent, scheduler, styles)
|
|
489
494
|
insert(next, domParent, frame, scheduler, styles, vParent, rootTarget, replacementAnchor)
|
|
490
495
|
}
|
|
@@ -709,6 +714,10 @@ function insert(
|
|
|
709
714
|
? (dom: Node) => domParent.insertBefore(dom, anchor)
|
|
710
715
|
: (dom: Node) => domParent.appendChild(dom)
|
|
711
716
|
|
|
717
|
+
if (isNonRenderNode(node)) {
|
|
718
|
+
return cursor
|
|
719
|
+
}
|
|
720
|
+
|
|
712
721
|
if (isTextNode(node)) {
|
|
713
722
|
if (cursor instanceof Text) {
|
|
714
723
|
node._parent = vParent
|
|
@@ -878,17 +887,7 @@ function insert(
|
|
|
878
887
|
if (isFragmentNode(node)) {
|
|
879
888
|
// Insert fragment children in order before the same anchor
|
|
880
889
|
for (let child of node._children) {
|
|
881
|
-
cursor = insert(
|
|
882
|
-
child,
|
|
883
|
-
domParent,
|
|
884
|
-
frame,
|
|
885
|
-
scheduler,
|
|
886
|
-
styles,
|
|
887
|
-
vParent,
|
|
888
|
-
rootTarget,
|
|
889
|
-
anchor,
|
|
890
|
-
cursor,
|
|
891
|
-
)
|
|
890
|
+
cursor = insert(child, domParent, frame, scheduler, styles, node, rootTarget, anchor, cursor)
|
|
892
891
|
}
|
|
893
892
|
return cursor
|
|
894
893
|
}
|
|
@@ -1275,6 +1274,8 @@ export function renderComponent(
|
|
|
1275
1274
|
anchor?: Node,
|
|
1276
1275
|
cursor?: Node | null,
|
|
1277
1276
|
): Node | null | undefined {
|
|
1277
|
+
if (handle.isRemoved()) return cursor
|
|
1278
|
+
|
|
1278
1279
|
let [element, tasks] = handle.render(next.props)
|
|
1279
1280
|
let content = toVNode(element)
|
|
1280
1281
|
let newCursor = diffVNodes(
|
|
@@ -2020,15 +2021,23 @@ function shouldDispatchInlineMixinLifecycle(node: Node): boolean {
|
|
|
2020
2021
|
return true
|
|
2021
2022
|
}
|
|
2022
2023
|
|
|
2023
|
-
export function findNextSiblingDomAnchor(curr: VNode
|
|
2024
|
+
export function findNextSiblingDomAnchor(curr: VNode): Node | null {
|
|
2025
|
+
let vParent = curr._parent
|
|
2024
2026
|
if (!vParent || !Array.isArray(vParent._children)) return null
|
|
2025
2027
|
let children = vParent._children
|
|
2028
|
+
if (children.length === 0) return findNextSiblingDomAnchor(vParent)
|
|
2029
|
+
|
|
2026
2030
|
let idx = children.indexOf(curr)
|
|
2027
2031
|
if (idx === -1) return null
|
|
2028
2032
|
for (let i = idx + 1; i < children.length; i++) {
|
|
2029
2033
|
let dom = findFirstDomAnchor(children[i])
|
|
2030
2034
|
if (dom) return dom
|
|
2031
2035
|
}
|
|
2036
|
+
|
|
2037
|
+
if (isFragmentNode(vParent)) {
|
|
2038
|
+
return findNextSiblingDomAnchor(vParent)
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2032
2041
|
return null
|
|
2033
2042
|
}
|
|
2034
2043
|
|
package/src/runtime/scheduler.ts
CHANGED
|
@@ -17,7 +17,23 @@ type SchedulerPhaseListener = EventListenerOrEventListenerObject | null
|
|
|
17
17
|
/**
|
|
18
18
|
* Scheduler API used by the reconciler and frame runtime.
|
|
19
19
|
*/
|
|
20
|
-
export
|
|
20
|
+
export interface Scheduler {
|
|
21
|
+
enqueue(vnode: CommittedComponentNode, domParent: ParentNode): void
|
|
22
|
+
enqueueWork(newTasks: EmptyFn[]): void
|
|
23
|
+
enqueueCommitPhase(newTasks: EmptyFn[]): void
|
|
24
|
+
enqueueTasks(newTasks: EmptyFn[]): void
|
|
25
|
+
addEventListener(
|
|
26
|
+
type: SchedulerPhaseType,
|
|
27
|
+
listener: SchedulerPhaseListener,
|
|
28
|
+
options?: AddEventListenerOptions | boolean,
|
|
29
|
+
): void
|
|
30
|
+
removeEventListener(
|
|
31
|
+
type: SchedulerPhaseType,
|
|
32
|
+
listener: SchedulerPhaseListener,
|
|
33
|
+
options?: EventListenerOptions | boolean,
|
|
34
|
+
): void
|
|
35
|
+
dequeue(): void
|
|
36
|
+
}
|
|
21
37
|
|
|
22
38
|
// Protect against infinite cascading updates (e.g. handle.update() during render)
|
|
23
39
|
const MAX_CASCADING_UPDATES = 50
|
|
@@ -38,7 +54,7 @@ export function createScheduler(
|
|
|
38
54
|
doc: Document,
|
|
39
55
|
rootTarget: EventTarget,
|
|
40
56
|
styles: StyleManager = defaultStyleManager,
|
|
41
|
-
) {
|
|
57
|
+
): Scheduler {
|
|
42
58
|
let documentState = createDocumentState(doc)
|
|
43
59
|
let scheduled = new Map<CommittedComponentNode, ParentNode>()
|
|
44
60
|
let workTasks: EmptyFn[] = []
|
|
@@ -53,23 +69,7 @@ export function createScheduler(
|
|
|
53
69
|
beforeUpdate: 0,
|
|
54
70
|
commit: 0,
|
|
55
71
|
}
|
|
56
|
-
let scheduler:
|
|
57
|
-
enqueue(vnode: CommittedComponentNode, domParent: ParentNode): void
|
|
58
|
-
enqueueWork(newTasks: EmptyFn[]): void
|
|
59
|
-
enqueueCommitPhase(newTasks: EmptyFn[]): void
|
|
60
|
-
enqueueTasks(newTasks: EmptyFn[]): void
|
|
61
|
-
addEventListener(
|
|
62
|
-
type: SchedulerPhaseType,
|
|
63
|
-
listener: SchedulerPhaseListener,
|
|
64
|
-
options?: AddEventListenerOptions | boolean,
|
|
65
|
-
): void
|
|
66
|
-
removeEventListener(
|
|
67
|
-
type: SchedulerPhaseType,
|
|
68
|
-
listener: SchedulerPhaseListener,
|
|
69
|
-
options?: EventListenerOptions | boolean,
|
|
70
|
-
): void
|
|
71
|
-
dequeue(): void
|
|
72
|
-
}
|
|
72
|
+
let scheduler: Scheduler
|
|
73
73
|
|
|
74
74
|
function dispatchError(error: unknown) {
|
|
75
75
|
console.error(error)
|
|
@@ -137,7 +137,7 @@ export function createScheduler(
|
|
|
137
137
|
// Needed for fragment self-updates that add children - without this, new children
|
|
138
138
|
// would be appended after siblings. The keyed diff has placement logic, but unkeyed
|
|
139
139
|
// diff relies on anchor for correct positioning.
|
|
140
|
-
let anchor = findNextSiblingDomAnchor(vnode
|
|
140
|
+
let anchor = findNextSiblingDomAnchor(vnode) || undefined
|
|
141
141
|
try {
|
|
142
142
|
let updateStyles = getFrameStyleManager(vnode)
|
|
143
143
|
renderComponent(
|
package/src/runtime/to-vnode.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Fragment } from './component.ts'
|
|
|
2
2
|
import { invariant } from './invariant.ts'
|
|
3
3
|
import { isEmptyChild, isPrimitiveChild, normalizeChildren } from './core/children.ts'
|
|
4
4
|
import type { RemixElement, RemixNode } from './jsx.ts'
|
|
5
|
-
import { isRemixElement, TEXT_NODE, type VNode } from './vnode.ts'
|
|
5
|
+
import { isRemixElement, NON_RENDER_NODE, TEXT_NODE, type VNode } from './vnode.ts'
|
|
6
6
|
|
|
7
7
|
function flatMapChildrenToVNodes(node: RemixElement): VNode[] {
|
|
8
8
|
if (!('children' in node.props)) return []
|
|
@@ -21,7 +21,7 @@ function flattenChildrenToVNodes(nodes: RemixNode[], out: VNode[]): void {
|
|
|
21
21
|
|
|
22
22
|
export function toVNode(node: RemixNode): VNode {
|
|
23
23
|
if (isEmptyChild(node)) {
|
|
24
|
-
return { type:
|
|
24
|
+
return { type: NON_RENDER_NODE }
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (isPrimitiveChild(node)) {
|
package/src/runtime/vnode.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ElementProps, RemixNode } from './jsx.ts'
|
|
|
6
6
|
export { isRemixElement }
|
|
7
7
|
|
|
8
8
|
export const TEXT_NODE = Symbol('TEXT_NODE')
|
|
9
|
+
export const NON_RENDER_NODE = Symbol('NON_RENDER_NODE')
|
|
9
10
|
export const ROOT_VNODE = Symbol('ROOT_VNODE')
|
|
10
11
|
|
|
11
12
|
export type VNodeType =
|
|
@@ -13,6 +14,7 @@ export type VNodeType =
|
|
|
13
14
|
| string // host element
|
|
14
15
|
| Function // component
|
|
15
16
|
| typeof TEXT_NODE
|
|
17
|
+
| typeof NON_RENDER_NODE
|
|
16
18
|
| typeof Fragment
|
|
17
19
|
| typeof Frame
|
|
18
20
|
|
|
@@ -66,6 +68,10 @@ export type TextNode = VNode & {
|
|
|
66
68
|
_text: string
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
export type NonRenderNode = VNode & {
|
|
72
|
+
type: typeof NON_RENDER_NODE
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
export type CommittedTextNode = TextNode & {
|
|
70
76
|
_dom: Text
|
|
71
77
|
}
|
|
@@ -102,6 +108,10 @@ export function isTextNode(node: VNode): node is TextNode {
|
|
|
102
108
|
return node.type === TEXT_NODE
|
|
103
109
|
}
|
|
104
110
|
|
|
111
|
+
export function isNonRenderNode(node: VNode): node is NonRenderNode {
|
|
112
|
+
return node.type === NON_RENDER_NODE
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
export function isCommittedTextNode(node: VNode): node is CommittedTextNode {
|
|
106
116
|
return isTextNode(node) && node._dom instanceof Text
|
|
107
117
|
}
|
package/src/server/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Server
|
|
2
2
|
|
|
3
|
-
Remix
|
|
3
|
+
Remix UI can render to HTML on the server using two APIs:
|
|
4
4
|
|
|
5
5
|
- `renderToString` - Returns a complete HTML string. Simple, but buffers the entire response.
|
|
6
6
|
- `renderToStream` - Returns a `ReadableStream<Uint8Array>`. Sends the initial HTML immediately and streams frame content as it resolves.
|
|
@@ -26,6 +26,7 @@ import { renderToStream } from 'remix/ui/server'
|
|
|
26
26
|
|
|
27
27
|
let stream = renderToStream(<App />, {
|
|
28
28
|
frameSrc: request.url,
|
|
29
|
+
signal: request.signal,
|
|
29
30
|
resolveFrame(src, _target, context) {
|
|
30
31
|
let frameUrl = new URL(src, context?.currentFrameSrc ?? request.url)
|
|
31
32
|
return fetchHtml(frameUrl)
|
|
@@ -44,6 +45,7 @@ return new Response(stream, {
|
|
|
44
45
|
|
|
45
46
|
- **`frameSrc`** - Seeds SSR frame state for the current render. When provided, server-rendered components can read `handle.frame.src` and `handle.frames.top.src` during SSR.
|
|
46
47
|
- **`topFrameSrc`** - Overrides the root frame URL used for `handle.frames.top.src`. This is mainly useful when calling `renderToStream()` from inside `resolveFrame()` for a nested frame render.
|
|
48
|
+
- **`signal`** - Cancels pending server rendering work. Pass `request.signal` so client disconnects can stop unresolved frame work without invoking `onError` for the disconnect itself.
|
|
47
49
|
- **`resolveFrame(src, target, context)`** - Called when a `<Frame>` needs its content. Return a string of HTML, a `ReadableStream<Uint8Array>`, or a promise of either. `context.currentFrameSrc` is the URL for the frame that contains the `<Frame>`, and `context.topFrameSrc` is the outer document URL. Required if your component tree contains `<Frame>` elements.
|
|
48
50
|
- **`onError(error)`** - Called when a rendering error occurs. If not provided, the stream rejects with the error.
|
|
49
51
|
|
|
@@ -80,7 +82,7 @@ function ProductPage() {
|
|
|
80
82
|
|
|
81
83
|
## CSS
|
|
82
84
|
|
|
83
|
-
Components using the `css`
|
|
85
|
+
Components using the `css(...)` mixin through `mix` have their styles collected during rendering and emitted as a single `<style>` tag in the `<head>`. No client-side style injection is needed for server-rendered content.
|
|
84
86
|
|
|
85
87
|
## See Also
|
|
86
88
|
|
package/src/server/stream.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SELF_CLOSING_TAGS,
|
|
8
8
|
normalizeAttributeName,
|
|
9
9
|
serializeStyleObject,
|
|
10
|
+
shouldStringifyBooleanAttribute,
|
|
10
11
|
} from '../runtime/core/attributes.ts'
|
|
11
12
|
import { appendFlushMarker, type FlushKind, stripFlushMarkers } from '../runtime/stream-protocol.ts'
|
|
12
13
|
import { REMIX_UI_STYLE_LAYER } from '../style/layers.ts'
|
|
@@ -31,6 +32,8 @@ export interface RenderToStreamOptions {
|
|
|
31
32
|
frameSrc?: string | URL
|
|
32
33
|
/** Source URL for the top-level frame in nested frame renders. */
|
|
33
34
|
topFrameSrc?: string | URL
|
|
35
|
+
/** Signal that cancels pending server rendering work. */
|
|
36
|
+
signal?: AbortSignal
|
|
34
37
|
/** Error hook invoked when rendering work throws. */
|
|
35
38
|
onError?: (error: unknown) => void
|
|
36
39
|
/** Callback used to resolve nested frame content during streaming SSR. */
|
|
@@ -97,6 +100,7 @@ interface RenderContext {
|
|
|
97
100
|
unresolvedHydrationData: Map<string, UnresolvedHydrationData>
|
|
98
101
|
frameData: Map<string, FrameData>
|
|
99
102
|
blockingFrameTails: ReadableStream<Uint8Array>[]
|
|
103
|
+
signal: AbortSignal
|
|
100
104
|
flushKind: FlushKind
|
|
101
105
|
serverIdScope: string
|
|
102
106
|
serverIdCounter: number
|
|
@@ -192,6 +196,7 @@ export function renderToStream(
|
|
|
192
196
|
let currentFrameSrc = normalizeFrameSrc(options?.frameSrc ?? options?.topFrameSrc)
|
|
193
197
|
let topFrameSrc = normalizeFrameSrc(options?.topFrameSrc ?? currentFrameSrc)
|
|
194
198
|
let rootFrameState = createSsrFrameState(currentFrameSrc, topFrameSrc)
|
|
199
|
+
let renderAbortController = new AbortController()
|
|
195
200
|
|
|
196
201
|
let context: RenderContext = {
|
|
197
202
|
insideSvg: false,
|
|
@@ -204,21 +209,38 @@ export function renderToStream(
|
|
|
204
209
|
unresolvedHydrationData: new Map(),
|
|
205
210
|
frameData: new Map(),
|
|
206
211
|
blockingFrameTails: [],
|
|
212
|
+
signal: renderAbortController.signal,
|
|
207
213
|
flushKind: 'fragment',
|
|
208
214
|
serverIdScope: crypto.randomUUID().slice(0, 8),
|
|
209
215
|
serverIdCounter: 0,
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
function cancel(reason: unknown): void {
|
|
219
|
+
if (!renderAbortController.signal.aborted) {
|
|
220
|
+
renderAbortController.abort(reason)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let signal = options?.signal
|
|
225
|
+
if (signal?.aborted) {
|
|
226
|
+
cancel(signal.reason)
|
|
227
|
+
} else {
|
|
228
|
+
signal?.addEventListener('abort', () => cancel(signal.reason), { once: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
212
231
|
return new ReadableStream({
|
|
213
232
|
async start(controller) {
|
|
214
233
|
try {
|
|
215
234
|
let root = buildSegment(node, context, rootFrameState)
|
|
216
235
|
await resolveBlocking(root)
|
|
236
|
+
if (closeIfCancelled(controller, context)) return
|
|
217
237
|
await resolveClientEntries(context, options?.resolveClientEntry)
|
|
238
|
+
if (closeIfCancelled(controller, context)) return
|
|
218
239
|
validateClientEntriesForHydration(context)
|
|
219
240
|
let html = serializeSegment(root)
|
|
220
241
|
let finalHtml = finalizeHtml(html, context)
|
|
221
242
|
let bytes = encoder.encode(appendFlushMarker(finalHtml, context.flushKind))
|
|
243
|
+
if (closeIfCancelled(controller, context)) return
|
|
222
244
|
controller.enqueue(bytes)
|
|
223
245
|
|
|
224
246
|
// If we have any tails from blocking frame streams, stream them now.
|
|
@@ -226,7 +248,7 @@ export function renderToStream(
|
|
|
226
248
|
// that must come after the initial document chunk.
|
|
227
249
|
let tailPromise =
|
|
228
250
|
context.blockingFrameTails.length > 0
|
|
229
|
-
? streamByteStreams(context.blockingFrameTails, controller, context
|
|
251
|
+
? streamByteStreams(context.blockingFrameTails, controller, context)
|
|
230
252
|
: Promise.resolve()
|
|
231
253
|
|
|
232
254
|
// If we have pending non-blocking frames, stream them as they resolve
|
|
@@ -237,15 +259,44 @@ export function renderToStream(
|
|
|
237
259
|
|
|
238
260
|
await Promise.all([tailPromise, pendingPromise])
|
|
239
261
|
|
|
262
|
+
if (closeIfCancelled(controller, context)) return
|
|
240
263
|
controller.close()
|
|
241
264
|
} catch (error) {
|
|
265
|
+
if (isSignalAbortError(context.signal, error)) {
|
|
266
|
+
closeStream(controller)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
242
269
|
onError(error)
|
|
243
270
|
controller.error(error)
|
|
244
271
|
}
|
|
245
272
|
},
|
|
273
|
+
cancel(reason) {
|
|
274
|
+
cancel(reason)
|
|
275
|
+
},
|
|
246
276
|
})
|
|
247
277
|
}
|
|
248
278
|
|
|
279
|
+
function isSignalAbortError(signal: AbortSignal, error: unknown): boolean {
|
|
280
|
+
return signal.aborted && error === signal.reason
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function closeIfCancelled(
|
|
284
|
+
controller: ReadableStreamDefaultController,
|
|
285
|
+
context: RenderContext,
|
|
286
|
+
): boolean {
|
|
287
|
+
if (!context.signal.aborted) return false
|
|
288
|
+
closeStream(controller)
|
|
289
|
+
return true
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function closeStream(controller: ReadableStreamDefaultController): void {
|
|
293
|
+
try {
|
|
294
|
+
controller.close()
|
|
295
|
+
} catch {
|
|
296
|
+
// The consumer may already have cancelled the stream.
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
249
300
|
function defaultResolveFrame(): never {
|
|
250
301
|
throw new Error('No resolveFrame provided')
|
|
251
302
|
}
|
|
@@ -387,7 +438,7 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
|
|
|
387
438
|
|
|
388
439
|
if (typeof type === 'function') {
|
|
389
440
|
if (type === Frame) {
|
|
390
|
-
return buildFrameSegment(
|
|
441
|
+
return buildFrameSegment(node, context, frameState)
|
|
391
442
|
}
|
|
392
443
|
if (isEntry(type)) {
|
|
393
444
|
return buildEntrySegment(type, props, context, frameState)
|
|
@@ -405,7 +456,12 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
|
|
|
405
456
|
return staticSeg('')
|
|
406
457
|
}
|
|
407
458
|
|
|
408
|
-
function buildFrameSegment(
|
|
459
|
+
function buildFrameSegment(
|
|
460
|
+
node: RemixElement,
|
|
461
|
+
context: RenderContext,
|
|
462
|
+
frameState: SsrFrameState,
|
|
463
|
+
): Segment {
|
|
464
|
+
let props = node.props
|
|
409
465
|
let frameId = randomId('f')
|
|
410
466
|
|
|
411
467
|
// Store frame data in context for aggregation
|
|
@@ -428,6 +484,9 @@ function buildFrameSegment(props: any, context: RenderContext, frameState: SsrFr
|
|
|
428
484
|
let framePromise = Promise.resolve(
|
|
429
485
|
context.resolveFrame(props.src, props.name, resolveFrameContext),
|
|
430
486
|
).then(async (resolved) => resolveFrameHtml(resolved))
|
|
487
|
+
// The response stream can be cancelled before pending frames are drained.
|
|
488
|
+
// Keep the promise observed so request aborts don't become unhandled.
|
|
489
|
+
framePromise.catch(() => {})
|
|
431
490
|
context.pendingFrames.push({ frameId, promise: framePromise })
|
|
432
491
|
} else {
|
|
433
492
|
seg.pending = Promise.resolve(
|
|
@@ -529,11 +588,15 @@ function renderAttributes(props: any, isSvg: boolean, excludedProps?: Set<string
|
|
|
529
588
|
if (excludedProps?.has(key)) continue
|
|
530
589
|
|
|
531
590
|
let value = props[key]
|
|
532
|
-
if (value === undefined || value === null || value === false) continue
|
|
533
|
-
|
|
534
591
|
let attrName = transformAttributeName(key, isSvg)
|
|
592
|
+
let shouldStringifyBoolean = shouldStringifyBooleanAttribute(attrName)
|
|
593
|
+
if (value === undefined || value === null || (value === false && !shouldStringifyBoolean)) {
|
|
594
|
+
continue
|
|
595
|
+
}
|
|
535
596
|
|
|
536
|
-
if (value ===
|
|
597
|
+
if (typeof value === 'boolean' && shouldStringifyBoolean) {
|
|
598
|
+
attrs += ` ${attrName}="${escapeHtml(String(value))}"`
|
|
599
|
+
} else if (value === true) {
|
|
537
600
|
attrs += ` ${attrName}`
|
|
538
601
|
} else {
|
|
539
602
|
attrs += ` ${attrName}="${escapeHtml(String(value))}"`
|
|
@@ -1248,26 +1311,33 @@ async function streamPendingFrames(
|
|
|
1248
1311
|
let processedFrames = new Set<string>()
|
|
1249
1312
|
|
|
1250
1313
|
while (true) {
|
|
1314
|
+
if (context.signal.aborted) break
|
|
1315
|
+
|
|
1251
1316
|
let batch = context.pendingFrames.filter(({ frameId }) => !processedFrames.has(frameId))
|
|
1252
1317
|
if (batch.length === 0) break
|
|
1253
1318
|
|
|
1254
1319
|
await Promise.all(
|
|
1255
1320
|
batch.map(async ({ frameId, promise }) => {
|
|
1321
|
+
if (context.signal.aborted) return
|
|
1256
1322
|
processedFrames.add(frameId)
|
|
1257
1323
|
try {
|
|
1258
1324
|
let { html, tail } = await promise
|
|
1325
|
+
if (context.signal.aborted) return
|
|
1259
1326
|
html = dedupeServerStyleTagsInHtml(html, context.emittedStyles)
|
|
1260
1327
|
|
|
1261
1328
|
// Stream as a template element (first chunk only)
|
|
1262
1329
|
let templateHtml = `<template id="${frameId}">${escapeTemplateContent(html)}</template>`
|
|
1330
|
+
if (context.signal.aborted) return
|
|
1263
1331
|
controller.enqueue(encoder.encode(templateHtml))
|
|
1264
1332
|
|
|
1265
1333
|
// Forward any additional chunks from a stream-valued resolveFrame result.
|
|
1266
1334
|
if (tail) {
|
|
1267
|
-
await streamByteStreams([tail], controller, context
|
|
1335
|
+
await streamByteStreams([tail], controller, context)
|
|
1268
1336
|
}
|
|
1269
1337
|
} catch (error) {
|
|
1270
|
-
context.
|
|
1338
|
+
if (!isSignalAbortError(context.signal, error)) {
|
|
1339
|
+
context.onError(error)
|
|
1340
|
+
}
|
|
1271
1341
|
}
|
|
1272
1342
|
}),
|
|
1273
1343
|
)
|
|
@@ -1277,19 +1347,23 @@ async function streamPendingFrames(
|
|
|
1277
1347
|
async function streamByteStreams(
|
|
1278
1348
|
streams: ReadableStream<Uint8Array>[],
|
|
1279
1349
|
controller: ReadableStreamDefaultController,
|
|
1280
|
-
|
|
1350
|
+
context: RenderContext,
|
|
1281
1351
|
): Promise<void> {
|
|
1282
1352
|
await Promise.all(
|
|
1283
1353
|
streams.map(async (stream) => {
|
|
1284
1354
|
let reader = stream.getReader()
|
|
1285
1355
|
try {
|
|
1286
1356
|
while (true) {
|
|
1357
|
+
if (context.signal.aborted) break
|
|
1287
1358
|
let { done, value } = await reader.read()
|
|
1288
1359
|
if (done) break
|
|
1360
|
+
if (context.signal.aborted) break
|
|
1289
1361
|
controller.enqueue(value)
|
|
1290
1362
|
}
|
|
1291
1363
|
} catch (error) {
|
|
1292
|
-
|
|
1364
|
+
if (!isSignalAbortError(context.signal, error)) {
|
|
1365
|
+
context.onError(error)
|
|
1366
|
+
}
|
|
1293
1367
|
} finally {
|
|
1294
1368
|
reader.releaseLock()
|
|
1295
1369
|
}
|
package/src/style/css-mixin.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createMixin, renderMixinElement } from '../runtime/mixins/mixin.ts'
|
|
2
|
+
import type { MixinFactory } from '../runtime/mixins/mixin.ts'
|
|
2
3
|
import type { MixinDescriptor } from '../runtime/mixins/mixin.ts'
|
|
3
4
|
import type { ElementProps } from '../runtime/jsx.ts'
|
|
4
5
|
import { invariant } from '../runtime/invariant.ts'
|
|
@@ -20,7 +21,11 @@ const clientStyleCache: StyleCache = new Map()
|
|
|
20
21
|
/**
|
|
21
22
|
* Applies generated class names for CSS object styles.
|
|
22
23
|
*/
|
|
23
|
-
export const css
|
|
24
|
+
export const css: MixinFactory<Element, [styles: CSSProps], ElementProps> = createMixin<
|
|
25
|
+
Element,
|
|
26
|
+
[styles: CSSProps],
|
|
27
|
+
ElementProps
|
|
28
|
+
>((handle) => {
|
|
24
29
|
let activeSelector = ''
|
|
25
30
|
let activeGeneration = -1
|
|
26
31
|
let currentStyles: CSSProps = {}
|
package/src/style/index.ts
CHANGED
|
@@ -6,5 +6,4 @@ export type {
|
|
|
6
6
|
} from './style.ts'
|
|
7
7
|
export { processStyleClass, normalizeCssValue } from './style.ts'
|
|
8
8
|
export { createStyleManager }
|
|
9
|
-
|
|
10
|
-
export type StyleManager = ReturnType<typeof createStyleManager>
|
|
9
|
+
export type { ServerStyleSource, StyleManager } from './stylesheet.ts'
|
package/src/style/style.ts
CHANGED
|
@@ -141,22 +141,15 @@ function styleToCss(styles: StyleObject, selector: string = ''): string {
|
|
|
141
141
|
continue
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// For nested selectors, keep them wholesale inside the base block
|
|
145
144
|
// Allow nested selectors to be conditionally disabled.
|
|
146
145
|
// e.g. { '&:hover': condition ? undefined : { ... } }
|
|
147
146
|
let record = toRecord(value)
|
|
148
147
|
if (!record) continue
|
|
149
148
|
|
|
150
|
-
let nestedContent =
|
|
151
|
-
|
|
152
|
-
if (propValue != null) {
|
|
153
|
-
let normalizedValue = normalizeCssValue(prop, propValue)
|
|
154
|
-
nestedContent += ` ${camelToKebab(prop)}: ${normalizedValue};\n`
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (nestedContent) {
|
|
149
|
+
let nestedContent = nestedStyleBodyToCss(record, 4)
|
|
150
|
+
if (nestedContent.trim().length > 0) {
|
|
158
151
|
// Preserve key verbatim (e.g., '&[aria-selected], &[rmx-focus]')
|
|
159
|
-
nestedBlocks.push(` ${key} {\n${nestedContent} }`)
|
|
152
|
+
nestedBlocks.push(` ${key} {\n${nestedContent}\n }`)
|
|
160
153
|
}
|
|
161
154
|
} else {
|
|
162
155
|
// Base declaration
|
|
@@ -189,6 +182,32 @@ function styleToCss(styles: StyleObject, selector: string = ''): string {
|
|
|
189
182
|
return css
|
|
190
183
|
}
|
|
191
184
|
|
|
185
|
+
function nestedStyleBodyToCss(styles: StyleObject, spaces: number): string {
|
|
186
|
+
// This renders content that is already inside a style rule. Nested selectors
|
|
187
|
+
// and nested at-rules can stay in that rule body; root at-rule placement is
|
|
188
|
+
// handled by styleToCss before entering a nested style body.
|
|
189
|
+
let pad = ' '.repeat(spaces)
|
|
190
|
+
let declarations: string[] = []
|
|
191
|
+
let nestedBlocks: string[] = []
|
|
192
|
+
|
|
193
|
+
for (let [key, value] of Object.entries(styles)) {
|
|
194
|
+
if (isComplexSelector(key)) {
|
|
195
|
+
let record = toRecord(value)
|
|
196
|
+
if (!record) continue
|
|
197
|
+
|
|
198
|
+
let nestedContent = nestedStyleBodyToCss(record, spaces + 2)
|
|
199
|
+
if (nestedContent.trim().length > 0) {
|
|
200
|
+
nestedBlocks.push(`${pad}${key} {\n${nestedContent}\n${pad}}`)
|
|
201
|
+
}
|
|
202
|
+
} else if (value != null) {
|
|
203
|
+
let normalizedValue = normalizeCssValue(key, value)
|
|
204
|
+
declarations.push(`${pad}${camelToKebab(key)}: ${normalizedValue};`)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return [...declarations, ...nestedBlocks].join('\n')
|
|
209
|
+
}
|
|
210
|
+
|
|
192
211
|
function indent(text: string, spaces: number): string {
|
|
193
212
|
let pad = ' '.repeat(spaces)
|
|
194
213
|
return text
|
package/src/style/stylesheet.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { REMIX_UI_STYLE_LAYER } from './layers.ts'
|
|
2
2
|
|
|
3
3
|
type RuleEntry = { count: number; index: number }
|
|
4
|
-
type ServerStyleSource = ParentNode | Iterable<Node>
|
|
4
|
+
export type ServerStyleSource = ParentNode | Iterable<Node>
|
|
5
|
+
|
|
6
|
+
export interface StyleManager {
|
|
7
|
+
insert(className: string, rule: string): void
|
|
8
|
+
remove(className: string): void
|
|
9
|
+
has(className: string): boolean
|
|
10
|
+
getGeneration(): number
|
|
11
|
+
reset(): void
|
|
12
|
+
adoptServerStyles(source: ServerStyleSource): Set<string>
|
|
13
|
+
replaceServerStyles(source: ServerStyleSource): void
|
|
14
|
+
selectors(): IterableIterator<string>
|
|
15
|
+
dispose(): void
|
|
16
|
+
}
|
|
5
17
|
|
|
6
18
|
const SERVER_STYLE_SELECTOR = 'style[data-rmx]'
|
|
7
19
|
|
|
@@ -68,7 +80,7 @@ function getStyleSelector(styleEl: HTMLStyleElement): string | null {
|
|
|
68
80
|
return selector ? selector : null
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
export function createStyleManager(layer: string = REMIX_UI_STYLE_LAYER) {
|
|
83
|
+
export function createStyleManager(layer: string = REMIX_UI_STYLE_LAYER): StyleManager {
|
|
72
84
|
let stylesheet: CSSStyleSheet | null = null
|
|
73
85
|
let generation = 0
|
|
74
86
|
|