@spectrum-web-components/menu 0.34.0 → 0.34.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 class=\"header\" ?hidden=${!this.headerElement}>\n <slot name=\"header\" @slotchange=${this.updateLabel}></slot>\n </span>\n <sp-menu ignore>\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,2CAC4B,CAAC,KAAK;AAAA,kDACC,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,SAMnD,CACJ,EAlEO,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 class=\"header\" ?hidden=${!this.headerElement}>\n <slot name=\"header\" @slotchange=${this.updateLabel}></slot>\n </span>\n <sp-menu ignore>${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,2CAC4B,CAAC,KAAK;AAAA,kDACC,KAAK;AAAA;AAAA,8BAEzB,KAAK,mBAAmB;AAAA,SAElD,CACJ,CAhDYO,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,83 +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();
76
49
  }
77
- }
78
- let addOrUpdateEvent = new MenuItemAddedOrUpdatedEvent();
79
- let removeEvent = new MenuItemRemovedEvent();
80
- let addOrUpdateEventRafId = 0;
81
- function resetAddOrUpdateEvent() {
82
- if (addOrUpdateEventRafId === 0) {
83
- addOrUpdateEventRafId = requestAnimationFrame(() => {
84
- addOrUpdateEvent = new MenuItemAddedOrUpdatedEvent();
85
- addOrUpdateEventRafId = 0;
86
- });
87
- }
88
- }
89
- let removeEventEventtRafId = 0;
90
- function resetRemoveEvent() {
91
- if (removeEventEventtRafId === 0) {
92
- removeEventEventtRafId = requestAnimationFrame(() => {
93
- removeEvent = new MenuItemRemovedEvent();
94
- removeEventEventtRafId = 0;
95
- });
50
+ get item() {
51
+ return this._item;
96
52
  }
97
53
  }
98
- const _MenuItem = class extends LikeAnchor(Focusable) {
54
+ export class MenuItem extends LikeAnchor(Focusable) {
99
55
  constructor() {
100
56
  super();
101
- this.isInSubmenu = false;
102
57
  this.active = false;
103
58
  this.focused = false;
104
59
  this.selected = false;
@@ -106,28 +61,17 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
106
61
  this.hasSubmenu = false;
107
62
  this.noWrap = false;
108
63
  this.open = false;
109
- /**
110
- * When there is a `change` event in the submenu for this item
111
- * then we "click" this item to cascade the selection up the
112
- * menu tree allowing all submenus between the initial selection
113
- * and the root of the tree to have their selection changes and
114
- * be closed.
115
- */
116
- this.handleSubmenuChange = () => {
117
- var _a;
118
- (_a = this.menuData.selectionRoot) == null ? void 0 : _a.selectOrToggleItem(this);
119
- };
120
- this.handleSubmenuPointerenter = () => {
121
- if (this.leaveTimeout) {
122
- clearTimeout(this.leaveTimeout);
123
- delete this.leaveTimeout;
124
- }
64
+ this.proxyFocus = () => {
65
+ this.focus();
125
66
  };
67
+ this.recentlyLeftChild = false;
68
+ this.willDispatchUpdate = false;
126
69
  this.menuData = {
127
70
  focusRoot: void 0,
128
- selectionRoot: void 0
71
+ parentMenu: void 0,
72
+ selectionRoot: void 0,
73
+ cleanupSteps: []
129
74
  };
130
- this.proxyFocus = this.proxyFocus.bind(this);
131
75
  this.addEventListener("click", this.handleClickCapture, {
132
76
  capture: true
133
77
  });
@@ -209,9 +153,6 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
209
153
  return false;
210
154
  }
211
155
  }
212
- proxyFocus() {
213
- this.focus();
214
- }
215
156
  shouldProxyClick() {
216
157
  let handled = false;
217
158
  if (this.anchorElement) {
@@ -224,6 +165,50 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
224
165
  this._itemChildren = void 0;
225
166
  this.triggerUpdate();
226
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
+ }
227
212
  render() {
228
213
  return html`
229
214
  <slot name="icon"></slot>
@@ -242,26 +227,17 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
242
227
  ariaHidden: true,
243
228
  className: "button anchor hidden"
244
229
  }) : html``}
245
- <slot
246
- hidden
247
- name="submenu"
248
- @slotchange=${this.manageSubmenu}
249
- ></slot>
250
- ${this.hasSubmenu ? html`
251
- <sp-icon-chevron100
252
- class="spectrum-UIIcon-ChevronRight100 chevron icon"
253
- ></sp-icon-chevron100>
254
- ` : html``}
230
+ ${this.renderSubmenu()}
255
231
  `;
256
232
  }
257
233
  manageSubmenu(event) {
258
234
  const assignedElements = event.target.assignedElements({
259
235
  flatten: true
260
236
  });
261
- this.hasSubmenu = this.open || !!assignedElements.length;
237
+ this.hasSubmenu = !!assignedElements.length;
262
238
  }
263
- handleRemoveActive(event) {
264
- if (event.type === "pointerleave" && this.hasSubmenu || this.hasSubmenu || this.open) {
239
+ handleRemoveActive() {
240
+ if (this.open) {
265
241
  return;
266
242
  }
267
243
  this.active = false;
@@ -273,20 +249,21 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
273
249
  super.firstUpdated(changes);
274
250
  this.setAttribute("tabindex", "-1");
275
251
  this.addEventListener("pointerdown", this.handlePointerdown);
252
+ this.addEventListener("pointerenter", this.closeOverlaysForRoot);
276
253
  if (!this.hasAttribute("id")) {
277
- this.id = `sp-menu-item-${_MenuItem.instanceCount++}`;
254
+ this.id = `sp-menu-item-${crypto.randomUUID().slice(0, 8)}`;
278
255
  }
279
- this.addEventListener("pointerenter", this.closeOverlaysForRoot);
280
256
  }
281
257
  closeOverlaysForRoot() {
258
+ var _a;
282
259
  if (this.open)
283
260
  return;
284
- const overalyCloseEvent = new OverlayCloseEvent({
285
- root: this.menuData.focusRoot
286
- });
287
- this.dispatchEvent(overalyCloseEvent);
261
+ (_a = this.menuData.parentMenu) == null ? void 0 : _a.closeDescendentOverlays();
288
262
  }
289
- handleSubmenuClick() {
263
+ handleSubmenuClick(event) {
264
+ if (event.composedPath().includes(this.overlayElement)) {
265
+ return;
266
+ }
290
267
  this.openOverlay();
291
268
  }
292
269
  handlePointerenter() {
@@ -298,64 +275,53 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
298
275
  this.openOverlay();
299
276
  }
300
277
  handlePointerleave() {
301
- if (this.hasSubmenu && this.open) {
278
+ if (this.open && !this.recentlyLeftChild) {
302
279
  this.leaveTimeout = setTimeout(() => {
303
280
  delete this.leaveTimeout;
304
- if (this.closeOverlay)
305
- this.closeOverlay();
281
+ this.open = false;
306
282
  }, POINTERLEAVE_TIMEOUT);
307
283
  }
308
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
+ }
309
316
  async openOverlay() {
310
317
  if (!this.hasSubmenu || this.open || this.disabled) {
311
318
  return;
312
319
  }
313
320
  this.open = true;
314
321
  this.active = true;
315
- const submenu = this.shadowRoot.querySelector(
316
- 'slot[name="submenu"]'
317
- ).assignedElements()[0];
318
- submenu.addEventListener(
319
- "pointerenter",
320
- this.handleSubmenuPointerenter
321
- );
322
- submenu.addEventListener("change", this.handleSubmenuChange);
323
- const popover = document.createElement("sp-popover");
324
- const returnSubmenu = reparentChildren([submenu], popover, {
325
- position: "beforeend",
326
- prepareCallback: (el) => {
327
- const slotName = el.slot;
328
- el.tabIndex = 0;
329
- el.removeAttribute("slot");
330
- el.isSubmenu = true;
331
- return (el2) => {
332
- el2.tabIndex = -1;
333
- el2.slot = slotName;
334
- el2.isSubmenu = false;
335
- };
336
- }
337
- });
338
- const closeOverlay = openOverlay(this, "click", popover, {
339
- placement: this.isLTR ? "right-start" : "left-start",
340
- receivesFocus: "auto",
341
- root: this.menuData.focusRoot
342
- });
343
- const closeSubmenu = async () => {
344
- delete this.closeOverlay;
345
- (await closeOverlay)();
346
- };
347
- this.closeOverlay = closeSubmenu;
348
- const cleanup = (event) => {
349
- event.stopPropagation();
350
- delete this.closeOverlay;
351
- returnSubmenu();
352
- this.open = false;
353
- this.active = false;
354
- };
355
- this.addEventListener("sp-closed", cleanup, {
322
+ this.addEventListener("sp-closed", this.cleanup, {
356
323
  once: true
357
324
  });
358
- popover.addEventListener("change", closeSubmenu);
359
325
  }
360
326
  updateAriaSelected() {
361
327
  const role = this.getAttribute("role");
@@ -373,12 +339,14 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
373
339
  this.updateAriaSelected();
374
340
  }
375
341
  updated(changes) {
342
+ var _a;
376
343
  super.updated(changes);
377
- if (changes.has("label")) {
344
+ if (changes.has("label") && (this.label || typeof changes.get("label") !== "undefined")) {
378
345
  this.setAttribute("aria-label", this.label || "");
379
346
  }
380
- if (changes.has("active")) {
347
+ if (changes.has("active") && (this.active || typeof changes.get("active") !== "undefined")) {
381
348
  if (this.active) {
349
+ (_a = this.menuData.selectionRoot) == null ? void 0 : _a.closeDescendentOverlays();
382
350
  this.addEventListener("pointerup", this.handleRemoveActive);
383
351
  this.addEventListener("pointerleave", this.handleRemoveActive);
384
352
  this.addEventListener("pointercancel", this.handleRemoveActive);
@@ -401,11 +369,12 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
401
369
  if (changes.has("selected")) {
402
370
  this.updateAriaSelected();
403
371
  }
404
- if (changes.has("hasSubmenu")) {
372
+ if (changes.has("hasSubmenu") && (this.hasSubmenu || typeof changes.get("hasSubmenu") !== "undefined")) {
405
373
  if (this.hasSubmenu) {
406
374
  this.addEventListener("click", this.handleSubmenuClick);
407
375
  this.addEventListener("pointerenter", this.handlePointerenter);
408
376
  this.addEventListener("pointerleave", this.handlePointerleave);
377
+ this.addEventListener("sp-opened", this.handleSubmenuOpen);
409
378
  } else if (!this.closeOverlay) {
410
379
  this.removeEventListener("click", this.handleSubmenuClick);
411
380
  this.removeEventListener(
@@ -416,42 +385,31 @@ const _MenuItem = class extends LikeAnchor(Focusable) {
416
385
  "pointerleave",
417
386
  this.handlePointerleave
418
387
  );
388
+ this.removeEventListener("sp-opened", this.handleSubmenuOpen);
419
389
  }
420
390
  }
421
391
  }
422
392
  connectedCallback() {
423
393
  super.connectedCallback();
424
- this.isInSubmenu = !!this.closest('[slot="submenu"]');
425
- if (this.isInSubmenu) {
426
- return;
427
- }
428
- addOrUpdateEvent.reset(this);
429
- this.dispatchEvent(addOrUpdateEvent);
430
- resetAddOrUpdateEvent();
431
- this._parentElement = this.parentElement;
394
+ this.triggerUpdate();
432
395
  }
433
396
  disconnectedCallback() {
434
- if (!this.isInSubmenu && this._parentElement) {
435
- removeEvent.reset(this);
436
- this._parentElement.dispatchEvent(removeEvent);
437
- resetRemoveEvent();
438
- }
439
- this.isInSubmenu = false;
440
- this._itemChildren = void 0;
397
+ this.menuData.cleanupSteps.forEach((removal) => removal(this));
441
398
  super.disconnectedCallback();
442
399
  }
443
400
  async triggerUpdate() {
444
- if (this.isInSubmenu) {
401
+ if (this.willDispatchUpdate) {
445
402
  return;
446
403
  }
404
+ this.willDispatchUpdate = true;
447
405
  await new Promise((ready) => requestAnimationFrame(ready));
448
- addOrUpdateEvent.reset(this);
449
- this.dispatchEvent(addOrUpdateEvent);
450
- resetAddOrUpdateEvent();
406
+ this.dispatchUpdate();
451
407
  }
452
- };
453
- export let MenuItem = _MenuItem;
454
- MenuItem.instanceCount = 0;
408
+ dispatchUpdate() {
409
+ this.dispatchEvent(new MenuItemAddedOrUpdatedEvent(this));
410
+ this.willDispatchUpdate = false;
411
+ }
412
+ }
455
413
  __decorateClass([
456
414
  property({ type: Boolean, reflect: true })
457
415
  ], MenuItem.prototype, "active", 2);
@@ -481,6 +439,9 @@ __decorateClass([
481
439
  query(".anchor")
482
440
  ], MenuItem.prototype, "anchorElement", 2);
483
441
  __decorateClass([
484
- property({ type: Boolean })
442
+ query("sp-overlay")
443
+ ], MenuItem.prototype, "overlayElement", 2);
444
+ __decorateClass([
445
+ property({ type: Boolean, reflect: true })
485
446
  ], MenuItem.prototype, "open", 2);
486
447
  //# sourceMappingURL=MenuItem.dev.js.map