@spectrum-web-components/menu 0.32.0 → 0.32.1-overlay.41

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["MenuGroup.ts"],
4
- "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport {\n CSSResultArray,\n html,\n TemplateResult,\n} from '@spectrum-web-components/base';\nimport {\n queryAssignedNodes,\n state,\n} from '@spectrum-web-components/base/src/decorators.js';\n\nimport { Menu } from './Menu.js';\n// Leveraged in build systems that use aliasing to prevent multiple registrations: https://github.com/adobe/spectrum-web-components/pull/3225\nimport \"@spectrum-web-components/menu/sp-menu.js\"\nimport menuGroupStyles from './menu-group.css.js';\n\n/**\n * @element sp-menu-group\n *\n * @slot header - headline of the menu group\n * @slot - menu items to be listed in the group\n */\nexport class MenuGroup extends Menu {\n public static override get styles(): CSSResultArray {\n return [...super.styles, menuGroupStyles];\n }\n\n private static instances = 0;\n\n private headerId!: string;\n\n public constructor() {\n super();\n MenuGroup.instances += 1;\n this.headerId = `sp-menu-group-label-${MenuGroup.instances}`;\n }\n\n @queryAssignedNodes({\n slot: 'header',\n flatten: true,\n })\n private headerElements!: NodeListOf<HTMLElement>;\n\n @state()\n private headerElement?: HTMLElement;\n\n protected override get ownRole(): string {\n switch (this.selects) {\n case 'multiple':\n case 'single':\n case 'inherit':\n return 'group';\n default:\n return 'menu';\n }\n }\n\n protected updateLabel(): void {\n const headerElement = this.headerElements.length\n ? this.headerElements[0]\n : undefined;\n if (headerElement !== this.headerElement) {\n if (this.headerElement && this.headerElement.id === this.headerId) {\n this.headerElement.removeAttribute('id');\n }\n if (headerElement) {\n const headerId = headerElement.id || this.headerId;\n if (!headerElement.id) {\n headerElement.id = headerId;\n }\n this.setAttribute('aria-labelledby', headerId);\n } else {\n this.removeAttribute('aria-labelledby');\n }\n }\n this.headerElement = headerElement;\n }\n\n public override render(): TemplateResult {\n return html`\n <span\n class=\"header\"\n aria-hidden=\"true\"\n ?hidden=${!this.headerElement}\n >\n <slot name=\"header\" @slotchange=${this.updateLabel}></slot>\n </span>\n <sp-menu role=\"none\">\n <slot></slot>\n </sp-menu>\n `;\n }\n}\n"],
5
- "mappings": "qNAYA,OAEI,QAAAA,MAEG,gCACP,OACI,sBAAAC,EACA,SAAAC,MACG,kDAEP,OAAS,QAAAC,MAAY,YAErB,MAAO,2CACP,OAAOC,MAAqB,sBAQrB,MAAMC,EAAN,cAAwBF,CAAK,CASzB,aAAc,CACjB,MAAM,EACNE,EAAU,WAAa,EACvB,KAAK,SAAW,uBAAuBA,EAAU,WACrD,CAZA,WAA2B,QAAyB,CAChD,MAAO,CAAC,GAAG,MAAM,OAAQD,CAAe,CAC5C,CAqBA,IAAuB,SAAkB,CACrC,OAAQ,KAAK,QAAS,CAClB,IAAK,WACL,IAAK,SACL,IAAK,UACD,MAAO,QACX,QACI,MAAO,MACf,CACJ,CAEU,aAAoB,CAC1B,MAAME,EAAgB,KAAK,eAAe,OACpC,KAAK,eAAe,CAAC,EACrB,OACN,GAAIA,IAAkB,KAAK,cAIvB,GAHI,KAAK,eAAiB,KAAK,cAAc,KAAO,KAAK,UACrD,KAAK,cAAc,gBAAgB,IAAI,EAEvCA,EAAe,CACf,MAAMC,EAAWD,EAAc,IAAM,KAAK,SACrCA,EAAc,KACfA,EAAc,GAAKC,GAEvB,KAAK,aAAa,kBAAmBA,CAAQ,OAE7C,KAAK,gBAAgB,iBAAiB,EAG9C,KAAK,cAAgBD,CACzB,CAEgB,QAAyB,CACrC,OAAON;AAAA;AAAA;AAAA;AAAA,0BAIW,CAAC,KAAK;AAAA;AAAA,kDAEkB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,SAMnD,CACJ,EAtEO,WAAM,UAANK,EAAM,UAKM,UAAY,EAcnBG,EAAA,CAJPP,EAAmB,CAChB,KAAM,SACN,QAAS,EACb,CAAC,GAlBQ,UAmBD,8BAGAO,EAAA,CADPN,EAAM,GArBE,UAsBD",
6
- "names": ["html", "queryAssignedNodes", "state", "Menu", "menuGroupStyles", "_MenuGroup", "headerElement", "headerId", "__decorateClass"]
4
+ "sourcesContent": ["/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\n\nimport {\n CSSResultArray,\n html,\n TemplateResult,\n} from '@spectrum-web-components/base';\nimport {\n queryAssignedNodes,\n state,\n} from '@spectrum-web-components/base/src/decorators.js';\n\nimport { Menu } from './Menu.js';\n// Leveraged in build systems that use aliasing to prevent multiple registrations: https://github.com/adobe/spectrum-web-components/pull/3225\nimport '@spectrum-web-components/menu/sp-menu.js';\nimport menuGroupStyles from './menu-group.css.js';\n\n/**\n * @element sp-menu-group\n *\n * @slot header - headline of the menu group\n * @slot - menu items to be listed in the group\n */\nexport class MenuGroup extends Menu {\n public static override get styles(): CSSResultArray {\n return [...super.styles, menuGroupStyles];\n }\n\n private headerId = '';\n\n @queryAssignedNodes({\n slot: 'header',\n flatten: true,\n })\n private headerElements!: NodeListOf<HTMLElement>;\n\n @state()\n private headerElement?: HTMLElement;\n\n protected override get ownRole(): string {\n switch (this.selects) {\n case 'multiple':\n case 'single':\n case 'inherit':\n return 'group';\n default:\n return 'menu';\n }\n }\n\n protected updateLabel(): void {\n const headerElement = this.headerElements.length\n ? this.headerElements[0]\n : undefined;\n if (headerElement !== this.headerElement) {\n if (this.headerElement && this.headerElement.id === this.headerId) {\n this.headerElement.removeAttribute('id');\n }\n if (headerElement) {\n this.headerId =\n this.headerId ||\n `sp-menu-group-label-${crypto.randomUUID().slice(0, 8)}`;\n const headerId = headerElement.id || this.headerId;\n if (!headerElement.id) {\n headerElement.id = headerId;\n }\n this.setAttribute('aria-labelledby', headerId);\n } else {\n this.removeAttribute('aria-labelledby');\n }\n }\n this.headerElement = headerElement;\n }\n\n public override render(): TemplateResult {\n return html`\n <span\n class=\"header\"\n aria-hidden=\"true\"\n ?hidden=${!this.headerElement}\n >\n <slot name=\"header\" @slotchange=${this.updateLabel}></slot>\n </span>\n <sp-menu role=\"none\">${this.renderMenuItemSlot()}</sp-menu>\n `;\n }\n}\n"],
5
+ "mappings": "qNAYA,OAEI,QAAAA,MAEG,gCACP,OACI,sBAAAC,EACA,SAAAC,MACG,kDAEP,OAAS,QAAAC,MAAY,YAErB,MAAO,2CACP,OAAOC,MAAqB,sBAQrB,aAAM,kBAAkBD,CAAK,CAA7B,kCAKH,KAAQ,SAAW,GAJnB,WAA2B,QAAyB,CAChD,MAAO,CAAC,GAAG,MAAM,OAAQC,CAAe,CAC5C,CAaA,IAAuB,SAAkB,CACrC,OAAQ,KAAK,QAAS,CAClB,IAAK,WACL,IAAK,SACL,IAAK,UACD,MAAO,QACX,QACI,MAAO,MACf,CACJ,CAEU,aAAoB,CAC1B,MAAMC,EAAgB,KAAK,eAAe,OACpC,KAAK,eAAe,CAAC,EACrB,OACN,GAAIA,IAAkB,KAAK,cAIvB,GAHI,KAAK,eAAiB,KAAK,cAAc,KAAO,KAAK,UACrD,KAAK,cAAc,gBAAgB,IAAI,EAEvCA,EAAe,CACf,KAAK,SACD,KAAK,UACL,uBAAuB,OAAO,WAAW,EAAE,MAAM,EAAG,CAAC,IACzD,MAAMC,EAAWD,EAAc,IAAM,KAAK,SACrCA,EAAc,KACfA,EAAc,GAAKC,GAEvB,KAAK,aAAa,kBAAmBA,CAAQ,OAE7C,KAAK,gBAAgB,iBAAiB,EAG9C,KAAK,cAAgBD,CACzB,CAEgB,QAAyB,CACrC,OAAOL;AAAA;AAAA;AAAA;AAAA,0BAIW,CAAC,KAAK;AAAA;AAAA,kDAEkB,KAAK;AAAA;AAAA,mCAEpB,KAAK,mBAAmB;AAAA,SAEvD,CACJ,CApDYO,EAAA,CAJPN,EAAmB,CAChB,KAAM,SACN,QAAS,EACb,CAAC,GAVQ,UAWD,8BAGAM,EAAA,CADPL,EAAM,GAbE,UAcD",
6
+ "names": ["html", "queryAssignedNodes", "state", "Menu", "menuGroupStyles", "headerElement", "headerId", "__decorateClass"]
7
7
  }
package/src/MenuItem.d.ts CHANGED
@@ -3,23 +3,19 @@ import '@spectrum-web-components/icons-ui/icons/sp-icon-checkmark100.js';
3
3
  import { Focusable } from '@spectrum-web-components/shared/src/focusable.js';
4
4
  import '@spectrum-web-components/icons-ui/icons/sp-icon-chevron100.js';
5
5
  import type { Menu } from './Menu.js';
6
- export declare class MenuItemRemovedEvent extends Event {
7
- constructor();
8
- get item(): MenuItem;
9
- _item: MenuItem;
10
- focused: boolean;
11
- reset(item: MenuItem): void;
12
- }
6
+ import '@spectrum-web-components/overlay/sp-overlay.js';
7
+ import { OverlayBase } from 'overlay/src/OverlayBase.js';
8
+ declare type MenuCascadeItem = {
9
+ hadFocusRoot: boolean;
10
+ ancestorWithSelects?: HTMLElement;
11
+ };
13
12
  export declare class MenuItemAddedOrUpdatedEvent extends Event {
14
- constructor();
15
- set focusRoot(root: Menu | undefined);
16
- set selectionRoot(root: Menu);
13
+ constructor(item: MenuItem);
14
+ clear(item: MenuItem): void;
15
+ menuCascade: WeakMap<HTMLElement, MenuCascadeItem>;
17
16
  get item(): MenuItem;
18
- _item: MenuItem;
19
- set currentAncestorWithSelects(ancestor: Menu | undefined);
20
- get currentAncestorWithSelects(): Menu | undefined;
21
- _currentAncestorWithSelects?: Menu;
22
- reset(item: MenuItem): void;
17
+ private _item;
18
+ currentAncestorWithSelects?: Menu;
23
19
  }
24
20
  export declare type MenuItemChildren = {
25
21
  icon: Element[];
@@ -37,12 +33,9 @@ declare const MenuItem_base: typeof Focusable & {
37
33
  * @slot value - content placed at the end of the Menu Item like values, keyboard shortcuts, etc.
38
34
  * @slot submenu - content placed in a submenu
39
35
  * @fires sp-menu-item-added - announces the item has been added so a parent menu can take ownerships
40
- * @fires sp-menu-item-removed - announces when removed from the DOM so the parent menu can remove ownership and update selected state
41
36
  */
42
37
  export declare class MenuItem extends MenuItem_base {
43
38
  static get styles(): CSSResultArray;
44
- static instanceCount: number;
45
- private isInSubmenu;
46
39
  active: boolean;
47
40
  focused: boolean;
48
41
  selected: boolean;
@@ -56,6 +49,7 @@ export declare class MenuItem extends MenuItem_base {
56
49
  hasSubmenu: boolean;
57
50
  noWrap: boolean;
58
51
  private anchorElement;
52
+ overlayElement: OverlayBase;
59
53
  get focusElement(): HTMLElement;
60
54
  get itemChildren(): MenuItemChildren;
61
55
  private _itemChildren?;
@@ -66,6 +60,7 @@ export declare class MenuItem extends MenuItem_base {
66
60
  private proxyFocus;
67
61
  private shouldProxyClick;
68
62
  protected breakItemChildrenCache(): void;
63
+ protected renderSubmenu(): TemplateResult;
69
64
  protected render(): TemplateResult;
70
65
  protected manageSubmenu(event: Event & {
71
66
  target: HTMLSlotElement;
@@ -75,9 +70,10 @@ export declare class MenuItem extends MenuItem_base {
75
70
  protected firstUpdated(changes: PropertyValues): void;
76
71
  protected closeOverlaysForRoot(): void;
77
72
  closeOverlay?: () => Promise<void>;
78
- protected handleSubmenuClick(): void;
73
+ protected handleSubmenuClick(event: Event): void;
79
74
  protected handlePointerenter(): void;
80
75
  protected leaveTimeout?: ReturnType<typeof setTimeout>;
76
+ protected recentlyLeftChild: boolean;
81
77
  protected handlePointerleave(): void;
82
78
  /**
83
79
  * When there is a `change` event in the submenu for this item
@@ -86,8 +82,11 @@ export declare class MenuItem extends MenuItem_base {
86
82
  * and the root of the tree to have their selection changes and
87
83
  * be closed.
88
84
  */
89
- protected handleSubmenuChange: () => void;
90
- protected handleSubmenuPointerenter: () => void;
85
+ protected handleSubmenuChange(event: Event): void;
86
+ protected handleSubmenuPointerenter(): void;
87
+ protected handleSubmenuPointerleave(): Promise<void>;
88
+ protected handleSubmenuOpen(event: Event): void;
89
+ protected cleanup(): void;
91
90
  openOverlay(): Promise<void>;
92
91
  updateAriaSelected(): void;
93
92
  setRole(role: string): void;
@@ -95,16 +94,19 @@ export declare class MenuItem extends MenuItem_base {
95
94
  connectedCallback(): void;
96
95
  _parentElement: HTMLElement;
97
96
  disconnectedCallback(): void;
97
+ private willDispatchUpdate;
98
98
  triggerUpdate(): Promise<void>;
99
+ dispatchUpdate(): void;
99
100
  menuData: {
100
101
  focusRoot?: Menu;
102
+ parentMenu?: Menu;
101
103
  selectionRoot?: Menu;
104
+ cleanupSteps: ((item: MenuItem) => void)[];
102
105
  };
103
106
  }
104
107
  declare global {
105
108
  interface GlobalEventHandlersEventMap {
106
109
  'sp-menu-item-added-or-updated': MenuItemAddedOrUpdatedEvent;
107
- 'sp-menu-item-removed': MenuItemRemovedEvent;
108
110
  }
109
111
  }
110
112
  export {};
@@ -22,65 +22,38 @@ import { LikeAnchor } from "@spectrum-web-components/shared/src/like-anchor.js";
22
22
  import { Focusable } from "@spectrum-web-components/shared/src/focusable.js";
23
23
  import "@spectrum-web-components/icons-ui/icons/sp-icon-chevron100.js";
24
24
  import chevronStyles from "@spectrum-web-components/icon/src/spectrum-icon-chevron.css.js";
25
- import { openOverlay } from "@spectrum-web-components/overlay/src/loader.js";
26
- import { OverlayCloseEvent } from "@spectrum-web-components/overlay/src/overlay-events.js";
27
25
  import menuItemStyles from "./menu-item.css.js";
28
26
  import checkmarkStyles from "@spectrum-web-components/icon/src/spectrum-icon-checkmark.css.js";
29
- import { reparentChildren } from "@spectrum-web-components/shared/src/reparent-children.js";
30
27
  import { MutationController } from "@lit-labs/observers/mutation-controller.js";
28
+ import "@spectrum-web-components/overlay/sp-overlay.js";
31
29
  const POINTERLEAVE_TIMEOUT = 100;
32
- export class MenuItemRemovedEvent extends Event {
33
- constructor() {
34
- super("sp-menu-item-removed", {
35
- bubbles: true,
36
- composed: true
37
- });
38
- this.focused = false;
39
- }
40
- get item() {
41
- return this._item;
42
- }
43
- reset(item) {
44
- this._item = item;
45
- }
46
- }
47
30
  export class MenuItemAddedOrUpdatedEvent extends Event {
48
- constructor() {
31
+ constructor(item) {
49
32
  super("sp-menu-item-added-or-updated", {
50
33
  bubbles: true,
51
34
  composed: true
52
35
  });
36
+ this.menuCascade = /* @__PURE__ */ new WeakMap();
37
+ this.clear(item);
53
38
  }
54
- set focusRoot(root) {
55
- this.item.menuData.focusRoot = this.item.menuData.focusRoot || root;
56
- }
57
- set selectionRoot(root) {
58
- this.item.menuData.selectionRoot = this.item.menuData.selectionRoot || root;
59
- }
60
- get item() {
61
- return this._item;
62
- }
63
- set currentAncestorWithSelects(ancestor) {
64
- this._currentAncestorWithSelects = ancestor;
65
- }
66
- get currentAncestorWithSelects() {
67
- return this._currentAncestorWithSelects;
68
- }
69
- reset(item) {
39
+ clear(item) {
70
40
  this._item = item;
71
- this._currentAncestorWithSelects = void 0;
41
+ this.currentAncestorWithSelects = void 0;
72
42
  item.menuData = {
43
+ cleanupSteps: [],
73
44
  focusRoot: void 0,
74
- selectionRoot: void 0
45
+ selectionRoot: void 0,
46
+ parentMenu: void 0
75
47
  };
48
+ this.menuCascade = /* @__PURE__ */ new WeakMap();
49
+ }
50
+ get item() {
51
+ return this._item;
76
52
  }
77
53
  }
78
- const addOrUpdateEvent = new MenuItemAddedOrUpdatedEvent();
79
- const removeEvent = new MenuItemRemovedEvent();
80
- const _MenuItem = class extends LikeAnchor(Focusable) {
54
+ export class MenuItem extends LikeAnchor(Focusable) {
81
55
  constructor() {
82
56
  super();
83
- this.isInSubmenu = false;
84
57
  this.active = false;
85
58
  this.focused = false;
86
59
  this.selected = false;
@@ -88,28 +61,17 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
88
61
  this.hasSubmenu = false;
89
62
  this.noWrap = false;
90
63
  this.open = false;
91
- /**
92
- * When there is a `change` event in the submenu for this item
93
- * then we "click" this item to cascade the selection up the
94
- * menu tree allowing all submenus between the initial selection
95
- * and the root of the tree to have their selection changes and
96
- * be closed.
97
- */
98
- this.handleSubmenuChange = () => {
99
- var _a;
100
- (_a = this.menuData.selectionRoot) == null ? void 0 : _a.selectOrToggleItem(this);
101
- };
102
- this.handleSubmenuPointerenter = () => {
103
- if (this.leaveTimeout) {
104
- clearTimeout(this.leaveTimeout);
105
- delete this.leaveTimeout;
106
- }
64
+ this.proxyFocus = () => {
65
+ this.focus();
107
66
  };
67
+ this.recentlyLeftChild = false;
68
+ this.willDispatchUpdate = false;
108
69
  this.menuData = {
109
70
  focusRoot: void 0,
110
- selectionRoot: void 0
71
+ parentMenu: void 0,
72
+ selectionRoot: void 0,
73
+ cleanupSteps: []
111
74
  };
112
- this.proxyFocus = this.proxyFocus.bind(this);
113
75
  this.addEventListener("click", this.handleClickCapture, {
114
76
  capture: true
115
77
  });
@@ -191,9 +153,6 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
191
153
  return false;
192
154
  }
193
155
  }
194
- proxyFocus() {
195
- this.focus();
196
- }
197
156
  shouldProxyClick() {
198
157
  let handled = false;
199
158
  if (this.anchorElement) {
@@ -206,6 +165,50 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
206
165
  this._itemChildren = void 0;
207
166
  this.triggerUpdate();
208
167
  }
168
+ renderSubmenu() {
169
+ const slot = html`
170
+ <slot
171
+ name="submenu"
172
+ @slotchange=${this.manageSubmenu}
173
+ @sp-menu-item-added-or-updated=${{
174
+ handleEvent: (event) => {
175
+ event.clear(event.item);
176
+ },
177
+ capture: true
178
+ }}
179
+ @focusin=${(event) => event.stopPropagation()}
180
+ ></slot>
181
+ `;
182
+ if (!this.hasSubmenu) {
183
+ return slot;
184
+ }
185
+ return html`
186
+ <sp-overlay
187
+ .triggerElement=${this}
188
+ ?disabled=${!this.hasSubmenu}
189
+ ?open=${this.hasSubmenu && this.open}
190
+ .placement=${this.isLTR ? "right-start" : "left-start"}
191
+ .offset=${[-10, -5]}
192
+ .type=${"auto"}
193
+ @close=${(event) => event.stopPropagation()}
194
+ >
195
+ <sp-popover
196
+ @change=${(event) => {
197
+ this.handleSubmenuChange(event);
198
+ this.open = false;
199
+ }}
200
+ @pointerenter=${this.handleSubmenuPointerenter}
201
+ @pointerleave=${this.handleSubmenuPointerleave}
202
+ @sp-menu-item-added-or-updated=${(event) => event.stopPropagation()}
203
+ >
204
+ ${slot}
205
+ </sp-popover>
206
+ </sp-overlay>
207
+ <sp-icon-chevron100
208
+ class="spectrum-UIIcon-ChevronRight100 chevron icon"
209
+ ></sp-icon-chevron100>
210
+ `;
211
+ }
209
212
  render() {
210
213
  return html`
211
214
  <slot name="icon"></slot>
@@ -224,26 +227,17 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
224
227
  ariaHidden: true,
225
228
  className: "button anchor hidden"
226
229
  }) : html``}
227
- <slot
228
- hidden
229
- name="submenu"
230
- @slotchange=${this.manageSubmenu}
231
- ></slot>
232
- ${this.hasSubmenu ? html`
233
- <sp-icon-chevron100
234
- class="spectrum-UIIcon-ChevronRight100 chevron icon"
235
- ></sp-icon-chevron100>
236
- ` : html``}
230
+ ${this.renderSubmenu()}
237
231
  `;
238
232
  }
239
233
  manageSubmenu(event) {
240
234
  const assignedElements = event.target.assignedElements({
241
235
  flatten: true
242
236
  });
243
- this.hasSubmenu = this.open || !!assignedElements.length;
237
+ this.hasSubmenu = !!assignedElements.length;
244
238
  }
245
- handleRemoveActive(event) {
246
- if (event.type === "pointerleave" && this.hasSubmenu || this.hasSubmenu || this.open) {
239
+ handleRemoveActive() {
240
+ if (this.open) {
247
241
  return;
248
242
  }
249
243
  this.active = false;
@@ -255,20 +249,21 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
255
249
  super.firstUpdated(changes);
256
250
  this.setAttribute("tabindex", "-1");
257
251
  this.addEventListener("pointerdown", this.handlePointerdown);
252
+ this.addEventListener("pointerenter", this.closeOverlaysForRoot);
258
253
  if (!this.hasAttribute("id")) {
259
- this.id = `sp-menu-item-${_MenuItem.instanceCount++}`;
254
+ this.id = `sp-menu-item-${crypto.randomUUID().slice(0, 8)}`;
260
255
  }
261
- this.addEventListener("pointerenter", this.closeOverlaysForRoot);
262
256
  }
263
257
  closeOverlaysForRoot() {
258
+ var _a;
264
259
  if (this.open)
265
260
  return;
266
- const overalyCloseEvent = new OverlayCloseEvent({
267
- root: this.menuData.focusRoot
268
- });
269
- this.dispatchEvent(overalyCloseEvent);
261
+ (_a = this.menuData.parentMenu) == null ? void 0 : _a.closeDescendentOverlays();
270
262
  }
271
- handleSubmenuClick() {
263
+ handleSubmenuClick(event) {
264
+ if (event.composedPath().includes(this.overlayElement)) {
265
+ return;
266
+ }
272
267
  this.openOverlay();
273
268
  }
274
269
  handlePointerenter() {
@@ -280,64 +275,53 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
280
275
  this.openOverlay();
281
276
  }
282
277
  handlePointerleave() {
283
- if (this.hasSubmenu && this.open) {
278
+ if (this.open && !this.recentlyLeftChild) {
284
279
  this.leaveTimeout = setTimeout(() => {
285
280
  delete this.leaveTimeout;
286
- if (this.closeOverlay)
287
- this.closeOverlay();
281
+ this.open = false;
288
282
  }, POINTERLEAVE_TIMEOUT);
289
283
  }
290
284
  }
285
+ /**
286
+ * When there is a `change` event in the submenu for this item
287
+ * then we "click" this item to cascade the selection up the
288
+ * menu tree allowing all submenus between the initial selection
289
+ * and the root of the tree to have their selection changes and
290
+ * be closed.
291
+ */
292
+ handleSubmenuChange(event) {
293
+ var _a;
294
+ event.stopPropagation();
295
+ (_a = this.menuData.selectionRoot) == null ? void 0 : _a.selectOrToggleItem(this);
296
+ }
297
+ handleSubmenuPointerenter() {
298
+ this.recentlyLeftChild = true;
299
+ }
300
+ async handleSubmenuPointerleave() {
301
+ requestAnimationFrame(() => {
302
+ this.recentlyLeftChild = false;
303
+ });
304
+ }
305
+ handleSubmenuOpen(event) {
306
+ this.focused = false;
307
+ const parentOverlay = event.composedPath().find((el) => {
308
+ return el !== this.overlayElement && el.localName === "sp-overlay";
309
+ });
310
+ this.overlayElement.parentOverlayToForceClose = parentOverlay;
311
+ }
312
+ cleanup() {
313
+ this.open = false;
314
+ this.active = false;
315
+ }
291
316
  async openOverlay() {
292
317
  if (!this.hasSubmenu || this.open || this.disabled) {
293
318
  return;
294
319
  }
295
320
  this.open = true;
296
321
  this.active = true;
297
- const submenu = this.shadowRoot.querySelector(
298
- 'slot[name="submenu"]'
299
- ).assignedElements()[0];
300
- submenu.addEventListener(
301
- "pointerenter",
302
- this.handleSubmenuPointerenter
303
- );
304
- submenu.addEventListener("change", this.handleSubmenuChange);
305
- const popover = document.createElement("sp-popover");
306
- const returnSubmenu = reparentChildren([submenu], popover, {
307
- position: "beforeend",
308
- prepareCallback: (el) => {
309
- const slotName = el.slot;
310
- el.tabIndex = 0;
311
- el.removeAttribute("slot");
312
- el.isSubmenu = true;
313
- return (el2) => {
314
- el2.tabIndex = -1;
315
- el2.slot = slotName;
316
- el2.isSubmenu = false;
317
- };
318
- }
319
- });
320
- const closeOverlay = openOverlay(this, "click", popover, {
321
- placement: this.isLTR ? "right-start" : "left-start",
322
- receivesFocus: "auto",
323
- root: this.menuData.focusRoot
324
- });
325
- const closeSubmenu = async () => {
326
- delete this.closeOverlay;
327
- (await closeOverlay)();
328
- };
329
- this.closeOverlay = closeSubmenu;
330
- const cleanup = (event) => {
331
- event.stopPropagation();
332
- delete this.closeOverlay;
333
- returnSubmenu();
334
- this.open = false;
335
- this.active = false;
336
- };
337
- this.addEventListener("sp-closed", cleanup, {
322
+ this.addEventListener("sp-closed", this.cleanup, {
338
323
  once: true
339
324
  });
340
- popover.addEventListener("change", closeSubmenu);
341
325
  }
342
326
  updateAriaSelected() {
343
327
  const role = this.getAttribute("role");
@@ -355,12 +339,14 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
355
339
  this.updateAriaSelected();
356
340
  }
357
341
  updated(changes) {
342
+ var _a;
358
343
  super.updated(changes);
359
- if (changes.has("label")) {
344
+ if (changes.has("label") && (this.label || typeof changes.get("label") !== "undefined")) {
360
345
  this.setAttribute("aria-label", this.label || "");
361
346
  }
362
- if (changes.has("active")) {
347
+ if (changes.has("active") && (this.active || typeof changes.get("active") !== "undefined")) {
363
348
  if (this.active) {
349
+ (_a = this.menuData.selectionRoot) == null ? void 0 : _a.closeDescendentOverlays();
364
350
  this.addEventListener("pointerup", this.handleRemoveActive);
365
351
  this.addEventListener("pointerleave", this.handleRemoveActive);
366
352
  this.addEventListener("pointercancel", this.handleRemoveActive);
@@ -383,11 +369,12 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
383
369
  if (changes.has("selected")) {
384
370
  this.updateAriaSelected();
385
371
  }
386
- if (changes.has("hasSubmenu")) {
372
+ if (changes.has("hasSubmenu") && (this.hasSubmenu || typeof changes.get("hasSubmenu") !== "undefined")) {
387
373
  if (this.hasSubmenu) {
388
374
  this.addEventListener("click", this.handleSubmenuClick);
389
375
  this.addEventListener("pointerenter", this.handlePointerenter);
390
376
  this.addEventListener("pointerleave", this.handlePointerleave);
377
+ this.addEventListener("sp-opened", this.handleSubmenuOpen);
391
378
  } else if (!this.closeOverlay) {
392
379
  this.removeEventListener("click", this.handleSubmenuClick);
393
380
  this.removeEventListener(
@@ -398,39 +385,31 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
398
385
  "pointerleave",
399
386
  this.handlePointerleave
400
387
  );
388
+ this.removeEventListener("sp-opened", this.handleSubmenuOpen);
401
389
  }
402
390
  }
403
391
  }
404
392
  connectedCallback() {
405
393
  super.connectedCallback();
406
- this.isInSubmenu = !!this.closest('[slot="submenu"]');
407
- if (this.isInSubmenu) {
408
- return;
409
- }
410
- addOrUpdateEvent.reset(this);
411
- this.dispatchEvent(addOrUpdateEvent);
412
- this._parentElement = this.parentElement;
394
+ this.triggerUpdate();
413
395
  }
414
396
  disconnectedCallback() {
415
- var _a;
416
- removeEvent.reset(this);
417
- if (!this.isInSubmenu) {
418
- (_a = this._parentElement) == null ? void 0 : _a.dispatchEvent(removeEvent);
419
- }
420
- this.isInSubmenu = false;
397
+ this.menuData.cleanupSteps.forEach((removal) => removal(this));
421
398
  super.disconnectedCallback();
422
399
  }
423
400
  async triggerUpdate() {
424
- if (this.isInSubmenu) {
401
+ if (this.willDispatchUpdate) {
425
402
  return;
426
403
  }
404
+ this.willDispatchUpdate = true;
427
405
  await new Promise((ready) => requestAnimationFrame(ready));
428
- addOrUpdateEvent.reset(this);
429
- this.dispatchEvent(addOrUpdateEvent);
406
+ this.dispatchUpdate();
430
407
  }
431
- };
432
- export let MenuItem = _MenuItem;
433
- MenuItem.instanceCount = 0;
408
+ dispatchUpdate() {
409
+ this.dispatchEvent(new MenuItemAddedOrUpdatedEvent(this));
410
+ this.willDispatchUpdate = false;
411
+ }
412
+ }
434
413
  __decorateClass([
435
414
  property({ type: Boolean, reflect: true })
436
415
  ], MenuItem.prototype, "active", 2);
@@ -460,6 +439,9 @@ __decorateClass([
460
439
  query(".anchor")
461
440
  ], MenuItem.prototype, "anchorElement", 2);
462
441
  __decorateClass([
463
- property({ type: Boolean })
442
+ query("sp-overlay")
443
+ ], MenuItem.prototype, "overlayElement", 2);
444
+ __decorateClass([
445
+ property({ type: Boolean, reflect: true })
464
446
  ], MenuItem.prototype, "open", 2);
465
447
  //# sourceMappingURL=MenuItem.dev.js.map