luxen-ui 0.18.1 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,6 +98,13 @@ export class Dropdown extends LuxenElement {
98
98
  this._onTriggerClick = (e) => {
99
99
  if (this.disabled)
100
100
  return;
101
+ // The pointerdown that produced this click already light-dismissed the panel:
102
+ // `open` still reads true, but the panel has left the top layer and the
103
+ // queued `toggle` owns the close (and its `hide` event). Closing here too
104
+ // would emit a second one. A keyboard activation never gets here — no
105
+ // light-dismiss, so the panel is still open.
106
+ if (this.open && !this._panelEl?.matches(':popover-open'))
107
+ return;
101
108
  this.toggle();
102
109
  // Space/Enter on a native button dispatches click with detail=0; focus the
103
110
  // first item so the menu is keyboard-navigable immediately on open.
@@ -108,7 +115,20 @@ export class Dropdown extends LuxenElement {
108
115
  this._onTriggerKeyDown = (e) => {
109
116
  if (this.disabled)
110
117
  return;
111
- if (e.key === 'ArrowDown') {
118
+ if (e.key === 'Escape') {
119
+ // A pointer-opened menu leaves focus on the trigger, so `_onPanelKeyDown`
120
+ // never sees the key. The platform's popover light-dismiss normally covers
121
+ // that, but it runs as a close request the page can suppress — any
122
+ // `preventDefault()` on the Escape keydown (a common app-level shortcut
123
+ // handler) leaves the menu stuck open. Close it here, and preventDefault
124
+ // so the close request doesn't race our animated hide.
125
+ if (!this.open)
126
+ return;
127
+ e.preventDefault();
128
+ this.hide();
129
+ // Focus is already on the trigger — nothing to restore.
130
+ }
131
+ else if (e.key === 'ArrowDown') {
112
132
  e.preventDefault();
113
133
  this.show();
114
134
  requestAnimationFrame(() => this._focusFirstItem());
@@ -220,13 +240,35 @@ export class Dropdown extends LuxenElement {
220
240
  clearTimeout(this._hoverTimer);
221
241
  this._hoverItem = null;
222
242
  };
223
- /** Sync `open` when popover="auto" light-dismiss fires. */
243
+ /**
244
+ * Route `popover="auto"` light-dismiss through the same close path as every
245
+ * other user-driven close (trigger click, Escape, item selection), so the
246
+ * cancelable `hide` event fires for it too. Assigning `open` here directly
247
+ * would skip it and desync anything a consumer syncs on `hide`.
248
+ *
249
+ * Driving `open` as a property or attribute still closes the menu with only
250
+ * `after-hide` — `l-dialog` mediates that path from `updated()`, this element
251
+ * does not. A gap worth closing on its own.
252
+ *
253
+ * The platform makes a popover's close non-cancelable (`beforetoggle` is only
254
+ * cancelable when opening), so a vetoed `hide` is honored by putting the panel
255
+ * back. `toggle` is the right hook for that: it is queued after the hide has
256
+ * actually been applied, whereas during `beforetoggle` the panel is still
257
+ * `:popover-open` and `showPopover()` would be a no-op the pending hide undoes.
258
+ */
224
259
  this._onToggle = (e) => {
225
260
  const toggleEvent = e;
226
- if (toggleEvent.newState === 'closed' && this.open) {
261
+ // Our own close path flips `open` before hiding the popover, so this guard
262
+ // keeps it from emitting a second `hide` for the same close.
263
+ if (toggleEvent.newState !== 'closed' || !this.open)
264
+ return;
265
+ if (this.dispatchEvent(new HideEvent())) {
227
266
  this.open = false;
228
- this._triggerEl?.setAttribute('aria-expanded', 'false');
267
+ return;
229
268
  }
269
+ const panel = this._panelEl;
270
+ if (panel?.isConnected)
271
+ panel.showPopover();
230
272
  };
231
273
  }
232
274
  /** Whether the dropdown is open. */
@@ -34,6 +34,8 @@ interface TabsEventMap {
34
34
  * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.
35
35
  * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.
36
36
  * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.
37
+ * @cssproperty [--hover-color=var(--l-color-bg-state-hover)] - `line` variant: fill of the pill that appears behind a hovered tab label.
38
+ * @cssproperty [--hover-inset=4px] - `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.
37
39
  *
38
40
  * @customElement l-tabs
39
41
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tabs/tabs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,CAAC;AAExD,gEAAgE;AAChE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;gBACjB,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAK/C;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qBAAa,IAAK,SAAQ,YAAY;IAC3B,gBAAgB;IAIzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,eAAe,CAA+B;IAEtD,sBAAsB;IAEtB,OAAO,EAAE,WAAW,CAAU;IAE9B,yCAAyC;IAEzC,KAAK,SAAO;IAEZ,4CAA4C;IAE5C,SAAS,UAAS;IAElB,uBAAuB;IAEvB,WAAW,EAAE,eAAe,CAAgB;IAInC,iBAAiB;IAUjB,oBAAoB;IAM7B,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAOR,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAW9C,OAAO,CAAC,MAAM;IA0Dd,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IA2BlB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,QAAQ,CAQd;IAEF,OAAO,CAAC,UAAU,CAkChB;CACH;AA8BD,MAAM,WAAW,IAAI;IACnB,gBAAgB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC3C,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC9C,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;CACT"}
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tabs/tabs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAG7D,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,CAAC;AAExD,gEAAgE;AAChE,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;gBACjB,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAK/C;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,qBAAa,IAAK,SAAQ,YAAY;IAC3B,gBAAgB;IAIzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,eAAe,CAA+B;IAEtD,sBAAsB;IAEtB,OAAO,EAAE,WAAW,CAAU;IAE9B,yCAAyC;IAEzC,KAAK,SAAO;IAEZ,4CAA4C;IAE5C,SAAS,UAAS;IAElB,uBAAuB;IAEvB,WAAW,EAAE,eAAe,CAAgB;IAInC,iBAAiB;IAUjB,oBAAoB;IAM7B,mFAAmF;IACnF,OAAO,CAAC,SAAS;IAOR,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAW9C,OAAO,CAAC,MAAM;IA0Dd,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IA2BlB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,QAAQ,CAQd;IAEF,OAAO,CAAC,UAAU,CAkChB;CACH;AA8BD,MAAM,WAAW,IAAI;IACnB,gBAAgB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC3C,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;IACR,mBAAmB,CAAC,CAAC,SAAS,MAAM,YAAY,EAC9C,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,EACnD,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;IACR,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI,CAAC;CACT"}
@@ -39,6 +39,8 @@ export class TabsChangeEvent extends Event {
39
39
  * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.
40
40
  * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.
41
41
  * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.
42
+ * @cssproperty [--hover-color=var(--l-color-bg-state-hover)] - `line` variant: fill of the pill that appears behind a hovered tab label.
43
+ * @cssproperty [--hover-inset=4px] - `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.
42
44
  *
43
45
  * @customElement l-tabs
44
46
  */
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.18.1",
2
+ "version": "0.18.2",
3
3
  "elements": [
4
4
  {
5
5
  "name": "alert",
@@ -5353,6 +5353,16 @@
5353
5353
  "name": "--track-thickness",
5354
5354
  "default": "1px",
5355
5355
  "description": "`line` variant: thickness of the static bottom border."
5356
+ },
5357
+ {
5358
+ "name": "--hover-color",
5359
+ "default": "var(--l-color-bg-state-hover)",
5360
+ "description": "`line` variant: fill of the pill that appears behind a hovered tab label."
5361
+ },
5362
+ {
5363
+ "name": "--hover-inset",
5364
+ "default": "4px",
5365
+ "description": "`line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border."
5356
5366
  }
5357
5367
  ],
5358
5368
  "commands": [],
@@ -83,6 +83,16 @@
83
83
  "name": "--track-thickness",
84
84
  "default": "1px",
85
85
  "description": "`line` variant: thickness of the static bottom border."
86
+ },
87
+ {
88
+ "name": "--hover-color",
89
+ "default": "var(--l-color-bg-state-hover)",
90
+ "description": "`line` variant: fill of the pill that appears behind a hovered tab label."
91
+ },
92
+ {
93
+ "name": "--hover-inset",
94
+ "default": "4px",
95
+ "description": "`line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border."
86
96
  }
87
97
  ],
88
98
  "commands": [],
@@ -54,7 +54,7 @@ Add `variant="line"` for a tablist with a sliding underline indicator.
54
54
  </l-tabs>
55
55
  ```
56
56
 
57
- Restyle the active underline and the static bottom border with `--indicator-color`, `--indicator-thickness`, `--track-color` and `--track-thickness`.
57
+ Hovering a tab reveals a subtle pill behind its label, detached from the bottom border. Restyle it with `--hover-color` (and `--hover-inset` for the gap), along with the active underline and the static bottom border via `--indicator-color`, `--indicator-thickness`, `--track-color` and `--track-thickness`.
58
58
 
59
59
  ```html
60
60
  <l-tabs
@@ -63,6 +63,7 @@ Restyle the active underline and the static bottom border with `--indicator-colo
63
63
  --indicator-color: var(--l-color-bg-fill-info-strong);
64
64
  --indicator-thickness: 3px;
65
65
  --track-color: var(--l-color-divider);
66
+ --hover-color: var(--l-color-bg-fill-info-soft);
66
67
  "
67
68
  >
68
69
  <div>
@@ -191,3 +192,5 @@ import 'luxen-ui/tabs';
191
192
  - `--indicator-thickness` (default: `2px`) — `line` variant: thickness of the active underline.
192
193
  - `--track-color` (default: `var(--l-color-border)`) — `line` variant: color of the static bottom border the tabs sit on.
193
194
  - `--track-thickness` (default: `1px`) — `line` variant: thickness of the static bottom border.
195
+ - `--hover-color` (default: `var(--l-color-bg-state-hover)`) — `line` variant: fill of the pill that appears behind a hovered tab label.
196
+ - `--hover-inset` (default: `4px`) — `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxen-ui",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Modern web components and CSS-first UI library built with Lit. Framework-agnostic, customizable prefix, design tokens.",
5
5
  "keywords": [
6
6
  "custom-elements",