|
1 |
| -<svelte:options runes={false} /> |
| 1 | +<svelte:options runes={true} /> |
2 | 2 |
|
3 | 3 | <MenuSurface
|
4 | 4 | bind:this={element}
|
|
8 | 8 | 'mdc-menu': true,
|
9 | 9 | })}
|
10 | 10 | bind:open
|
11 |
| - {...$$restProps} |
| 11 | + bind:anchorElement |
| 12 | + {...restProps} |
12 | 13 | onkeydown={(e) => {
|
13 | 14 | handleKeydown(e);
|
14 |
| - $$restProps.onkeydown?.(e); |
| 15 | + restProps.onkeydown?.(e); |
15 | 16 | }}
|
16 | 17 | onSMUIMenuSurfaceOpened={(e) => {
|
17 | 18 | if (instance) {
|
18 | 19 | instance.handleMenuSurfaceOpened();
|
19 | 20 | }
|
20 |
| - $$restProps.onSMUIMenuSurfaceOpened?.(e); |
| 21 | + restProps.onSMUIMenuSurfaceOpened?.(e); |
21 | 22 | }}
|
22 | 23 | onSMUIListAction={(e) => {
|
23 |
| - if (instance) { |
| 24 | + if (instance && listAccessor) { |
24 | 25 | instance.handleItemAction(
|
25 | 26 | listAccessor.getOrderedList()[e.detail.index].element,
|
26 | 27 | );
|
27 | 28 | }
|
28 |
| - $$restProps.onSMUIListAction?.(e); |
29 |
| - }}><slot /></MenuSurface |
| 29 | + restProps.onSMUIListAction?.(e); |
| 30 | + }} |
| 31 | + >{#if children}{@render children()}{/if}</MenuSurface |
30 | 32 | >
|
31 | 33 |
|
32 | 34 | <script lang="ts">
|
33 | 35 | import { MDCMenuFoundation, cssClasses } from '@material/menu';
|
34 | 36 | import { ponyfill } from '@material/dom';
|
35 |
| - import type { ComponentProps } from 'svelte'; |
| 37 | + import type { ComponentProps, Snippet } from 'svelte'; |
36 | 38 | import { onMount, getContext, setContext } from 'svelte';
|
37 | 39 | import type { ActionArray } from '@smui/common/internal';
|
38 | 40 | import { classMap, dispatch } from '@smui/common/internal';
|
|
45 | 47 | const { closest } = ponyfill;
|
46 | 48 |
|
47 | 49 | type OwnProps = {
|
| 50 | + /** |
| 51 | + * An array of Action or [Action, ActionProps] to be applied to the element. |
| 52 | + */ |
48 | 53 | use?: ActionArray;
|
| 54 | + /** |
| 55 | + * A space separated list of CSS classes. |
| 56 | + */ |
49 | 57 | class?: string;
|
| 58 | + /** |
| 59 | + * Whether the menu is open. |
| 60 | + */ |
50 | 61 | open?: boolean;
|
51 |
| - }; |
52 |
| - type $$Props = OwnProps & |
53 |
| - Omit<ComponentProps<typeof MenuSurface>, keyof OwnProps>; |
54 | 62 |
|
55 |
| - export let use: ActionArray = []; |
56 |
| - let className = ''; |
57 |
| - export { className as class }; |
58 |
| - export let open = false; |
| 63 | + children?: Snippet; |
| 64 | + }; |
| 65 | + let { |
| 66 | + use = [], |
| 67 | + class: className = '', |
| 68 | + open = $bindable(false), |
| 69 | + anchorElement = $bindable(), |
| 70 | + children, |
| 71 | + ...restProps |
| 72 | + }: OwnProps & |
| 73 | + Omit<ComponentProps<typeof MenuSurface>, keyof OwnProps> = $props(); |
59 | 74 |
|
60 | 75 | let element: MenuSurface;
|
61 |
| - let instance: MDCMenuFoundation; |
62 |
| - let menuSurfaceAccessor: SMUIMenuSurfaceAccessor; |
63 |
| - let listAccessor: SMUIListAccessor; |
| 76 | + let instance: MDCMenuFoundation | undefined = $state(); |
| 77 | + let menuSurfaceAccessor: SMUIMenuSurfaceAccessor | undefined = $state(); |
| 78 | + let listAccessor: SMUIListAccessor | undefined = $state(); |
64 | 79 |
|
65 | 80 | setContext('SMUI:menu-surface:mount', (accessor: SMUIMenuSurfaceAccessor) => {
|
66 | 81 | if (!menuSurfaceAccessor) {
|
|
87 | 102 | onMount(() => {
|
88 | 103 | instance = new MDCMenuFoundation({
|
89 | 104 | addClassToElementAtIndex: (index, className) => {
|
| 105 | + if (listAccessor == null) { |
| 106 | + throw new Error('List accessor is undefined.'); |
| 107 | + } |
90 | 108 | listAccessor.addClassForElementIndex(index, className);
|
91 | 109 | },
|
92 | 110 | removeClassFromElementAtIndex: (index, className) => {
|
| 111 | + if (listAccessor == null) { |
| 112 | + throw new Error('List accessor is undefined.'); |
| 113 | + } |
93 | 114 | listAccessor.removeClassForElementIndex(index, className);
|
94 | 115 | },
|
95 | 116 | addAttributeToElementAtIndex: (index, attr, value) => {
|
| 117 | + if (listAccessor == null) { |
| 118 | + throw new Error('List accessor is undefined.'); |
| 119 | + } |
96 | 120 | listAccessor.setAttributeForElementIndex(index, attr, value);
|
97 | 121 | },
|
98 | 122 | removeAttributeFromElementAtIndex: (index, attr) => {
|
| 123 | + if (listAccessor == null) { |
| 124 | + throw new Error('List accessor is undefined.'); |
| 125 | + } |
99 | 126 | listAccessor.removeAttributeForElementIndex(index, attr);
|
100 | 127 | },
|
101 |
| - getAttributeFromElementAtIndex: (index, attr) => |
102 |
| - listAccessor.getAttributeFromElementIndex(index, attr), |
| 128 | + getAttributeFromElementAtIndex: (index, attr) => { |
| 129 | + if (listAccessor == null) { |
| 130 | + throw new Error('List accessor is undefined.'); |
| 131 | + } |
| 132 | + return listAccessor.getAttributeFromElementIndex(index, attr); |
| 133 | + }, |
103 | 134 | elementContainsClass: (element, className) =>
|
104 | 135 | element.classList.contains(className),
|
105 | 136 | closeSurface: (skipRestoreFocus) => {
|
106 |
| - menuSurfaceAccessor.closeProgrammatic(skipRestoreFocus); |
| 137 | + menuSurfaceAccessor?.closeProgrammatic(skipRestoreFocus); |
107 | 138 | dispatch(getElement(), 'SMUIMenuClosedProgrammatically');
|
108 | 139 | },
|
109 |
| - getElementIndex: (element) => |
110 |
| - listAccessor |
| 140 | + getElementIndex: (element) => { |
| 141 | + if (listAccessor == null) { |
| 142 | + throw new Error('List accessor is undefined.'); |
| 143 | + } |
| 144 | + return listAccessor |
111 | 145 | .getOrderedList()
|
112 | 146 | .map((accessor) => accessor.element)
|
113 |
| - .indexOf(element), |
114 |
| - notifySelected: (evtData) => |
| 147 | + .indexOf(element); |
| 148 | + }, |
| 149 | + notifySelected: (evtData) => { |
| 150 | + if (listAccessor == null) { |
| 151 | + throw new Error('List accessor is undefined.'); |
| 152 | + } |
115 | 153 | dispatch(getElement(), 'SMUIMenuSelected', {
|
116 | 154 | index: evtData.index,
|
117 | 155 | item: listAccessor.getOrderedList()[evtData.index].element,
|
118 |
| - }), |
119 |
| - getMenuItemCount: () => listAccessor.items.length, |
120 |
| - focusItemAtIndex: (index) => listAccessor.focusItemAtIndex(index), |
121 |
| - focusListRoot: () => |
122 |
| - 'focus' in listAccessor.element && |
123 |
| - (listAccessor.element as HTMLInputElement).focus(), |
124 |
| - isSelectableItemAtIndex: (index) => |
125 |
| - !!closest( |
| 156 | + }); |
| 157 | + }, |
| 158 | + getMenuItemCount: () => { |
| 159 | + if (listAccessor == null) { |
| 160 | + throw new Error('List accessor is undefined.'); |
| 161 | + } |
| 162 | + return listAccessor.items.length; |
| 163 | + }, |
| 164 | + focusItemAtIndex: (index) => { |
| 165 | + if (listAccessor == null) { |
| 166 | + throw new Error('List accessor is undefined.'); |
| 167 | + } |
| 168 | + listAccessor.focusItemAtIndex(index); |
| 169 | + }, |
| 170 | + focusListRoot: () => { |
| 171 | + if (listAccessor == null) { |
| 172 | + throw new Error('List accessor is undefined.'); |
| 173 | + } |
| 174 | + if ('focus' in listAccessor.element) { |
| 175 | + (listAccessor.element as HTMLInputElement).focus(); |
| 176 | + } |
| 177 | + }, |
| 178 | + isSelectableItemAtIndex: (index) => { |
| 179 | + if (listAccessor == null) { |
| 180 | + throw new Error('List accessor is undefined.'); |
| 181 | + } |
| 182 | + return !!closest( |
126 | 183 | listAccessor.getOrderedList()[index].element,
|
127 | 184 | `.${cssClasses.MENU_SELECTION_GROUP}`,
|
128 |
| - ), |
| 185 | + ); |
| 186 | + }, |
129 | 187 | getSelectedSiblingOfItemAtIndex: (index) => {
|
| 188 | + if (listAccessor == null) { |
| 189 | + throw new Error('List accessor is undefined.'); |
| 190 | + } |
130 | 191 | const orderedList = listAccessor.getOrderedList();
|
131 | 192 | const selectionGroupEl = closest(
|
132 | 193 | orderedList[index].element,
|
|
146 | 207 | instance.init();
|
147 | 208 |
|
148 | 209 | return () => {
|
149 |
| - SMUIMenuUnmount && SMUIMenuUnmount(instance); |
| 210 | + if (SMUIMenuUnmount && instance) { |
| 211 | + SMUIMenuUnmount(instance); |
| 212 | + } |
150 | 213 |
|
151 |
| - instance.destroy(); |
| 214 | + instance?.destroy(); |
152 | 215 | };
|
153 | 216 | });
|
154 | 217 |
|
|
165 | 228 | }
|
166 | 229 |
|
167 | 230 | export function setDefaultFocusState(focusState: DefaultFocusState) {
|
| 231 | + if (instance == null) { |
| 232 | + throw new Error('Instance is undefined.'); |
| 233 | + } |
168 | 234 | instance.setDefaultFocusState(focusState);
|
169 | 235 | }
|
170 | 236 |
|
171 | 237 | export function getSelectedIndex() {
|
| 238 | + if (instance == null) { |
| 239 | + throw new Error('Instance is undefined.'); |
| 240 | + } |
172 | 241 | return instance.getSelectedIndex();
|
173 | 242 | }
|
174 | 243 |
|
|
0 commit comments