@rcarls/rc-menu-button 0.6.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,125 @@
1
1
  # @rcarls/rc-menu-button
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9ee769c: Anchor popups in logical terms only. A placement is now `block-start`,
8
+ `block-end`, `inline-start`, or `inline-end`, each with an optional
9
+ `-start`/`-end` edge alignment, and all of them follow the writing mode and
10
+ direction. Block sides open above or below a row in horizontal text and
11
+ beside it in vertical text; inline sides open along the reading direction,
12
+ so `inline-end` opens to the left in RTL. `resolveAnchorPlacement` exposes
13
+ the resolution for other consumers.
14
+
15
+ **Breaking:** the physical placements `top*`, `bottom*`, `left*`, and
16
+ `right*` are removed from `AnchorPlacement`, `rc-menu-button`'s and
17
+ `rc-fab-menu`'s `placement`, and the React and Solid typings. Migrate:
18
+
19
+ - `bottom*` → `block-end*`
20
+ - `top*` → `block-start*`
21
+ - `right*` → `inline-end*` (in LTR)
22
+ - `left*` → `inline-start*` (in LTR)
23
+
24
+ Defaults move with them and render the same in horizontal LTR text:
25
+ `rc-menu-button`, `rc-select`, and `rc-combobox` open at `block-end-start`,
26
+ `rc-adaptive-menu`'s overflow menu at `block-end-end`, and `rc-fab-menu` at
27
+ `block-start-end`. In vertical text these popups now open beside their
28
+ trigger instead of over the next item. Vertical menubars and vertical
29
+ adaptive menus open submenus at `inline-end-start`. Open popups also
30
+ re-resolve their physical side and alignment when an ancestor's `dir`
31
+ changes, and `rc-fab-menu` keeps its corner-aware default even when nested in
32
+ a vertical menubar.
33
+
34
+ - 94dc987: `rc-menu-button`'s, `rc-select`'s, and `rc-combobox`'s popup/listbox now fade
35
+ in and out through CSS alone, using `@starting-style` and a discrete
36
+ `display`/`overlay` transition, driven by new `--rc-menu-button-popup-duration`,
37
+ `--rc-select-listbox-duration`, and `--rc-combobox-listbox-duration`
38
+ properties. `showPopover()`/`hidePopover()` (via `openPopup()`/`closePopup()`)
39
+ stay synchronous, and nothing waits on the transition finishing.
40
+
41
+ These animate opacity only, not a scale or slide transform: all three
42
+ position their popup with the shared `AnchorController`, which applies its
43
+ own viewport-clamping `translate` and reads `getBoundingClientRect()` to
44
+ compute the next correction. A second, CSS-driven transform on the same
45
+ element and property would fight that positioning math on every open, not
46
+ only during an edge case.
47
+
48
+ `rc-fab-menu` already had a scale-based entrance for its popup (verified safe
49
+ here: unlike the general `AnchorController` case, this popup isn't
50
+ collision-clamped), but no exit animation at all and no directional easing.
51
+ Both are added: the popup now fades and scales back out on close, and enter
52
+ uses a decelerating curve while exit uses an accelerating one.
53
+
54
+ ### Patch Changes
55
+
56
+ - 1179f24: Drop the `@oddbird/css-anchor-positioning` polyfill. Browsers without full
57
+ native anchor positioning now use `AnchorController`'s script positioning,
58
+ which applies the same placement and flip rules and re-positions when the page
59
+ scrolls or the viewport, anchor, or floating element resizes. Those browsers no
60
+ longer download the 41 kB polyfill the first time a popup opens.
61
+ - 5c006de: Ship component CSS minified. Package builds now run the CSS in Lit `css` styles
62
+ and light-DOM structural styles through Lightning CSS, removing comments and
63
+ whitespace while keeping source maps that resolve to each template. Rendered
64
+ styles are unchanged.
65
+
66
+ `@rcarls/rc-common` adds `cssText`, an identity tag that marks a plain CSS
67
+ string for minification. `rc-chip`, `rc-disclosure`, and `rc-accordion` now
68
+ depend on `@rcarls/rc-common` for it.
69
+
70
+ - a6553ff: Batch layout sampling into a shared animation-frame read phase so mounting a
71
+ large component route no longer alternates forced layout reads with component
72
+ writes. Closed anchored popups now skip placement work until they open, while
73
+ scroller boundaries, carousel positioning, flow state, and opted-in initial
74
+ scroll observation settle before the next paint.
75
+
76
+ Add `RafScheduler.schedulePhased()`, live `AnchorController.disabled` getters,
77
+ and the `ScrollObserverController.initialEvaluation` timing option.
78
+
79
+ - f1ca82d: Add an anchored, non-modal popover package with a Material rich-tooltip recipe. Share native Popover API visibility and fallback behavior across existing popup components.
80
+ - f0f7511: Document `orientation` as the layout the trigger sits in, with the opening
81
+ keys for each writing mode and direction, and the `inline-end-start` default
82
+ placement under vertical orientation. List the
83
+ `--rc-menu-button-popup-duration` custom property in the README.
84
+ - c5b6f15: Read a plain `[role="menubar"]` parent's `aria-orientation` as the
85
+ orientation it renders, not its layout, so a menu button in a menubar row
86
+ in vertical text opens with the right arrow key.
87
+ - Updated dependencies [e3dfa59]
88
+ - Updated dependencies [50d5610]
89
+ - Updated dependencies [9ee769c]
90
+ - Updated dependencies [1179f24]
91
+ - Updated dependencies [595fe37]
92
+ - Updated dependencies [38cd00c]
93
+ - Updated dependencies [5c006de]
94
+ - Updated dependencies [a6553ff]
95
+ - Updated dependencies [2205478]
96
+ - Updated dependencies [4341311]
97
+ - Updated dependencies [899bb5e]
98
+ - Updated dependencies [25fcfe0]
99
+ - Updated dependencies [51f6403]
100
+ - Updated dependencies [9caca6b]
101
+ - Updated dependencies [b764a3c]
102
+ - Updated dependencies [54d9ead]
103
+ - Updated dependencies [b9175e9]
104
+ - Updated dependencies [c8b762b]
105
+ - Updated dependencies [55e1e7f]
106
+ - Updated dependencies [f1ca82d]
107
+ - Updated dependencies [687a9be]
108
+ - Updated dependencies [b87f27b]
109
+ - Updated dependencies [32ebead]
110
+ - Updated dependencies [bfefedd]
111
+ - Updated dependencies [c130173]
112
+ - Updated dependencies [11680b6]
113
+ - Updated dependencies [223d36c]
114
+ - Updated dependencies [7b91a44]
115
+ - Updated dependencies [aa04ead]
116
+ - Updated dependencies [ac65b5b]
117
+ - Updated dependencies [6d126fd]
118
+ - Updated dependencies [afcfef8]
119
+ - Updated dependencies [11ef1ad]
120
+ - @rcarls/rc-common@0.7.0
121
+ - @rcarls/rc-menu@0.7.0
122
+
3
123
  ## 0.6.0
4
124
 
5
125
  ### Minor Changes
package/README.md CHANGED
@@ -53,13 +53,13 @@ item.
53
53
 
54
54
  ### Properties / attributes
55
55
 
56
- | Property | Attribute | Type | Default | Description |
57
- | ------------- | -------------- | ----------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
58
- | `open` | `open` | `boolean` | `false` | Whether the menu popup is visible. Reflects to attribute. Controlled mode: host writes silently. |
59
- | `defaultOpen` | `default-open` | `boolean` | `false` | Initial open state for uncontrolled mode. Ignored after the first controlled write to `open`. |
60
- | `orientation` | `orientation` | `'horizontal' \| 'vertical' \| undefined` | `undefined` | Arrow-key axis for opening the menu. Vertical triggers span their allocated menu-row width. When unset, inherited from a parent `rc-menubar` or `[role="menubar"]` element. Falls back to `'horizontal'`. |
61
- | `placement` | `placement` | `AnchorPlacement` | `'bottom-start'` | Preferred placement of the popup relative to the trigger. Switches to `'right-start'` under vertical orientation unless explicitly overridden. |
62
- | | `icon-only` | boolean attribute | absent | Hints that the trigger has no visible label so themes can apply icon-button sizing and activation-area geometry. |
56
+ | Property | Attribute | Type | Default | Description |
57
+ | ------------- | -------------- | ----------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
+ | `open` | `open` | `boolean` | `false` | Whether the menu popup is visible. Reflects to attribute. Controlled mode: host writes silently. |
59
+ | `defaultOpen` | `default-open` | `boolean` | `false` | Initial open state for uncontrolled mode. Ignored after the first controlled write to `open`. |
60
+ | `orientation` | `orientation` | `'horizontal' \| 'vertical' \| undefined` | `undefined` | The layout the trigger sits in: `horizontal` along the inline axis, `vertical` along the block axis. Picks the arrow key that opens the menu, following how the layout renders in RTL and vertical text. Vertical triggers span their allocated menu-row width. When unset, inherited from a parent `rc-menubar` or `[role="menubar"]` element. Falls back to `'horizontal'`. |
61
+ | `placement` | `placement` | `AnchorPlacement` | `'block-end-start'` | Preferred placement of the popup relative to the trigger, in logical terms: `block-start*`/`block-end*` (above or below in horizontal text, beside the trigger in vertical text) or `inline-start*`/`inline-end*` (beside it along the reading direction), with an optional `-start`/`-end` edge alignment. When unset, switches to `'inline-end-start'` under vertical orientation. |
62
+ | | `icon-only` | boolean attribute | absent | Hints that the trigger has no visible label so themes can apply icon-button sizing and activation-area geometry. |
63
63
 
64
64
  ### CSS custom properties
65
65
 
@@ -88,6 +88,7 @@ item.
88
88
  | `--rc-menu-button-touch-target-inline-size` | Activation-area block size | Minimum inline size of an `icon-only` trigger's activation area |
89
89
  | `--rc-menu-button-touch-target-overlap-inline-start` | `0px` | Allows touch-target inflation to overlap the leading neighbor |
90
90
  | `--rc-menu-button-touch-target-overlap-inline-end` | `0px` | Allows touch-target inflation to overlap the trailing neighbor |
91
+ | `--rc-menu-button-popup-duration` | `150ms` | Popup open/close fade transition duration |
91
92
 
92
93
  ### CSS parts
93
94
 
@@ -128,17 +129,24 @@ toggleMenu(): void
128
129
  ## Keyboard behavior
129
130
 
130
131
  Keyboard handling depends on the resolved `orientation` (horizontal by
131
- default, or inherited from a parent menubar).
132
-
133
- | Key | Condition | Action |
134
- | ----------------- | ---------------------- | ----------------------------------- |
135
- | `ArrowDown` | Horizontal orientation | Open menu, focus first item |
136
- | `ArrowUp` | Horizontal orientation | Open menu, focus last item |
137
- | `ArrowRight` | Vertical orientation | Open menu, focus first item |
138
- | `ArrowLeft` | Vertical orientation | Open menu, focus last item |
139
- | `Enter` / `Space` | Menu closed | Open menu |
140
- | `Enter` / `Space` | Menu open | Activate focused item, close menu |
141
- | `Escape` | Menu open | Close menu, return focus to trigger |
132
+ default, or inherited from a parent menubar) and on how that layout renders.
133
+ The opening keys turn with RTL and vertical writing modes:
134
+
135
+ | Layout | Flow | Open, focus first item | Open, focus last item |
136
+ | ---------- | -------------------- | ---------------------- | --------------------- |
137
+ | Horizontal | Horizontal text | `ArrowDown` | `ArrowUp` |
138
+ | Horizontal | `vertical-rl` | `ArrowLeft` | `ArrowRight` |
139
+ | Horizontal | `vertical-lr` | `ArrowRight` | `ArrowLeft` |
140
+ | Vertical | Horizontal text, LTR | `ArrowRight` | `ArrowLeft` |
141
+ | Vertical | Horizontal text, RTL | `ArrowLeft` | `ArrowRight` |
142
+ | Vertical | Vertical text, LTR | `ArrowDown` | `ArrowUp` |
143
+ | Vertical | Vertical text, RTL | `ArrowUp` | `ArrowDown` |
144
+
145
+ | Key | Condition | Action |
146
+ | ----------------- | ----------- | ----------------------------------- |
147
+ | `Enter` / `Space` | Menu closed | Open menu |
148
+ | `Enter` / `Space` | Menu open | Activate focused item, close menu |
149
+ | `Escape` | Menu open | Close menu, return focus to trigger |
142
150
 
143
151
  ---
144
152
 
@@ -160,6 +160,11 @@
160
160
  "description": "The trailing-edge counterpart to `--rc-menu-button-touch-target-overlap-inline-start`.",
161
161
  "name": "--rc-menu-button-touch-target-overlap-inline-end",
162
162
  "default": "0px"
163
+ },
164
+ {
165
+ "description": "Popup open/close fade transition duration.",
166
+ "name": "--rc-menu-button-popup-duration",
167
+ "default": "150ms"
163
168
  }
164
169
  ],
165
170
  "cssParts": [
@@ -283,21 +288,39 @@
283
288
  "type": {
284
289
  "text": "'horizontal' | 'vertical' | undefined"
285
290
  },
286
- "description": "Orientation of this menu button, affects which arrow keys open/close the menu.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
291
+ "description": "The layout this menu button sits in, which picks the arrow keys that open\nand close the menu. `horizontal` runs along the inline axis and `vertical`\nalong the block axis; the keys follow how that layout renders.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
287
292
  "attribute": "orientation",
288
293
  "reflects": true
289
294
  },
295
+ {
296
+ "kind": "field",
297
+ "name": "_placement",
298
+ "type": {
299
+ "text": "AnchorPlacement | undefined"
300
+ },
301
+ "privacy": "private"
302
+ },
290
303
  {
291
304
  "kind": "field",
292
305
  "name": "placement",
293
306
  "type": {
294
307
  "text": "AnchorPlacement"
295
308
  },
296
- "default": "'bottom-start'",
297
- "description": "Preferred placement of the popup relative to the trigger button.",
309
+ "description": "Preferred placement of the popup relative to the trigger button. Reads the\ndefault until the author sets one.",
310
+ "default": "'block-end-start'",
298
311
  "attribute": "placement",
299
312
  "reflects": true
300
313
  },
314
+ {
315
+ "kind": "field",
316
+ "name": "_defaultPlacement",
317
+ "type": {
318
+ "text": "AnchorPlacement"
319
+ },
320
+ "privacy": "protected",
321
+ "description": "The placement used when the author sets none. Subclasses override it.",
322
+ "readonly": true
323
+ },
301
324
  {
302
325
  "kind": "field",
303
326
  "name": "_effectivePlacement",
@@ -305,7 +328,7 @@
305
328
  "text": "AnchorPlacement"
306
329
  },
307
330
  "privacy": "protected",
308
- "description": "Popup placement adjusted for orientation.\n\nSwitches from `bottom-start` to `right-start` when the resolved orientation\nis `vertical`, so vertical menubars open submenus to the side.",
331
+ "description": "Popup placement adjusted for orientation.\n\nWithout an author placement, a menu button whose resolved orientation is\n`vertical` opens at `inline-end-start`, so vertical menubars open\nsubmenus to the side, toward the inline end.",
309
332
  "readonly": true
310
333
  },
311
334
  {
@@ -358,9 +381,15 @@
358
381
  "kind": "field",
359
382
  "name": "_anchorCtrl",
360
383
  "privacy": "protected",
361
- "default": "new AnchorController(this, { anchor: () => this._$root ?? null, floating: () => this._$popup ?? null, shadowHost: () => this, placement: 'bottom-start', offset: 2, })",
384
+ "default": "new AnchorController(this, { anchor: () => this._$root ?? null, floating: () => this._$popup ?? null, shadowHost: () => this, placement: 'block-end-start', offset: 2, disabled: () => !this.open, })",
362
385
  "description": "Positions the popup relative to the trigger using CSS anchor positioning."
363
386
  },
387
+ {
388
+ "kind": "field",
389
+ "name": "_popoverCtrl",
390
+ "privacy": "protected",
391
+ "default": "new PopoverController(this, { element: () => this._$popup })"
392
+ },
364
393
  {
365
394
  "kind": "field",
366
395
  "name": "_boundHandleDocumentClick",
@@ -591,7 +620,7 @@
591
620
  "fieldName": "defaultOpen"
592
621
  },
593
622
  {
594
- "description": "Orientation of this menu button, affects which arrow keys open/close the menu.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
623
+ "description": "The layout this menu button sits in, which picks the arrow keys that open\nand close the menu. `horizontal` runs along the inline axis and `vertical`\nalong the block axis; the keys follow how that layout renders.\n\nIf not set, inherits from a parent rc-menubar or element with role=\"menubar\".",
595
624
  "name": "orientation",
596
625
  "type": {
597
626
  "text": "'horizontal' | 'vertical' | undefined"
@@ -599,12 +628,12 @@
599
628
  "fieldName": "orientation"
600
629
  },
601
630
  {
602
- "description": "Preferred placement of the popup relative to the trigger button.",
631
+ "description": "Preferred placement of the popup relative to the trigger button. Reads the\ndefault until the author sets one.",
603
632
  "name": "placement",
604
633
  "type": {
605
634
  "text": "AnchorPlacement"
606
635
  },
607
- "default": "'bottom-start'",
636
+ "default": "'block-end-start'",
608
637
  "fieldName": "placement"
609
638
  },
610
639
  {
@@ -0,0 +1,284 @@
1
+ import { css as h, LitElement as p, html as b } from "lit";
2
+ import { property as l, query as d, state as g } from "lit/decorators.js";
3
+ import { AnchorController as m, PopoverController as v, layoutOrientation as f, resolveFlow as _, keyNavigation as y, keyInteraction as k, cssText as z } from "@rcarls/rc-common";
4
+ const $ = h`:host{display:inline-block}:host([orientation=vertical]){display:block}#root{display:inline-block}:host([orientation=vertical]) #root{inline-size:100%;display:block}#trigger-wrap{display:inline-block;position:relative}:host([orientation=vertical]) #trigger-wrap{inline-size:100%;display:block}:host([icon-only]) #trigger-wrap{min-block-size:var(--rc-menu-button-touch-target-block-size,3rem);min-inline-size:var(--rc-menu-button-touch-target-inline-size,var(--rc-menu-button-touch-target-block-size,3rem));justify-content:center;align-items:center;margin-inline-start:calc(-1 * var(--rc-menu-button-touch-target-overlap-inline-start,0px));margin-inline-end:calc(-1 * var(--rc-menu-button-touch-target-overlap-inline-end,0px));display:inline-flex}:host([icon-only][orientation=vertical]) #trigger-wrap{display:flex}:host([icon-only]) slot[name=trigger]::slotted(button),:host([icon-only]) slot[name=trigger]::slotted([role=button]){inline-size:var(--rc-menu-button-icon-size,var(--rc-menu-button-trigger-block-size,var(--rc-control-block-size,2.25em)));min-inline-size:var(--rc-menu-button-icon-size,var(--rc-menu-button-trigger-block-size,var(--rc-control-block-size,2.25em)));padding-inline:0}slot[name=trigger]::slotted(button),slot[name=trigger]::slotted([role=button]){justify-content:center;align-items:center;gap:var(--rc-menu-button-trigger-gap,var(--rc-item-gap,.5em));min-block-size:var(--rc-menu-button-trigger-block-size,var(--rc-control-block-size,2.25em));padding:var(--rc-menu-button-trigger-padding-block,var(--rc-control-padding-block,.25em)) var(--rc-menu-button-trigger-padding-inline,var(--rc-control-padding-inline,.5em));appearance:none;box-sizing:border-box;border:var(--rc-menu-button-trigger-border,var(--rc-border,1px solid ButtonBorder));border-radius:var(--rc-menu-button-trigger-radius,var(--rc-control-radius,.125em));background:var(--rc-menu-button-trigger-background,var(--rc-button-bg,ButtonFace));color:var(--rc-menu-button-trigger-color,var(--rc-button-text,ButtonText));font:inherit;cursor:default;user-select:none;transition:var(--rc-menu-button-trigger-transition);text-decoration:none;display:inline-flex;position:relative}slot[name=trigger]::slotted(button:hover),slot[name=trigger]::slotted([role=button]:hover){border-color:var(--rc-menu-button-trigger-hover-border-color,currentColor);background:var(--rc-menu-button-trigger-hover-background,color-mix(in srgb, Highlight 8%, transparent));color:var(--rc-menu-button-trigger-hover-color,inherit)}slot[name=trigger]::slotted(button[aria-expanded=true]),slot[name=trigger]::slotted([role=button][aria-expanded=true]){border-color:var(--rc-menu-button-trigger-open-border-color,currentColor);background:var(--rc-menu-button-trigger-open-background,color-mix(in srgb, Highlight 12%, transparent));color:var(--rc-menu-button-trigger-open-color,inherit)}:host([orientation=vertical]) slot[name=trigger]::slotted(button),:host([orientation=vertical]) slot[name=trigger]::slotted([role=button]){text-align:start;inline-size:100%}:host([has-indicator]) slot[name=trigger]::slotted(button),:host([has-indicator]) slot[name=trigger]::slotted([role=button]){padding-inline-end:calc(var(--rc-menu-button-indicator-inset,var(--rc-menu-button-trigger-padding-inline,var(--rc-control-padding-inline,.5em))) + var(--rc-menu-button-indicator-size,1em) + var(--rc-menu-button-trigger-gap,var(--rc-item-gap,.5em)))}slot[name=indicator]{inline-size:var(--rc-menu-button-indicator-size,1em);block-size:var(--rc-menu-button-indicator-size,1em);color:var(--rc-menu-button-indicator-color,currentColor);pointer-events:none;justify-content:center;align-items:center;display:inline-flex;position:absolute;inset-block-start:50%;inset-inline-end:var(--rc-menu-button-indicator-inset,var(--rc-menu-button-trigger-padding-inline,var(--rc-control-padding-inline,.5em)));transform:translateY(-50%)}slot[name=indicator]::slotted(*){block-size:100%;inline-size:100%}#popup{color:inherit;background:0 0;border:none;margin:0;padding:0;overflow:visible}#popup[data-rc-popover-fallback-open]{display:block}@media (prefers-reduced-motion:no-preference){:host([open]) #popup{opacity:1;transition:opacity var(--rc-menu-button-popup-duration,.15s) var(--rc-motion-effects-easing-enter,ease-out), overlay var(--rc-menu-button-popup-duration,.15s) allow-discrete, display var(--rc-menu-button-popup-duration,.15s) allow-discrete}:host(:not([open])) #popup{opacity:0;transition:opacity var(--rc-menu-button-popup-duration,.15s) var(--rc-motion-effects-easing-exit,ease-in), overlay var(--rc-menu-button-popup-duration,.15s) allow-discrete, display var(--rc-menu-button-popup-duration,.15s) allow-discrete}@starting-style{:host([open]) #popup{opacity:0}}}`;
5
+ var x = Object.defineProperty, C = Object.getOwnPropertyDescriptor, s = (u, t, e, n) => {
6
+ for (var i = n > 1 ? void 0 : n ? C(t, e) : t, a = u.length - 1, c; a >= 0; a--)
7
+ (c = u[a]) && (i = (n ? c(t, e, i) : c(i)) || i);
8
+ return n && i && x(t, e, i), i;
9
+ };
10
+ const O = z`@layer rc-base{rc-menu-button[icon-only]>[slot=trigger]:before{content:"";inset-block:calc(( var(--rc-menu-button-touch-target-block-size,3rem) - max(100%, var(--rc-menu-button-trigger-block-size,0px)) ) / -2);inset-inline:calc(( var(--rc-menu-button-touch-target-inline-size,var(--rc-menu-button-touch-target-block-size,3rem)) - max(100%, var(--rc-menu-button-icon-size,var(--rc-menu-button-trigger-block-size,0px))) ) / -2);position:absolute}}`;
11
+ var r;
12
+ const o = (r = class extends p {
13
+ constructor() {
14
+ super(...arguments), this._defaultOpen = !1, this._openInitialized = !1, this._anchorCtrl = new m(this, {
15
+ anchor: () => this._$root ?? null,
16
+ floating: () => this._$popup ?? null,
17
+ shadowHost: () => this,
18
+ placement: "block-end-start",
19
+ offset: 2,
20
+ disabled: () => !this.open
21
+ }), this._popoverCtrl = new v(this, { element: () => this._$popup }), this._boundHandleDocumentClick = this._handleDocumentClick.bind(this), this._onNavigate = (t) => {
22
+ switch (t) {
23
+ case "open-to-first":
24
+ case "activate":
25
+ this.openMenu();
26
+ break;
27
+ case "open-to-last":
28
+ this.openMenu("last");
29
+ break;
30
+ case "escape":
31
+ this.closeMenu();
32
+ break;
33
+ }
34
+ };
35
+ }
36
+ static _ensureBaseStyles(t) {
37
+ if (r._styledRoots.has(t))
38
+ return;
39
+ r._styledRoots.add(t);
40
+ const e = document.createElement("style");
41
+ e.setAttribute("data-rc-light-dom-base", "rc-menu-button"), e.textContent = O, t instanceof Document ? t.head.append(e) : t.append(e);
42
+ }
43
+ get open() {
44
+ return this._open ?? this._uncontrolledOpen ?? this._defaultOpen;
45
+ }
46
+ /** Applies open state in controlled mode without dispatching an event. */
47
+ set open(t) {
48
+ const e = this.open;
49
+ this._open = t, this._openInitialized = !0, this.requestUpdate("open", e), this._syncTriggerAria();
50
+ }
51
+ get defaultOpen() {
52
+ return this._defaultOpen;
53
+ }
54
+ /** Applies the uncontrolled default before any controlled write occurs. */
55
+ set defaultOpen(t) {
56
+ const e = this._defaultOpen;
57
+ this._defaultOpen = t, !this._openInitialized && this._open === void 0 && this._uncontrolledOpen === void 0 && this.requestUpdate("open", e), this.requestUpdate("defaultOpen", e);
58
+ }
59
+ get placement() {
60
+ return this._placement ?? this._defaultPlacement;
61
+ }
62
+ set placement(t) {
63
+ const e = this.placement;
64
+ this._placement = t ?? void 0, this.requestUpdate("placement", e);
65
+ }
66
+ /** The placement used when the author sets none. Subclasses override it. */
67
+ get _defaultPlacement() {
68
+ return "block-end-start";
69
+ }
70
+ /**
71
+ * Popup placement adjusted for orientation.
72
+ *
73
+ * Without an author placement, a menu button whose resolved orientation is
74
+ * `vertical` opens at `inline-end-start`, so vertical menubars open
75
+ * submenus to the side, toward the inline end.
76
+ */
77
+ get _effectivePlacement() {
78
+ return this._placement ? this._placement : this._resolvedOrientation === "vertical" ? "inline-end-start" : this._defaultPlacement;
79
+ }
80
+ /**
81
+ * Resolved orientation, considering inheritance from parent menubar.
82
+ * Falls back to 'horizontal' if no orientation is set or inherited.
83
+ */
84
+ get _resolvedOrientation() {
85
+ if (this.orientation)
86
+ return this.orientation;
87
+ const t = this.closest('rc-menubar, [role="menubar"]');
88
+ if (t) {
89
+ const e = t.getAttribute("orientation");
90
+ if (e === "vertical" || e === "horizontal")
91
+ return e;
92
+ const n = t.getAttribute("aria-orientation");
93
+ if (n === "vertical" || n === "horizontal")
94
+ return f(n, _(t));
95
+ }
96
+ return "horizontal";
97
+ }
98
+ /** Installs the tabindex mutation observer and the document-level click listener. */
99
+ connectedCallback() {
100
+ super.connectedCallback(), r._ensureBaseStyles(this.getRootNode()), this._tabObserver = new MutationObserver(() => this._syncTriggerTabindex()), this._tabObserver.observe(this, { attributes: !0, attributeFilter: ["tabindex"] }), document.addEventListener("click", this._boundHandleDocumentClick, !0), this.requestUpdate("open");
101
+ }
102
+ /** Disconnects the tabindex observer and removes the document-level click listener. */
103
+ disconnectedCallback() {
104
+ this._tabObserver?.disconnect(), this._tabObserver = void 0, super.disconnectedCallback(), document.removeEventListener("click", this._boundHandleDocumentClick, !0);
105
+ }
106
+ /**
107
+ * Opens the menu and moves focus into it.
108
+ *
109
+ * @param focusTarget - which item receives initial focus when the menu opens
110
+ */
111
+ openMenu(t = "first") {
112
+ this.open || (this._anchorCtrl.setOptions({ placement: this._effectivePlacement }), this._setOpen(!0, !0), this.updateComplete.then(() => {
113
+ if (!this.open)
114
+ return;
115
+ const e = this._$menu?.deref();
116
+ t === "last" ? e?.focusLast() : e?.focusFirst();
117
+ }));
118
+ }
119
+ /**
120
+ * Closes the menu.
121
+ *
122
+ * @param returnFocus - when `true`, returns focus to the trigger button after closing
123
+ */
124
+ closeMenu(t = !0) {
125
+ this.open && (this._setOpen(!1, !0), t && !this.open && this._$trigger?.deref()?.focus());
126
+ }
127
+ /** Toggles the menu between open and closed. */
128
+ toggleMenu() {
129
+ this.open ? this.closeMenu() : this.openMenu();
130
+ }
131
+ /** Dispatches the `rc-menu-button-toggle` bubbling composed event with the requested open state. */
132
+ _dispatchToggle(t = this.open) {
133
+ this.dispatchEvent(
134
+ new CustomEvent("rc-menu-button-toggle", {
135
+ bubbles: !0,
136
+ composed: !0,
137
+ detail: { open: t }
138
+ })
139
+ );
140
+ }
141
+ /** Caches a weak reference to the first assigned trigger element and syncs its ARIA and tabindex state. */
142
+ _handleTriggerSlotChange(t) {
143
+ const n = t.currentTarget.assignedElements()[0];
144
+ n && (this._$trigger = new WeakRef(n), this._syncTriggerAria(), this._syncTriggerTabindex());
145
+ }
146
+ /** Tracks optional indicator content so the trigger reserves its inline-end space. */
147
+ _handleIndicatorSlotChange(t) {
148
+ const e = t.currentTarget;
149
+ this.toggleAttribute("has-indicator", e.assignedElements().length > 0);
150
+ }
151
+ /**
152
+ * Mirrors the host's `tabindex` attribute to the trigger button.
153
+ *
154
+ * When a roving-tabindex parent (e.g. rc-toolbar) marks this host inactive
155
+ * with `tabindex="-1"`, the trigger must also be suppressed so it does not
156
+ * become an independent tab stop. When the host is the active item
157
+ * (`tabindex="0"`) or standalone (no tabindex), the trigger retains its
158
+ * natural focusability so that Chrome's delegatesFocus can route sequential
159
+ * Tab navigation through the host to the trigger.
160
+ */
161
+ _syncTriggerTabindex() {
162
+ const t = this._$trigger?.deref();
163
+ t && (this.getAttribute("tabindex") === "-1" ? t.setAttribute("tabindex", "-1") : t.removeAttribute("tabindex"));
164
+ }
165
+ /**
166
+ * Overrides the default focus() so that programmatic focus calls (e.g. from
167
+ * a roving-tabindex parent navigating back to this element via arrow keys)
168
+ * always reach the trigger.
169
+ *
170
+ * Chrome's delegatesFocus will not delegate to a tabindex="-1" element, so
171
+ * when the toolbar marks this host inactive it suppresses the trigger — and
172
+ * the next focusItem() call silently fails. Lifting the suppression here and
173
+ * directly calling trigger.focus() bypasses that restriction.
174
+ */
175
+ focus(t) {
176
+ const e = this._$trigger?.deref();
177
+ if (!e) {
178
+ super.focus(t);
179
+ return;
180
+ }
181
+ e.removeAttribute("tabindex"), e.focus(t);
182
+ }
183
+ /** Caches a weak reference to the first assigned `rc-menu` element. */
184
+ _handleMenuSlotChange(t) {
185
+ const i = t.currentTarget.assignedElements().find((a) => a.tagName === "RC-MENU");
186
+ i && (this._$menu = new WeakRef(i));
187
+ }
188
+ /** Prevents the default button action and delegates to `toggleMenu`. */
189
+ _handleTriggerClick(t) {
190
+ t.preventDefault(), this.toggleMenu();
191
+ }
192
+ /** Closes the menu when an `rc-menu-close` event bubbles up from the popup. */
193
+ _handleMenuClose() {
194
+ this.closeMenu();
195
+ }
196
+ /** Closes the menu when an `rc-menu-activate` event bubbles up from the popup. */
197
+ _handleMenuActivate() {
198
+ this.closeMenu();
199
+ }
200
+ /** Closes the menu on clicks outside this component (light dismiss). */
201
+ _handleDocumentClick(t) {
202
+ !this.open || t.composedPath().includes(this) || this.closeMenu(!1);
203
+ }
204
+ /** Syncs trigger ARIA when `open` changes; updates anchor placement when `placement` or `orientation` change. */
205
+ updated(t) {
206
+ super.updated(t), t.has("open") && (this._syncTriggerAria(), this._popoverCtrl.sync(this.open), this.open && this._$popup?.isConnected && this._anchorCtrl.update()), (t.has("placement") || t.has("orientation")) && this._anchorCtrl.setOptions({ placement: this._effectivePlacement });
207
+ }
208
+ _setOpen(t, e) {
209
+ const n = this.open;
210
+ n !== t && (this._open === void 0 && (this._uncontrolledOpen = t), this.requestUpdate("open", n), this._syncTriggerAria(), e && this._dispatchToggle(t));
211
+ }
212
+ /** Writes `aria-haspopup` and `aria-expanded` to the trigger button to match the current open state. */
213
+ _syncTriggerAria() {
214
+ const t = this._$trigger?.deref();
215
+ t?.isConnected && (t.setAttribute("aria-haspopup", "menu"), t.setAttribute("aria-expanded", String(this.open)));
216
+ }
217
+ render() {
218
+ return b`
219
+ <div id="root" part="root">
220
+ <div
221
+ id="trigger-wrap"
222
+ ${y(this._onNavigate, {
223
+ navigationAxis: this._resolvedOrientation,
224
+ handleNavAxis: !1,
225
+ handleOpenAxis: !0,
226
+ handleActivate: !0,
227
+ handleEscape: this.open
228
+ })}
229
+ ${k()}
230
+ @click=${this._handleTriggerClick}
231
+ >
232
+ <slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
233
+ <slot
234
+ name="indicator"
235
+ aria-hidden="true"
236
+ @slotchange=${this._handleIndicatorSlotChange}
237
+ ></slot>
238
+ </div>
239
+
240
+ <div
241
+ id="popup"
242
+ part="popup"
243
+ popover="manual"
244
+ @rc-menu-close=${this._handleMenuClose}
245
+ @rc-menu-activate=${this._handleMenuActivate}
246
+ >
247
+ <slot @slotchange=${this._handleMenuSlotChange}></slot>
248
+ </div>
249
+ </div>
250
+ `;
251
+ }
252
+ }, r.styles = [$], r.shadowRootOptions = {
253
+ ...p.shadowRootOptions,
254
+ delegatesFocus: !0
255
+ }, r._styledRoots = /* @__PURE__ */ new Set(), r);
256
+ s([
257
+ l({ type: Boolean, reflect: !0 })
258
+ ], o.prototype, "open", 1);
259
+ s([
260
+ l({ type: Boolean, attribute: "default-open" })
261
+ ], o.prototype, "defaultOpen", 1);
262
+ s([
263
+ l({ type: String, reflect: !0 })
264
+ ], o.prototype, "orientation", 2);
265
+ s([
266
+ l({ reflect: !0 })
267
+ ], o.prototype, "placement", 1);
268
+ s([
269
+ d("#root")
270
+ ], o.prototype, "_$root", 2);
271
+ s([
272
+ d("#popup")
273
+ ], o.prototype, "_$popup", 2);
274
+ s([
275
+ g()
276
+ ], o.prototype, "_$trigger", 2);
277
+ s([
278
+ g()
279
+ ], o.prototype, "_$menu", 2);
280
+ let M = o;
281
+ export {
282
+ M as R
283
+ };
284
+ //# sourceMappingURL=rc-menu-button-C2gavu7b.js.map