@greypan/web-ui 6.1.0 → 6.3.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 (129) hide show
  1. package/README.CN.md +209 -80
  2. package/README.md +189 -60
  3. package/dist/assets/menu-portal.js +1 -1
  4. package/dist/assets/menu-portal.js.map +1 -1
  5. package/dist/assets/overlay-motion.js +1 -1
  6. package/dist/assets/overlay-motion.js.map +1 -1
  7. package/dist/components/autocomplete/index.js +54 -53
  8. package/dist/components/autocomplete/index.js.map +1 -1
  9. package/dist/components/autocomplete/style.js +1 -1
  10. package/dist/components/autocomplete/style.js.map +1 -1
  11. package/dist/components/button/index.js +36 -32
  12. package/dist/components/button/index.js.map +1 -1
  13. package/dist/components/button/style.js +1 -1
  14. package/dist/components/button/style.js.map +1 -1
  15. package/dist/components/collapse/index.d.ts +29 -1
  16. package/dist/components/collapse/index.js +92 -30
  17. package/dist/components/collapse/index.js.map +1 -1
  18. package/dist/components/collapse/style.js +1 -1
  19. package/dist/components/collapse/style.js.map +1 -1
  20. package/dist/components/context-menu/index.d.ts +1 -0
  21. package/dist/components/context-menu/index.js +80 -76
  22. package/dist/components/context-menu/index.js.map +1 -1
  23. package/dist/components/dialog/index.js +6 -2
  24. package/dist/components/dialog/index.js.map +1 -1
  25. package/dist/components/dialog/style.js +1 -1
  26. package/dist/components/dialog/style.js.map +1 -1
  27. package/dist/components/drawer/index.js +24 -14
  28. package/dist/components/drawer/index.js.map +1 -1
  29. package/dist/components/drawer/style.js +1 -1
  30. package/dist/components/drawer/style.js.map +1 -1
  31. package/dist/components/dropdown/index.d.ts +1 -0
  32. package/dist/components/dropdown/index.js +65 -60
  33. package/dist/components/dropdown/index.js.map +1 -1
  34. package/dist/components/image-preview/index.d.ts +58 -0
  35. package/dist/components/image-preview/index.js +481 -0
  36. package/dist/components/image-preview/index.js.map +1 -0
  37. package/dist/components/image-preview/style.js +6 -0
  38. package/dist/components/image-preview/style.js.map +1 -0
  39. package/dist/components/index.d.ts +1 -0
  40. package/dist/components/index.js +20 -19
  41. package/dist/components/input/style.js +1 -1
  42. package/dist/components/input/style.js.map +1 -1
  43. package/dist/components/input-number/style.js +1 -1
  44. package/dist/components/input-number/style.js.map +1 -1
  45. package/dist/components/layout/index.d.ts +1 -1
  46. package/dist/components/layout/index.js +5 -3
  47. package/dist/components/layout/index.js.map +1 -1
  48. package/dist/components/layout/style.js +1 -1
  49. package/dist/components/layout/style.js.map +1 -1
  50. package/dist/components/popover/index.d.ts +1 -0
  51. package/dist/components/popover/index.js +53 -39
  52. package/dist/components/popover/index.js.map +1 -1
  53. package/dist/components/popover/style.js.map +1 -1
  54. package/dist/components/radio/index.js +18 -16
  55. package/dist/components/radio/index.js.map +1 -1
  56. package/dist/components/radio/style.js +1 -1
  57. package/dist/components/radio/style.js.map +1 -1
  58. package/dist/components/segmented/index.js +24 -22
  59. package/dist/components/segmented/index.js.map +1 -1
  60. package/dist/components/segmented/style.js +1 -1
  61. package/dist/components/segmented/style.js.map +1 -1
  62. package/dist/components/segmented-trigger/index.js +17 -15
  63. package/dist/components/segmented-trigger/index.js.map +1 -1
  64. package/dist/components/segmented-trigger/style.js +1 -1
  65. package/dist/components/segmented-trigger/style.js.map +1 -1
  66. package/dist/components/select/index.js +43 -38
  67. package/dist/components/select/index.js.map +1 -1
  68. package/dist/components/select/style.js +1 -1
  69. package/dist/components/select/style.js.map +1 -1
  70. package/dist/components/slider/index.js +32 -30
  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.js +1 -1
  75. package/dist/components/svg-draw-lines/index.js.map +1 -1
  76. package/dist/components/switch/index.js +25 -23
  77. package/dist/components/switch/index.js.map +1 -1
  78. package/dist/components/switch/style.js +1 -1
  79. package/dist/components/switch/style.js.map +1 -1
  80. package/dist/components/textarea/style.js +1 -1
  81. package/dist/components/textarea/style.js.map +1 -1
  82. package/dist/components/theme/index.js +1 -1
  83. package/dist/components/theme/index.js.map +1 -1
  84. package/dist/components/theme/style.js +1 -1
  85. package/dist/components/theme/style.js.map +1 -1
  86. package/dist/components/toast/manager.js +2 -2
  87. package/dist/components/toast/manager.js.map +1 -1
  88. package/dist/components/toast/style.js +1 -1
  89. package/dist/components/toast/style.js.map +1 -1
  90. package/dist/components/tooltip/index.d.ts +1 -0
  91. package/dist/components/tooltip/index.js +37 -33
  92. package/dist/components/tooltip/index.js.map +1 -1
  93. package/dist/components/tooltip/style.js.map +1 -1
  94. package/dist/icons/generated/radix-icons-reset.d.ts +6 -0
  95. package/dist/icons/generated/radix-icons-reset.js +10 -0
  96. package/dist/icons/generated/radix-icons-reset.js.map +1 -0
  97. package/dist/icons/index.d.ts +1 -0
  98. package/dist/icons/index.js +2 -1
  99. package/dist/shared/focus/pointer-focus.js +37 -0
  100. package/dist/shared/focus/pointer-focus.js.map +1 -0
  101. package/dist/shared/gesture/drag-gesture.js +53 -30
  102. package/dist/shared/gesture/drag-gesture.js.map +1 -1
  103. package/dist/shared/gesture/pinch-gesture.js +84 -0
  104. package/dist/shared/gesture/pinch-gesture.js.map +1 -0
  105. package/dist/shared/group-management/index.js.map +1 -1
  106. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  107. package/dist/shared/option-portal/combobox-shell.js +8 -6
  108. package/dist/shared/option-portal/combobox-shell.js.map +1 -1
  109. package/dist/shared/overlay/anchored-panel.js +33 -30
  110. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  111. package/dist/shared/overlay/composition.js +57 -0
  112. package/dist/shared/overlay/composition.js.map +1 -0
  113. package/dist/shared/overlay/lifecycle.js +41 -0
  114. package/dist/shared/overlay/lifecycle.js.map +1 -0
  115. package/dist/shared/overlay/native-dialog-presence.js +15 -15
  116. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  117. package/dist/shared/{theme → overlay}/overlay-root.js +1 -1
  118. package/dist/shared/overlay/overlay-root.js.map +1 -0
  119. package/dist/shared/overlay/overlay.js +43 -41
  120. package/dist/shared/overlay/overlay.js.map +1 -1
  121. package/dist/shared/overlay/portal.js +2 -2
  122. package/dist/shared/overlay/portal.js.map +1 -1
  123. package/dist/shared/overlay/positioning-generation.js +20 -0
  124. package/dist/shared/overlay/positioning-generation.js.map +1 -0
  125. package/dist/shared/{theme/theme-scope.js → overlay/theme-overlay-scope.js} +2 -2
  126. package/dist/shared/overlay/theme-overlay-scope.js.map +1 -0
  127. package/package.json +5 -5
  128. package/dist/shared/theme/overlay-root.js.map +0 -1
  129. package/dist/shared/theme/theme-scope.js.map +0 -1
package/README.md CHANGED
@@ -289,6 +289,7 @@ All form controls participate in native `FormData`, constraint validation, `form
289
289
  | | [`<web-ui-button-group>`](#web-ui-button-group) |
290
290
  | **Overlay / Modal** | [`<web-ui-dialog>`](#web-ui-dialog) |
291
291
  | | [`<web-ui-drawer>`](#web-ui-drawer) |
292
+ | | [`imagePreview()`](#imagepreview) |
292
293
  | **In-flow Disclosure** | [`<web-ui-collapse>`](#web-ui-collapse) |
293
294
  | **Floating** | [`<web-ui-popover>`](#web-ui-popover) |
294
295
  | | [`<web-ui-tooltip>`](#web-ui-tooltip) |
@@ -405,16 +406,16 @@ ArrowUp/ArrowDown keyboard increments and decrements the value. Empty or `-` inp
405
406
 
406
407
  Select dropdown with option items, keyboard navigation, and portal support.
407
408
 
408
- | Attribute | Type | Default | Description |
409
- | ------------------ | ---------------------------------- | ------- | --------------------------------- |
410
- | `value` | `string` | `''` | Selected value |
411
- | `placeholder` | `string` | `''` | Placeholder text |
412
- | `name` | `string` | `''` | Form field name |
413
- | `disabled` | `boolean` | `false` | Disabled state |
414
- | `required` | `boolean` | `false` | Required validation |
415
- | `portal` | `boolean` | `false` | Render dropdown in theme overlay |
416
- | `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
417
- | `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 |
418
419
 
419
420
  **Events:** `input`, `change`, `open-change` (`CustomEvent<{ open: boolean }>`)
420
421
 
@@ -452,7 +453,7 @@ Editable combobox with input filtering and single option selection.
452
453
  | `readonly` | `boolean` | `false` | Read-only state (no typing, no dropdown) |
453
454
  | `required` | `boolean` | `false` | Required validation |
454
455
  | `allow-custom-value` | `boolean` | `false` | Allow Enter to submit a custom value that is not among candidates |
455
- | `portal` | `boolean` | `false` | Render dropdown in theme overlay |
456
+ | `portal` | `boolean` | `false` | Render dropdown in theme-owned overlay root |
456
457
  | `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
457
458
  | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
458
459
  | `aria-label` | `string` | — | Accessible name |
@@ -498,7 +499,7 @@ Range slider with marks and vertical orientation.
498
499
 
499
500
  **Methods:** `focus()`, `blur()`
500
501
 
501
- 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.
502
503
 
503
504
  **CSS Custom Properties:**
504
505
 
@@ -508,8 +509,9 @@ Supports ArrowLeft/Right/Up/Down, Home/End, PageUp/PageDown keyboard navigation.
508
509
  | `--wui-slider-vertical-height` | `200px` | Vertical slider height |
509
510
  | `--wui-slider-height` | `var(--wui-slider-track-size, 6px)` | Track thickness |
510
511
  | `--wui-slider-track-size` | `6px` | Track size |
511
- | `--wui-slider-thumb-width` | `30px` | Thumb width |
512
- | `--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 |
513
515
  | `--wui-slider-marks-inset` | `0` | Marks inset from edges |
514
516
 
515
517
  #### `<web-ui-checkbox>`
@@ -584,6 +586,8 @@ Form-associated: integrates with native `<form>` via `ElementInternals`.
584
586
 
585
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`.
586
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
+
587
591
  #### `<web-ui-checkbox-group>`
588
592
 
589
593
  Checkbox group managing multiple selection.
@@ -699,11 +703,17 @@ Uses native `<dialog>` with `@cancel` prevention. Escape calls `close()` unless
699
703
 
700
704
  **CSS Custom Properties:**
701
705
 
702
- | Property | Default | Description |
703
- | ------------------------- | ---------------------------------- | -------------------------------------------------------------- |
704
- | `--wui-dialog-max-width` | `360px` | Dialog max width |
705
- | `--wui-dialog-overlay-bg` | `var(--wui-color-backdrop)` | Backdrop background |
706
- | `--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 |
707
717
 
708
718
  #### `<web-ui-drawer>`
709
719
 
@@ -732,7 +742,9 @@ Side drawer using native `<dialog>` with closing animation. In non-headless mode
732
742
 
733
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.
734
744
 
735
- **Drag to close:** With `draggable`, a gray capsule drag bar (4×56px by default, visually centered 10px from the inner edge inside a 32px-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:
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:
736
748
 
737
749
  - Dragging follows the pointer in real time; the backdrop fades proportionally.
738
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.
@@ -750,9 +762,89 @@ Closing keeps the native dialog in the top layer until the `--wui-duration-drawe
750
762
  | `--wui-drawer-radius` | `var(--wui-radius-overlay, 28px)` | Floating card corner radius (non-headless) |
751
763
  | `--wui-drawer-inset` | `8px` | Floating card viewport inset (non-headless); `0` gives edge-to-edge geometry |
752
764
  | `--wui-drawer-overlay-bg` | `rgb(0 0 0 / 0.12)` | Backdrop background |
753
- | `--wui-drawer-drag-zone-size` | `32px` | Drag-to-close hit zone thickness on the inner edge (draggable) |
765
+ | `--wui-drawer-drag-zone-size` | `20px` | Drag-to-close hit zone thickness on the inner edge (draggable) |
754
766
  | `--wui-drawer-drag-bar-thickness` | `4px` | Drag bar capsule thickness (short axis) |
755
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 |
756
848
 
757
849
  ---
758
850
 
@@ -762,12 +854,13 @@ Closing keeps the native dialog in the top layer until the `--wui-duration-drawe
762
854
 
763
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.
764
856
 
765
- | Attribute | Type | Default | Description |
766
- | -------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
767
- | `open` | `boolean` | `false` | Expanded state; self-managed on interaction, `open-change` emits only for user-originated toggles |
768
- | `disabled` | `boolean` | `false` | Ignores trigger clicks and sets `aria-disabled` on the trigger element; expanded content is kept |
769
- | `horizontal` | `boolean` | `false` | Animate width instead of height |
770
- | `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 |
771
864
 
772
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`.
773
866
 
@@ -788,6 +881,38 @@ The initial `open` attribute settles instantly without playing the animation. Ne
788
881
 
789
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.
790
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
+
791
916
  **Known limitations:** the `horizontal` animation reflows content while width changes.
792
917
 
793
918
  ---
@@ -798,15 +923,15 @@ The initial `open` attribute settles instantly without playing the animation. Ne
798
923
 
799
924
  Popover overlay anchored to trigger element.
800
925
 
801
- | Attribute | Type | Default | Description |
802
- | ------------------ | ---------------------------------- | ---------- | ------------------------- |
803
- | `open` | `boolean` | `false` | Popover visibility |
804
- | `disabled` | `boolean` | `false` | Disabled state |
805
- | `placement` | `Placement` | `'bottom'` | Floating UI placement |
806
- | `trigger` | `'click' \| 'hover' \| 'manual'` | `'click'` | Open trigger |
807
- | `offset` | `number` | `8` | Offset from anchor |
808
- | `portal` | `boolean` | `false` | Render in theme overlay |
809
- | `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 |
810
935
 
811
936
  **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
812
937
 
@@ -831,7 +956,7 @@ Tooltip overlay using pointer/focus triggers.
831
956
  | `show-delay` | `number` | `200` | Show delay in ms |
832
957
  | `hide-delay` | `number` | `100` | Hide delay in ms |
833
958
  | `offset` | `number` | `6` | Offset from trigger |
834
- | `portal` | `boolean` | `false` | Render in theme overlay |
959
+ | `portal` | `boolean` | `false` | Render in theme-owned overlay root |
835
960
  | `overlayContainer` | `HTMLElement \| () => HTMLElement` | — | Explicit portal container |
836
961
 
837
962
  **Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
@@ -1030,7 +1155,7 @@ WebUiSpinner.hide() // hide
1030
1155
 
1031
1156
  #### `<web-ui-layout>`
1032
1157
 
1033
- 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.
1034
1159
 
1035
1160
  | Attribute | Type | Default | Description |
1036
1161
  | ------------------- | --------- | --------- | -------------------------------------------------------------------------------------------------- |
@@ -1052,15 +1177,15 @@ Responsive page layout with an optional full-width banner, a collapsible desktop
1052
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.
1053
1178
  - The mobile drawer always supports drag-to-close via its built-in `draggable` drawer.
1054
1179
 
1055
- | Slot | Description |
1056
- | --------- | ----------------------------------------------------------------------------------------- |
1057
- | `banner` | Optional full-width banner above the layout body |
1058
- | `header` | Sticky content-area header |
1059
- | `sidebar` | Sidebar-card content. The consumer owns its internal fixed regions and scroll containers. |
1060
- | `default` | Main content |
1061
- | `tabbar` | Bottom tab bar |
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 |
1062
1187
 
1063
- `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:
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:
1064
1189
 
1065
1190
  ```html
1066
1191
  <div slot="sidebar" class="sidebar-root">
@@ -1088,7 +1213,7 @@ Responsive page layout with an optional full-width banner, a collapsible desktop
1088
1213
  }
1089
1214
  ```
1090
1215
 
1091
- 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`.
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`.
1092
1217
 
1093
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`).
1094
1219
 
@@ -1155,10 +1280,14 @@ Theme provider defining CSS custom property tokens.
1155
1280
  | `appearance` | `'light' \| 'dark' \| 'system'` | `'light'` | Color scheme |
1156
1281
  | `motion` | `'full' \| 'reduced' \| 'system'` | `'system'` | Motion preference for this nested theme scope |
1157
1282
 
1158
- **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.
1159
1286
 
1160
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`.
1161
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
+
1162
1291
  **Foundation tokens:**
1163
1292
 
1164
1293
  | Property | Default | Description |
@@ -1193,7 +1322,7 @@ Defines foundation, color, layer, shadow, and motion tokens for its subtree. `mo
1193
1322
  | `--wui-layer-toast` | `200` | Toasts |
1194
1323
  | `--wui-layer-loading` | `300` | Blocking loading surfaces |
1195
1324
 
1196
- **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.
1197
1326
 
1198
1327
  **Color tokens:**
1199
1328
 
@@ -1278,17 +1407,17 @@ toast.updateMessage(id, { message: 'Upload 60% complete', heading: 'Uploading' }
1278
1407
 
1279
1408
  **ToastOptions:**
1280
1409
 
1281
- | Option | Type | Default | Description |
1282
- | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------- |
1283
- | `message` | `string` | — | Notification text |
1284
- | `type` | `'success' \| 'info' \| 'warning' \| 'error'` | `'info'` | Toast type |
1285
- | `duration` | `number` | `3000` (`5000` for error) | Auto-close duration (0 = no auto-close) |
1286
- | `closable` | `boolean` | `true` | Show close button |
1287
- | `id` | `string` | auto | Deduplication identifier |
1288
- | `heading` | `string` | `''` | Bold heading text |
1289
- | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'top-right'` | Screen position |
1290
- | `target` | `Element` | — | Used to find nearest theme scope |
1291
- | `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) |
1292
1421
 
1293
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 }`.
1294
1423
 
@@ -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);--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-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
 
@@ -1 +1 @@
1
- {"version":3,"file":"menu-portal.js","names":[],"sources":["../../src/assets/menu-portal.css?inline"],"sourcesContent":["/* 双类选择器覆盖 .wui-glass { position: relative },保持 fixed 定位。 */\n.wui-menu-portal-overlay.wui-glass {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-internal-duration-overlay-enter: var(--wui-duration-menu-enter, 140ms);\n --wui-internal-duration-overlay-exit: var(--wui-duration-menu-exit, 100ms);\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n user-select: none;\n\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n\n overflow: hidden;\n\n box-sizing: border-box;\n min-width: var(--wui-overlay-min-width, 200px);\n max-width: 90vw;\n max-height: 80vh;\n border-radius: var(--wui-radius-menu, 18px);\n\n color: var(--wui-color-text, #1b1b1b);\n\n background-color: var(--wui-color-surface-menu, rgb(246 246 246 / 0.82));\n\n & .wui-menu-scroll {\n overflow-y: auto;\n max-height: 80vh;\n }\n\n & .wui-menu-content {\n padding: 4px;\n }\n\n &.dropdown-overlay {\n & .wui-menu-scroll {\n max-height: 320px;\n }\n\n &[data-level]:not([data-level='0']) {\n z-index: var(--wui-layer-menu-nested, 110);\n }\n }\n\n &.context-submenu {\n z-index: var(--wui-layer-menu-nested, 110);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"menu-portal.js","names":[],"sources":["../../src/assets/menu-portal.css?inline"],"sourcesContent":["/* 菜单面板:wui-glass 玻璃(背景/描边/阴影)由面板自身承担,随面板 opacity 与\n backdrop-filter 插值一起过渡(单层玻璃,见 overlay-motion.css)。\n 选择器用 .wui-menu-portal-overlay.wui-floating-panel(0,2,0)压制 .wui-glass\n (0,1,0):面板无自有 shadow root,直接挂在宿主 shadow(overlay root 的 \\3c style>\n 或 dialog 的 adopted stylesheets)里,若只用单类选择器,dialog 场景下宿主 adopted\n stylesheets 的 .wui-glass 会在层叠中晚于面板内联 \\3c style> 而胜出(position:relative\n 顶掉 fixed、背景/阴影变量回弱默认)。 */\n.wui-menu-portal-overlay.wui-floating-panel {\n --wui-internal-glass-shadow: var(--wui-shadow-panel, 0 3px 9px rgb(0 0 0 / 0.27));\n --wui-internal-duration-float-enter: var(--wui-duration-float-enter, 160ms);\n --wui-internal-duration-float-exit: var(--wui-duration-float-exit, 120ms);\n --wui-glass-corner-radius: var(--wui-radius-menu, 18px);\n user-select: none;\n\n position: fixed;\n z-index: var(--wui-layer-menu, 100);\n\n overflow: hidden;\n\n box-sizing: border-box;\n min-width: var(--wui-overlay-min-width, 200px);\n max-width: 90vw;\n max-height: 80vh;\n border-radius: var(--wui-radius-menu, 18px);\n\n color: var(--wui-color-text, #1b1b1b);\n\n /* 玻璃背景:面板自身 overflow:hidden 不裁自身的 box-shadow(box-shadow 不受\n 元素自身 overflow 影响)。 */\n background-color: var(--wui-color-surface-menu, rgb(246 246 246 / 0.82));\n\n & .wui-menu-scroll {\n overflow-y: auto;\n max-height: 80vh;\n }\n\n & .wui-menu-content {\n padding: 4px;\n }\n\n &.dropdown-overlay {\n & .wui-menu-scroll {\n max-height: 320px;\n }\n\n &[data-level]:not([data-level='0']) {\n z-index: var(--wui-layer-menu-nested, 110);\n }\n }\n\n &.context-submenu {\n z-index: var(--wui-layer-menu-nested, 110);\n }\n}\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  //#region src/assets/overlay-motion.css?inline
2
- var e = ".wui-floating-panel{transform-origin:var(--wui-internal-overlay-transform-origin,center);opacity:1;transition:opacity var(--wui-internal-duration-overlay-enter,var(--wui-duration-overlay-enter,.18s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1)), transform var(--wui-internal-duration-overlay-enter,var(--wui-duration-overlay-enter,.18s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1));transform:scale(1)}.wui-floating-panel[data-wui-presence=entering],.wui-floating-panel[data-wui-presence=closing]{transform:scale(var(--wui-scale-enter,.97));opacity:0}.wui-floating-panel[data-wui-presence=closing]{pointer-events:none;transition-duration:var(--wui-internal-duration-overlay-exit,var(--wui-duration-overlay-exit,.14s))}@media (prefers-reduced-motion:reduce){.wui-floating-panel,.wui-floating-panel[data-wui-presence=entering],.wui-floating-panel[data-wui-presence=closing]{transition-duration:.12s;transform:none}}";
2
+ var e = ".wui-floating-panel{transform-origin:var(--wui-internal-overlay-transform-origin,center);opacity:1;-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));transition:opacity var(--wui-internal-duration-float-enter,var(--wui-duration-float-enter,.16s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1)), -webkit-backdrop-filter var(--wui-internal-duration-float-enter,var(--wui-duration-float-enter,.16s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1)), backdrop-filter var(--wui-internal-duration-float-enter,var(--wui-duration-float-enter,.16s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1)), transform var(--wui-internal-duration-float-enter,var(--wui-duration-float-enter,.16s)) var(--wui-ease-enter,cubic-bezier(.23, 1, .32, 1));transform:scale(1)}.wui-floating-panel[data-wui-presence=entering],.wui-floating-panel[data-wui-presence=closing]{transform:scale(var(--wui-scale-enter,.97));opacity:0;-webkit-backdrop-filter:blur(0) saturate(160%) brightness(var(--wui-glass-brightness,1.06));backdrop-filter:blur(0) saturate(160%) brightness(var(--wui-glass-brightness,1.06))}.wui-floating-panel[data-wui-presence=closing]{pointer-events:none;transition-duration:var(--wui-internal-duration-float-exit,var(--wui-duration-float-exit,.12s))}@media (prefers-reduced-motion:reduce){.wui-floating-panel,.wui-floating-panel[data-wui-presence=entering],.wui-floating-panel[data-wui-presence=closing]{transition-duration:.12s;transform:none}}";
3
3
  //#endregion
4
4
  export { e as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"overlay-motion.js","names":[],"sources":["../../src/assets/overlay-motion.css?inline"],"sourcesContent":[".wui-floating-panel {\n transform-origin: var(--wui-internal-overlay-transform-origin, center);\n transform: scale(1);\n opacity: 1;\n transition:\n opacity var(--wui-internal-duration-overlay-enter, var(--wui-duration-overlay-enter, 180ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n transform var(--wui-internal-duration-overlay-enter, var(--wui-duration-overlay-enter, 180ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1));\n}\n\n.wui-floating-panel[data-wui-presence='entering'],\n.wui-floating-panel[data-wui-presence='closing'] {\n transform: scale(var(--wui-scale-enter, 0.97));\n opacity: 0;\n}\n\n.wui-floating-panel[data-wui-presence='closing'] {\n pointer-events: none;\n transition-duration: var(--wui-internal-duration-overlay-exit, var(--wui-duration-overlay-exit, 140ms));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .wui-floating-panel,\n .wui-floating-panel[data-wui-presence='entering'],\n .wui-floating-panel[data-wui-presence='closing'] {\n transform: none;\n transition-duration: 120ms;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"overlay-motion.js","names":[],"sources":["../../src/assets/overlay-motion.css?inline"],"sourcesContent":["/*\n * 浮层面板共享动效(单层玻璃)。\n *\n * backdrop-filter 的 blur(0px) ↔ blur(4px) 在 Chrome/Safari 均平滑插值(Manager\n * 双引擎实测,Safari 逐帧采样 blur 值连续),当年「生硬跳变」的根因是 none↔blur\n * 不可插值,而不是 backdrop-filter 不可动画。因此面板自身直接承担玻璃:背景、\n * 阴影与 backdrop-filter 都在面板元素上(wui-glass 类也回面板),打开/关闭时\n * opacity 与 backdrop-filter 同步过渡,无需双层结构——双层带来的全部层间问题\n * (变白、关闭残影、双重模糊、阴影作用域承接)一并消失。\n */\n\n.wui-floating-panel {\n transform-origin: var(--wui-internal-overlay-transform-origin, center);\n transform: scale(1);\n\n opacity: 1;\n\n /* 玻璃由面板自身承担(wui-glass 提供背景/描边/阴影)。打开目标态 blur(4px),\n entering/closing 切到 blur(0px) 作为插值起点——blur(0px) 与 blur(4px) 平滑过渡\n (none↔blur 不可插值才是当年生硬跳变的根因)。 */\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\n transition:\n opacity var(--wui-internal-duration-float-enter, var(--wui-duration-float-enter, 160ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n -webkit-backdrop-filter var(--wui-internal-duration-float-enter, var(--wui-duration-float-enter, 160ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n backdrop-filter var(--wui-internal-duration-float-enter, var(--wui-duration-float-enter, 160ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1)),\n transform var(--wui-internal-duration-float-enter, var(--wui-duration-float-enter, 160ms))\n var(--wui-ease-enter, cubic-bezier(0.23, 1, 0.32, 1));\n}\n\n.wui-floating-panel[data-wui-presence='entering'],\n.wui-floating-panel[data-wui-presence='closing'] {\n transform: scale(var(--wui-scale-enter, 0.97));\n opacity: 0;\n\n /* stylelint-disable-next-line property-no-vendor-prefix */\n -webkit-backdrop-filter: blur(0) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n backdrop-filter: blur(0) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n}\n\n.wui-floating-panel[data-wui-presence='closing'] {\n pointer-events: none;\n transition-duration: var(--wui-internal-duration-float-exit, var(--wui-duration-float-exit, 120ms));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .wui-floating-panel,\n .wui-floating-panel[data-wui-presence='entering'],\n .wui-floating-panel[data-wui-presence='closing'] {\n transform: none;\n transition-duration: 120ms;\n }\n}\n"],"mappings":""}