@greypan/web-ui 6.0.0 → 6.2.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 (187) hide show
  1. package/README.CN.md +285 -93
  2. package/README.md +266 -87
  3. package/dist/assets/glass.js +1 -1
  4. package/dist/assets/glass.js.map +1 -1
  5. package/dist/assets/menu-portal.js +1 -1
  6. package/dist/assets/menu-portal.js.map +1 -1
  7. package/dist/assets/overlay-motion.js +1 -1
  8. package/dist/assets/overlay-motion.js.map +1 -1
  9. package/dist/components/autocomplete/index.d.ts +367 -4
  10. package/dist/components/autocomplete/index.js +87 -80
  11. package/dist/components/autocomplete/index.js.map +1 -1
  12. package/dist/components/autocomplete/style.js +1 -1
  13. package/dist/components/autocomplete/style.js.map +1 -1
  14. package/dist/components/avatar/style.js +1 -1
  15. package/dist/components/avatar/style.js.map +1 -1
  16. package/dist/components/back-top/style.js +1 -1
  17. package/dist/components/back-top/style.js.map +1 -1
  18. package/dist/components/badge/style.js +1 -1
  19. package/dist/components/badge/style.js.map +1 -1
  20. package/dist/components/button/index.js +36 -32
  21. package/dist/components/button/index.js.map +1 -1
  22. package/dist/components/button/style.js +1 -1
  23. package/dist/components/button/style.js.map +1 -1
  24. package/dist/components/button-group/style.js +1 -1
  25. package/dist/components/button-group/style.js.map +1 -1
  26. package/dist/components/checkbox/index.d.ts +366 -5
  27. package/dist/components/checkbox/index.js +19 -31
  28. package/dist/components/checkbox/index.js.map +1 -1
  29. package/dist/components/checkbox/style.js +1 -1
  30. package/dist/components/checkbox/style.js.map +1 -1
  31. package/dist/components/checkbox-group/index.d.ts +366 -4
  32. package/dist/components/checkbox-group/index.js +16 -25
  33. package/dist/components/checkbox-group/index.js.map +1 -1
  34. package/dist/components/collapse/index.d.ts +29 -1
  35. package/dist/components/collapse/index.js +105 -46
  36. package/dist/components/collapse/index.js.map +1 -1
  37. package/dist/components/collapse/style.js +1 -1
  38. package/dist/components/collapse/style.js.map +1 -1
  39. package/dist/components/context-menu/index.d.ts +7 -14
  40. package/dist/components/context-menu/index.js +113 -164
  41. package/dist/components/context-menu/index.js.map +1 -1
  42. package/dist/components/dialog/index.js +26 -25
  43. package/dist/components/dialog/index.js.map +1 -1
  44. package/dist/components/dialog/style.js +1 -1
  45. package/dist/components/dialog/style.js.map +1 -1
  46. package/dist/components/drawer/index.js +71 -64
  47. package/dist/components/drawer/index.js.map +1 -1
  48. package/dist/components/drawer/style.js +1 -1
  49. package/dist/components/drawer/style.js.map +1 -1
  50. package/dist/components/dropdown/index.d.ts +6 -14
  51. package/dist/components/dropdown/index.js +100 -156
  52. package/dist/components/dropdown/index.js.map +1 -1
  53. package/dist/components/dropdown-header/style.js +1 -1
  54. package/dist/components/dropdown-header/style.js.map +1 -1
  55. package/dist/components/dropdown-item/style.js +1 -1
  56. package/dist/components/dropdown-item/style.js.map +1 -1
  57. package/dist/components/empty/style.js +1 -1
  58. package/dist/components/empty/style.js.map +1 -1
  59. package/dist/components/image-preview/index.d.ts +58 -0
  60. package/dist/components/image-preview/index.js +481 -0
  61. package/dist/components/image-preview/index.js.map +1 -0
  62. package/dist/components/image-preview/style.js +6 -0
  63. package/dist/components/image-preview/style.js.map +1 -0
  64. package/dist/components/index.d.ts +1 -0
  65. package/dist/components/index.js +20 -19
  66. package/dist/components/input/index.d.ts +366 -5
  67. package/dist/components/input/index.js +35 -47
  68. package/dist/components/input/index.js.map +1 -1
  69. package/dist/components/input/style.js +1 -1
  70. package/dist/components/input/style.js.map +1 -1
  71. package/dist/components/input-number/index.d.ts +366 -5
  72. package/dist/components/input-number/index.js +31 -44
  73. package/dist/components/input-number/index.js.map +1 -1
  74. package/dist/components/input-number/style.js +1 -1
  75. package/dist/components/input-number/style.js.map +1 -1
  76. package/dist/components/layout/index.d.ts +1 -1
  77. package/dist/components/layout/index.js +5 -3
  78. package/dist/components/layout/index.js.map +1 -1
  79. package/dist/components/layout/style.js +1 -1
  80. package/dist/components/layout/style.js.map +1 -1
  81. package/dist/components/option/style.js +1 -1
  82. package/dist/components/option/style.js.map +1 -1
  83. package/dist/components/popover/index.d.ts +1 -0
  84. package/dist/components/popover/index.js +55 -60
  85. package/dist/components/popover/index.js.map +1 -1
  86. package/dist/components/popover/style.js +1 -1
  87. package/dist/components/popover/style.js.map +1 -1
  88. package/dist/components/radio/index.d.ts +366 -5
  89. package/dist/components/radio/index.js +21 -31
  90. package/dist/components/radio/index.js.map +1 -1
  91. package/dist/components/radio/style.js +1 -1
  92. package/dist/components/radio/style.js.map +1 -1
  93. package/dist/components/radio-group/index.d.ts +366 -4
  94. package/dist/components/radio-group/index.js +16 -25
  95. package/dist/components/radio-group/index.js.map +1 -1
  96. package/dist/components/segmented/index.d.ts +366 -4
  97. package/dist/components/segmented/index.js +28 -35
  98. package/dist/components/segmented/index.js.map +1 -1
  99. package/dist/components/segmented/style.js +1 -1
  100. package/dist/components/segmented/style.js.map +1 -1
  101. package/dist/components/segmented-trigger/index.js +17 -15
  102. package/dist/components/segmented-trigger/index.js.map +1 -1
  103. package/dist/components/segmented-trigger/style.js +1 -1
  104. package/dist/components/segmented-trigger/style.js.map +1 -1
  105. package/dist/components/select/index.d.ts +368 -5
  106. package/dist/components/select/index.js +71 -68
  107. package/dist/components/select/index.js.map +1 -1
  108. package/dist/components/select/style.js +1 -1
  109. package/dist/components/select/style.js.map +1 -1
  110. package/dist/components/slider/index.d.ts +366 -5
  111. package/dist/components/slider/index.js +40 -50
  112. package/dist/components/slider/index.js.map +1 -1
  113. package/dist/components/slider/style.js +1 -1
  114. package/dist/components/slider/style.js.map +1 -1
  115. package/dist/components/spinner/style.js +1 -1
  116. package/dist/components/spinner/style.js.map +1 -1
  117. package/dist/components/svg-draw-lines/index.js +1 -1
  118. package/dist/components/svg-draw-lines/index.js.map +1 -1
  119. package/dist/components/switch/index.d.ts +366 -5
  120. package/dist/components/switch/index.js +28 -38
  121. package/dist/components/switch/index.js.map +1 -1
  122. package/dist/components/switch/style.js +1 -1
  123. package/dist/components/switch/style.js.map +1 -1
  124. package/dist/components/textarea/index.d.ts +366 -5
  125. package/dist/components/textarea/index.js +43 -57
  126. package/dist/components/textarea/index.js.map +1 -1
  127. package/dist/components/textarea/style.js +1 -1
  128. package/dist/components/textarea/style.js.map +1 -1
  129. package/dist/components/theme/index.js +1 -1
  130. package/dist/components/theme/index.js.map +1 -1
  131. package/dist/components/theme/style.js +1 -1
  132. package/dist/components/theme/style.js.map +1 -1
  133. package/dist/components/toast/manager.js +2 -2
  134. package/dist/components/toast/manager.js.map +1 -1
  135. package/dist/components/toast/style.js +1 -1
  136. package/dist/components/toast/style.js.map +1 -1
  137. package/dist/components/tooltip/index.d.ts +1 -0
  138. package/dist/components/tooltip/index.js +43 -58
  139. package/dist/components/tooltip/index.js.map +1 -1
  140. package/dist/components/tooltip/style.js +1 -1
  141. package/dist/components/tooltip/style.js.map +1 -1
  142. package/dist/icons/generated/radix-icons-reset.d.ts +6 -0
  143. package/dist/icons/generated/radix-icons-reset.js +10 -0
  144. package/dist/icons/generated/radix-icons-reset.js.map +1 -0
  145. package/dist/icons/index.d.ts +1 -0
  146. package/dist/icons/index.js +2 -1
  147. package/dist/shared/focus/pointer-focus.js +37 -0
  148. package/dist/shared/focus/pointer-focus.js.map +1 -0
  149. package/dist/shared/form-association/index.js +31 -4
  150. package/dist/shared/form-association/index.js.map +1 -1
  151. package/dist/shared/gesture/drag-gesture.js +53 -30
  152. package/dist/shared/gesture/drag-gesture.js.map +1 -1
  153. package/dist/shared/gesture/pinch-gesture.js +84 -0
  154. package/dist/shared/gesture/pinch-gesture.js.map +1 -0
  155. package/dist/shared/group-management/index.js.map +1 -1
  156. package/dist/shared/menu-behavior/index.js +112 -0
  157. package/dist/shared/menu-behavior/index.js.map +1 -0
  158. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  159. package/dist/shared/open-state/index.js +12 -0
  160. package/dist/shared/open-state/index.js.map +1 -0
  161. package/dist/shared/option-portal/combobox-shell.js +43 -0
  162. package/dist/shared/option-portal/combobox-shell.js.map +1 -0
  163. package/dist/shared/option-portal/index.js +22 -21
  164. package/dist/shared/option-portal/index.js.map +1 -1
  165. package/dist/shared/overlay/anchored-panel.js +33 -30
  166. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  167. package/dist/shared/overlay/composition.js +57 -0
  168. package/dist/shared/overlay/composition.js.map +1 -0
  169. package/dist/shared/overlay/lifecycle.js +41 -0
  170. package/dist/shared/overlay/lifecycle.js.map +1 -0
  171. package/dist/shared/overlay/native-dialog-presence.js +15 -15
  172. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  173. package/dist/shared/{theme → overlay}/overlay-root.js +1 -1
  174. package/dist/shared/overlay/overlay-root.js.map +1 -0
  175. package/dist/shared/overlay/overlay.js +43 -41
  176. package/dist/shared/overlay/overlay.js.map +1 -1
  177. package/dist/shared/overlay/placement-props.js +19 -0
  178. package/dist/shared/overlay/placement-props.js.map +1 -0
  179. package/dist/shared/overlay/portal.js +6 -4
  180. package/dist/shared/overlay/portal.js.map +1 -1
  181. package/dist/shared/overlay/positioning-generation.js +20 -0
  182. package/dist/shared/overlay/positioning-generation.js.map +1 -0
  183. package/dist/shared/{theme/theme-scope.js → overlay/theme-overlay-scope.js} +2 -2
  184. package/dist/shared/overlay/theme-overlay-scope.js.map +1 -0
  185. package/package.json +6 -10
  186. package/dist/shared/theme/overlay-root.js.map +0 -1
  187. package/dist/shared/theme/theme-scope.js.map +0 -1
package/README.md CHANGED
@@ -123,12 +123,12 @@ useEffect(() => {
123
123
  <web-ui-dropdown-item ref={itemRef}>Paste and close</web-ui-dropdown-item>
124
124
  ```
125
125
 
126
- In `portal` mode the library physically moves panel content into the overlay Shadow DOM. React removes nodes
127
- through its recorded insertion parent, so a bare conditional child (`{condition && <el/>}`) whose node was
128
- already migrated cannot be removed while the panel is open: the commit fails and the node can reappear when
129
- the panel closes. Conditionally render inside a stable wrapper element instead — React records the wrapper as
130
- the insertion parent, so wrapper-local additions and removals keep working while the panel is open and across
131
- open/close cycles:
126
+ In `portal` mode the library physically moves panel content into the overlay Shadow DOM:
127
+
128
+ - React removes nodes through its recorded insertion parent, so a bare conditional child (`{condition && <el/>}`) whose node was already migrated cannot be removed while the panel is open: the commit fails and the node can reappear when the panel closes.
129
+ - Conditionally render inside a stable wrapper element instead — React records the wrapper as the insertion parent, so wrapper-local additions and removals keep working while the panel is open and across open/close cycles (example below).
130
+ - Bare conditional additions while the panel is open (appending to the host) keep working and migrate into the panel automatically.
131
+ - Keeping content mounted and toggling visibility remains a valid fallback.
132
132
 
133
133
  ```tsx
134
134
  <web-ui-popover portal open={open} trigger="manual">
@@ -136,9 +136,6 @@ open/close cycles:
136
136
  </web-ui-popover>
137
137
  ```
138
138
 
139
- Bare conditional additions while the panel is open (appending to the host) keep working and migrate into the
140
- panel automatically. Keeping content mounted and toggling visibility remains a valid fallback.
141
-
142
139
  ### Lit
143
140
 
144
141
  Portal panels support live rendering for Lit child parts: `${condition ? html`<el/>` : nothing}` inside
@@ -257,11 +254,16 @@ components use this mode; the instant open/close family (collapse, popover, drop
257
254
  The control manages its own value/checked state internally; user interaction flips it immediately and then dispatches
258
255
  native `input`/`change` events. Assigning the property programmatically (`el.checked = true`) overrides the internal
259
256
  state. This mirrors native `<input type="checkbox">` — there is no `controlled` prop because the value is always readable
260
- and writable, and React/Vue layers provide their own controlled wrappers on top. For interactions that need an external
261
- round-trip before the flip is durable (e.g. a switch that should only stay on after an API call succeeds), use the
262
- optimistic-update path: flip immediately, observe `change`, set the control's `loading` during the request (this blocks
263
- further interaction and shows a spinner), and on failure write the property back (`el.checked = false`) plus surface a
264
- toast. The flip-before-approval window is accepted; there is no `controlled` prop for a reject-before-flip contract.
257
+ and writable, and React/Vue layers provide their own controlled wrappers on top.
258
+
259
+ For interactions that need an external round-trip before the flip is durable (e.g. a switch that should only stay on
260
+ after an API call succeeds), use the optimistic-update path:
261
+
262
+ - Flip immediately, then observe `change`.
263
+ - Set the control's `loading` during the request; this blocks further interaction and shows a spinner.
264
+ - On failure, write the property back (`el.checked = false`) and surface a toast.
265
+
266
+ The flip-before-approval window is accepted; there is no `controlled` prop for a reject-before-flip contract.
265
267
 
266
268
  ### Form-associated controls
267
269
 
@@ -287,6 +289,7 @@ All form controls participate in native `FormData`, constraint validation, `form
287
289
  | | [`<web-ui-button-group>`](#web-ui-button-group) |
288
290
  | **Overlay / Modal** | [`<web-ui-dialog>`](#web-ui-dialog) |
289
291
  | | [`<web-ui-drawer>`](#web-ui-drawer) |
292
+ | | [`imagePreview()`](#imagepreview) |
290
293
  | **In-flow Disclosure** | [`<web-ui-collapse>`](#web-ui-collapse) |
291
294
  | **Floating** | [`<web-ui-popover>`](#web-ui-popover) |
292
295
  | | [`<web-ui-tooltip>`](#web-ui-tooltip) |
@@ -403,16 +406,16 @@ ArrowUp/ArrowDown keyboard increments and decrements the value. Empty or `-` inp
403
406
 
404
407
  Select dropdown with option items, keyboard navigation, and portal support.
405
408
 
406
- | Attribute | Type | Default | Description |
407
- | ------------------ | ---------------------------------- | ------- | --------------------------------- |
408
- | `value` | `string` | `''` | Selected value |
409
- | `placeholder` | `string` | `''` | Placeholder text |
410
- | `name` | `string` | `''` | Form field name |
411
- | `disabled` | `boolean` | `false` | Disabled state |
412
- | `required` | `boolean` | `false` | Required validation |
413
- | `portal` | `boolean` | `false` | Render dropdown in theme overlay |
414
- | `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
415
- | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
409
+ | Attribute | Type | Default | Description |
410
+ | ------------------ | ---------------------------------- | ------- | ------------------------------------------- |
411
+ | `value` | `string` | `''` | Selected value |
412
+ | `placeholder` | `string` | `''` | Placeholder text |
413
+ | `name` | `string` | `''` | Form field name |
414
+ | `disabled` | `boolean` | `false` | Disabled state |
415
+ | `required` | `boolean` | `false` | Required validation |
416
+ | `portal` | `boolean` | `false` | Render dropdown in theme-owned overlay root |
417
+ | `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
418
+ | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
416
419
 
417
420
  **Events:** `input`, `change`, `open-change` (`CustomEvent<{ open: boolean }>`)
418
421
 
@@ -450,7 +453,7 @@ Editable combobox with input filtering and single option selection.
450
453
  | `readonly` | `boolean` | `false` | Read-only state (no typing, no dropdown) |
451
454
  | `required` | `boolean` | `false` | Required validation |
452
455
  | `allow-custom-value` | `boolean` | `false` | Allow Enter to submit a custom value that is not among candidates |
453
- | `portal` | `boolean` | `false` | Render dropdown in theme overlay |
456
+ | `portal` | `boolean` | `false` | Render dropdown in theme-owned overlay root |
454
457
  | `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
455
458
  | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
456
459
  | `aria-label` | `string` | — | Accessible name |
@@ -496,7 +499,7 @@ Range slider with marks and vertical orientation.
496
499
 
497
500
  **Methods:** `focus()`, `blur()`
498
501
 
499
- Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation. Uses pointer capture for drag interaction across mouse, touch, and pen.
502
+ Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation. Uses pointer capture for drag interaction across mouse, touch, and pen. Both the component host and the track declare `touch-action: none`, and an active drag suppresses `touchmove` defaulting, so iOS Safari never takes over horizontal or vertical drags mid-gesture.
500
503
 
501
504
  **CSS Custom Properties:**
502
505
 
@@ -506,8 +509,9 @@ Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation.
506
509
  | `--wui-slider-vertical-height` | `200px` | Vertical slider height |
507
510
  | `--wui-slider-height` | `var(--wui-slider-track-size, 6px)` | Track thickness |
508
511
  | `--wui-slider-track-size` | `6px` | Track size |
509
- | `--wui-slider-thumb-width` | `30px` | Thumb width |
510
- | `--wui-slider-thumb-height` | `20px` | Thumb height |
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 |
511
515
  | `--wui-slider-marks-inset` | `0` | Marks inset from edges |
512
516
 
513
517
  #### `<web-ui-checkbox>`
@@ -582,6 +586,8 @@ Form-associated: integrates with native `<form>` via `ElementInternals`.
582
586
 
583
587
  Manages child trigger `checked` state based on `value`. `disabled` supplies inherited effective disabled state without changing a trigger's own `disabled` property. Setting `value` directly does not dispatch `input`/`change`.
584
588
 
589
+ Press the selected segment and drag horizontally to slide the indicator; on release it snaps to the nearest option (a fast flick switches by velocity). Touch surfaces declare `touch-action: none` and suppress `touchmove` defaulting during an active drag, so iOS Safari does not interrupt the gesture.
590
+
585
591
  #### `<web-ui-checkbox-group>`
586
592
 
587
593
  Checkbox group managing multiple selection.
@@ -697,11 +703,17 @@ Uses native `<dialog>` with `@cancel` prevention. Escape calls `close()` unless
697
703
 
698
704
  **CSS Custom Properties:**
699
705
 
700
- | Property | Default | Description |
701
- | ------------------------- | ---------------------------------- | -------------------------------------------------------------- |
702
- | `--wui-dialog-max-width` | `360px` | Dialog max width |
703
- | `--wui-dialog-overlay-bg` | `var(--wui-color-backdrop)` | Backdrop background |
704
- | `--wui-dialog-bg` | `var(--wui-color-surface-overlay)` | Glass card background, falls back to `rgb(246 246 246 / 0.88)` |
706
+ | Property | Default | Description |
707
+ | ----------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------- |
708
+ | `--wui-dialog-width` | `360px` | Dialog width |
709
+ | `--wui-dialog-max-height` | `90vh` | Dialog max height |
710
+ | `--wui-dialog-overlay-bg` | `var(--wui-color-backdrop)` | Backdrop background |
711
+ | `--wui-dialog-bg` | `var(--wui-color-surface-overlay)` | Glass card background, falls back to `rgb(246 246 246 / 0.88)` |
712
+ | `--wui-dialog-padding` | `20px 24px 24px` | Dialog surface padding |
713
+ | `--wui-dialog-title-gap` | `16px` | Spacing below the title |
714
+ | `--wui-dialog-desc-gap` | `24px` | Spacing below the body content |
715
+ | `--wui-dialog-footer-gap` | `10px` / `12px` (horizontal) | Spacing between footer buttons |
716
+ | `--wui-dialog-footer-justify` | `flex-end` (default) / `center` (horizontal) | Footer `justify-content`; override to `flex-end` for right-aligned horizontal buttons |
705
717
 
706
718
  #### `<web-ui-drawer>`
707
719
 
@@ -730,18 +742,109 @@ Side drawer using native `<dialog>` with closing animation. In non-headless mode
730
742
 
731
743
  Closing keeps the native dialog in the top layer until the `--wui-duration-drawer-exit` transition completes (240ms by default), then calls `dialog.close()`. Escape always follows this close path; `no-backdrop-close` controls backdrop clicks only.
732
744
 
733
- **Drag to close:** With `draggable`, a gray capsule drag bar 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. Dragging follows the pointer in real time (backdrop fades proportionally); 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. 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. Drag-to-open is not supported because the closed drawer renders nothing outside the native dialog. Under `prefers-reduced-motion`, release snaps instantly without spring animation.
745
+ When `closable` is set, the built-in close button is positioned at the header's top-right corner (defaults `16px` down and `20px` in from the edge) instead of stretching across the drawer body.
746
+
747
+ **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:
748
+
749
+ - Dragging follows the pointer in real time; the backdrop fades proportionally.
750
+ - 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.
751
+ - 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.
752
+ - Drag-to-open is not supported because the closed drawer renders nothing outside the native dialog.
753
+ - Under `prefers-reduced-motion`, release snaps instantly without spring animation.
734
754
 
735
755
  **CSS Custom Properties:**
736
756
 
737
- | Property | Default | Description |
738
- | ------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- |
739
- | `--wui-drawer-width` | `320px` | Drawer width |
740
- | `--wui-drawer-height` | `300px` | Drawer height (top/bottom) |
741
- | `--wui-drawer-bg` | `var(--wui-color-surface-overlay)` | Drawer body background |
742
- | `--wui-drawer-radius` | `28px` | Floating card corner radius (non-headless) |
743
- | `--wui-drawer-inset` | `8px` | Floating card viewport inset (non-headless); `0` gives edge-to-edge geometry |
744
- | `--wui-drawer-overlay-bg` | `rgb(0 0 0 / 0.12)` | Backdrop background |
757
+ | Property | Default | Description |
758
+ | --------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------- |
759
+ | `--wui-drawer-width` | `320px` | Drawer width |
760
+ | `--wui-drawer-height` | `300px` | Drawer height (top/bottom) |
761
+ | `--wui-drawer-bg` | `var(--wui-color-surface-overlay)` | Drawer body background |
762
+ | `--wui-drawer-radius` | `var(--wui-radius-overlay, 28px)` | Floating card corner radius (non-headless) |
763
+ | `--wui-drawer-inset` | `8px` | Floating card viewport inset (non-headless); `0` gives edge-to-edge geometry |
764
+ | `--wui-drawer-overlay-bg` | `rgb(0 0 0 / 0.12)` | Backdrop background |
765
+ | `--wui-drawer-drag-zone-size` | `20px` | Drag-to-close hit zone thickness on the inner edge (draggable) |
766
+ | `--wui-drawer-drag-bar-thickness` | `4px` | Drag bar capsule thickness (short axis) |
767
+ | `--wui-drawer-drag-bar-length` | `56px` | Drag bar capsule length (along the drawer edge) |
768
+ | `--wui-drawer-header-padding` | `16px 20px` | Header section padding |
769
+ | `--wui-drawer-close-top` | `16px` | Built-in close button offset from the top of the header |
770
+ | `--wui-drawer-close-right` | `20px` | Built-in close button offset from the right edge of the drawer |
771
+ | `--wui-drawer-content-padding` | `20px` | Content area padding; also drives drag bar visual center (half value) |
772
+ | `--wui-drawer-footer-padding` | `16px 20px` | Footer section padding |
773
+
774
+ #### `imagePreview()`
775
+
776
+ Imperative image preview with no declarative tag contract: open it through `imagePreview()` and drive it through the returned handle. It uses the native `<dialog>` `showModal()` and mounts into the nearest `web-ui-theme` theme-owned overlay root, falling back to the global fallback overlay root when no theme-owned root exists.
777
+
778
+ It does not reuse the `<web-ui-dialog>` component: the preview needs a dialog that fills the viewport itself (the full-viewport backdrop scrim is rendered by a dedicated overlay layer inside the dialog, so the dialog element never fades its own opacity and the glass controls' backdrop blur stays continuous) plus its own pointer interaction, which is a different source than the dialog's glass panel and slot contract. The two share the `native-dialog-presence` and `scroll-lock` plugins, and `noScrollLock` / `noBackdropClose` mirror the same-named properties in naming and semantics.
779
+
780
+ ```ts
781
+ import { imagePreview } from '@greypan/web-ui'
782
+
783
+ const preview = imagePreview({
784
+ images: [{ src: '/a.jpg', alt: 'Image A' }, { src: '/b.jpg' }],
785
+ index: 0,
786
+ nav: true,
787
+ toolbar: true,
788
+ closable: true,
789
+ indicator: true
790
+ })
791
+
792
+ preview.next()
793
+ preview.zoomIn()
794
+
795
+ await preview.closed
796
+ ```
797
+
798
+ **Options:**
799
+
800
+ | Option | Type | Default | Description |
801
+ | ----------------- | -------------------- | ------- | --------------------------------------------------------------------------- |
802
+ | `images` | `ImagePreviewItem[]` | — | Image list; must contain at least one item, else it throws |
803
+ | `index` | `number` | `0` | Initial index, clamped into range |
804
+ | `loop` | `boolean` | `true` | Wrap around at both ends |
805
+ | `target` | `Element` | — | Trigger element used to resolve the nearest theme-owned overlay root |
806
+ | `container` | `HTMLElement` | — | Explicit mount container, highest priority |
807
+ | `nav` | `boolean` | `false` | Show prev/next buttons; rendered only when more than one image |
808
+ | `toolbar` | `boolean` | `false` | Show the zoom toolbar (out / factor / in / reset) |
809
+ | `closable` | `boolean` | `false` | Show the close button |
810
+ | `indicator` | `boolean` | `false` | Show the "current / total" indicator, announced via `aria-live` |
811
+ | `swipe` | `boolean` | `false` | At 1x, swipe horizontally to change images; owns that axis while it applies |
812
+ | `noScrollLock` | `boolean` | `false` | Do not lock page scroll while open |
813
+ | `noBackdropClose` | `boolean` | `false` | Clicking the blank area outside the image does not close |
814
+
815
+ `ImagePreviewItem` is `{ src: string; alt?: string }`; a missing `alt` defaults to an empty string.
816
+
817
+ Every presentation option defaults to off: with no options passed only the image itself is rendered, so navigation, toolbar, close button and indicator all have to be enabled explicitly.
818
+
819
+ **Returned handle:**
820
+
821
+ | Member | Type | Description |
822
+ | ---------------------------------------- | ----------------------------- | ----------------------------------------------- |
823
+ | `index` | `number` | Current index; keeps its last value after close |
824
+ | `scale` | `number` | Current zoom factor within `[1, 4]` |
825
+ | `images` | `readonly ImagePreviewItem[]` | Normalized image list |
826
+ | `closed` | `Promise<void>` | Resolves after the exit transition and unmount |
827
+ | `next()` / `prev()` | `() => void` | Relative navigation |
828
+ | `goTo(index)` | `(index: number) => void` | Jump; clamps at the bounds when `loop` is off |
829
+ | `zoomIn()` / `zoomOut()` / `resetZoom()` | `() => void` | Zoom controls |
830
+ | `close()` | `() => void` | Close and play the exit animation |
831
+
832
+ **Interaction:** Arrow keys always navigate, independently of `nav`; `+` / `-` and the mouse wheel zoom, and `0` resets. Zooming is anchored: the wheel keeps the point under the cursor fixed and a pinch keeps the midpoint between the two fingers fixed, while the toolbar buttons, keyboard shortcuts and double-click expand around the viewport center. Holding the mouse button down or resting a single finger drags the image to pan in any direction, at any zoom level: zoomed in it reveals the cropped edges, and at 1x it moves the image around within the viewport. Either way the drag is bounded by half the size difference between the image and the stage, so the image can never be dragged outside the viewport, and it is clamped (not snapped back) when you keep dragging past the edge. Double-clicking the image toggles between 1x and 2x.
833
+
834
+ Pinch-to-zoom is always on and has no option: the stage already owns pointer interaction, so there is nothing for a gesture to conflict with. The first finger still drives pan/swipe as usual and the second finger starts the pinch, which aborts any in-flight pan or swipe. A pinch does not close the preview, and the compatibility `click` that mixed input may synthesize afterwards is swallowed rather than treated as a backdrop click.
835
+
836
+ Clicking anywhere outside the image, or pressing Escape, closes the preview. The native dialog always exposes the accessible name `图片预览`. With `swipe` on and more than one image, the images live on a single carousel track: the current image and its neighbors sit side by side in slides of equal size, the stage clips the overflow, and the whole track translates with the finger. Releasing past the distance threshold (15% of the stage width, rounded) slides the next image in, as does a fast flick whose sampled velocity is at least 320px/s even below that distance; a flick covering at least 16px whose velocity (windowed sample or whole-gesture average) reaches 160px/s also commits; otherwise it bounces back. With `loop` on the wrap-around neighbor is always present beside the current image, and at the non-looping bounds the strip bounces back instead of crossing the boundary. While it applies, the swipe gesture takes over the drag entirely: at 1x only horizontal movement changes images, and vertical movement is ignored (no pan, no vertical follow). Once zoomed in, dragging always pans on both axes and never changes images. Because panning also works at 1x, `resetZoom()` is offered whenever the image is zoomed _or_ displaced — the reset button in the toolbar is enabled for a panned 1x image too.
837
+
838
+ A close request does not destroy the native dialog immediately: it stays in the top layer until the exit transition completes, then `dialog.close()` runs, the host is removed from the DOM, and `closed` resolves. Page scroll is locked while open unless `noScrollLock` is `true`.
839
+
840
+ **CSS Custom Properties:**
841
+
842
+ | Property | Default | Description |
843
+ | -------------------------------- | -------------------------------- | ------------------------------------------------- |
844
+ | `--wui-image-preview-overlay-bg` | `rgb(0 0 0 / 0.72)` | Full-viewport backdrop background color |
845
+ | `--wui-image-preview-edge-gap` | `20px` | Distance from controls to viewport edge |
846
+ | `--wui-duration-swipe-settle` | `220ms` | Carousel settle (slide-in / bounce-back) duration |
847
+ | `--wui-ease-swipe` | `cubic-bezier(0.32, 0.72, 0, 1)` | Carousel settle (slide-in / bounce-back) easing |
745
848
 
746
849
  ---
747
850
 
@@ -751,12 +854,13 @@ Closing keeps the native dialog in the top layer until the `--wui-duration-drawe
751
854
 
752
855
  In-flow expand/collapse container with animated height (or width) transition. Single element with two slots; no portal, no scroll lock, no focus management.
753
856
 
754
- | Attribute | Type | Default | Description |
755
- | -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
756
- | `open` | `boolean` | `false` | Expanded state; self-managed on interaction, `open-change` emits only for user-originated toggles |
757
- | `disabled` | `boolean` | `false` | Ignores trigger clicks and sets `aria-disabled` on the trigger element; expanded content is kept |
758
- | `horizontal` | `boolean` | `false` | Animate width instead of height |
759
- | `keep-mounted` | `boolean` | `false` | Closed state keeps the content in the 0fr track with `inert` (scroll position and layout measurable) instead of `hidden` |
857
+ | Attribute | Type | Default | Description |
858
+ | -------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
859
+ | `open` | `boolean` | `false` | Expanded state; self-managed on interaction, `open-change` emits only for user-originated toggles |
860
+ | `disabled` | `boolean` | `false` | Ignores trigger clicks and sets `aria-disabled` on the trigger element; expanded content is kept |
861
+ | `horizontal` | `boolean` | `false` | Animate width instead of height |
862
+ | `keep-mounted` | `boolean` | `false` | Closed state keeps the content in the 0fr track with `inert` (scroll position and layout measurable) instead of `hidden` |
863
+ | `peek` | `string` | — | Closed state reveals this much of the content (CSS length, e.g. `120px`); along the animation axis; ends in an auto-sized edge fade |
760
864
 
761
865
  **Events:** `open-change` (`CustomEvent<{ open: boolean }>`). Emitted only for user-originated toggles (trigger click). Programmatic writes (`open`, `show()`, `close()`, `toggle()`) never emit. Nested collapses: an inner `open-change` bubbles through the outer root (composed event); distinguish by `event.target`.
762
866
 
@@ -777,6 +881,38 @@ The initial `open` attribute settles instantly without playing the animation. Ne
777
881
 
778
882
  **Closed-state semantics:** the consumer's light DOM is never moved or unmounted. Default closed state applies `hidden` to the internal content container; with `keep-mounted` the inner container is marked `inert` while staying measurable inside the collapsed track.
779
883
 
884
+ **`peek` (partial reveal):** setting `peek` makes the closed state reveal the first `peek` of the content along the animation axis (`horizontal` switches it to width) instead of collapsing to zero — the track stays at the content height and the inner container is clamped. It implies `keep-mounted` semantics: the content stays mounted but is `inert`, so the clipped portion is not focusable or clickable. Content shorter than `peek` collapses to its own size rather than leaving blank space. Because a fixed length and an auto height cannot be interpolated by CSS, the two `peek` ↔ expanded directions are driven by explicit pixel lengths (measured once per toggle); every other animation path stays a zero-measurement grid `fr` transition.
885
+
886
+ ```html
887
+ <web-ui-collapse peek="120px">
888
+ <button type="button">Toggle me</button>
889
+ <div slot="content">Long content — only the first 120px show while closed</div>
890
+ </web-ui-collapse>
891
+ ```
892
+
893
+ **Edge fade:** the revealed area ends in an alpha-gradient fade (right edge when `horizontal`) so the clipped portion melts into the background instead of ending in a hard line. The length is derived from `peek` — there is no second attribute to set. Tune it with CSS custom properties instead:
894
+
895
+ | Custom property | Default | Description |
896
+ | -------------------------------- | ------------- | --------------------------------------------------------------------- |
897
+ | `--wui-collapse-peek-edge-ratio` | `0.4` | Fade length as a fraction of `peek` |
898
+ | `--wui-collapse-peek-edge-max` | `112px` | Upper bound, so a large `peek` cannot produce an oversized fade band |
899
+ | `--wui-collapse-peek-edge` | — | Explicit fade length; wins over the derived value |
900
+ | `--wui-collapse-peek-edge-color` | `transparent` | Fade-stop color (must carry alpha — `mask-image` reads alpha channel) |
901
+
902
+ Set the ratio to `0` to turn the fade off. The derivation is pure CSS `calc(peek * ratio)`, so a `rem`-based `peek` scales the fade with the root font size and nothing has to be measured in JS. Known limitation: a percentage `peek` does not derive to a length, so the fade silently falls back to "none" — set `--wui-collapse-peek-edge` explicitly in that case.
903
+
904
+ The fade is a four-stop ease-out gradient rather than a linear one: the body stays fully opaque to the start of the fade band, drops to 60% alpha within the first 30% of the band, then 26%, then reaches the edge color. A linear gradient hugs 1.0 alpha across the first 40% of the band, so the eye perceives a much shorter fade than declared — front-loading the drop makes the band visibly soft from its very start, nearly doubling the perceived length at the same size.
905
+
906
+ The fade band is positioned in percentages, relative to the container's **current rendered height** — which is what `max-height` animates. So the band tracks the cut edge frame by frame during the collapse animation without the `mask-image` string ever needing to interpolate. Its length is driven by a registered `<length>` custom property so it can fade in and out with the toggle instead of popping in once the close animation settles.
907
+
908
+ ```html
909
+ <!-- 调强渐隐:比例 0.5 → 120px peek 配 60px 渐隐 -->
910
+ <web-ui-collapse peek="120px" style="--wui-collapse-peek-edge-ratio: 0.5">
911
+ <button type="button">Toggle me</button>
912
+ <div slot="content">Long content with a stronger fade at the bottom of the 120px peek</div>
913
+ </web-ui-collapse>
914
+ ```
915
+
780
916
  **Known limitations:** the `horizontal` animation reflows content while width changes.
781
917
 
782
918
  ---
@@ -787,15 +923,15 @@ The initial `open` attribute settles instantly without playing the animation. Ne
787
923
 
788
924
  Popover overlay anchored to trigger element.
789
925
 
790
- | Attribute | Type | Default | Description |
791
- | ------------------ | ---------------------------------- | ---------- | ------------------------- |
792
- | `open` | `boolean` | `false` | Popover visibility |
793
- | `disabled` | `boolean` | `false` | Disabled state |
794
- | `placement` | `Placement` | `'bottom'` | Floating UI placement |
795
- | `trigger` | `'click' \| 'hover' \| 'manual'` | `'click'` | Open trigger |
796
- | `offset` | `number` | `8` | Offset from anchor |
797
- | `portal` | `boolean` | `false` | Render in theme overlay |
798
- | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
926
+ | Attribute | Type | Default | Description |
927
+ | ------------------ | ---------------------------------- | ---------- | ---------------------------------- |
928
+ | `open` | `boolean` | `false` | Popover visibility |
929
+ | `disabled` | `boolean` | `false` | Disabled state |
930
+ | `placement` | `Placement` | `'bottom'` | Floating UI placement |
931
+ | `trigger` | `'click' \| 'hover' \| 'manual'` | `'click'` | Open trigger |
932
+ | `offset` | `number` | `8` | Offset from anchor |
933
+ | `portal` | `boolean` | `false` | Render in theme-owned overlay root |
934
+ | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
799
935
 
800
936
  **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
801
937
 
@@ -820,7 +956,7 @@ Tooltip overlay using pointer/focus triggers.
820
956
  | `show-delay` | `number` | `200` | Show delay in ms |
821
957
  | `hide-delay` | `number` | `100` | Hide delay in ms |
822
958
  | `offset` | `number` | `6` | Offset from trigger |
823
- | `portal` | `boolean` | `false` | Render in theme overlay |
959
+ | `portal` | `boolean` | `false` | Render in theme-owned overlay root |
824
960
  | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
825
961
 
826
962
  **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
@@ -1019,7 +1155,7 @@ WebUiSpinner.hide() // hide
1019
1155
 
1020
1156
  #### `<web-ui-layout>`
1021
1157
 
1022
- Responsive page layout with an optional full-width banner, a collapsible desktop sidebar, and a headless-drawer mobile sidebar. The page itself scrolls; the desktop sidebar and header stick to the viewport after the banner scrolls away.
1158
+ Responsive page layout with an optional full-width banner, a collapsible desktop sidebar, and a default-drawer mobile sidebar. The page itself scrolls; the desktop sidebar and header stick to the viewport after the banner scrolls away.
1023
1159
 
1024
1160
  | Attribute | Type | Default | Description |
1025
1161
  | ------------------- | --------- | --------- | -------------------------------------------------------------------------------------------------- |
@@ -1034,28 +1170,59 @@ Responsive page layout with an optional full-width banner, a collapsible desktop
1034
1170
 
1035
1171
  **Events:** `sidebar-collapsed-change` (`CustomEvent<{ collapsed: boolean }>`) requests a desktop collapse-state update. `sidebar-open-change` (`CustomEvent<{ open: boolean }>`) requests a mobile drawer open-state update. `sidebar-width-change` (`CustomEvent<{ width: string }>`) requests a sidebar width update after a resize drag ends. Consumers must write the requested value back to the corresponding controlled property.
1036
1172
 
1037
- **Sidebar resize:** With `sidebar-resizable`, a resize handle appears on the desktop sidebar's right edge (hidden while collapsed). Hovering or dragging shows a 3px accent vertical line with a `col-resize` cursor. Dragging updates the width in real time (transition suppressed, clamped to `[min, max]` and the viewport); releasing emits `sidebar-width-change` with the final pixel width and returns control to the `sidebar-width` property once the consumer writes it back. `pointercancel` restores the property-controlled width without emitting. The handle is keyboard-operable (WAI-ARIA splitter pattern): focus it and use ←/→ to step by 16px (Shift for 64px), Home/End to jump to min/max, Enter to commit via the same `sidebar-width-change` request, and Escape to revert an uncommitted adjustment. The mobile drawer always supports drag-to-close via its built-in `draggable` drawer.
1173
+ **Sidebar resize:** With `sidebar-resizable`, a resize handle appears on the desktop sidebar's right edge (hidden while collapsed); hovering or dragging shows a 3px accent vertical line with a `col-resize` cursor.
1038
1174
 
1039
- | Slot | Description |
1040
- | --------- | ----------------------------------------------------------------------------------------- |
1041
- | `banner` | Optional full-width banner above the layout body |
1042
- | `header` | Sticky content-area header |
1043
- | `sidebar` | Sidebar-card content. The consumer owns its internal fixed regions and scroll containers. |
1044
- | `default` | Main content |
1045
- | `tabbar` | Bottom tab bar |
1175
+ - Dragging updates the width in real time (transition suppressed, clamped to `[min, max]` and the viewport).
1176
+ - Releasing emits `sidebar-width-change` with the final pixel width; the width is property-controlled again once the consumer writes it back. `pointercancel` restores the property-controlled width without emitting.
1177
+ - Keyboard (WAI-ARIA splitter pattern): focus the handle and use ←/→ to step by 16px (Shift for 64px), Home/End to jump to min/max, Enter to commit via the same `sidebar-width-change` request, and Escape to revert an uncommitted adjustment.
1178
+ - The mobile drawer always supports drag-to-close via its built-in `draggable` drawer.
1046
1179
 
1047
- `web-ui-layout` constrains the sidebar card and owns the desktop toggle area, but does not create a sidebar scrollport. To make only part of the sidebar scroll, make the `sidebar` slot root a `height: 100%; min-height: 0` flex column and apply `overflow-y: auto` to the intended child. This keeps consumer-defined headers and footers fixed without adding extra public slots.
1180
+ | Slot | Description |
1181
+ | --------- | ---------------------------------------------------------------------------------------------------- |
1182
+ | `banner` | Optional full-width banner above the layout body |
1183
+ | `header` | Sticky content-area header |
1184
+ | `sidebar` | Sidebar-card content. On desktop, the consumer owns its internal fixed regions and scroll container. |
1185
+ | `default` | Main content |
1186
+ | `tabbar` | Bottom tab bar |
1048
1187
 
1049
- At `640px` and below, the sidebar becomes a headless `web-ui-drawer`. The consumer content is rendered in the same rounded sidebar card; the mobile toggle appears in the header row as a glass button. Its left inset defaults to `8px`; align it with your own header padding via `--wui-layout-mobile-toggle-inset`.
1188
+ `web-ui-layout` constrains the sidebar card and owns the desktop toggle area, but does not create a desktop sidebar scrollport. To make only part of the desktop sidebar scroll, make the `sidebar` slot root a `height: 100%; min-height: 0` flex column and apply `overflow-y: auto` to the intended child. This keeps consumer-defined headers and footers fixed without adding extra public slots:
1189
+
1190
+ ```html
1191
+ <div slot="sidebar" class="sidebar-root">
1192
+ <div class="sidebar-title">Components</div>
1193
+ <nav class="sidebar-nav">...</nav>
1194
+ </div>
1195
+ ```
1196
+
1197
+ ```css
1198
+ .sidebar-root {
1199
+ display: flex;
1200
+ flex-direction: column;
1201
+ height: 100%;
1202
+ min-height: 0;
1203
+ }
1204
+
1205
+ .sidebar-title {
1206
+ flex-shrink: 0;
1207
+ }
1208
+
1209
+ .sidebar-nav {
1210
+ flex: 1;
1211
+ min-height: 0;
1212
+ overflow-y: auto;
1213
+ }
1214
+ ```
1215
+
1216
+ At `640px` and below, the sidebar becomes a `web-ui-drawer` with its built-in glass body, scrollable content, and drag zone. The layout maps `sidebar-width` to `--wui-drawer-width` and `--wui-layout-sidebar-radius` to `--wui-drawer-radius`. The mobile toggle appears in the header row as a glass button. Its left inset defaults to `8px`; align it with your own header padding via `--wui-layout-mobile-toggle-inset`.
1050
1217
 
1051
1218
  `header-glow` adds a pointer-transparent decorative glow behind header-slot content and the mobile toggle. It is a Header background rather than a foreground layer, so slotted content remains above it. Override its color with `--wui-layout-header-glow-color` (default: `--wui-color-page`). The glow concentration and spread are controlled by the internal variable `--wui-layout-header-glow-height` (default: `150%`); increase for stronger coverage, decrease for a subtler effect. Layout layers are ordered as Header (`10`) < Auxiliary (`20`) < Banner (`30`) < Tabbar (`40`) < Sidebar (`50`).
1052
1219
 
1053
1220
  **CSS Custom Properties:**
1054
1221
 
1055
- | Property | Default | Description |
1056
- | ---------------------------------- | ------- | ------------------------------------------------ |
1057
- | `--wui-layout-sidebar-radius` | `24px` | Border radius of sidebar card (desktop & mobile) |
1058
- | `--wui-layout-mobile-toggle-inset` | `8px` | Left inset of the mobile header toggle |
1222
+ | Property | Default | Description |
1223
+ | ---------------------------------- | --------------------------------- | ------------------------------------------------ |
1224
+ | `--wui-layout-sidebar-radius` | `var(--wui-radius-overlay, 28px)` | Border radius of sidebar card (desktop & mobile) |
1225
+ | `--wui-layout-mobile-toggle-inset` | `8px` | Left inset of the mobile header toggle |
1059
1226
 
1060
1227
  #### `<web-ui-back-top>`
1061
1228
 
@@ -1113,10 +1280,14 @@ Theme provider defining CSS custom property tokens.
1113
1280
  | `appearance` | `'light' \| 'dark' \| 'system'` | `'light'` | Color scheme |
1114
1281
  | `motion` | `'full' \| 'reduced' \| 'system'` | `'system'` | Motion preference for this nested theme scope |
1115
1282
 
1116
- **Methods:** `getOverlayRoot()` — returns the portal overlay container
1283
+ **Methods:** `getOverlayRoot()` — returns this theme-owned overlay root
1284
+
1285
+ **Portal mounting contract:** Every active `<web-ui-theme>` is also the default scoped theme-owned overlay root. Portal-based components without an explicit `overlayContainer` resolve to the nearest active theme's `getOverlayRoot()`; target-less portal calls prefer the root theme's theme-owned overlay root. When no active theme provides a root, they fall back to the global fallback overlay root.
1117
1286
 
1118
1287
  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`.
1119
1288
 
1289
+ 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.
1290
+
1120
1291
  **Foundation tokens:**
1121
1292
 
1122
1293
  | Property | Default | Description |
@@ -1127,6 +1298,14 @@ Defines foundation, color, layer, shadow, and motion tokens for its subtree. `mo
1127
1298
  | `--wui-overlay-min-width` | `200px` | Minimum anchored overlay width |
1128
1299
  | `--wui-focus-ring-width` | `3px` | Focus indicator width |
1129
1300
 
1301
+ **Radius tokens:**
1302
+
1303
+ | Property | Default | Description |
1304
+ | ---------------------- | ---------------------- | --------------------------------------------------------------- |
1305
+ | `--wui-radius-control` | `calc(infinity * 1px)` | Pill radius for small controls (button, input, switch, option…) |
1306
+ | `--wui-radius-menu` | `18px` | Menu/popover floating panels, multiline textarea and toast |
1307
+ | `--wui-radius-overlay` | `28px` | Large overlay surfaces: dialog, drawer, layout sidebar |
1308
+
1130
1309
  **Layer tokens:**
1131
1310
 
1132
1311
  | Property | Default | Description |
@@ -1143,7 +1322,7 @@ Defines foundation, color, layer, shadow, and motion tokens for its subtree. `mo
1143
1322
  | `--wui-layer-toast` | `200` | Toasts |
1144
1323
  | `--wui-layer-loading` | `300` | Blocking loading surfaces |
1145
1324
 
1146
- **Motion tokens:** duration defaults are `--wui-duration-press: 80ms`, `--wui-duration-feedback: 100ms`, `--wui-duration-trigger: 160ms`, `--wui-duration-focus: 200ms`, `--wui-duration-menu-enter: 140ms`, `--wui-duration-menu-exit: 100ms`, `--wui-duration-overlay-enter: 180ms`, `--wui-duration-overlay-exit: 140ms`, `--wui-duration-drawer-enter: 280ms`, `--wui-duration-drawer-exit: 240ms`, `--wui-duration-drawer-nested: 450ms`, `--wui-duration-collapse-enter: 200ms`, `--wui-duration-collapse-exit: 160ms`, `--wui-duration-layout: 200ms`. Easing tokens are `--wui-ease-enter` and `--wui-ease-slide`; enter scale is `--wui-scale-enter: 0.97`. Hover/active background feedback switches instantly with no transition; checked/pressed/focus and overlay enter/exit transitions are unaffected.
1325
+ **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: 160ms`, `--wui-duration-float-exit: 120ms`, `--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`. Easing tokens are `--wui-ease-enter`, `--wui-ease-dialog` (`cubic-bezier(0.2, 0, 0, 1)`), and `--wui-ease-slide`; enter scale is `--wui-scale-enter: 0.97`. Hover/active background feedback switches instantly with no transition; checked/pressed/focus transitions are unaffected.
1147
1326
 
1148
1327
  **Color tokens:**
1149
1328
 
@@ -1190,7 +1369,7 @@ Defines foundation, color, layer, shadow, and motion tokens for its subtree. `mo
1190
1369
  | `--wui-shadow-panel` | `0 3px 9px rgb(0 0 0 / 0.27)` | `0 4px 16px rgb(0 0 0 / 0.32)` | Small floating panel shadow |
1191
1370
  | `--wui-shadow-glass` | four-layer diffuse shadow | `0 8px 24px rgb(0 0 0 / 0.08)` | Base liquid glass shadow |
1192
1371
 
1193
- **Glass effect tokens:** `--wui-glass-brightness` is `1.06` in light mode and `1.02` in dark mode. `--wui-glass-corner-radius` defaults to `32px`; glass components set it from their own corner radius so the diagonal border lighting aligns with their shape.
1372
+ **Glass effect tokens:** `--wui-glass-brightness` is `1.06` in light mode and `1.02` in dark mode. `--wui-glass-corner-radius` defaults to `32px`; non-pill glass surfaces set it from their own semantic radius (`--wui-radius-menu` / `--wui-radius-overlay`, or `--wui-drawer-radius` for the drawer) so the diagonal border lighting follows token overrides. Pill controls keep a finite corner radius derived from their physical size or internal size token instead, because the lighting gradient needs a physical size.
1194
1373
 
1195
1374
  **Internal tokens:** variables prefixed `--wui-internal-*` are private wiring between shadow DOM parts; they are not part of the public token API and must not be overridden by consumers.
1196
1375
 
@@ -1228,17 +1407,17 @@ toast.updateMessage(id, { message: 'Upload 60% complete', heading: 'Uploading' }
1228
1407
 
1229
1408
  **ToastOptions:**
1230
1409
 
1231
- | Option | Type | Default | Description |
1232
- | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------- |
1233
- | `message` | `string` | — | Notification text |
1234
- | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
1235
- | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
1236
- | `closable` | `boolean` | `true` | Show close button |
1237
- | `id` | `string` | auto | Deduplication identifier |
1238
- | `heading` | `string` | `''` | Bold heading text |
1239
- | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
1240
- | `target` | `Element` | — | Used to find nearest theme scope |
1241
- | `container` | `HTMLElement` | — | Explicit mount container (highest priority) |
1410
+ | Option | Type | Default | Description |
1411
+ | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------- |
1412
+ | `message` | `string` | — | Notification text |
1413
+ | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
1414
+ | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
1415
+ | `closable` | `boolean` | `true` | Show close button |
1416
+ | `id` | `string` | auto | Deduplication identifier |
1417
+ | `heading` | `string` | `''` | Bold heading text |
1418
+ | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
1419
+ | `target` | `Element` | — | Used to find nearest theme-owned overlay root |
1420
+ | `container` | `HTMLElement` | — | Explicit mount container (highest priority) |
1242
1421
 
1243
1422
  **`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 }`.
1244
1423
 
@@ -1,5 +1,5 @@
1
1
  //#region src/assets/glass.css?inline
2
- var e = ".wui-glass{--wui-internal-glass-focus-ring:0 0 0 0 transparent;--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-focus-ring,0 0 0 0 transparent), 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,#3333331f);background-image:radial-gradient(var(--wui-glass-corner-radius,32px) at 100% 0, var(--wui-color-glass-shade,#0003), transparent), radial-gradient(var(--wui-glass-corner-radius,32px) at 0 100%, var(--wui-color-glass-shade,#0003), 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-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 #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-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-focus-ring: 0 0 0 0 transparent;\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 导入不做自动前缀,需手动写前缀版本 */\n /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\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(--wui-internal-glass-focus-ring, 0 0 0 0 transparent),\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-composite 旧语法覆盖 Chrome 111-119,\n * 无前缀 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, rgb(51 51 51 / 0.12));\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.2)),\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.2)),\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 /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\n"],"mappings":""}
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 导入不做自动前缀,需手动写前缀版本 */\n /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\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-composite 旧语法覆盖 Chrome 111-119,\n * 无前缀 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 /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n mask:\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0);\n /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-mask-composite: xor;\n mask-composite: exclude;\n}\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  //#region src/assets/menu-portal.css?inline
2
- var e = ".wui-menu-portal-overlay.wui-glass{--wui-internal-glass-shadow:var(--wui-shadow-panel,0 3px 9px #00000045);--wui-internal-duration-overlay-enter:var(--wui-duration-menu-enter,.14s);--wui-internal-duration-overlay-exit:var(--wui-duration-menu-exit,.1s);-webkit-user-select:none;user-select:none;z-index:var(--wui-layer-menu,100);box-sizing:border-box;min-width:var(--wui-overlay-min-width,200px);max-width:90vw;max-height:80vh;color:var(--wui-color-text,#1b1b1b);background-color:var(--wui-color-surface-menu,#f6f6f6d1);border-radius:20px;position:fixed;overflow:hidden}.wui-menu-portal-overlay.wui-glass .wui-menu-scroll{max-height:80vh;overflow-y:auto}.wui-menu-portal-overlay.wui-glass .wui-menu-content{padding:4px}.wui-menu-portal-overlay.wui-glass.dropdown-overlay .wui-menu-scroll{max-height:320px}.wui-menu-portal-overlay.wui-glass.dropdown-overlay[data-level]:not([data-level=\"0\"]),.wui-menu-portal-overlay.wui-glass.context-submenu{z-index:var(--wui-layer-menu-nested,110)}";
2
+ var e = ".wui-menu-portal-overlay.wui-floating-panel{--wui-internal-glass-shadow:var(--wui-shadow-panel,0 3px 9px #00000045);--wui-internal-duration-float-enter:var(--wui-duration-float-enter,.16s);--wui-internal-duration-float-exit:var(--wui-duration-float-exit,.12s);--wui-glass-corner-radius:var(--wui-radius-menu,18px);-webkit-user-select:none;user-select:none;z-index:var(--wui-layer-menu,100);box-sizing:border-box;min-width:var(--wui-overlay-min-width,200px);border-radius:var(--wui-radius-menu,18px);max-width:90vw;max-height:80vh;color:var(--wui-color-text,#1b1b1b);background-color:var(--wui-color-surface-menu,#f6f6f6d1);position:fixed;overflow:hidden}.wui-menu-portal-overlay.wui-floating-panel .wui-menu-scroll{max-height:80vh;overflow-y:auto}.wui-menu-portal-overlay.wui-floating-panel .wui-menu-content{padding:4px}.wui-menu-portal-overlay.wui-floating-panel.dropdown-overlay .wui-menu-scroll{max-height:320px}.wui-menu-portal-overlay.wui-floating-panel.dropdown-overlay[data-level]:not([data-level=\"0\"]),.wui-menu-portal-overlay.wui-floating-panel.context-submenu{z-index:var(--wui-layer-menu-nested,110)}";
3
3
  //#endregion
4
4
  export { e as default };
5
5