@spectrum-web-components/menu 1.1.0 → 1.1.2

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.
Files changed (170) hide show
  1. package/package.json +12 -12
  2. package/sp-menu-divider.d.ts +6 -0
  3. package/sp-menu-divider.dev.js +5 -0
  4. package/sp-menu-divider.dev.js.map +7 -0
  5. package/sp-menu-divider.js +2 -0
  6. package/sp-menu-divider.js.map +7 -0
  7. package/sp-menu-group.d.ts +6 -0
  8. package/sp-menu-group.dev.js +5 -0
  9. package/sp-menu-group.dev.js.map +7 -0
  10. package/sp-menu-group.js +2 -0
  11. package/sp-menu-group.js.map +7 -0
  12. package/sp-menu-item.d.ts +6 -0
  13. package/sp-menu-item.dev.js +5 -0
  14. package/sp-menu-item.dev.js.map +7 -0
  15. package/sp-menu-item.js +2 -0
  16. package/sp-menu-item.js.map +7 -0
  17. package/sp-menu.d.ts +6 -0
  18. package/sp-menu.dev.js +5 -0
  19. package/sp-menu.dev.js.map +7 -0
  20. package/sp-menu.js +2 -0
  21. package/sp-menu.js.map +7 -0
  22. package/src/Menu.d.ts +118 -0
  23. package/src/Menu.dev.js +740 -0
  24. package/src/Menu.dev.js.map +7 -0
  25. package/src/Menu.js +8 -0
  26. package/src/Menu.js.map +7 -0
  27. package/src/MenuDivider.d.ts +13 -0
  28. package/src/MenuDivider.dev.js +19 -0
  29. package/src/MenuDivider.dev.js.map +7 -0
  30. package/src/MenuDivider.js +2 -0
  31. package/src/MenuDivider.js.map +7 -0
  32. package/src/MenuGroup.d.ts +18 -0
  33. package/src/MenuGroup.dev.js +78 -0
  34. package/src/MenuGroup.dev.js.map +7 -0
  35. package/src/MenuGroup.js +7 -0
  36. package/src/MenuGroup.js.map +7 -0
  37. package/src/MenuItem.d.ts +125 -0
  38. package/src/MenuItem.dev.js +518 -0
  39. package/src/MenuItem.dev.js.map +7 -0
  40. package/src/MenuItem.js +50 -0
  41. package/src/MenuItem.js.map +7 -0
  42. package/src/checkmark-overrides.css.d.ts +2 -0
  43. package/src/checkmark-overrides.css.dev.js +7 -0
  44. package/src/checkmark-overrides.css.dev.js.map +7 -0
  45. package/src/checkmark-overrides.css.js +4 -0
  46. package/src/checkmark-overrides.css.js.map +7 -0
  47. package/src/chevron-overrides.css.d.ts +2 -0
  48. package/src/chevron-overrides.css.dev.js +7 -0
  49. package/src/chevron-overrides.css.dev.js.map +7 -0
  50. package/src/chevron-overrides.css.js +4 -0
  51. package/src/chevron-overrides.css.js.map +7 -0
  52. package/src/index.d.ts +4 -0
  53. package/src/index.dev.js +6 -0
  54. package/src/index.dev.js.map +7 -0
  55. package/src/index.js +2 -0
  56. package/src/index.js.map +7 -0
  57. package/src/menu-divider-overrides.css.d.ts +2 -0
  58. package/src/menu-divider-overrides.css.dev.js +7 -0
  59. package/src/menu-divider-overrides.css.dev.js.map +7 -0
  60. package/src/menu-divider-overrides.css.js +4 -0
  61. package/src/menu-divider-overrides.css.js.map +7 -0
  62. package/src/menu-divider.css.d.ts +2 -0
  63. package/src/menu-divider.css.dev.js +7 -0
  64. package/src/menu-divider.css.dev.js.map +7 -0
  65. package/src/menu-divider.css.js +4 -0
  66. package/src/menu-divider.css.js.map +7 -0
  67. package/src/menu-group.css.d.ts +2 -0
  68. package/src/menu-group.css.dev.js +7 -0
  69. package/src/menu-group.css.dev.js.map +7 -0
  70. package/src/menu-group.css.js +4 -0
  71. package/src/menu-group.css.js.map +7 -0
  72. package/src/menu-item-overrides.css.d.ts +2 -0
  73. package/src/menu-item-overrides.css.dev.js +7 -0
  74. package/src/menu-item-overrides.css.dev.js.map +7 -0
  75. package/src/menu-item-overrides.css.js +4 -0
  76. package/src/menu-item-overrides.css.js.map +7 -0
  77. package/src/menu-item.css.d.ts +2 -0
  78. package/src/menu-item.css.dev.js +7 -0
  79. package/src/menu-item.css.dev.js.map +7 -0
  80. package/src/menu-item.css.js +4 -0
  81. package/src/menu-item.css.js.map +7 -0
  82. package/src/menu-overrides.css.d.ts +2 -0
  83. package/src/menu-overrides.css.dev.js +7 -0
  84. package/src/menu-overrides.css.dev.js.map +7 -0
  85. package/src/menu-overrides.css.js +4 -0
  86. package/src/menu-overrides.css.js.map +7 -0
  87. package/src/menu-sectionHeading-overrides.css.d.ts +2 -0
  88. package/src/menu-sectionHeading-overrides.css.dev.js +7 -0
  89. package/src/menu-sectionHeading-overrides.css.dev.js.map +7 -0
  90. package/src/menu-sectionHeading-overrides.css.js +4 -0
  91. package/src/menu-sectionHeading-overrides.css.js.map +7 -0
  92. package/src/menu.css.d.ts +2 -0
  93. package/src/menu.css.dev.js +7 -0
  94. package/src/menu.css.dev.js.map +7 -0
  95. package/src/menu.css.js +4 -0
  96. package/src/menu.css.js.map +7 -0
  97. package/src/spectrum-checkmark.css.d.ts +2 -0
  98. package/src/spectrum-checkmark.css.dev.js +7 -0
  99. package/src/spectrum-checkmark.css.dev.js.map +7 -0
  100. package/src/spectrum-checkmark.css.js +4 -0
  101. package/src/spectrum-checkmark.css.js.map +7 -0
  102. package/src/spectrum-chevron.css.d.ts +2 -0
  103. package/src/spectrum-chevron.css.dev.js +7 -0
  104. package/src/spectrum-chevron.css.dev.js.map +7 -0
  105. package/src/spectrum-chevron.css.js +4 -0
  106. package/src/spectrum-chevron.css.js.map +7 -0
  107. package/src/spectrum-menu-divider.css.d.ts +2 -0
  108. package/src/spectrum-menu-divider.css.dev.js +7 -0
  109. package/src/spectrum-menu-divider.css.dev.js.map +7 -0
  110. package/src/spectrum-menu-divider.css.js +4 -0
  111. package/src/spectrum-menu-divider.css.js.map +7 -0
  112. package/src/spectrum-menu-item.css.d.ts +2 -0
  113. package/src/spectrum-menu-item.css.dev.js +7 -0
  114. package/src/spectrum-menu-item.css.dev.js.map +7 -0
  115. package/src/spectrum-menu-item.css.js +4 -0
  116. package/src/spectrum-menu-item.css.js.map +7 -0
  117. package/src/spectrum-menu-sectionHeading.css.d.ts +2 -0
  118. package/src/spectrum-menu-sectionHeading.css.dev.js +7 -0
  119. package/src/spectrum-menu-sectionHeading.css.dev.js.map +7 -0
  120. package/src/spectrum-menu-sectionHeading.css.js +4 -0
  121. package/src/spectrum-menu-sectionHeading.css.js.map +7 -0
  122. package/src/spectrum-menu.css.d.ts +2 -0
  123. package/src/spectrum-menu.css.dev.js +7 -0
  124. package/src/spectrum-menu.css.dev.js.map +7 -0
  125. package/src/spectrum-menu.css.js +4 -0
  126. package/src/spectrum-menu.css.js.map +7 -0
  127. package/stories/index.js +82 -0
  128. package/stories/index.js.map +7 -0
  129. package/stories/menu-divider.stories.js +32 -0
  130. package/stories/menu-divider.stories.js.map +7 -0
  131. package/stories/menu-group.stories.js +144 -0
  132. package/stories/menu-group.stories.js.map +7 -0
  133. package/stories/menu-item.disconnected.stories.js +178 -0
  134. package/stories/menu-item.disconnected.stories.js.map +7 -0
  135. package/stories/menu-item.stories.js +73 -0
  136. package/stories/menu-item.stories.js.map +7 -0
  137. package/stories/menu-sizes.stories.js +11 -0
  138. package/stories/menu-sizes.stories.js.map +7 -0
  139. package/stories/menu.stories.js +407 -0
  140. package/stories/menu.stories.js.map +7 -0
  141. package/stories/submenu.stories.js +346 -0
  142. package/stories/submenu.stories.js.map +7 -0
  143. package/test/benchmark/test-basic.js +24 -0
  144. package/test/benchmark/test-basic.js.map +7 -0
  145. package/test/menu-divider.test-vrt.js +5 -0
  146. package/test/menu-divider.test-vrt.js.map +7 -0
  147. package/test/menu-group.test-vrt.js +5 -0
  148. package/test/menu-group.test-vrt.js.map +7 -0
  149. package/test/menu-group.test.js +405 -0
  150. package/test/menu-group.test.js.map +7 -0
  151. package/test/menu-item.disconnected.test-vrt.js +5 -0
  152. package/test/menu-item.disconnected.test-vrt.js.map +7 -0
  153. package/test/menu-item.test-vrt.js +5 -0
  154. package/test/menu-item.test-vrt.js.map +7 -0
  155. package/test/menu-item.test.js +189 -0
  156. package/test/menu-item.test.js.map +7 -0
  157. package/test/menu-memory.test.js +5 -0
  158. package/test/menu-memory.test.js.map +7 -0
  159. package/test/menu-selects.test.js +530 -0
  160. package/test/menu-selects.test.js.map +7 -0
  161. package/test/menu-sizes.test-vrt.js +5 -0
  162. package/test/menu-sizes.test-vrt.js.map +7 -0
  163. package/test/menu.test-vrt.js +5 -0
  164. package/test/menu.test-vrt.js.map +7 -0
  165. package/test/menu.test.js +559 -0
  166. package/test/menu.test.js.map +7 -0
  167. package/test/submenu.test-vrt.js +5 -0
  168. package/test/submenu.test-vrt.js.map +7 -0
  169. package/test/submenu.test.js +970 -0
  170. package/test/submenu.test.js.map +7 -0
@@ -0,0 +1,740 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __decorateClass = (decorators, target, key, kind) => {
5
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
6
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
7
+ if (decorator = decorators[i])
8
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
9
+ if (kind && result) __defProp(target, key, result);
10
+ return result;
11
+ };
12
+ import {
13
+ html,
14
+ SizedMixin,
15
+ SpectrumElement
16
+ } from "@spectrum-web-components/base";
17
+ import {
18
+ property,
19
+ query
20
+ } from "@spectrum-web-components/base/src/decorators.js";
21
+ import menuStyles from "./menu.css.js";
22
+ function elementIsOrContains(el, isOrContains) {
23
+ return !!isOrContains && (el === isOrContains || el.contains(isOrContains));
24
+ }
25
+ export class Menu extends SizedMixin(SpectrumElement, { noDefaultSize: true }) {
26
+ constructor() {
27
+ super();
28
+ this.label = "";
29
+ this.ignore = false;
30
+ this.value = "";
31
+ this.valueSeparator = ",";
32
+ this._selected = [];
33
+ this.selectedItems = [];
34
+ this.childItemSet = /* @__PURE__ */ new Set();
35
+ this.focusedItemIndex = 0;
36
+ this.focusInItemIndex = 0;
37
+ this.selectedItemsMap = /* @__PURE__ */ new Map();
38
+ // if the click and pointerup events are on the same target, we should not
39
+ // handle the click event.
40
+ this.pointerUpTarget = null;
41
+ this.descendentOverlays = /* @__PURE__ */ new Map();
42
+ this.handleSubmenuClosed = (event) => {
43
+ event.stopPropagation();
44
+ const target = event.composedPath()[0];
45
+ target.dispatchEvent(
46
+ new Event("sp-menu-submenu-closed", {
47
+ bubbles: true,
48
+ composed: true
49
+ })
50
+ );
51
+ };
52
+ this.handleSubmenuOpened = (event) => {
53
+ event.stopPropagation();
54
+ const target = event.composedPath()[0];
55
+ target.dispatchEvent(
56
+ new Event("sp-menu-submenu-opened", {
57
+ bubbles: true,
58
+ composed: true
59
+ })
60
+ );
61
+ const focusedItem = this.childItems[this.focusedItemIndex];
62
+ if (focusedItem) {
63
+ focusedItem.focused = false;
64
+ }
65
+ const openedItem = event.composedPath().find((el) => this.childItemSet.has(el));
66
+ if (!openedItem) return;
67
+ const openedItemIndex = this.childItems.indexOf(openedItem);
68
+ this.focusedItemIndex = openedItemIndex;
69
+ this.focusInItemIndex = openedItemIndex;
70
+ };
71
+ this._hasUpdatedSelectedItemIndex = false;
72
+ this._willUpdateItems = false;
73
+ this.cacheUpdated = Promise.resolve();
74
+ /* c8 ignore next 3 */
75
+ this.resolveCacheUpdated = () => {
76
+ return;
77
+ };
78
+ this.addEventListener(
79
+ "sp-menu-item-added-or-updated",
80
+ this.onSelectableItemAddedOrUpdated
81
+ );
82
+ this.addEventListener(
83
+ "sp-menu-item-added-or-updated",
84
+ this.onFocusableItemAddedOrUpdated,
85
+ {
86
+ capture: true
87
+ }
88
+ );
89
+ this.addEventListener("click", this.handleClick);
90
+ this.addEventListener("pointerup", this.handlePointerup);
91
+ this.addEventListener("focusin", this.handleFocusin);
92
+ this.addEventListener("blur", this.handleBlur);
93
+ this.addEventListener("sp-opened", this.handleSubmenuOpened);
94
+ this.addEventListener("sp-closed", this.handleSubmenuClosed);
95
+ }
96
+ static get styles() {
97
+ return [menuStyles];
98
+ }
99
+ get isSubmenu() {
100
+ return this.slot === "submenu";
101
+ }
102
+ get selected() {
103
+ return this._selected;
104
+ }
105
+ set selected(selected) {
106
+ if (selected === this.selected) {
107
+ return;
108
+ }
109
+ const old = this.selected;
110
+ this._selected = selected;
111
+ this.selectedItems = [];
112
+ this.selectedItemsMap.clear();
113
+ this.childItems.forEach((item) => {
114
+ if (this !== item.menuData.selectionRoot) {
115
+ return;
116
+ }
117
+ item.selected = this.selected.includes(item.value);
118
+ if (item.selected) {
119
+ this.selectedItems.push(item);
120
+ this.selectedItemsMap.set(item, true);
121
+ }
122
+ });
123
+ this.requestUpdate("selected", old);
124
+ }
125
+ get childItems() {
126
+ if (!this.cachedChildItems) {
127
+ this.cachedChildItems = this.updateCachedMenuItems();
128
+ }
129
+ return this.cachedChildItems;
130
+ }
131
+ updateCachedMenuItems() {
132
+ this.cachedChildItems = [];
133
+ if (!this.menuSlot) {
134
+ return [];
135
+ }
136
+ const slottedElements = this.menuSlot.assignedElements({
137
+ flatten: true
138
+ });
139
+ for (const [i, slottedElement] of slottedElements.entries()) {
140
+ if (this.childItemSet.has(slottedElement)) {
141
+ this.cachedChildItems.push(slottedElement);
142
+ continue;
143
+ }
144
+ const isHTMLSlotElement = slottedElement.localName === "slot";
145
+ const flattenedChildren = isHTMLSlotElement ? slottedElement.assignedElements({
146
+ flatten: true
147
+ }) : [...slottedElement.querySelectorAll(`:scope > *`)];
148
+ slottedElements.splice(
149
+ i,
150
+ 1,
151
+ slottedElement,
152
+ ...flattenedChildren
153
+ );
154
+ }
155
+ return this.cachedChildItems;
156
+ }
157
+ /**
158
+ * Hide this getter from web-component-analyzer until
159
+ * https://github.com/runem/web-component-analyzer/issues/131
160
+ * has been addressed.
161
+ *
162
+ * @private
163
+ */
164
+ get childRole() {
165
+ if (this.resolvedRole === "listbox") {
166
+ return "option";
167
+ }
168
+ switch (this.resolvedSelects) {
169
+ case "single":
170
+ return "menuitemradio";
171
+ case "multiple":
172
+ return "menuitemcheckbox";
173
+ default:
174
+ return "menuitem";
175
+ }
176
+ }
177
+ get ownRole() {
178
+ return "menu";
179
+ }
180
+ /**
181
+ * When a descendant `<sp-menu-item>` element is added or updated it will dispatch
182
+ * this event to announce its presence in the DOM. During the CAPTURE phase the first
183
+ * Menu based element that the event encounters will manage the focus state of the
184
+ * dispatching `<sp-menu-item>` element.
185
+ * @param event
186
+ */
187
+ onFocusableItemAddedOrUpdated(event) {
188
+ event.menuCascade.set(this, {
189
+ hadFocusRoot: !!event.item.menuData.focusRoot,
190
+ ancestorWithSelects: event.currentAncestorWithSelects
191
+ });
192
+ if (this.selects) {
193
+ event.currentAncestorWithSelects = this;
194
+ }
195
+ event.item.menuData.focusRoot = event.item.menuData.focusRoot || this;
196
+ }
197
+ /**
198
+ * When a descendant `<sp-menu-item>` element is added or updated it will dispatch
199
+ * this event to announce its presence in the DOM. During the BUBBLE phase the first
200
+ * Menu based element that the event encounters that does not inherit selection will
201
+ * manage the selection state of the dispatching `<sp-menu-item>` element.
202
+ * @param event
203
+ */
204
+ onSelectableItemAddedOrUpdated(event) {
205
+ var _a, _b;
206
+ const cascadeData = event.menuCascade.get(this);
207
+ if (!cascadeData) return;
208
+ event.item.menuData.parentMenu = event.item.menuData.parentMenu || this;
209
+ if (cascadeData.hadFocusRoot && !this.ignore) {
210
+ this.tabIndex = -1;
211
+ }
212
+ this.addChildItem(event.item);
213
+ if (this.selects === "inherit") {
214
+ this.resolvedSelects = "inherit";
215
+ const ignoreMenu = (_a = event.currentAncestorWithSelects) == null ? void 0 : _a.ignore;
216
+ this.resolvedRole = ignoreMenu ? "none" : ((_b = event.currentAncestorWithSelects) == null ? void 0 : _b.getAttribute("role")) || this.getAttribute("role") || void 0;
217
+ } else if (this.selects) {
218
+ this.resolvedRole = this.ignore ? "none" : this.getAttribute("role") || void 0;
219
+ this.resolvedSelects = this.selects;
220
+ } else {
221
+ this.resolvedRole = this.ignore ? "none" : this.getAttribute("role") || void 0;
222
+ this.resolvedSelects = this.resolvedRole === "none" ? "ignore" : "none";
223
+ }
224
+ const selects = this.resolvedSelects === "single" || this.resolvedSelects === "multiple";
225
+ event.item.menuData.cleanupSteps.push(
226
+ (item) => this.removeChildItem(item)
227
+ );
228
+ if ((selects || !this.selects && this.resolvedSelects !== "ignore") && !event.item.menuData.selectionRoot) {
229
+ event.item.setRole(this.childRole);
230
+ event.item.menuData.selectionRoot = event.item.menuData.selectionRoot || this;
231
+ if (event.item.selected) {
232
+ this.selectedItemsMap.set(event.item, true);
233
+ this.selectedItems = [...this.selectedItems, event.item];
234
+ this._selected = [...this.selected, event.item.value];
235
+ this.value = this.selected.join(this.valueSeparator);
236
+ }
237
+ }
238
+ }
239
+ addChildItem(item) {
240
+ this.childItemSet.add(item);
241
+ this.handleItemsChanged();
242
+ }
243
+ async removeChildItem(item) {
244
+ this.childItemSet.delete(item);
245
+ this.cachedChildItems = void 0;
246
+ if (item.focused) {
247
+ this.handleItemsChanged();
248
+ await this.updateComplete;
249
+ this.focus();
250
+ }
251
+ }
252
+ focus({ preventScroll } = {}) {
253
+ if (!this.childItems.length || this.childItems.every((childItem) => childItem.disabled)) {
254
+ return;
255
+ }
256
+ if (this.childItems.some(
257
+ (childItem) => childItem.menuData.focusRoot !== this
258
+ )) {
259
+ super.focus({ preventScroll });
260
+ return;
261
+ }
262
+ this.focusMenuItemByOffset(0);
263
+ super.focus({ preventScroll });
264
+ const selectedItem = this.selectedItems[0];
265
+ if (selectedItem && !preventScroll) {
266
+ selectedItem.scrollIntoView({ block: "nearest" });
267
+ }
268
+ }
269
+ handleClick(event) {
270
+ if (this.pointerUpTarget === event.target) {
271
+ this.pointerUpTarget = null;
272
+ return;
273
+ }
274
+ this.handlePointerBasedSelection(event);
275
+ }
276
+ handlePointerup(event) {
277
+ this.pointerUpTarget = event.target;
278
+ this.handlePointerBasedSelection(event);
279
+ }
280
+ handlePointerBasedSelection(event) {
281
+ var _a, _b;
282
+ if (event instanceof MouseEvent && event.button !== 0) {
283
+ return;
284
+ }
285
+ const path = event.composedPath();
286
+ const target = path.find((el) => {
287
+ if (!(el instanceof Element)) {
288
+ return false;
289
+ }
290
+ return el.getAttribute("role") === this.childRole;
291
+ });
292
+ if (event.defaultPrevented) {
293
+ const index = this.childItems.indexOf(target);
294
+ if (((_a = target == null ? void 0 : target.menuData) == null ? void 0 : _a.focusRoot) === this && index > -1) {
295
+ this.focusedItemIndex = index;
296
+ }
297
+ return;
298
+ }
299
+ if ((target == null ? void 0 : target.href) && target.href.length) {
300
+ this.dispatchEvent(
301
+ new Event("change", {
302
+ bubbles: true,
303
+ composed: true
304
+ })
305
+ );
306
+ return;
307
+ } else if (((_b = target == null ? void 0 : target.menuData) == null ? void 0 : _b.selectionRoot) === this && this.childItems.length) {
308
+ event.preventDefault();
309
+ if (target.hasSubmenu || target.open) {
310
+ return;
311
+ }
312
+ this.selectOrToggleItem(target);
313
+ } else {
314
+ return;
315
+ }
316
+ this.prepareToCleanUp();
317
+ }
318
+ handleFocusin(event) {
319
+ var _a;
320
+ if (this.childItems.some(
321
+ (childItem) => childItem.menuData.focusRoot !== this
322
+ )) {
323
+ return;
324
+ }
325
+ const activeElement = this.getRootNode().activeElement;
326
+ const selectionRoot = ((_a = this.childItems[this.focusedItemIndex]) == null ? void 0 : _a.menuData.selectionRoot) || this;
327
+ if (activeElement !== selectionRoot || event.target !== this) {
328
+ selectionRoot.focus({ preventScroll: true });
329
+ if (activeElement && this.focusedItemIndex === 0) {
330
+ const offset = this.childItems.findIndex(
331
+ (childItem) => childItem === activeElement
332
+ );
333
+ this.focusMenuItemByOffset(Math.max(offset, 0));
334
+ }
335
+ }
336
+ this.startListeningToKeyboard();
337
+ }
338
+ startListeningToKeyboard() {
339
+ this.addEventListener("keydown", this.handleKeydown);
340
+ }
341
+ handleBlur(event) {
342
+ if (elementIsOrContains(this, event.relatedTarget)) {
343
+ return;
344
+ }
345
+ this.stopListeningToKeyboard();
346
+ this.childItems.forEach((child) => child.focused = false);
347
+ this.removeAttribute("aria-activedescendant");
348
+ }
349
+ stopListeningToKeyboard() {
350
+ this.removeEventListener("keydown", this.handleKeydown);
351
+ }
352
+ handleDescendentOverlayOpened(event) {
353
+ const target = event.composedPath()[0];
354
+ if (!target.overlayElement) return;
355
+ this.descendentOverlays.set(
356
+ target.overlayElement,
357
+ target.overlayElement
358
+ );
359
+ }
360
+ handleDescendentOverlayClosed(event) {
361
+ const target = event.composedPath()[0];
362
+ if (!target.overlayElement) return;
363
+ this.descendentOverlays.delete(target.overlayElement);
364
+ }
365
+ async selectOrToggleItem(targetItem) {
366
+ const resolvedSelects = this.resolvedSelects;
367
+ const oldSelectedItemsMap = new Map(this.selectedItemsMap);
368
+ const oldSelected = this.selected.slice();
369
+ const oldSelectedItems = this.selectedItems.slice();
370
+ const oldValue = this.value;
371
+ const focusedChild = this.childItems[this.focusedItemIndex];
372
+ if (focusedChild) {
373
+ focusedChild.focused = false;
374
+ focusedChild.active = false;
375
+ }
376
+ this.focusedItemIndex = this.childItems.indexOf(targetItem);
377
+ this.forwardFocusVisibleToItem(targetItem);
378
+ if (resolvedSelects === "multiple") {
379
+ if (this.selectedItemsMap.has(targetItem)) {
380
+ this.selectedItemsMap.delete(targetItem);
381
+ } else {
382
+ this.selectedItemsMap.set(targetItem, true);
383
+ }
384
+ const selected = [];
385
+ const selectedItems = [];
386
+ this.childItemSet.forEach((childItem) => {
387
+ if (childItem.menuData.selectionRoot !== this) return;
388
+ if (this.selectedItemsMap.has(childItem)) {
389
+ selected.push(childItem.value);
390
+ selectedItems.push(childItem);
391
+ }
392
+ });
393
+ this._selected = selected;
394
+ this.selectedItems = selectedItems;
395
+ this.value = this.selected.join(this.valueSeparator);
396
+ } else {
397
+ this.selectedItemsMap.clear();
398
+ this.selectedItemsMap.set(targetItem, true);
399
+ this.value = targetItem.value;
400
+ this._selected = [targetItem.value];
401
+ this.selectedItems = [targetItem];
402
+ }
403
+ const applyDefault = this.dispatchEvent(
404
+ new Event("change", {
405
+ cancelable: true,
406
+ bubbles: true,
407
+ composed: true
408
+ })
409
+ );
410
+ if (!applyDefault) {
411
+ this._selected = oldSelected;
412
+ this.selectedItems = oldSelectedItems;
413
+ this.selectedItemsMap = oldSelectedItemsMap;
414
+ this.value = oldValue;
415
+ return;
416
+ }
417
+ if (resolvedSelects === "single") {
418
+ for (const oldItem of oldSelectedItemsMap.keys()) {
419
+ if (oldItem !== targetItem) {
420
+ oldItem.selected = false;
421
+ }
422
+ }
423
+ targetItem.selected = true;
424
+ } else if (resolvedSelects === "multiple") {
425
+ targetItem.selected = !targetItem.selected;
426
+ }
427
+ }
428
+ navigateWithinMenu(event) {
429
+ const { key } = event;
430
+ const lastFocusedItem = this.childItems[this.focusedItemIndex];
431
+ const direction = key === "ArrowDown" ? 1 : -1;
432
+ const itemToFocus = this.focusMenuItemByOffset(direction);
433
+ if (itemToFocus === lastFocusedItem) {
434
+ return;
435
+ }
436
+ event.preventDefault();
437
+ event.stopPropagation();
438
+ itemToFocus.scrollIntoView({ block: "nearest" });
439
+ }
440
+ navigateBetweenRelatedMenus(event) {
441
+ const { key } = event;
442
+ event.stopPropagation();
443
+ const shouldOpenSubmenu = this.isLTR && key === "ArrowRight" || !this.isLTR && key === "ArrowLeft";
444
+ const shouldCloseSelfAsSubmenu = this.isLTR && key === "ArrowLeft" || !this.isLTR && key === "ArrowRight";
445
+ if (shouldOpenSubmenu) {
446
+ const lastFocusedItem = this.childItems[this.focusedItemIndex];
447
+ if (lastFocusedItem == null ? void 0 : lastFocusedItem.hasSubmenu) {
448
+ lastFocusedItem.openOverlay();
449
+ }
450
+ } else if (shouldCloseSelfAsSubmenu && this.isSubmenu) {
451
+ this.dispatchEvent(new Event("close", { bubbles: true }));
452
+ this.updateSelectedItemIndex();
453
+ }
454
+ }
455
+ handleKeydown(event) {
456
+ if (event.defaultPrevented) {
457
+ return;
458
+ }
459
+ const lastFocusedItem = this.childItems[this.focusedItemIndex];
460
+ if (lastFocusedItem) {
461
+ lastFocusedItem.focused = true;
462
+ }
463
+ const { key } = event;
464
+ if (event.shiftKey && event.target !== this && this.hasAttribute("tabindex")) {
465
+ this.removeAttribute("tabindex");
466
+ const replaceTabindex = (event2) => {
467
+ if (!event2.shiftKey && !this.hasAttribute("tabindex")) {
468
+ this.tabIndex = 0;
469
+ document.removeEventListener("keyup", replaceTabindex);
470
+ this.removeEventListener("focusout", replaceTabindex);
471
+ }
472
+ };
473
+ document.addEventListener("keyup", replaceTabindex);
474
+ this.addEventListener("focusout", replaceTabindex);
475
+ }
476
+ if (key === "Tab") {
477
+ this.prepareToCleanUp();
478
+ return;
479
+ }
480
+ if (key === " ") {
481
+ if (lastFocusedItem == null ? void 0 : lastFocusedItem.hasSubmenu) {
482
+ lastFocusedItem.openOverlay();
483
+ return;
484
+ }
485
+ }
486
+ if (key === " " || key === "Enter") {
487
+ const childItem = this.childItems[this.focusedItemIndex];
488
+ if (childItem && childItem.menuData.selectionRoot === event.target) {
489
+ event.preventDefault();
490
+ childItem.click();
491
+ }
492
+ return;
493
+ }
494
+ if (key === "ArrowDown" || key === "ArrowUp") {
495
+ const childItem = this.childItems[this.focusedItemIndex];
496
+ if (childItem && childItem.menuData.selectionRoot === event.target) {
497
+ this.navigateWithinMenu(event);
498
+ }
499
+ return;
500
+ }
501
+ this.navigateBetweenRelatedMenus(event);
502
+ }
503
+ focusMenuItemByOffset(offset) {
504
+ const step = offset || 1;
505
+ const focusedItem = this.childItems[this.focusedItemIndex];
506
+ if (focusedItem) {
507
+ focusedItem.focused = false;
508
+ focusedItem.active = focusedItem.open;
509
+ }
510
+ this.focusedItemIndex = (this.childItems.length + this.focusedItemIndex + offset) % this.childItems.length;
511
+ let itemToFocus = this.childItems[this.focusedItemIndex];
512
+ let availableItems = this.childItems.length;
513
+ while ((itemToFocus == null ? void 0 : itemToFocus.disabled) && availableItems) {
514
+ availableItems -= 1;
515
+ this.focusedItemIndex = (this.childItems.length + this.focusedItemIndex + step) % this.childItems.length;
516
+ itemToFocus = this.childItems[this.focusedItemIndex];
517
+ }
518
+ if (!(itemToFocus == null ? void 0 : itemToFocus.disabled)) {
519
+ this.forwardFocusVisibleToItem(itemToFocus);
520
+ }
521
+ return itemToFocus;
522
+ }
523
+ prepareToCleanUp() {
524
+ document.addEventListener(
525
+ "focusout",
526
+ () => {
527
+ requestAnimationFrame(() => {
528
+ const focusedItem = this.childItems[this.focusedItemIndex];
529
+ if (focusedItem) {
530
+ focusedItem.focused = false;
531
+ this.updateSelectedItemIndex();
532
+ }
533
+ });
534
+ },
535
+ { once: true }
536
+ );
537
+ }
538
+ updateSelectedItemIndex() {
539
+ let firstOrFirstSelectedIndex = 0;
540
+ const selectedItemsMap = /* @__PURE__ */ new Map();
541
+ const selected = [];
542
+ const selectedItems = [];
543
+ let itemIndex = this.childItems.length;
544
+ while (itemIndex) {
545
+ itemIndex -= 1;
546
+ const childItem = this.childItems[itemIndex];
547
+ if (childItem.menuData.selectionRoot === this) {
548
+ if (childItem.selected || !this._hasUpdatedSelectedItemIndex && this.selected.includes(childItem.value)) {
549
+ firstOrFirstSelectedIndex = itemIndex;
550
+ selectedItemsMap.set(childItem, true);
551
+ selected.unshift(childItem.value);
552
+ selectedItems.unshift(childItem);
553
+ }
554
+ if (itemIndex !== firstOrFirstSelectedIndex) {
555
+ childItem.focused = false;
556
+ }
557
+ }
558
+ }
559
+ selectedItems.map((item, i) => {
560
+ if (i > 0) {
561
+ item.focused = false;
562
+ }
563
+ });
564
+ this.selectedItemsMap = selectedItemsMap;
565
+ this._selected = selected;
566
+ this.selectedItems = selectedItems;
567
+ this.value = this.selected.join(this.valueSeparator);
568
+ this.focusedItemIndex = firstOrFirstSelectedIndex;
569
+ this.focusInItemIndex = firstOrFirstSelectedIndex;
570
+ }
571
+ handleItemsChanged() {
572
+ this.cachedChildItems = void 0;
573
+ if (!this._willUpdateItems) {
574
+ this._willUpdateItems = true;
575
+ this.cacheUpdated = this.updateCache();
576
+ }
577
+ }
578
+ async updateCache() {
579
+ if (!this.hasUpdated) {
580
+ await Promise.all([
581
+ new Promise((res) => requestAnimationFrame(() => res(true))),
582
+ this.updateComplete
583
+ ]);
584
+ } else {
585
+ await new Promise((res) => requestAnimationFrame(() => res(true)));
586
+ }
587
+ if (this.cachedChildItems === void 0) {
588
+ this.updateSelectedItemIndex();
589
+ this.updateItemFocus();
590
+ }
591
+ this._willUpdateItems = false;
592
+ }
593
+ updateItemFocus() {
594
+ if (this.childItems.length == 0) {
595
+ return;
596
+ }
597
+ const focusInItem = this.childItems[this.focusInItemIndex];
598
+ if (this.getRootNode().activeElement === focusInItem.menuData.focusRoot) {
599
+ this.forwardFocusVisibleToItem(focusInItem);
600
+ }
601
+ }
602
+ closeDescendentOverlays() {
603
+ this.descendentOverlays.forEach((overlay) => {
604
+ overlay.open = false;
605
+ });
606
+ this.descendentOverlays = /* @__PURE__ */ new Map();
607
+ }
608
+ forwardFocusVisibleToItem(item) {
609
+ if (!item || item.menuData.focusRoot !== this) {
610
+ return;
611
+ }
612
+ this.closeDescendentOverlays();
613
+ const focused = this.hasVisibleFocusInTree() || !!this.childItems.find((child) => {
614
+ return child.hasVisibleFocusInTree();
615
+ });
616
+ item.focused = focused;
617
+ this.setAttribute("aria-activedescendant", item.id);
618
+ if (item.menuData.selectionRoot && item.menuData.selectionRoot !== this) {
619
+ item.menuData.selectionRoot.focus();
620
+ }
621
+ }
622
+ handleSlotchange({
623
+ target
624
+ }) {
625
+ const assignedElements = target.assignedElements({
626
+ flatten: true
627
+ });
628
+ if (this.childItems.length !== assignedElements.length) {
629
+ assignedElements.forEach((item) => {
630
+ if (typeof item.triggerUpdate !== "undefined") {
631
+ item.triggerUpdate();
632
+ } else if (typeof item.childItems !== "undefined") {
633
+ item.childItems.forEach((child) => {
634
+ child.triggerUpdate();
635
+ });
636
+ }
637
+ });
638
+ }
639
+ }
640
+ renderMenuItemSlot() {
641
+ return html`
642
+ <slot
643
+ @sp-menu-submenu-opened=${this.handleDescendentOverlayOpened}
644
+ @sp-menu-submenu-closed=${this.handleDescendentOverlayClosed}
645
+ @slotchange=${this.handleSlotchange}
646
+ ></slot>
647
+ `;
648
+ }
649
+ render() {
650
+ return this.renderMenuItemSlot();
651
+ }
652
+ firstUpdated(changed) {
653
+ super.firstUpdated(changed);
654
+ if (!this.hasAttribute("tabindex") && !this.ignore) {
655
+ const role = this.getAttribute("role");
656
+ if (role === "group") {
657
+ this.tabIndex = -1;
658
+ } else {
659
+ this.tabIndex = 0;
660
+ }
661
+ }
662
+ const updates = [
663
+ new Promise((res) => requestAnimationFrame(() => res(true)))
664
+ ];
665
+ [...this.children].forEach((item) => {
666
+ if (item.localName === "sp-menu-item") {
667
+ updates.push(item.updateComplete);
668
+ }
669
+ });
670
+ this.childItemsUpdated = Promise.all(updates);
671
+ }
672
+ updated(changes) {
673
+ super.updated(changes);
674
+ if (changes.has("selects") && this.hasUpdated) {
675
+ this.selectsChanged();
676
+ }
677
+ if (changes.has("label") && (this.label || typeof changes.get("label") !== "undefined")) {
678
+ if (this.label) {
679
+ this.setAttribute("aria-label", this.label);
680
+ } else {
681
+ this.removeAttribute("aria-label");
682
+ }
683
+ }
684
+ }
685
+ selectsChanged() {
686
+ const updates = [
687
+ new Promise((res) => requestAnimationFrame(() => res(true)))
688
+ ];
689
+ this.childItemSet.forEach((childItem) => {
690
+ updates.push(childItem.triggerUpdate());
691
+ });
692
+ this.childItemsUpdated = Promise.all(updates);
693
+ }
694
+ connectedCallback() {
695
+ super.connectedCallback();
696
+ if (!this.hasAttribute("role") && !this.ignore) {
697
+ this.setAttribute("role", this.ownRole);
698
+ }
699
+ this.updateComplete.then(() => this.updateItemFocus());
700
+ }
701
+ disconnectedCallback() {
702
+ this.cachedChildItems = void 0;
703
+ this.selectedItems = [];
704
+ this.selectedItemsMap.clear();
705
+ this.childItemSet.clear();
706
+ this.descendentOverlays = /* @__PURE__ */ new Map();
707
+ super.disconnectedCallback();
708
+ }
709
+ async getUpdateComplete() {
710
+ const complete = await super.getUpdateComplete();
711
+ await this.childItemsUpdated;
712
+ await this.cacheUpdated;
713
+ return complete;
714
+ }
715
+ }
716
+ __decorateClass([
717
+ property({ type: String, reflect: true })
718
+ ], Menu.prototype, "label", 2);
719
+ __decorateClass([
720
+ property({ type: Boolean, reflect: true })
721
+ ], Menu.prototype, "ignore", 2);
722
+ __decorateClass([
723
+ property({ type: String, reflect: true })
724
+ ], Menu.prototype, "selects", 2);
725
+ __decorateClass([
726
+ property({ type: String })
727
+ ], Menu.prototype, "value", 2);
728
+ __decorateClass([
729
+ property({ type: String, attribute: "value-separator" })
730
+ ], Menu.prototype, "valueSeparator", 2);
731
+ __decorateClass([
732
+ property({ attribute: false })
733
+ ], Menu.prototype, "selected", 1);
734
+ __decorateClass([
735
+ property({ attribute: false })
736
+ ], Menu.prototype, "selectedItems", 2);
737
+ __decorateClass([
738
+ query("slot:not([name])")
739
+ ], Menu.prototype, "menuSlot", 2);
740
+ //# sourceMappingURL=Menu.dev.js.map