@remix-run/ui 0.1.1 → 0.2.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 +33 -10
- package/dist/animation/animate-layout-mixin.js +66 -66
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/spring.d.ts +6 -35
- package/dist/animation/spring.js +9 -8
- package/dist/animation/spring.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +7 -7
- 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.d.ts +1 -1
- 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 +86 -4
- package/dist/components/button/button.js +63 -3
- 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 +11 -11
- 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 +4 -9
- 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 +15 -24
- 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/popover/popover.d.ts +5 -5
- package/dist/components/popover/popover.js.map +1 -1
- 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 +11 -11
- package/dist/components/select/select.js +17 -26
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +6 -6
- package/dist/components/tabs/tabs.js +0 -2
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- 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/component.d.ts +18 -12
- package/dist/runtime/component.js +93 -80
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.d.ts +14 -0
- package/dist/runtime/core/attributes.js +103 -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 +170 -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 +20 -9
- package/dist/runtime/frame.d.ts +23 -2
- package/dist/runtime/frame.js +41 -47
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +2 -28
- 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 +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/mixins/ref-mixin.d.ts +2 -1
- package/dist/runtime/mixins/ref-mixin.js.map +1 -1
- package/dist/runtime/navigation.js +9 -1
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +1 -1
- package/dist/runtime/reconcile.js +452 -197
- 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.d.ts +10 -10
- package/dist/runtime/scheduler.js +1 -1
- 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/svg-attributes.js +1 -0
- package/dist/runtime/svg-attributes.js.map +1 -1
- package/dist/runtime/to-vnode.js +7 -11
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vnode.d.ts +11 -5
- package/dist/runtime/vnode.js +6 -3
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/server/stream.d.ts +2 -0
- package/dist/server/stream.js +154 -114
- 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/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 +13 -11
- package/dist/style/stylesheet.js +69 -8
- 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/theme/runtime.js +4 -1
- package/dist/theme/runtime.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 +3 -3
- package/src/animation/README.md +316 -0
- package/src/animation/animate-layout-mixin.ts +79 -71
- package/src/animation/spring.ts +21 -9
- package/src/components/accordion/README.md +166 -0
- package/src/components/accordion/accordion.tsx +14 -14
- 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/anchor/anchor.ts +1 -1
- package/src/components/breadcrumbs/README.md +55 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +1 -3
- 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 +118 -20
- 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/README.md +1 -1
- package/src/components/combobox/combobox.tsx +37 -21
- package/src/components/combobox/demos/overview.demo.tsx +199 -0
- package/src/components/glyph/README.md +72 -0
- package/src/components/glyph/glyph.tsx +2 -2
- package/src/components/listbox/README.md +115 -0
- package/src/components/listbox/demos/overview.demo.tsx +101 -0
- package/src/components/listbox/listbox.ts +106 -110
- 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 +61 -55
- package/src/components/popover/README.md +3 -3
- package/src/components/popover/demos/overview.demo.tsx +143 -0
- package/src/components/popover/popover.ts +90 -79
- 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 +53 -36
- package/src/components/tabs/tabs.tsx +10 -5
- package/src/index.ts +2 -1
- package/src/runtime/component.ts +122 -88
- package/src/runtime/core/attributes.ts +118 -0
- package/src/runtime/core/children.ts +42 -0
- package/src/runtime/core/props.ts +187 -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 +20 -9
- package/src/runtime/frame.ts +71 -55
- package/src/runtime/jsx.ts +3 -30
- package/src/runtime/mixins/link-mixin.ts +88 -86
- package/src/runtime/mixins/mixin.ts +18 -5
- package/src/runtime/mixins/on-mixin.ts +20 -12
- package/src/runtime/mixins/ref-mixin.ts +4 -5
- package/src/runtime/navigation.ts +10 -1
- package/src/runtime/reconcile.ts +563 -223
- package/src/runtime/run.ts +17 -1
- package/src/runtime/scheduler.ts +20 -20
- package/src/runtime/stream-protocol.ts +27 -0
- package/src/runtime/svg-attributes.ts +1 -0
- package/src/runtime/to-vnode.ts +7 -10
- package/src/runtime/vnode.ts +16 -9
- package/src/server/README.md +90 -0
- package/src/server/stream.ts +176 -124
- package/src/style/css-mixin.ts +6 -1
- package/src/style/index.ts +1 -2
- package/src/style/layers.ts +2 -0
- package/src/style/stylesheet.ts +88 -11
- package/src/test/README.md +107 -0
- package/src/test/utils.ts +3 -1
- package/src/theme/README.md +103 -0
- 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/theme/runtime.ts +4 -1
- package/src/utils/scroll-lock/README.md +33 -0
- package/src/utils/scroll-lock.ts +7 -3
package/src/server/stream.ts
CHANGED
|
@@ -2,7 +2,15 @@ import type { ComponentHandle, FrameHandle, Key, RemixNode } from '../runtime/co
|
|
|
2
2
|
import type { ElementType, ElementProps, RemixElement } from '../runtime/jsx.ts'
|
|
3
3
|
import { Fragment, createComponent, createFrameHandle, Frame } from '../runtime/component.ts'
|
|
4
4
|
import { isEntry, type EntryComponent } from '../runtime/client-entries.ts'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
FRAMEWORK_PROPS as RUNTIME_FRAMEWORK_PROPS,
|
|
7
|
+
SELF_CLOSING_TAGS,
|
|
8
|
+
normalizeAttributeName,
|
|
9
|
+
serializeStyleObject,
|
|
10
|
+
shouldStringifyBooleanAttribute,
|
|
11
|
+
} from '../runtime/core/attributes.ts'
|
|
12
|
+
import { appendFlushMarker, type FlushKind, stripFlushMarkers } from '../runtime/stream-protocol.ts'
|
|
13
|
+
import { REMIX_UI_STYLE_LAYER } from '../style/layers.ts'
|
|
6
14
|
|
|
7
15
|
interface VNode {
|
|
8
16
|
type: ElementType
|
|
@@ -24,6 +32,8 @@ export interface RenderToStreamOptions {
|
|
|
24
32
|
frameSrc?: string | URL
|
|
25
33
|
/** Source URL for the top-level frame in nested frame renders. */
|
|
26
34
|
topFrameSrc?: string | URL
|
|
35
|
+
/** Signal that cancels pending server rendering work. */
|
|
36
|
+
signal?: AbortSignal
|
|
27
37
|
/** Error hook invoked when rendering work throws. */
|
|
28
38
|
onError?: (error: unknown) => void
|
|
29
39
|
/** Callback used to resolve nested frame content during streaming SSR. */
|
|
@@ -90,6 +100,8 @@ interface RenderContext {
|
|
|
90
100
|
unresolvedHydrationData: Map<string, UnresolvedHydrationData>
|
|
91
101
|
frameData: Map<string, FrameData>
|
|
92
102
|
blockingFrameTails: ReadableStream<Uint8Array>[]
|
|
103
|
+
signal: AbortSignal
|
|
104
|
+
flushKind: FlushKind
|
|
93
105
|
serverIdScope: string
|
|
94
106
|
serverIdCounter: number
|
|
95
107
|
}
|
|
@@ -114,54 +126,32 @@ type Segment =
|
|
|
114
126
|
pending?: Promise<void>
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'br',
|
|
121
|
-
'col',
|
|
122
|
-
'embed',
|
|
123
|
-
'hr',
|
|
124
|
-
'img',
|
|
125
|
-
'input',
|
|
126
|
-
'link',
|
|
127
|
-
'meta',
|
|
128
|
-
'param',
|
|
129
|
-
'source',
|
|
130
|
-
'track',
|
|
131
|
-
'wbr',
|
|
132
|
-
])
|
|
133
|
-
|
|
134
|
-
const DEFAULT_STYLE_LAYER = 'rmx'
|
|
129
|
+
const TEXTAREA_VALUE_PROPS = new Set(['value', 'defaultValue'])
|
|
130
|
+
const INPUT_DEFAULT_PROPS = new Set(['defaultValue', 'defaultChecked'])
|
|
131
|
+
|
|
135
132
|
const DOCTYPE_PATTERN = /<!doctype(?:\s[^>]*)?>/gi
|
|
136
133
|
|
|
137
134
|
function stripDoctypeMarkup(html: string): string {
|
|
138
135
|
return html.replace(DOCTYPE_PATTERN, '')
|
|
139
136
|
}
|
|
140
137
|
|
|
141
|
-
function
|
|
138
|
+
function hasRenderableHtml(html: string): boolean {
|
|
139
|
+
return stripDoctypeMarkup(html).trim() !== ''
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function emptyReadableStream(): ReadableStream<Uint8Array> {
|
|
143
|
+
return new ReadableStream({
|
|
144
|
+
start(controller) {
|
|
145
|
+
controller.close()
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getStyleLayerName(selector: string, layer: string = REMIX_UI_STYLE_LAYER): string {
|
|
142
151
|
return `${layer}.${selector}`
|
|
143
152
|
}
|
|
144
153
|
|
|
145
|
-
const
|
|
146
|
-
'z-index',
|
|
147
|
-
'opacity',
|
|
148
|
-
'flex-grow',
|
|
149
|
-
'flex-shrink',
|
|
150
|
-
'flex-order',
|
|
151
|
-
'grid-area',
|
|
152
|
-
'grid-row',
|
|
153
|
-
'grid-column',
|
|
154
|
-
'font-weight',
|
|
155
|
-
'line-height',
|
|
156
|
-
'order',
|
|
157
|
-
'orphans',
|
|
158
|
-
'widows',
|
|
159
|
-
'zoom',
|
|
160
|
-
'columns',
|
|
161
|
-
'column-count',
|
|
162
|
-
])
|
|
163
|
-
|
|
164
|
-
const FRAMEWORK_PROPS = new Set(['children', 'innerHTML', 'on', 'key', 'mix'])
|
|
154
|
+
const SSR_OMITTED_PROPS = RUNTIME_FRAMEWORK_PROPS
|
|
165
155
|
|
|
166
156
|
const ssrSignal = Object.freeze({
|
|
167
157
|
get aborted() {
|
|
@@ -206,6 +196,7 @@ export function renderToStream(
|
|
|
206
196
|
let currentFrameSrc = normalizeFrameSrc(options?.frameSrc ?? options?.topFrameSrc)
|
|
207
197
|
let topFrameSrc = normalizeFrameSrc(options?.topFrameSrc ?? currentFrameSrc)
|
|
208
198
|
let rootFrameState = createSsrFrameState(currentFrameSrc, topFrameSrc)
|
|
199
|
+
let renderAbortController = new AbortController()
|
|
209
200
|
|
|
210
201
|
let context: RenderContext = {
|
|
211
202
|
insideSvg: false,
|
|
@@ -218,20 +209,38 @@ export function renderToStream(
|
|
|
218
209
|
unresolvedHydrationData: new Map(),
|
|
219
210
|
frameData: new Map(),
|
|
220
211
|
blockingFrameTails: [],
|
|
212
|
+
signal: renderAbortController.signal,
|
|
213
|
+
flushKind: 'fragment',
|
|
221
214
|
serverIdScope: crypto.randomUUID().slice(0, 8),
|
|
222
215
|
serverIdCounter: 0,
|
|
223
216
|
}
|
|
224
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
|
+
|
|
225
231
|
return new ReadableStream({
|
|
226
232
|
async start(controller) {
|
|
227
233
|
try {
|
|
228
234
|
let root = buildSegment(node, context, rootFrameState)
|
|
229
235
|
await resolveBlocking(root)
|
|
236
|
+
if (closeIfCancelled(controller, context)) return
|
|
230
237
|
await resolveClientEntries(context, options?.resolveClientEntry)
|
|
238
|
+
if (closeIfCancelled(controller, context)) return
|
|
231
239
|
validateClientEntriesForHydration(context)
|
|
232
240
|
let html = serializeSegment(root)
|
|
233
241
|
let finalHtml = finalizeHtml(html, context)
|
|
234
|
-
let bytes = encoder.encode(finalHtml)
|
|
242
|
+
let bytes = encoder.encode(appendFlushMarker(finalHtml, context.flushKind))
|
|
243
|
+
if (closeIfCancelled(controller, context)) return
|
|
235
244
|
controller.enqueue(bytes)
|
|
236
245
|
|
|
237
246
|
// If we have any tails from blocking frame streams, stream them now.
|
|
@@ -239,7 +248,7 @@ export function renderToStream(
|
|
|
239
248
|
// that must come after the initial document chunk.
|
|
240
249
|
let tailPromise =
|
|
241
250
|
context.blockingFrameTails.length > 0
|
|
242
|
-
? streamByteStreams(context.blockingFrameTails, controller, context
|
|
251
|
+
? streamByteStreams(context.blockingFrameTails, controller, context)
|
|
243
252
|
: Promise.resolve()
|
|
244
253
|
|
|
245
254
|
// If we have pending non-blocking frames, stream them as they resolve
|
|
@@ -250,15 +259,44 @@ export function renderToStream(
|
|
|
250
259
|
|
|
251
260
|
await Promise.all([tailPromise, pendingPromise])
|
|
252
261
|
|
|
262
|
+
if (closeIfCancelled(controller, context)) return
|
|
253
263
|
controller.close()
|
|
254
264
|
} catch (error) {
|
|
265
|
+
if (isSignalAbortError(context.signal, error)) {
|
|
266
|
+
closeStream(controller)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
255
269
|
onError(error)
|
|
256
270
|
controller.error(error)
|
|
257
271
|
}
|
|
258
272
|
},
|
|
273
|
+
cancel(reason) {
|
|
274
|
+
cancel(reason)
|
|
275
|
+
},
|
|
259
276
|
})
|
|
260
277
|
}
|
|
261
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
|
+
|
|
262
300
|
function defaultResolveFrame(): never {
|
|
263
301
|
throw new Error('No resolveFrame provided')
|
|
264
302
|
}
|
|
@@ -289,24 +327,28 @@ function createServerComponentId(context: RenderContext): string {
|
|
|
289
327
|
return `s${context.serverIdScope}-${context.serverIdCounter}`
|
|
290
328
|
}
|
|
291
329
|
|
|
292
|
-
async function splitFirstChunk(
|
|
293
|
-
stream: ReadableStream<Uint8Array>,
|
|
294
|
-
): Promise<{ first: Uint8Array; tail: ReadableStream<Uint8Array> }> {
|
|
330
|
+
async function splitFirstChunk(stream: ReadableStream<Uint8Array>): Promise<ResolvedFrameHtml> {
|
|
295
331
|
let reader = stream.getReader()
|
|
332
|
+
let decoder = new TextDecoder()
|
|
296
333
|
|
|
297
|
-
let
|
|
298
|
-
|
|
299
|
-
reader.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}),
|
|
334
|
+
let first: Uint8Array | undefined
|
|
335
|
+
while (true) {
|
|
336
|
+
let { value, done } = await reader.read()
|
|
337
|
+
if (done || !value) break
|
|
338
|
+
|
|
339
|
+
let text = decoder.decode(value, { stream: true })
|
|
340
|
+
if (hasRenderableHtml(text)) {
|
|
341
|
+
first = value
|
|
342
|
+
break
|
|
307
343
|
}
|
|
308
344
|
}
|
|
309
345
|
|
|
346
|
+
if (!first) {
|
|
347
|
+
decoder.decode()
|
|
348
|
+
reader.releaseLock()
|
|
349
|
+
return { html: '', tail: emptyReadableStream() }
|
|
350
|
+
}
|
|
351
|
+
|
|
310
352
|
let released = false
|
|
311
353
|
function release() {
|
|
312
354
|
if (released) return
|
|
@@ -334,17 +376,15 @@ async function splitFirstChunk(
|
|
|
334
376
|
},
|
|
335
377
|
})
|
|
336
378
|
|
|
337
|
-
return {
|
|
379
|
+
return { html: stripFlushMarkers(stripDoctypeMarkup(decoder.decode(first))), tail }
|
|
338
380
|
}
|
|
339
381
|
|
|
340
382
|
async function resolveFrameHtml(
|
|
341
383
|
input: string | ReadableStream<Uint8Array>,
|
|
342
384
|
): Promise<ResolvedFrameHtml> {
|
|
343
|
-
if (typeof input === 'string') return { html: stripDoctypeMarkup(input) }
|
|
385
|
+
if (typeof input === 'string') return { html: stripFlushMarkers(stripDoctypeMarkup(input)) }
|
|
344
386
|
|
|
345
|
-
|
|
346
|
-
let { first, tail } = await splitFirstChunk(input)
|
|
347
|
-
return { html: stripDoctypeMarkup(decoder.decode(first)), tail }
|
|
387
|
+
return await splitFirstChunk(input)
|
|
348
388
|
}
|
|
349
389
|
|
|
350
390
|
function isRemixElement(node: unknown): node is RemixElement {
|
|
@@ -385,6 +425,7 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
|
|
|
385
425
|
let tag = type
|
|
386
426
|
|
|
387
427
|
if (tag === 'html') {
|
|
428
|
+
context.flushKind = 'document'
|
|
388
429
|
return buildElementSegment(tag, props, context, frameState)
|
|
389
430
|
}
|
|
390
431
|
|
|
@@ -438,6 +479,9 @@ function buildFrameSegment(props: any, context: RenderContext, frameState: SsrFr
|
|
|
438
479
|
let framePromise = Promise.resolve(
|
|
439
480
|
context.resolveFrame(props.src, props.name, resolveFrameContext),
|
|
440
481
|
).then(async (resolved) => resolveFrameHtml(resolved))
|
|
482
|
+
// The response stream can be cancelled before pending frames are drained.
|
|
483
|
+
// Keep the promise observed so request aborts don't become unhandled.
|
|
484
|
+
framePromise.catch(() => {})
|
|
441
485
|
context.pendingFrames.push({ frameId, promise: framePromise })
|
|
442
486
|
} else {
|
|
443
487
|
seg.pending = Promise.resolve(
|
|
@@ -464,7 +508,15 @@ function buildElementSegment(
|
|
|
464
508
|
let processedProps = processStyleProps(mixedProps)
|
|
465
509
|
// Determine namespace context for the current element and its children
|
|
466
510
|
let currentIsSvg = context.insideSvg || tag === 'svg'
|
|
467
|
-
|
|
511
|
+
|
|
512
|
+
if (!currentIsSvg && tag === 'textarea') {
|
|
513
|
+
return buildTextareaElementSegment(tag, processedProps)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let attrs =
|
|
517
|
+
!currentIsSvg && tag === 'input'
|
|
518
|
+
? renderInputAttributes(processedProps)
|
|
519
|
+
: renderAttributes(processedProps, currentIsSvg)
|
|
468
520
|
|
|
469
521
|
if (SELF_CLOSING_TAGS.has(tag)) {
|
|
470
522
|
return staticSeg(`<${tag}${attrs} />`)
|
|
@@ -485,6 +537,27 @@ function buildElementSegment(
|
|
|
485
537
|
return compositeSeg([open, children, close])
|
|
486
538
|
}
|
|
487
539
|
|
|
540
|
+
function buildTextareaElementSegment(tag: string, props: any): Segment {
|
|
541
|
+
let attrs = renderAttributes(props, false, TEXTAREA_VALUE_PROPS)
|
|
542
|
+
let value = props.value ?? props.defaultValue ?? ''
|
|
543
|
+
return staticSeg(`<${tag}${attrs}>${escapeTextContent(String(value))}</${tag}>`)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function renderInputAttributes(props: any): string {
|
|
547
|
+
let value =
|
|
548
|
+
props.value === undefined && props.defaultValue !== undefined ? props.defaultValue : props.value
|
|
549
|
+
let checked =
|
|
550
|
+
props.checked === undefined && props.defaultChecked !== undefined
|
|
551
|
+
? props.defaultChecked
|
|
552
|
+
: props.checked
|
|
553
|
+
let inputProps = {
|
|
554
|
+
...props,
|
|
555
|
+
...(value === undefined ? {} : { value }),
|
|
556
|
+
...(checked === undefined ? {} : { checked }),
|
|
557
|
+
}
|
|
558
|
+
return renderAttributes(inputProps, false, INPUT_DEFAULT_PROPS)
|
|
559
|
+
}
|
|
560
|
+
|
|
488
561
|
function buildHeadElementSegment(
|
|
489
562
|
tag: string,
|
|
490
563
|
props: any,
|
|
@@ -502,18 +575,23 @@ function buildHeadElementSegment(
|
|
|
502
575
|
return compositeSeg([open, children, close])
|
|
503
576
|
}
|
|
504
577
|
|
|
505
|
-
function renderAttributes(props: any, isSvg: boolean): string {
|
|
578
|
+
function renderAttributes(props: any, isSvg: boolean, excludedProps?: Set<string>): string {
|
|
506
579
|
let attrs = ''
|
|
507
580
|
|
|
508
581
|
for (let key in props) {
|
|
509
|
-
if (
|
|
582
|
+
if (SSR_OMITTED_PROPS.has(key)) continue
|
|
583
|
+
if (excludedProps?.has(key)) continue
|
|
510
584
|
|
|
511
585
|
let value = props[key]
|
|
512
|
-
if (value === undefined || value === null || value === false) continue
|
|
513
|
-
|
|
514
586
|
let attrName = transformAttributeName(key, isSvg)
|
|
587
|
+
let shouldStringifyBoolean = shouldStringifyBooleanAttribute(attrName)
|
|
588
|
+
if (value === undefined || value === null || (value === false && !shouldStringifyBoolean)) {
|
|
589
|
+
continue
|
|
590
|
+
}
|
|
515
591
|
|
|
516
|
-
if (value ===
|
|
592
|
+
if (typeof value === 'boolean' && shouldStringifyBoolean) {
|
|
593
|
+
attrs += ` ${attrName}="${escapeHtml(String(value))}"`
|
|
594
|
+
} else if (value === true) {
|
|
517
595
|
attrs += ` ${attrName}`
|
|
518
596
|
} else {
|
|
519
597
|
attrs += ` ${attrName}="${escapeHtml(String(value))}"`
|
|
@@ -798,7 +876,11 @@ function buildComponentSegment(
|
|
|
798
876
|
let [renderedNode] = handle.render(props)
|
|
799
877
|
let childContext = { ...context, parentVNode: vnode }
|
|
800
878
|
|
|
801
|
-
|
|
879
|
+
let rendered = buildSegment(renderedNode, childContext, frameState)
|
|
880
|
+
if (childContext.flushKind === 'document') {
|
|
881
|
+
context.flushKind = 'document'
|
|
882
|
+
}
|
|
883
|
+
return rendered
|
|
802
884
|
}
|
|
803
885
|
|
|
804
886
|
function createHydrationPropsReplacer(context: RenderContext, frameState: SsrFrameState) {
|
|
@@ -1063,24 +1145,11 @@ function escapeTemplateContent(html: string): string {
|
|
|
1063
1145
|
}
|
|
1064
1146
|
|
|
1065
1147
|
function transformAttributeName(name: string, isSvg: boolean): string {
|
|
1066
|
-
|
|
1067
|
-
if (name.startsWith('aria-') || name.startsWith('data-')) return name
|
|
1068
|
-
|
|
1069
|
-
// HTML mappings
|
|
1070
|
-
if (name === 'className') return 'class'
|
|
1071
|
-
if (!isSvg) {
|
|
1072
|
-
if (name === 'htmlFor') return 'for'
|
|
1073
|
-
if (name === 'tabIndex') return 'tabindex'
|
|
1074
|
-
if (name === 'acceptCharset') return 'accept-charset'
|
|
1075
|
-
if (name === 'httpEquiv') return 'http-equiv'
|
|
1076
|
-
return name.toLowerCase()
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
return normalizeSvgAttribute(name).attr
|
|
1148
|
+
return normalizeAttributeName(name, isSvg).attr
|
|
1080
1149
|
}
|
|
1081
1150
|
|
|
1082
1151
|
function finalizeHtml(html: string, context: RenderContext): string {
|
|
1083
|
-
let hasHtmlRoot =
|
|
1152
|
+
let hasHtmlRoot = context.flushKind === 'document'
|
|
1084
1153
|
|
|
1085
1154
|
let styles = collectStyleTags(context)
|
|
1086
1155
|
if (styles) {
|
|
@@ -1164,7 +1233,7 @@ function collectStyleTags(context: RenderContext): string {
|
|
|
1164
1233
|
function wrapStyleForLayer(
|
|
1165
1234
|
selector: string,
|
|
1166
1235
|
css: string,
|
|
1167
|
-
layer: string =
|
|
1236
|
+
layer: string = REMIX_UI_STYLE_LAYER,
|
|
1168
1237
|
): string {
|
|
1169
1238
|
let trimmed = css.trim()
|
|
1170
1239
|
if (!trimmed) return ''
|
|
@@ -1174,7 +1243,7 @@ function wrapStyleForLayer(
|
|
|
1174
1243
|
function renderStyleTag(
|
|
1175
1244
|
selector: string,
|
|
1176
1245
|
css: string,
|
|
1177
|
-
layer: string =
|
|
1246
|
+
layer: string = REMIX_UI_STYLE_LAYER,
|
|
1178
1247
|
): string {
|
|
1179
1248
|
let wrappedCss = wrapStyleForLayer(selector, css, layer)
|
|
1180
1249
|
if (!wrappedCss) return ''
|
|
@@ -1225,36 +1294,6 @@ function escapeScriptJson(json: string): string {
|
|
|
1225
1294
|
return json.replace(/</g, '\\u003c')
|
|
1226
1295
|
}
|
|
1227
1296
|
|
|
1228
|
-
function serializeStyleObject(style: Record<string, any>): string {
|
|
1229
|
-
let parts: string[] = []
|
|
1230
|
-
|
|
1231
|
-
for (let [key, value] of Object.entries(style)) {
|
|
1232
|
-
if (value == null) continue
|
|
1233
|
-
if (typeof value === 'boolean') continue
|
|
1234
|
-
if (typeof value === 'number' && !Number.isFinite(value)) continue
|
|
1235
|
-
|
|
1236
|
-
// Convert camelCase to kebab-case
|
|
1237
|
-
let cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
|
|
1238
|
-
|
|
1239
|
-
// Add px to numeric values where appropriate
|
|
1240
|
-
let shouldAppendPx =
|
|
1241
|
-
typeof value === 'number' &&
|
|
1242
|
-
value !== 0 &&
|
|
1243
|
-
!NUMERIC_CSS_PROPS.has(cssKey) &&
|
|
1244
|
-
!cssKey.startsWith('--')
|
|
1245
|
-
|
|
1246
|
-
let cssValue = shouldAppendPx
|
|
1247
|
-
? `${value}px`
|
|
1248
|
-
: Array.isArray(value)
|
|
1249
|
-
? value.join(', ')
|
|
1250
|
-
: String(value)
|
|
1251
|
-
|
|
1252
|
-
parts.push(`${cssKey}: ${cssValue};`)
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
return parts.join(' ')
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
1297
|
// Frame styles work end-to-end when frame handlers use their own `renderToStream`:
|
|
1259
1298
|
// the handler's `finalizeHtml` emits selector-addressed `<style>` tags in its HTML, and on the client,
|
|
1260
1299
|
// the `adoptServerStyleTag` MutationObserver (stylesheet.ts) picks it up anywhere in the
|
|
@@ -1267,26 +1306,33 @@ async function streamPendingFrames(
|
|
|
1267
1306
|
let processedFrames = new Set<string>()
|
|
1268
1307
|
|
|
1269
1308
|
while (true) {
|
|
1309
|
+
if (context.signal.aborted) break
|
|
1310
|
+
|
|
1270
1311
|
let batch = context.pendingFrames.filter(({ frameId }) => !processedFrames.has(frameId))
|
|
1271
1312
|
if (batch.length === 0) break
|
|
1272
1313
|
|
|
1273
1314
|
await Promise.all(
|
|
1274
1315
|
batch.map(async ({ frameId, promise }) => {
|
|
1316
|
+
if (context.signal.aborted) return
|
|
1275
1317
|
processedFrames.add(frameId)
|
|
1276
1318
|
try {
|
|
1277
1319
|
let { html, tail } = await promise
|
|
1320
|
+
if (context.signal.aborted) return
|
|
1278
1321
|
html = dedupeServerStyleTagsInHtml(html, context.emittedStyles)
|
|
1279
1322
|
|
|
1280
1323
|
// Stream as a template element (first chunk only)
|
|
1281
1324
|
let templateHtml = `<template id="${frameId}">${escapeTemplateContent(html)}</template>`
|
|
1325
|
+
if (context.signal.aborted) return
|
|
1282
1326
|
controller.enqueue(encoder.encode(templateHtml))
|
|
1283
1327
|
|
|
1284
1328
|
// Forward any additional chunks from a stream-valued resolveFrame result.
|
|
1285
1329
|
if (tail) {
|
|
1286
|
-
await streamByteStreams([tail], controller, context
|
|
1330
|
+
await streamByteStreams([tail], controller, context)
|
|
1287
1331
|
}
|
|
1288
1332
|
} catch (error) {
|
|
1289
|
-
context.
|
|
1333
|
+
if (!isSignalAbortError(context.signal, error)) {
|
|
1334
|
+
context.onError(error)
|
|
1335
|
+
}
|
|
1290
1336
|
}
|
|
1291
1337
|
}),
|
|
1292
1338
|
)
|
|
@@ -1296,19 +1342,23 @@ async function streamPendingFrames(
|
|
|
1296
1342
|
async function streamByteStreams(
|
|
1297
1343
|
streams: ReadableStream<Uint8Array>[],
|
|
1298
1344
|
controller: ReadableStreamDefaultController,
|
|
1299
|
-
|
|
1345
|
+
context: RenderContext,
|
|
1300
1346
|
): Promise<void> {
|
|
1301
1347
|
await Promise.all(
|
|
1302
1348
|
streams.map(async (stream) => {
|
|
1303
1349
|
let reader = stream.getReader()
|
|
1304
1350
|
try {
|
|
1305
1351
|
while (true) {
|
|
1352
|
+
if (context.signal.aborted) break
|
|
1306
1353
|
let { done, value } = await reader.read()
|
|
1307
1354
|
if (done) break
|
|
1355
|
+
if (context.signal.aborted) break
|
|
1308
1356
|
controller.enqueue(value)
|
|
1309
1357
|
}
|
|
1310
1358
|
} catch (error) {
|
|
1311
|
-
|
|
1359
|
+
if (!isSignalAbortError(context.signal, error)) {
|
|
1360
|
+
context.onError(error)
|
|
1361
|
+
}
|
|
1312
1362
|
} finally {
|
|
1313
1363
|
reader.releaseLock()
|
|
1314
1364
|
}
|
|
@@ -1337,11 +1387,13 @@ async function drain(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
|
1337
1387
|
* @returns Rendered HTML.
|
|
1338
1388
|
*/
|
|
1339
1389
|
export async function renderToString(node: RemixNode): Promise<string> {
|
|
1340
|
-
return
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1390
|
+
return stripFlushMarkers(
|
|
1391
|
+
await drain(
|
|
1392
|
+
renderToStream(node, {
|
|
1393
|
+
onError(error) {
|
|
1394
|
+
throw error
|
|
1395
|
+
},
|
|
1396
|
+
}),
|
|
1397
|
+
),
|
|
1346
1398
|
)
|
|
1347
1399
|
}
|
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'
|