@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
|
@@ -8,7 +8,7 @@ Use it when the user should type draft text, filter a popup list, and still comm
|
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
10
|
import { css, type Handle } from 'remix/ui'
|
|
11
|
-
import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui'
|
|
11
|
+
import { Combobox, ComboboxOption, onComboboxChange } from 'remix/ui/combobox'
|
|
12
12
|
|
|
13
13
|
let airports = [
|
|
14
14
|
{
|
|
@@ -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'
|
|
@@ -213,7 +214,9 @@ function getOptionSearchValue(option: {
|
|
|
213
214
|
return option.searchValue ?? option.textValue ?? option.label
|
|
214
215
|
}
|
|
215
216
|
|
|
216
|
-
function ComboboxProvider(
|
|
217
|
+
function ComboboxProvider(
|
|
218
|
+
handle: Handle<ComboboxContextProps, ComboboxContextValue>,
|
|
219
|
+
): () => RemixNode {
|
|
217
220
|
let inputRef: HTMLInputElement | undefined
|
|
218
221
|
let listboxRef: listbox.ListboxRef | undefined
|
|
219
222
|
let surfaceRef: HTMLElement | undefined
|
|
@@ -807,7 +810,11 @@ function getComboboxContext(handle: Handle | MixinHandle) {
|
|
|
807
810
|
return handle.context.get(ComboboxProvider)
|
|
808
811
|
}
|
|
809
812
|
|
|
810
|
-
const inputMixin
|
|
813
|
+
const inputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = createMixin<
|
|
814
|
+
HTMLInputElement,
|
|
815
|
+
[],
|
|
816
|
+
ElementProps
|
|
817
|
+
>((handle) => {
|
|
811
818
|
let context = getComboboxContext(handle)
|
|
812
819
|
|
|
813
820
|
return (props) => [
|
|
@@ -882,7 +889,11 @@ const inputMixin = createMixin<HTMLInputElement, [], ElementProps>((handle) => {
|
|
|
882
889
|
]
|
|
883
890
|
})
|
|
884
891
|
|
|
885
|
-
const popoverMixin
|
|
892
|
+
const popoverMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
893
|
+
HTMLElement,
|
|
894
|
+
[],
|
|
895
|
+
ElementProps
|
|
896
|
+
>((handle) => {
|
|
886
897
|
let context = getComboboxContext(handle)
|
|
887
898
|
|
|
888
899
|
return () => [
|
|
@@ -908,12 +919,20 @@ const popoverMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
908
919
|
]
|
|
909
920
|
})
|
|
910
921
|
|
|
911
|
-
const listMixin
|
|
922
|
+
const listMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
923
|
+
HTMLElement,
|
|
924
|
+
[],
|
|
925
|
+
ElementProps
|
|
926
|
+
>((handle) => {
|
|
912
927
|
let context = getComboboxContext(handle)
|
|
913
928
|
return () => [attrs({ id: context.listId }), listbox.list()]
|
|
914
929
|
})
|
|
915
930
|
|
|
916
|
-
const hiddenInputMixin
|
|
931
|
+
const hiddenInputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = createMixin<
|
|
932
|
+
HTMLInputElement,
|
|
933
|
+
[],
|
|
934
|
+
ElementProps
|
|
935
|
+
>((handle) => {
|
|
917
936
|
let context = getComboboxContext(handle)
|
|
918
937
|
return () =>
|
|
919
938
|
attrs({
|
|
@@ -924,8 +943,8 @@ const hiddenInputMixin = createMixin<HTMLInputElement, [], ElementProps>((handle
|
|
|
924
943
|
})
|
|
925
944
|
})
|
|
926
945
|
|
|
927
|
-
const optionMixin
|
|
928
|
-
(handle) => {
|
|
946
|
+
const optionMixin: MixinFactory<HTMLElement, [options: ComboboxOptionOptions], ElementProps> =
|
|
947
|
+
createMixin<HTMLElement, [options: ComboboxOptionOptions], ElementProps>((handle) => {
|
|
929
948
|
let context = getComboboxContext(handle)
|
|
930
949
|
|
|
931
950
|
return (options) => {
|
|
@@ -946,8 +965,7 @@ const optionMixin = createMixin<HTMLElement, [options: ComboboxOptionOptions], E
|
|
|
946
965
|
}),
|
|
947
966
|
]
|
|
948
967
|
}
|
|
949
|
-
}
|
|
950
|
-
)
|
|
968
|
+
})
|
|
951
969
|
|
|
952
970
|
export const Context = ComboboxProvider
|
|
953
971
|
export const hiddenInput = hiddenInputMixin
|
|
@@ -968,11 +986,11 @@ const combobox = {
|
|
|
968
986
|
export function onComboboxChange<target extends HTMLElement>(
|
|
969
987
|
handler: ComboboxChangeHandler<target>,
|
|
970
988
|
captureBoolean?: boolean,
|
|
971
|
-
) {
|
|
989
|
+
): ReturnType<typeof on<target, typeof COMBOBOX_CHANGE_EVENT>> {
|
|
972
990
|
return on(COMBOBOX_CHANGE_EVENT, handler, captureBoolean)
|
|
973
991
|
}
|
|
974
992
|
|
|
975
|
-
export function Combobox(handle: Handle<ComboboxProps>) {
|
|
993
|
+
export function Combobox(handle: Handle<ComboboxProps>): () => RemixNode {
|
|
976
994
|
return () => {
|
|
977
995
|
let { children, defaultValue, disabled, inputId, name, placeholder, ...divProps } = handle.props
|
|
978
996
|
|
|
@@ -995,7 +1013,7 @@ export function Combobox(handle: Handle<ComboboxProps>) {
|
|
|
995
1013
|
}
|
|
996
1014
|
}
|
|
997
1015
|
|
|
998
|
-
export function ComboboxOption(handle: Handle<ComboboxOptionProps>) {
|
|
1016
|
+
export function ComboboxOption(handle: Handle<ComboboxOptionProps>): () => RemixNode {
|
|
999
1017
|
return () => {
|
|
1000
1018
|
let { children, disabled, label, mix, searchValue, value, ...divProps } = handle.props
|
|
1001
1019
|
|
|
@@ -31,7 +31,7 @@ export function createGlyphSheet(values: GlyphValues): GlyphSheetComponent {
|
|
|
31
31
|
>,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
function GlyphSheet(handle: Handle<GlyphSheetProps>) {
|
|
34
|
+
function GlyphSheet(handle: Handle<GlyphSheetProps>): () => RemixElement {
|
|
35
35
|
return () => {
|
|
36
36
|
let { style, ...svgProps } = handle.props
|
|
37
37
|
let hiddenStyle = {
|
|
@@ -70,7 +70,7 @@ export function createGlyphSheet(values: GlyphValues): GlyphSheetComponent {
|
|
|
70
70
|
})
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export function Glyph(handle: Handle<GlyphProps>) {
|
|
73
|
+
export function Glyph(handle: Handle<GlyphProps>): () => RemixElement {
|
|
74
74
|
return () => {
|
|
75
75
|
let { fill, name, ...svgProps } = handle.props
|
|
76
76
|
let glyphId = glyphContract[name].id
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
css,
|
|
5
5
|
on,
|
|
6
6
|
type CSSMixinDescriptor,
|
|
7
|
+
type ElementProps,
|
|
7
8
|
type Handle,
|
|
9
|
+
type MixinFactory,
|
|
8
10
|
type RemixNode,
|
|
9
11
|
} from '@remix-run/ui'
|
|
10
12
|
import { theme } from '../../theme/theme.ts'
|
|
@@ -149,7 +151,7 @@ export const optionStyle = itemCss
|
|
|
149
151
|
export const glyphStyle = itemGlyphCss
|
|
150
152
|
export const labelStyle = itemLabelCss
|
|
151
153
|
|
|
152
|
-
function ListboxProvider(handle: Handle<ListboxProviderProps, ListboxContext>) {
|
|
154
|
+
function ListboxProvider(handle: Handle<ListboxProviderProps, ListboxContext>): () => RemixNode {
|
|
153
155
|
let options: RegisteredOption[] = []
|
|
154
156
|
let state: State = 'idle'
|
|
155
157
|
|
|
@@ -336,49 +338,51 @@ function ListboxProvider(handle: Handle<ListboxProviderProps, ListboxContext>) {
|
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
const listMixin = createMixin<HTMLElement>(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
341
|
+
const listMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<HTMLElement>(
|
|
342
|
+
(handle) => (props) => {
|
|
343
|
+
let context = handle.context.get(ListboxProvider)
|
|
344
|
+
return [
|
|
345
|
+
attrs({
|
|
346
|
+
tabIndex: props.tabIndex ?? -1,
|
|
347
|
+
role: props.role ?? 'listbox',
|
|
348
|
+
}),
|
|
349
|
+
on('focus', () => {
|
|
350
|
+
context.scrollActiveOptionIntoView()
|
|
351
|
+
}),
|
|
352
|
+
on('keydown', (event) => {
|
|
353
|
+
switch (event.key) {
|
|
354
|
+
case 'ArrowDown':
|
|
355
|
+
event.preventDefault()
|
|
356
|
+
context.navigate('next')
|
|
357
|
+
break
|
|
358
|
+
case 'ArrowUp':
|
|
359
|
+
event.preventDefault()
|
|
360
|
+
context.navigate('previous')
|
|
361
|
+
break
|
|
362
|
+
case 'Tab':
|
|
363
|
+
event.preventDefault()
|
|
364
|
+
context.navigate('first')
|
|
365
|
+
break
|
|
366
|
+
case 'Enter':
|
|
367
|
+
case ' ':
|
|
368
|
+
event.preventDefault()
|
|
369
|
+
void context.select(context.activeValue)
|
|
370
|
+
break
|
|
371
|
+
case 'Home':
|
|
372
|
+
event.preventDefault()
|
|
373
|
+
context.navigate('first')
|
|
374
|
+
break
|
|
375
|
+
case 'End':
|
|
376
|
+
event.preventDefault()
|
|
377
|
+
context.navigate('last')
|
|
378
|
+
}
|
|
379
|
+
}),
|
|
380
|
+
hiddenTypeahead((text) => {
|
|
381
|
+
context.highlightSearchMatch(text)
|
|
382
|
+
}),
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
)
|
|
382
386
|
|
|
383
387
|
export interface ListboxOption {
|
|
384
388
|
id: string
|
|
@@ -388,50 +392,51 @@ export interface ListboxOption {
|
|
|
388
392
|
textValue?: SearchValue
|
|
389
393
|
}
|
|
390
394
|
|
|
391
|
-
const optionMixin
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
handle.queueTask((node) => {
|
|
395
|
-
optionRef = node
|
|
396
|
-
})
|
|
395
|
+
const optionMixin: MixinFactory<HTMLElement, [option: Omit<ListboxOption, 'id'>], ElementProps> =
|
|
396
|
+
createMixin<HTMLElement, [option: Omit<ListboxOption, 'id'>]>((handle) => {
|
|
397
|
+
let optionRef: HTMLElement | undefined
|
|
397
398
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
context.registerOption({
|
|
401
|
-
...option,
|
|
402
|
-
id: handle.id,
|
|
403
|
-
get hidden() {
|
|
404
|
-
return optionRef?.hidden === true
|
|
405
|
-
},
|
|
406
|
-
get node() {
|
|
407
|
-
return optionRef as HTMLElement
|
|
408
|
-
},
|
|
399
|
+
handle.queueTask((node) => {
|
|
400
|
+
optionRef = node
|
|
409
401
|
})
|
|
410
402
|
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
|
|
403
|
+
return (option) => {
|
|
404
|
+
let context = handle.context.get(ListboxProvider)
|
|
405
|
+
context.registerOption({
|
|
406
|
+
...option,
|
|
414
407
|
id: handle.id,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
408
|
+
get hidden() {
|
|
409
|
+
return optionRef?.hidden === true
|
|
410
|
+
},
|
|
411
|
+
get node() {
|
|
412
|
+
return optionRef as HTMLElement
|
|
413
|
+
},
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
return [
|
|
417
|
+
attrs({
|
|
418
|
+
role: 'option',
|
|
419
|
+
id: handle.id,
|
|
420
|
+
'aria-selected': context.value === option.value ? 'true' : 'false',
|
|
421
|
+
'aria-disabled': option.disabled ? 'true' : 'false',
|
|
422
|
+
'data-highlighted': context.activeValue === option.value ? 'true' : 'false',
|
|
422
423
|
}),
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
})
|
|
424
|
+
!option.disabled && [
|
|
425
|
+
on('click', () => {
|
|
426
|
+
context.select(option.value)
|
|
427
|
+
}),
|
|
428
|
+
on('mousemove', () => {
|
|
429
|
+
if (context.activeValue === option.value) return
|
|
430
|
+
context.highlight(option.value)
|
|
431
|
+
}),
|
|
432
|
+
on('mouseleave', () => {
|
|
433
|
+
if (context.activeValue !== option.value) return
|
|
434
|
+
context.highlight(null)
|
|
435
|
+
}),
|
|
436
|
+
],
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
})
|
|
435
440
|
|
|
436
441
|
export const Context = ListboxProvider
|
|
437
442
|
export const list = listMixin
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type Dispatched,
|
|
10
10
|
type ElementProps,
|
|
11
11
|
type Handle,
|
|
12
|
+
type MixinFactory,
|
|
12
13
|
type Props,
|
|
13
14
|
type RemixNode,
|
|
14
15
|
} from '@remix-run/ui'
|
|
@@ -416,7 +417,7 @@ function focusNode(node: HTMLElement | undefined) {
|
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
|
|
419
|
-
function MenuProvider(handle: Handle<MenuProviderProps, MenuContextValue>) {
|
|
420
|
+
function MenuProvider(handle: Handle<MenuProviderProps, MenuContextValue>): () => RemixNode {
|
|
420
421
|
let parent = handle.context.get(MenuProvider)
|
|
421
422
|
|
|
422
423
|
let activeId: string | null = null
|
|
@@ -957,8 +958,8 @@ function MenuProvider(handle: Handle<MenuProviderProps, MenuContextValue>) {
|
|
|
957
958
|
}
|
|
958
959
|
}
|
|
959
960
|
|
|
960
|
-
const triggerMixin
|
|
961
|
-
(handle) => {
|
|
961
|
+
const triggerMixin: MixinFactory<HTMLElement, [options?: MenuTriggerOptions], ElementProps> =
|
|
962
|
+
createMixin<HTMLElement, [options?: MenuTriggerOptions], ElementProps>((handle) => {
|
|
962
963
|
let context = handle.context.get(MenuProvider)
|
|
963
964
|
|
|
964
965
|
return (options = {}) => [
|
|
@@ -1005,10 +1006,13 @@ const triggerMixin = createMixin<HTMLElement, [options?: MenuTriggerOptions], El
|
|
|
1005
1006
|
}
|
|
1006
1007
|
}),
|
|
1007
1008
|
]
|
|
1008
|
-
}
|
|
1009
|
-
)
|
|
1009
|
+
})
|
|
1010
1010
|
|
|
1011
|
-
const popoverMixin
|
|
1011
|
+
const popoverMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
1012
|
+
HTMLElement,
|
|
1013
|
+
[],
|
|
1014
|
+
ElementProps
|
|
1015
|
+
>((handle) => {
|
|
1012
1016
|
let context = handle.context.get(MenuProvider)
|
|
1013
1017
|
|
|
1014
1018
|
return () => [
|
|
@@ -1045,7 +1049,11 @@ const popoverMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
1045
1049
|
]
|
|
1046
1050
|
})
|
|
1047
1051
|
|
|
1048
|
-
const listMixin
|
|
1052
|
+
const listMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<
|
|
1053
|
+
HTMLElement,
|
|
1054
|
+
[],
|
|
1055
|
+
ElementProps
|
|
1056
|
+
>((handle) => {
|
|
1049
1057
|
let context = handle.context.get(MenuProvider)
|
|
1050
1058
|
|
|
1051
1059
|
return () => [
|
|
@@ -1146,7 +1154,11 @@ const listMixin = createMixin<HTMLElement, [], ElementProps>((handle) => {
|
|
|
1146
1154
|
]
|
|
1147
1155
|
})
|
|
1148
1156
|
|
|
1149
|
-
const itemMixin
|
|
1157
|
+
const itemMixin: MixinFactory<HTMLElement, [options: MenuItemOptions], ElementProps> = createMixin<
|
|
1158
|
+
HTMLElement,
|
|
1159
|
+
[options: MenuItemOptions],
|
|
1160
|
+
ElementProps
|
|
1161
|
+
>((handle) => {
|
|
1150
1162
|
let itemRef: HTMLElement | undefined
|
|
1151
1163
|
|
|
1152
1164
|
handle.queueTask((node) => {
|
|
@@ -1226,11 +1238,11 @@ const itemMixin = createMixin<HTMLElement, [options: MenuItemOptions], ElementPr
|
|
|
1226
1238
|
}
|
|
1227
1239
|
})
|
|
1228
1240
|
|
|
1229
|
-
const submenuTriggerMixin
|
|
1241
|
+
const submenuTriggerMixin: MixinFactory<
|
|
1230
1242
|
HTMLElement,
|
|
1231
1243
|
[options: SubmenuTriggerOptions],
|
|
1232
1244
|
ElementProps
|
|
1233
|
-
>((handle) => {
|
|
1245
|
+
> = createMixin<HTMLElement, [options: SubmenuTriggerOptions], ElementProps>((handle) => {
|
|
1234
1246
|
let itemRef: HTMLElement | undefined
|
|
1235
1247
|
let openTimeoutId = 0
|
|
1236
1248
|
let aborted = false
|
|
@@ -1400,7 +1412,7 @@ const menu = {
|
|
|
1400
1412
|
export function onMenuSelect<target extends HTMLElement>(
|
|
1401
1413
|
handler: MenuSelectHandler<target>,
|
|
1402
1414
|
captureBoolean?: boolean,
|
|
1403
|
-
) {
|
|
1415
|
+
): ReturnType<typeof on<target, typeof MENU_SELECT_EVENT>> {
|
|
1404
1416
|
return on(MENU_SELECT_EVENT, handler, captureBoolean)
|
|
1405
1417
|
}
|
|
1406
1418
|
|
|
@@ -1425,7 +1437,7 @@ export interface SubmenuProps
|
|
|
1425
1437
|
menuLabel?: string
|
|
1426
1438
|
}
|
|
1427
1439
|
|
|
1428
|
-
export function Menu(handle: Handle<MenuProps>) {
|
|
1440
|
+
export function Menu(handle: Handle<MenuProps>): () => RemixNode {
|
|
1429
1441
|
let buttonRef: HTMLButtonElement | undefined
|
|
1430
1442
|
|
|
1431
1443
|
return () => {
|
|
@@ -1472,7 +1484,7 @@ export function Menu(handle: Handle<MenuProps>) {
|
|
|
1472
1484
|
}
|
|
1473
1485
|
}
|
|
1474
1486
|
|
|
1475
|
-
export function MenuList(handle: Handle<MenuListProps>) {
|
|
1487
|
+
export function MenuList(handle: Handle<MenuListProps>): () => RemixNode {
|
|
1476
1488
|
return () => {
|
|
1477
1489
|
let { children, mix, ...divProps } = handle.props
|
|
1478
1490
|
|
|
@@ -1486,7 +1498,7 @@ export function MenuList(handle: Handle<MenuListProps>) {
|
|
|
1486
1498
|
}
|
|
1487
1499
|
}
|
|
1488
1500
|
|
|
1489
|
-
export function MenuItem(handle: Handle<MenuItemProps>) {
|
|
1501
|
+
export function MenuItem(handle: Handle<MenuItemProps>): () => RemixNode {
|
|
1490
1502
|
return () => {
|
|
1491
1503
|
let { checked, children, disabled, label, mix, name, searchValue, type, value, ...divProps } =
|
|
1492
1504
|
handle.props
|
|
@@ -1509,7 +1521,7 @@ export function MenuItem(handle: Handle<MenuItemProps>) {
|
|
|
1509
1521
|
}
|
|
1510
1522
|
}
|
|
1511
1523
|
|
|
1512
|
-
export function Submenu(handle: Handle<SubmenuProps>) {
|
|
1524
|
+
export function Submenu(handle: Handle<SubmenuProps>): () => RemixNode {
|
|
1513
1525
|
return () => {
|
|
1514
1526
|
let { children, disabled, label, listProps, menuLabel, mix, searchValue, value, ...divProps } =
|
|
1515
1527
|
handle.props
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
css,
|
|
5
5
|
on,
|
|
6
6
|
type CSSMixinDescriptor,
|
|
7
|
+
type ElementProps,
|
|
7
8
|
type Handle,
|
|
9
|
+
type MixinFactory,
|
|
8
10
|
type RemixNode,
|
|
9
11
|
} from '@remix-run/ui'
|
|
10
12
|
import { anchor as positionAnchor, type AnchorOptions } from '../anchor/anchor.ts'
|
|
@@ -75,7 +77,7 @@ const popoverSurfaceCss: CSSMixinDescriptor = css({
|
|
|
75
77
|
export const contentStyle = popoverContentCss
|
|
76
78
|
export const surfaceStyle = [popoverSurfaceCss, popoverSurfaceTransitionCss] as const
|
|
77
79
|
|
|
78
|
-
function PopoverProvider(handle: Handle<PopoverProps, PopoverContext>) {
|
|
80
|
+
function PopoverProvider(handle: Handle<PopoverProps, PopoverContext>): () => RemixNode {
|
|
79
81
|
handle.context.set({
|
|
80
82
|
hideFocusTarget: null,
|
|
81
83
|
showFocusTarget: null,
|
|
@@ -99,7 +101,10 @@ export interface PopoverHideRequest {
|
|
|
99
101
|
target?: Node | null
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
const anchorMixin
|
|
104
|
+
const anchorMixin: MixinFactory<HTMLElement, [options: AnchorOptions], ElementProps> = createMixin<
|
|
105
|
+
HTMLElement,
|
|
106
|
+
[options: AnchorOptions]
|
|
107
|
+
>((handle) => {
|
|
103
108
|
let context = handle.context.get(PopoverProvider)
|
|
104
109
|
return (options) => {
|
|
105
110
|
handle.queueTask((node) => {
|
|
@@ -108,91 +113,97 @@ const anchorMixin = createMixin<HTMLElement, [options: AnchorOptions]>((handle)
|
|
|
108
113
|
}
|
|
109
114
|
})
|
|
110
115
|
|
|
111
|
-
const surfaceMixin
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
handle.queueTask((node, signal) => {
|
|
118
|
-
context.surface = node
|
|
119
|
-
|
|
120
|
-
signal.addEventListener('abort', () => {
|
|
121
|
-
if (context.surface === node) {
|
|
122
|
-
context.surface = null
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
})
|
|
116
|
+
const surfaceMixin: MixinFactory<HTMLElement, [options: PopoverSurfaceOptions], ElementProps> =
|
|
117
|
+
createMixin<HTMLElement, [options: PopoverSurfaceOptions]>((handle) => {
|
|
118
|
+
let openProp = false
|
|
119
|
+
let cleanupAnchor = () => {}
|
|
120
|
+
let unlockScroll = () => {}
|
|
121
|
+
let context = handle.context.get(PopoverProvider)
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
openProp = options.open
|
|
130
|
-
|
|
131
|
-
handle.queueTask(async (node) => {
|
|
132
|
-
if (openProp && !wasOpen) {
|
|
133
|
-
node.showPopover()
|
|
134
|
-
} else if (!openProp && wasOpen) {
|
|
135
|
-
node.hidePopover()
|
|
136
|
-
}
|
|
137
|
-
})
|
|
123
|
+
handle.queueTask((node, signal) => {
|
|
124
|
+
context.surface = node
|
|
138
125
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
on('beforetoggle', (event) => {
|
|
143
|
-
if (event.newState === 'open') {
|
|
144
|
-
cleanupAnchor = positionAnchor(
|
|
145
|
-
event.currentTarget,
|
|
146
|
-
context.anchor!.node,
|
|
147
|
-
context.anchor!.options,
|
|
148
|
-
)
|
|
149
|
-
unlockScroll = lockScroll()
|
|
150
|
-
} else if (event.newState === 'closed') {
|
|
151
|
-
cleanupAnchor()
|
|
152
|
-
unlockScroll()
|
|
126
|
+
signal.addEventListener('abort', () => {
|
|
127
|
+
if (context.surface === node) {
|
|
128
|
+
context.surface = null
|
|
153
129
|
}
|
|
154
|
-
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
155
132
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
} else if (event.newState === 'closed' && options.restoreFocusOnHide !== false) {
|
|
160
|
-
context.hideFocusTarget?.focus()
|
|
161
|
-
}
|
|
162
|
-
}),
|
|
133
|
+
return (options) => {
|
|
134
|
+
let wasOpen = openProp
|
|
135
|
+
openProp = options.open
|
|
163
136
|
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
137
|
+
handle.queueTask(async (node) => {
|
|
138
|
+
if (openProp && !wasOpen) {
|
|
139
|
+
node.showPopover()
|
|
140
|
+
} else if (!openProp && wasOpen) {
|
|
141
|
+
node.hidePopover()
|
|
168
142
|
}
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return [
|
|
146
|
+
attrs({ popover: 'manual' }),
|
|
147
|
+
|
|
148
|
+
on('beforetoggle', (event) => {
|
|
149
|
+
if (event.newState === 'open') {
|
|
150
|
+
cleanupAnchor = positionAnchor(
|
|
151
|
+
event.currentTarget,
|
|
152
|
+
context.anchor!.node,
|
|
153
|
+
context.anchor!.options,
|
|
154
|
+
)
|
|
155
|
+
unlockScroll = lockScroll()
|
|
156
|
+
} else if (event.newState === 'closed') {
|
|
157
|
+
cleanupAnchor()
|
|
158
|
+
unlockScroll()
|
|
159
|
+
}
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
on('toggle', async (event) => {
|
|
163
|
+
if (event.newState === 'open') {
|
|
164
|
+
context.showFocusTarget?.focus()
|
|
165
|
+
} else if (event.newState === 'closed' && options.restoreFocusOnHide !== false) {
|
|
166
|
+
context.hideFocusTarget?.focus()
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
|
|
170
|
+
on('keydown', (event) => {
|
|
171
|
+
if (event.key === 'Escape') {
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
options.onHide({ reason: 'escape-key' })
|
|
174
|
+
}
|
|
175
|
+
}),
|
|
176
|
+
|
|
177
|
+
onOutsideClick(
|
|
178
|
+
openProp,
|
|
179
|
+
(target) => {
|
|
180
|
+
options.onHide({ reason: 'outside-click', target })
|
|
181
|
+
},
|
|
182
|
+
(target) =>
|
|
183
|
+
options.closeOnAnchorClick === false && !!context.anchor?.node.contains(target),
|
|
184
|
+
options.stopOutsideClickPropagation ?? true,
|
|
185
|
+
),
|
|
186
|
+
]
|
|
187
|
+
}
|
|
187
188
|
})
|
|
188
|
-
})
|
|
189
189
|
|
|
190
|
-
const
|
|
191
|
-
handle
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
})
|
|
190
|
+
const focusOnShowMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<HTMLElement, []>(
|
|
191
|
+
(handle) => {
|
|
192
|
+
handle.addEventListener('insert', (event) => {
|
|
193
|
+
let context = handle.context.get(PopoverProvider)
|
|
194
|
+
context.showFocusTarget = event.node
|
|
195
|
+
})
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
const focusOnHideMixin: MixinFactory<HTMLElement, [], ElementProps> = createMixin<HTMLElement, []>(
|
|
200
|
+
(handle) => {
|
|
201
|
+
handle.addEventListener('insert', (event) => {
|
|
202
|
+
let context = handle.context.get(PopoverProvider)
|
|
203
|
+
context.hideFocusTarget = event.node
|
|
204
|
+
})
|
|
205
|
+
},
|
|
206
|
+
)
|
|
196
207
|
|
|
197
208
|
export const Context = PopoverProvider
|
|
198
209
|
export const anchor = anchorMixin
|