@greypan/web-ui 8.1.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.CN.md +246 -133
- package/README.md +246 -133
- package/dist/assets/glass.js +1 -1
- package/dist/assets/glass.js.map +1 -1
- package/dist/assets/menu-portal.js +1 -1
- package/dist/assets/menu-portal.js.map +1 -1
- package/dist/assets/selection-control.js +1 -1
- package/dist/assets/selection-control.js.map +1 -1
- package/dist/assets/toast-containers.js +1 -1
- package/dist/assets/toast-containers.js.map +1 -1
- package/dist/components/autocomplete/index.d.ts +3 -2
- package/dist/components/autocomplete/index.js +40 -44
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/autocomplete/style.js +1 -1
- package/dist/components/autocomplete/style.js.map +1 -1
- package/dist/components/avatar/index.js.map +1 -1
- package/dist/components/avatar/style.js +1 -1
- package/dist/components/avatar/style.js.map +1 -1
- package/dist/components/back-top/style.js +1 -1
- package/dist/components/back-top/style.js.map +1 -1
- package/dist/components/badge/style.js +1 -1
- package/dist/components/badge/style.js.map +1 -1
- package/dist/components/button/style.js +1 -1
- package/dist/components/button/style.js.map +1 -1
- package/dist/components/button-group/index.js +0 -1
- package/dist/components/button-group/index.js.map +1 -1
- package/dist/components/button-group/style.js +1 -1
- package/dist/components/button-group/style.js.map +1 -1
- package/dist/components/checkbox/index.js +4 -4
- package/dist/components/checkbox/index.js.map +1 -1
- package/dist/components/checkbox-group/style.js +1 -1
- package/dist/components/checkbox-group/style.js.map +1 -1
- package/dist/components/context-menu/index.d.ts +167 -3
- package/dist/components/context-menu/index.js +162 -91
- package/dist/components/context-menu/index.js.map +1 -1
- package/dist/components/context-menu/scrim.js +6 -0
- package/dist/components/context-menu/scrim.js.map +1 -0
- package/dist/components/context-menu/style.js +1 -1
- package/dist/components/context-menu/style.js.map +1 -1
- package/dist/components/dialog/index.d.ts +9 -0
- package/dist/components/dialog/index.js +61 -46
- package/dist/components/dialog/index.js.map +1 -1
- package/dist/components/dialog/style.js +1 -1
- package/dist/components/dialog/style.js.map +1 -1
- package/dist/components/drawer/index.d.ts +45 -0
- package/dist/components/drawer/index.js +116 -52
- package/dist/components/drawer/index.js.map +1 -1
- package/dist/components/drawer/style.js +1 -1
- package/dist/components/drawer/style.js.map +1 -1
- package/dist/components/dropdown/index.d.ts +12 -0
- package/dist/components/dropdown/index.js +13 -10
- package/dist/components/dropdown/index.js.map +1 -1
- package/dist/components/dropdown-divider/style.js +1 -1
- package/dist/components/dropdown-divider/style.js.map +1 -1
- package/dist/components/dropdown-header/style.js +1 -1
- package/dist/components/dropdown-header/style.js.map +1 -1
- package/dist/components/dropdown-item/style.js +1 -1
- package/dist/components/dropdown-item/style.js.map +1 -1
- package/dist/components/editable-text/index.d.ts +39 -0
- package/dist/components/editable-text/index.js +6 -2
- package/dist/components/editable-text/index.js.map +1 -1
- package/dist/components/empty/index.d.ts +21 -4
- package/dist/components/empty/index.js +37 -32
- package/dist/components/empty/index.js.map +1 -1
- package/dist/components/empty/style.js +1 -1
- package/dist/components/empty/style.js.map +1 -1
- package/dist/components/image-preview/index.js +9 -2
- package/dist/components/image-preview/index.js.map +1 -1
- package/dist/components/image-preview/style.js +1 -1
- package/dist/components/image-preview/style.js.map +1 -1
- package/dist/components/index.js +23 -23
- package/dist/components/input/style.js +1 -1
- package/dist/components/input/style.js.map +1 -1
- package/dist/components/input-number/style.js +1 -1
- package/dist/components/input-number/style.js.map +1 -1
- package/dist/components/layout/index.d.ts +0 -3
- package/dist/components/layout/index.js +1 -1
- package/dist/components/layout/index.js.map +1 -1
- package/dist/components/layout/style.js +1 -1
- package/dist/components/layout/style.js.map +1 -1
- package/dist/components/option/style.js +1 -1
- package/dist/components/option/style.js.map +1 -1
- package/dist/components/popover/index.js.map +1 -1
- package/dist/components/popover/style.js +1 -1
- package/dist/components/popover/style.js.map +1 -1
- package/dist/components/radio/style.js +1 -1
- package/dist/components/radio/style.js.map +1 -1
- package/dist/components/radio-group/style.js +1 -1
- package/dist/components/radio-group/style.js.map +1 -1
- package/dist/components/segmented/index.d.ts +5 -0
- package/dist/components/segmented/index.js +45 -28
- package/dist/components/segmented/index.js.map +1 -1
- package/dist/components/segmented/style.js +1 -1
- package/dist/components/segmented/style.js.map +1 -1
- package/dist/components/segmented-trigger/style.js +1 -1
- package/dist/components/segmented-trigger/style.js.map +1 -1
- package/dist/components/select/index.js +0 -1
- package/dist/components/select/index.js.map +1 -1
- package/dist/components/select/style.js +1 -1
- package/dist/components/select/style.js.map +1 -1
- package/dist/components/slider/style.js +1 -1
- package/dist/components/slider/style.js.map +1 -1
- package/dist/components/spinner/style.js +1 -1
- package/dist/components/spinner/style.js.map +1 -1
- package/dist/components/svg-draw-lines/index.js.map +1 -1
- package/dist/components/switch/style.js +1 -1
- package/dist/components/switch/style.js.map +1 -1
- package/dist/components/textarea/index.js +2 -2
- package/dist/components/textarea/index.js.map +1 -1
- package/dist/components/textarea/style.js +1 -1
- package/dist/components/textarea/style.js.map +1 -1
- package/dist/components/theme/style.js +1 -1
- package/dist/components/theme/style.js.map +1 -1
- package/dist/components/toast/style.js +1 -1
- package/dist/components/toast/style.js.map +1 -1
- package/dist/components/tooltip/index.js.map +1 -1
- package/dist/components/tooltip/style.js +1 -1
- package/dist/components/tooltip/style.js.map +1 -1
- package/dist/icons/generated/akar-icons-circle-check.d.ts +6 -0
- package/dist/icons/generated/akar-icons-circle-check.js +10 -0
- package/dist/icons/generated/akar-icons-circle-check.js.map +1 -0
- package/dist/icons/generated/bi-check-lg.d.ts +6 -0
- package/dist/icons/generated/bi-check-lg.js +10 -0
- package/dist/icons/generated/bi-check-lg.js.map +1 -0
- package/dist/icons/generated/fluent-tag-search-24-regular.d.ts +6 -0
- package/dist/icons/generated/fluent-tag-search-24-regular.js +10 -0
- package/dist/icons/generated/fluent-tag-search-24-regular.js.map +1 -0
- package/dist/icons/generated/mdi-tag-search-outline.d.ts +6 -0
- package/dist/icons/generated/mdi-tag-search-outline.js +10 -0
- package/dist/icons/generated/mdi-tag-search-outline.js.map +1 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/icons/index.js +5 -1
- package/dist/shared/element-height/index.js +47 -0
- package/dist/shared/element-height/index.js.map +1 -0
- package/dist/shared/menu-portal/menu-portal.js +11 -11
- package/dist/shared/menu-portal/menu-portal.js.map +1 -1
- package/dist/shared/menu-portal/menu-tree.js +2 -2
- package/dist/shared/menu-portal/menu-tree.js.map +1 -1
- package/dist/shared/overlay/anchored-panel.js.map +1 -1
- package/dist/shared/overlay/native-dialog-presence.js +23 -11
- package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
- package/dist/shared/overlay/nested-drawer-layers.js +30 -23
- package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
- package/dist/shared/scroll-lock/scroll-lock.js +3 -3
- package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ import '@greypan/web-ui'
|
|
|
25
25
|
// import '@greypan/web-ui/components/button'
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
A subpath import registers only the component it names, so import every tag the page uses.
|
|
29
|
+
|
|
28
30
|
```html
|
|
29
31
|
<web-ui-button variant="primary">Click me</web-ui-button> <web-ui-icon .icon="${lucidePlus}"></web-ui-icon>
|
|
30
32
|
```
|
|
@@ -354,24 +356,24 @@ Text input with clearable, prefix/suffix slots.
|
|
|
354
356
|
|
|
355
357
|
Multi-line text input with auto-resize.
|
|
356
358
|
|
|
357
|
-
| Attribute | Type | Default | Description
|
|
358
|
-
| ----------------- | --------- | ------- |
|
|
359
|
-
| `value` | `string` | `''` | Textarea value
|
|
360
|
-
| `placeholder` | `string` | `''` | Placeholder text
|
|
361
|
-
| `rows` | `number` | `3` | Visible rows
|
|
362
|
-
| `name` | `string` | `''` | Form field name
|
|
363
|
-
| `disabled` | `boolean` | `false` | Disabled state
|
|
364
|
-
| `readonly` | `boolean` | `false` | Read-only state
|
|
365
|
-
| `required` | `boolean` | `false` | Required validation
|
|
366
|
-
| `clearable` | `boolean` | `false` | Show clear button
|
|
367
|
-
| `full` | `boolean` | `false` | Full width
|
|
368
|
-
| `borderless` | `boolean` | `false` | Ghost form: removes border, background, and shadow; keeps padding, height metrics, and focus ring
|
|
369
|
-
| `autosize` | `boolean` | `false` | Auto-resize height
|
|
370
|
-
| `max-height` | `number` | `0` | Autosize max height (px); `0` = unlimited
|
|
371
|
-
| `minlength` | `number` | — | Minimum length validation
|
|
372
|
-
| `maxlength` | `number` | — | Maximum length validation
|
|
373
|
-
| `aria-label` | `string` | — | Accessible label
|
|
374
|
-
| `aria-labelledby` | `string` | — | Accessible label reference
|
|
359
|
+
| Attribute | Type | Default | Description |
|
|
360
|
+
| ----------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------- |
|
|
361
|
+
| `value` | `string` | `''` | Textarea value |
|
|
362
|
+
| `placeholder` | `string` | `''` | Placeholder text |
|
|
363
|
+
| `rows` | `number` | `3` | Visible rows |
|
|
364
|
+
| `name` | `string` | `''` | Form field name |
|
|
365
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
366
|
+
| `readonly` | `boolean` | `false` | Read-only state |
|
|
367
|
+
| `required` | `boolean` | `false` | Required validation |
|
|
368
|
+
| `clearable` | `boolean` | `false` | Show clear button |
|
|
369
|
+
| `full` | `boolean` | `false` | Full width |
|
|
370
|
+
| `borderless` | `boolean` | `false` | Ghost form: removes border, background, and shadow; keeps padding, height metrics, and focus ring |
|
|
371
|
+
| `autosize` | `boolean` | `false` | Auto-resize height; also forces `resize: none`, because the native grip fights a script-owned height |
|
|
372
|
+
| `max-height` | `number` | `0` | Autosize max height (px); `0` = unlimited |
|
|
373
|
+
| `minlength` | `number` | — | Minimum length validation |
|
|
374
|
+
| `maxlength` | `number` | — | Maximum length validation |
|
|
375
|
+
| `aria-label` | `string` | — | Accessible label |
|
|
376
|
+
| `aria-labelledby` | `string` | — | Accessible label reference |
|
|
375
377
|
|
|
376
378
|
**Events:** `input`, `change`, `focus`, `blur`
|
|
377
379
|
|
|
@@ -390,14 +392,15 @@ Multi-line text input with auto-resize.
|
|
|
390
392
|
|
|
391
393
|
Inline plain-text editor: click the text to edit in place, `Enter` and `blur` commit, `Escape` cancels. The text layer and the editing layer share one box, so entering edit mode does not move a single pixel.
|
|
392
394
|
|
|
393
|
-
| Attribute
|
|
394
|
-
|
|
|
395
|
-
| `value`
|
|
396
|
-
| `placeholder`
|
|
397
|
-
| `name`
|
|
398
|
-
| `disabled`
|
|
399
|
-
| `readonly`
|
|
400
|
-
| `
|
|
395
|
+
| Attribute | Type | Default | Description |
|
|
396
|
+
| ------------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
397
|
+
| `value` | `string` | `''` | Current value; the declarative value captured on first connection becomes the `form.reset()` default; later attribute or property changes do not update it |
|
|
398
|
+
| `placeholder` | `string` | `''` | Text shown while the value is empty |
|
|
399
|
+
| `name` | `string` | `''` | Form field name |
|
|
400
|
+
| `disabled` | `boolean` | `false` | Disabled state; behavior only, no visual dimming |
|
|
401
|
+
| `readonly` | `boolean` | `false` | Read-only state; focus, selection, and copying remain available, but edits cannot commit |
|
|
402
|
+
| `commit-on-unmount` | `boolean` | `false` | End an in-progress edit by committing when the element is unmounted; see below |
|
|
403
|
+
| `aria-label` | `string` | — | Accessible label |
|
|
401
404
|
|
|
402
405
|
**Events:** `input` (per keystroke), `change` (commit), `cancel` (cancel; shares its name with the native `<dialog>` `cancel`, so it neither bubbles nor crosses shadow boundaries and is dispatched on the component itself only). React has no synthetic event covering `cancel`: listen with `addEventListener('cancel', ...)`
|
|
403
406
|
|
|
@@ -407,6 +410,18 @@ Clicking places the caret at the clicked offset; keyboard focus places it at the
|
|
|
407
410
|
|
|
408
411
|
`select()` enters edit mode with the whole content selected, and re-selects it when already editing. While `disabled` it does nothing, matching `focus()`. While `readonly`, focus and selection remain available, but input is rejected and leaving edit mode does not dispatch `change`.
|
|
409
412
|
|
|
413
|
+
**Unmounting mid-edit.** What happens when an element being edited is removed from the DOM depends on the engine, and the difference is the reason this property exists:
|
|
414
|
+
|
|
415
|
+
- **Chromium** dispatches `blur` on the editing layer, so a plain `value` commit already happens today and the default `false` leaves that untouched. `blur` and the `change` it triggers are dispatched synchronously _after_ the unmount callback has run, so the commit arrives after whatever state the caller updates in response to the removal has already been applied.
|
|
416
|
+
- **WebKit (Safari, iOS)** dispatches no `blur` at all. With the default `false` nothing is committed and the draft is dropped along with the element.
|
|
417
|
+
- **Gecko (Firefox)** is **unverified**: no Firefox build would start on the machine this was measured on, so nothing is claimed here. Treat Gecko as unknown rather than as matching either column above, and enable the property if you need the unmount commit to be the thing that saves the draft.
|
|
418
|
+
|
|
419
|
+
If your code relies on a draft surviving the row being recycled, **enable `commit-on-unmount`**: on Safari and iOS it is the only thing that saves the draft, and in Chromium it moves the commit ahead of the removal-driven `blur`. In both engines the unmount then ends an open editing session by committing — the draft becomes the new value, edit mode exits, and `change` is dispatched exactly once, from the same code `Enter` and `blur` use. Measured on Chromium and WebKit; the browser test suite runs on Chromium only, so the WebKit result comes from a one-off cross-engine run rather than from CI, and Gecko was not measured at all.
|
|
420
|
+
|
|
421
|
+
Unlike `Enter` and `blur`, which a user action triggers and which therefore always count as an intent to commit, unmount carries no such intent. The commit therefore only fires when the draft really differs from the value captured when editing began, so a session that changed nothing does not produce an empty `change`. An element that is not editing at all has nothing to commit and dispatches nothing, and neither does one that is `disabled` or `readonly` — matching the `blur` path, which dispatches no commit in either state. What happens to a draft that already exists differs between the two, though: under `disabled` the draft stays on the element, while under `readonly` it is genuinely lost. `readonly` does not retract input typed earlier, and on unmount there is no `blur` left to commit it — on WebKit there is no `blur` at all. The path is reachable: enter editing while editable, type a draft, flip to `readonly`, then unmount. It is not treated as a cancel, because cancelling means restoring the value editing started from and there is no focus to return or host to hand a result to at unmount; the user simply sees their typing disappear, with neither `change` nor `cancel`.
|
|
422
|
+
|
|
423
|
+
The commit fires from the unmount callback, so `change` still travels the composed path and reaches listeners on the component or any ancestor, including ancestors that are themselves being unmounted: dispatching does not require an ancestor to still be in the document. Listeners must therefore not assume the component is still connected; hand the result to state outside the host tree explicitly (for example in a `queueMicrotask`) when it has to outlive the removal. Note that this reach is not guaranteed for an unmount React drives from its own render: such a removal never reaches React's delegated event root, so `onChange` will not fire there and the value has to be read from the element instead — the same caveat that applies to `cancel` above. Setting `display: none` on an ancestor is not a removal — the component stays connected and dispatches nothing in either engine, so end the edit explicitly in that case.
|
|
424
|
+
|
|
410
425
|
The host is an inline-level box: it sizes to its content unless a width is set, and grows with wrapped lines. Font, color, text alignment and white space are inherited from the surrounding context, so the component reads as ordinary text until it is edited.
|
|
411
426
|
|
|
412
427
|
The caret follows the shared `--wui-color-accent` semantic token by default; set that token on the host or any ancestor to recolor it. Both layers wrap a long unbroken string (letters and digits with no spaces) at the same points, so a token wider than the box breaks inside the box instead of overflowing it while idle.
|
|
@@ -504,9 +519,9 @@ Editable combobox with input filtering and single option selection.
|
|
|
504
519
|
|
|
505
520
|
**Methods:** `focus()`, `blur()`
|
|
506
521
|
|
|
507
|
-
**Slots:** `default` (project `<web-ui-option>` elements), `trigger` (custom trigger content, replacing the default input)
|
|
522
|
+
**Slots:** `default` (project `<web-ui-option>` elements), `trigger` (custom trigger content, replacing the default input)
|
|
508
523
|
|
|
509
|
-
Typing filters the option list by label (`contains` or `prefix`, or `none` to disable filtering). Selecting an option fills the input with its label and exposes the option's value via `selected-value`; `change` fires on selection commit. Supports ArrowDown/ArrowUp/Enter/Escape keyboard navigation.
|
|
524
|
+
Typing filters the option list by label (`contains` or `prefix`, or `none` to disable filtering). When no option matches, the dropdown stays closed; deleting characters so at least one option matches reopens it. Selecting an option fills the input with its label and exposes the option's value via `selected-value`; `change` fires on selection commit. Supports ArrowDown/ArrowUp/Enter/Escape keyboard navigation.
|
|
510
525
|
|
|
511
526
|
**Trigger:** the default trigger is an internal `web-ui-input`. Put any editable component in the `trigger` slot to replace it. The wrapper keeps the combobox ARIA and marks itself with `data-custom-trigger`, and the dropdown stays anchored to the trigger element. Programmatic `focus()` / `blur()` delegate to the active trigger: `web-ui-input` and `web-ui-textarea` move focus to their native control, while a custom trigger without its own focus redirection is focused as the host itself.
|
|
512
527
|
|
|
@@ -522,9 +537,7 @@ The custom trigger exposes the same contract as the default one: a string `value
|
|
|
522
537
|
|
|
523
538
|
Multiline triggers (a trigger whose editable element is a `<textarea>`) keep Enter for newlines: Enter never selects the highlighted option and never commits a custom value; close the panel with Escape or blur. Selecting an option still writes its label back to the trigger. A single-line custom trigger keeps the default Enter behavior. ArrowUp/ArrowDown follow the same exception while the panel is open: they move the text caret instead of navigating options, so keyboard option navigation is unavailable in that state. Select with a pointer click. With the panel closed, ArrowDown/ArrowUp still open it.
|
|
524
539
|
|
|
525
|
-
When `allow-custom-value` is enabled, pressing Enter with no active option and no matching candidate commits the raw input as a custom value and
|
|
526
|
-
|
|
527
|
-
Use `<div slot="empty">…</div>` for static, non-interactive empty-state content. Portal rendering moves this node into the floating panel and restores it to the host after close.
|
|
540
|
+
When `allow-custom-value` is enabled, pressing Enter with no active option and no matching candidate commits the raw input as a custom value and keeps the dropdown closed. `change` fires and `selected-value` remains empty. The component does not create an option automatically or trim the raw value. Text matching a disabled option cannot bypass the disabled state or derive as a selected option.
|
|
528
541
|
|
|
529
542
|
**CSS Custom Properties:**
|
|
530
543
|
|
|
@@ -681,11 +694,11 @@ Syncs child checkbox `checked` state. `disabled` supplies inherited effective di
|
|
|
681
694
|
|
|
682
695
|
##### Tokens
|
|
683
696
|
|
|
684
|
-
| Token | Default
|
|
685
|
-
| -------------------------- |
|
|
686
|
-
| `--wui-checkbox-group-gap` | `
|
|
697
|
+
| Token | Default | Description |
|
|
698
|
+
| -------------------------- | -------------------- | ----------------------------- |
|
|
699
|
+
| `--wui-checkbox-group-gap` | `var(--wui-space-2)` | Gap between member checkboxes |
|
|
687
700
|
|
|
688
|
-
Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares
|
|
701
|
+
Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares the spacing step for its own subtree, so an override placed above a `<web-ui-theme>` element does not reach the group. Its default now resolves through `--wui-space-2`, so overriding that step on the theme host also moves the gap between members; setting this token still wins over both.
|
|
689
702
|
|
|
690
703
|
```css
|
|
691
704
|
web-ui-checkbox-group {
|
|
@@ -715,11 +728,11 @@ Radio group managing single selection.
|
|
|
715
728
|
|
|
716
729
|
##### Tokens
|
|
717
730
|
|
|
718
|
-
| Token | Default
|
|
719
|
-
| ----------------------- |
|
|
720
|
-
| `--wui-radio-group-gap` | `
|
|
731
|
+
| Token | Default | Description |
|
|
732
|
+
| ----------------------- | -------------------- | ------------------------- |
|
|
733
|
+
| `--wui-radio-group-gap` | `var(--wui-space-2)` | Gap between member radios |
|
|
721
734
|
|
|
722
|
-
Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares
|
|
735
|
+
Set it on the group element itself, or on an ancestor inside the same `<web-ui-theme>` scope. The theme host declares the spacing step for its own subtree, so an override placed above a `<web-ui-theme>` element does not reach the group. Its default now resolves through `--wui-space-2`, so overriding that step on the theme host also moves the gap between members; setting this token still wins over both.
|
|
723
736
|
|
|
724
737
|
```css
|
|
725
738
|
web-ui-radio-group {
|
|
@@ -802,6 +815,7 @@ Modal dialog using native `<dialog>` with `showModal()`.
|
|
|
802
815
|
| Attribute | Type | Default | Description |
|
|
803
816
|
| ------------------- | --------- | ------- | ------------------------------------------------------------------------------ |
|
|
804
817
|
| `open` | `boolean` | `false` | Dialog visibility |
|
|
818
|
+
| `closable` | `boolean` | `false` | Show the built-in close button |
|
|
805
819
|
| `no-scroll-lock` | `boolean` | `false` | Do not lock body scroll when open |
|
|
806
820
|
| `no-backdrop-close` | `boolean` | `false` | Do not close on backdrop click |
|
|
807
821
|
| `no-escape-close` | `boolean` | `false` | Do not close when Escape is pressed |
|
|
@@ -815,22 +829,29 @@ Modal dialog using native `<dialog>` with `showModal()`.
|
|
|
815
829
|
|
|
816
830
|
Uses native `<dialog>` with `@cancel` prevention. Escape calls `close()` unless `no-escape-close` is present. Click on backdrop closes dialog unless `no-backdrop-close` is present. With `controlled`, both only emit the close request instead.
|
|
817
831
|
|
|
832
|
+
With `closable`, a built-in close button is rendered in both content modes, and it follows the same close path as Escape and backdrop clicks (so `controlled` applies to it identically). The two modes place it differently: without a `body` slot the button sits in a `.title-row` flex line next to the title; with a `body` slot the title row does not exist, so the button is a direct child of the glass card and is absolutely positioned at its top-right corner (offset by `--wui-dialog-close-top` / `--wui-dialog-close-right`). It uses the same `ooui:close` icon as the drawer close button. Omitting `closable` renders nothing at all — no button and no extra wrapper.
|
|
833
|
+
|
|
818
834
|
> **Escape ownership:** Escape is arbitrated by a single shared owner, so one keypress closes only the **innermost** open overlay (popover, select, autocomplete, dropdown, context-menu, drawer and dialog all take part). With a select open inside a drawer, the first Escape closes the select and only the second closes the drawer. Sibling overlays that do not nest fall back to open order, closing the most recently opened one. A panel that is still playing its exit transition stays on screen and still catches the Escape, but any overlay that is still open outranks it, so one keypress still closes exactly one layer. `image-preview` takes part as well: its native `<dialog>` is registered with the same arbiter, so Escape is decided by layer order; the component's `cancel` handler only vetoes the native instant close, keeping the top layer until the exit transition finishes.
|
|
819
835
|
|
|
820
836
|
**CSS Custom Properties:**
|
|
821
837
|
|
|
822
|
-
| Property
|
|
823
|
-
|
|
|
824
|
-
| `--wui-dialog-width`
|
|
825
|
-
| `--wui-dialog-max-height`
|
|
826
|
-
| `--wui-dialog-
|
|
827
|
-
| `--wui-dialog-
|
|
828
|
-
| `--wui-dialog-
|
|
829
|
-
| `--wui-dialog-
|
|
830
|
-
| `--wui-dialog-
|
|
831
|
-
| `--wui-dialog-
|
|
832
|
-
| `--wui-dialog-
|
|
833
|
-
| `--wui-dialog-
|
|
838
|
+
| Property | Default | Description |
|
|
839
|
+
| --------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
840
|
+
| `--wui-dialog-width` | `360px` | Dialog width |
|
|
841
|
+
| `--wui-dialog-max-height` | `90vh` / `min(90vh, 90dvh)` (dvh engines) | Max height of the content area (`.desc`), not of the card. Two fallback tiers: `90vh` on the base rule, `min(90vh, 90dvh)` inside `@supports (height: 100dvh)` — an engine without `dvh` support only ever gets `90vh`. See the note below for the chrome conversion |
|
|
842
|
+
| `--wui-dialog-close-top` | `16px` | `closable` button offset from the card top (body mode) |
|
|
843
|
+
| `--wui-dialog-close-right` | `16px` | `closable` button offset from the card right edge (body mode) |
|
|
844
|
+
| `--wui-dialog-overlay-bg` | `var(--wui-color-backdrop)` | Backdrop background |
|
|
845
|
+
| `--wui-dialog-bg` | `var(--wui-color-surface-overlay)` | Glass card background, falls back to `rgb(248 248 248 / 0.92)` |
|
|
846
|
+
| `--wui-dialog-padding` | `20px 24px 24px` | Dialog surface padding |
|
|
847
|
+
| `--wui-dialog-title-gap` | `16px` | Spacing below the title |
|
|
848
|
+
| `--wui-dialog-desc-gap` | `24px` | Spacing below the body content |
|
|
849
|
+
| `--wui-dialog-desc-focus-padding` | `6px` | Focus-ring margin reserved on **all four** sides of the content area. `.desc` is a scroll container and, because one axis must be non-`visible`, a clip box as well, so a control flush against any of its four edges would have its ring clipped by the padding box; the margin covers the `5px` ring (`offset: 2` + `width: 3`) on every axis. Hosts do not need to reserve this themselves. Paired negative margins keep the chrome height unchanged, so this can be retuned to a platform's font metrics without moving the card |
|
|
850
|
+
| `--wui-dialog-footer-gap` | `10px` / `12px` (horizontal) | Spacing between footer buttons |
|
|
851
|
+
| `--wui-dialog-footer-justify` | `flex-end` (default) / `center` (horizontal) | Footer `justify-content`; override to `flex-end` for right-aligned horizontal buttons |
|
|
852
|
+
| `--wui-dialog-scale-enter` | `1.1` | Enter scale start: the panel shrinks in from `1.1` to `1`, and exit reverses it |
|
|
853
|
+
|
|
854
|
+
> **Height semantics:** the token caps the content area, not the card. The title row and the footer stay sized by their content, and anything past the cap scrolls inside `.desc`; the card itself is bounded only by a literal `100vh` / `100dvh` backstop, which catches the case where the viewport is too short to fit chrome plus the cap. A host that wants to bound the **whole card** must still measure its own chrome (card padding + title row + gaps + footer) and subtract it from the token — the three dialogs in the Interweave frontend subtract 142, 142 and 106 respectively. What no longer needs that number is making the content area scroll: before, `.desc` had neither a cap nor `overflow`, so the only element that could scroll was a host-sized inner box, and a host that wanted overflow to scroll had to compute that height itself.
|
|
834
855
|
|
|
835
856
|
#### `<web-ui-drawer>`
|
|
836
857
|
|
|
@@ -859,9 +880,17 @@ Side drawer using native `<dialog>` with closing animation. In non-headless mode
|
|
|
859
880
|
|
|
860
881
|
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.
|
|
861
882
|
|
|
862
|
-
When `closable` is set, the built-in close button is positioned at the header's top-right corner (defaults `16px` down and `
|
|
883
|
+
When `closable` is set, the built-in close button is positioned at the header's top-right corner (defaults `16px` down and `16px` in from the edge) instead of stretching across the drawer body. The `16px` down resolves through `--wui-space-4` so it tracks the drawer's header padding; the `16px` in is the button's own offset to the panel's right edge, which is not the header's horizontal padding (`20px`), so it stays a literal.
|
|
884
|
+
|
|
885
|
+
**Drag to close:** With `draggable`, a gray capsule drag bar (4×56px by default, visually centered 10px from the inner edge) 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:
|
|
863
886
|
|
|
864
|
-
|
|
887
|
+
The visual center follows half of `--wui-drawer-content-padding`, floored at half the bar thickness plus 4px: zeroing that padding to let content fill the panel edge-to-edge still leaves the capsule a visible inset, so it neither crosses the panel's inner edge nor sits flush against it.
|
|
888
|
+
|
|
889
|
+
The capsule is a visual marker only: it is `pointer-events: none` and never swallows a press. What you press is a hit surface. On `left` / `right` that is the drag zone alone. On `top` / `bottom` there are three: the drag zone, a band along the grab edge (exactly as wide as the capsule, and at least `--wui-drawer-drag-edge-size` thick), and the header (`placement=bottom`) or footer (`placement=top`) section in full. The edge band's height is the larger of the capsule's own outer edge and that token, so it covers the capsule's band by construction — pressing the capsule starts a drag and hovering it lights it up, on every placement.
|
|
890
|
+
|
|
891
|
+
The drag zone yields to the header (`placement=bottom`) or the footer (`placement=top`), so that section never pushes the capsule away from the edge — and that section takes the drag itself instead of sitting inert: a press anywhere in it starts a drag, title text included. Controls are kept clear **one by one, at the event layer**, rather than by cutting the hit surface geometrically. A press whose composed path contains an interactive element — native `button` / `input` / `select` / `textarea` / `label` / `a[href]` / `summary`, anything `contenteditable`, an interactive ARIA `role` (`button`, `checkbox`, `switch`, `tab`, `menuitem`, `option`, `slider`, …), or any explicit `tabindex` other than `-1` — reaches the control and starts no drag. A custom control that is not natively interactive has to carry one of those markers to be recognised; adding `role` or `tabindex` is the way in, and it is the right call for accessibility anyway. Geometric cutting was rejected on purpose: it would have to measure each control's position, so it breaks the moment a control moves, and it relocates the A6 defect rather than removing it. The trade is explicit. The built-in close button sits against the panel's right edge, outside the capsule's span, so it stays clickable; a control placed inside the capsule's horizontal span is still covered by the drag surface over the band's height.
|
|
892
|
+
|
|
893
|
+
One consequence follows from the handle occupying that span: a control placed inside it whose top edge falls within the band's height (`--wui-drawer-drag-edge-size`, `20px` by default, or the capsule's own outer edge when that is thicker) has its upper part covered by the drag surface, since the handle is drawn there and a drag has to start where the handle is. `z-index` does not lift it clear — Chromium keeps the band above slotted content — so the levers are `--wui-drawer-drag-bar-length`, which sets the capsule length and the band width together, and `--wui-drawer-drag-edge-size`, which sets the band's thickness.
|
|
865
894
|
|
|
866
895
|
- Dragging follows the pointer in real time; the backdrop fades proportionally.
|
|
867
896
|
- Backdrop click now only honors a genuine tap chain: the press must start on the backdrop itself and the press-to-release travel must stay within the tap magnitude. The click the browser generates for a press–drag–release lands on the common ancestor (`dialog`) regardless of where the press started. Pressing on the panel content or on the backdrop itself and releasing over the backdrop used to close the drawer through that click. The component records the pointerdown origin and validates it on click, so such drag releases always rebound. A `detail`-0 click (keyboard / programmatic) never consumes the pointer record.
|
|
@@ -879,22 +908,29 @@ When `closable` is set, the built-in close button is positioned at the header's
|
|
|
879
908
|
|
|
880
909
|
**CSS Custom Properties:**
|
|
881
910
|
|
|
882
|
-
| Property | Default | Description
|
|
883
|
-
| --------------------------------- | ---------------------------------- |
|
|
884
|
-
| `--wui-drawer-width` | `320px` | Drawer width
|
|
885
|
-
| `--wui-drawer-height` | `300px` | Drawer height (top/bottom)
|
|
886
|
-
| `--wui-drawer-bg` | `var(--wui-color-surface-overlay)` | Drawer body background
|
|
887
|
-
| `--wui-drawer-radius` | `var(--wui-radius-overlay, 28px)` | Floating card corner radius (non-headless)
|
|
888
|
-
| `--wui-drawer-inset` | `8px` | Floating card viewport inset (non-headless); `0` gives edge-to-edge geometry
|
|
889
|
-
| `--wui-drawer-
|
|
890
|
-
| `--wui-drawer-
|
|
891
|
-
| `--wui-drawer-drag-
|
|
892
|
-
| `--wui-drawer-drag-
|
|
893
|
-
| `--wui-drawer-
|
|
894
|
-
| `--wui-drawer-
|
|
895
|
-
| `--wui-drawer-
|
|
896
|
-
| `--wui-drawer-
|
|
897
|
-
| `--wui-drawer-
|
|
911
|
+
| Property | Default | Description |
|
|
912
|
+
| --------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
913
|
+
| `--wui-drawer-width` | `320px` | Drawer width |
|
|
914
|
+
| `--wui-drawer-height` | `300px` | Drawer height (top/bottom) |
|
|
915
|
+
| `--wui-drawer-bg` | `var(--wui-color-surface-overlay)` | Drawer body background |
|
|
916
|
+
| `--wui-drawer-radius` | `var(--wui-radius-overlay, 28px)` | Floating card corner radius (non-headless) |
|
|
917
|
+
| `--wui-drawer-inset` | `8px` | Floating card viewport inset (non-headless); `0` gives edge-to-edge geometry |
|
|
918
|
+
| `--wui-drawer-nested-peek-base` | `43.2px` | Nested-stack reveal step base `A` (`28.8px` at `width <= 640px`); total stack width grows as `A · ln(n)`, so a single layer never moves and each extra layer reveals less. `0` disables the reveal. See the note below. |
|
|
919
|
+
| `--wui-drawer-overlay-bg` | `rgb(0 0 0 / 0.12)` | Backdrop background |
|
|
920
|
+
| `--wui-drawer-drag-zone-size` | `20px` | Drag-to-close hit zone thickness on the inner edge (draggable) |
|
|
921
|
+
| `--wui-drawer-drag-zone-inset` | `var(--wui-space-1, 4px)` | Breathing gap between the drag hit zone and the header (`placement=bottom`) or footer (`placement=top`) it yields to. `0` when that section is absent |
|
|
922
|
+
| `--wui-drawer-drag-bar-thickness` | `4px` | Drag bar capsule thickness (short axis) |
|
|
923
|
+
| `--wui-drawer-drag-bar-length` | `56px` | Drag bar capsule length (along the drawer edge) |
|
|
924
|
+
| `--wui-drawer-drag-edge-size` | `20px` | Thickness of the capsule's hit band along the grab edge (`top` / `bottom` only). Floored by the capsule's own outer edge, so the capsule band is always covered; the default matches `--wui-drawer-drag-zone-size`, so the handle is never harder to hit than the strip beside it |
|
|
925
|
+
| `--wui-drawer-header-padding` | `16px 20px` | Header section padding |
|
|
926
|
+
| `--wui-drawer-close-top` | `16px` | Built-in close button offset from the top of the header |
|
|
927
|
+
| `--wui-drawer-close-right` | `16px` | Built-in close button offset from the right edge of the drawer |
|
|
928
|
+
| `--wui-drawer-content-padding` | `20px` | Content area padding; also drives drag bar visual center (half value, floored at half the bar thickness + 4px) |
|
|
929
|
+
| `--wui-drawer-footer-padding` | `16px 20px` | Footer section padding |
|
|
930
|
+
|
|
931
|
+
`--wui-drawer-nested-peek-base` needs no declaration of yours: the `43.2px` default is the `initialValue` of the property the component registers with `CSS.registerProperty` (in `components/drawer/index.ts`), and narrow viewports get a smaller `28.8px` base from the `@media (width <= 640px)` rule in `components/drawer/style.css`. Those two places are where the shipped defaults live. Setting the token on the host (or any ancestor) overrides both, at any viewport width.
|
|
932
|
+
|
|
933
|
+
With the shipped defaults, a four-layer stack of `320px` drawers reveals `29.94px` / `17.52px` / `12.43px` per step on desktop and `19.96px` / `11.68px` / `8.29px` on narrow viewports, for a total stack width of `59.89px` and `39.93px` respectively. A single layer never moves, because `ln(1)` is `0`.
|
|
898
934
|
|
|
899
935
|
#### `imagePreview()`
|
|
900
936
|
|
|
@@ -1101,10 +1137,12 @@ Tooltip overlay using pointer/focus triggers.
|
|
|
1101
1137
|
|
|
1102
1138
|
Right-click context menu.
|
|
1103
1139
|
|
|
1104
|
-
| Attribute
|
|
1105
|
-
|
|
|
1106
|
-
| `disabled`
|
|
1107
|
-
| `no-scroll-lock`
|
|
1140
|
+
| Attribute | Type | Default | Description |
|
|
1141
|
+
| ------------------ | --------- | ------- | ------------------------------------------------------------- |
|
|
1142
|
+
| `disabled` | `boolean` | `false` | Right-click does not open the menu; no visual dimming |
|
|
1143
|
+
| `no-scroll-lock` | `boolean` | `false` | Allow background scrolling |
|
|
1144
|
+
| `long-press` | `boolean` | `false` | Open on a touch long press (opt-in; mouse uses `contextmenu`) |
|
|
1145
|
+
| `long-press-delay` | `number` | `500` | Hold duration in ms before `long-press` opens the menu |
|
|
1108
1146
|
|
|
1109
1147
|
**Events:** `open-change` (`CustomEvent<{ open: boolean }>`)
|
|
1110
1148
|
|
|
@@ -1112,10 +1150,26 @@ Right-click context menu.
|
|
|
1112
1150
|
|
|
1113
1151
|
**Methods:** `openAt(x: number, y: number)`, `close()`
|
|
1114
1152
|
|
|
1153
|
+
**CSS Custom Properties:**
|
|
1154
|
+
|
|
1155
|
+
| Property | Default | Description |
|
|
1156
|
+
| ------------------------------------- | ---------------------------------- | -------------------------------- |
|
|
1157
|
+
| `--wui-context-menu-safe-area-bottom` | `env(safe-area-inset-bottom, 0px)` | Bottom inset a touch menu clears |
|
|
1158
|
+
|
|
1115
1159
|
Opens on `contextmenu` event. Menu items: `<web-ui-dropdown-item>`, `<web-ui-dropdown-divider>`, `<web-ui-dropdown-header>`. Supports keyboard navigation (Arrow keys, Enter, Escape) and submenu hover with `pointerenter`.
|
|
1116
1160
|
|
|
1161
|
+
While the menu is open it is modal: it renders into a transparent `<dialog>` in the browser's top layer, so nothing on the page below can be clicked or hit-tested. Clicking outside therefore dismisses the menu without also activating whatever was underneath — a row, a checkbox, a link. The scrim is deliberately invisible; it exists to absorb the hit, not to dim the page. One consequence to know about: for the ~160ms exit transition after a menu item is activated, the scrim has already left the top layer, so the page below is briefly clickable again and the menu's stacking level is that of the normal flow.
|
|
1162
|
+
|
|
1163
|
+
`long-press` is opt-in and touch-only: a pointer held for `long-press-delay` (default `500` ms, matching the platform long press) opens the menu, through the same path as a right-click. It only reacts to `pointerType === 'touch'`, and holding while moving more than 10px cancels it, so scrolling never opens a menu. Because the browser also reports that same hold as a native gesture, the `contextmenu` and `click` it emits afterwards are absorbed rather than allowed to reopen — or immediately light-dismiss — the menu the long press just opened.
|
|
1164
|
+
|
|
1165
|
+
A `long-press` menu opens downward from the press point: its top edge sits at the press `y` and it is centred horizontally on the press `x`, which is how native iOS and Android long-press menus appear. When there is not enough room below, the panel flips above the press point rather than running off the screen, and both axes are clamped into the viewport while clearing `--wui-context-menu-safe-area-bottom` so it does not sit under a home indicator. The split is by input, not by viewport width: a narrow window still anchors a mouse right-click at the cursor's top-left corner. Only the horizontal alignment differs — both paths run the same `computePosition` pass, so touch and mouse cannot drift apart.
|
|
1166
|
+
|
|
1167
|
+
`disabled` suppresses menu behaviour only: right-click and `openAt()` do not open a menu. It does not dim or otherwise restyle what you put in the default slot — the component renders no menu surface of its own to dim, and disabling the right-click menu is not the same as disabling your trigger. If a disabled state should read as disabled, render it on your own content.
|
|
1168
|
+
|
|
1117
1169
|
While the menu is open, consumer code may conditionally render, move, or remove menu items (e.g. Vue `v-if`) without re-inserting them at the host element; changes inside the portal are reconciled automatically, and framework anchors are returned to the host on close so subsequent framework patches keep working.
|
|
1118
1170
|
|
|
1171
|
+
On close, focus returns to whatever was focused when the menu opened. That is the browser's own modal-dialog behaviour rather than something the component reimplements, which is also why it only works while the menu is the top layer. Activating a menu item is the one path that hands focus back early — at the click, before the exit transition — so that an item action which moves focus elsewhere (opening an inline editor, say) ends up keeping it: the closing menu never reaches in and pulls focus back out of the new target, so nothing that commits on blur fires a spurious edit.
|
|
1172
|
+
|
|
1119
1173
|
---
|
|
1120
1174
|
|
|
1121
1175
|
### Menu
|
|
@@ -1205,24 +1259,48 @@ Badge / notification count.
|
|
|
1205
1259
|
|
|
1206
1260
|
Empty state placeholder.
|
|
1207
1261
|
|
|
1208
|
-
| Attribute | Type
|
|
1209
|
-
| ------------- |
|
|
1210
|
-
| `title` | `string`
|
|
1211
|
-
| `description` | `string`
|
|
1212
|
-
| `size` | `
|
|
1262
|
+
| Attribute | Type | Default | Description |
|
|
1263
|
+
| ------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
1264
|
+
| `title` | `string` | `''` | Title text |
|
|
1265
|
+
| `description` | `string` | `''` | Description text |
|
|
1266
|
+
| `size` | `number` | `56` | Layout scale in px; invalid or non-positive values become `56` |
|
|
1267
|
+
|
|
1268
|
+
`size` is the one knob for the whole placeholder. It drives the icon container, `min-height`, padding, and the title/description font size, so a large icon no longer sits in medium-sized whitespace:
|
|
1269
|
+
|
|
1270
|
+
| `size` | Icon box | Glyph | `min-height` | Padding block/inline | Title | Description |
|
|
1271
|
+
| ------ | -------- | ------ | ------------ | -------------------- | ------ | ----------- |
|
|
1272
|
+
| `40` | `40px` | `17px` | `171px` | `23px/17px` | `14px` | `13px` |
|
|
1273
|
+
| `56` | `56px` | `24px` | `240px` | `32px/24px` | `16px` | `14px` |
|
|
1274
|
+
| `72` | `72px` | `31px` | `309px` | `41px/31px` | `16px` | `14px` |
|
|
1275
|
+
|
|
1276
|
+
The derived values are `min-height: round(size * 30 / 7)`, `padding-block: round(size * 4 / 7)`, and `padding-inline: round(size * 3 / 7)`; the default glyph is `round(size * 3 / 7)`.
|
|
1277
|
+
|
|
1278
|
+
Font size is one step rather than a scale: below `56` the title is `14px` and the description `13px`, and from `56` up they are `16px` and `14px`. A bigger placeholder gets its presence from whitespace, not from bigger text.
|
|
1279
|
+
|
|
1280
|
+
**Migrating from the `'small' \| 'medium' \| 'large'` enum:** `size` is now a number, and the removed tiers had hand-tuned metrics that these ratios only approach — `small` was `160px` tall with `20px 16px` padding, `large` was `320px` with `48px 32px`. Section margins and content width never followed the tiers.
|
|
1281
|
+
|
|
1282
|
+
| Old value | New value | Default glyph |
|
|
1283
|
+
| --------- | --------- | ------------- |
|
|
1284
|
+
| `small` | `40` | `17` |
|
|
1285
|
+
| `medium` | `56` | `24` |
|
|
1286
|
+
| `large` | `72` | `31` |
|
|
1213
1287
|
|
|
1214
1288
|
**Slots:** `default` (title, overrides `title` prop), `icon`, `description`, `action`
|
|
1215
1289
|
|
|
1216
1290
|
**CSS Custom Properties:**
|
|
1217
1291
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
|
1221
|
-
|
|
|
1222
|
-
| `--wui-empty-
|
|
1223
|
-
| `--wui-empty-
|
|
1224
|
-
| `--wui-empty-
|
|
1225
|
-
| `--wui-empty-
|
|
1292
|
+
Each one overrides the value `size` derives, so all eight stay authoritative — including the two font sizes. Defaults are the values at the default `size` of `56`.
|
|
1293
|
+
|
|
1294
|
+
| Property | Default | Description |
|
|
1295
|
+
| ----------------------------------- | ----------- | ------------------------------------------- |
|
|
1296
|
+
| `--wui-empty-min-height` | `240px` | Min height |
|
|
1297
|
+
| `--wui-empty-padding` | `32px 24px` | Padding |
|
|
1298
|
+
| `--wui-empty-icon-size` | `56px` | Icon container size; overrides `size` |
|
|
1299
|
+
| `--wui-empty-content-width` | `480px` | Max width of title, description, and action |
|
|
1300
|
+
| `--wui-empty-title-margin-top` | `16px` | Space between the icon and the title |
|
|
1301
|
+
| `--wui-empty-title-font-size` | `16px` | Title font size |
|
|
1302
|
+
| `--wui-empty-description-font-size` | `14px` | Description font size |
|
|
1303
|
+
| `--wui-empty-action-margin-top` | `20px` | Space above the action slot |
|
|
1226
1304
|
|
|
1227
1305
|
#### `<web-ui-icon>`
|
|
1228
1306
|
|
|
@@ -1350,16 +1428,17 @@ Responsive page layout with an optional full-width banner, a collapsible desktop
|
|
|
1350
1428
|
|
|
1351
1429
|
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`.
|
|
1352
1430
|
|
|
1353
|
-
`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.
|
|
1431
|
+
`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. The glow takes its colour from `--wui-color-page`, so it follows light and dark appearance on its own; override that property on `web-ui-layout`, on the theme, or anywhere above it to change it. Two pseudo-elements build it. `::before` paints the colour as a `linear-gradient` inside the header box, fading to fully transparent, and `::after` adds a real `backdrop-filter: blur(4px)` layer whose `mask` fades that blur toward zero alpha at the bottom edge. Keeping the two apart means blur strength and colour strength do not pull on each other. Both expand with a negative `margin` rather than a `transform`. Horizontal clipping is owned by `.layout-content`, so the glow never produces a horizontal scrollbar. Layout layers are ordered as Header (`10`) < Auxiliary (`20`) < Banner (`30`) < Tabbar (`40`) < Sidebar (`50`).
|
|
1354
1432
|
|
|
1355
1433
|
The sidebar card surface is `--wui-color-surface-sidebar`. In light mode it keeps its translucent neutral layer; in dark mode it sits one notch above `--wui-color-page`, level with `--wui-color-surface`. It is a separate token because dialog, drawer and toast share `--wui-color-surface-overlay` and may use a different surface.
|
|
1356
1434
|
|
|
1357
1435
|
**CSS Custom Properties:**
|
|
1358
1436
|
|
|
1359
|
-
| Property
|
|
1360
|
-
|
|
|
1361
|
-
| `--wui-layout-sidebar-radius`
|
|
1362
|
-
| `--wui-layout-mobile-toggle-inset`
|
|
1437
|
+
| Property | Default | Description |
|
|
1438
|
+
| ----------------------------------- | ------- | ------------------------------------------------------------------------------------ |
|
|
1439
|
+
| `--wui-layout-sidebar-radius` | `24px` | Border radius of sidebar card (desktop & mobile) |
|
|
1440
|
+
| `--wui-layout-mobile-toggle-inset` | `8px` | Left inset of the mobile header toggle |
|
|
1441
|
+
| `--wui-layout-sidebar-toggle-width` | `44px` | Width of the desktop sidebar collapse toggle; floored at `--wui-control-size` (36px) |
|
|
1363
1442
|
|
|
1364
1443
|
#### `<web-ui-back-top>`
|
|
1365
1444
|
|
|
@@ -1445,19 +1524,54 @@ The host uses `display: contents` and does not paint any background: the library
|
|
|
1445
1524
|
|
|
1446
1525
|
| Property | Default | Description |
|
|
1447
1526
|
| ------------------------- | ------- | ------------------------------------------------ |
|
|
1448
|
-
| `--wui-font-size` | `14px` | Base font size for controls |
|
|
1449
1527
|
| `--wui-input-width` | `200px` | Default width for compact form controls |
|
|
1450
1528
|
| `--wui-control-size` | `36px` | Default height and square min-width for controls |
|
|
1451
1529
|
| `--wui-overlay-min-width` | `200px` | Minimum anchored overlay width |
|
|
1452
1530
|
| `--wui-focus-ring-width` | `3px` | Focus indicator width |
|
|
1453
1531
|
|
|
1532
|
+
**Typography tokens:** font sizes are named by the role the text plays, not by scale position — `caption` for dense chrome labels, `readout` for numeric readouts, the unsuffixed base for body copy, and `title` for bounded card headings. Weights are named by weight, and line heights by how much vertical air the text needs. These are appearance-independent: they stay the same under both light and dark.
|
|
1533
|
+
|
|
1534
|
+
| Property | Default | Description |
|
|
1535
|
+
| ---------------------------- | ------- | --------------------------------------------------------------- |
|
|
1536
|
+
| `--wui-font-size-caption` | `12px` | Dense chrome labels (badge, toast timestamp, menu section head) |
|
|
1537
|
+
| `--wui-font-size-readout` | `13px` | Numeric readouts (image preview count and zoom ratio) |
|
|
1538
|
+
| `--wui-font-size` | `14px` | Base font size for body copy and controls |
|
|
1539
|
+
| `--wui-font-size-title` | `18px` | Bounded card headings (dialog, drawer) |
|
|
1540
|
+
| `--wui-font-weight-medium` | `500` | Medium weight for emphasized inline text |
|
|
1541
|
+
| `--wui-font-weight-semibold` | `600` | Semibold weight for headings |
|
|
1542
|
+
| `--wui-line-height-tight` | `1.2` | Tight leading for large headings |
|
|
1543
|
+
| `--wui-line-height-snug` | `1.4` | Snug leading for mid-size headings |
|
|
1544
|
+
| `--wui-line-height-normal` | `1.5` | Normal leading for body copy |
|
|
1545
|
+
| `--wui-line-height-relaxed` | `1.6` | Relaxed leading for long-form reading |
|
|
1546
|
+
|
|
1547
|
+
`line-height: 1` and `line-height: 0` inside components are intentionally not tokenized: the former vertically centers a single-line control label, the latter collapses a wrapper box onto its content. Neither is typographic leading, and tokenizing them would invite callers to retune them as if they were.
|
|
1548
|
+
|
|
1549
|
+
**Spacing tokens:** a six-step scale on a 4px base, stopping at 24px — the largest spacing value the component layer writes as an _effective_ static rhythm literal. (The one wider padding, `<web-ui-empty>`'s `32px 24px`, is derived in JS from `--wui-empty-size`, so it belongs to that size axis rather than to the rhythm; see the exclusions below.) These do not replace the per-component override tokens (`--wui-button-px`, `--wui-dialog-padding`, and friends) — they act as the fallback default _for_ those tokens, so each component still decides its own padding while the scale says where that value sits in the overall rhythm. Overriding a step therefore gives the embedding application a density lever across every migrated call site — a lever over rhythm, not over structure; see the exclusions below.
|
|
1550
|
+
|
|
1551
|
+
| Property | Default | Description |
|
|
1552
|
+
| --------------- | ------- | ---------------------------------- |
|
|
1553
|
+
| `--wui-space-1` | `4px` | Tight inset (badge, menu) |
|
|
1554
|
+
| `--wui-space-2` | `8px` | Control gap, group gap, inline pad |
|
|
1555
|
+
| `--wui-space-3` | `12px` | Horizontal control padding |
|
|
1556
|
+
| `--wui-space-4` | `16px` | Standard section separation |
|
|
1557
|
+
| `--wui-space-5` | `20px` | Overlay content padding |
|
|
1558
|
+
| `--wui-space-6` | `24px` | Wide overlay inset |
|
|
1559
|
+
|
|
1560
|
+
The scale deliberately omits `1px`, `2px`, `6px`, `7.5px`, `10px`, and negative values. `1px` is the structural width of hairlines and stroke rings; `2px`, `6px`, and `7.5px` are optical corrections; `10px` is a half-step coupling value off the 4px grid; negative margins cancel a flex gap or a button's padding. Each answers a different "why this number", none of them share spacing semantics, and folding them into one ruler would make overriding any one of them disturb the rest.
|
|
1561
|
+
|
|
1562
|
+
A second exclusion is by meaning rather than by value: offsets that align to the viewport edge or to the host's own content edge stay off the scale even when they land on the 4px grid — `--wui-image-preview-edge-gap`, `--wui-toast-viewport-gap`, `--wui-layout-mobile-toggle-inset`, `--wui-back-top-right` / `--wui-back-top-left` / `--wui-back-top-bottom`, `--wui-drawer-inset`, and `--wui-drawer-close-right`. Their reference frame is the screen or the host's content edge, not the rhythm between neighbours, so folding them in would make "tighten `--wui-space-2`" unexpectedly move a component away from the screen edge. `--wui-drawer-close-top` is the counter-example: it does resolve through `--wui-space-4`, because it tracks the drawer's header padding and the two must move together.
|
|
1563
|
+
|
|
1564
|
+
A third exclusion is spacing that a component derives from a **size** prop instead of writing as rhythm: `<web-ui-empty>` scales its `padding`, `min-block-size`, and glyph with `--wui-empty-size`, so its padding is `23px 17px` / `32px 24px` / `41px 31px` at the `40` / `56` / `72` steps. Four of those six values are odd, so a 4px scale cannot represent them at all. The `56` step's `32px 24px` landing on the base is a coincidence of the size choice, not a sign that the value belongs to the rhythm: `56` is the only one of the three that divides by 7, its quotient is exactly 8, and 8 is itself a multiple of 4 — no rounding happens on that step at all. A density lever that is silently inert on two of three sizes and coincidentally live on the third is worse than no lever, and the whole set moves whenever `size` does — so these stay off the scale. Reach for a literal `px` there instead.
|
|
1565
|
+
|
|
1566
|
+
Overrides must sit inside the `<web-ui-theme>` scope, since the theme host declares the steps for its own subtree. One known boundary: the overlay styles in `assets/*.css` are injected either into a theme-owned overlay root, where the scale applies, or into the document-level fallback overlay root — a plain `div` that sees no theme tokens — where overlay spacing always resolves to its literal fallback.
|
|
1567
|
+
|
|
1454
1568
|
**Selection control tokens (radio, checkbox):**
|
|
1455
1569
|
|
|
1456
|
-
| Property | Default
|
|
1457
|
-
| ------------------------------ |
|
|
1458
|
-
| `--wui-selection-control-size` | `18px`
|
|
1459
|
-
| `--wui-
|
|
1460
|
-
| `--wui-
|
|
1570
|
+
| Property | Default | Description |
|
|
1571
|
+
| ------------------------------ | -------------------- | ----------------------------------------------- |
|
|
1572
|
+
| `--wui-selection-control-size` | `18px` | Indicator (circle / box) width and height |
|
|
1573
|
+
| `--wui-checkbox-group-gap` | `var(--wui-space-2)` | Member spacing inside `<web-ui-checkbox-group>` |
|
|
1574
|
+
| `--wui-radio-group-gap` | `var(--wui-space-2)` | Member spacing inside `<web-ui-radio-group>` |
|
|
1461
1575
|
|
|
1462
1576
|
**Radius tokens:**
|
|
1463
1577
|
|
|
@@ -1471,7 +1585,6 @@ The host uses `display: contents` and does not paint any background: the library
|
|
|
1471
1585
|
|
|
1472
1586
|
| Property | Default | Description |
|
|
1473
1587
|
| ---------------------------- | ------- | ---------------------------- |
|
|
1474
|
-
| `--wui-layer-base` | `0` | Base content |
|
|
1475
1588
|
| `--wui-layer-inline-overlay` | `1` | Overlay local to a component |
|
|
1476
1589
|
| `--wui-layer-header` | `10` | Page header |
|
|
1477
1590
|
| `--wui-layer-auxiliary` | `20` | Floating utility controls |
|
|
@@ -1487,41 +1600,41 @@ The host uses `display: contents` and does not paint any background: the library
|
|
|
1487
1600
|
|
|
1488
1601
|
**Color tokens:**
|
|
1489
1602
|
|
|
1490
|
-
| Property | Light default
|
|
1491
|
-
| ---------------------------------- |
|
|
1492
|
-
| `--wui-color-page` | `#fff`
|
|
1493
|
-
| `--wui-color-surface` | `#fff`
|
|
1494
|
-
| `--wui-color-surface-raised` | `#f2f2f7`
|
|
1495
|
-
| `--wui-color-surface-control` | `#dfdfdf`
|
|
1496
|
-
| `--wui-color-surface-track` | `#e5e5ea`
|
|
1497
|
-
| `--wui-color-surface-menu` | `rgb(
|
|
1498
|
-
| `--wui-color-surface-glass` | `rgb(250 250 250 / 0.34)`
|
|
1499
|
-
| `--wui-color-surface-glass-hover` | `color-mix(... text 6%, surface-glass)`
|
|
1500
|
-
| `--wui-color-surface-glass-active` | `color-mix(... text 15%, surface-glass)`
|
|
1501
|
-
| `--wui-color-surface-overlay` | `rgb(
|
|
1502
|
-
| `--wui-color-surface-sidebar` | `rgb(233 233 233 / 0.82)`
|
|
1503
|
-
| `--wui-color-surface-segmented` | `#e5e5ea`
|
|
1504
|
-
| `--wui-color-surface-selected` | `#fff`
|
|
1505
|
-
| `--wui-color-text` | `#1b1b1b`
|
|
1506
|
-
| `--wui-color-text-secondary` | `#6a6a6a`
|
|
1507
|
-
| `--wui-color-text-tertiary` | `color-mix(in srgb, var(--wui-color-text) 35%, transparent)`
|
|
1508
|
-
| `--wui-color-text-disabled` | `color-mix(in srgb, var(--wui-color-text) 32%, transparent)`
|
|
1509
|
-
| `--wui-color-state-layer-hover` | `color-mix(in srgb, var(--wui-color-text) 6%, transparent)`
|
|
1510
|
-
| `--wui-color-state-layer-active` | `color-mix(in srgb, var(--wui-color-text) 15%, transparent)`
|
|
1511
|
-
| `--wui-color-border` | `rgb(0 0 0 / 0.1)`
|
|
1512
|
-
| `--wui-color-glass-
|
|
1513
|
-
| `--wui-color-glass-
|
|
1514
|
-
| `--wui-color-glass-
|
|
1515
|
-
| `--wui-color-glass-shade`
|
|
1516
|
-
| `--wui-color-accent` | `#08f`
|
|
1517
|
-
| `--wui-color-on-accent` | `#fff`
|
|
1518
|
-
| `--wui-color-on-control` | `#fff`
|
|
1519
|
-
| `--wui-color-success` | `#16a34a`
|
|
1520
|
-
| `--wui-color-warning` | `#d97706`
|
|
1521
|
-
| `--wui-color-danger` | `#dc2626`
|
|
1522
|
-
| `--wui-color-info` | `#2563eb`
|
|
1523
|
-
| `--wui-color-backdrop` | `rgb(0 0 0 / 0.12)`
|
|
1524
|
-
| `--wui-color-focus-ring` | `
|
|
1603
|
+
| Property | Light default | Dark default | Description |
|
|
1604
|
+
| ---------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------- |
|
|
1605
|
+
| `--wui-color-page` | `#fff` | `#242628` | Page background |
|
|
1606
|
+
| `--wui-color-surface` | `#fff` | `#2c2c2e` | Plain surface |
|
|
1607
|
+
| `--wui-color-surface-raised` | `#f2f2f7` | `#2c2c2e` | Raised surface |
|
|
1608
|
+
| `--wui-color-surface-control` | `#dfdfdf` | `#3a3a3c` | Neutral control surface |
|
|
1609
|
+
| `--wui-color-surface-track` | `#e5e5ea` | `#444446` | Slider and switch track surface |
|
|
1610
|
+
| `--wui-color-surface-menu` | `rgb(250 250 250 / 0.76)` | `rgb(49 48 50 / 0.74)` | Menu and floating panel surface |
|
|
1611
|
+
| `--wui-color-surface-glass` | `rgb(250 250 250 / 0.34)` | `rgb(44 44 46 / 0.42)` | Liquid glass surface |
|
|
1612
|
+
| `--wui-color-surface-glass-hover` | `color-mix(... text 6%, surface-glass)` | `color-mix(... text 6%, surface-glass)` | Full glass hover background |
|
|
1613
|
+
| `--wui-color-surface-glass-active` | `color-mix(... text 15%, surface-glass)` | `color-mix(... text 15%, surface-glass)` | Full glass pressed background |
|
|
1614
|
+
| `--wui-color-surface-overlay` | `rgb(248 248 248 / 0.92)` | `rgb(32 34 34 / 0.92)` | Translucent overlay surface |
|
|
1615
|
+
| `--wui-color-surface-sidebar` | `rgb(233 233 233 / 0.82)` | `rgb(44 44 46 / 0.8)` | Sidebar panel surface |
|
|
1616
|
+
| `--wui-color-surface-segmented` | `#e5e5ea` | `#3a3a3c` | Segmented indicator surface |
|
|
1617
|
+
| `--wui-color-surface-selected` | `#fff` | `#5c5c5e` | Selected surface |
|
|
1618
|
+
| `--wui-color-text` | `#1b1b1b` | `#e9eaea` | Primary text |
|
|
1619
|
+
| `--wui-color-text-secondary` | `#6a6a6a` | `#a1a1a6` | Secondary text |
|
|
1620
|
+
| `--wui-color-text-tertiary` | `color-mix(in srgb, var(--wui-color-text) 35%, transparent)` | `color-mix(in srgb, var(--wui-color-text) 42%, transparent)` | Tertiary text and quiet icons |
|
|
1621
|
+
| `--wui-color-text-disabled` | `color-mix(in srgb, var(--wui-color-text) 32%, transparent)` | `color-mix(in srgb, var(--wui-color-text) 38%, transparent)` | Disabled foreground text |
|
|
1622
|
+
| `--wui-color-state-layer-hover` | `color-mix(in srgb, var(--wui-color-text) 6%, transparent)` | `color-mix(in srgb, var(--wui-color-text) 6%, transparent)` | Transparent hover overlay |
|
|
1623
|
+
| `--wui-color-state-layer-active` | `color-mix(in srgb, var(--wui-color-text) 15%, transparent)` | `color-mix(in srgb, var(--wui-color-text) 15%, transparent)` | Transparent pressed overlay |
|
|
1624
|
+
| `--wui-color-border` | `rgb(0 0 0 / 0.1)` | `rgb(255 255 255 / 0.14)` | Normal border and divider |
|
|
1625
|
+
| `--wui-color-glass-highlight` | `rgb(255 255 255 / 0.9)` | `rgb(255 255 255 / 0.1)` | Inset highlight on the surface |
|
|
1626
|
+
| `--wui-color-glass-ring` | `rgb(0 0 0 / 0.05)` | `rgb(255 255 255 / 0.05)` | Base tint of the 1px ring |
|
|
1627
|
+
| `--wui-color-glass-ring-sheen` | `rgb(255 255 255 / 0.5)` | `rgb(255 255 255 / 0.2)` | Corner sheen on the ring |
|
|
1628
|
+
| `--wui-color-glass-ring-shade` | `rgb(0 0 0 / 0.03)` | `rgb(0 0 0 / 0.5)` | Corner shade on the ring |
|
|
1629
|
+
| `--wui-color-accent` | `#08f` | `#0a84ff` | Accent and focused input border |
|
|
1630
|
+
| `--wui-color-on-accent` | `#fff` | `#fff` | Foreground on accent |
|
|
1631
|
+
| `--wui-color-on-control` | `#fff` | `#f2f2f7` | Foreground on control inner |
|
|
1632
|
+
| `--wui-color-success` | `#16a34a` | `#30d158` | Success |
|
|
1633
|
+
| `--wui-color-warning` | `#d97706` | `#ff9f0a` | Warning |
|
|
1634
|
+
| `--wui-color-danger` | `#dc2626` | `#ff453a` | Danger |
|
|
1635
|
+
| `--wui-color-info` | `#2563eb` | `#64d2ff` | Info |
|
|
1636
|
+
| `--wui-color-backdrop` | `rgb(0 0 0 / 0.12)` | `rgb(0 0 0 / 0.48)` | Modal backdrop |
|
|
1637
|
+
| `--wui-color-focus-ring` | `color-mix(in srgb, var(--wui-color-accent, #08f) 40%, transparent)` | `color-mix(in srgb, var(--wui-color-accent, #0a84ff) 62%, transparent)` | Focus indicator color, derived from the accent |
|
|
1525
1638
|
|
|
1526
1639
|
**Shadow tokens:**
|
|
1527
1640
|
|