@remix-run/ui 0.1.2 → 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 +9 -7
- 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 +6 -6
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
- 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/combobox/combobox.d.ts +10 -10
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/listbox/listbox.d.ts +3 -3
- package/dist/components/listbox/listbox.js.map +1 -1
- package/dist/components/menu/menu.d.ts +12 -12
- package/dist/components/menu/menu.js.map +1 -1
- package/dist/components/popover/popover.d.ts +5 -5
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/select/select.d.ts +10 -10
- 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/component.d.ts +18 -15
- package/dist/runtime/component.js +14 -5
- 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/dom.d.ts +16 -9
- package/dist/runtime/jsx.d.ts +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 +79 -9
- 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/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 +3 -3
- package/src/animation/README.md +7 -5
- package/src/animation/spring.ts +21 -9
- package/src/components/accordion/accordion.tsx +7 -5
- package/src/components/anchor/anchor.ts +1 -1
- package/src/components/breadcrumbs/breadcrumbs.tsx +1 -1
- package/src/components/button/button.tsx +30 -18
- package/src/components/combobox/README.md +1 -1
- package/src/components/combobox/combobox.tsx +30 -12
- package/src/components/glyph/glyph.tsx +2 -2
- package/src/components/listbox/listbox.ts +88 -83
- package/src/components/menu/menu.tsx +27 -15
- package/src/components/popover/popover.ts +90 -79
- package/src/components/select/select.tsx +26 -9
- package/src/index.ts +1 -0
- package/src/runtime/component.ts +34 -12
- package/src/runtime/core/attributes.ts +21 -0
- package/src/runtime/core/props.ts +6 -4
- package/src/runtime/dom.ts +16 -9
- package/src/runtime/jsx.ts +1 -1
- 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 +77 -8
- package/src/style/css-mixin.ts +6 -1
- package/src/style/index.ts +1 -2
- 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
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ElementProps,
|
|
11
11
|
type Handle,
|
|
12
12
|
type MixinHandle,
|
|
13
|
+
type MixinFactory,
|
|
13
14
|
type Props,
|
|
14
15
|
type RemixNode,
|
|
15
16
|
} from '@remix-run/ui'
|
|
@@ -106,7 +107,7 @@ interface SelectContextValue {
|
|
|
106
107
|
unregisterTrigger: (node: HTMLButtonElement) => void
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>) {
|
|
110
|
+
function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>): () => RemixNode {
|
|
110
111
|
let triggerRef: HTMLButtonElement | undefined
|
|
111
112
|
let listboxRef: listbox.ListboxRef | undefined
|
|
112
113
|
let surfaceRef: HTMLElement | undefined
|
|
@@ -396,7 +397,11 @@ function getSelectContext(handle: Handle | MixinHandle) {
|
|
|
396
397
|
return handle.context.get(SelectProvider)
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
const triggerMixin
|
|
400
|
+
const triggerMixin: MixinFactory<HTMLButtonElement, [], ElementProps> = createMixin<
|
|
401
|
+
HTMLButtonElement,
|
|
402
|
+
[],
|
|
403
|
+
ElementProps
|
|
404
|
+
>((handle) => {
|
|
400
405
|
let context = getSelectContext(handle)
|
|
401
406
|
|
|
402
407
|
return (props) => [
|
|
@@ -428,7 +433,11 @@ const triggerMixin = createMixin<HTMLButtonElement, [], ElementProps>((handle) =
|
|
|
428
433
|
]
|
|
429
434
|
})
|
|
430
435
|
|
|
431
|
-
const popoverMixin
|
|
436
|
+
const popoverMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
437
|
+
HTMLElement,
|
|
438
|
+
[],
|
|
439
|
+
ElementProps
|
|
440
|
+
>((handle) => {
|
|
432
441
|
let context = getSelectContext(handle)
|
|
433
442
|
let popoverState = handle.context.get(popover.Context)
|
|
434
443
|
|
|
@@ -453,7 +462,11 @@ const popoverMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
453
462
|
]
|
|
454
463
|
})
|
|
455
464
|
|
|
456
|
-
const listMixin
|
|
465
|
+
const listMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
466
|
+
HTMLElement,
|
|
467
|
+
[],
|
|
468
|
+
ElementProps
|
|
469
|
+
>((handle) => {
|
|
457
470
|
let context = getSelectContext(handle)
|
|
458
471
|
|
|
459
472
|
return () => [
|
|
@@ -466,7 +479,11 @@ const listMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
466
479
|
]
|
|
467
480
|
})
|
|
468
481
|
|
|
469
|
-
const hiddenInputMixin
|
|
482
|
+
const hiddenInputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = createMixin<
|
|
483
|
+
HTMLInputElement,
|
|
484
|
+
[],
|
|
485
|
+
ElementProps
|
|
486
|
+
>((handle) => {
|
|
470
487
|
let context = getSelectContext(handle)
|
|
471
488
|
|
|
472
489
|
return () =>
|
|
@@ -532,17 +549,17 @@ const select = {
|
|
|
532
549
|
export function onSelectChange<target extends HTMLElement>(
|
|
533
550
|
handler: SelectChangeHandler<target>,
|
|
534
551
|
captureBoolean?: boolean,
|
|
535
|
-
) {
|
|
552
|
+
): ReturnType<typeof on<target, typeof SELECT_CHANGE_EVENT>> {
|
|
536
553
|
return on(SELECT_CHANGE_EVENT, handler, captureBoolean)
|
|
537
554
|
}
|
|
538
555
|
|
|
539
|
-
function SelectLabel(handle: Handle) {
|
|
556
|
+
function SelectLabel(handle: Handle): () => RemixNode {
|
|
540
557
|
let context = getSelectContext(handle)
|
|
541
558
|
|
|
542
559
|
return () => <span mix={button.labelStyle}>{context.displayedLabel}</span>
|
|
543
560
|
}
|
|
544
561
|
|
|
545
|
-
export function Select(handle: Handle<SelectProps>) {
|
|
562
|
+
export function Select(handle: Handle<SelectProps>): () => RemixNode {
|
|
546
563
|
return () => {
|
|
547
564
|
let { children, defaultLabel, defaultValue, disabled, name, mix, ...buttonProps } = handle.props
|
|
548
565
|
|
|
@@ -568,7 +585,7 @@ export function Select(handle: Handle<SelectProps>) {
|
|
|
568
585
|
}
|
|
569
586
|
}
|
|
570
587
|
|
|
571
|
-
export function Option(handle: Handle<SelectOptionProps>) {
|
|
588
|
+
export function Option(handle: Handle<SelectOptionProps>): () => RemixNode {
|
|
572
589
|
return () => {
|
|
573
590
|
let { label, value, disabled, textValue, children, mix, ...divProps } = handle.props
|
|
574
591
|
|
package/src/index.ts
CHANGED
package/src/runtime/component.ts
CHANGED
|
@@ -118,13 +118,18 @@ export type ContextFrom<ComponentType> =
|
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
120
|
* Context storage API exposed on component handles.
|
|
121
|
+
*
|
|
122
|
+
* Context values are keyed by provider component identity. `get(Component)`
|
|
123
|
+
* reads the nearest ancestor instance whose component function is exactly
|
|
124
|
+
* `Component`, so nested instances of the same provider shadow outer instances
|
|
125
|
+
* while different component types remain independent.
|
|
121
126
|
*/
|
|
122
127
|
export interface Context<C> {
|
|
123
128
|
/** Replaces the current context value for this component instance. */
|
|
124
129
|
set(values: C): void
|
|
125
|
-
/** Reads the context value
|
|
130
|
+
/** Reads the context value from the nearest ancestor instance of the given component type. */
|
|
126
131
|
get<ComponentType>(component: ComponentType): ContextFrom<ComponentType>
|
|
127
|
-
/** Reads
|
|
132
|
+
/** Reads an unknown context value for an untyped lookup. */
|
|
128
133
|
get(component: ElementType | symbol): unknown | undefined
|
|
129
134
|
}
|
|
130
135
|
|
|
@@ -218,7 +223,14 @@ type ComponentConfig = {
|
|
|
218
223
|
/**
|
|
219
224
|
* Runtime handle returned by {@link createComponent}.
|
|
220
225
|
*/
|
|
221
|
-
export
|
|
226
|
+
export interface ComponentHandle<C = NoContext> {
|
|
227
|
+
frame: FrameHandle
|
|
228
|
+
render(nextProps: ElementProps): [RemixNode, Array<() => void>]
|
|
229
|
+
remove(): Array<() => void>
|
|
230
|
+
setScheduleUpdate(nextScheduleUpdate: () => void): void
|
|
231
|
+
getContextValue(): C | undefined
|
|
232
|
+
isRemoved(): boolean
|
|
233
|
+
}
|
|
222
234
|
|
|
223
235
|
/**
|
|
224
236
|
* Creates the internal runtime wrapper for a component instance.
|
|
@@ -226,11 +238,11 @@ export type ComponentHandle = ReturnType<typeof createComponent>
|
|
|
226
238
|
* @param config Component runtime configuration.
|
|
227
239
|
* @returns Component runtime helpers used by the reconciler.
|
|
228
240
|
*/
|
|
229
|
-
export function createComponent<C = NoContext>(config: ComponentConfig) {
|
|
241
|
+
export function createComponent<C = NoContext>(config: ComponentConfig): ComponentHandle<C> {
|
|
230
242
|
return new ComponentRuntime<C>(config)
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
class ComponentRuntime<C = NoContext> {
|
|
245
|
+
class ComponentRuntime<C = NoContext> implements ComponentHandle<C> {
|
|
234
246
|
frame: FrameHandle
|
|
235
247
|
|
|
236
248
|
#config: ComponentConfig
|
|
@@ -240,6 +252,7 @@ class ComponentRuntime<C = NoContext> {
|
|
|
240
252
|
#props = {} as ElementProps
|
|
241
253
|
#renderController: AbortController | undefined
|
|
242
254
|
#renderFn: RenderFn | undefined
|
|
255
|
+
#removed = false
|
|
243
256
|
#scheduleUpdate: () => void = () => {
|
|
244
257
|
throw new Error('scheduleUpdate not implemented')
|
|
245
258
|
}
|
|
@@ -252,7 +265,7 @@ class ComponentRuntime<C = NoContext> {
|
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
render = (nextProps: ElementProps): [RemixNode, Array<() => void>] => {
|
|
255
|
-
if (this.#
|
|
268
|
+
if (this.#removed) {
|
|
256
269
|
console.warn('render called after component was removed, potential application memory leak')
|
|
257
270
|
return [null, []]
|
|
258
271
|
}
|
|
@@ -278,9 +291,11 @@ class ComponentRuntime<C = NoContext> {
|
|
|
278
291
|
}
|
|
279
292
|
|
|
280
293
|
remove = (): Array<() => void> => {
|
|
294
|
+
if (this.#removed) return []
|
|
295
|
+
this.#removed = true
|
|
281
296
|
this.#connectedController?.abort()
|
|
282
297
|
this.#abortRenderSignal()
|
|
283
|
-
return this.#dequeueTasks()
|
|
298
|
+
return this.#dequeueTasks(AbortSignal.abort())
|
|
284
299
|
}
|
|
285
300
|
|
|
286
301
|
setScheduleUpdate = (nextScheduleUpdate: () => void): void => {
|
|
@@ -289,6 +304,8 @@ class ComponentRuntime<C = NoContext> {
|
|
|
289
304
|
|
|
290
305
|
getContextValue = (): C | undefined => this.#contextValue
|
|
291
306
|
|
|
307
|
+
isRemoved = (): boolean => this.#removed
|
|
308
|
+
|
|
292
309
|
#createHandle(): Handle<ElementProps, C> {
|
|
293
310
|
let component = this
|
|
294
311
|
let context: Context<C> = {
|
|
@@ -303,6 +320,11 @@ class ComponentRuntime<C = NoContext> {
|
|
|
303
320
|
props: this.#props,
|
|
304
321
|
update: () =>
|
|
305
322
|
new Promise((resolve) => {
|
|
323
|
+
if (component.#removed) {
|
|
324
|
+
resolve(AbortSignal.abort())
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
|
|
306
328
|
this.#tasks.push((signal) => resolve(signal))
|
|
307
329
|
this.#scheduleUpdate()
|
|
308
330
|
}),
|
|
@@ -335,14 +357,14 @@ class ComponentRuntime<C = NoContext> {
|
|
|
335
357
|
this.#renderController = undefined
|
|
336
358
|
}
|
|
337
359
|
|
|
338
|
-
#dequeueTasks(): Array<() => void> {
|
|
339
|
-
let needsSignal = this.#tasks.some((task) => task.length >= 1)
|
|
360
|
+
#dequeueTasks(signal?: AbortSignal): Array<() => void> {
|
|
361
|
+
let needsSignal = signal === undefined && this.#tasks.some((task) => task.length >= 1)
|
|
340
362
|
|
|
341
363
|
if (needsSignal) {
|
|
342
364
|
this.#renderController ??= new AbortController()
|
|
343
365
|
}
|
|
344
366
|
|
|
345
|
-
|
|
367
|
+
signal ??= this.#renderController?.signal
|
|
346
368
|
let tasks = this.#tasks.splice(0, this.#tasks.length)
|
|
347
369
|
return tasks.map((task) => () => task(signal!))
|
|
348
370
|
}
|
|
@@ -366,7 +388,7 @@ function syncProps(target: ElementProps, next: ElementProps): void {
|
|
|
366
388
|
* @param handle Component handle for the frame instance.
|
|
367
389
|
* @returns A placeholder render function handled by the reconciler.
|
|
368
390
|
*/
|
|
369
|
-
export function Frame(handle: Handle<FrameProps, FrameHandle>) {
|
|
391
|
+
export function Frame(handle: Handle<FrameProps, FrameHandle>): RenderFn {
|
|
370
392
|
void handle
|
|
371
393
|
return () => null // reconciler renders
|
|
372
394
|
}
|
|
@@ -377,7 +399,7 @@ export function Frame(handle: Handle<FrameProps, FrameHandle>) {
|
|
|
377
399
|
* @param handle Component handle for the fragment instance.
|
|
378
400
|
* @returns A placeholder render function handled by the reconciler.
|
|
379
401
|
*/
|
|
380
|
-
export function Fragment(handle: Handle<FragmentProps>) {
|
|
402
|
+
export function Fragment(handle: Handle<FragmentProps>): RenderFn {
|
|
381
403
|
void handle
|
|
382
404
|
return () => null // reconciler renders
|
|
383
405
|
}
|
|
@@ -14,6 +14,17 @@ const ATTRIBUTE_FALLBACK_NAMES = new Set([
|
|
|
14
14
|
'colSpan',
|
|
15
15
|
'role',
|
|
16
16
|
'popover',
|
|
17
|
+
'translate',
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const BOOLEANISH_STRING_ATTRIBUTES = new Set([
|
|
21
|
+
'autoReverse',
|
|
22
|
+
'contenteditable',
|
|
23
|
+
'draggable',
|
|
24
|
+
'externalResourcesRequired',
|
|
25
|
+
'focusable',
|
|
26
|
+
'preserveAlpha',
|
|
27
|
+
'spellcheck',
|
|
17
28
|
])
|
|
18
29
|
|
|
19
30
|
export const FRAMEWORK_PROPS = new Set(['children', 'mix', 'key', 'animate', 'innerHTML', 'on'])
|
|
@@ -43,9 +54,11 @@ export function canUseProperty(
|
|
|
43
54
|
element: Element,
|
|
44
55
|
name: string,
|
|
45
56
|
isSvg: boolean,
|
|
57
|
+
attr: string,
|
|
46
58
|
): element is Element & Record<string, unknown> {
|
|
47
59
|
if (isSvg) return false
|
|
48
60
|
if (ATTRIBUTE_FALLBACK_NAMES.has(name)) return false
|
|
61
|
+
if (isBooleanishStringAttribute(attr)) return false
|
|
49
62
|
return name in element
|
|
50
63
|
}
|
|
51
64
|
|
|
@@ -68,6 +81,14 @@ export function normalizeAttributeName(
|
|
|
68
81
|
return normalizeSvgAttribute(name)
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
export function isBooleanishStringAttribute(name: string): boolean {
|
|
85
|
+
return BOOLEANISH_STRING_ATTRIBUTES.has(name)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function shouldStringifyBooleanAttribute(name: string): boolean {
|
|
89
|
+
return isBooleanishStringAttribute(name) || name === 'value'
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
export function serializeStyleObject(style: Record<string, unknown>): string {
|
|
72
93
|
let parts: string[] = []
|
|
73
94
|
|
|
@@ -2,6 +2,7 @@ import type { ElementProps } from '../jsx.ts'
|
|
|
2
2
|
import {
|
|
3
3
|
canUseProperty,
|
|
4
4
|
getMergedClassName,
|
|
5
|
+
isBooleanishStringAttribute,
|
|
5
6
|
normalizeAttributeName,
|
|
6
7
|
serializeStyleObject,
|
|
7
8
|
toKebabCase,
|
|
@@ -68,11 +69,11 @@ export function patchHostProps(curr: ElementProps, next: ElementProps, dom: Elem
|
|
|
68
69
|
if (name === 'class' || name === 'className') continue
|
|
69
70
|
if (name in next && next[name] != null) continue
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
73
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
72
74
|
clearRuntimePropertyOnRemoval(dom, name)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
let { ns, attr } = normalizeAttributeName(name, isSvg)
|
|
76
77
|
if (ns) dom.removeAttributeNS(ns, toLocalName(attr))
|
|
77
78
|
else dom.removeAttribute(attr)
|
|
78
79
|
}
|
|
@@ -114,7 +115,7 @@ function patchHostProp(dom: Element, name: string, value: unknown, isSvg: boolea
|
|
|
114
115
|
return
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
if (canUseProperty(dom, name, isSvg)) {
|
|
118
|
+
if (canUseProperty(dom, name, isSvg, attr)) {
|
|
118
119
|
try {
|
|
119
120
|
dom[name] = value == null ? '' : value
|
|
120
121
|
return
|
|
@@ -124,7 +125,8 @@ function patchHostProp(dom: Element, name: string, value: unknown, isSvg: boolea
|
|
|
124
125
|
if (typeof value === 'function') return
|
|
125
126
|
|
|
126
127
|
let isAriaOrData = name.startsWith('aria-') || name.startsWith('data-')
|
|
127
|
-
|
|
128
|
+
let isBooleanishString = isBooleanishStringAttribute(attr)
|
|
129
|
+
if (value != null && (value !== false || isAriaOrData || isBooleanishString)) {
|
|
128
130
|
let attrValue = name === 'popover' && value === true ? '' : String(value)
|
|
129
131
|
if (ns) dom.setAttributeNS(ns, attr, attrValue)
|
|
130
132
|
else dom.setAttribute(attr, attrValue)
|
package/src/runtime/dom.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { MixInput } from './mixins/mixin.ts'
|
|
|
9
9
|
* - Copyright (c) 2015-present Jason Miller
|
|
10
10
|
*/
|
|
11
11
|
type Booleanish = boolean | 'true' | 'false'
|
|
12
|
+
type TranslateValue = 'yes' | 'no'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Layout animation configuration for FLIP-based position animations.
|
|
@@ -109,6 +110,8 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
|
|
|
109
110
|
attributeName?: Trackable<string | undefined>
|
|
110
111
|
/** The `attributeType` SVG attribute. */
|
|
111
112
|
attributeType?: Trackable<string | undefined>
|
|
113
|
+
/** The `autoReverse` SVG attribute. */
|
|
114
|
+
autoReverse?: Trackable<Booleanish | undefined>
|
|
112
115
|
/** The `azimuth` SVG attribute. */
|
|
113
116
|
azimuth?: Trackable<number | string | undefined>
|
|
114
117
|
/** The `baseFrequency` SVG attribute. */
|
|
@@ -212,7 +215,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
|
|
|
212
215
|
/** The `exponent` SVG attribute. */
|
|
213
216
|
exponent?: Trackable<number | string | undefined>
|
|
214
217
|
/** The `externalResourcesRequired` SVG attribute. */
|
|
215
|
-
externalResourcesRequired?: Trackable<
|
|
218
|
+
externalResourcesRequired?: Trackable<Booleanish | undefined>
|
|
216
219
|
/** The `fill` SVG attribute. */
|
|
217
220
|
fill?: Trackable<string | undefined>
|
|
218
221
|
/** The `fillOpacity` SVG attribute. */
|
|
@@ -238,7 +241,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
|
|
|
238
241
|
/** The `flood-opacity` SVG attribute. */
|
|
239
242
|
'flood-opacity'?: Trackable<number | string | undefined>
|
|
240
243
|
/** The `focusable` SVG attribute. */
|
|
241
|
-
focusable?: Trackable<
|
|
244
|
+
focusable?: Trackable<Booleanish | undefined>
|
|
242
245
|
/** The `fontFamily` SVG attribute. */
|
|
243
246
|
fontFamily?: Trackable<string | undefined>
|
|
244
247
|
/** The `font-family` SVG attribute. */
|
|
@@ -446,7 +449,7 @@ export interface SVGProps<eventTarget extends EventTarget = SVGElement>
|
|
|
446
449
|
/** The `pointsAtZ` SVG attribute. */
|
|
447
450
|
pointsAtZ?: Trackable<number | string | undefined>
|
|
448
451
|
/** The `preserveAlpha` SVG attribute. */
|
|
449
|
-
preserveAlpha?: Trackable<
|
|
452
|
+
preserveAlpha?: Trackable<Booleanish | undefined>
|
|
450
453
|
/** The `preserveAspectRatio` SVG attribute. */
|
|
451
454
|
preserveAspectRatio?: Trackable<string | undefined>
|
|
452
455
|
/** The `primitiveUnits` SVG attribute. */
|
|
@@ -1263,7 +1266,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1263
1266
|
/** The `decoding` HTML attribute. */
|
|
1264
1267
|
decoding?: Trackable<'sync' | 'async' | 'auto' | undefined>
|
|
1265
1268
|
/** The `draggable` HTML attribute. */
|
|
1266
|
-
draggable?: Trackable<
|
|
1269
|
+
draggable?: Trackable<Booleanish | undefined>
|
|
1267
1270
|
/** The `encType` HTML attribute. */
|
|
1268
1271
|
encType?: Trackable<string | undefined>
|
|
1269
1272
|
/** The `enctype` HTML attribute. */
|
|
@@ -1487,7 +1490,9 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1487
1490
|
/** The `span` HTML attribute. */
|
|
1488
1491
|
span?: Trackable<number | undefined>
|
|
1489
1492
|
/** The `spellcheck` HTML attribute. */
|
|
1490
|
-
spellcheck?: Trackable<
|
|
1493
|
+
spellcheck?: Trackable<Booleanish | undefined>
|
|
1494
|
+
/** The `spellCheck` HTML attribute. */
|
|
1495
|
+
spellCheck?: Trackable<Booleanish | undefined>
|
|
1491
1496
|
/** The `src` HTML attribute. */
|
|
1492
1497
|
src?: Trackable<string | undefined>
|
|
1493
1498
|
/** The `srcDoc` HTML attribute. */
|
|
@@ -1551,7 +1556,7 @@ export interface AllHTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1551
1556
|
/** The `results` HTML attribute. */
|
|
1552
1557
|
results?: Trackable<number | undefined>
|
|
1553
1558
|
/** The `translate` HTML attribute. */
|
|
1554
|
-
translate?: Trackable<
|
|
1559
|
+
translate?: Trackable<TranslateValue | undefined>
|
|
1555
1560
|
|
|
1556
1561
|
// RDFa Attributes
|
|
1557
1562
|
/** The `about` HTML attribute. */
|
|
@@ -1632,7 +1637,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1632
1637
|
/** The `dir` HTML attribute. */
|
|
1633
1638
|
dir?: Trackable<'auto' | 'rtl' | 'ltr' | undefined>
|
|
1634
1639
|
/** The `draggable` HTML attribute. */
|
|
1635
|
-
draggable?: Trackable<
|
|
1640
|
+
draggable?: Trackable<Booleanish | undefined>
|
|
1636
1641
|
/** The `enterkeyhint` HTML attribute. */
|
|
1637
1642
|
enterkeyhint?: Trackable<
|
|
1638
1643
|
'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined
|
|
@@ -1662,7 +1667,9 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1662
1667
|
/** The `slot` HTML attribute. */
|
|
1663
1668
|
slot?: Trackable<string | undefined>
|
|
1664
1669
|
/** The `spellcheck` HTML attribute. */
|
|
1665
|
-
spellcheck?: Trackable<
|
|
1670
|
+
spellcheck?: Trackable<Booleanish | undefined>
|
|
1671
|
+
/** The `spellCheck` HTML attribute. */
|
|
1672
|
+
spellCheck?: Trackable<Booleanish | undefined>
|
|
1666
1673
|
/** The `style` HTML attribute. */
|
|
1667
1674
|
style?: Trackable<string | StyleProps | undefined>
|
|
1668
1675
|
/** The `tabindex` HTML attribute. */
|
|
@@ -1672,7 +1679,7 @@ export interface HTMLProps<eventTarget extends EventTarget = EventTarget>
|
|
|
1672
1679
|
/** The `title` HTML attribute. */
|
|
1673
1680
|
title?: Trackable<string | undefined>
|
|
1674
1681
|
/** The `translate` HTML attribute. */
|
|
1675
|
-
translate?: Trackable<
|
|
1682
|
+
translate?: Trackable<TranslateValue | undefined>
|
|
1676
1683
|
|
|
1677
1684
|
// WAI-ARIA Attributes
|
|
1678
1685
|
// Most elements only allow a subset of roles and so this
|
package/src/runtime/jsx.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type Renderable = RemixElement | string | number | bigint | boolean | nul
|
|
|
44
44
|
* Particularly useful for `props.children`.
|
|
45
45
|
*
|
|
46
46
|
* ```tsx
|
|
47
|
-
* function MyComponent(
|
|
47
|
+
* function MyComponent(handle: Handle<{ children: RemixNode }>) {}
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
50
|
export type RemixNode = Renderable | RemixNode[]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMixin, renderMixinElement } from './mixin.ts'
|
|
1
|
+
import { createMixin, renderMixinElement, type MixinFactory } from './mixin.ts'
|
|
2
2
|
import { navigate } from '../navigation.ts'
|
|
3
3
|
import { on } from './on-mixin.ts'
|
|
4
4
|
import type { ElementProps } from '../jsx.ts'
|
|
@@ -21,108 +21,110 @@ const nativeLinkHostTypes = new Set(['a', 'area'])
|
|
|
21
21
|
/**
|
|
22
22
|
* Adds client-side navigation behavior to anchor-like elements.
|
|
23
23
|
*/
|
|
24
|
-
export const link
|
|
24
|
+
export const link: MixinFactory<
|
|
25
25
|
HTMLElement,
|
|
26
26
|
[href: string, options?: NavigationOptions],
|
|
27
27
|
LinkCurrentProps
|
|
28
|
-
>
|
|
29
|
-
|
|
28
|
+
> = createMixin<HTMLElement, [href: string, options?: NavigationOptions], LinkCurrentProps>(
|
|
29
|
+
(handle, hostType) => {
|
|
30
|
+
let suppressKeyboardClick = false
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let nextProps = { ...props }
|
|
43
|
-
if (nextProps.role == null) {
|
|
44
|
-
nextProps.role = 'link'
|
|
45
|
-
}
|
|
46
|
-
if (nextProps.disabled === true && nextProps['aria-disabled'] == null) {
|
|
47
|
-
nextProps['aria-disabled'] = 'true'
|
|
48
|
-
}
|
|
49
|
-
if (hostType === 'button' && nextProps.type == null) {
|
|
50
|
-
nextProps.type = 'button'
|
|
51
|
-
}
|
|
52
|
-
if (
|
|
53
|
-
hostType !== 'button' &&
|
|
54
|
-
nextProps.tabIndex == null &&
|
|
55
|
-
nextProps.tabindex == null &&
|
|
56
|
-
nextProps.contentEditable == null &&
|
|
57
|
-
nextProps.contenteditable == null
|
|
58
|
-
) {
|
|
59
|
-
nextProps.tabIndex = 0
|
|
60
|
-
}
|
|
32
|
+
return (href, options, props: LinkCurrentProps) => {
|
|
33
|
+
if (nativeLinkHostTypes.has(hostType)) {
|
|
34
|
+
return renderMixinElement(handle.element, {
|
|
35
|
+
...(props ?? {}),
|
|
36
|
+
href,
|
|
37
|
+
...(options?.target == null ? {} : { 'rmx-target': options.target }),
|
|
38
|
+
...(options?.src == null ? {} : { 'rmx-src': options.src }),
|
|
39
|
+
...(options?.resetScroll === false ? { 'rmx-reset-scroll': 'false' } : {}),
|
|
40
|
+
})
|
|
41
|
+
}
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
43
|
+
let nextProps = { ...props }
|
|
44
|
+
if (nextProps.role == null) {
|
|
45
|
+
nextProps.role = 'link'
|
|
46
|
+
}
|
|
47
|
+
if (nextProps.disabled === true && nextProps['aria-disabled'] == null) {
|
|
48
|
+
nextProps['aria-disabled'] = 'true'
|
|
49
|
+
}
|
|
50
|
+
if (hostType === 'button' && nextProps.type == null) {
|
|
51
|
+
nextProps.type = 'button'
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
hostType !== 'button' &&
|
|
55
|
+
nextProps.tabIndex == null &&
|
|
56
|
+
nextProps.tabindex == null &&
|
|
57
|
+
nextProps.contentEditable == null &&
|
|
58
|
+
nextProps.contenteditable == null
|
|
59
|
+
) {
|
|
60
|
+
nextProps.tabIndex = 0
|
|
61
|
+
}
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
return renderMixinElement(handle.element, {
|
|
64
|
+
...nextProps,
|
|
65
|
+
mix: [
|
|
66
|
+
on('click', (event) => {
|
|
67
|
+
if (event.detail === 0 && suppressKeyboardClick) {
|
|
68
|
+
suppressKeyboardClick = false
|
|
69
|
+
event.preventDefault()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
78
72
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
suppressKeyboardClick = false
|
|
74
|
+
if (isDisabledElement(event.currentTarget)) {
|
|
75
|
+
event.preventDefault()
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if (event.button !== 0) return
|
|
84
79
|
|
|
85
|
-
void navigate(href, options)
|
|
86
|
-
}),
|
|
87
|
-
on('auxclick', (event) => {
|
|
88
|
-
suppressKeyboardClick = false
|
|
89
|
-
if (isDisabledElement(event.currentTarget)) {
|
|
90
80
|
event.preventDefault()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
81
|
+
if (event.metaKey || event.ctrlKey) {
|
|
82
|
+
globalThis.open(href, '_blank')
|
|
83
|
+
return
|
|
84
|
+
}
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (event.key === 'Enter') {
|
|
100
|
-
if (event.repeat) return
|
|
86
|
+
void navigate(href, options)
|
|
87
|
+
}),
|
|
88
|
+
on('auxclick', (event) => {
|
|
89
|
+
suppressKeyboardClick = false
|
|
101
90
|
if (isDisabledElement(event.currentTarget)) {
|
|
102
91
|
event.preventDefault()
|
|
103
92
|
return
|
|
104
93
|
}
|
|
94
|
+
if (event.button !== 1) return
|
|
105
95
|
|
|
106
|
-
suppressKeyboardClick = hostType === 'button'
|
|
107
96
|
event.preventDefault()
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
97
|
+
globalThis.open(href, '_blank')
|
|
98
|
+
}),
|
|
99
|
+
on('keydown', (event) => {
|
|
100
|
+
if (event.key === 'Enter') {
|
|
101
|
+
if (event.repeat) return
|
|
102
|
+
if (isDisabledElement(event.currentTarget)) {
|
|
103
|
+
event.preventDefault()
|
|
104
|
+
return
|
|
105
|
+
}
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
107
|
+
suppressKeyboardClick = hostType === 'button'
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
void navigate(href, options)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (hostType === 'button' && event.key === ' ') {
|
|
114
|
+
suppressKeyboardClick = true
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
on('keyup', (event) => {
|
|
119
|
+
if (hostType === 'button' && event.key === ' ') {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
}
|
|
122
|
+
}),
|
|
123
|
+
],
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
)
|
|
126
128
|
|
|
127
129
|
function isDisabledElement(node: Element) {
|
|
128
130
|
return (
|
|
@@ -130,6 +130,14 @@ export type MixinDescriptor<
|
|
|
130
130
|
readonly __node?: (node: node) => void
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
export type MixinFactory<
|
|
134
|
+
node extends EventTarget = Element,
|
|
135
|
+
args extends unknown[] = [],
|
|
136
|
+
props extends ElementProps = ElementProps,
|
|
137
|
+
> = <boundNode extends node = node>(
|
|
138
|
+
...args: RebindTuple<args, node, boundNode>
|
|
139
|
+
) => MixinDescriptor<boundNode, RebindTuple<args, node, boundNode>, props>
|
|
140
|
+
|
|
133
141
|
type PreviousMixDepth = [0, 0, 1, 2, 3, 4]
|
|
134
142
|
type FalsyMixValue = false | 0 | 0n | '' | null | undefined
|
|
135
143
|
type NullableMixValue<descriptor> = descriptor | FalsyMixValue
|
|
@@ -244,7 +252,7 @@ export function createMixin<
|
|
|
244
252
|
node extends EventTarget = Element,
|
|
245
253
|
args extends unknown[] = [],
|
|
246
254
|
props extends ElementProps = ElementProps,
|
|
247
|
-
>(type: MixinType<node, args, props>) {
|
|
255
|
+
>(type: MixinType<node, args, props>): MixinFactory<node, args, props> {
|
|
248
256
|
return <boundNode extends node = node>(
|
|
249
257
|
...args: RebindTuple<args, node, boundNode>
|
|
250
258
|
): MixinDescriptor<boundNode, RebindTuple<args, node, boundNode>, props> => ({
|