@remix-run/ui 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -1
- package/dist/accordion/index.js +2 -2
- package/dist/accordion/primitives.d.ts +1 -1
- package/dist/accordion/primitives.js.map +1 -1
- package/dist/anchor/index.js.map +1 -1
- package/dist/animation/animate-layout-mixin.js +1 -1
- package/dist/animation/animate-layout-mixin.js.map +1 -1
- package/dist/animation/animate-mixins.js +2 -2
- package/dist/animation/animate-mixins.js.map +1 -1
- package/dist/animation/demos/color-interpolation.js +1 -1
- package/dist/animation/demos/color-interpolation.js.map +1 -1
- package/dist/animation/demos/drag-release.d.ts +1 -1
- package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
- package/dist/animation/demos/material-ripple.js +1 -3
- package/dist/animation/demos/material-ripple.js.map +1 -1
- package/dist/animation/index.js +4 -4
- package/dist/animation/spring.js.map +1 -1
- package/dist/breadcrumbs/index.js +2 -2
- package/dist/button/index.js +25 -25
- package/dist/button/index.js.map +1 -1
- package/dist/checkbox/index.js +11 -9
- package/dist/checkbox/index.js.map +1 -1
- package/dist/combobox/index.js +4 -4
- package/dist/combobox/index.js.map +1 -1
- package/dist/combobox/primitives.d.ts +1 -1
- package/dist/combobox/primitives.js +5 -5
- package/dist/combobox/primitives.js.map +1 -1
- package/dist/dev/refresh.d.ts +1 -0
- package/dist/dev/refresh.js +2 -0
- package/dist/dev/refresh.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -14
- package/dist/input/index.js +9 -9
- package/dist/input/index.js.map +1 -1
- package/dist/jsx-runtime.js +2 -2
- package/dist/listbox/index.js +2 -2
- package/dist/menu/hover-aim.js.map +1 -1
- package/dist/menu/index.js +3 -3
- package/dist/menu/primitives.d.ts +1 -1
- package/dist/menu/primitives.js +6 -6
- package/dist/menu/primitives.js.map +1 -1
- package/dist/popover/index.js +3 -3
- package/dist/popover/scroll-lock.js.map +1 -1
- package/dist/radio/index.js +7 -7
- package/dist/radio/index.js.map +1 -1
- package/dist/runtime/client-entry-boundary.d.ts +14 -0
- package/dist/runtime/client-entry-boundary.js +29 -0
- package/dist/runtime/client-entry-boundary.js.map +1 -0
- package/dist/runtime/component.d.ts +19 -4
- package/dist/runtime/component.js +39 -9
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/attributes.js +36 -3
- package/dist/runtime/core/attributes.js.map +1 -1
- package/dist/runtime/core/children.d.ts +2 -0
- package/dist/runtime/core/children.js +26 -2
- package/dist/runtime/core/children.js.map +1 -1
- package/dist/runtime/core/props.js +2 -2
- package/dist/runtime/core/vnode.d.ts +3 -3
- package/dist/runtime/core/vnode.js +22 -1
- package/dist/runtime/core/vnode.js.map +1 -1
- package/dist/runtime/create-element.d.ts +2 -2
- package/dist/runtime/create-element.js +3 -2
- package/dist/runtime/create-element.js.map +1 -1
- package/dist/runtime/diff-dom.js +294 -219
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/diff-props.js +3 -3
- package/dist/runtime/dom.d.ts +12 -0
- package/dist/runtime/element-function.d.ts +3 -0
- package/dist/runtime/element-function.js +2 -0
- package/dist/runtime/element-function.js.map +1 -0
- package/dist/runtime/form-navigation.d.ts +25 -0
- package/dist/runtime/form-navigation.js +132 -0
- package/dist/runtime/form-navigation.js.map +1 -0
- package/dist/runtime/frame-resolution.d.ts +5 -0
- package/dist/runtime/frame-resolution.js +10 -0
- package/dist/runtime/frame-resolution.js.map +1 -0
- package/dist/runtime/frame.d.ts +58 -15
- package/dist/runtime/frame.js +367 -114
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/jsx.d.ts +1 -1
- package/dist/runtime/jsx.js +1 -1
- package/dist/runtime/jsx.js.map +1 -1
- package/dist/runtime/key.d.ts +1 -0
- package/dist/runtime/key.js +2 -0
- package/dist/runtime/key.js.map +1 -0
- package/dist/runtime/mixins/attrs-mixin.js +2 -2
- package/dist/runtime/mixins/link-mixin.js +4 -3
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +15 -10
- package/dist/runtime/mixins/mixin.js +4 -4
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.js +1 -1
- package/dist/runtime/mixins/ref-mixin.js +1 -1
- package/dist/runtime/navigation.d.ts +4 -1
- package/dist/runtime/navigation.js +157 -32
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.d.ts +11 -10
- package/dist/runtime/reconcile.js +549 -421
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/refresh.d.ts +28 -0
- package/dist/runtime/refresh.js +31 -0
- package/dist/runtime/refresh.js.map +1 -0
- package/dist/runtime/render.js +1 -1
- package/dist/runtime/render.js.map +1 -1
- package/dist/runtime/run.d.ts +3 -1
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +8 -16
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/to-vnode.d.ts +2 -2
- package/dist/runtime/to-vnode.js +68 -20
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/vdom.d.ts +4 -2
- package/dist/runtime/vdom.js +87 -42
- package/dist/runtime/vdom.js.map +1 -1
- package/dist/runtime/vnode.d.ts +138 -67
- package/dist/runtime/vnode.js +17 -12
- package/dist/runtime/vnode.js.map +1 -1
- package/dist/select/index.js +11 -11
- package/dist/select/index.js.map +1 -1
- package/dist/select/primitives.d.ts +1 -1
- package/dist/select/primitives.js +6 -6
- package/dist/server/stream.js +20 -27
- package/dist/server/stream.js.map +1 -1
- package/dist/shared/focus-styles.d.ts +1 -1
- package/dist/shared/focus-styles.js +1 -1
- package/dist/shared/focus-styles.js.map +1 -1
- package/dist/shared/listbox-popover-styles.js +1 -1
- package/dist/shared/style-values.js +26 -26
- package/dist/shared/style-values.js.map +1 -1
- package/dist/style/css-mixin.js +3 -3
- package/dist/style/index.js +2 -2
- package/dist/style/style.js +25 -6
- package/dist/style/style.js.map +1 -1
- package/dist/style/stylesheet.d.ts +0 -1
- package/dist/style/stylesheet.js +13 -54
- package/dist/style/stylesheet.js.map +1 -1
- package/dist/tabs/index.js +11 -11
- package/dist/tabs/index.js.map +1 -1
- package/dist/tabs/primitives.d.ts +1 -1
- package/dist/test.js +1 -1
- package/dist/toggle/index.js +9 -9
- package/dist/toggle/index.js.map +1 -1
- package/dist/toggle/primitives.d.ts +1 -1
- package/package.json +10 -7
- package/src/animation/animate-mixins.ts +3 -3
- package/src/animation/demos/color-interpolation.tsx +1 -1
- package/src/button/index.ts +24 -24
- package/src/checkbox/index.ts +10 -8
- package/src/combobox/index.tsx +1 -1
- package/src/dev/refresh.ts +1 -0
- package/src/index.ts +1 -1
- package/src/input/index.ts +8 -8
- package/src/radio/index.ts +6 -5
- package/src/runtime/client-entry-boundary.ts +54 -0
- package/src/runtime/component.ts +65 -16
- package/src/runtime/core/attributes.ts +35 -1
- package/src/runtime/core/children.ts +24 -2
- package/src/runtime/core/vnode.ts +25 -4
- package/src/runtime/create-element.ts +6 -5
- package/src/runtime/demos/readme.demo.tsx +4 -4
- package/src/runtime/diff-dom.ts +358 -243
- package/src/runtime/dom.ts +331 -214
- package/src/runtime/element-function.ts +1 -0
- package/src/runtime/event-listeners.ts +1 -1
- package/src/runtime/form-navigation.ts +184 -0
- package/src/runtime/frame-resolution.ts +13 -0
- package/src/runtime/frame.ts +482 -124
- package/src/runtime/jsx.ts +3 -5
- package/src/runtime/key.ts +1 -0
- package/src/runtime/mixins/link-mixin.ts +1 -0
- package/src/runtime/mixins/mixin.ts +21 -18
- package/src/runtime/navigation.ts +204 -30
- package/src/runtime/reconcile.ts +762 -759
- package/src/runtime/refresh.ts +46 -0
- package/src/runtime/run.ts +12 -2
- package/src/runtime/scheduler.ts +9 -29
- package/src/runtime/to-vnode.ts +85 -23
- package/src/runtime/vdom.ts +89 -70
- package/src/runtime/vnode.ts +192 -88
- package/src/select/index.tsx +9 -9
- package/src/server/stream.ts +22 -28
- package/src/shared/focus-styles.ts +1 -1
- package/src/shared/style-values.ts +26 -26
- package/src/style/style.ts +25 -6
- package/src/style/stylesheet.ts +32 -57
- package/src/tabs/index.tsx +12 -11
- package/src/test/README.md +151 -60
- package/src/toggle/index.tsx +9 -7
package/src/runtime/vnode.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type { ComponentHandle,
|
|
2
|
-
import { Fragment, Frame } from './component.ts'
|
|
1
|
+
import type { ComponentHandle, Fragment, Frame, FrameHandle, FrameProps } from './component.ts'
|
|
3
2
|
import { isRemixElement } from './core/vnode.ts'
|
|
4
|
-
import type {
|
|
3
|
+
import type { Frame as FrameInstance } from './frame.ts'
|
|
4
|
+
import type { RemixNode } from './jsx.ts'
|
|
5
|
+
import type { ElementFunction } from './element-function.ts'
|
|
6
|
+
import type { Key } from './key.ts'
|
|
7
|
+
import type { MixinRuntimeState, MixinRuntimeValue } from './mixins/mixin.ts'
|
|
8
|
+
import type { OnMixinDescriptor } from './mixins/on-mixin.ts'
|
|
9
|
+
import type { Scheduler } from './scheduler.ts'
|
|
10
|
+
import type { StyleManager } from '../style/index.ts'
|
|
5
11
|
|
|
6
12
|
export { isRemixElement }
|
|
7
13
|
|
|
@@ -9,136 +15,234 @@ export const TEXT_NODE = Symbol('TEXT_NODE')
|
|
|
9
15
|
export const NON_RENDER_NODE = Symbol('NON_RENDER_NODE')
|
|
10
16
|
export const ROOT_VNODE = Symbol('ROOT_VNODE')
|
|
11
17
|
|
|
12
|
-
export type
|
|
13
|
-
| typeof ROOT_VNODE
|
|
14
|
-
| string // host element
|
|
15
|
-
| Function // component
|
|
16
|
-
| typeof TEXT_NODE
|
|
17
|
-
| typeof NON_RENDER_NODE
|
|
18
|
-
| typeof Fragment
|
|
19
|
-
| typeof Frame
|
|
20
|
-
|
|
21
|
-
export type VNode<T extends VNodeType = VNodeType> = {
|
|
22
|
-
type: T
|
|
23
|
-
props?: ElementProps
|
|
24
|
-
_mixedProps?: ElementProps
|
|
25
|
-
key?: string
|
|
26
|
-
|
|
27
|
-
// _prefixes assigned during reconciliation
|
|
28
|
-
_parent?: VNode
|
|
29
|
-
_children?: VNode[]
|
|
30
|
-
_dom?: unknown
|
|
31
|
-
_controller?: AbortController
|
|
32
|
-
_mixState?: unknown
|
|
33
|
-
_directEventDescriptors?: unknown
|
|
34
|
-
_directEventState?: unknown
|
|
35
|
-
_controlledState?: unknown
|
|
36
|
-
_svg?: boolean
|
|
37
|
-
// Range roots render between comment boundary markers
|
|
38
|
-
_rangeStart?: Node
|
|
39
|
-
_rangeEnd?: Node
|
|
40
|
-
_pendingHydrationComponentId?: string
|
|
41
|
-
_frameInstance?: unknown
|
|
42
|
-
_frameFallbackRoot?: { render: (element: RemixNode) => void; dispose: () => void }
|
|
43
|
-
_frameResolveToken?: number
|
|
44
|
-
_frameResolveController?: AbortController
|
|
45
|
-
_frameResolved?: boolean
|
|
18
|
+
export type VNodeKind = 'root' | 'empty' | 'text' | 'fragment' | 'host' | 'component' | 'frame'
|
|
46
19
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Component
|
|
51
|
-
_handle?: ComponentHandle
|
|
52
|
-
_id?: string
|
|
53
|
-
_content?: VNode
|
|
20
|
+
export type RuntimeElementProps = {
|
|
21
|
+
[name: string]: unknown
|
|
22
|
+
}
|
|
54
23
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
24
|
+
export type RuntimeHostProps = RuntimeElementProps & {
|
|
25
|
+
children?: RemixNode
|
|
26
|
+
innerHTML?: string
|
|
27
|
+
mix?: MixinRuntimeValue
|
|
59
28
|
}
|
|
60
29
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
30
|
+
type InputNodeBase<kind extends VNodeKind, type> = {
|
|
31
|
+
kind: kind
|
|
32
|
+
type: type
|
|
33
|
+
key?: Key
|
|
34
|
+
_parent?: never
|
|
64
35
|
}
|
|
65
36
|
|
|
66
|
-
export type
|
|
67
|
-
|
|
37
|
+
export type NonRenderNode = InputNodeBase<'empty', typeof NON_RENDER_NODE>
|
|
38
|
+
|
|
39
|
+
export type TextNode = InputNodeBase<'text', typeof TEXT_NODE> & {
|
|
68
40
|
_text: string
|
|
69
41
|
}
|
|
70
42
|
|
|
71
|
-
export type
|
|
72
|
-
|
|
43
|
+
export type FragmentNode = InputNodeBase<'fragment', typeof Fragment> & {
|
|
44
|
+
_children: VNodeInput[]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type HostNode = InputNodeBase<'host', string> & {
|
|
48
|
+
props: RuntimeHostProps
|
|
49
|
+
_children: VNodeInput[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ComponentNode = InputNodeBase<'component', ElementFunction> & {
|
|
53
|
+
props: RuntimeElementProps
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type FrameNode = InputNodeBase<'frame', typeof Frame> & {
|
|
57
|
+
props: RuntimeElementProps & FrameProps
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
export type
|
|
60
|
+
export type VNodeInput =
|
|
61
|
+
| NonRenderNode
|
|
62
|
+
| TextNode
|
|
63
|
+
| FragmentNode
|
|
64
|
+
| HostNode
|
|
65
|
+
| ComponentNode
|
|
66
|
+
| FrameNode
|
|
67
|
+
|
|
68
|
+
type MountedNodeBase = {
|
|
69
|
+
_parent: VNodeParent
|
|
70
|
+
_svg: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type CommittedNodeBase<kind extends VNodeKind, type> = {
|
|
74
|
+
kind: kind
|
|
75
|
+
type: type
|
|
76
|
+
key?: Key
|
|
77
|
+
} & MountedNodeBase
|
|
78
|
+
|
|
79
|
+
export type CommittedNonRenderNode = CommittedNodeBase<'empty', typeof NON_RENDER_NODE>
|
|
80
|
+
|
|
81
|
+
export type CommittedTextNode = CommittedNodeBase<'text', typeof TEXT_NODE> & {
|
|
82
|
+
_text: string
|
|
76
83
|
_dom: Text
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
export type
|
|
86
|
+
export type ControlledReflectionState = {
|
|
87
|
+
disposed: boolean
|
|
88
|
+
listenersAttached: boolean
|
|
89
|
+
pendingRestoreVersion: number
|
|
90
|
+
managesValue: boolean
|
|
91
|
+
managesChecked: boolean
|
|
92
|
+
hasControlledValue: boolean
|
|
93
|
+
controlledValue: unknown
|
|
94
|
+
hasControlledChecked: boolean
|
|
95
|
+
controlledChecked: unknown
|
|
96
|
+
onInput(): void
|
|
97
|
+
onChange(): void
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type DirectEventBinding = {
|
|
80
101
|
type: string
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
handler: (event: Event, signal: AbortSignal) => void | Promise<void>
|
|
103
|
+
capture: boolean
|
|
104
|
+
stableHandler: ((event: Event) => void) | null
|
|
105
|
+
reentry: AbortController | null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type DirectEventState = {
|
|
109
|
+
bindings: DirectEventBinding[]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type HostPersistenceState = {
|
|
113
|
+
parent: ParentNode
|
|
114
|
+
token: number
|
|
83
115
|
}
|
|
84
116
|
|
|
85
|
-
export type CommittedHostNode =
|
|
117
|
+
export type CommittedHostNode = CommittedNodeBase<'host', string> & {
|
|
118
|
+
props: RuntimeHostProps
|
|
119
|
+
_children: CommittedVNode[]
|
|
86
120
|
_dom: Element
|
|
87
|
-
|
|
121
|
+
_mixedProps: RuntimeHostProps
|
|
122
|
+
_mixState?: MixinRuntimeState
|
|
123
|
+
_directEventDescriptors?: OnMixinDescriptor[]
|
|
124
|
+
_directEventState?: DirectEventState
|
|
125
|
+
_controlledState?: ControlledReflectionState
|
|
126
|
+
_persistence?: HostPersistenceState
|
|
88
127
|
}
|
|
89
128
|
|
|
90
|
-
export type
|
|
91
|
-
|
|
92
|
-
props: ElementProps
|
|
93
|
-
_handle: ComponentHandle
|
|
129
|
+
export type CommittedFragmentNode = CommittedNodeBase<'fragment', typeof Fragment> & {
|
|
130
|
+
_children: CommittedVNode[]
|
|
94
131
|
}
|
|
95
132
|
|
|
96
|
-
export type
|
|
97
|
-
|
|
98
|
-
props: ElementProps
|
|
99
|
-
_content: VNode
|
|
133
|
+
export type MountingComponentNode = CommittedNodeBase<'component', ElementFunction> & {
|
|
134
|
+
props: RuntimeElementProps
|
|
100
135
|
_handle: ComponentHandle
|
|
136
|
+
_context: ReconcileContext
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type CommittedComponentNode = MountingComponentNode & {
|
|
140
|
+
_content: CommittedVNode
|
|
101
141
|
}
|
|
102
142
|
|
|
103
|
-
export
|
|
104
|
-
|
|
143
|
+
export interface FrameFallbackRoot {
|
|
144
|
+
render(element: RemixNode): void
|
|
145
|
+
dispose(): void
|
|
105
146
|
}
|
|
106
147
|
|
|
107
|
-
export
|
|
108
|
-
|
|
148
|
+
export type FrameMountState = {
|
|
149
|
+
instance: FrameInstance
|
|
150
|
+
fallbackRoot?: FrameFallbackRoot
|
|
151
|
+
resolveToken: number
|
|
152
|
+
resolveController?: AbortController
|
|
153
|
+
resolved: boolean
|
|
109
154
|
}
|
|
110
155
|
|
|
111
|
-
export
|
|
112
|
-
|
|
156
|
+
export type CommittedFrameNode = CommittedNodeBase<'frame', typeof Frame> & {
|
|
157
|
+
props: RuntimeElementProps & FrameProps
|
|
158
|
+
_rangeStart: Comment
|
|
159
|
+
_rangeEnd: Comment
|
|
160
|
+
_state: FrameMountState
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type CommittedVNode =
|
|
164
|
+
| CommittedNonRenderNode
|
|
165
|
+
| CommittedTextNode
|
|
166
|
+
| CommittedFragmentNode
|
|
167
|
+
| CommittedHostNode
|
|
168
|
+
| CommittedComponentNode
|
|
169
|
+
| CommittedFrameNode
|
|
170
|
+
|
|
171
|
+
export type RootVNode = {
|
|
172
|
+
kind: 'root'
|
|
173
|
+
type: typeof ROOT_VNODE
|
|
174
|
+
_children: CommittedVNode[]
|
|
175
|
+
_svg: boolean
|
|
176
|
+
_rangeStart?: Node
|
|
177
|
+
_rangeEnd?: Node
|
|
178
|
+
_pendingHydrationComponentId?: string
|
|
113
179
|
}
|
|
114
180
|
|
|
115
|
-
export
|
|
116
|
-
|
|
181
|
+
export interface ReconcileContext {
|
|
182
|
+
frame: FrameHandle
|
|
183
|
+
scheduler: Scheduler
|
|
184
|
+
styles: StyleManager
|
|
185
|
+
rootTarget: EventTarget
|
|
117
186
|
}
|
|
118
187
|
|
|
119
|
-
export
|
|
120
|
-
|
|
188
|
+
export type VNodeParent =
|
|
189
|
+
| RootVNode
|
|
190
|
+
| CommittedFragmentNode
|
|
191
|
+
| CommittedHostNode
|
|
192
|
+
| MountingComponentNode
|
|
193
|
+
| CommittedComponentNode
|
|
194
|
+
|
|
195
|
+
export type VNode = VNodeInput | CommittedVNode | RootVNode | MountingComponentNode
|
|
196
|
+
|
|
197
|
+
export function isFragmentNode(node: VNode): node is FragmentNode | CommittedFragmentNode {
|
|
198
|
+
return node.kind === 'fragment'
|
|
121
199
|
}
|
|
122
200
|
|
|
123
|
-
export function
|
|
124
|
-
return
|
|
201
|
+
export function isTextNode(node: VNode): node is TextNode | CommittedTextNode {
|
|
202
|
+
return node.kind === 'text'
|
|
125
203
|
}
|
|
126
204
|
|
|
127
|
-
export function
|
|
128
|
-
return
|
|
205
|
+
export function isNonRenderNode(node: VNode): node is NonRenderNode | CommittedNonRenderNode {
|
|
206
|
+
return node.kind === 'empty'
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function isCommittedTextNode(node: CommittedVNode): node is CommittedTextNode {
|
|
210
|
+
return node.kind === 'text'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function isHostNode(node: VNode): node is HostNode | CommittedHostNode {
|
|
214
|
+
return node.kind === 'host'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function isCommittedHostNode(node: CommittedVNode): node is CommittedHostNode {
|
|
218
|
+
return node.kind === 'host'
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function isComponentNode(
|
|
222
|
+
node: VNode,
|
|
223
|
+
): node is ComponentNode | MountingComponentNode | CommittedComponentNode {
|
|
224
|
+
return node.kind === 'component'
|
|
129
225
|
}
|
|
130
226
|
|
|
131
227
|
export function isCommittedComponentNode(node: VNode): node is CommittedComponentNode {
|
|
132
|
-
return
|
|
228
|
+
return node.kind === 'component' && '_content' in node
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function isFrameNode(node: VNode): node is FrameNode | CommittedFrameNode {
|
|
232
|
+
return node.kind === 'frame'
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function isCommittedFrameNode(node: CommittedVNode): node is CommittedFrameNode {
|
|
236
|
+
return node.kind === 'frame'
|
|
133
237
|
}
|
|
134
238
|
|
|
135
|
-
export function findContextFromAncestry(node:
|
|
136
|
-
let current:
|
|
239
|
+
export function findContextFromAncestry(node: VNodeParent, type: ElementFunction): unknown {
|
|
240
|
+
let current: VNodeParent | undefined = node
|
|
137
241
|
while (current) {
|
|
138
|
-
if (current.
|
|
242
|
+
if (current.kind === 'component' && current.type === type) {
|
|
139
243
|
return current._handle.getContextValue()
|
|
140
244
|
}
|
|
141
|
-
current = current._parent
|
|
245
|
+
current = '_parent' in current ? current._parent : undefined
|
|
142
246
|
}
|
|
143
247
|
return undefined
|
|
144
248
|
}
|
package/src/select/index.tsx
CHANGED
|
@@ -30,10 +30,10 @@ export type SelectOptionProps = Props<'div'> & {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const selectTriggerShadow =
|
|
33
|
-
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(0, 0, 0, 0.12)'
|
|
33
|
+
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.2))'
|
|
34
34
|
|
|
35
35
|
const selectTriggerFocusShadow =
|
|
36
|
-
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px #3573F6, 0 0 0 4px rgba(53, 115, 246, 0.1), 0 6px 32px 4px rgba(53, 115, 246, 0.08), inset 0 0 8px 1px rgba(53, 115, 246, 0.05)'
|
|
36
|
+
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px light-dark(#3573F6, #6eaaff), 0 0 0 4px light-dark(rgba(53, 115, 246, 0.1), rgba(110, 170, 255, 0.18)), 0 6px 32px 4px light-dark(rgba(53, 115, 246, 0.08), rgba(110, 170, 255, 0.14)), inset 0 0 8px 1px light-dark(rgba(53, 115, 246, 0.05), rgba(110, 170, 255, 0.1))'
|
|
37
37
|
|
|
38
38
|
const selectTriggerCss: CSSMixinDescriptor = css({
|
|
39
39
|
appearance: 'none',
|
|
@@ -51,9 +51,9 @@ const selectTriggerCss: CSSMixinDescriptor = css({
|
|
|
51
51
|
gap: '6px',
|
|
52
52
|
border: 0,
|
|
53
53
|
borderRadius: '8px',
|
|
54
|
-
background: '#FFFFFF',
|
|
54
|
+
background: 'light-dark(#FFFFFF, #1a1a1a)',
|
|
55
55
|
boxShadow: selectTriggerShadow,
|
|
56
|
-
color: '#101010',
|
|
56
|
+
color: 'light-dark(#101010, #ececec)',
|
|
57
57
|
fontFamily: '"Inter Variable", Inter, ui-sans-serif, system-ui, sans-serif',
|
|
58
58
|
fontStyle: 'normal',
|
|
59
59
|
fontWeight: 400,
|
|
@@ -62,15 +62,15 @@ const selectTriggerCss: CSSMixinDescriptor = css({
|
|
|
62
62
|
fontFeatureSettings: '"ss01" on, "cv01" on',
|
|
63
63
|
letterSpacing: 0,
|
|
64
64
|
textAlign: 'left',
|
|
65
|
-
textShadow: '0 1px 0 #FFFFFF',
|
|
65
|
+
textShadow: '0 1px 0 light-dark(#FFFFFF, rgb(0 0 0 / 0.35))',
|
|
66
66
|
whiteSpace: 'nowrap',
|
|
67
67
|
'&:hover, &:focus-visible, &[aria-expanded="true"], &[aria-expanded="true"]:hover, &[aria-expanded="true"]:focus-visible':
|
|
68
68
|
{
|
|
69
|
-
background: '#FFFFFF',
|
|
70
|
-
color: '#101010',
|
|
69
|
+
background: 'light-dark(#FFFFFF, #1a1a1a)',
|
|
70
|
+
color: 'light-dark(#101010, #ececec)',
|
|
71
71
|
},
|
|
72
72
|
'&:active': {
|
|
73
|
-
background: '#FFFFFF',
|
|
73
|
+
background: 'light-dark(#FFFFFF, #1a1a1a)',
|
|
74
74
|
},
|
|
75
75
|
'&:focus-visible': {
|
|
76
76
|
outline: 0,
|
|
@@ -99,7 +99,7 @@ const triggerIconCss: CSSMixinDescriptor = css({
|
|
|
99
99
|
justifyContent: 'center',
|
|
100
100
|
width: '16px',
|
|
101
101
|
height: '16px',
|
|
102
|
-
color: '#707070',
|
|
102
|
+
color: 'light-dark(#707070, #b3b3b3)',
|
|
103
103
|
flex: 'none',
|
|
104
104
|
'& > svg': {
|
|
105
105
|
display: 'block',
|
package/src/server/stream.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComponentHandle, FrameHandle, Key, RemixNode } from '../runtime/component.ts'
|
|
2
2
|
import type { ElementType, ElementProps, RemixElement } from '../runtime/jsx.ts'
|
|
3
|
+
import type { ElementFunction } from '../runtime/element-function.ts'
|
|
3
4
|
import { Fragment, createComponent, createFrameHandle, Frame } from '../runtime/component.ts'
|
|
4
5
|
import { isEntry, type EntryComponent } from '../runtime/client-entries.ts'
|
|
5
6
|
import {
|
|
@@ -89,7 +90,6 @@ interface RenderContext {
|
|
|
89
90
|
onError: (error: unknown) => void
|
|
90
91
|
parentVNode?: VNode
|
|
91
92
|
styleCache: Map<string, { selector: string; css: string }>
|
|
92
|
-
emittedStyles: Set<string>
|
|
93
93
|
resolveFrame: (
|
|
94
94
|
src: string,
|
|
95
95
|
target?: string,
|
|
@@ -203,7 +203,6 @@ export function renderToStream(
|
|
|
203
203
|
onError,
|
|
204
204
|
resolveFrame: options?.resolveFrame ?? defaultResolveFrame,
|
|
205
205
|
styleCache: new Map(),
|
|
206
|
-
emittedStyles: new Set(),
|
|
207
206
|
pendingFrames: [],
|
|
208
207
|
hydrationData: new Map(),
|
|
209
208
|
unresolvedHydrationData: new Map(),
|
|
@@ -436,7 +435,7 @@ function buildSegment(node: RemixNode, context: RenderContext, frameState: SsrFr
|
|
|
436
435
|
return buildElementSegment(tag, props, context, frameState)
|
|
437
436
|
}
|
|
438
437
|
|
|
439
|
-
if (
|
|
438
|
+
if (isElementFunction(type)) {
|
|
440
439
|
if (type === Frame) {
|
|
441
440
|
return buildFrameSegment(node, context, frameState)
|
|
442
441
|
}
|
|
@@ -788,8 +787,8 @@ function sanitizeReturnedSsrMixinProps(props: ElementProps): ElementProps {
|
|
|
788
787
|
|
|
789
788
|
function resolveReturnedSsrMixDescriptors(
|
|
790
789
|
value: unknown,
|
|
791
|
-
): Array<{ type:
|
|
792
|
-
let descriptors: Array<{ type:
|
|
790
|
+
): Array<{ type: ElementFunction; args: unknown[] }> | null {
|
|
791
|
+
let descriptors: Array<{ type: ElementFunction; args: unknown[] }> = []
|
|
793
792
|
if (!collectReturnedSsrMixDescriptors(value, descriptors)) {
|
|
794
793
|
return null
|
|
795
794
|
}
|
|
@@ -799,7 +798,7 @@ function resolveReturnedSsrMixDescriptors(
|
|
|
799
798
|
|
|
800
799
|
function collectReturnedSsrMixDescriptors(
|
|
801
800
|
value: unknown,
|
|
802
|
-
output: Array<{ type:
|
|
801
|
+
output: Array<{ type: ElementFunction; args: unknown[] }>,
|
|
803
802
|
): boolean {
|
|
804
803
|
if (!value) {
|
|
805
804
|
return true
|
|
@@ -829,7 +828,11 @@ function isSsrMixinElement(
|
|
|
829
828
|
return '__rmxMixinElementType' in value
|
|
830
829
|
}
|
|
831
830
|
|
|
832
|
-
function
|
|
831
|
+
function isElementFunction(value: unknown): value is ElementFunction {
|
|
832
|
+
return typeof value === 'function'
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function isSsrMixinDescriptor(value: unknown): value is { type: ElementFunction; args: unknown[] } {
|
|
833
836
|
if (!value || typeof value !== 'object' || isRemixElement(value)) {
|
|
834
837
|
return false
|
|
835
838
|
}
|
|
@@ -839,7 +842,7 @@ function isSsrMixinDescriptor(value: unknown): value is { type: Function; args:
|
|
|
839
842
|
}
|
|
840
843
|
|
|
841
844
|
function buildComponentSegment(
|
|
842
|
-
type:
|
|
845
|
+
type: ElementFunction,
|
|
843
846
|
props: any,
|
|
844
847
|
context: RenderContext,
|
|
845
848
|
componentId: string,
|
|
@@ -923,7 +926,7 @@ function createHydrationPropsReplacer(context: RenderContext, frameState: SsrFra
|
|
|
923
926
|
}
|
|
924
927
|
|
|
925
928
|
// Component function: render synchronously, then unwrap its result
|
|
926
|
-
if (
|
|
929
|
+
if (isElementFunction(type)) {
|
|
927
930
|
let vnode = createVNode(type, props)
|
|
928
931
|
if (context.parentVNode) {
|
|
929
932
|
vnode._parent = context.parentVNode
|
|
@@ -1180,8 +1183,6 @@ function finalizeHtml(html: string, context: RenderContext): string {
|
|
|
1180
1183
|
}
|
|
1181
1184
|
}
|
|
1182
1185
|
|
|
1183
|
-
html = dedupeServerStyleTagsInHtml(html, context.emittedStyles)
|
|
1184
|
-
|
|
1185
1186
|
// Append aggregated hydration/frame data script at the end
|
|
1186
1187
|
let rmxData = buildRmxDataScript(context)
|
|
1187
1188
|
if (rmxData) {
|
|
@@ -1252,24 +1253,12 @@ function renderStyleTag(
|
|
|
1252
1253
|
): string {
|
|
1253
1254
|
let wrappedCss = wrapStyleForLayer(selector, css, layer)
|
|
1254
1255
|
if (!wrappedCss) return ''
|
|
1255
|
-
return `<style data-rmx="${escapeHtml(selector)}">${wrappedCss}</style>`
|
|
1256
|
+
return `<style data-rmx="${escapeHtml(selector)}">${escapeStyleText(wrappedCss)}</style>`
|
|
1256
1257
|
}
|
|
1257
1258
|
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
return match[1] ?? match[2] ?? null
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
function dedupeServerStyleTagsInHtml(html: string, seenStyles: Set<string>): string {
|
|
1265
|
-
return html.replace(/<style\b([^>]*)>[\s\S]*?<\/style>/gi, (match, attrs) => {
|
|
1266
|
-
let selector = readStyleTagAttribute(attrs, 'data-rmx')
|
|
1267
|
-
if (!selector) return match
|
|
1268
|
-
|
|
1269
|
-
if (seenStyles.has(selector)) return ''
|
|
1270
|
-
seenStyles.add(selector)
|
|
1271
|
-
return match
|
|
1272
|
-
})
|
|
1259
|
+
function escapeStyleText(css: string): string {
|
|
1260
|
+
// A literal "</style" closes an HTML style element even when it appears inside a CSS string.
|
|
1261
|
+
return css.replace(/</g, '\\3C ')
|
|
1273
1262
|
}
|
|
1274
1263
|
|
|
1275
1264
|
function buildRmxDataScript(context: RenderContext): string {
|
|
@@ -1303,6 +1292,12 @@ function escapeScriptJson(json: string): string {
|
|
|
1303
1292
|
// the handler's `finalizeHtml` emits selector-addressed `<style>` tags in its HTML, and on the client,
|
|
1304
1293
|
// the `adoptServerStyleTag` MutationObserver (stylesheet.ts) picks it up anywhere in the
|
|
1305
1294
|
// document and adopts the CSS into an adopted stylesheet.
|
|
1295
|
+
//
|
|
1296
|
+
// Style tags are intentionally NOT deduped across frame boundaries: each frame
|
|
1297
|
+
// owns its style rules independently on the client (per-frame refcounted
|
|
1298
|
+
// adoption), so every frame's HTML must carry the full set of style tags its
|
|
1299
|
+
// content references — even when a sibling frame or the enclosing document
|
|
1300
|
+
// already emitted the same selector.
|
|
1306
1301
|
async function streamPendingFrames(
|
|
1307
1302
|
context: RenderContext,
|
|
1308
1303
|
controller: ReadableStreamDefaultController,
|
|
@@ -1323,7 +1318,6 @@ async function streamPendingFrames(
|
|
|
1323
1318
|
try {
|
|
1324
1319
|
let { html, tail } = await promise
|
|
1325
1320
|
if (context.signal.aborted) return
|
|
1326
|
-
html = dedupeServerStyleTagsInHtml(html, context.emittedStyles)
|
|
1327
1321
|
|
|
1328
1322
|
// Stream as a template element (first chunk only)
|
|
1329
1323
|
let templateHtml = `<template id="${frameId}">${escapeTemplateContent(html)}</template>`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const controlFocusShadow =
|
|
2
|
-
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px #3573F6, 0 0 0 4px rgba(53, 115, 246, 0.1), 0 6px 32px 4px rgba(53, 115, 246, 0.08), inset 0 0 8px 1px rgba(53, 115, 246, 0.05)'
|
|
2
|
+
'0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px light-dark(#3573F6, #6eaaff), 0 0 0 4px light-dark(rgba(53, 115, 246, 0.1), rgba(110, 170, 255, 0.18)), 0 6px 32px 4px light-dark(rgba(53, 115, 246, 0.08), rgba(110, 170, 255, 0.14)), inset 0 0 8px 1px light-dark(rgba(53, 115, 246, 0.05), rgba(110, 170, 255, 0.1))'
|
|
@@ -114,11 +114,11 @@ export const componentStyleValues: ComponentStyleValues = {
|
|
|
114
114
|
},
|
|
115
115
|
},
|
|
116
116
|
surface: {
|
|
117
|
-
lvl0: '#ffffff',
|
|
118
|
-
lvl1: '#f8f8f8',
|
|
119
|
-
lvl2: '#f5f5f5',
|
|
120
|
-
lvl3: '#f3f3f3',
|
|
121
|
-
lvl4: '#efefef',
|
|
117
|
+
lvl0: 'light-dark(#ffffff, #1a1a1a)',
|
|
118
|
+
lvl1: 'light-dark(#f8f8f8, #1f1f1f)',
|
|
119
|
+
lvl2: 'light-dark(#f5f5f5, #232323)',
|
|
120
|
+
lvl3: 'light-dark(#f3f3f3, #272727)',
|
|
121
|
+
lvl4: 'light-dark(#efefef, #2c2c2c)',
|
|
122
122
|
},
|
|
123
123
|
shadow: {
|
|
124
124
|
xs: '0 1px 1px rgb(0 0 0 / 0.05)',
|
|
@@ -127,38 +127,38 @@ export const componentStyleValues: ComponentStyleValues = {
|
|
|
127
127
|
},
|
|
128
128
|
colors: {
|
|
129
129
|
text: {
|
|
130
|
-
primary: '#151515',
|
|
131
|
-
secondary: '#4f4f4f',
|
|
132
|
-
muted: '#6d6d6d',
|
|
130
|
+
primary: 'light-dark(#151515, #ececec)',
|
|
131
|
+
secondary: 'light-dark(#4f4f4f, #b3b3b3)',
|
|
132
|
+
muted: 'light-dark(#6d6d6d, #b3b3b3)',
|
|
133
133
|
},
|
|
134
134
|
border: {
|
|
135
|
-
subtle: '#e7e7e7',
|
|
136
|
-
default: '#d1d1d1',
|
|
135
|
+
subtle: 'light-dark(#e7e7e7, #333333)',
|
|
136
|
+
default: 'light-dark(#d1d1d1, #444444)',
|
|
137
137
|
},
|
|
138
138
|
focus: {
|
|
139
|
-
ring: '#1A72FF',
|
|
139
|
+
ring: 'light-dark(#1A72FF, #6eaaff)',
|
|
140
140
|
},
|
|
141
141
|
action: {
|
|
142
142
|
primary: {
|
|
143
|
-
background: '#1A72FF',
|
|
144
|
-
backgroundHover: '#1463e0',
|
|
145
|
-
backgroundActive: '#0f55c9',
|
|
146
|
-
foreground: 'rgb(255 255 255 / 0.92)',
|
|
147
|
-
border: '#1A72FF',
|
|
143
|
+
background: 'light-dark(#1A72FF, #6eaaff)',
|
|
144
|
+
backgroundHover: 'light-dark(#1463e0, #8bbcff)',
|
|
145
|
+
backgroundActive: 'light-dark(#0f55c9, #a7ccff)',
|
|
146
|
+
foreground: 'light-dark(rgb(255 255 255 / 0.92), #151515)',
|
|
147
|
+
border: 'light-dark(#1A72FF, #6eaaff)',
|
|
148
148
|
},
|
|
149
149
|
secondary: {
|
|
150
|
-
background: '#ffffff',
|
|
151
|
-
backgroundHover: '#fbfbfb',
|
|
152
|
-
backgroundActive: '#f3f3f3',
|
|
153
|
-
foreground: '#202020',
|
|
154
|
-
border: '#d1d1d1',
|
|
150
|
+
background: 'light-dark(#ffffff, #1a1a1a)',
|
|
151
|
+
backgroundHover: 'light-dark(#fbfbfb, #232323)',
|
|
152
|
+
backgroundActive: 'light-dark(#f3f3f3, #2c2c2c)',
|
|
153
|
+
foreground: 'light-dark(#202020, #ececec)',
|
|
154
|
+
border: 'light-dark(#d1d1d1, #444444)',
|
|
155
155
|
},
|
|
156
156
|
danger: {
|
|
157
|
-
background: '#FF3000',
|
|
158
|
-
backgroundHover: '#e12b00',
|
|
159
|
-
backgroundActive: '#c52600',
|
|
160
|
-
foreground: 'rgb(255 255 255 / 0.92)',
|
|
161
|
-
border: '#FF3000',
|
|
157
|
+
background: 'light-dark(#FF3000, #ff8a70)',
|
|
158
|
+
backgroundHover: 'light-dark(#e12b00, #ff9f8a)',
|
|
159
|
+
backgroundActive: 'light-dark(#c52600, #ffb39f)',
|
|
160
|
+
foreground: 'light-dark(rgb(255 255 255 / 0.92), #151515)',
|
|
161
|
+
border: 'light-dark(#FF3000, #ff8a70)',
|
|
162
162
|
},
|
|
163
163
|
},
|
|
164
164
|
},
|
package/src/style/style.ts
CHANGED
|
@@ -19,8 +19,22 @@ export interface CSSProps extends DOMStyleProperties {
|
|
|
19
19
|
type StyleObject = Record<string, unknown>
|
|
20
20
|
|
|
21
21
|
// Convert camelCase CSS properties to kebab-case
|
|
22
|
+
// Property names repeat constantly across renders; cache conversions instead
|
|
23
|
+
// of running a regex each time.
|
|
24
|
+
const CAMEL_TO_KEBAB_CACHE_LIMIT = 256
|
|
25
|
+
const camelToKebabCache = new Map<string, string>()
|
|
26
|
+
|
|
22
27
|
function camelToKebab(str: string): string {
|
|
23
|
-
|
|
28
|
+
let cached = camelToKebabCache.get(str)
|
|
29
|
+
if (cached === undefined) {
|
|
30
|
+
cached = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
31
|
+
if (camelToKebabCache.size >= CAMEL_TO_KEBAB_CACHE_LIMIT) {
|
|
32
|
+
let oldest = camelToKebabCache.keys().next()
|
|
33
|
+
if (!oldest.done) camelToKebabCache.delete(oldest.value)
|
|
34
|
+
}
|
|
35
|
+
camelToKebabCache.set(str, cached)
|
|
36
|
+
}
|
|
37
|
+
return cached
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
// Properties that should remain unitless (numeric values without px)
|
|
@@ -81,18 +95,23 @@ function isKeyframesAtRule(key: string): boolean {
|
|
|
81
95
|
)
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
// Generate a hash for style objects to create unique class names
|
|
98
|
+
// Generate a hash for style objects to create unique class names.
|
|
99
|
+
// Class names are content-addressed — a collision silently applies the wrong
|
|
100
|
+
// styles — so use two independent 32-bit FNV-1a passes (~64 bits of hash
|
|
101
|
+
// space) rather than a single 32-bit hash. Must be deterministic across
|
|
102
|
+
// server and client.
|
|
85
103
|
function hashStyle(obj: any): string {
|
|
86
104
|
// Sort keys to ensure consistent hashing, but include values in the string
|
|
87
105
|
let sortedEntries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))
|
|
88
106
|
let str = JSON.stringify(sortedEntries)
|
|
89
|
-
let
|
|
107
|
+
let h1 = 0x811c9dc5
|
|
108
|
+
let h2 = 0xcbf29ce4
|
|
90
109
|
for (let i = 0; i < str.length; i++) {
|
|
91
110
|
let char = str.charCodeAt(i)
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
h1 = Math.imul(h1 ^ char, 0x01000193) >>> 0
|
|
112
|
+
h2 = Math.imul(h2 ^ char, 0x01000193) >>> 0
|
|
94
113
|
}
|
|
95
|
-
return
|
|
114
|
+
return h1.toString(36) + h2.toString(36)
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
// Convert style object to CSS text
|