@greypan/web-ui 6.4.0 → 7.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 (126) hide show
  1. package/README.CN.md +134 -40
  2. package/README.md +145 -51
  3. package/dist/assets/glass.js +1 -1
  4. package/dist/assets/glass.js.map +1 -1
  5. package/dist/assets/selection-control.js +6 -0
  6. package/dist/assets/selection-control.js.map +1 -0
  7. package/dist/assets/toast-containers.js.map +1 -1
  8. package/dist/components/autocomplete/index.d.ts +2 -0
  9. package/dist/components/autocomplete/index.js +19 -20
  10. package/dist/components/autocomplete/index.js.map +1 -1
  11. package/dist/components/autocomplete/style.js.map +1 -1
  12. package/dist/components/badge/style.js.map +1 -1
  13. package/dist/components/button/style.js.map +1 -1
  14. package/dist/components/checkbox/index.d.ts +9 -2
  15. package/dist/components/checkbox/index.js +50 -26
  16. package/dist/components/checkbox/index.js.map +1 -1
  17. package/dist/components/checkbox/style.js +1 -1
  18. package/dist/components/checkbox/style.js.map +1 -1
  19. package/dist/components/checkbox-group/index.d.ts +3 -0
  20. package/dist/components/checkbox-group/index.js +24 -13
  21. package/dist/components/checkbox-group/index.js.map +1 -1
  22. package/dist/components/checkbox-group/style.js +1 -1
  23. package/dist/components/checkbox-group/style.js.map +1 -1
  24. package/dist/components/collapse/index.js.map +1 -1
  25. package/dist/components/context-menu/index.d.ts +10 -0
  26. package/dist/components/context-menu/index.js +48 -34
  27. package/dist/components/context-menu/index.js.map +1 -1
  28. package/dist/components/dialog/index.d.ts +6 -0
  29. package/dist/components/dialog/index.js +45 -20
  30. package/dist/components/dialog/index.js.map +1 -1
  31. package/dist/components/dialog/style.js +1 -1
  32. package/dist/components/dialog/style.js.map +1 -1
  33. package/dist/components/drawer/index.d.ts +22 -4
  34. package/dist/components/drawer/index.js +134 -106
  35. package/dist/components/drawer/index.js.map +1 -1
  36. package/dist/components/drawer/style.js +1 -1
  37. package/dist/components/drawer/style.js.map +1 -1
  38. package/dist/components/dropdown/index.d.ts +21 -1
  39. package/dist/components/dropdown/index.js +74 -51
  40. package/dist/components/dropdown/index.js.map +1 -1
  41. package/dist/components/image-preview/index.js +86 -66
  42. package/dist/components/image-preview/index.js.map +1 -1
  43. package/dist/components/image-preview/style.js +1 -1
  44. package/dist/components/image-preview/style.js.map +1 -1
  45. package/dist/components/index.js +29 -29
  46. package/dist/components/input/index.js +2 -2
  47. package/dist/components/input/index.js.map +1 -1
  48. package/dist/components/input/style.js.map +1 -1
  49. package/dist/components/layout/index.js +2 -2
  50. package/dist/components/layout/index.js.map +1 -1
  51. package/dist/components/layout/style.js +1 -1
  52. package/dist/components/layout/style.js.map +1 -1
  53. package/dist/components/popover/index.d.ts +8 -2
  54. package/dist/components/popover/index.js +47 -52
  55. package/dist/components/popover/index.js.map +1 -1
  56. package/dist/components/radio/index.d.ts +1 -1
  57. package/dist/components/radio/index.js +14 -13
  58. package/dist/components/radio/index.js.map +1 -1
  59. package/dist/components/radio/style.js +1 -1
  60. package/dist/components/radio/style.js.map +1 -1
  61. package/dist/components/radio-group/index.d.ts +3 -0
  62. package/dist/components/radio-group/index.js +24 -13
  63. package/dist/components/radio-group/index.js.map +1 -1
  64. package/dist/components/radio-group/style.js +1 -1
  65. package/dist/components/radio-group/style.js.map +1 -1
  66. package/dist/components/segmented/style.js +1 -1
  67. package/dist/components/segmented/style.js.map +1 -1
  68. package/dist/components/select/index.d.ts +2 -0
  69. package/dist/components/select/index.js +20 -19
  70. package/dist/components/select/index.js.map +1 -1
  71. package/dist/components/slider/index.js.map +1 -1
  72. package/dist/components/slider/style.js +1 -1
  73. package/dist/components/slider/style.js.map +1 -1
  74. package/dist/components/svg-draw-lines/index.d.ts +13 -2
  75. package/dist/components/svg-draw-lines/index.js +44 -31
  76. package/dist/components/svg-draw-lines/index.js.map +1 -1
  77. package/dist/components/switch/index.js.map +1 -1
  78. package/dist/components/switch/style.js.map +1 -1
  79. package/dist/components/textarea/index.js +2 -2
  80. package/dist/components/textarea/index.js.map +1 -1
  81. package/dist/components/textarea/style.js.map +1 -1
  82. package/dist/components/theme/index.d.ts +17 -0
  83. package/dist/components/theme/index.js +180 -20
  84. package/dist/components/theme/index.js.map +1 -1
  85. package/dist/components/theme/style.js +1 -1
  86. package/dist/components/theme/style.js.map +1 -1
  87. package/dist/components/toast/index.d.ts +1 -1
  88. package/dist/components/toast/manager.d.ts +1 -3
  89. package/dist/components/toast/manager.js +92 -49
  90. package/dist/components/toast/manager.js.map +1 -1
  91. package/dist/components/toast/style.js.map +1 -1
  92. package/dist/components/toast/toast.d.ts +37 -0
  93. package/dist/components/toast/toast.js +61 -8
  94. package/dist/components/toast/toast.js.map +1 -1
  95. package/dist/components/toast/types.d.ts +0 -4
  96. package/dist/components/tooltip/index.d.ts +6 -1
  97. package/dist/components/tooltip/index.js +11 -12
  98. package/dist/components/tooltip/index.js.map +1 -1
  99. package/dist/shared/gesture/drag-gesture.js +66 -64
  100. package/dist/shared/gesture/drag-gesture.js.map +1 -1
  101. package/dist/shared/gesture/physics.js +7 -29
  102. package/dist/shared/gesture/physics.js.map +1 -1
  103. package/dist/shared/menu-behavior/index.js +3 -4
  104. package/dist/shared/menu-behavior/index.js.map +1 -1
  105. package/dist/shared/option-portal/combobox-shell.js +7 -8
  106. package/dist/shared/option-portal/combobox-shell.js.map +1 -1
  107. package/dist/shared/overlay/anchored-panel.js +36 -31
  108. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  109. package/dist/shared/overlay/native-dialog-presence.js +16 -0
  110. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  111. package/dist/shared/overlay/nested-drawer-layers.js +7 -4
  112. package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
  113. package/dist/shared/overlay/open-overlay.js +182 -0
  114. package/dist/shared/overlay/open-overlay.js.map +1 -0
  115. package/dist/shared/overlay/presence.js +19 -22
  116. package/dist/shared/overlay/presence.js.map +1 -1
  117. package/dist/shared/theme/duration.js +9 -0
  118. package/dist/shared/theme/duration.js.map +1 -0
  119. package/dist/shared/theme/reduced-motion.js +15 -0
  120. package/dist/shared/theme/reduced-motion.js.map +1 -0
  121. package/package.json +1 -1
  122. package/dist/shared/overlay/composition.js +0 -57
  123. package/dist/shared/overlay/composition.js.map +0 -1
  124. package/dist/shared/overlay/lifecycle.js +0 -41
  125. package/dist/shared/overlay/lifecycle.js.map +0 -1
  126. package/dist/vite.svg +0 -1
package/README.md CHANGED
@@ -510,8 +510,7 @@ Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation.
510
510
  | `--wui-slider-height` | `var(--wui-slider-track-size, 6px)` | Track thickness |
511
511
  | `--wui-slider-track-size` | `6px` | Track size |
512
512
  | `--wui-slider-thumb-width` | `24px` | Thumb width |
513
- | `--wui-slider-thumb-height` | `18px` | Thumb height |
514
- | `--wui-slider-thumb-radius` | `8px` | Thumb border radius |
513
+ | `--wui-slider-thumb-height` | `20px` | Thumb height |
515
514
  | `--wui-slider-marks-inset` | `0` | Marks inset from edges |
516
515
 
517
516
  #### `<web-ui-checkbox>`
@@ -532,6 +531,12 @@ Individual checkbox.
532
531
 
533
532
  Uses native `<label>` with `role="checkbox"` and `aria-checked`. Enter/Space keyboard toggle.
534
533
 
534
+ **Layout:** the host is an inline-flex box whose height is set by its content, so the inherited page line-height can no longer inflate it or shift the indicator up and down; it aligns against surrounding text with `vertical-align: middle`. The indicator measures `--wui-selection-control-size` (`18px`), and `<web-ui-radio>` shares the same box contract.
535
+
536
+ **Check animation:** the checkmark is the control's own stroked path rather than a `<web-ui-icon>` asset, wrapped in `<web-ui-svg-draw-lines>` (with `no-autoplay`, so a control that mounts already checked shows a static check). Checking draws it in left to right at a constant speed over `--wui-duration-trigger` (160ms by default), read from the resolved theme at the time of the toggle so the stroke lands on the same beat as the indicator's background transition; unchecking retracts the same path back to blank instead of only fading out. Neither plays inside a `motion="reduced"` theme scope, where both states switch instantly.
537
+
538
+ **Idle states:** the unchecked indicator is filled with `--wui-color-surface-control`, the same control surface neutral buttons use, so it stays separable from `--wui-color-page` in dark mode. Hovering anywhere in the trigger row — indicator, gap or slotted label — tints that surface with a 6% state layer over `--wui-color-surface-control`. Hover applies only on `(hover: hover) and (pointer: fine)` devices; there is no pressed state, and checked and disabled controls keep their own surface. `<web-ui-radio>` shares the same states.
539
+
535
540
  #### `<web-ui-radio>`
536
541
 
537
542
  Individual radio button.
@@ -548,6 +553,8 @@ Individual radio button.
548
553
 
549
554
  **Slots:** `default` (label text)
550
555
 
556
+ **Layout:** shares the selection-control box contract with `<web-ui-checkbox>` — host height comes from its content, alignment against surrounding text is `vertical-align: middle`, and the indicator measures `--wui-selection-control-size`.
557
+
551
558
  #### `<web-ui-switch>`
552
559
 
553
560
  Toggle switch.
@@ -592,35 +599,69 @@ Press the selected segment and drag horizontally to slide the indicator; on rele
592
599
 
593
600
  Checkbox group managing multiple selection.
594
601
 
595
- | Attribute | Type | Default | Description |
596
- | ---------- | ---------- | ------- | --------------------------- |
597
- | `value` | `string[]` | `[]` | Selected values |
598
- | `name` | `string` | `''` | Form field name |
599
- | `disabled` | `boolean` | `false` | Disables all child controls |
600
- | `required` | `boolean` | `false` | Required validation |
602
+ ##### Attributes
603
+
604
+ | Attribute | Type | Default | Description |
605
+ | ----------- | ---------------------------- | ------------ | --------------------------- |
606
+ | `value` | `string[]` | `[]` | Selected values |
607
+ | `name` | `string` | `''` | Form field name |
608
+ | `disabled` | `boolean` | `false` | Disables all child controls |
609
+ | `required` | `boolean` | `false` | Required validation |
610
+ | `direction` | `'horizontal' \| 'vertical'` | `'vertical'` | Layout direction |
601
611
 
602
612
  **Events:** `input`, `change`
603
613
 
604
614
  **Slots:** `default` (project `<web-ui-checkbox>` elements)
605
615
 
606
- Syncs child checkbox `checked` state. `disabled` supplies inherited effective disabled state without changing a child's own `disabled` property. Listens to child `change` events.
616
+ Syncs child checkbox `checked` state. `disabled` supplies inherited effective disabled state without changing a child's own `disabled` property. Listens to child `change` events. An invalid `direction` value falls back to `vertical`.
617
+
618
+ ##### Tokens
619
+
620
+ | Token | Default | Description |
621
+ | -------------------------- | ------- | ----------------------------- |
622
+ | `--wui-checkbox-group-gap` | `8px` | Gap between member checkboxes |
623
+
624
+ Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares `8px` for its own subtree, so an override placed above a `<web-ui-theme>` element does not reach the group.
625
+
626
+ ```css
627
+ web-ui-checkbox-group {
628
+ --wui-checkbox-group-gap: 16px;
629
+ }
630
+ ```
607
631
 
608
632
  #### `<web-ui-radio-group>`
609
633
 
610
634
  Radio group managing single selection.
611
635
 
612
- | Attribute | Type | Default | Description |
613
- | ---------- | --------- | ------- | ---------------------------------------- |
614
- | `value` | `string` | `''` | Selected radio value |
615
- | `name` | `string` | `''` | Form field name (propagates to children) |
616
- | `disabled` | `boolean` | `false` | Disables all child controls |
617
- | `required` | `boolean` | `false` | Required validation |
636
+ ##### Attributes
637
+
638
+ | Attribute | Type | Default | Description |
639
+ | ----------- | ---------------------------- | ------------ | ---------------------------------------- |
640
+ | `value` | `string` | `''` | Selected radio value |
641
+ | `name` | `string` | `''` | Form field name (propagates to children) |
642
+ | `disabled` | `boolean` | `false` | Disables all child controls |
643
+ | `required` | `boolean` | `false` | Required validation |
644
+ | `direction` | `'horizontal' \| 'vertical'` | `'vertical'` | Layout direction |
618
645
 
619
646
  **Events:** `input`, `change`
620
647
 
621
648
  **Slots:** `default` (project `<web-ui-radio>` elements)
622
649
 
623
- `disabled` supplies inherited effective disabled state without changing a child's own `disabled` property.
650
+ `disabled` supplies inherited effective disabled state without changing a child's own `disabled` property. An invalid `direction` value falls back to `vertical`.
651
+
652
+ ##### Tokens
653
+
654
+ | Token | Default | Description |
655
+ | ----------------------- | ------- | ------------------------- |
656
+ | `--wui-radio-group-gap` | `8px` | Gap between member radios |
657
+
658
+ Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares `8px` for its own subtree, so an override placed above a `<web-ui-theme>` element does not reach the group.
659
+
660
+ ```css
661
+ web-ui-radio-group {
662
+ --wui-radio-group-gap: 16px;
663
+ }
664
+ ```
624
665
 
625
666
  ---
626
667
 
@@ -701,6 +742,8 @@ Modal dialog using native `<dialog>` with `showModal()`.
701
742
 
702
743
  Uses native `<dialog>` with `@cancel` prevention. Escape calls `close()` unless `no-escape-close` is present. Click on backdrop closes dialog unless `no-backdrop-close` is present. With `controlled`, both only emit the close request instead.
703
744
 
745
+ > **Escape ownership:** Escape is arbitrated by a single shared owner, so one keypress closes only the **innermost** open overlay (popover, select, autocomplete, dropdown, context-menu, drawer and dialog all take part). With a select open inside a drawer, the first Escape closes the select and only the second closes the drawer. Sibling overlays that do not nest fall back to open order, closing the most recently opened one. `image-preview` takes part as well: its native `<dialog>` is registered with the same arbiter, so Escape is decided by layer order; the component's `cancel` handler only vetoes the native instant close, keeping the top layer until the exit transition finishes.
746
+
704
747
  **CSS Custom Properties:**
705
748
 
706
749
  | Property | Default | Description |
@@ -714,7 +757,7 @@ Uses native `<dialog>` with `@cancel` prevention. Escape calls `close()` unless
714
757
  | `--wui-dialog-desc-gap` | `24px` | Spacing below the body content |
715
758
  | `--wui-dialog-footer-gap` | `10px` / `12px` (horizontal) | Spacing between footer buttons |
716
759
  | `--wui-dialog-footer-justify` | `flex-end` (default) / `center` (horizontal) | Footer `justify-content`; override to `flex-end` for right-aligned horizontal buttons |
717
- | `--wui-dialog-scale-enter` | `1.2` | Enter scale start: the panel shrinks in from `1.2` to `1`, and exit reverses it |
760
+ | `--wui-dialog-scale-enter` | `1.1` | Enter scale start: the panel shrinks in from `1.1` to `1`, and exit reverses it |
718
761
 
719
762
  #### `<web-ui-drawer>`
720
763
 
@@ -748,10 +791,18 @@ When `closable` is set, the built-in close button is positioned at the header's
748
791
  **Drag to close:** With `draggable`, a gray capsule drag bar (4×56px by default, visually centered 10px from the inner edge inside a 20px-thick hit zone) appears on the drawer's inner edge (left edge for `right`, right edge for `left`, bottom edge for `top`, top edge for `bottom`) while open:
749
792
 
750
793
  - Dragging follows the pointer in real time; the backdrop fades proportionally.
751
- - Releasing past ~1/3 of the drawer size, or with a fast closing flick, springs the drawer shut; otherwise it springs back open. The close direction is placement-aware.
752
- - With `controlled`, release past the threshold only emits `open-change(false)`; the drawer holds at the closed position briefly (120ms write-back window) and springs back open if the consumer rejects or misses the write-back.
794
+ - Backdrop click now only honors a genuine tap chain: the press must start on the backdrop itself and the press-to-release travel must stay within the tap magnitude. The click the browser generates for a press–drag–release lands on the common ancestor (`dialog`) regardless of where the press started — pressing on the panel content or on the backdrop itself and releasing over the backdrop used to close the drawer through that click. The component records the pointerdown origin and validates it on click, so such drag releases always rebound. A `detail`-0 click (keyboard / programmatic) never consumes the pointer record.
795
+ - Releasing with a net displacement past half the drawer size (floor of 10px), or with a fast closing flick, closes the drawer; otherwise it animates back open. The close direction is placement-aware, and each part of the decision matches Base UI's `useSwipeDismiss`:
796
+ - Both the displacement origin and the decision clock reset to the first `pointermove`, absorbing the gap between the press and that move; its whole travel is discarded, so a gesture needs at least two moves to accumulate displacement.
797
+ - The flick is judged by the **whole gesture's** average velocity — net displacement ÷ gesture duration, divisor floored at 50ms — reaching 500px/s, not by the instantaneous velocity at release. A sliding window only describes the last short stretch of the trace, so under a whole-gesture average sweeping back towards the edge can no longer read as a flick. A zero-length duration yields zero velocity instead of a floored divisor, so an unmeasurable gesture is never read as a flick.
798
+ - Any release whose net displacement isn't in the close direction animates back open.
799
+ - The distance test also uses the net displacement, so grabbing the drawer mid-rebound and releasing without dragging further never counts as "already past half way".
800
+ - A withdrawal of 10px or more from the point where the close direction was confirmed cancels the flick, so dragging out into the overscroll and sweeping back never closes. Displacement that has already crossed the distance threshold clears the mark again, so a gesture that is past half way is unaffected by a small retreat. Upstream has the same guard (`cancelledSwipe`), but never reaches it on the drawer path because `DrawerViewport.onRelease` always returns a decision.
801
+ - Dragging in the opening direction is damped by a square root (`sign(d) · |d| ** 0.5`), matching `applyDirectionalDamping`. The damping applies to the gesture's increment, added on top of the offset the drag started from, and self-limits rather than being capped at a fixed distance.
802
+ - The release velocity only shapes the settle transition's duration (180–420ms); the settle itself is a single CSS transition on `transform`, handed back by JS at release — no WAAPI animation is created.
803
+ - With `controlled`, release past the threshold only emits `open-change(false)`; the drawer holds at the closed position briefly (120ms write-back window) and animates back open if the consumer rejects or misses the write-back.
753
804
  - Drag-to-open is not supported because the closed drawer renders nothing outside the native dialog.
754
- - Under `prefers-reduced-motion`, release snaps instantly without spring animation.
805
+ - Under `prefers-reduced-motion`, release settles instantly, with no transition.
755
806
 
756
807
  **CSS Custom Properties:**
757
808
 
@@ -768,7 +819,7 @@ When `closable` is set, the built-in close button is positioned at the header's
768
819
  | `--wui-drawer-drag-bar-length` | `56px` | Drag bar capsule length (along the drawer edge) |
769
820
  | `--wui-drawer-header-padding` | `16px 20px` | Header section padding |
770
821
  | `--wui-drawer-close-top` | `16px` | Built-in close button offset from the top of the header |
771
- | `--wui-drawer-close-right` | `20px` | Built-in close button offset from the right edge of the drawer |
822
+ | `--wui-drawer-close-right` | `16px` | Built-in close button offset from the right edge of the drawer |
772
823
  | `--wui-drawer-content-padding` | `20px` | Content area padding; also drives drag bar visual center (half value) |
773
824
  | `--wui-drawer-footer-padding` | `16px 20px` | Footer section padding |
774
825
 
@@ -1228,10 +1279,10 @@ At `640px` and below, the sidebar becomes a `web-ui-drawer` with its built-in gl
1228
1279
 
1229
1280
  **CSS Custom Properties:**
1230
1281
 
1231
- | Property | Default | Description |
1232
- | ---------------------------------- | --------------------------------- | ------------------------------------------------ |
1233
- | `--wui-layout-sidebar-radius` | `var(--wui-radius-overlay, 28px)` | Border radius of sidebar card (desktop & mobile) |
1234
- | `--wui-layout-mobile-toggle-inset` | `8px` | Left inset of the mobile header toggle |
1282
+ | Property | Default | Description |
1283
+ | ---------------------------------- | ------- | ------------------------------------------------ |
1284
+ | `--wui-layout-sidebar-radius` | `24px` | Border radius of sidebar card (desktop & mobile) |
1285
+ | `--wui-layout-mobile-toggle-inset` | `8px` | Left inset of the mobile header toggle |
1235
1286
 
1236
1287
  #### `<web-ui-back-top>`
1237
1288
 
@@ -1265,20 +1316,25 @@ Role: `button`, keyboard Enter scrolls to top.
1265
1316
 
1266
1317
  #### `<web-ui-svg-draw-lines>`
1267
1318
 
1268
- SVG line drawing animation using `stroke-dashoffset`. Animates geometry in-place — no cloning, no DOM manipulation.
1319
+ SVG line drawing animation using `stroke-dashoffset`. Animates geometry in-place — no cloning, no DOM manipulation. Two playback directions: the reveal draws geometry from nothing to fully drawn, `replay({ reverse: true })` retracts it along the same path back to nothing.
1269
1320
 
1270
- | Attribute | Type | Default | Description |
1271
- | ---------- | -------- | ---------- | ------------------------------------------------- |
1272
- | `duration` | `number` | `1000` | Animation duration in ms, clamped to `[0, 30000]` |
1273
- | `easing` | `string` | `'linear'` | CSS easing function passed to `element.animate()` |
1321
+ | Attribute | Type | Default | Description |
1322
+ | ------------- | --------- | ---------- | ------------------------------------------------------ |
1323
+ | `duration` | `number` | `1000` | Animation duration in ms, clamped to `[0, 30000]` |
1324
+ | `easing` | `string` | `'linear'` | CSS easing function passed to `element.animate()` |
1325
+ | `no-autoplay` | `boolean` | `false` | Skip the automatic playback when content first appears |
1274
1326
 
1275
- Both attributes are reflected.
1327
+ All attributes are reflected.
1276
1328
 
1277
- **Methods:** `replay(): Promise<void>` — cancels running animation, re-collects geometry elements from current DOM, and starts a new animation. All targets animate in parallel with the same duration/easing. Resolves when all complete. Returns immediately without animation when the nearest theme scope uses `motion="reduced"`, or when its `motion="system"` mode matches `prefers-reduced-motion: reduce`.
1329
+ **Auto-play:** the first time slotted content settles, one reveal plays by itself. Set `no-autoplay` to leave the geometry as authored and decide when to play — that is how `<web-ui-checkbox>` avoids drawing a checkmark that was already checked on mount.
1330
+
1331
+ **Methods:** `replay(options?: { reverse?: boolean }): Promise<void>` — cancels running animation, re-collects geometry elements from current DOM, and starts a new animation. All targets animate in parallel with the same duration/easing. Resolves when all complete. A reverse run ends on blank and leaves that end state in the DOM: the animation itself is cancelled, while the dash values that hide the stroke stay as inline styles until the next `replay()` replaces or undoes them. A reveal run restores the authored inline styles instead. A retract always starts from the full stroke, so interrupting a reveal draws the whole path back rather than only the part that was visible. When the nearest theme scope uses `motion="reduced"`, or its `motion="system"` mode matches `prefers-reduced-motion: reduce`, no animation starts — but a previous retract's blank is still undone, so the geometry returns to as authored and visibility is left entirely to the consumer's own styles.
1332
+
1333
+ **Reverse end state:** the blank a retract leaves behind is inline style on the geometry it animated, including nodes inside nested open shadow roots such as `<web-ui-icon>`. Copying those nodes (`cloneNode`, an `innerHTML` round-trip) carries the blank along, and detaching and re-appending one keeps the stroke hidden — a finished retract is a DOM state rather than a live animation, so nothing is left to restore it. Call `replay()` to draw them again. Because the component reaches into nested open shadow roots, two `<web-ui-svg-draw-lines>` must not cover the same geometry: each records the inline dash values the first time it animates an element, so a blank left by the inner one becomes the outer one's idea of the consumer's own value.
1278
1334
 
1279
1335
  **Slots:** `default` — SVG content to animate. Accepts inline `<svg>` elements (light DOM) as well as components that render an SVG in an open shadow root, such as `<web-ui-icon>`. Closed shadow roots are skipped.
1280
1336
 
1281
- Finds `path`, `rect`, `circle`, `line`, `polyline`, `polygon`, `ellipse` elements by recursively traversing the light DOM and all open shadow roots. Paths ending with `Z`/`z` receive a temporary gap fix for proper closing-segment rendering. After animation completes or is cancelled, all in-line styles are restored.
1337
+ Finds `path`, `rect`, `circle`, `line`, `polyline`, `polygon`, `ellipse` elements by recursively traversing the light DOM and all open shadow roots. Paths ending with `Z`/`z` receive a temporary gap fix for proper closing-segment rendering. After a reveal completes or is cancelled, all in-line styles are restored.
1282
1338
 
1283
1339
  #### `<web-ui-theme>`
1284
1340
 
@@ -1288,6 +1344,7 @@ Theme provider defining CSS custom property tokens.
1288
1344
  | ------------ | --------------------------------- | ---------- | --------------------------------------------- |
1289
1345
  | `appearance` | `'light' \| 'dark' \| 'system'` | `'light'` | Color scheme |
1290
1346
  | `motion` | `'full' \| 'reduced' \| 'system'` | `'system'` | Motion preference for this nested theme scope |
1347
+ | `transition` | `boolean` | `false` | Circular reveal on appearance changes |
1291
1348
 
1292
1349
  **Methods:** `getOverlayRoot()` — returns this theme-owned overlay root
1293
1350
 
@@ -1295,6 +1352,8 @@ Theme provider defining CSS custom property tokens.
1295
1352
 
1296
1353
  Defines foundation, color, layer, shadow, and motion tokens for its subtree. `motion="system"` follows `prefers-reduced-motion`; use `motion="reduced"` to reduce animation in a scope or `motion="full"` in a nested theme to restore normal token values. System appearance follows `prefers-color-scheme`.
1297
1354
 
1355
+ Set the `transition` boolean attribute to animate appearance changes with the View Transitions API. Removing the attribute disables the reveal; framework bindings must write the boolean property when toggling it dynamically. A root theme reveals the whole page; a nested theme reveals only its own capture box. The origin is the last pointer-down position when available, otherwise the theme box or viewport center. Dark-to-light and light-to-dark directions are reversed. Unsupported browsers, reduced-motion scopes, zero durations, and another request already in the same flight fall back to applying the new appearance immediately; `appearance="system"` does not animate OS light/dark changes in this version.
1356
+
1298
1357
  The host uses `display: contents` and does not paint any background: the library never draws on the host page, so the embedding application keeps full control of the surface behind the themed subtree. Custom properties still inherit to slotted content reliably.
1299
1358
 
1300
1359
  **Foundation tokens:**
@@ -1307,6 +1366,14 @@ The host uses `display: contents` and does not paint any background: the library
1307
1366
  | `--wui-overlay-min-width` | `200px` | Minimum anchored overlay width |
1308
1367
  | `--wui-focus-ring-width` | `3px` | Focus indicator width |
1309
1368
 
1369
+ **Selection control tokens (radio, checkbox):**
1370
+
1371
+ | Property | Default | Description |
1372
+ | ------------------------------ | ------- | ----------------------------------------------- |
1373
+ | `--wui-selection-control-size` | `18px` | Indicator (circle / box) width and height |
1374
+ | `--wui-radio-group-gap` | `8px` | Member spacing inside `<web-ui-radio-group>` |
1375
+ | `--wui-checkbox-group-gap` | `8px` | Member spacing inside `<web-ui-checkbox-group>` |
1376
+
1310
1377
  **Radius tokens:**
1311
1378
 
1312
1379
  | Property | Default | Description |
@@ -1331,7 +1398,7 @@ The host uses `display: contents` and does not paint any background: the library
1331
1398
  | `--wui-layer-toast` | `200` | Toasts |
1332
1399
  | `--wui-layer-loading` | `300` | Blocking loading surfaces |
1333
1400
 
1334
- **Motion tokens:** duration defaults are `--wui-duration-press: 80ms`, `--wui-duration-feedback: 100ms`, `--wui-duration-trigger: 160ms`, `--wui-duration-focus: 200ms`, `--wui-duration-float-enter: 240ms`, `--wui-duration-float-exit: 160ms`, `--wui-duration-dialog-enter: 320ms`, `--wui-duration-dialog-exit: 260ms`, `--wui-duration-drawer-enter: 280ms`, `--wui-duration-drawer-exit: 240ms`, `--wui-duration-drawer-nested: 450ms`, `--wui-duration-toast-enter: 280ms`, `--wui-duration-toast-exit: 200ms`, `--wui-duration-collapse-enter: 200ms`, `--wui-duration-collapse-exit: 160ms`, `--wui-duration-layout: 200ms`, `--wui-duration-swipe-settle: 220ms` (image preview carousel settle), `--wui-duration-spin: 600ms` (icon rotation) and `--wui-duration-spinner: 800ms` (spinner leaf cycle). Easing tokens are `--wui-ease-enter`, `--wui-ease-dialog` (`cubic-bezier(0.2, 0, 0, 1)`), `--wui-ease-slide`, `--wui-ease-float` (`cubic-bezier(0.4, 0.38, 0.2, 1)`, a no-bounce spring fit used by anchored floating panels), and `--wui-ease-swipe` (`cubic-bezier(0.32, 0.72, 0, 1)`, the decelerating tail of the carousel settle). Enter scale tokens are `--wui-scale-enter: 0.95` (floating panels and the image preview's image surface grow in) and `--wui-dialog-scale-enter: 1.2` (the dialog shrinks in from above, reversed on exit). Under `motion="reduced"` every transition duration collapses to `0ms`, the carousel settle with them, while the two infinite loading loops keep running at a slower period (`--wui-duration-spin` / `--wui-duration-spinner`: `600ms` / `800ms` → `1600ms`). A frozen loading indicator reads as a hung UI. Hover/active background feedback switches instantly with no transition; checked, pressed and focus states do transition, and those durations collapse to 0ms under reduced motion along with the rest.
1401
+ **Motion tokens:** duration defaults are `--wui-duration-press: 80ms`, `--wui-duration-feedback: 100ms`, `--wui-duration-trigger: 160ms`, `--wui-duration-focus: 200ms`, `--wui-duration-float-enter: 240ms`, `--wui-duration-float-exit: 160ms`, `--wui-duration-dialog-enter: 320ms`, `--wui-duration-dialog-exit: 260ms`, `--wui-duration-drawer-enter: 280ms`, `--wui-duration-drawer-exit: 240ms`, `--wui-duration-drawer-nested: 450ms`, `--wui-duration-toast-enter: 280ms`, `--wui-duration-toast-exit: 200ms`, `--wui-duration-collapse-enter: 200ms`, `--wui-duration-collapse-exit: 160ms`, `--wui-duration-layout: 200ms`, `--wui-duration-swipe-settle: 220ms` (image preview carousel settle), `--wui-duration-spin: 600ms` (icon rotation), `--wui-duration-spinner: 800ms` (spinner leaf cycle) and `--wui-theme-transition-duration: 500ms` (theme appearance reveal). Easing tokens are `--wui-ease-enter`, `--wui-ease-dialog` (`cubic-bezier(0.2, 0, 0, 1)`), `--wui-ease-slide`, `--wui-ease-float` (`cubic-bezier(0.4, 0.38, 0.2, 1)`, a no-bounce spring fit used by anchored floating panels), `--wui-ease-swipe` (`cubic-bezier(0.32, 0.72, 0, 1)`, the decelerating tail of the carousel settle) and `--wui-theme-transition-easing: ease-in`. Enter scale tokens are `--wui-scale-enter: 0.95` (floating panels and the image preview's image surface grow in) and `--wui-dialog-scale-enter: 1.1` (the dialog shrinks in from above, reversed on exit). Under `motion="reduced"` every transition duration collapses to `0ms`, the carousel settle with them, while the two infinite loading loops keep running at a slower period (`--wui-duration-spin` / `--wui-duration-spinner`: `600ms` / `800ms` → `1600ms`). A frozen loading indicator reads as a hung UI. Hover/active background feedback switches instantly with no transition; checked, pressed and focus states do transition, and those durations collapse to 0ms under reduced motion along with the rest.
1335
1402
 
1336
1403
  **Color tokens:**
1337
1404
 
@@ -1358,8 +1425,8 @@ The host uses `display: contents` and does not paint any background: the library
1358
1425
  | `--wui-color-border` | `rgb(0 0 0 / 0.1)` | `rgb(255 255 255 / 0.14)` | Normal border and divider |
1359
1426
  | `--wui-color-glass-border` | `transparent` | `rgb(255 255 255 / 0.05)` | Glass border tint |
1360
1427
  | `--wui-color-glass-highlight` | `rgb(255 255 255 / 0.9)` | `rgb(255 255 255 / 0.1)` | Glass edge highlight |
1361
- | `--wui-color-glass-corner` | `rgb(255 255 255 / 0.08)` | `rgb(255 255 255 / 0.2)` | Glass corner sheen on border |
1362
- | `--wui-color-glass-shade` | `rgb(0 0 0 / 0.06)` | `rgb(0 0 0 / 0.5)` | Glass corner shade on border |
1428
+ | `--wui-color-glass-corner` | `rgb(255 255 255 / 0.5)` | `rgb(255 255 255 / 0.2)` | Glass corner sheen on border |
1429
+ | `--wui-color-glass-shade` | `rgb(0 0 0 / 0.03)` | `rgb(0 0 0 / 0.5)` | Glass corner shade on border |
1363
1430
  | `--wui-color-accent` | `#08f` | `#0a84ff` | Accent and focused input border |
1364
1431
  | `--wui-color-on-accent` | `#fff` | `#fff` | Foreground on accent |
1365
1432
  | `--wui-color-on-control` | `#fff` | `#f2f2f7` | Foreground on control inner |
@@ -1410,29 +1477,56 @@ const id = toast({ message: 'Custom', type: 'info', position: 'bottom-right', du
1410
1477
  toast.close(id)
1411
1478
  toast.clear()
1412
1479
 
1413
- // Update visible content without resetting auto-close timing
1414
- toast.updateMessage(id, { message: 'Upload 60% complete', heading: 'Uploading' })
1480
+ // Upsert: calling again with the same id updates that one toast instead of adding another
1481
+ toast.error('Network lost, retrying…', { id: 'network' })
1482
+ toast.error('Network lost (retry 2)', { id: 'network' })
1483
+
1484
+ // One toast through its whole lifecycle
1485
+ toast.info('Uploading 0%', { id: 'upload', duration: 0 })
1486
+ toast.info('Uploading 60%', { id: 'upload' }) // duration omitted: timer keeps running
1487
+ toast.success('Upload complete', { id: 'upload', duration: 3000 }) // new type, timer restarts
1415
1488
  ```
1416
1489
 
1490
+ Every call returns the final id of that toast, so callers never branch on create vs. update.
1491
+
1417
1492
  **ToastOptions:**
1418
1493
 
1419
- | Option | Type | Default | Description |
1420
- | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------- |
1421
- | `message` | `string` | — | Notification text |
1422
- | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
1423
- | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
1424
- | `closable` | `boolean` | `true` | Show close button |
1425
- | `id` | `string` | auto | Deduplication identifier |
1426
- | `heading` | `string` | `''` | Bold heading text |
1427
- | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
1428
- | `target` | `Element` | — | Used to find nearest theme-owned overlay root |
1429
- | `container` | `HTMLElement` | — | Explicit mount container (highest priority) |
1494
+ | Option | Type | Default | Description |
1495
+ | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | ----------------------------------------------- |
1496
+ | `message` | `string` | — | Notification text |
1497
+ | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
1498
+ | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
1499
+ | `closable` | `boolean` | `true` | Show close button |
1500
+ | `id` | `string` | auto | Merge key: calls sharing an id update one toast |
1501
+ | `heading` | `string` | `''` | Bold heading text |
1502
+ | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
1503
+ | `target` | `Element` | — | Used to find nearest theme-owned overlay root |
1504
+ | `container` | `HTMLElement` | — | Explicit mount container (highest priority) |
1505
+
1506
+ **Upsert semantics** — what a second call with the same `id` does:
1507
+
1508
+ | Target state | Result |
1509
+ | ----------------------------- | ------------------------------------------------------------------------------- |
1510
+ | Mounted | Same id returned; supplied fields overwrite, omitted ones keep their value |
1511
+ | Still queued in the same tick | The queued options are patched and applied on mount; no second toast is created |
1512
+ | Closed, or still exiting | A new toast is created; the one leaving finishes its exit animation on its own |
1513
+
1514
+ `duration` restarts the countdown only when it is passed explicitly (the one exception is a hover pause: the new value is recorded without starting the timer, and the countdown runs with it once the pointer leaves); `message`, `heading` and `type` are plain properties and leave the timer alone. The `error` shortcut's 5000 ms default is applied when the toast is created, so it does not count as an explicit `duration` — repeated `toast.error(msg, { id })` calls do not reset the countdown. Changing `position` moves the element to the new container and keeps the remaining time (via `moveBefore` where available, with a pause/resume fallback elsewhere); if the countdown already elapsed while the main thread was blocked, the fallback closes the toast on resume instead of leaving it open. `container` and `target` are read from the first call only — moving a toast to a different overlay root is not supported.
1515
+
1516
+ **Close semantics** — `toast.close(id)` covers every state a toast can be in, including the two before it becomes visible:
1517
+
1518
+ | Target state | Result |
1519
+ | ---------------------------------- | --------------------------------------------------------------- |
1520
+ | Visible | Plays the exit animation, then emits `toast-close` |
1521
+ | Mounted, `show()` has not run yet | No exit animation to play: emits `toast-close` immediately |
1522
+ | Still queued in the same microtask | Dropped before it mounts — nothing appears, no event is emitted |
1523
+ | Already exiting | No-op; that toast finishes its own exit |
1430
1524
 
1431
- **`toast.updateMessage(id, options)`** updates the visible toast's `message` and, when supplied, `heading`. It does not restart the auto-close timer. `options` is `ToastMessageUpdateOptions`: `{ message: string; heading?: string }`.
1525
+ `toast.clear()` has the same scope: it drops queued entries and closes everything mounted.
1432
1526
 
1433
1527
  **Events:** `toast-close` (`CustomEvent<{ id: string; reason: 'auto' | 'manual' | 'programmatic' | 'clear' }>`)
1434
1528
 
1435
- Hover pauses auto-close timer (uses `pointerenter`/`pointerleave`). Batch-mounts toasts created in the same microtask.
1529
+ Hover pauses the auto-close timer (uses `pointerenter`/`pointerleave`); leaving resumes the **remaining** time instead of restarting the full duration. If the `pointerleave` is missed while paused — the pointer is dragged out of the window, or the element is moved or removed while hovered — a document-level `pointerover`/`pointerout`/`pointerleave` fallback resumes the countdown, so a hovered toast cannot stay on screen forever. Batch-mounts toasts created in the same microtask.
1436
1530
 
1437
1531
  **CSS Custom Properties:**
1438
1532
 
@@ -1,5 +1,5 @@
1
1
  //#region src/assets/glass.css?inline
2
- var e = ".wui-glass{--wui-internal-glass-shadow:var(--wui-shadow-glass,0 0 1px #0000000d, 0 0 8px #00000008, 0 2px 16px #00000006, 0 8px 32px #00000004);background-color:var(--wui-color-surface-glass,#fafafa57);-webkit-backdrop-filter:blur(4px) saturate(160%) brightness(var(--wui-glass-brightness,1.06));backdrop-filter:blur(4px) saturate(160%) brightness(var(--wui-glass-brightness,1.06));box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 38%, transparent), inset 0 1px .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 52%, transparent), inset 0 -.5px 1px #00000008, var(--wui-internal-glass-shadow,var(--wui-shadow-glass,0 0 1px #0000000d, 0 0 8px #00000008, 0 2px 16px #00000006, 0 8px 32px #00000004));background-clip:padding-box;border:none;position:relative}.wui-glass:before{pointer-events:none;content:\"\";border-radius:inherit;background-color:var(--wui-color-glass-border,transparent);background-image:radial-gradient(var(--wui-glass-corner-radius,32px) at 100% 0, var(--wui-color-glass-shade,#0000000f), transparent), radial-gradient(var(--wui-glass-corner-radius,32px) at 0 100%, var(--wui-color-glass-shade,#0000000f), transparent), radial-gradient(calc(var(--wui-glass-corner-radius,32px) * 2) at 0 0, var(--wui-color-glass-corner,#ffffff14), transparent), radial-gradient(calc(var(--wui-glass-corner-radius,32px) * 2) at 100% 100%, var(--wui-color-glass-corner,#ffffff14), transparent);-webkit-mask-composite:xor;-webkit-mask-source-type:auto,auto;padding:1px;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);-webkit-mask-position:0 0,0 0;mask-position:0 0,0 0;-webkit-mask-size:auto,auto;mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}";
2
+ var e = ".wui-glass{--wui-internal-glass-shadow:var(--wui-shadow-glass,0 0 1px #00000005, 0 0 8px #00000008, 0 2px 16px #00000006, 0 8px 32px #00000004);background-color:var(--wui-color-surface-glass,#fafafa57);-webkit-backdrop-filter:blur(4px) saturate(160%) brightness(var(--wui-glass-brightness,1.06));backdrop-filter:blur(4px) saturate(160%) brightness(var(--wui-glass-brightness,1.06));box-shadow:inset 0 0 0 .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 38%, transparent), inset 0 1px .5px color-mix(in srgb, var(--wui-color-glass-highlight,#ffffffe6) 52%, transparent), inset 0 -.5px 1px #00000008, var(--wui-internal-glass-shadow,var(--wui-shadow-glass,0 0 1px #00000005, 0 0 8px #00000008, 0 2px 16px #00000006, 0 8px 32px #00000004));background-clip:padding-box;border:none;position:relative}.wui-glass:before{pointer-events:none;content:\"\";border-radius:inherit;background-color:var(--wui-color-glass-border,transparent);background-image:radial-gradient(var(--wui-glass-corner-radius,32px) at 100% 0, var(--wui-color-glass-shade,#00000008), transparent), radial-gradient(var(--wui-glass-corner-radius,32px) at 0 100%, var(--wui-color-glass-shade,#00000008), transparent), radial-gradient(calc(var(--wui-glass-corner-radius,32px) * 2) at 0 0, var(--wui-color-glass-corner,#ffffff80), transparent), radial-gradient(calc(var(--wui-glass-corner-radius,32px) * 2) at 100% 100%, var(--wui-color-glass-corner,#ffffff80), transparent);-webkit-mask-composite:xor;-webkit-mask-source-type:auto,auto;padding:1px;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);mask-image:linear-gradient(#000 0 0),linear-gradient(#000 0 0);-webkit-mask-position:0 0,0 0;mask-position:0 0,0 0;-webkit-mask-size:auto,auto;mask-size:auto,auto;-webkit-mask-repeat:repeat,repeat;mask-repeat:repeat,repeat;-webkit-mask-clip:content-box,border-box;mask-clip:content-box,border-box;-webkit-mask-origin:content-box,border-box;mask-origin:content-box,border-box;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-source-type:auto,auto;mask-mode:match-source,match-source}";
3
3
  //#endregion
4
4
  export { e as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"glass.js","names":[],"sources":["../../src/assets/glass.css?inline"],"sourcesContent":["/*\n * 玻璃公共样式\n */\n\n.wui-glass {\n /*\n * 内部阴影变量在 glass 元素上自声明默认值,切断从祖先 glass 容器(drawer/dialog\n * body 等声明了 overlay 阴影的元素)沿 flattened tree 继承进 slotted 内容的\n * 通道;各组件在自身元素上的同变量配置(特异性/源顺序恒胜出)不受影响。\n */\n --wui-internal-glass-shadow: var(\n --wui-shadow-glass,\n 0 0 1px rgb(0 0 0 / 0.05),\n 0 0 8px rgb(0 0 0 / 0.03),\n 0 2px 16px rgb(0 0 0 / 0.025),\n 0 8px 32px rgb(0 0 0 / 0.015)\n );\n\n position: relative;\n\n border: none;\n\n background-color: var(--wui-color-surface-glass, rgb(250 250 250 / 0.34));\n background-clip: padding-box;\n\n /* Safari 16.4 只支持带 -webkit- 前缀的 backdrop-filter(无前缀 18 才支持);\n 前缀由 lightningcss 按 ?inline 管线默认 targets(含 ios16.4)自动生成,无需手写。 */\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n box-shadow:\n inset 0 0 0 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 38%, transparent),\n inset 0 1px 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 52%, transparent),\n inset 0 -0.5px 1px rgb(0 0 0 / 0.03),\n var(\n --wui-internal-glass-shadow,\n var(\n --wui-shadow-glass,\n 0 0 1px rgb(0 0 0 / 0.05),\n 0 0 8px rgb(0 0 0 / 0.03),\n 0 2px 16px rgb(0 0 0 / 0.025),\n 0 8px 32px rgb(0 0 0 / 0.015)\n )\n );\n}\n\n/*\n * 玻璃描边:底环 + 对角受光/背光。background-color 用 border 色铺满整圈(含圆角\n * 弧段,修复胶囊弧段无着色发暗的问题);叠两枚 corner→transparent radial 作左上/\n * 右下受光光泽,再叠两枚 shade→transparent radial(置于最上层)压暗右上/左下背光\n * 角,形成对角光影的玻璃立体感。四枚渐变均为锚定角点、半径 --wui-glass-corner-radius\n * 的固定物理尺寸正圆——视觉覆盖范围不随元素宽高缩放(百分比方案在大面板上暗边\n * 过长、小控件上过弱),各组件用与自身 border-radius 同源的值覆盖该变量即可联动。\n * 颜色由 theme 按明暗主题提供 --wui-color-glass-corner / --wui-color-glass-shade /\n * --wui-color-glass-border;-webkit-mask-* 旧语法(含 mask-composite:xor)由\n * lightningcss 按 targets 自动生成,无前缀 mask-composite 留给 Chrome 120+。\n */\n.wui-glass::before {\n pointer-events: none;\n content: '';\n\n position: absolute;\n inset: 0;\n\n padding: 1px;\n border-radius: inherit;\n\n background-color: var(--wui-color-glass-border, transparent);\n background-image:\n radial-gradient(\n var(--wui-glass-corner-radius, 32px) at 100% 0,\n var(--wui-color-glass-shade, rgb(0 0 0 / 0.06)),\n transparent\n ),\n radial-gradient(\n var(--wui-glass-corner-radius, 32px) at 0 100%,\n var(--wui-color-glass-shade, rgb(0 0 0 / 0.06)),\n transparent\n ),\n radial-gradient(\n calc(var(--wui-glass-corner-radius, 32px) * 2) at 0 0,\n var(--wui-color-glass-corner, rgb(255 255 255 / 0.08)),\n transparent\n ),\n radial-gradient(\n calc(var(--wui-glass-corner-radius, 32px) * 2) at 100% 100%,\n var(--wui-color-glass-corner, rgb(255 255 255 / 0.08)),\n transparent\n );\n\n mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n mask-composite: exclude;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"glass.js","names":[],"sources":["../../src/assets/glass.css?inline"],"sourcesContent":[".wui-glass {\n /*\n * 内部阴影变量在 glass 元素上自声明默认值,切断从祖先 glass 容器(drawer/dialog\n * body 等声明了 overlay 阴影的元素)沿 flattened tree 继承进 slotted 内容的\n * 通道;各组件在自身元素上的同变量配置(特异性/源顺序恒胜出)不受影响。\n */\n --wui-internal-glass-shadow: var(\n --wui-shadow-glass,\n 0 0 1px rgb(0 0 0 / 0.02),\n 0 0 8px rgb(0 0 0 / 0.03),\n 0 2px 16px rgb(0 0 0 / 0.025),\n 0 8px 32px rgb(0 0 0 / 0.015)\n );\n\n position: relative;\n\n border: none;\n\n background-color: var(--wui-color-surface-glass, rgb(250 250 250 / 0.34));\n background-clip: padding-box;\n\n /* Safari 16.4 只支持带 -webkit- 前缀的 backdrop-filter(无前缀 18 才支持);\n 前缀由 lightningcss 按 ?inline 管线默认 targets(含 ios16.4)自动生成,无需手写。 */\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n box-shadow:\n inset 0 0 0 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 38%, transparent),\n inset 0 1px 0.5px color-mix(in srgb, var(--wui-color-glass-highlight, rgb(255 255 255 / 0.9)) 52%, transparent),\n inset 0 -0.5px 1px rgb(0 0 0 / 0.03),\n var(\n --wui-internal-glass-shadow,\n var(\n --wui-shadow-glass,\n 0 0 1px rgb(0 0 0 / 0.02),\n 0 0 8px rgb(0 0 0 / 0.03),\n 0 2px 16px rgb(0 0 0 / 0.025),\n 0 8px 32px rgb(0 0 0 / 0.015)\n )\n );\n}\n\n/*\n * 玻璃描边:底环 + 对角受光/背光。background-color 用 border 色铺满整圈(含圆角\n * 弧段,修复胶囊弧段无着色发暗的问题);叠两枚 corner→transparent radial 作左上/\n * 右下受光光泽,再叠两枚 shade→transparent radial(置于最上层)压暗右上/左下背光\n * 角,形成对角光影的玻璃立体感。四枚渐变均为锚定角点、半径 --wui-glass-corner-radius\n * 的固定物理尺寸正圆——视觉覆盖范围不随元素宽高缩放(百分比方案在大面板上暗边\n * 过长、小控件上过弱),各组件用与自身 border-radius 同源的值覆盖该变量即可联动。\n * 颜色由 theme 按明暗主题提供 --wui-color-glass-corner / --wui-color-glass-shade /\n * --wui-color-glass-border;-webkit-mask-* 旧语法(含 mask-composite:xor)由\n * lightningcss 按 targets 自动生成,无前缀 mask-composite 留给 Chrome 120+。\n */\n.wui-glass::before {\n pointer-events: none;\n content: '';\n\n position: absolute;\n inset: 0;\n\n padding: 1px;\n border-radius: inherit;\n\n background-color: var(--wui-color-glass-border, transparent);\n background-image:\n radial-gradient(\n var(--wui-glass-corner-radius, 32px) at 100% 0,\n var(--wui-color-glass-shade, rgb(0 0 0 / 0.03)),\n transparent\n ),\n radial-gradient(\n var(--wui-glass-corner-radius, 32px) at 0 100%,\n var(--wui-color-glass-shade, rgb(0 0 0 / 0.03)),\n transparent\n ),\n radial-gradient(\n calc(var(--wui-glass-corner-radius, 32px) * 2) at 0 0,\n var(--wui-color-glass-corner, rgb(255 255 255 / 0.5)),\n transparent\n ),\n radial-gradient(\n calc(var(--wui-glass-corner-radius, 32px) * 2) at 100% 100%,\n var(--wui-color-glass-corner, rgb(255 255 255 / 0.5)),\n transparent\n );\n\n mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n mask-composite: exclude;\n}\n"],"mappings":""}
@@ -0,0 +1,6 @@
1
+ //#region src/assets/selection-control.css?inline
2
+ var e = "*,:before,:after{box-sizing:border-box}:host{cursor:default;vertical-align:middle;align-items:center;display:inline-flex}:host([disabled]){cursor:not-allowed}:where(.wui-radio,.wui-checkbox){cursor:inherit;-webkit-user-select:none;user-select:none;font-size:var(--wui-font-size,14px);color:var(--wui-color-text,#1b1b1b);align-items:center;gap:10px;line-height:1;display:inline-flex}:where(.wui-radio,.wui-checkbox):focus-visible{border-radius:var(--wui-radius-control,calc(infinity * 1px));outline:var(--wui-focus-ring-width,3px) solid var(--wui-color-focus-ring,#08f6);outline-offset:2px}:host([data-wui-pointer-focus]) :where(.wui-radio,.wui-checkbox):focus-visible{outline-color:#0000}:where(.wui-radio.is-disabled,.wui-checkbox.is-disabled){cursor:not-allowed;color:var(--wui-color-text-secondary,#6a6a6a);opacity:.4}:where(.wui-radio-circle,.wui-checkbox-box){isolation:isolate;width:var(--wui-selection-control-size,18px);height:var(--wui-selection-control-size,18px);background-color:var(--wui-color-surface-control,#dfdfdf);transition:background-color var(--wui-duration-trigger,.16s) ease;flex-shrink:0;justify-content:center;align-items:center;display:flex;position:relative}:where(.wui-radio-circle,.wui-checkbox-box):before{content:\"\";z-index:-1;border-radius:inherit;position:absolute;inset:0}:where(.wui-radio-label,.wui-checkbox-label){pointer-events:none}@media (hover:hover) and (pointer:fine){:where(.wui-radio,.wui-checkbox):hover:not(.is-disabled,.is-checked) :where(.wui-radio-circle,.wui-checkbox-box):before{background-color:color-mix(in srgb, var(--wui-color-text,#1b1b1b) 6%, transparent)}}";
3
+ //#endregion
4
+ export { e as default };
5
+
6
+ //# sourceMappingURL=selection-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-control.js","names":[],"sources":["../../src/assets/selection-control.css?inline"],"sourcesContent":["/*\n * radio / checkbox 共享外壳。\n *\n * 指示器只有 18px。宿主一旦走 inline-block,页面行高会在宿主内生成匿名行盒,把盒子撑到\n * 行高(Tailwind preflight 下是 24px),且多出的空隙落在哪一侧取决于内部基线落点,radio 与\n * checkbox 因此一个贴顶一个贴底。改成 inline-flex 后宿主高度由内容决定,行高不再参与,\n * 与相邻文字的对齐交给 vertical-align 显式声明。\n */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n cursor: default;\n display: inline-flex;\n align-items: center;\n vertical-align: middle;\n}\n\n:host([disabled]) {\n cursor: not-allowed;\n}\n\n/* :where() 保持零特异性,组件级配置(圆角、选中色、内部图形)始终覆盖共享层。 */\n:where(.wui-radio, .wui-checkbox) {\n cursor: inherit;\n user-select: none;\n\n display: inline-flex;\n gap: 10px;\n align-items: center;\n\n font-size: var(--wui-font-size, 14px);\n line-height: 1;\n color: var(--wui-color-text, #1b1b1b);\n\n &:focus-visible {\n border-radius: var(--wui-radius-control, calc(infinity * 1px));\n outline: var(--wui-focus-ring-width, 3px) solid var(--wui-color-focus-ring, rgb(0 136 255 / 0.4));\n outline-offset: 2px;\n }\n}\n\n:host([data-wui-pointer-focus]) :where(.wui-radio, .wui-checkbox):focus-visible {\n outline-color: transparent;\n}\n\n:where(.wui-radio.is-disabled, .wui-checkbox.is-disabled) {\n cursor: not-allowed;\n color: var(--wui-color-text-secondary, #6a6a6a);\n opacity: 0.4;\n}\n\n/*\n * 指示器底色取控件档。surface-raised 在深色下(#2c2c2e)离页面底色(#242628)只有 8 个\n * 亮度点,未选中时几乎看不出有一个圆/方框;surface-control 才是主题里「控件自己的底」\n * 那一档(中性按钮同档),深色比页面高 22 个亮度点,浅色下也从近白的 #f2f2f7 变成可读的 #dfdfdf。\n */\n:where(.wui-radio-circle, .wui-checkbox-box) {\n isolation: isolate;\n position: relative;\n\n display: flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n\n width: var(--wui-selection-control-size, 18px);\n height: var(--wui-selection-control-size, 18px);\n\n background-color: var(--wui-color-surface-control, #dfdfdf);\n\n transition: background-color var(--wui-duration-trigger, 160ms) ease;\n}\n\n/*\n * 状态层走覆盖层,不占用指示器自己的 background-color:同一个属性没法一半过渡一半即时,\n * 而这里要的是 hover 当帧切换、选中态的 accent 按 --wui-duration-trigger 淡入淡出。\n * z-index: -1 配父级 isolation 把覆盖层夹在控件底与圆点/勾之间,半径跟着指示器走。\n */\n:where(.wui-radio-circle, .wui-checkbox-box)::before {\n content: '';\n\n position: absolute;\n z-index: -1;\n inset: 0;\n\n border-radius: inherit;\n}\n\n:where(.wui-radio-label, .wui-checkbox-label) {\n pointer-events: none;\n}\n\n/*\n * 未激活态的 hover 状态层。用半透明色而不是混进控件底:覆盖层本就压在不透明的控件底上,\n * 合成结果与 color-mix(text 6%, surface-control) 一致,深色档同样成立。\n * 标签文字带 pointer-events: none,命中落回 label 自身,所以整条触发区(含右侧文字)\n * 的 hover 都会作用到指示器。hover 只在真正的指针设备上生效,避免触屏留态。\n */\n@media (hover: hover) and (pointer: fine) {\n :where(.wui-radio, .wui-checkbox):hover:not(.is-disabled, .is-checked)\n :where(.wui-radio-circle, .wui-checkbox-box)::before {\n background-color: color-mix(in srgb, var(--wui-color-text, #1b1b1b) 6%, transparent);\n }\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"toast-containers.js","names":[],"sources":["../../src/assets/toast-containers.css?inline"],"sourcesContent":["/* Toast 容器定位 — 由 toast 命令式 API 动态注入到 body */\n\n.wui-toast-container {\n --wui-toast-viewport-gap: 16px;\n --wui-toast-container-padding: 40px;\n pointer-events: none;\n\n position: fixed;\n z-index: var(--wui-layer-toast, 200);\n\n overflow-y: auto;\n overscroll-behavior: contain;\n display: flex;\n flex-direction: column;\n gap: 8px;\n\n /* 负向定位只露出可见 gap;剩余 padding 继续作为阴影裁剪缓冲区 */\n max-height: calc(100vh + 2 * (var(--wui-toast-container-padding) - var(--wui-toast-viewport-gap)));\n\n /* padding 需要 >= 阴影偏移 + 模糊半径,否则 overflow 会裁剪子元素 box-shadow */\n padding: var(--wui-toast-container-padding);\n}\n\n.wui-toast-container > * {\n pointer-events: auto;\n}\n\n/* ===== 顶部位置 ===== */\n\n.wui-toast-top-left {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n align-items: flex-start;\n}\n\n.wui-toast-top-center {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n}\n\n.wui-toast-top-right {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n right: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n align-items: flex-end;\n}\n\n/* ===== 底部位置 ===== */\n\n.wui-toast-bottom-left {\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n flex-direction: column-reverse;\n align-items: flex-start;\n}\n\n.wui-toast-bottom-center {\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: 50%;\n transform: translateX(-50%);\n\n flex-direction: column-reverse;\n align-items: center;\n}\n\n.wui-toast-bottom-right {\n right: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n flex-direction: column-reverse;\n align-items: flex-end;\n}\n"],"mappings":""}
1
+ {"version":3,"file":"toast-containers.js","names":[],"sources":["../../src/assets/toast-containers.css?inline"],"sourcesContent":["/* Toast 容器定位 — 由 toast 命令式 API 动态注入到 body */\n\n.wui-toast-container {\n --wui-toast-viewport-gap: 16px;\n --wui-toast-container-padding: 40px;\n pointer-events: none;\n\n position: fixed;\n z-index: var(--wui-layer-toast, 200);\n\n overflow-y: auto;\n overscroll-behavior: contain;\n display: flex;\n flex-direction: column;\n gap: 8px;\n\n /* 负向定位只露出可见 gap;剩余 padding 继续作为阴影裁剪缓冲区 */\n max-height: calc(100vh + 2 * (var(--wui-toast-container-padding) - var(--wui-toast-viewport-gap)));\n\n /* padding 需要 >= 阴影偏移 + 模糊半径,否则 overflow 会裁剪子元素 box-shadow */\n padding: var(--wui-toast-container-padding);\n}\n\n.wui-toast-container > * {\n pointer-events: auto;\n}\n\n.wui-toast-top-left {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n align-items: flex-start;\n}\n\n.wui-toast-top-center {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: 50%;\n transform: translateX(-50%);\n align-items: center;\n}\n\n.wui-toast-top-right {\n top: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n right: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n align-items: flex-end;\n}\n\n.wui-toast-bottom-left {\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n flex-direction: column-reverse;\n align-items: flex-start;\n}\n\n.wui-toast-bottom-center {\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n left: 50%;\n transform: translateX(-50%);\n\n flex-direction: column-reverse;\n align-items: center;\n}\n\n.wui-toast-bottom-right {\n right: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n bottom: calc(var(--wui-toast-viewport-gap) - var(--wui-toast-container-padding));\n flex-direction: column-reverse;\n align-items: flex-end;\n}\n"],"mappings":""}
@@ -412,6 +412,7 @@ export declare class WebUiAutocomplete extends WebUiAutocomplete_base {
412
412
  private _portalContent?;
413
413
  private readonly _scrollLock;
414
414
  private readonly _optionPortal;
415
+ private readonly _overlay;
415
416
  private readonly _panel;
416
417
  get isOpen(): boolean;
417
418
  get open(): boolean;
@@ -427,6 +428,7 @@ export declare class WebUiAutocomplete extends WebUiAutocomplete_base {
427
428
  formDisabledCallback(disabled: boolean): void;
428
429
  private _syncValidity;
429
430
  private _syncOpenAttribute;
431
+ private _syncOverlayInert;
430
432
  private _syncSelectedValue;
431
433
  private _syncOptionA11y;
432
434
  private _getA11yOptionId;
@@ -5,10 +5,10 @@ import n from "../../assets/overlay-motion.js";
5
5
  import { FormAssociated as r, FormAssociationController as i, defineFormAssociation as a } from "../../shared/form-association/index.js";
6
6
  import { normalizeLiteral as o } from "../../shared/normalize/index.js";
7
7
  import { dispatchOpenChangeEvent as s } from "../../shared/open-state/index.js";
8
- import { overlayComposition as c } from "../../shared/overlay/composition.js";
9
- import { createComboboxOpenController as l, createOptionListenerBinding as u, handleComboboxFocusOut as d, nextWrappingIndex as f } from "../../shared/option-portal/combobox-shell.js";
10
- import { defineOptionPortal as p } from "../../shared/option-portal/index.js";
11
- import { defineAnchoredPanel as m } from "../../shared/overlay/anchored-panel.js";
8
+ import { createComboboxOpenController as c, createOptionListenerBinding as l, handleComboboxFocusOut as u, nextWrappingIndex as d } from "../../shared/option-portal/combobox-shell.js";
9
+ import { defineOptionPortal as f } from "../../shared/option-portal/index.js";
10
+ import { defineAnchoredPanel as p } from "../../shared/overlay/anchored-panel.js";
11
+ import { defineOpenOverlay as m } from "../../shared/overlay/open-overlay.js";
12
12
  import { applyOverlayVariables as h, defineOverlayPortal as g } from "../../shared/overlay/portal.js";
13
13
  import { defineScrollLockLease as _ } from "../../shared/scroll-lock/scroll-lock.js";
14
14
  import v from "./style.js";
@@ -45,7 +45,7 @@ var A = class extends r(y) {
45
45
  typeof e == "string" && (this.value = e);
46
46
  },
47
47
  syncValidity: () => this._syncValidity()
48
- }).make(), this._formAssociationController = new i(this, this._formAssociation), this._isOpen = !1, this._activeIndex = -1, this._focused = !1, this._idPrefix = `web-ui-autocomplete-${++D._nextInstanceId}`, this._a11yOptionIds = /* @__PURE__ */ new WeakMap(), this._nextA11yOptionId = 0, this._options = [], this._scrollLock = _().make(), this._optionPortal = p().make({
48
+ }).make(), this._formAssociationController = new i(this, this._formAssociation), this._isOpen = !1, this._activeIndex = -1, this._focused = !1, this._idPrefix = `web-ui-autocomplete-${++D._nextInstanceId}`, this._a11yOptionIds = /* @__PURE__ */ new WeakMap(), this._nextA11yOptionId = 0, this._options = [], this._scrollLock = _().make(), this._optionPortal = f().make({
49
49
  element: this,
50
50
  idPrefix: `${this._idPrefix}-option`,
51
51
  getPortal: () => this._portal,
@@ -56,7 +56,7 @@ var A = class extends r(y) {
56
56
  requestUpdate: () => this.requestUpdate(),
57
57
  bindOption: (e) => this._bindOption(e),
58
58
  unbindOption: (e) => this._unbindOption(e)
59
- }), this._panel = m().make({
59
+ }), this._overlay = m().make({ requestClose: () => this._close() }), this._panel = p().make({
60
60
  getAnchor: () => this.shadowRoot?.querySelector(".input-wrapper") ?? null,
61
61
  getLocalPanel: () => this.shadowRoot?.querySelector(".autocomplete-overlay") ?? null,
62
62
  getPositioning: () => ({
@@ -66,16 +66,15 @@ var A = class extends r(y) {
66
66
  strategy: this.portal ? "fixed" : "absolute"
67
67
  }),
68
68
  isPortal: () => this.portal,
69
- createPortal: () => this._createPortal()
69
+ createPortal: () => this._createPortal(),
70
+ openOverlay: this._overlay
70
71
  }), this._onClickOutside = (e) => {
71
- if (!this._isOpen) return;
72
- let t = e.composedPath(), n = this._panel.getPanel();
73
- !t.includes(this) && !(n && c.containsEvent(n, e)) && this._close();
72
+ this._isOpen && (e.composedPath().includes(this) || this._panel.getHandle()?.containsEvent(e) || this._close());
74
73
  }, this._onFocusOut = () => {
75
- d(this, () => this._panel.getPanel(), () => this._isOpen, () => this._close());
74
+ u(this, () => this._panel.getHandle(), () => this._isOpen, () => this._close());
76
75
  }, this._handlePanelPointerDown = (e) => {
77
76
  e.preventDefault();
78
- }, this._optionListeners = u({
77
+ }, this._optionListeners = l({
79
78
  onClick: (e) => this._handleOptionClick(e),
80
79
  onPointerOver: (e) => this._handleOptionPointerOver(e),
81
80
  onPointerDown: (e) => this._handleOptionPointerDown(e),
@@ -94,9 +93,6 @@ var A = class extends r(y) {
94
93
  this._optionPortal.scheduleRefresh();
95
94
  }, this._onKeydown = (e) => {
96
95
  if (!(this._isDisabled || this.readonly)) switch (e.key) {
97
- case "Escape":
98
- this._isOpen && (this._close(), e.preventDefault());
99
- break;
100
96
  case "ArrowDown":
101
97
  e.preventDefault(), this._isOpen ? this._navigateActive(1) : this._open(!0);
102
98
  break;
@@ -130,7 +126,7 @@ var A = class extends r(y) {
130
126
  this._isDisabled || this.readonly || this._options.length > 0 && this._open();
131
127
  }, this._onBlur = () => {
132
128
  this._focused = !1;
133
- }, this._openController = l({
129
+ }, this._openController = c({
134
130
  canOpen: () => !this._isDisabled && !this.readonly,
135
131
  getIsOpen: () => this._isOpen,
136
132
  setIsOpen: (e) => {
@@ -213,7 +209,7 @@ var A = class extends r(y) {
213
209
  }
214
210
  updated(e) {
215
211
  let t = this.shadowRoot?.querySelector(".autocomplete-input");
216
- t && t.value !== this._value && (t.value = this._value), (e.has("portal") || e.has("overlayContainer")) && requestAnimationFrame(() => this._reconfigureOverlay()), e.has("noScrollLock") && this._syncScrollLock(), this.toggleAttribute("focused", this._focused), this._syncOpenAttribute(), this._syncValidity(), this._syncEmptyState();
212
+ t && t.value !== this._value && (t.value = this._value), (e.has("portal") || e.has("overlayContainer")) && requestAnimationFrame(() => this._reconfigureOverlay()), e.has("noScrollLock") && this._syncScrollLock(), this.toggleAttribute("focused", this._focused), this._syncOpenAttribute(), this._syncOverlayInert(), this._syncValidity(), this._syncEmptyState();
217
213
  }
218
214
  formDisabledCallback(e) {
219
215
  this._formAssociation.setDisabled(e), e && this._isOpen && this._close();
@@ -225,6 +221,9 @@ var A = class extends r(y) {
225
221
  _syncOpenAttribute() {
226
222
  this.toggleAttribute("open", this._isOpen);
227
223
  }
224
+ _syncOverlayInert() {
225
+ this._panel.getHandle()?.setInert(this._isDisabled || this.readonly);
226
+ }
228
227
  _syncSelectedValue() {
229
228
  let e = this._value.trim().toLowerCase(), t = e ? this._options.find((t) => !t.disabled && t.label.trim().toLowerCase() === e)?.value ?? "" : "";
230
229
  this._setSelectedValue(t);
@@ -261,7 +260,7 @@ var A = class extends r(y) {
261
260
  let t = this._options.filter((e) => !e.disabled && !e.hasAttribute("data-filtered"));
262
261
  if (!t.length) return;
263
262
  let n = this._activeIndex >= 0 ? t.indexOf(this._options[this._activeIndex]) : e > 0 ? -1 : 0;
264
- this._activeIndex = this._options.indexOf(t[f(t.length, n, e)]), this._syncActiveOption();
263
+ this._activeIndex = this._options.indexOf(t[d(t.length, n, e)]), this._syncActiveOption();
265
264
  }
266
265
  _setInitialActiveOption() {
267
266
  let e = this._options.findIndex((e) => !e.disabled && !e.hasAttribute("data-filtered"));
@@ -306,13 +305,13 @@ var A = class extends r(y) {
306
305
  s(this, this._isOpen);
307
306
  }
308
307
  _openOverlay(e = !1) {
309
- this._panel.open(e), this._syncEmptyState();
308
+ this._panel.open(e), this._syncOverlayInert(), this._syncEmptyState();
310
309
  }
311
310
  async _closeOverlay() {
312
311
  await this._panel.close(() => this._isOpen) && (this._portal = void 0, this._portalContent = void 0);
313
312
  }
314
313
  _reconfigureOverlay() {
315
- this._portal = void 0, this._portalContent = void 0, this._panel.reconfigure(this._isOpen);
314
+ this._portal = void 0, this._portalContent = void 0, this._panel.reconfigure(this._isOpen), this._syncOverlayInert();
316
315
  }
317
316
  _syncScrollLock(e = this._isOpen) {
318
317
  this._scrollLock.sync(e && !this.noScrollLock);