@greypan/web-ui 9.0.0 → 10.0.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.
Files changed (50) hide show
  1. package/README.CN.md +102 -31
  2. package/README.md +115 -42
  3. package/dist/assets/modal-surface.js +6 -0
  4. package/dist/assets/modal-surface.js.map +1 -0
  5. package/dist/components/context-menu/index.d.ts +15 -0
  6. package/dist/components/context-menu/index.js +50 -46
  7. package/dist/components/context-menu/index.js.map +1 -1
  8. package/dist/components/context-menu/scrim.js +1 -1
  9. package/dist/components/context-menu/scrim.js.map +1 -1
  10. package/dist/components/dialog/index.d.ts +30 -5
  11. package/dist/components/dialog/index.js +53 -33
  12. package/dist/components/dialog/index.js.map +1 -1
  13. package/dist/components/dialog/style.js +1 -1
  14. package/dist/components/dialog/style.js.map +1 -1
  15. package/dist/components/drawer/index.js +54 -49
  16. package/dist/components/drawer/index.js.map +1 -1
  17. package/dist/components/drawer/style.js +1 -1
  18. package/dist/components/drawer/style.js.map +1 -1
  19. package/dist/components/image-preview/index.js +66 -61
  20. package/dist/components/image-preview/index.js.map +1 -1
  21. package/dist/components/image-preview/style.js +1 -1
  22. package/dist/components/image-preview/style.js.map +1 -1
  23. package/dist/components/index.d.ts +1 -0
  24. package/dist/components/index.js +17 -16
  25. package/dist/components/input/style.js +1 -1
  26. package/dist/components/input/style.js.map +1 -1
  27. package/dist/components/input-number/style.js +1 -1
  28. package/dist/components/input-number/style.js.map +1 -1
  29. package/dist/components/layout/index.d.ts +22 -4
  30. package/dist/components/layout/index.js +46 -26
  31. package/dist/components/layout/index.js.map +1 -1
  32. package/dist/components/layout/style.js +1 -1
  33. package/dist/components/layout/style.js.map +1 -1
  34. package/dist/components/middle-ellipsis/index.d.ts +100 -0
  35. package/dist/components/middle-ellipsis/index.js +112 -0
  36. package/dist/components/middle-ellipsis/index.js.map +1 -0
  37. package/dist/components/middle-ellipsis/style.js +6 -0
  38. package/dist/components/middle-ellipsis/style.js.map +1 -0
  39. package/dist/components/middle-ellipsis/truncate.d.ts +62 -0
  40. package/dist/components/middle-ellipsis/truncate.js +45 -0
  41. package/dist/components/middle-ellipsis/truncate.js.map +1 -0
  42. package/dist/components/textarea/style.js +1 -1
  43. package/dist/components/textarea/style.js.map +1 -1
  44. package/dist/scrollbars/index.d.ts +33 -0
  45. package/dist/scrollbars/index.js +18 -0
  46. package/dist/scrollbars/index.js.map +1 -0
  47. package/dist/scrollbars/style.css +10 -0
  48. package/dist/shared/element-width/index.js +37 -0
  49. package/dist/shared/element-width/index.js.map +1 -0
  50. package/package.json +14 -2
@@ -5,19 +5,20 @@ import { computePosition as r, flip as i, shift as a } from "../../node_modules/
5
5
  import { defineOverlayPositioningGeneration as o } from "../../shared/overlay/positioning-generation.js";
6
6
  import { hideOverlayPresence as s, showOverlayPresence as c } from "../../shared/overlay/presence.js";
7
7
  import { defineOpenOverlay as l } from "../../shared/overlay/open-overlay.js";
8
- import { defineScrollLockLease as u } from "../../shared/scroll-lock/scroll-lock.js";
9
- import { UserChangeController as d } from "../../shared/events/user-change.js";
10
- import { captureFrameworkAnchors as f, findFocusedMenuItem as p, focusMenuItem as m, getMenuChildren as h, getMenuItemFromEvent as g, hideNestedMenuChildren as _, moveMenuChildren as v, orderManagedMenuItems as y, reconcileManagedMenuItems as b, restoreFrameworkAnchors as x, returnManagedMenuItemsToSlot as S } from "../../shared/menu-portal/menu-tree.js";
11
- import { createClosingSubmenuStack as C, createMenuHoverBinder as w, createMenuOutsideClickGuard as T, getEnabledMenuLevelItems as E, getFocusedMenuItemFromPanels as D, handleMenuKeyboard as O } from "../../shared/menu-behavior/index.js";
12
- import { createMenuPortalOverlay as k } from "../../shared/menu-portal/menu-portal.js";
13
- import A from "./scrim.js";
14
- import j from "./style.js";
15
- import { LitElement as M, html as N, unsafeCSS as P } from "lit";
16
- import { customElement as F, property as I, state as L } from "lit/decorators.js";
8
+ import { findNearestTheme as u } from "../../shared/overlay/theme-overlay-scope.js";
9
+ import { defineScrollLockLease as d } from "../../shared/scroll-lock/scroll-lock.js";
10
+ import { UserChangeController as f } from "../../shared/events/user-change.js";
11
+ import { captureFrameworkAnchors as p, findFocusedMenuItem as m, focusMenuItem as h, getMenuChildren as g, getMenuItemFromEvent as _, hideNestedMenuChildren as v, moveMenuChildren as y, orderManagedMenuItems as b, reconcileManagedMenuItems as x, restoreFrameworkAnchors as S, returnManagedMenuItemsToSlot as C } from "../../shared/menu-portal/menu-tree.js";
12
+ import { createClosingSubmenuStack as w, createMenuHoverBinder as T, createMenuOutsideClickGuard as E, getEnabledMenuLevelItems as D, getFocusedMenuItemFromPanels as O, handleMenuKeyboard as k } from "../../shared/menu-behavior/index.js";
13
+ import { createMenuPortalOverlay as A } from "../../shared/menu-portal/menu-portal.js";
14
+ import j from "./scrim.js";
15
+ import M from "./style.js";
16
+ import { LitElement as N, html as P, unsafeCSS as F } from "lit";
17
+ import { customElement as I, property as L, state as R } from "lit/decorators.js";
17
18
  //#region src/components/context-menu/index.ts
18
- var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wui-menu-scrim", W = 5, G = 8, K = class extends M {
19
+ var z = "wui-context-menu-item", B = 500, V = 5e3, H = 10, U = 1e3, W = "data-wui-menu-scrim", G = 5, K = 8, q = class extends N {
19
20
  constructor(...e) {
20
- super(...e), this.disabled = !1, this.noScrollLock = !1, this.longPress = !1, this._longPressDelay = z, this._isOpen = !1, this._x = 0, this._y = 0, this._activeSubmenus = [], this._activeSubmenuItems = [], this._longPressOpenedAt = null, this._submenuPositionEpochs = /* @__PURE__ */ new WeakMap(), this._centerOnPressPoint = !1, this._closeSource = "default", this._menuHandle = null, this._submenuHandles = /* @__PURE__ */ new Map(), this._outsideClickGuard = T(this, (e) => this._menuHandle?.contains(e) ?? !1), this._closingSubmenus = C({
21
+ super(...e), this.disabled = !1, this.noScrollLock = !1, this.longPress = !1, this._longPressDelay = B, this._isOpen = !1, this._x = 0, this._y = 0, this._activeSubmenus = [], this._activeSubmenuItems = [], this._longPressOpenedAt = null, this._submenuPositionEpochs = /* @__PURE__ */ new WeakMap(), this._centerOnPressPoint = !1, this._closeSource = "default", this._menuHandle = null, this._submenuHandles = /* @__PURE__ */ new Map(), this._outsideClickGuard = E(this, (e) => this._menuHandle?.contains(e) ?? !1), this._closingSubmenus = w({
21
22
  getPanel: (e) => e.panel,
22
23
  restoreItems: (e, t) => this._restoreSubmenuItems(e, t),
23
24
  dispose: (e) => {
@@ -32,13 +33,13 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
32
33
  closeFrom: (e) => {
33
34
  this._closeSubmenusFrom(e), this._hoverBinder.bind();
34
35
  }
35
- }, this._hoverBinder = w(this._hoverDelegate, "web-ui-dropdown-item"), this._keyboardDelegate = {
36
- getFocusedItem: (e) => D(e, [this._menu?.content, ...this._activeSubmenus.map((e) => e.content)], "web-ui-dropdown-item"),
36
+ }, this._hoverBinder = T(this._hoverDelegate, "web-ui-dropdown-item"), this._keyboardDelegate = {
37
+ getFocusedItem: (e) => O(e, [this._menu?.content, ...this._activeSubmenus.map((e) => e.content)], "web-ui-dropdown-item"),
37
38
  getLevelOf: (e) => {
38
39
  let t = this._getItemLevel(e);
39
40
  return t === -1 ? void 0 : t;
40
41
  },
41
- getEnabledItems: (e) => E(this._getLevelContainer(e)?.content),
42
+ getEnabledItems: (e) => D(this._getLevelContainer(e)?.content),
42
43
  isSubmenuItem: (e) => e.hasAttribute("submenu"),
43
44
  openSubmenuInstant: (e) => this._openSubmenu(e, !0),
44
45
  closeToParent: (e) => {
@@ -49,7 +50,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
49
50
  }, this._overlay = l().make({
50
51
  requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),
51
52
  isConnected: () => this.isConnected
52
- }), this._menuItemAnchors = /* @__PURE__ */ new Map(), this._scrollLock = u().make(), this._userOpenChange = new d(), this._shouldOpenInstantly = !0, this._suppressInitialFocusVisible = !0, this._refreshScheduled = !1, this._menuPositionGeneration = o().make(), this._contentObserver = new MutationObserver(() => {
53
+ }), this._menuItemAnchors = /* @__PURE__ */ new Map(), this._scrollLock = d().make(), this._userOpenChange = new f(), this._shouldOpenInstantly = !0, this._suppressInitialFocusVisible = !0, this._refreshScheduled = !1, this._menuPositionGeneration = o().make(), this._contentObserver = new MutationObserver(() => {
53
54
  this._isOpen && this._scheduleRefresh();
54
55
  }), this._closeFromUser = (e = "default") => {
55
56
  this._isOpen && (this._userOpenChange.mark(), this._closeSource = e, this.close());
@@ -69,7 +70,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
69
70
  }, this._onPointerMove = (e) => {
70
71
  if (e.pointerType !== "touch") return;
71
72
  let t = this._longPressOrigin;
72
- t && Math.hypot(e.clientX - t.x, e.clientY - t.y) > V && this._cancelLongPress();
73
+ t && Math.hypot(e.clientX - t.x, e.clientY - t.y) > H && this._cancelLongPress();
73
74
  }, this._onPointerUp = (e) => {
74
75
  e.pointerType === "touch" && this._cancelLongPress();
75
76
  }, this._onPointerCancel = () => {
@@ -95,7 +96,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
95
96
  return;
96
97
  }
97
98
  }, this._onMenuClick = (e) => {
98
- let t = g(e);
99
+ let t = _(e);
99
100
  if (t && !t.hasAttribute("disabled")) {
100
101
  if (t.hasAttribute("submenu")) {
101
102
  this._openSubmenu(t);
@@ -108,18 +109,18 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
108
109
  }, this._onTouchMove = (e) => {
109
110
  this._preventBackgroundScroll(e);
110
111
  }, this._onDocumentKeydown = (e) => {
111
- this._isOpen && O(this._keyboardDelegate, e);
112
+ this._isOpen && k(this._keyboardDelegate, e);
112
113
  };
113
114
  }
114
115
  static {
115
- this.styles = P(j);
116
+ this.styles = F(M);
116
117
  }
117
118
  get longPressDelay() {
118
119
  return this._longPressDelay;
119
120
  }
120
121
  set longPressDelay(e) {
121
122
  let n = this._longPressDelay;
122
- this._longPressDelay = t(e, 0, B, z), this.requestUpdate("longPressDelay", n);
123
+ this._longPressDelay = t(e, 0, V, B), this.requestUpdate("longPressDelay", n);
123
124
  }
124
125
  get isOpen() {
125
126
  return this._isOpen;
@@ -145,7 +146,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
145
146
  if (this._isOpen) {
146
147
  if (this._syncScrollLock(), !this._menu) {
147
148
  let e = this._openScrim();
148
- this._menu = k("context-menu", this, e), this._menu.panel.setAttribute("role", "menu"), this._menu.panel.setAttribute("aria-label", "上下文菜单"), this._menu.panel.addEventListener("click", this._onMenuClick);
149
+ this._menu = A("context-menu", this, e), this._menu.panel.setAttribute("role", "menu"), this._menu.panel.setAttribute("aria-label", "上下文菜单"), this._menu.panel.addEventListener("click", this._onMenuClick);
149
150
  }
150
151
  if (!this._menuHandle) {
151
152
  this._menuHandle = this._overlay.claim(this._menu.panel);
@@ -188,10 +189,10 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
188
189
  let t = this._safeAreaBottom();
189
190
  e.style.visibility = "hidden", e.style.display = "";
190
191
  let n = this._menuPositionGeneration.next(), o = {
191
- top: G,
192
- bottom: G + t,
193
- left: G,
194
- right: G
192
+ top: K,
193
+ bottom: K + t,
194
+ left: K,
195
+ right: K
195
196
  }, s = this._centerOnPressPoint ? ["bottom", "top"] : ["bottom-start", "top-start"];
196
197
  r({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, e, {
197
198
  strategy: "fixed",
@@ -218,7 +219,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
218
219
  }
219
220
  _focusFirstItem(e = 0) {
220
221
  let t = this._menu?.content.querySelectorAll("web-ui-dropdown-item:not([disabled])")?.[0];
221
- t && (m(t, { suppressFocusVisible: this._suppressInitialFocusVisible }), !(e + 1 >= W) && (p([this._menu?.panel]) || requestAnimationFrame(() => {
222
+ t && (h(t, { suppressFocusVisible: this._suppressInitialFocusVisible }), !(e + 1 >= G) && (m([this._menu?.panel]) || requestAnimationFrame(() => {
222
223
  this._isOpen && this._menu && this._focusFirstItem(e + 1);
223
224
  })));
224
225
  }
@@ -226,19 +227,22 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
226
227
  let e = this._menu;
227
228
  if (!e) return;
228
229
  let t = e.content;
229
- b(this, t, this._menuItemAnchors, R), this._hideMenuItems(), _(t, "context-menu-hidden");
230
- let n = f(t, R);
231
- y(this, t, this._menuItemAnchors, R), x(t, n);
230
+ x(this, t, this._menuItemAnchors, z), this._hideMenuItems(), v(t, "context-menu-hidden");
231
+ let n = p(t, z);
232
+ b(this, t, this._menuItemAnchors, z), S(t, n);
232
233
  }
233
234
  _returnItemsToSlot() {
234
235
  let e = this._menu;
235
- e && (this._closeSubmenusFrom(0, !0), this._closingSubmenus.restoreAll(), S(this, e.content, this._menuItemAnchors));
236
+ e && (this._closeSubmenusFrom(0, !0), this._closingSubmenus.restoreAll(), C(this, e.content, this._menuItemAnchors));
236
237
  }
237
238
  _openScrim() {
238
239
  let e = document.createElement("dialog");
239
240
  e.className = "wui-context-menu-scrim";
240
241
  let t = document.createElement("style");
241
- return t.textContent = A, e.append(t), e.setAttribute(U, ""), e.addEventListener("click", this._onScrimClick), e.addEventListener("cancel", this._onScrimCancel), document.body.append(e), e.showModal(), this._scrim = e, e;
242
+ return t.textContent = j, e.append(t), e.setAttribute(W, ""), e.addEventListener("click", this._onScrimClick), e.addEventListener("cancel", this._onScrimCancel), this._scrimParent().append(e), e.showModal(), this._scrim = e, e;
243
+ }
244
+ _scrimParent() {
245
+ return u(this)?.getOverlayRoot() ?? document.body;
242
246
  }
243
247
  _closeScrim() {
244
248
  let e = this._scrim;
@@ -253,7 +257,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
253
257
  (!e || await s(e.panel)) && !this._isOpen && this.isConnected && this._menu === e && (this._closeScrim(), this._returnItemsToSlot(), e?.panel.remove(), this._menu = void 0);
254
258
  }
255
259
  _hideMenuItems() {
256
- h(this).forEach((e) => e.setAttribute("slot", "context-menu-hidden")), _(this, "context-menu-hidden");
260
+ g(this).forEach((e) => e.setAttribute("slot", "context-menu-hidden")), v(this, "context-menu-hidden");
257
261
  }
258
262
  _openSubmenu(e, t = !1) {
259
263
  if (!this._isOpen || !e.hasAttribute("submenu") || e.hasAttribute("disabled")) return;
@@ -265,9 +269,9 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
265
269
  this._activeSubmenus[n] = r, this._activeSubmenuItems[n] = e, this._menuHandle && this._submenuHandles.set(r.panel, this._menuHandle.adopt(r.panel)), e.setAttribute("active", ""), this._positionSubmenu(e, r), c(r.panel, { isInstant: t }), this._hoverBinder.bind();
266
270
  return;
267
271
  }
268
- if (h(e).length === 0) return;
269
- let i = k("context-submenu", this, this._scrim);
270
- i.panel.dataset.level = String(n), i.panel.setAttribute("role", "menu"), i.panel.setAttribute("aria-label", "子菜单"), i.panel.style.visibility = "hidden", i.panel.addEventListener("click", this._onMenuClick), v(e, i.content), this._activeSubmenus[n] = i, this._activeSubmenuItems[n] = e, this._menuHandle && this._submenuHandles.set(i.panel, this._menuHandle.adopt(i.panel)), e.setAttribute("active", ""), this._positionSubmenu(e, i), c(i.panel, { isInstant: t }), this._hoverBinder.bind();
272
+ if (g(e).length === 0) return;
273
+ let i = A("context-submenu", this, this._scrim);
274
+ i.panel.dataset.level = String(n), i.panel.setAttribute("role", "menu"), i.panel.setAttribute("aria-label", "子菜单"), i.panel.style.visibility = "hidden", i.panel.addEventListener("click", this._onMenuClick), y(e, i.content), this._activeSubmenus[n] = i, this._activeSubmenuItems[n] = e, this._menuHandle && this._submenuHandles.set(i.panel, this._menuHandle.adopt(i.panel)), e.setAttribute("active", ""), this._positionSubmenu(e, i), c(i.panel, { isInstant: t }), this._hoverBinder.bind();
271
275
  }
272
276
  _closeSubmenusFrom(e, t = !1) {
273
277
  for (let n = this._activeSubmenus.length - 1; n >= e; n--) {
@@ -277,7 +281,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
277
281
  this._activeSubmenus.length = e, this._activeSubmenuItems.length = e;
278
282
  }
279
283
  _restoreSubmenuItems(e, t) {
280
- t && (v(e.content, t), _(t, "context-menu-hidden"));
284
+ t && (y(e.content, t), v(t, "context-menu-hidden"));
281
285
  }
282
286
  _getItemLevel(e) {
283
287
  if (this._menu?.panel.contains(e)) return 0;
@@ -312,19 +316,19 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
312
316
  }
313
317
  _isLongPressFollowUp() {
314
318
  let e = this._longPressOpenedAt;
315
- return e === null ? !1 : performance.now() - e > H ? (this._longPressOpenedAt = null, !1) : !0;
319
+ return e === null ? !1 : performance.now() - e > U ? (this._longPressOpenedAt = null, !1) : !0;
316
320
  }
317
321
  _getLevelContainer(e) {
318
322
  return e === 0 ? this._menu : this._activeSubmenus[e - 1];
319
323
  }
320
324
  _getLevelItems(e) {
321
325
  let t = this._getLevelContainer(e);
322
- return t ? h(t.content) : [];
326
+ return t ? g(t.content) : [];
323
327
  }
324
328
  _closeLastSubmenuOrMenu() {
325
329
  if (this._activeSubmenus.length > 0) {
326
330
  let e = this._activeSubmenus.length - 1, t = this._activeSubmenuItems[e];
327
- this._closeSubmenusFrom(e), m(t), this._hoverBinder.bind();
331
+ this._closeSubmenusFrom(e), h(t), this._hoverBinder.bind();
328
332
  } else this._closeFromUser();
329
333
  }
330
334
  _preventBackgroundScroll(e) {
@@ -340,7 +344,7 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
340
344
  return this._menuHandle?.containsEvent(e) ?? !1;
341
345
  }
342
346
  render() {
343
- return N`
347
+ return P`
344
348
  <div class="context-menu-anchor">
345
349
  <slot @slotchange=${this._onSlotChange}></slot>
346
350
  </div>
@@ -350,22 +354,22 @@ var R = "wui-context-menu-item", z = 500, B = 5e3, V = 10, H = 1e3, U = "data-wu
350
354
  this._hideMenuItems(), this._isOpen && this._scheduleRefresh();
351
355
  }
352
356
  };
353
- e([I({
357
+ e([L({
354
358
  type: Boolean,
355
359
  reflect: !0
356
- })], K.prototype, "disabled", void 0), e([I({
360
+ })], q.prototype, "disabled", void 0), e([L({
357
361
  type: Boolean,
358
362
  reflect: !0,
359
363
  attribute: "no-scroll-lock"
360
- })], K.prototype, "noScrollLock", void 0), e([I({
364
+ })], q.prototype, "noScrollLock", void 0), e([L({
361
365
  type: Boolean,
362
366
  reflect: !0,
363
367
  attribute: "long-press"
364
- })], K.prototype, "longPress", void 0), e([I({
368
+ })], q.prototype, "longPress", void 0), e([L({
365
369
  type: Number,
366
370
  attribute: "long-press-delay"
367
- })], K.prototype, "longPressDelay", null), e([L()], K.prototype, "_isOpen", void 0), e([L()], K.prototype, "_x", void 0), e([L()], K.prototype, "_y", void 0), K = e([F("web-ui-context-menu")], K);
371
+ })], q.prototype, "longPressDelay", null), e([R()], q.prototype, "_isOpen", void 0), e([R()], q.prototype, "_x", void 0), e([R()], q.prototype, "_y", void 0), q = e([I("web-ui-context-menu")], q);
368
372
  //#endregion
369
- export { K as WebUiContextMenu };
373
+ export { q as WebUiContextMenu };
370
374
 
371
375
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开菜单后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与菜单关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、菜单也没关闭」这种不该发生的路径。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * 菜单打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— 面板与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** 面板与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开菜单。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开菜单、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开菜单的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的菜单立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开菜单(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步菜单开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n // 子菜单 dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级子菜单各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。菜单打开期间存在并处于 `showModal()` 状态,关闭时随面板一起移除。\n *\n * 面板必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是面板的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,菜单在它正下方居中展开,\n * 正是 iOS / Android 原生长按菜单的形态;左对齐会让菜单整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是菜单**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在面板上缘正中 —— 也就是落在第一项的命中区里。后果是菜单一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开子菜单)会打在菜单项上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有菜单项激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /** 根面板的开启会话句柄;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * 子菜单 panel → 会话句柄。子层经 `adopt` 进根句柄子树(默认不成为独立候选),\n * 但仍各自持有句柄:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根句柄的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态浮层(issue #120 Block 1)。根面板是登记对象:子菜单经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深子菜单优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主菜单 dialog 路径与子菜单一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // 菜单打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到菜单上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在 document.body 上,不随宿主一起脱离文档。不在这里收掉就会留下一张\n * 仍然 `showModal()` 的全屏 dialog:top layer 不受宿主移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,菜单无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把面板挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。面板随后 `_focusFirstItem()` 把焦点带进菜单,关闭时 UA 还回去。\n *\n * 面板挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,面板若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', '上下文菜单')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n }\n /*\n * 登记与面板建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把句柄\n * 撤了;此时重开必须补 claim。否则菜单开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括面板\n * 内部与子菜单父项——都被 `_outsideClickGuard` 判成外部点击而关闭菜单。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的子菜单面板要重新 adopt 回\n // 新句柄的子树,否则根句柄的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的子菜单面板同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,面板仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在句柄的登记树之外,被判成「面板外」,点它内部会关掉整张菜单。\n // 用例 `退场窗口内重开后,收尾中的子菜单面板在取回前后都不可被当成面板外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取句柄释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级子菜单在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 模态化之前没有这一步,也就没有这场竞争。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在菜单项内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。菜单已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开菜单。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n return true\n }\n\n /**\n * 关闭菜单。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * 菜单项激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而菜单项的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * 此刻焦点还在菜单里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到面板真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item') this._releaseScrimModality()\n this._closeSource = 'default'\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「面板上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 之前有一条手写的贴底分支(先按点、再按视口下缘算 `top`),那是把「贴视口底」硬编码\n * 成算术;真机反馈证明它错(菜单跑到屏幕最底部,与手指按下的位置完全脱节)。现在\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图。\n *\n * 不再按「是否触屏」分流:窄视口的鼠标右键同样期望菜单贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。分流所需的 `_anchorAtPressPoint`\n * 连同 `_positionMenuInViewport` / `_positionMenuAtPressPoint` 两个方法一起删掉 ——\n * 面板恒为 open scrim `<dialog>` 的直接子节点(`createMenuPortalOverlay` 第三参传入\n * scrim,`menu-portal.ts` 里 `container = scrim ?? …`),所以旧代码那条\n * 「非 dialog 容器」的分支对 context-menu 本来就不可达。\n *\n * 算出来的 left/top 是**视口坐标**。面板是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰面板:`getComputedStyle` 会强制一次同步布局,让它发生在面板进入\n * 「可测」状态之前,而不是夹在「面板刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因。把这一行移到\n * 最前面实测并没有让那个偏差消失(改前改后都是 204 对期望 206)。那个 2px 来自 flip 的\n * reset 会多跑一轮测量:那一刻面板宽 202px、落定后 200px。相关用例已经不再钉与视口\n * 边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把面板贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把面板下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把面板整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的选项,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):面板上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进面板上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的面板会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完面板仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— 面板下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且面板极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明面板\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,菜单才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 改按压点展开之后它**仍然必需**,而且不再只服务触屏:菜单默认从触发点向下长,触发点\n * 落在屏幕下半部时面板下缘会进入 home indicator 区域。它现在折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不再需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下面板坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「面板顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进菜单的第一项。\n *\n * `attempt` 是重试计数:菜单项是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「菜单开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— 菜单项若始终渲染不出来\n * (空菜单、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在菜单打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级菜单上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供面板挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,而 scrim 是 `document.body` 下的\n * light DOM 节点,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— 菜单一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n document.body.append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于面板 `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。面板挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且面板的层级退回普通流 —— 在「菜单开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在菜单项激活时。\n * 摘除仍在 `_closeScrim()`,那时面板已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关菜单(Q12:必关,不给 prop 选项),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在面板(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:菜单打开期间下层收不到命中,事件只可能\n * 落在 scrim 或面板上,两者都由本组件判定。\n *\n * 更早的实现在 document 捕获阶段挂 click 监听,把「点外面」理解成「命中测试落在菜单\n * 面板之外」—— 宿主 light DOM 的每一行都满足这个条件,于是判定与行激活成了同一个\n * click 的两个后果,中间没有任何仲裁,下层目标先被激活。那条监听器连同它的实现已一并\n * 删除:模态化之后它没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的菜单关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深子菜单还是关整张菜单。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张菜单没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除面板之前做完:面板一脱离文档,「焦点仍在菜单内」就再也读不到了。\n * 子菜单层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘面板,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不再由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——面板先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * 菜单项激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护不是被删除,是被**移到了更早的位置**:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是菜单项激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的面板写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的句柄。\n * 注:「面板在根句柄子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的面板一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取句柄),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * 子菜单与根菜单挂进**同一个** scrim。只改根菜单会让两层行为分裂:根层被 scrim\n * 罩住时,子菜单若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张菜单。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', '子菜单')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根句柄的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。原先按 level===0 选择根面板还是上一层子面板的父级判断随之消失:contains\n * 沿子树递归,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // 子菜单打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主菜单同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenu.panel.getBoundingClientRect().width + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主菜单 dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n const itemRect = item.getBoundingClientRect()\n const submenuRect = submenu.panel.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenuRect.width + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuRect.width)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuRect.height - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._longPressOpenedAt = null\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:面板上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不再二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType === 'touch') this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开菜单之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - openedAt > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._longPressOpenedAt = null\n return false\n }\n return true\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的菜单焦点不在菜单内,e.target !== this 时也必须能 Escape 关闭;\n // 菜单为模态浮层,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层子菜单的登记;句柄已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/context-menu/index.ts"],"sourcesContent":["import { computePosition, flip, shift } from '@floating-ui/dom'\nimport { html, LitElement, unsafeCSS } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { UserChangeController } from '@/shared/events/user-change'\nimport {\n createClosingSubmenuStack,\n createMenuHoverBinder,\n createMenuOutsideClickGuard,\n getEnabledMenuLevelItems,\n getFocusedMenuItemFromPanels,\n handleMenuKeyboard,\n type MenuHoverDelegate,\n type MenuKeyboardDelegate\n} from '@/shared/menu-behavior'\nimport { createMenuPortalOverlay, type MenuPortalOverlay } from '@/shared/menu-portal/menu-portal'\nimport {\n captureFrameworkAnchors,\n findFocusedMenuItem,\n focusMenuItem,\n getMenuChildren,\n getMenuItemFromEvent,\n hideNestedMenuChildren,\n moveMenuChildren,\n orderManagedMenuItems,\n reconcileManagedMenuItems,\n restoreFrameworkAnchors,\n returnManagedMenuItemsToSlot\n} from '@/shared/menu-portal/menu-tree'\nimport { normalizeNumber } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineOverlayPositioningGeneration } from '@/shared/overlay/positioning-generation'\nimport { hideOverlayPresence, showOverlayPresence } from '@/shared/overlay/presence'\nimport { findNearestTheme } from '@/shared/overlay/theme-overlay-scope'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\n\nimport scrimStyle from './scrim.css?inline'\nimport style from './style.css?inline'\n\nconst MARKER_TEXT = 'wui-context-menu-item'\n\n/** 与 iOS / Android 原生长按一致的默认时长。 */\nconst DEFAULT_LONG_PRESS_DELAY = 500\n/** long-press-delay 的上界:超过 5s 的长按不再是「长按」,等同没有该属性。 */\nconst MAX_LONG_PRESS_DELAY = 5000\n/** 按住期间的容差位移;超出即视为滚动/拖拽意图,取消长按。 */\nconst LONG_PRESS_MOVE_TOLERANCE = 10\n/**\n * 长按开菜单后吸收浏览器补发事件的窗口上限。\n *\n * 这不是时序保证:实测补发几乎紧跟抬手(touchend(1019ms) → click(1022ms),间隔 3ms),\n * 1000ms 远大于实际需要,只是一个陈旧兜底上限。真正的边界是下一次 `pointerdown`\n * (`_onPointerDown` 无条件清零,含鼠标)与菜单关闭,两条硬边界都在窗口判定之外,\n * 所以窗口只负责兜住「补发事件既没等到下一次按下、菜单也没关闭」这种不该发生的路径。\n */\nconst LONG_PRESS_FOLLOW_UP_WINDOW_MS = 1000\n\n/**\n * 菜单打开期间铺在整屏上的 scrim `<dialog>`。\n *\n * 用 `<dialog>` + `showModal()` 而不是 plain `div`:模态 dialog 进 top layer,\n * 于是「z-index 谁高谁低」这道约束整体消失 —— 面板与 scrim 同在 top layer,\n * 而 top layer 之下的任何 z-index 都碰不到它们。plain `div` 也能修好 light-dismiss\n * 穿透,但那样 scrim 必须靠 z-index 压过 `--wui-layer-menu`(100),是另一套易碎的约定。\n */\nconst SCRIM_ATTR = 'data-wui-menu-scrim'\n\ntype CloseSource = 'menu-item' | 'default'\n\n/** 首项聚焦的最大尝试次数(含首次)。见 `_focusFirstItem`。 */\nconst FOCUS_FIRST_ITEM_ATTEMPTS = 5\n\n/** 面板与视口边缘的最小间距,与既有夹取算术同源。 */\nconst VIEWPORT_PADDING = 8\n\n@customElement('web-ui-context-menu')\nexport class WebUiContextMenu extends LitElement {\n static override styles = unsafeCSS(style)\n\n @property({ type: Boolean, reflect: true }) disabled = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n /** opt-in:触屏长按打开菜单。默认关闭,鼠标路径靠原生 contextmenu,无需此属性。 */\n @property({ type: Boolean, reflect: true, attribute: 'long-press' }) longPress = false\n\n private _longPressDelay = DEFAULT_LONG_PRESS_DELAY\n\n /**\n * 走包内既有的 `normalizeNumber` 通道(与 `dropdown.offset`、`popover.offset` 同形)。\n * 不能只靠 `Math.max(0, …)` 挡负数:`Math.max(0, NaN) === NaN`,而属性写错\n * (`long-press-delay=\"abc\"`)时 Lit 的 Number converter 给的正是 NaN,\n * `setTimeout(fn, NaN)` 会立刻触发,长按就在 pointerdown 同一帧开菜单、绕过全部时长语义。\n */\n @property({ type: Number, attribute: 'long-press-delay' })\n get longPressDelay(): number {\n return this._longPressDelay\n }\n set longPressDelay(v: number) {\n const old = this._longPressDelay\n this._longPressDelay = normalizeNumber(v, 0, MAX_LONG_PRESS_DELAY, DEFAULT_LONG_PRESS_DELAY)\n this.requestUpdate('longPressDelay', old)\n }\n\n @state() private _isOpen = false\n @state() private _x = 0\n @state() private _y = 0\n\n private _activeSubmenus: MenuPortalOverlay[] = []\n private _activeSubmenuItems: HTMLElement[] = []\n private _longPressTimer: ReturnType<typeof setTimeout> | undefined\n private _longPressOrigin: { x: number; y: number } | undefined\n /**\n * 长按自行开菜单的时刻。浏览器会为**同一次触摸**补发两个事件,二者都属于\n * 那一次意图,不是两次独立操作:\n *\n * - 原生 `contextmenu`:长按被识别时立刻派发,会重复打开(`_openAt` 走已开分支)。\n * - 合成 `click`:`touchend` 之后由引擎补发,会把刚打开的菜单立刻 light-dismiss。\n * 实测真实触控管线下的顺序为 pointerdown(9ms) → 长按开菜单(≈509ms) →\n * touchend(1019ms) → click(1022ms),缺这一步菜单开了就被自己的补发事件关掉。\n *\n * 因此在窗口内吸收这两个补发事件;窗口由下一次 `pointerdown` 关闭。\n */\n private _longPressOpenedAt: number | null = null\n // 子菜单 dialog 定位的唯一写者代数(按 panel 键控):同帧关闭→重开复用同一 panel\n // 时只允许最新一次定位写入;不同层级子菜单各有 panel,互不作废。\n private readonly _submenuPositionEpochs = new WeakMap<HTMLElement, number>()\n private _menu?: MenuPortalOverlay\n /**\n * 模态 scrim。菜单打开期间存在并处于 `showModal()` 状态,关闭时随面板一起移除。\n *\n * 面板必须**显式**挂进它(`createMenuPortalOverlay` 的第三参):`resolveOverlayContainer`\n * 查的是 target 的**祖先**里有无已打开的 dialog,而 scrim 是面板的反方向,祖先发现查不到。\n */\n private _scrim?: HTMLDialogElement\n /**\n * 本次开启是否**水平居中于触发点**(触屏长按路径)。鼠标 / 键盘路径仍左上角对齐光标。\n *\n * 只切换 placement 的对齐量,不切换定位算法 —— 两条入口共用 `_positionMenu` 里的\n * 同一套 `computePosition` + `flip` / `shift`,垂直契约(上缘对齐触发点、向下展开、\n * 放不下翻到上方)完全一致。\n *\n * 触屏要居中:手指按下去的位置是「这块内容在这里」的意思,菜单在它正下方居中展开,\n * 正是 iOS / Android 原生长按菜单的形态;左对齐会让菜单整体偏到手指右侧,看起来像\n * 弹在了别的东西上面。\n *\n * 鼠标不居中:桌面右键的肌肉记忆是菜单**左上角**出现在光标处。更要紧的是居中会让光标\n * 落在面板上缘正中 —— 也就是落在第一项的命中区里。后果是菜单一开就有项处于 hover 高亮,\n * 并且紧接着再右键一次(想开子菜单)会打在菜单项上而不是空白处。`overlay-in-dialog`\n * 里 `openAt(100, 400)` 那条用例钉的 `'top left'` 正是这条契约。\n *\n * 判据是**开启来源**(调用点显式传入),不靠运行时嗅探 pointerType 或视口宽度:\n * 后两者会让同一块屏幕上的两种输入拿到两套布局,且与本组件 opt-in 的 `long-press`\n * 属性不同源。\n */\n private _centerOnPressPoint = false\n\n /**\n * 本次关闭的来源,决定焦点归还走哪条路。语义见 `close()` 里的说明。\n *\n * 只有菜单项激活这一条需要抢在退场之前出顶层;点外面 / Escape / 程序式关闭一律\n * 保留 dialog 默认效果 —— 那正是用户「回到原处」的期望。\n */\n private _closeSource: CloseSource = 'default'\n /** 根面板的开启会话句柄;未开启时为 null。查询走它。 */\n private _menuHandle: OpenOverlayHandle | null = null\n /**\n * 子菜单 panel → 会话句柄。子层经 `adopt` 进根句柄子树(默认不成为独立候选),\n * 但仍各自持有句柄:整层关闭或收尾动画结束时按 panel 精确撤销。\n */\n private readonly _submenuHandles = new Map<HTMLElement, OpenOverlayHandle>()\n // 行为层(hover / outside-click / 键盘 / submenu 收尾)由 shared/menu-behavior 驱动\n private readonly _outsideClickGuard = createMenuOutsideClickGuard(\n this,\n // 根句柄的 contains 递归覆盖全部已 adopt 的子层,一次查询替代逐 panel 判定。\n node => this._menuHandle?.contains(node) ?? false\n )\n private readonly _closingSubmenus = createClosingSubmenuStack<MenuPortalOverlay>({\n getPanel: container => container.panel,\n restoreItems: (container, parentItem) => this._restoreSubmenuItems(container, parentItem),\n dispose: container => {\n this._releaseSubmenu(container.panel)\n container.panel.remove()\n }\n })\n private readonly _hoverDelegate: MenuHoverDelegate = {\n getOpenDepth: () => this._activeSubmenus.length,\n getLevelItems: level => this._getLevelItems(level),\n getActiveItem: level => this._activeSubmenuItems[level],\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenu: item => this._openSubmenu(item),\n closeFrom: level => {\n this._closeSubmenusFrom(level)\n this._hoverBinder.bind()\n }\n }\n private readonly _hoverBinder = createMenuHoverBinder(this._hoverDelegate, 'web-ui-dropdown-item')\n private readonly _keyboardDelegate: MenuKeyboardDelegate = {\n getFocusedItem: event =>\n getFocusedMenuItemFromPanels(\n event,\n [this._menu?.content, ...this._activeSubmenus.map(submenu => submenu.content)],\n 'web-ui-dropdown-item'\n ),\n getLevelOf: item => {\n const level = this._getItemLevel(item)\n return level === -1 ? undefined : level\n },\n getEnabledItems: level => getEnabledMenuLevelItems(this._getLevelContainer(level)?.content),\n isSubmenuItem: item => item.hasAttribute('submenu'),\n openSubmenuInstant: item => this._openSubmenu(item, true),\n closeToParent: level => {\n const parent = this._activeSubmenuItems[level - 1]\n this._closeSubmenusFrom(level - 1)\n this._hoverBinder.bind()\n return parent\n },\n closeDeepestOrAll: () => this._closeLastSubmenuOrMenu()\n }\n /*\n * 开启态浮层(issue #120 Block 1)。根面板是登记对象:子菜单经 adopt 成为它的逻辑\n * 后代;关闭动作仍走 closeDeepestOrAll(最深子菜单优先)。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._keyboardDelegate.closeDeepestOrAll(),\n isConnected: () => this.isConnected\n })\n private readonly _menuItemAnchors = new Map<HTMLElement, Comment>()\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _userOpenChange = new UserChangeController()\n private _shouldOpenInstantly = true\n private _suppressInitialFocusVisible = true\n private _refreshScheduled = false\n // 主菜单 dialog 路径与子菜单一样使用代数令牌;快速 openAt/refresh 或 close 后,\n // 迟到的 positioning promise 只能被丢弃,不能覆盖最新坐标。\n private readonly _menuPositionGeneration = defineOverlayPositioningGeneration().make()\n // 菜单打开期间宿主可能不经重定位直接改写子内容(网络推送、定时器等),\n // 新节点缺隐藏 slot 会可见叠加到菜单上;观察 portal 内容并在下一帧重新同步。\n private readonly _contentObserver = new MutationObserver(() => {\n if (this._isOpen) this._scheduleRefresh()\n })\n\n get isOpen(): boolean {\n return this._isOpen\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._hideMenuItems()\n this.addEventListener('contextmenu', this._onContextMenu)\n this.addEventListener('keydown', this._onKeydown)\n this.addEventListener('click', this._onMenuClick)\n this.addEventListener('pointerdown', this._onPointerDown)\n this.addEventListener('pointermove', this._onPointerMove)\n this.addEventListener('pointerup', this._onPointerUp)\n this.addEventListener('pointercancel', this._onPointerCancel)\n document.addEventListener('contextmenu', this._onContextMenuOutside)\n document.addEventListener('wheel', this._onWheel, { capture: true, passive: false })\n document.addEventListener('touchmove', this._onTouchMove, { capture: true, passive: false })\n document.addEventListener('keydown', this._onDocumentKeydown)\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this.removeEventListener('contextmenu', this._onContextMenu)\n this.removeEventListener('keydown', this._onKeydown)\n this.removeEventListener('click', this._onMenuClick)\n this.removeEventListener('pointerdown', this._onPointerDown)\n this.removeEventListener('pointermove', this._onPointerMove)\n this.removeEventListener('pointerup', this._onPointerUp)\n this.removeEventListener('pointercancel', this._onPointerCancel)\n document.removeEventListener('contextmenu', this._onContextMenuOutside)\n document.removeEventListener('wheel', this._onWheel, true)\n document.removeEventListener('touchmove', this._onTouchMove, true)\n document.removeEventListener('keydown', this._onDocumentKeydown)\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._outsideClickGuard.dispose()\n this._hoverBinder.dispose()\n this._scrollLock.release()\n this._menuHandle?.release()\n this._menuHandle = null\n this._returnItemsToSlot()\n this._menu?.panel.remove()\n this._menu = undefined\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n /*\n * scrim 挂在宿主以外的容器里(theme overlay root,无主题时 document.body),不随宿主一起\n * 脱离文档。不在这里收掉就会留下一张仍然 `showModal()` 的全屏 dialog:top layer 不受宿主\n * 移除影响,整个页面就此锁死。\n */\n this._closeScrim()\n // 脱离文档即视为关闭:否则重连后 _isOpen 仍为 true 而 _menu 已清空,\n // 下次 openAt 会走已打开分支静默失败,菜单无法再打开。\n this._isOpen = false\n this._cancelLongPress()\n this._longPressOpenedAt = null\n }\n\n protected override updated(changed: Map<string, unknown>) {\n // Lit 的已排队更新可在卸载后执行,不能让已失效实例重新获取全局滚动锁。\n if (!this.isConnected) {\n this._syncScrollLock(false)\n return\n }\n\n if (changed.has('_isOpen')) {\n if (this._isOpen) {\n this._syncScrollLock()\n if (!this._menu) {\n /*\n * 顺序:先建 scrim 并 `showModal()`,再把面板挂进去。\n *\n * `showModal()` 会把此前聚焦的元素记为还原目标;此刻焦点仍在 opener 上,\n * 正是归还目标。面板随后 `_focusFirstItem()` 把焦点带进菜单,关闭时 UA 还回去。\n *\n * 面板挂进 scrim 是承重的,不是「顺手放一起」:top layer 只对**自身子树内的\n * 元素**做命中拦截,面板若留在 overlay root(scrim 之外),模态期间它自己就不可点。\n */\n const scrim = this._openScrim()\n this._menu = createMenuPortalOverlay('context-menu', this, scrim)\n this._menu.panel.setAttribute('role', 'menu')\n this._menu.panel.setAttribute('aria-label', '上下文菜单')\n this._menu.panel.addEventListener('click', this._onMenuClick)\n }\n /*\n * 登记与面板建立解耦。退场动画被打断时(`_closeMenuAfterPresence` 在\n * `hideOverlayPresence` 返回 false 后提前退出)`_menu` 还在,而关闭分支已经把句柄\n * 撤了;此时重开必须补 claim。否则菜单开着且可见却没有登记:Escape 关不掉它\n * (仲裁看不到它),而 `_menuHandle === null` 会让每次 document click——包括面板\n * 内部与子菜单父项——都被 `_outsideClickGuard` 判成外部点击而关闭菜单。\n */\n if (!this._menuHandle) {\n this._menuHandle = this._overlay.claim(this._menu.panel)\n // 重新 claim = 新会话:根层的子层随之作废,仍在场的子菜单面板要重新 adopt 回\n // 新句柄的子树,否则根句柄的 contains 看不到它们。\n for (const submenu of this._activeSubmenus) {\n this._submenuHandles.delete(submenu.panel)\n this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n }\n // 收尾栈里的子菜单面板同样「可见但已脱离快照」:`_closeSubmenusFrom` 只把它们\n // 移出 `_activeSubmenus`,面板仍在 DOM 里退场。**这个循环承重**:不补 adopt,\n // 它们就会落在句柄的登记树之外,被判成「面板外」,点它内部会关掉整张菜单。\n // 用例 `退场窗口内重开后,收尾中的子菜单面板在取回前后都不可被当成面板外` 守着\n // 这一条 —— 把循环体整段删掉,那条会红(实测 1 failed | 95 passed)。\n // 写进 `_submenuHandles` 还因为收尾结束时 `_releaseSubmenu` 按 panel 取句柄释放。\n for (const container of this._closingSubmenus.closing()) {\n this._submenuHandles.set(container.panel, this._menuHandle.adopt(container.panel))\n }\n }\n // 父项始终留在 menu.content 内,观察它即可覆盖各级子菜单在打开期间的内容重建。\n this._contentObserver.observe(this._menu.content, { childList: true, subtree: true })\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n showOverlayPresence(this._menu.panel, { isInstant: this._shouldOpenInstantly })\n this._shouldOpenInstantly = true\n /*\n * 首项聚焦必须再等一帧,不能和 `_refreshMenu()` 同帧。\n *\n * 两个原因都指向「太早」:\n *\n * 1. `showModal()` 的 dialog focusing steps 由 UA 排队执行,会在我们已经聚焦首项\n * **之后**再把焦点挪到 dialog 上 —— 模态化之前没有这一步,也就没有这场竞争。\n * 2. `_setupMenuItems()` 刚创建的 `web-ui-dropdown-item` 尚未完成 Lit 的 shadow\n * 渲染,`focusItem()` 此刻还找不到 `.item-inner`,静默无操作。\n *\n * 实测:同帧聚焦后 `document.activeElement` 是 `<dialog>` 而非首项;再等一帧聚焦,\n * 焦点稳定落在菜单项内。\n */\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem()\n })\n })\n } else {\n this._syncScrollLock(false)\n // 吸收窗口只覆盖「长按之后浏览器补发的那几个事件」。菜单已关,\n // 之后任何一次真实右键或点击都必须走正常路径,不能被这个窗口吞掉。\n this._longPressOpenedAt = null\n // release ⟺ 关闭:登记与仲裁归属同时撤销,退场动画只是视觉收尾。\n this._menuHandle?.release()\n this._menuHandle = null\n this._contentObserver.disconnect()\n this._menuPositionGeneration.invalidate()\n this._refreshScheduled = false\n void this._closeMenuAfterPresence()\n }\n if (this._userOpenChange.consume()) this._dispatchChange(this._isOpen)\n }\n if (changed.has('noScrollLock')) this._syncScrollLock()\n }\n\n /**\n * 在指定视口坐标打开菜单。\n * @param x 水平坐标(px)。\n * @param y 垂直坐标(px)。\n * @returns 无返回值;命令式打开不派发 `open-change` 事件。\n */\n openAt(x: number, y: number) {\n this._openAt(x, y, true, true)\n }\n\n private _openAt(\n x: number,\n y: number,\n isInstant: boolean,\n suppressFocusVisible: boolean,\n centerOnPressPoint = false\n ): boolean {\n if (this.disabled) return false\n this._x = x\n this._y = y\n this._shouldOpenInstantly = isInstant\n this._suppressInitialFocusVisible = suppressFocusVisible\n this._centerOnPressPoint = centerOnPressPoint\n this._outsideClickGuard.arm()\n if (this._isOpen) {\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n // 先失效尚未完成的旧定位,再调度下一帧新代;避免新请求前的旧 promise 胜出。\n this._menuPositionGeneration.invalidate()\n this._scheduleRefresh()\n return false\n }\n this._isOpen = true\n return true\n }\n\n /**\n * 关闭菜单。\n * @returns 无返回值;命令式关闭不派发 `open-change` 事件。\n */\n close() {\n if (!this._isOpen) return\n this._isOpen = false\n this._menuPositionGeneration.invalidate()\n /*\n * 菜单项激活触发的关闭要**现在**就让 scrim 出顶层,不能等退场结束。\n *\n * 焦点归还由 UA 负责(`close()` 把焦点还给 `showModal()` 之前的 activeElement),\n * 而菜单项的回调常紧接着把焦点送到别处 —— 典型是右键重命名:回调把行换成编辑态、\n * 下一帧 `select()` 进 textarea。等到退场结束才关,UA 的归还会把焦点从 textarea\n * 拽回那一行,blur 掉编辑器,而 editable-text 的 blur 契约会**提交未改动的标题**。\n * 那是 interweave 上真实存在的 bug,不能放回来。\n *\n * 此刻焦点还在菜单里,UA 的归还正好落在 opener 上——那本来就是用户期望的落点,\n * 应用随后的焦点转移不会被覆盖。元素本身要留到面板真正移除(见 `_releaseScrimModality`)。\n */\n if (this._closeSource === 'menu-item') this._releaseScrimModality()\n this._closeSource = 'default'\n }\n\n private readonly _closeFromUser = (source: CloseSource = 'default') => {\n if (!this._isOpen) return\n this._userOpenChange.mark()\n this._closeSource = source\n this.close()\n }\n\n // 同一帧内多次内容变化只刷新一次,避免观察者与刷新自身 append 形成循环。\n private _scheduleRefresh() {\n if (this._refreshScheduled || !this._isOpen) return\n this._refreshScheduled = true\n requestAnimationFrame(() => {\n this._refreshScheduled = false\n if (!this._isOpen || !this._menu) return\n this._refreshMenu()\n })\n }\n\n // 重新同步 portal 内容并重定位;fresh-open、重定位与观察者触发的刷新共用。\n private _refreshMenu() {\n if (!this._menu) return\n this._setupMenuItems()\n this._positionMenu()\n this._hoverBinder.bind()\n }\n\n /**\n * 定位:**触屏长按与鼠标右键共用这一条 Floating UI 路径**。\n *\n * 两条入口现在只差「触发点的 x/y 从哪来」,布局契约完全一致:\n * `bottom-start` 表达「面板上缘 == 触发点 y、向下展开」,水平以触发点 x 为基准。\n * 之前有一条手写的贴底分支(先按点、再按视口下缘算 `top`),那是把「贴视口底」硬编码\n * 成算术;真机反馈证明它错(菜单跑到屏幕最底部,与手指按下的位置完全脱节)。现在\n * 落点交给 `flip` / `shift` 按**实际空间**决定,`placement` 只表达意图。\n *\n * 不再按「是否触屏」分流:窄视口的鼠标右键同样期望菜单贴着光标出现,按输入类型或视口\n * 宽度分流会让同一块屏幕上的两种输入拿到两套布局。分流所需的 `_anchorAtPressPoint`\n * 连同 `_positionMenuInViewport` / `_positionMenuAtPressPoint` 两个方法一起删掉 ——\n * 面板恒为 open scrim `<dialog>` 的直接子节点(`createMenuPortalOverlay` 第三参传入\n * scrim,`menu-portal.ts` 里 `container = scrim ?? …`),所以旧代码那条\n * 「非 dialog 容器」的分支对 context-menu 本来就不可达。\n *\n * 算出来的 left/top 是**视口坐标**。面板是 `position: fixed`,而 scrim 刻意不带\n * transform / filter / contain(见 style.css),因此不构成 fixed 的包含块 ——\n * 坐标直接相对视口解析。这条等价关系由 style.css 的满屏声明保证,并由 R6 的\n * 「scrim 无 transform/filter/contain」+ viewport-fit 的几何断言兜底。\n */\n private _positionMenu() {\n const panel = this._menu?.panel\n if (!panel) return\n\n /*\n * 先读安全区,再碰面板:`getComputedStyle` 会强制一次同步布局,让它发生在面板进入\n * 「可测」状态之前,而不是夹在「面板刚可测」与「Floating UI 量它」之间。\n *\n * 需要说清楚的是,**这不是** viewport-fit 那条横向断言里 2px 抖动的成因。把这一行移到\n * 最前面实测并没有让那个偏差消失(改前改后都是 204 对期望 206)。那个 2px 来自 flip 的\n * reset 会多跑一轮测量:那一刻面板宽 202px、落定后 200px。相关用例已经不再钉与视口\n * 边界的精确距离。\n */\n const safeAreaBottom = this._safeAreaBottom()\n\n panel.style.visibility = 'hidden'\n panel.style.display = ''\n\n const generation = this._menuPositionGeneration.next()\n /*\n * 可用矩形 = 视口 − 安全区 − 最小间距。flip 与 shift 必须吃**同一份** padding:\n *\n * - flip 用它判断「这一侧放不放得下」,决定翻不翻;\n * - shift 用它做实际钳制,且 `crossAxis: true` 会**同时**钳主轴与交叉轴\n * (core 的 shift 默认 mainAxis=true / crossAxis=false,开了 crossAxis 才两边都管)。\n *\n * 安全区必须进 shift 而不只是 flip:flip 只挑「哪一侧」,挑完就把面板贴在触发点上,\n * 不再管这一侧内部的位置。触发点本身落在安全区里时(按在屏幕最底部),挑出来的\n * 「上方」那一侧仍然把面板下缘按在安全区里 —— 只有 shift 的主轴钳制会把它推上来。\n * 这条正是 R7 用例在钉的行为。\n */\n const boundaryPadding = {\n top: VIEWPORT_PADDING,\n bottom: VIEWPORT_PADDING + safeAreaBottom,\n left: VIEWPORT_PADDING,\n right: VIEWPORT_PADDING\n }\n /*\n * 限制 flip 的候选集为「下方 / 上方」两个,**刻意不含 `-start` / `-end` 的另一侧**。\n *\n * 为什么必须限制:`flip` 对对齐过的 placement(`bottom-start`)默认展开成\n * `getExpandedPlacements()`,候选里带着 `bottom-end` / `top-end`。于是鼠标贴视口右缘、\n * `bottom-start` 的对齐侧溢出时,flip 会认定「`bottom-end` 才放得下」并把面板整个挪到\n * 光标左侧。落点仍在视口内,但那是一次没人要求的契约变更 —— 水平夹取本来就归 shift,\n * 而且对齐量一变,transform-origin 的推导前提(对齐量不变 ⇒ 水平 origin 只由 shift 位移\n * 决定)就不成立了。\n *\n * 用的是 `fallbackPlacements` 而不是 `allowedPlacements`:后者是 `autoPlacement` 的选项,\n * `flip` 根本不读,传进去只会被静默忽略(TS 会报错,运行时会当成 padding 之外的字段丢掉)。\n * `fallbackPlacements` 正是 flip 用来**替换**上面那个默认展开列表的字段,指定后\n * `placements = [initialPlacement, ...fallbackPlacements]`,连 bestFit 兜底也只在这份\n * 候选里挑,`-end` 因此永远不可能出现。\n *\n * 用例「鼠标贴视口右缘时对齐量不被 flip 改成 -end」钉的就是这一条。\n */\n const placements = this._centerOnPressPoint\n ? (['bottom', 'top'] as const)\n : (['bottom-start', 'top-start'] as const)\n\n void computePosition({ getBoundingClientRect: () => new DOMRect(this._x, this._y, 0, 0) }, panel, {\n strategy: 'fixed',\n /*\n * `bottom`(触屏):面板上缘对齐触发点 y、水平居中于触发点 x。\n * `bottom-start`(鼠标):同样上缘对齐 y,但左缘对齐光标 x。理由见\n * `_centerOnPressPoint` —— 居中会把光标落进面板上缘的命中区。\n *\n * 居中在贴边时**必然**被 shift 的交叉轴钳制打破(按点 x 太靠边,居中的面板会溢出视口)。\n * 这不是缺陷而是取舍:完整可见优先于几何居中。shift 推完面板仍在视口内,\n * 只是不再居中 —— 用例「按点贴视口右缘时仍完整可见」钉的就是这一条。\n */\n placement: placements[0],\n middleware: [\n /*\n * flip 负责「向下放不下就翻到按点上方」—— 面板下缘对齐按点、向上长。\n * 两侧都放不下(按点极低且面板极高)时它按 bestFit 选空间多的一侧并夹住主轴,\n * 所以不需要自己再写一遍兜底夹取。\n */\n flip({ fallbackPlacements: [...placements], padding: boundaryPadding }),\n shift({ padding: boundaryPadding, crossAxis: true })\n ]\n }).then(({ x, y, placement, middlewareData }) => {\n if (!this._isOpen || this._menu?.panel !== panel || !this._menuPositionGeneration.isCurrent(generation)) return\n const shiftX = middlewareData.shift?.x ?? 0\n /*\n * 垂直 origin 取 flip 后的实际 placement,不看 shift 位移:placement 直接说明面板\n * 落在触发点的哪一侧,而「离触发点最近的那条边」就是 scale 应该长出来的地方。\n *\n * 水平:默认跟 placement 的对齐量;被 shift 推动时改用推动方向那一侧,菜单才是朝\n * 光标「长过来」而不是从视口边缘滑进去。居中(placement 不带 `-` 对齐后缀)且未被\n * 推动时保持 `center`。\n */\n const horizontalOrigin = !placement.includes('-')\n ? shiftX === 0\n ? 'center'\n : shiftX < 0\n ? 'right'\n : 'left'\n : shiftX < 0\n ? 'right'\n : 'left'\n this._applyMenuPosition(panel, x, y, horizontalOrigin, placement.startsWith('bottom') ? 'top' : 'bottom')\n })\n }\n\n /**\n * 视口下缘的安全区高度(px)。\n *\n * 改按压点展开之后它**仍然必需**,而且不再只服务触屏:菜单默认从触发点向下长,触发点\n * 落在屏幕下半部时面板下缘会进入 home indicator 区域。它现在折进 flip 与 shift 共用的\n * 边界 padding(见 `_positionMenu`),不再需要单独一条 `maxBottom` 算术。\n *\n * 读宿主的自定义属性而不是 `env()`:自定义属性读回的是**计算后**的解析值,\n * 而 `env()` 在 JS 里没有对应的求值入口,只能间接经由声明它的属性取。\n * 属性未解析时 `parseFloat` 得 NaN,落到 0 —— 与 `env(..., 0px)` 的缺省一致。\n */\n private _safeAreaBottom(): number {\n const value = Number.parseFloat(getComputedStyle(this).getPropertyValue('--wui-context-menu-safe-area-bottom'))\n return Number.isFinite(value) ? value : 0\n }\n\n /**\n * 写下面板坐标与进场 transform 的原点。\n *\n * 两个 origin 必传且**不**再回退成坐标比较:flip 会改变垂直方向,回退表达式\n * `y < this._y ? 'bottom' : 'top'` 在「面板顶边与触发点重合」时两边都成立,等于随机取。\n * 唯一调用方从 flip 后的 placement 与 shift 位移推出真实侧边,见 `_positionMenu`。\n */\n private _applyMenuPosition(\n panel: HTMLElement,\n x: number,\n y: number,\n horizontalOrigin: 'left' | 'right' | 'center',\n verticalOrigin: 'top' | 'bottom'\n ) {\n panel.style.left = `${x}px`\n panel.style.top = `${y}px`\n panel.style.setProperty('--wui-internal-overlay-transform-origin', `${verticalOrigin} ${horizontalOrigin}`)\n panel.style.visibility = ''\n }\n\n /**\n * 把焦点带进菜单的第一项。\n *\n * `attempt` 是重试计数:菜单项是刚被 `reconcileManagedMenuItems` 搬进来的自定义元素,\n * 它们的 shadow(`.item-inner`)由各自的 Lit 更新**异步**渲染。`focusMenuItem()` 委托给\n * 项的 `focusItem()`,那一刻 `.item-inner` 还不存在,于是**静默无操作** —— 不抛错、\n * 也不留痕,只表现为「菜单开了但焦点没进去」。\n *\n * 所以这里不假设一次成功:调用后用 `findFocusedMenuItem` 复核,没落上就在下一帧再试,\n * 最多 `FOCUS_FIRST_ITEM_ATTEMPTS` 次。上界是必要的 —— 菜单项若始终渲染不出来\n * (空菜单、内容被外部换掉),不能让重试链一直跑下去。\n */\n private _focusFirstItem(attempt = 0) {\n const items = this._menu?.content.querySelectorAll<HTMLElement>('web-ui-dropdown-item:not([disabled])')\n const firstItem = items?.[0]\n if (!firstItem) return\n focusMenuItem(firstItem, { suppressFocusVisible: this._suppressInitialFocusVisible })\n if (attempt + 1 >= FOCUS_FIRST_ITEM_ATTEMPTS) return\n if (findFocusedMenuItem([this._menu?.panel])) return\n requestAnimationFrame(() => {\n if (!this._isOpen || !this._menu) return\n this._focusFirstItem(attempt + 1)\n })\n }\n\n private _setupMenuItems() {\n const menu = this._menu\n if (!menu) return\n const content = menu.content\n\n reconcileManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n this._hideMenuItems()\n // 宿主可能在菜单打开期间动态改写 slot 子内容(如切换上下文后重建嵌套子项),\n // 新节点没有隐藏 slot 会落入父项默认 slot 可见渲染并叠到一级菜单上;\n // 因此对已移入 content 的嵌套子项也要重新隐藏。\n hideNestedMenuChildren(content, 'context-menu-hidden')\n\n // 框架 v-if 注释锚点的复位独立于元素重排:无论元素序是否已变都要执行。\n // 若挂在「元素序已变才重排」之下,顺序恰好正确时会跳过复位,锚点漂移无法收敛,\n // 而 Vue 下次翻转正依赖锚点引导新分支项的插入点。\n const anchors = captureFrameworkAnchors(content, MARKER_TEXT)\n orderManagedMenuItems(this, content, this._menuItemAnchors, MARKER_TEXT)\n restoreFrameworkAnchors(content, anchors)\n }\n\n private _returnItemsToSlot() {\n const menu = this._menu\n if (!menu) return\n\n this._closeSubmenusFrom(0, true)\n this._closingSubmenus.restoreAll()\n\n returnManagedMenuItemsToSlot(this, menu.content, this._menuItemAnchors)\n }\n\n /**\n * 建 scrim 并让它进入模态态,返回容器供面板挂载。\n *\n * 每次开启都新建而不是复用:scrim 的生命周期必须与「这一次开启」严格同进同出。\n * 复用一个 dialog 意味着上一轮的 `close()` 事件、`open` 属性与 UA 记下的焦点还原目标\n * 会跨会话残留,而这些都不是本组件能重置的。\n */\n private _openScrim(): HTMLDialogElement {\n const scrim = document.createElement('dialog')\n scrim.className = 'wui-context-menu-scrim'\n /*\n * scrim 的样式必须**显式挂到 scrim 元素上**。\n *\n * `static styles`(style.css)只进宿主的 shadow root,scrim 又是本组件自己造的节点、不进\n * 宿主 shadow root,拿不到那份样式;`scrim.css` 若只躺在文件里而不注入,就是死代码。\n * 实测未注入时 scrim 完全是 UA 默认 `dialog:modal` 的样子:`margin: auto` +\n * `width/height: fit-content` 让它在 414x896 视口里缩成 **38x38 的白盒子**正中,\n * `background` 还是不透明白 —— 菜单一开就在屏幕中间糊一块白色方块。\n *\n * 用子 `<style>` 而不是行内 `cssText`:与 `menu-portal.ts` 对 dialog 容器注入共享样式\n * 是同一手法(那边已在 dialog 容器上验证过),随元素一起移除,不留全局残留。\n */\n const scrimStyles = document.createElement('style')\n scrimStyles.textContent = scrimStyle\n scrim.append(scrimStyles)\n scrim.setAttribute(SCRIM_ATTR, '')\n scrim.addEventListener('click', this._onScrimClick)\n scrim.addEventListener('cancel', this._onScrimCancel)\n // showModal() 要求元素已在文档中。\n this._scrimParent().append(scrim)\n scrim.showModal()\n this._scrim = scrim\n return scrim\n }\n\n /**\n * scrim 的挂载点:**与面板在没有 scrim 时会落到的容器同源** —— 最近主题的 theme-owned\n * overlay root。scrim 就是那个容器的替身(面板必须挂进 scrim,见 `updated()` 的顺序注释),\n * 所以它得站在容器该站的位置上。\n *\n * 为什么不能在整棵 DOM 里随便挑位置:`--wui-color-*` 由 `<web-ui-theme>` 写在自己的 `:host`\n * 上,只沿 DOM 树向下继承。scrim 若挂到 `document.body`,而 `<web-ui-theme>` 是 body 的**子节点**,\n * 这条继承链就断了 —— 面板与 scrim 双双落回 `var(--wui-color-*, <浅色字面量>)` 的 fallback,\n * 深色外观下菜单是一块浅色玻璃(app 侧的 `dark:` 变体也够不到它)。挂进 theme overlay root\n * 之后,面板与 scrim 跟 dropdown / select 一样吃同一份 token。\n *\n * 无主题时保持历史行为挂在 `document.body`:那条路径本来就没有 token 作用域可继承,改用\n * fallback overlay root 只会改变既有测试与消费者的观察面,不换来任何东西。\n */\n private _scrimParent(): HTMLElement {\n return findNearestTheme(this)?.getOverlayRoot() ?? document.body\n }\n\n /**\n * 关掉并摘除 scrim。幂等:关闭分支与 `disconnectedCallback` 都会调。\n *\n * `close()` 触发 UA 的焦点归还,**必须先于面板 `remove()`** —— 理由见 `_closeMenuAfterPresence`。\n */\n private _closeScrim() {\n const scrim = this._scrim\n if (!scrim) return\n this._scrim = undefined\n if (scrim.open) scrim.close()\n scrim.remove()\n }\n\n /**\n * 只让 scrim 出顶层,不摘除元素。面板挂在 scrim 里,摘了就把退场动画一起摘掉。\n *\n * 提前出顶层是为了焦点(理由见 `close()`)。代价是这个尾巴上 scrim 已经在 top layer\n * 之外:下层恢复可命中,且面板的层级退回普通流 —— 在「菜单开在 drawer/dialog 之上」\n * 的场景里,退场动画会被下层的 top layer 盖住。\n *\n * 这条尾巴只有一次退场过渡的时长(默认 160ms),且只发生在菜单项激活时。\n * 摘除仍在 `_closeScrim()`,那时面板已经走完。\n */\n private _releaseScrimModality() {\n const scrim = this._scrim\n if (scrim?.open) scrim.close()\n }\n\n /**\n * 点 scrim = 关菜单(Q12:必关,不给 prop 选项),与 dialog / drawer 的实现同形:\n * 只有落点就是 scrim 本身才算「点外面」,落在面板(scrim 的后代)上不算。\n *\n * 落点判据之所以只能是这两个,是模态给的:菜单打开期间下层收不到命中,事件只可能\n * 落在 scrim 或面板上,两者都由本组件判定。\n *\n * 更早的实现在 document 捕获阶段挂 click 监听,把「点外面」理解成「命中测试落在菜单\n * 面板之外」—— 宿主 light DOM 的每一行都满足这个条件,于是判定与行激活成了同一个\n * click 的两个后果,中间没有任何仲裁,下层目标先被激活。那条监听器连同它的实现已一并\n * 删除:模态化之后它没有任何还能生效的场景,别再挂回来。\n */\n private _onScrimClick = (e: MouseEvent) => {\n if (e.target !== this._scrim) return\n // 长按抬手后浏览器补发的 click 属于那一次触摸意图,不能把刚开的菜单关掉。\n if (this._isLongPressFollowUp()) return\n this._closeFromUser()\n }\n\n /**\n * Escape 的**唯一**关闭路径仍是共享仲裁者(`_overlay.requestClose` → `closeDeepestOrAll`),\n * 它按层深决定是关最深子菜单还是关整张菜单。原生 dialog 自己也监听 Escape 并派发\n * `cancel`,若放任默认行为,UA 会直接关掉 scrim —— 那条路径不经过组件状态机,\n * 既可能漏派 `open-change`,也会把「关最深一层」的语义降级成「整张菜单没了」。\n *\n * 因此这里只吞掉默认行为,不重复关闭。仲裁者那条路径已经会走 `_closeFromUser()`。\n */\n private _onScrimCancel = (e: Event) => {\n e.preventDefault()\n }\n\n private async _closeMenuAfterPresence() {\n const menu = this._menu\n if (menu && !(await hideOverlayPresence(menu.panel))) return\n if (this._isOpen || !this.isConnected || this._menu !== menu) return\n\n /*\n * 归还判定必须在移除面板之前做完:面板一脱离文档,「焦点仍在菜单内」就再也读不到了。\n * 子菜单层此刻仍可能持有焦点,一并计入。\n */\n /*\n * 先关 scrim 再摘面板,顺序不能反。\n *\n * 焦点归还是 `showModal()` / `close()` 的 UA 行为,不再由组件自己实现:UA 记的是\n * `showModal()` 之前的 `document.activeElement`,`close()` 时还回去。反过来做就成了\n * 实测里那条「open 中直接移除 dialog → 焦点丢到 body,不归还」——面板先脱离文档,\n * 焦点链断了,UA 的还原目标也就落空。\n *\n * 菜单项激活那条路径的 scrim 早在 `close()` 里就出了顶层(`_releaseScrimModality`),\n * 这里的 `close()` 对它是幂等空转,摘除照常发生。\n *\n * 「关闭期间焦点被外部接管时不抢回」那道防护不是被删除,是被**移到了更早的位置**:\n * UA 自己没有这个分支(实测:外部接管后 close,焦点必被抢回 opener,`close()` /\n * `remove()` / `open = false` / 摘 `open` / 置 `display:none` / opener 移除或不可聚焦,\n * 十种写法全都照旧夺焦点)。所以只能在焦点**还没被送走**的时候就把顶层让掉 ——\n * 也就是菜单项激活那一刻。点外面 / Escape / 程序式关闭不抢焦点是用户期望的语义,\n * 那三条保留 dialog 默认效果。\n */\n this._closeScrim()\n\n // 登记已在关闭分支撤销(release ⟺ 关闭),这里只做内容归还与 DOM 收尾。\n this._returnItemsToSlot()\n menu?.panel.remove()\n this._menu = undefined\n }\n\n private _hideMenuItems() {\n getMenuChildren(this).forEach(child => child.setAttribute('slot', 'context-menu-hidden'))\n hideNestedMenuChildren(this, 'context-menu-hidden')\n }\n\n private _openSubmenu(item: HTMLElement, isInstant = false) {\n if (!this._isOpen || !item.hasAttribute('submenu') || item.hasAttribute('disabled')) return\n\n const level = this._getItemLevel(item)\n if (level === -1 || this._activeSubmenuItems[level] === item) return\n\n this._closeSubmenusFrom(level)\n const closingSubmenu = this._closingSubmenus.take(item)\n if (closingSubmenu) {\n this._activeSubmenus[level] = closingSubmenu\n this._activeSubmenuItems[level] = item\n /*\n * 取回的面板写回 `_submenuHandles`,让收尾结束时的 `_releaseSubmenu` 释放到正确的句柄。\n * 注:「面板在根句柄子树里」这一条已由上面的 claim 分支保证 —— 该分支会把仍在\n * closing 栈里的面板一并重挂(`_closingSubmenus.closing()`)。实测单独摘掉这一行\n * 全部用例仍绿,所以它维护的是释放账本,不是树的成员关系。\n *\n * **这一行没有任何测试守护**:把 `adopt` 与 `set` 一起摘掉,context-menu 的 96 条\n * 用例仍然全绿(实测 96 passed)。所以下面写的是「当前如此」—— 这一行维护的是\n * 释放账本(收尾结束时 `_releaseSubmenu` 按 panel 取句柄),不是树的成员关系;\n * 成员关系由上面 claim 分支的 `_closingSubmenus.closing()` 循环负责,那一条**是**\n * 有测试的。改动这一行前请自己判断要不要补一条能红的用例,别把这里的沉默当成覆盖。\n */\n if (this._menuHandle) this._submenuHandles.set(closingSubmenu.panel, this._menuHandle.adopt(closingSubmenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, closingSubmenu)\n showOverlayPresence(closingSubmenu.panel, { isInstant })\n this._hoverBinder.bind()\n return\n }\n if (getMenuChildren(item).length === 0) return\n\n /*\n * 子菜单与根菜单挂进**同一个** scrim。只改根菜单会让两层行为分裂:根层被 scrim\n * 罩住时,子菜单若仍留在 overlay root,它自己就成了 scrim 子树外的元素 —— 模态期间\n * 不可点,且点它内部会被判成「点外面」而关掉整张菜单。\n */\n const submenu = createMenuPortalOverlay('context-submenu', this, this._scrim)\n submenu.panel.dataset.level = String(level)\n submenu.panel.setAttribute('role', 'menu')\n submenu.panel.setAttribute('aria-label', '子菜单')\n submenu.panel.style.visibility = 'hidden'\n submenu.panel.addEventListener('click', this._onMenuClick)\n moveMenuChildren(item, submenu.content)\n\n this._activeSubmenus[level] = submenu\n this._activeSubmenuItems[level] = item\n /*\n * 子层进根句柄的子树,默认不是独立候选 —— 「它在树里、但不参与这一层裁决」由此\n * 表达。原先按 level===0 选择根面板还是上一层子面板的父级判断随之消失:contains\n * 沿子树递归,拍平与嵌套对任何现有查询结果等价。\n */\n if (this._menuHandle) this._submenuHandles.set(submenu.panel, this._menuHandle.adopt(submenu.panel))\n item.setAttribute('active', '')\n this._positionSubmenu(item, submenu)\n showOverlayPresence(submenu.panel, { isInstant })\n this._hoverBinder.bind()\n }\n\n private _closeSubmenusFrom(level: number, isInstant = false) {\n for (let index = this._activeSubmenus.length - 1; index >= level; index--) {\n const submenu = this._activeSubmenus[index]\n const item = this._activeSubmenuItems[index]\n item?.removeAttribute('active')\n if (!item || isInstant) {\n if (submenu) this._releaseSubmenu(submenu.panel)\n this._restoreSubmenuItems(submenu, item)\n submenu.panel.remove()\n } else {\n this._closingSubmenus.closeAsync(item, submenu)\n }\n }\n this._activeSubmenus.length = level\n this._activeSubmenuItems.length = level\n }\n\n private _restoreSubmenuItems(submenu: MenuPortalOverlay, item?: HTMLElement) {\n if (!item) return\n moveMenuChildren(submenu.content, item)\n // 子菜单打开期间宿主可能重建了嵌套子项,归还时补隐藏,避免可见叠加。\n hideNestedMenuChildren(item, 'context-menu-hidden')\n }\n\n private _getItemLevel(item: HTMLElement): number {\n const menu = this._menu\n if (menu?.panel.contains(item)) return 0\n const parentLevel = this._activeSubmenus.findIndex(submenu => submenu.panel.contains(item))\n return parentLevel === -1 ? -1 : parentLevel + 1\n }\n\n private _positionSubmenu(item: HTMLElement, submenu: MenuPortalOverlay) {\n // 与主菜单同因:panel 进入 open native dialog 后处于 transformed containing\n // block,viewport 坐标的 left/top 会相对 dialog padding box 解析而整体偏移,\n // 需改走 Floating UI 换算为 dialog 相对坐标;开合方向仍按视口坐标度量预判。\n if (submenu.panel.parentElement instanceof HTMLDialogElement && submenu.panel.parentElement.open) {\n const itemRect = item.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenu.panel.getBoundingClientRect().width + padding <= window.innerWidth\n // 同帧关闭→重开会复用同一 panel 产生两个 in-flight 定位 promise,完成序不保证\n // 后者胜出;按 panel 键控的代数 token 只允许最新一次调用写入,迟到旧 promise 丢弃。\n const epoch = (this._submenuPositionEpochs.get(submenu.panel) ?? 0) + 1\n this._submenuPositionEpochs.set(submenu.panel, epoch)\n void computePosition(item, submenu.panel, {\n strategy: 'fixed',\n placement: canOpenRight ? 'right-start' : 'left-start',\n middleware: [shift({ padding, crossAxis: true })]\n }).then(({ x, y, middlewareData }) => {\n if (!this._activeSubmenus.includes(submenu) || epoch !== this._submenuPositionEpochs.get(submenu.panel)) return\n // 与主菜单 dialog 路径一致:origin 由 shift 增量推导,视口预判只决定 placement。\n const shiftX = middlewareData.shift?.x ?? 0\n const shiftY = middlewareData.shift?.y ?? 0\n const horizontalOrigin = shiftX < 0 ? 'right' : 'left'\n const verticalOrigin = shiftY < 0 ? 'bottom' : 'top'\n submenu.panel.style.left = `${x}px`\n submenu.panel.style.top = `${y}px`\n submenu.panel.style.setProperty(\n '--wui-internal-overlay-transform-origin',\n `${verticalOrigin} ${horizontalOrigin}`\n )\n submenu.panel.style.visibility = ''\n })\n return\n }\n\n const itemRect = item.getBoundingClientRect()\n const submenuRect = submenu.panel.getBoundingClientRect()\n const padding = 8\n const canOpenRight = itemRect.right + submenuRect.width + padding <= window.innerWidth\n const left = canOpenRight ? itemRect.right : Math.max(padding, itemRect.left - submenuRect.width)\n const top = Math.min(Math.max(padding, itemRect.top), window.innerHeight - submenuRect.height - padding)\n\n submenu.panel.style.left = `${left}px`\n submenu.panel.style.top = `${top}px`\n submenu.panel.style.setProperty('--wui-internal-overlay-transform-origin', canOpenRight ? 'top left' : 'top right')\n submenu.panel.style.visibility = ''\n }\n\n private _dispatchChange(open: boolean) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _onPointerDown = (e: PointerEvent) => {\n // 任何一次新的按下都结束「上一次长按的补发事件」窗口:抬手后浏览器合成的\n // click 一定早于下一次 pointerdown,所以这个边界既能吸收补发,又不会误吞\n // 用户之后真正的点击。\n this._longPressOpenedAt = null\n // 鼠标走原生 contextmenu,无需长按;只认触屏指针。\n if (!this.longPress || this.disabled || e.pointerType !== 'touch') return\n this._cancelLongPress()\n this._longPressOrigin = { x: e.clientX, y: e.clientY }\n this._longPressTimer = setTimeout(\n () => {\n this._longPressTimer = undefined\n const origin = this._longPressOrigin\n if (!origin) return\n this._longPressOrigin = undefined\n if (this._isOpen) return\n this._longPressOpenedAt = performance.now()\n // 触屏长按:面板上缘对齐按点、向下展开,水平居中于按点(判据见 `_centerOnPressPoint`)。\n if (this._openAt(origin.x, origin.y, false, true, true)) this._userOpenChange.mark()\n },\n // 属性已被 normalizeNumber 钳到 [0, 5000],这里直接用;不再二次 Math.max。\n this.longPressDelay\n )\n }\n\n private _onPointerMove = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') return\n const origin = this._longPressOrigin\n if (!origin) return\n // 触屏有隐式指针捕获,pointermove 会持续派发到 pointerdown 的落点,\n // 因此位移超阈值即判定为滚动意图,取消长按。\n const distance = Math.hypot(e.clientX - origin.x, e.clientY - origin.y)\n if (distance > LONG_PRESS_MOVE_TOLERANCE) this._cancelLongPress()\n }\n\n private _onPointerUp = (e: PointerEvent) => {\n if (e.pointerType === 'touch') this._cancelLongPress()\n }\n\n private _onPointerCancel = () => {\n this._cancelLongPress()\n }\n\n private _cancelLongPress() {\n if (this._longPressTimer !== undefined) {\n clearTimeout(this._longPressTimer)\n this._longPressTimer = undefined\n }\n this._longPressOrigin = undefined\n }\n\n /** 该事件是否是长按开菜单之后、浏览器为同一次触摸补发的事件。 */\n private _isLongPressFollowUp(): boolean {\n const openedAt = this._longPressOpenedAt\n if (openedAt === null) return false\n // 超期只说明「这次长按的补发没来」,不代表窗口承担时序保证;见常量注释。\n if (performance.now() - openedAt > LONG_PRESS_FOLLOW_UP_WINDOW_MS) {\n this._longPressOpenedAt = null\n return false\n }\n return true\n }\n\n private _onContextMenu = (e: MouseEvent) => {\n if (this.disabled) return\n if (this._isLongPressFollowUp()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n if (this._openAt(e.clientX, e.clientY, false, true)) this._userOpenChange.mark()\n }\n\n private _onContextMenuOutside = (e: MouseEvent) => {\n if (this._isOpen && !this._outsideClickGuard.isInside(e)) {\n this._closeFromUser()\n }\n }\n\n private _onKeydown = (e: KeyboardEvent) => {\n if (this.disabled) return\n\n if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {\n e.preventDefault()\n const focused = document.activeElement\n if (focused && focused !== document.body) {\n const rect = focused.getBoundingClientRect()\n if (this._openAt(rect.left, rect.bottom, true, false)) this._userOpenChange.mark()\n } else {\n if (this._openAt(window.innerWidth / 2, window.innerHeight / 2, true, false)) this._userOpenChange.mark()\n }\n return\n }\n }\n\n private _onMenuClick = (e: MouseEvent) => {\n const item = getMenuItemFromEvent(e)\n if (!item || item.hasAttribute('disabled')) return\n if (item.hasAttribute('submenu')) {\n this._openSubmenu(item)\n return\n }\n this._closeFromUser('menu-item')\n }\n\n private _getLevelContainer(level: number) {\n return level === 0 ? this._menu : this._activeSubmenus[level - 1]\n }\n\n private _getLevelItems(level: number): HTMLElement[] {\n const container = this._getLevelContainer(level)\n if (!container) return []\n return getMenuChildren(container.content)\n }\n\n private _onWheel = (e: WheelEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onTouchMove = (e: TouchEvent) => {\n this._preventBackgroundScroll(e)\n }\n\n private _onDocumentKeydown = (e: KeyboardEvent) => {\n if (!this._isOpen) return\n // 鼠标右键打开的菜单焦点不在菜单内,e.target !== this 时也必须能 Escape 关闭;\n // 菜单为模态浮层,按 Escape 即关闭,无需限定焦点位置\n handleMenuKeyboard(this._keyboardDelegate, e)\n }\n\n private _closeLastSubmenuOrMenu() {\n if (this._activeSubmenus.length > 0) {\n const level = this._activeSubmenus.length - 1\n const parent = this._activeSubmenuItems[level]\n this._closeSubmenusFrom(level)\n focusMenuItem(parent)\n this._hoverBinder.bind()\n } else {\n this._closeFromUser()\n }\n }\n\n private _preventBackgroundScroll(e: Event) {\n if (!this._isOpen || this.noScrollLock || this._isMenuPanelEvent(e)) return\n e.preventDefault()\n }\n\n private _syncScrollLock(isOpen = this._isOpen) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n /** 按 panel 撤销一层子菜单的登记;句柄已随根层递归撤销时幂等无操作。 */\n private _releaseSubmenu(panel: HTMLElement) {\n this._submenuHandles.get(panel)?.release()\n this._submenuHandles.delete(panel)\n }\n\n private _isMenuPanelEvent(e: Event): boolean {\n // 在监听器内部判定:composedPath() 在派发结束后会被清空。\n return this._menuHandle?.containsEvent(e) ?? false\n }\n\n override render() {\n return html`\n <div class=\"context-menu-anchor\">\n <slot @slotchange=${this._onSlotChange}></slot>\n </div>\n `\n }\n\n private _onSlotChange() {\n this._hideMenuItems()\n // 打开期间宿主重建顶层项时,新成员要移入 portal 才对用户可见;\n // 关闭状态下无需移动,等下次打开时由 _setupMenuItems 统一处理。\n if (this._isOpen) this._scheduleRefresh()\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-context-menu': WebUiContextMenu\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/components/context-menu/scrim.css?inline
2
- var e = "dialog.wui-context-menu-scrim{width:auto;max-width:none;height:auto;max-height:none;color:inherit;background:0 0;border:0;margin:0;padding:0;position:fixed;inset:0;overflow:visible}dialog.wui-context-menu-scrim::backdrop{background:0 0}dialog.wui-context-menu-scrim:not([open]){display:block}";
2
+ var e = "dialog.wui-context-menu-scrim{width:auto;max-width:none;height:auto;max-height:none;color:inherit;background:0 0;border:0;margin:0;padding:0;position:fixed;inset:0;overflow:visible}dialog.wui-context-menu-scrim::backdrop{background:0 0}dialog.wui-context-menu-scrim:not([open]){display:block}dialog.wui-context-menu-scrim:focus,dialog.wui-context-menu-scrim:focus-visible{outline:none}";
3
3
  //#endregion
4
4
  export { e as default };
5
5