@spectral_labs/ui 1.5.1 → 1.5.3
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/dist/components/context-menu/components/context-menu-content.svelte +8 -7
- package/dist/components/context-menu/components/context-menu-content.svelte.d.ts.map +1 -1
- package/dist/components/context-menu/components/context-menu-root.svelte +9 -7
- package/dist/components/context-menu/components/context-menu-root.svelte.d.ts.map +1 -1
- package/dist/components/context-menu/index.d.ts +3 -0
- package/dist/components/context-menu/index.d.ts.map +1 -1
- package/dist/components/context-menu/index.js +2 -0
- package/dist/components/dropdown-menu/__tests__/DropdownMenuRadioTest.svelte +90 -0
- package/dist/components/dropdown-menu/__tests__/DropdownMenuTest.svelte +22 -1
- package/dist/components/dropdown-menu/__tests__/dropdown-menu-radio-group.test.svelte.js +306 -0
- package/dist/components/dropdown-menu/__tests__/dropdown-menu.test.svelte.js +438 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-checkbox-item.svelte +10 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-checkbox-item.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-content.svelte +8 -7
- package/dist/components/dropdown-menu/components/dropdown-menu-content.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-item.svelte +8 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-item.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-group.svelte +45 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-group.svelte.d.ts +5 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-group.svelte.d.ts.map +1 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-item.svelte +143 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-item.svelte.d.ts +5 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-radio-item.svelte.d.ts.map +1 -0
- package/dist/components/dropdown-menu/components/dropdown-menu-root.svelte +9 -7
- package/dist/components/dropdown-menu/components/dropdown-menu-root.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-sub-content.svelte +94 -4
- package/dist/components/dropdown-menu/components/dropdown-menu-sub-content.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-sub-trigger.svelte +32 -7
- package/dist/components/dropdown-menu/components/dropdown-menu-sub-trigger.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/components/dropdown-menu-sub.svelte +180 -6
- package/dist/components/dropdown-menu/components/dropdown-menu-sub.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.svelte.d.ts +48 -2
- package/dist/components/dropdown-menu/dropdown-menu.svelte.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.svelte.js +16 -0
- package/dist/components/dropdown-menu/index.d.ts +3 -1
- package/dist/components/dropdown-menu/index.d.ts.map +1 -1
- package/dist/components/dropdown-menu/index.js +2 -0
- package/dist/components/dropdown-menu/types.d.ts +37 -1
- package/dist/components/dropdown-menu/types.d.ts.map +1 -1
- package/dist/components/menubar/__tests__/menubar.test.svelte.js +95 -0
- package/dist/components/menubar/components/menubar-content.svelte +8 -7
- package/dist/components/menubar/components/menubar-content.svelte.d.ts.map +1 -1
- package/dist/components/menubar/components/menubar-menu.svelte +8 -6
- package/dist/components/menubar/components/menubar-menu.svelte.d.ts.map +1 -1
- package/dist/components/menubar/index.d.ts +3 -1
- package/dist/components/menubar/index.d.ts.map +1 -1
- package/dist/components/menubar/index.js +2 -0
- package/dist/internal/escape-layer/__tests__/escape-layer.test.js +29 -0
- package/dist/internal/escape-layer/escape-layer.svelte.d.ts +5 -1
- package/dist/internal/escape-layer/escape-layer.svelte.d.ts.map +1 -1
- package/dist/internal/escape-layer/escape-layer.svelte.js +20 -13
- package/dist/internal/menu-context/index.d.ts +2 -2
- package/dist/internal/menu-context/index.d.ts.map +1 -1
- package/dist/internal/menu-context/index.js +1 -1
- package/dist/internal/menu-context/menu-context.svelte.d.ts +64 -2
- package/dist/internal/menu-context/menu-context.svelte.d.ts.map +1 -1
- package/dist/internal/menu-context/menu-context.svelte.js +59 -0
- package/dist/internal/roving-focus/roving-focus.svelte.d.ts.map +1 -1
- package/dist/internal/roving-focus/roving-focus.svelte.js +2 -1
- package/dist/internal/roving-focus/types.d.ts +6 -0
- package/dist/internal/roving-focus/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/dropdown-menu.svelte.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,YAAY,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CAC/C;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAErE;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAE1D;AAED,wBAAgB,qBAAqB;;;EAKpC;AAID,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"dropdown-menu.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/dropdown-menu.svelte.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,YAAY,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;CAC/C;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAErE;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,CAE1D;AAED,wBAAgB,qBAAqB;;;EAKpC;AAID,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IACtC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IACvD,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IACxD,2DAA2D;IAC3D,0BAA0B,EAAE,MAAM,IAAI,CAAA;IACtC,YAAY,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,YAAY,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IAC9C;;;OAGG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC7C;;;;OAIG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;CAC9C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAE7E;AAED,wBAAgB,yBAAyB,IAAI,sBAAsB,CAElE;AAID;;;;;GAKG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,8EAA8E;IAC9E,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AAED,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,6BAA6B,GAAG,IAAI,CAE3F;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,6BAA6B,CAQhF"}
|
|
@@ -20,3 +20,19 @@ export function setDropdownMenuSubContext(state) {
|
|
|
20
20
|
export function getDropdownMenuSubContext() {
|
|
21
21
|
return getContext(SUB_KEY);
|
|
22
22
|
}
|
|
23
|
+
const RADIO_GROUP_KEY = Symbol('sp-dropdown-radio-group');
|
|
24
|
+
export function setDropdownMenuRadioGroupContext(state) {
|
|
25
|
+
setContext(RADIO_GROUP_KEY, state);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Throws a NAMED error instead of returning `undefined`: an orphan
|
|
29
|
+
* `RadioItem` would otherwise fail later with an opaque property-access
|
|
30
|
+
* TypeError, at a point that no longer names the mistake.
|
|
31
|
+
*/
|
|
32
|
+
export function getDropdownMenuRadioGroupContext() {
|
|
33
|
+
const ctx = getContext(RADIO_GROUP_KEY);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
throw new Error('[spectral-ui] DropdownMenu.RadioItem must be rendered inside a DropdownMenu.RadioGroup.');
|
|
36
|
+
}
|
|
37
|
+
return ctx;
|
|
38
|
+
}
|
|
@@ -2,11 +2,13 @@ export { default as CheckboxItem } from './components/dropdown-menu-checkbox-ite
|
|
|
2
2
|
export { default as Content } from './components/dropdown-menu-content.svelte';
|
|
3
3
|
export { default as Item } from './components/dropdown-menu-item.svelte';
|
|
4
4
|
export { default as Label } from './components/dropdown-menu-label.svelte';
|
|
5
|
+
export { default as RadioGroup } from './components/dropdown-menu-radio-group.svelte';
|
|
6
|
+
export { default as RadioItem } from './components/dropdown-menu-radio-item.svelte';
|
|
5
7
|
export { default as Root } from './components/dropdown-menu-root.svelte';
|
|
6
8
|
export { default as Separator } from './components/dropdown-menu-separator.svelte';
|
|
7
9
|
export { default as SubContent } from './components/dropdown-menu-sub-content.svelte';
|
|
8
10
|
export { default as SubTrigger } from './components/dropdown-menu-sub-trigger.svelte';
|
|
9
11
|
export { default as Sub } from './components/dropdown-menu-sub.svelte';
|
|
10
12
|
export { default as Trigger } from './components/dropdown-menu-trigger.svelte';
|
|
11
|
-
export type { DropdownMenuCheckboxItemProps, DropdownMenuContentProps, DropdownMenuItemProps, DropdownMenuLabelProps, DropdownMenuProps, DropdownMenuSeparatorProps, DropdownMenuSubContentProps, DropdownMenuSubProps, DropdownMenuSubTriggerProps, DropdownMenuTriggerProps, } from './types.js';
|
|
13
|
+
export type { DropdownMenuCheckboxItemProps, DropdownMenuContentProps, DropdownMenuItemProps, DropdownMenuLabelProps, DropdownMenuProps, DropdownMenuRadioGroupProps, DropdownMenuRadioItemProps, DropdownMenuSeparatorProps, DropdownMenuSubContentProps, DropdownMenuSubProps, DropdownMenuSubTriggerProps, DropdownMenuTriggerProps, } from './types.js';
|
|
12
14
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iDAAiD,CAAA;AACzF,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAC9E,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,yCAAyC,CAAA;AAC1E,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,6CAA6C,CAAA;AAClF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+CAA+C,CAAA;AACrF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+CAA+C,CAAA;AACrF,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAC9E,YAAY,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iDAAiD,CAAA;AACzF,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAC9E,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,yCAAyC,CAAA;AAC1E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+CAA+C,CAAA;AACrF,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,8CAA8C,CAAA;AACnF,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,6CAA6C,CAAA;AAClF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+CAA+C,CAAA;AACrF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,+CAA+C,CAAA;AACrF,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,uCAAuC,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,2CAA2C,CAAA;AAC9E,YAAY,EACV,6BAA6B,EAC7B,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,YAAY,CAAA"}
|
|
@@ -2,6 +2,8 @@ export { default as CheckboxItem } from './components/dropdown-menu-checkbox-ite
|
|
|
2
2
|
export { default as Content } from './components/dropdown-menu-content.svelte';
|
|
3
3
|
export { default as Item } from './components/dropdown-menu-item.svelte';
|
|
4
4
|
export { default as Label } from './components/dropdown-menu-label.svelte';
|
|
5
|
+
export { default as RadioGroup } from './components/dropdown-menu-radio-group.svelte';
|
|
6
|
+
export { default as RadioItem } from './components/dropdown-menu-radio-item.svelte';
|
|
5
7
|
export { default as Root } from './components/dropdown-menu-root.svelte';
|
|
6
8
|
export { default as Separator } from './components/dropdown-menu-separator.svelte';
|
|
7
9
|
export { default as SubContent } from './components/dropdown-menu-sub-content.svelte';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
2
|
+
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
import type { Align, Side } from '../../internal/floating-layer/types.js';
|
|
4
4
|
export interface DropdownMenuProps {
|
|
5
5
|
open?: boolean;
|
|
@@ -55,6 +55,40 @@ export interface DropdownMenuCheckboxItemProps {
|
|
|
55
55
|
class?: string;
|
|
56
56
|
children?: Snippet;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Extends the native div attributes so the real `role="group"` node can be
|
|
60
|
+
* named: several radio groups in one menu each need their own `aria-label` /
|
|
61
|
+
* `aria-labelledby`, and consumers need `id` / `data-testid` on the group
|
|
62
|
+
* itself.
|
|
63
|
+
*/
|
|
64
|
+
export interface DropdownMenuRadioGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> {
|
|
65
|
+
/** The selected value. Bindable. */
|
|
66
|
+
value?: string;
|
|
67
|
+
/** Called whenever the selection changes. Never fired for a re-selection. */
|
|
68
|
+
onValueChange?: (value: string) => void;
|
|
69
|
+
class?: string;
|
|
70
|
+
children?: Snippet;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extends the native div attributes: unlike the older menu rows, a consumer's
|
|
74
|
+
* `data-testid`, `aria-*` and event handlers reach the DOM node
|
|
75
|
+
* (spectral-ui#201 is not reproduced here). Consumer handlers COMPOSE with the
|
|
76
|
+
* built-in ones — they run first, and calling `preventDefault()` cancels the
|
|
77
|
+
* built-in behaviour.
|
|
78
|
+
*/
|
|
79
|
+
export interface DropdownMenuRadioItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> {
|
|
80
|
+
/** Value identifying this row within its `RadioGroup`. */
|
|
81
|
+
value: string;
|
|
82
|
+
disabled?: boolean;
|
|
83
|
+
/** Override textContent for typeahead matching. */
|
|
84
|
+
textValue?: string;
|
|
85
|
+
/** Close the menu after selection (default true). */
|
|
86
|
+
closeOnSelect?: boolean;
|
|
87
|
+
/** Callback on selection, after the group's value has moved. */
|
|
88
|
+
onSelect?: (event: Event) => void;
|
|
89
|
+
class?: string;
|
|
90
|
+
children?: Snippet;
|
|
91
|
+
}
|
|
58
92
|
export interface DropdownMenuSubProps {
|
|
59
93
|
/** Whether the submenu is open. */
|
|
60
94
|
open?: boolean;
|
|
@@ -63,6 +97,8 @@ export interface DropdownMenuSubProps {
|
|
|
63
97
|
}
|
|
64
98
|
export interface DropdownMenuSubTriggerProps {
|
|
65
99
|
disabled?: boolean;
|
|
100
|
+
/** Override textContent for typeahead matching (parity with `DropdownMenuItemProps`). */
|
|
101
|
+
textValue?: string;
|
|
66
102
|
class?: string;
|
|
67
103
|
children?: Snippet;
|
|
68
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/components/dropdown-menu/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC3E,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAA;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uFAAuF;IACvF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iDAAiD;IACjD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,IAAI,CACvD,cAAc,CAAC,cAAc,CAAC,EAC9B,OAAO,GAAG,UAAU,CACrB;IACC,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CACtD,cAAc,CAAC,cAAc,CAAC,EAC9B,OAAO,GAAG,UAAU,CACrB;IACC,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,mCAAmC;IACnC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB"}
|
|
@@ -229,5 +229,100 @@ describe('menubar', () => {
|
|
|
229
229
|
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
230
230
|
expect(triggers[1].getAttribute('data-state')).toBe('closed');
|
|
231
231
|
});
|
|
232
|
+
it('arrowRight on the sub-trigger moves focus to the first sub item', () => {
|
|
233
|
+
setup({ withSub: true });
|
|
234
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
235
|
+
triggers[0].click();
|
|
236
|
+
flushSync();
|
|
237
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
238
|
+
subTrigger.focus();
|
|
239
|
+
pressKey('ArrowRight');
|
|
240
|
+
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
241
|
+
});
|
|
242
|
+
it('arrowLeft inside the sub closes it, restores focus to the sub-trigger and keeps the menu', () => {
|
|
243
|
+
setup({ withSub: true });
|
|
244
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
245
|
+
triggers[0].click();
|
|
246
|
+
flushSync();
|
|
247
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
248
|
+
subTrigger.focus();
|
|
249
|
+
pressKey('ArrowRight');
|
|
250
|
+
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
251
|
+
pressKey('ArrowLeft');
|
|
252
|
+
// Sub closed, focus back on the trigger row — no dead end on body.
|
|
253
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
254
|
+
expect(document.activeElement).toBe(subTrigger);
|
|
255
|
+
// The menubar did NOT switch to the previous menu.
|
|
256
|
+
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
257
|
+
expect(triggers[1].getAttribute('data-state')).toBe('closed');
|
|
258
|
+
expect(triggers[2].getAttribute('data-state')).toBe('closed');
|
|
259
|
+
});
|
|
260
|
+
it('hovering a sibling item closes the open sub', () => {
|
|
261
|
+
setup({ withSub: true });
|
|
262
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
263
|
+
triggers[0].click();
|
|
264
|
+
flushSync();
|
|
265
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
266
|
+
subTrigger.click();
|
|
267
|
+
flushSync();
|
|
268
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
|
|
269
|
+
const newItem = Array.from(target.querySelectorAll('.sp-menubar-content .sp-dropdown-menu-item')).find((el) => el.textContent?.trim() === 'New');
|
|
270
|
+
newItem.dispatchEvent(new MouseEvent('pointerenter', { clientX: 0, clientY: 0 }));
|
|
271
|
+
flushSync();
|
|
272
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
273
|
+
// The menubar menu itself stayed open.
|
|
274
|
+
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
275
|
+
});
|
|
276
|
+
it('escape with focus in the menu and a hover-opened sub closes the whole menu in ONE keypress and refocuses the menubar trigger', () => {
|
|
277
|
+
setup({ withSub: true });
|
|
278
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
279
|
+
triggers[0].click();
|
|
280
|
+
flushSync();
|
|
281
|
+
// Autofocus lands on the first parent row.
|
|
282
|
+
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
283
|
+
// Pointer path: the sub opens without moving focus.
|
|
284
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
285
|
+
subTrigger.click();
|
|
286
|
+
flushSync();
|
|
287
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeTruthy();
|
|
288
|
+
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
289
|
+
// ONE Escape: the sub layer defers (focus is NOT inside the sub) and the
|
|
290
|
+
// menubar layer closes the whole menu — focus returns to the MENUBAR
|
|
291
|
+
// trigger, never warped onto the sub trigger.
|
|
292
|
+
pressKey('Escape');
|
|
293
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
294
|
+
expect(target.querySelector('[role="menu"]')).toBeNull();
|
|
295
|
+
expect(document.activeElement).toBe(triggers[0]);
|
|
296
|
+
});
|
|
297
|
+
it('escape with focus INSIDE the sub closes only the sub (menu stays open)', () => {
|
|
298
|
+
setup({ withSub: true });
|
|
299
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
300
|
+
triggers[0].click();
|
|
301
|
+
flushSync();
|
|
302
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
303
|
+
subTrigger.focus();
|
|
304
|
+
pressKey('ArrowRight');
|
|
305
|
+
expect(document.activeElement?.textContent?.trim()).toBe('Email');
|
|
306
|
+
pressKey('Escape');
|
|
307
|
+
expect(target.querySelector('.sp-dropdown-menu-sub-content')).toBeNull();
|
|
308
|
+
expect(document.activeElement).toBe(subTrigger);
|
|
309
|
+
// The menubar menu itself stayed open.
|
|
310
|
+
expect(triggers[0].getAttribute('data-state')).toBe('open');
|
|
311
|
+
});
|
|
312
|
+
it('parent roving skips sub items while the sub is open', () => {
|
|
313
|
+
setup({ withSub: true });
|
|
314
|
+
const triggers = target.querySelectorAll('[data-menubar-trigger]');
|
|
315
|
+
triggers[0].click();
|
|
316
|
+
flushSync();
|
|
317
|
+
const subTrigger = target.querySelector('.sp-dropdown-sub-trigger');
|
|
318
|
+
// Pointer open keeps focus on the trigger row.
|
|
319
|
+
subTrigger.click();
|
|
320
|
+
flushSync();
|
|
321
|
+
subTrigger.focus();
|
|
322
|
+
// Share is the last row: ArrowDown loops to the first PARENT item,
|
|
323
|
+
// not into the open sub.
|
|
324
|
+
pressKey('ArrowDown');
|
|
325
|
+
expect(document.activeElement?.textContent?.trim()).toBe('New');
|
|
326
|
+
});
|
|
232
327
|
});
|
|
233
328
|
});
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { EscapeLayerState } from '../../../internal/escape-layer/index.js'
|
|
5
5
|
import { FloatingLayerState } from '../../../internal/floating-layer/index.js'
|
|
6
6
|
import { isBrowser, kbd } from '../../../internal/helpers/index.js'
|
|
7
|
+
import { getOwnLevelMenuItems, isOwnLevelMenuItem } from '../../../internal/menu-context/index.js'
|
|
7
8
|
import { Portal } from '../../../internal/portal/index.js'
|
|
8
9
|
import { PresenceLayerState } from '../../../internal/presence-layer/index.js'
|
|
9
10
|
import { RovingFocusGroup } from '../../../internal/roving-focus/index.js'
|
|
@@ -28,6 +29,9 @@
|
|
|
28
29
|
orientation: 'vertical',
|
|
29
30
|
loop: true,
|
|
30
31
|
candidateSelector: '[data-roving-focus-item]',
|
|
32
|
+
// Sub content renders inline inside this content: exclude its rows from
|
|
33
|
+
// the parent collection (the sub content has its own roving group).
|
|
34
|
+
candidateFilter: (el) => !contentEl || isOwnLevelMenuItem(contentEl, el),
|
|
31
35
|
})
|
|
32
36
|
|
|
33
37
|
const presence = new PresenceLayerState({
|
|
@@ -79,10 +83,8 @@
|
|
|
79
83
|
floating.setElements(menu.triggerEl, contentEl)
|
|
80
84
|
floating.startAutoUpdate()
|
|
81
85
|
|
|
82
|
-
// Focus first interactive item
|
|
83
|
-
const firstItem = contentEl
|
|
84
|
-
'[data-roving-focus-item]:not([data-disabled])',
|
|
85
|
-
)
|
|
86
|
+
// Focus first interactive item (own level only — never a sub row)
|
|
87
|
+
const firstItem = getOwnLevelMenuItems(contentEl)[0]
|
|
86
88
|
firstItem?.focus()
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -101,9 +103,8 @@
|
|
|
101
103
|
if (!isBrowser) return
|
|
102
104
|
if (!contentEl) return
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
106
|
+
// Typeahead collection scoped to this level (sub rows excluded).
|
|
107
|
+
const items = getOwnLevelMenuItems(contentEl)
|
|
107
108
|
|
|
108
109
|
switch (event.key) {
|
|
109
110
|
case kbd.ARROW_DOWN: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menubar-content.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/menubar/components/menubar-content.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"menubar-content.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/menubar/components/menubar-content.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AA8MtD,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { MenubarMenuProps } from '../types.js'
|
|
3
|
-
import { setMenuContext } from '../../../internal/menu-context/index.js'
|
|
3
|
+
import { createMenuContext, setMenuContext } from '../../../internal/menu-context/index.js'
|
|
4
4
|
import {
|
|
5
5
|
createMenubarMenuIds,
|
|
6
6
|
getMenubarContext,
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
|
|
20
20
|
// Set the shared MenuContext so dropdown-menu items (Item, CheckboxItem, etc.)
|
|
21
21
|
// can call close() correctly
|
|
22
|
-
setMenuContext(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
setMenuContext(
|
|
23
|
+
createMenuContext({
|
|
24
|
+
close() {
|
|
25
|
+
root.closeMenu()
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
27
29
|
|
|
28
30
|
setMenubarMenuContext({
|
|
29
31
|
value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menubar-menu.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/menubar/components/menubar-menu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"menubar-menu.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/menubar/components/menubar-menu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAwDnD,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { default as CheckboxItem } from '../dropdown-menu/components/dropdown-menu-checkbox-item.svelte';
|
|
2
2
|
export { default as Item } from '../dropdown-menu/components/dropdown-menu-item.svelte';
|
|
3
3
|
export { default as Label } from '../dropdown-menu/components/dropdown-menu-label.svelte';
|
|
4
|
+
export { default as RadioGroup } from '../dropdown-menu/components/dropdown-menu-radio-group.svelte';
|
|
5
|
+
export { default as RadioItem } from '../dropdown-menu/components/dropdown-menu-radio-item.svelte';
|
|
4
6
|
export { default as Separator } from '../dropdown-menu/components/dropdown-menu-separator.svelte';
|
|
5
7
|
export { default as SubContent } from '../dropdown-menu/components/dropdown-menu-sub-content.svelte';
|
|
6
8
|
export { default as SubTrigger } from '../dropdown-menu/components/dropdown-menu-sub-trigger.svelte';
|
|
7
9
|
export { default as Sub } from '../dropdown-menu/components/dropdown-menu-sub.svelte';
|
|
8
|
-
export type { DropdownMenuCheckboxItemProps as MenubarCheckboxItemProps, DropdownMenuItemProps as MenubarItemProps, DropdownMenuLabelProps as MenubarLabelProps, DropdownMenuSeparatorProps as MenubarSeparatorProps, DropdownMenuSubContentProps as MenubarSubContentProps, DropdownMenuSubProps as MenubarSubProps, DropdownMenuSubTriggerProps as MenubarSubTriggerProps, } from '../dropdown-menu/types.js';
|
|
10
|
+
export type { DropdownMenuCheckboxItemProps as MenubarCheckboxItemProps, DropdownMenuItemProps as MenubarItemProps, DropdownMenuLabelProps as MenubarLabelProps, DropdownMenuRadioGroupProps as MenubarRadioGroupProps, DropdownMenuRadioItemProps as MenubarRadioItemProps, DropdownMenuSeparatorProps as MenubarSeparatorProps, DropdownMenuSubContentProps as MenubarSubContentProps, DropdownMenuSubProps as MenubarSubProps, DropdownMenuSubTriggerProps as MenubarSubTriggerProps, } from '../dropdown-menu/types.js';
|
|
9
11
|
export { default as Content } from './components/menubar-content.svelte';
|
|
10
12
|
export { default as Menu } from './components/menubar-menu.svelte';
|
|
11
13
|
export { default as Root } from './components/menubar-root.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/menubar/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gEAAgE,CAAA;AACxG,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,uDAAuD,CAAA;AACvF,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wDAAwD,CAAA;AACzF,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,4DAA4D,CAAA;AAEjG,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8DAA8D,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8DAA8D,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,sDAAsD,CAAA;AAErF,YAAY,EACV,6BAA6B,IAAI,wBAAwB,EACzD,qBAAqB,IAAI,gBAAgB,EACzC,sBAAsB,IAAI,iBAAiB,EAC3C,0BAA0B,IAAI,qBAAqB,EACnD,2BAA2B,IAAI,sBAAsB,EACrD,oBAAoB,IAAI,eAAe,EACvC,2BAA2B,IAAI,sBAAsB,GACtD,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,qCAAqC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAElE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,qCAAqC,CAAA;AAExE,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,GACpB,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/menubar/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gEAAgE,CAAA;AACxG,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,uDAAuD,CAAA;AACvF,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wDAAwD,CAAA;AACzF,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8DAA8D,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,6DAA6D,CAAA;AAClG,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,4DAA4D,CAAA;AAEjG,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8DAA8D,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,8DAA8D,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,sDAAsD,CAAA;AAErF,YAAY,EACV,6BAA6B,IAAI,wBAAwB,EACzD,qBAAqB,IAAI,gBAAgB,EACzC,sBAAsB,IAAI,iBAAiB,EAC3C,2BAA2B,IAAI,sBAAsB,EACrD,0BAA0B,IAAI,qBAAqB,EACnD,0BAA0B,IAAI,qBAAqB,EACnD,2BAA2B,IAAI,sBAAsB,EACrD,oBAAoB,IAAI,eAAe,EACvC,2BAA2B,IAAI,sBAAsB,GACtD,MAAM,2BAA2B,CAAA;AAElC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,qCAAqC,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,kCAAkC,CAAA;AAElE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,qCAAqC,CAAA;AAExE,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,GACpB,MAAM,YAAY,CAAA"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export { default as CheckboxItem } from '../dropdown-menu/components/dropdown-menu-checkbox-item.svelte';
|
|
3
3
|
export { default as Item } from '../dropdown-menu/components/dropdown-menu-item.svelte';
|
|
4
4
|
export { default as Label } from '../dropdown-menu/components/dropdown-menu-label.svelte';
|
|
5
|
+
export { default as RadioGroup } from '../dropdown-menu/components/dropdown-menu-radio-group.svelte';
|
|
6
|
+
export { default as RadioItem } from '../dropdown-menu/components/dropdown-menu-radio-item.svelte';
|
|
5
7
|
export { default as Separator } from '../dropdown-menu/components/dropdown-menu-separator.svelte';
|
|
6
8
|
export { default as SubContent } from '../dropdown-menu/components/dropdown-menu-sub-content.svelte';
|
|
7
9
|
export { default as SubTrigger } from '../dropdown-menu/components/dropdown-menu-sub-trigger.svelte';
|
|
@@ -81,4 +81,33 @@ describe('escapeLayer', () => {
|
|
|
81
81
|
// Should not throw after cleanup
|
|
82
82
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
83
83
|
});
|
|
84
|
+
it('resolves behavior at keydown time (getter can flip between keypresses)', () => {
|
|
85
|
+
// A layer may pass `behavior` as a getter and decide per keypress — the
|
|
86
|
+
// submenu layer closes itself only when focus lives inside its content,
|
|
87
|
+
// and defers to the parent menu layer otherwise.
|
|
88
|
+
let topBehavior = 'close';
|
|
89
|
+
const onBottom = vi.fn();
|
|
90
|
+
const onTop = vi.fn();
|
|
91
|
+
const bottom = new EscapeLayerState({ enabled: true, onEscapeKeydown: onBottom });
|
|
92
|
+
const top = new EscapeLayerState({
|
|
93
|
+
enabled: true,
|
|
94
|
+
get behavior() {
|
|
95
|
+
return topBehavior;
|
|
96
|
+
},
|
|
97
|
+
onEscapeKeydown: onTop,
|
|
98
|
+
});
|
|
99
|
+
bottom.mount();
|
|
100
|
+
top.mount();
|
|
101
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
102
|
+
expect(onTop).toHaveBeenCalledOnce();
|
|
103
|
+
expect(onBottom).not.toHaveBeenCalled();
|
|
104
|
+
// Same mounted layer, new answer from the getter: the keypress falls
|
|
105
|
+
// through to the bottom layer in the SAME stack state.
|
|
106
|
+
topBehavior = 'defer-otherwise-close';
|
|
107
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
108
|
+
expect(onTop).toHaveBeenCalledOnce();
|
|
109
|
+
expect(onBottom).toHaveBeenCalledOnce();
|
|
110
|
+
top.unmount();
|
|
111
|
+
bottom.unmount();
|
|
112
|
+
});
|
|
84
113
|
});
|
|
@@ -10,7 +10,11 @@ export declare class EscapeLayerState {
|
|
|
10
10
|
constructor(props: EscapeLayerProps);
|
|
11
11
|
mount(): void;
|
|
12
12
|
unmount(): void;
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Update behavior dynamically. Writes through to `props.behavior` (behavior
|
|
15
|
+
* is resolved live at keydown time); only meaningful when `behavior` was
|
|
16
|
+
* provided as a plain value, not a getter.
|
|
17
|
+
*/
|
|
14
18
|
updateBehavior(behavior: EscapeKeydownBehavior): void;
|
|
15
19
|
}
|
|
16
20
|
/** Reset the layer stack (for testing only). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape-layer.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/escape-layer/escape-layer.svelte.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"escape-layer.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/escape-layer/escape-layer.svelte.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AA4CzE,qBAAa,gBAAgB;IAC3B,KAAK,EAAE,gBAAgB,CAAA;gBAEX,KAAK,EAAE,gBAAgB;IAInC,KAAK,IAAI,IAAI;IAYb,OAAO,IAAI,IAAI;IAcf;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;CAGtD;AAED,gDAAgD;AAChD,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { isBrowser, kbd } from '../helpers/index.js';
|
|
8
8
|
const layerStack = [];
|
|
9
|
+
/**
|
|
10
|
+
* Behavior is resolved at KEYDOWN time, not mount time, so a layer can pass
|
|
11
|
+
* `behavior` as a getter and decide per keypress — e.g. the submenu layer
|
|
12
|
+
* closes only itself when focus lives inside its content, and defers to the
|
|
13
|
+
* parent menu layer otherwise (APG: Escape closes the menu containing focus).
|
|
14
|
+
*/
|
|
15
|
+
function resolveBehavior(entry) {
|
|
16
|
+
return entry.state.props.behavior ?? 'close';
|
|
17
|
+
}
|
|
9
18
|
function handleGlobalKeydown(event) {
|
|
10
19
|
if (event.key !== kbd.ESCAPE)
|
|
11
20
|
return;
|
|
@@ -14,18 +23,19 @@ function handleGlobalKeydown(event) {
|
|
|
14
23
|
// Find the topmost layer that should handle the escape
|
|
15
24
|
for (let i = layerStack.length - 1; i >= 0; i--) {
|
|
16
25
|
const entry = layerStack[i];
|
|
17
|
-
|
|
26
|
+
const behavior = resolveBehavior(entry);
|
|
27
|
+
if (behavior === 'close') {
|
|
18
28
|
entry.state.props.onEscapeKeydown?.(event);
|
|
19
29
|
return;
|
|
20
30
|
}
|
|
21
|
-
if (
|
|
31
|
+
if (behavior === 'ignore') {
|
|
22
32
|
return;
|
|
23
33
|
}
|
|
24
34
|
// 'defer-otherwise-close' — continue to next layer
|
|
25
35
|
}
|
|
26
36
|
// If all layers deferred, the bottom layer handles it
|
|
27
37
|
const bottom = layerStack[0];
|
|
28
|
-
if (bottom && bottom
|
|
38
|
+
if (bottom && resolveBehavior(bottom) === 'defer-otherwise-close') {
|
|
29
39
|
bottom.state.props.onEscapeKeydown?.(event);
|
|
30
40
|
}
|
|
31
41
|
}
|
|
@@ -39,15 +49,11 @@ export class EscapeLayerState {
|
|
|
39
49
|
return;
|
|
40
50
|
if (!this.props.enabled)
|
|
41
51
|
return;
|
|
42
|
-
const entry = {
|
|
43
|
-
state: this,
|
|
44
|
-
behavior: this.props.behavior ?? 'close',
|
|
45
|
-
};
|
|
46
52
|
// Add global listener if this is the first layer
|
|
47
53
|
if (layerStack.length === 0) {
|
|
48
54
|
document.addEventListener('keydown', handleGlobalKeydown, true);
|
|
49
55
|
}
|
|
50
|
-
layerStack.push(
|
|
56
|
+
layerStack.push({ state: this });
|
|
51
57
|
}
|
|
52
58
|
unmount() {
|
|
53
59
|
if (!isBrowser)
|
|
@@ -61,12 +67,13 @@ export class EscapeLayerState {
|
|
|
61
67
|
document.removeEventListener('keydown', handleGlobalKeydown, true);
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Update behavior dynamically. Writes through to `props.behavior` (behavior
|
|
72
|
+
* is resolved live at keydown time); only meaningful when `behavior` was
|
|
73
|
+
* provided as a plain value, not a getter.
|
|
74
|
+
*/
|
|
65
75
|
updateBehavior(behavior) {
|
|
66
|
-
|
|
67
|
-
if (entry) {
|
|
68
|
-
entry.behavior = behavior;
|
|
69
|
-
}
|
|
76
|
+
this.props.behavior = behavior;
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
79
|
/** Reset the layer stack (for testing only). */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { getMenuContext, setMenuContext } from './menu-context.svelte.js';
|
|
2
|
-
export type { MenuContext } from './menu-context.svelte.js';
|
|
1
|
+
export { createMenuContext, getMenuContext, getOwnLevelMenuItems, isOwnLevelMenuItem, MENU_SUB_CONTENT_SELECTOR, setMenuContext, } from './menu-context.svelte.js';
|
|
2
|
+
export type { MenuContext, MenuSubHandle } from './menu-context.svelte.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/menu-context/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/menu-context/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,GACf,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { getMenuContext, setMenuContext } from './menu-context.svelte.js';
|
|
1
|
+
export { createMenuContext, getMenuContext, getOwnLevelMenuItems, isOwnLevelMenuItem, MENU_SUB_CONTENT_SELECTOR, setMenuContext, } from './menu-context.svelte.js';
|
|
@@ -1,10 +1,72 @@
|
|
|
1
|
+
import type { Point } from '../grace-area/types.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
3
|
+
* Handle a submenu registers on its parent menu level so that sibling
|
|
4
|
+
* rows can coordinate pointer behavior (macOS/Radix model: hovering
|
|
5
|
+
* another item of the parent menu closes any open submenu, unless the
|
|
6
|
+
* pointer is traveling through the submenu's grace area).
|
|
7
|
+
*/
|
|
8
|
+
export interface MenuSubHandle {
|
|
9
|
+
/** Whether `target` belongs to this submenu (its trigger or its content). */
|
|
10
|
+
ownsTarget: (target: EventTarget | null) => boolean;
|
|
11
|
+
/** Whether the pointer is currently traveling through this submenu's grace area. */
|
|
12
|
+
isPointerInGrace: (point: Point) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Closes the submenu (pointer path). The submenu still restores focus to
|
|
15
|
+
* its trigger when keyboard focus was inside its content (mixed modality),
|
|
16
|
+
* so focus is never orphaned on `document.body`.
|
|
17
|
+
*/
|
|
18
|
+
closeSub: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Shared menu context used by DropdownMenu, ContextMenu and Menubar menus.
|
|
22
|
+
* Provides the minimal interface needed by shared sub-components
|
|
23
|
+
* (Item, CheckboxItem, Sub, etc.).
|
|
4
24
|
*/
|
|
5
25
|
export interface MenuContext {
|
|
6
26
|
close: () => void;
|
|
27
|
+
/** Registers a submenu living at this menu level. Returns an unregister fn. */
|
|
28
|
+
registerSub: (sub: MenuSubHandle) => () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Pointer coordination — called on `pointerenter` of any item row at this
|
|
31
|
+
* level. Closes every registered submenu that neither owns the entered
|
|
32
|
+
* element nor has the pointer inside its grace area.
|
|
33
|
+
*/
|
|
34
|
+
onItemEnter: (event: PointerEvent) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Single-open-sub invariant (Radix semantics: one open sub per menu level).
|
|
37
|
+
* Called by a submenu right before it opens: force-closes every OTHER
|
|
38
|
+
* registered submenu at this level, including one spared by a grace-area
|
|
39
|
+
* check (its grace tracking and safety timer are cancelled by its
|
|
40
|
+
* `closeSub`). Covers keyboard opens, which never go through `onItemEnter`.
|
|
41
|
+
*/
|
|
42
|
+
closeOtherSubs: (keep: MenuSubHandle) => void;
|
|
7
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Builds a `MenuContext` with a per-menu submenu registry.
|
|
46
|
+
* SSR-safe: no DOM access happens here — only event handlers touch the DOM.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createMenuContext(options: {
|
|
49
|
+
close: () => void;
|
|
50
|
+
}): MenuContext;
|
|
8
51
|
export declare function setMenuContext(ctx: MenuContext): void;
|
|
9
52
|
export declare function getMenuContext(): MenuContext;
|
|
53
|
+
/** Class hook of the inline submenu content (shared by every menu family). */
|
|
54
|
+
export declare const MENU_SUB_CONTENT_SELECTOR = ".sp-dropdown-menu-sub-content";
|
|
55
|
+
/**
|
|
56
|
+
* Whether `candidate` belongs to `container`'s own menu level.
|
|
57
|
+
*
|
|
58
|
+
* Submenu content renders INLINE inside the parent menu content (no portal),
|
|
59
|
+
* so a naive `querySelectorAll('[data-roving-focus-item]')` on the parent
|
|
60
|
+
* leaks the submenu's items into the parent's roving/typeahead collections.
|
|
61
|
+
* A candidate is "own level" when its closest sub-content ancestor is either
|
|
62
|
+
* absent, the container itself (items directly inside a sub-content), or
|
|
63
|
+
* outside the container.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isOwnLevelMenuItem(container: HTMLElement, candidate: HTMLElement): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the enabled roving candidates that belong to `container`'s own
|
|
68
|
+
* menu level (excluding rows nested in an open submenu). Used for roving
|
|
69
|
+
* focus, typeahead and open-autofocus in every menu content.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getOwnLevelMenuItems(container: HTMLElement): HTMLElement[];
|
|
10
72
|
//# sourceMappingURL=menu-context.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-context.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/menu-context/menu-context.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-context.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/menu-context/menu-context.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAMnD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,OAAO,CAAA;IACnD,oFAAoF;IACpF,gBAAgB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;IAC3C;;;;OAIG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,+EAA+E;IAC/E,WAAW,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,MAAM,IAAI,CAAA;IAC/C;;;;OAIG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IAC1C;;;;;;OAMG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;CAC9C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,WAAW,CA0B7E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAErD;AAED,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,kCAAkC,CAAA;AAExE;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,GAAG,OAAO,CAG1F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,WAAW,GAAG,WAAW,EAAE,CAI1E"}
|