use-wc 1.0.0-pre.54 → 1.0.0-pre.56

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.
@@ -708,7 +708,7 @@
708
708
  "declarations": [
709
709
  {
710
710
  "kind": "class",
711
- "description": "`use-caret` supplies the caret icon and label layout for a consumer who wants to own the\ninvoking button itself. It renders inside a plain `<button>`\nthat carries the native `popovertarget` attribute, pointing at a `use-menu`:\n\n```html\n<button id=\"thing\" popovertarget=\"thing-menu\">\n <use-caret>Menu</use-caret>\n</button>\n<use-menu id=\"thing-menu\" aria-label=\"Menu\">\n <button role=\"menuitem\">First thing</button>\n</use-menu>\n```\n\n`use-menu` finds this button on its own via `popovertarget` and anchors to it — no\n`anchortarget` needed here; that's only for a menu with more than one invoker. See `use-menu`'s\nown docs for that case.\n\nThe button is entirely the consumer's own element — their classes, their content, their event\nhandlers. `use-caret` contributes no interactivity of its own: no internal button, no focus\ntarget, no state. It only renders the label and caret glyph, so consumers get a consistent\nvisual trigger without giving up control of the button.\n\nThe caret direction is derived rather than configured: a trigger nested inside a `use-menu`\nis understood to open a submenu and renders pointing sideways; a top-level trigger renders\npointing down.\n\nSwapping in a different caret **per instance** is a slot: put your own element in the `icon`\nslot and it replaces `part(icon-default)` entirely for that trigger.\n\nSwapping the default caret **for every trigger in the app** doesn't need touching this\ncomponent's `render()` at all — style `::part(icon-default)` from an outside stylesheet, the\nsame way any shadow DOM part is themed:\n\n```css\nuse-caret::part(icon-default) {\n mask-image: url(\"/icons/my-caret.svg\");\n}\n```\n\n(When the design system's `theme.css` is loaded, this is already wired to a token —\n`--usewc-effect-dropdown-trigger-icon-mask-image` — so redefining that custom property has\nthe same effect without writing a `::part()` rule at all.)\n\n`::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For\nanything else — different markup, an icon font, logic beyond a mask — set the static\n`customIcon` hook. Every `<use-caret>` in the page picks it up; consumers keep writing the\nsame tag, no new element to register or remember to use instead. Set it once, as early as\npossible (an app's entry point, before any triggers connect) — Lit calls `render()` fresh on\nevery update, so an already-connected instance only reflects the change once something causes\nit to re-render:\n\n```ts\n// A bare string or TemplateResult is used as-is, on every trigger, nested or not:\nUseCaret.customIcon = \"→\";\n\n// A function is called on every render instead, and told whether this particular trigger is\n// nested — the same distinction the built-in ▼/▶ caret already makes for submenus:\nUseCaret.customIcon = (html, isNested) =>\n isNested\n ? html`<my-icon name=\"chevron-right\"></my-icon>`\n : html`<my-icon name=\"chevron-down\"></my-icon>`;\n```\n\nThe function form is called with the component's own `html` tag as its (optional) first\nargument rather than requiring the caller to import one — a page can end up with more than one\ncopy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy\nthan the one this component renders with can silently fail to render. Taking `html` in as a\nparameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`\nthis component already uses. A bare string skips the concern entirely, at the cost of not\nbeing able to vary by `isNested`.\n\nOnly need to change the icon on **some** triggers, not every one in the app? Extend the\nclass, override `render()` normally, and register the subclass under a different tag name so\nit coexists with the built-in element instead of replacing it everywhere:\n\n```ts\nclass MyCaret extends UseCaret {\n override render() {\n return html`<slot></slot><slot name=\"icon\"><my-icon name=\"chevron\"></my-icon></slot>`;\n }\n}\ncustomElements.define(\"my-caret\", MyCaret);\n```",
711
+ "description": "`use-caret` supplies the caret icon and label layout for a consumer who wants to own the\ninvoking button itself. It renders inside a plain `<button>`\nthat carries the native `popovertarget` attribute, pointing at a `use-menu`:\n\n```html\n<button id=\"thing\" popovertarget=\"thing-menu\">\n <use-caret>Menu</use-caret>\n</button>\n<use-menu id=\"thing-menu\" aria-label=\"Menu\">\n <button role=\"menuitem\">First thing</button>\n</use-menu>\n```\n\n`use-menu` finds this button on its own via `popovertarget` and anchors to it — no\n`anchortarget` needed here; that's only for a menu with more than one invoker. See `use-menu`'s\nown docs for that case.\n\nThe button is entirely the consumer's own element — their classes, their content, their event\nhandlers. `use-caret` contributes no interactivity of its own: no internal button, no focus\ntarget, no state. It only renders the label and caret glyph, so consumers get a consistent\nvisual trigger without giving up control of the button.\n\nThe caret direction is derived rather than configured: a trigger nested inside a `use-menu`\nis understood to open a submenu and renders pointing sideways; a top-level trigger renders\npointing down.\n\nSwapping in a different caret **per instance** is a slot: put your own element in the `icon`\nslot and it replaces `part(icon-default)` entirely for that trigger.\n\nSwapping the default caret **for every trigger in the app** doesn't need touching this\ncomponent's `render()` at all — style `::part(icon-default)` from an outside stylesheet, the\nsame way any shadow DOM part is themed:\n\n```css\nuse-caret::part(icon-default) {\n mask-image: url(\"/icons/my-caret.svg\");\n}\n```\n\n(When the design system's `theme.css` is loaded, this is already wired to a token —\n`--usewc-effect-dropdown-trigger-icon-mask-image` — so redefining that custom property has\nthe same effect without writing a `::part()` rule at all.)\n\n`::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For\nanything else — different markup, an icon font, logic beyond a mask — set the static `icon`\nand `nestedIcon` hooks. Every `<use-caret>` in the page picks them up; consumers keep writing\nthe same tag, no new element to register or remember to use instead. `icon` supplies a\ntop-level trigger's glyph, `nestedIcon` a submenu trigger's — set whichever apply; an unset one\nkeeps the built-in ▼/▶ for that case. Set them once, as early as possible (an app's entry\npoint, before any triggers connect) — Lit calls `render()` fresh on every update, so an\nalready-connected instance only reflects the change once something causes it to re-render:\n\n```ts\n// A bare string or TemplateResult is used as-is, on every trigger of that kind:\nUseCaret.icon = \"▾\";\nUseCaret.nestedIcon = \"▸\";\n\n// A function is called on every render instead:\nUseCaret.nestedIcon = (html) => html`<my-icon name=\"chevron-right\"></my-icon>`;\n```\n\nThe function form is called with the component's own `html` tag as its (optional) first\nargument rather than requiring the caller to import one — a page can end up with more than one\ncopy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy\nthan the one this component renders with can silently fail to render. Taking `html` in as a\nparameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`\nthis component already uses. A bare string skips the concern entirely.\n\nOnly need to change the icon on **some** triggers, not every one in the app? Extend the\nclass, override `render()` normally, and register the subclass under a different tag name so\nit coexists with the built-in element instead of replacing it everywhere:\n\n```ts\nclass MyCaret extends UseCaret {\n override render() {\n return html`<slot></slot><slot name=\"icon\"><my-icon name=\"chevron\"></my-icon></slot>`;\n }\n}\ncustomElements.define(\"my-caret\", MyCaret);\n```",
712
712
  "name": "UseCaret",
713
713
  "slots": [
714
714
  {
@@ -723,12 +723,21 @@
723
723
  "members": [
724
724
  {
725
725
  "kind": "field",
726
- "name": "customIcon",
726
+ "name": "icon",
727
727
  "type": {
728
- "text": "| ((html: HtmlTag, isNested: boolean) => TemplateResult | string)\n | TemplateResult\n | string | undefined"
728
+ "text": "((html: HtmlTag) => TemplateResult | string) | TemplateResult | string | undefined"
729
729
  },
730
730
  "static": true,
731
- "description": "Global override for the default caret. When set, every `<use-caret>` in the page\nrenders this inside `part(icon-default)` instead of the built-in ▼/▶ character. A plain\nstring or `TemplateResult` is used as-is for every trigger, nested or not — for a caret that\nstill needs to point sideways on a submenu trigger, use the function form instead: it's\ncalled with the component's own `html` tag and whether *this* trigger is nested, so a\nreturned template is guaranteed to come from the same `lit-html` instance this component\nrenders with, and can still vary the same way the built-in caret does. Unset (the default)\nkeeps the built-in caret."
731
+ "description": "Global override for a top-level trigger's caret. When set, every non-nested `<use-caret>` in\nthe page renders this inside `part(icon-default)` instead of the built-in ▼ character. A\nplain string or `TemplateResult` is used as-is; the function form is called with the\ncomponent's own `html` tag, so a returned template is guaranteed to come from the same\n`lit-html` instance this component renders with. Unset (the default) keeps the built-in ▼."
732
+ },
733
+ {
734
+ "kind": "field",
735
+ "name": "nestedIcon",
736
+ "type": {
737
+ "text": "((html: HtmlTag) => TemplateResult | string) | TemplateResult | string | undefined"
738
+ },
739
+ "static": true,
740
+ "description": "Global override for a submenu trigger's caret — the counterpart to icon for a\n`<use-caret>` nested inside a `use-menu`. Unset (the default) keeps the built-in ▶."
732
741
  }
733
742
  ],
734
743
  "superclass": {
@@ -2211,7 +2220,7 @@
2211
2220
  "declarations": [
2212
2221
  {
2213
2222
  "kind": "class",
2214
- "description": "Accessible grid component following [WAI-ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).",
2223
+ "description": "Accessible grid component following [WAI-ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).\n\n## Grid Cell Modes\nThe `mode` attribute of the `use-gridcell` element determines how the cell behaves in terms of focus and interaction. The possible values are:\n\n- `'none'` - the cell itself is focusable and it does not contain any interactive elements.\n- `'widget'` - the cell itself is focusable and it contains more than one interactive element. To access the interactive elements, the user must press `Enter` or `F2`, and to restore focus to the cell, the user must press `Esc` or `F2`.\n- `'action'` - the cell itself is not focusable and it contains a single interactive element. The user can tab to the interactive elements directly.\n\n## Accessible Label\nProvide `aria-label` or `aria-labelledby` on the `use-grid` element so screen readers can identify the grid.",
2215
2224
  "name": "UseGrid",
2216
2225
  "slots": [
2217
2226
  {
@@ -2751,9 +2760,6 @@
2751
2760
  {
2752
2761
  "description": "NodeList of `use-option` elements",
2753
2762
  "name": "default"
2754
- },
2755
- {
2756
- "name": "arrow"
2757
2763
  }
2758
2764
  ],
2759
2765
  "members": [
@@ -3318,15 +3324,41 @@
3318
3324
  "declarations": [
3319
3325
  {
3320
3326
  "kind": "class",
3321
- "description": "`use-option` is a custom element that represents an option in a `use-listbox-input` custom element.\n\nThe children of this element are the content of the option and must not contain any interactive elements.",
3327
+ "description": "`use-option` is a custom element that represents an option in a `use-listbox-input` custom\nelement.\n\nThe children of this element are the content of the option and must not contain any\ninteractive elements.\n\nSelection is shown by one of two indicators, swapped based on the option's `selected` state:\na checkmark when selected, nothing (a reserved-space placeholder) when not.\n\nSwapping an indicator **per instance** is a slot: put your own element in the `selected-indicator`\nor `deselected-indicator` slot and it replaces `part(selected-indicator-default)` /\n`part(deselected-indicator-default)` entirely for that option.\n\nSwapping an indicator **for every option in the app** doesn't need touching this component's\n`render()` at all — style `::part(selected-indicator-default)` /\n`::part(deselected-indicator-default)` from an outside stylesheet, the same way any shadow DOM\npart is themed:\n\n```css\nuse-option::part(selected-indicator-default) {\n mask-image: url(\"/icons/my-checkmark.svg\");\n}\n```\n\n(When the design system's `theme.css` is loaded, this is already wired to a pair of tokens —\n`--usewc-effect-selected-indicator-mask-image` for the shape and\n`--usewc-effect-selected-indicator-color` for what paints it (`--usewc-effect-deselected-…`\nfor the other state) — so redefining those custom properties has the same effect without\nwriting a `::part()` rule at all.)\n\n`::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For\nanything else — different markup, an icon font, logic beyond a mask — set the static\n`selectedIcon` and `deselectedIcon` hooks. Every `<use-option>` in the page picks them up;\nconsumers keep writing the same tag, no new element to register or remember to use instead.\nSet them once, as early as possible (an app's entry point, before any options connect) — Lit\ncalls `render()` fresh on every update, so an already-connected instance only reflects the\nchange once something causes it to re-render:\n\n```ts\n// A bare string or TemplateResult is used as-is, on every option:\nUseOption.selectedIcon = \"✓\";\nUseOption.deselectedIcon = \"\";\n\n// A function is called on every render instead:\nUseOption.selectedIcon = (html) => html`<my-icon name=\"check\"></my-icon>`;\n```\n\nThe function form is called with the component's own `html` tag as its (optional) first\nargument rather than requiring the caller to import one — a page can end up with more than one\ncopy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy\nthan the one this component renders with can silently fail to render. Taking `html` in as a\nparameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`\nthis component already uses. A bare string skips the concern entirely.\n\nA set icon still renders under `part(selected-indicator-default)` /\n`part(deselected-indicator-default)` — the same part the design system's theme masks into the\nbuilt-in glyph. Combining a static hook with `theme.css` means the theme's masking still\napplies to the custom content; reset the relevant `mask-image` token or `::part()` rule if that\nfights with what was set.\n\nOnly need to change the icon on **some** options, not every one in the app? Extend the class,\noverride `render()` normally, and register the subclass under a different tag name so it\ncoexists with the built-in element instead of replacing it everywhere:\n\n```ts\nclass MyOption extends UseOption {\n override render() {\n return html`<slot></slot><slot name=\"selected-indicator\"><my-icon name=\"check\"></my-icon></slot>`;\n }\n}\ncustomElements.define(\"my-option\", MyOption);\n```",
3322
3328
  "name": "UseOption",
3323
3329
  "slots": [
3324
3330
  {
3331
+ "description": "Replaces the default checkmark shown when the option is selected.",
3325
3332
  "name": "selected-indicator"
3326
3333
  },
3327
- {}
3334
+ {
3335
+ "description": "Replaces the default (empty) indicator shown when the option is not selected.",
3336
+ "name": "deselected-indicator"
3337
+ },
3338
+ {
3339
+ "description": "Option content.",
3340
+ "name": "default"
3341
+ }
3328
3342
  ],
3329
3343
  "members": [
3344
+ {
3345
+ "kind": "field",
3346
+ "name": "selectedIcon",
3347
+ "type": {
3348
+ "text": "IconOverride | undefined"
3349
+ },
3350
+ "static": true,
3351
+ "description": "Global override for the selected-state indicator. When set, every `<use-option>` in the page\nrenders this inside `part(selected-indicator-default)` instead of the built-in ✔ character.\nUnset (the default) keeps the built-in checkmark."
3352
+ },
3353
+ {
3354
+ "kind": "field",
3355
+ "name": "deselectedIcon",
3356
+ "type": {
3357
+ "text": "IconOverride | undefined"
3358
+ },
3359
+ "static": true,
3360
+ "description": "Global override for the deselected-state indicator — the counterpart to selectedIcon.\nUnset (the default) keeps the built-in (empty) placeholder."
3361
+ },
3330
3362
  {
3331
3363
  "kind": "field",
3332
3364
  "name": "selected",
@@ -3728,7 +3760,7 @@
3728
3760
  "declarations": [
3729
3761
  {
3730
3762
  "kind": "class",
3731
- "description": "Custom input control for managing selected values in a hierarchy.",
3763
+ "description": "Custom input control for managing selected values in a hierarchy.\n\nA convenience for distributing a custom indicator to every item at once: slot an icon into\n`use-tree` itself under `expanded-indicator`, `collapsed-indicator`, `selected-indicator`, or\n`deselected-indicator`, and it's cloned into each lazily-discovered `use-treeitem`'s matching\nslot. This is per-item slotting, just done once for the whole tree — it doesn't replace\n`use-treeitem`'s own static hooks (`expandedIcon`, `collapsedIcon`, `selectedIcon`,\n`deselectedIcon`), which still apply for indicators the tree hasn't cloned one over.",
3732
3764
  "name": "UseTree",
3733
3765
  "slots": [
3734
3766
  {
@@ -3736,7 +3768,20 @@
3736
3768
  "name": "default"
3737
3769
  },
3738
3770
  {
3739
- "name": "arrow"
3771
+ "description": "Cloned into every item's `expanded-indicator` slot.",
3772
+ "name": "expanded-indicator"
3773
+ },
3774
+ {
3775
+ "description": "Cloned into every item's `collapsed-indicator` slot.",
3776
+ "name": "collapsed-indicator"
3777
+ },
3778
+ {
3779
+ "description": "Cloned into every item's `selected-indicator` slot.",
3780
+ "name": "selected-indicator"
3781
+ },
3782
+ {
3783
+ "description": "Cloned into every item's `deselected-indicator` slot.",
3784
+ "name": "deselected-indicator"
3740
3785
  }
3741
3786
  ],
3742
3787
  "members": [
@@ -3874,15 +3919,67 @@
3874
3919
  "declarations": [
3875
3920
  {
3876
3921
  "kind": "class",
3877
- "description": "`use-option` is a custom element that represents an option in a `use-listbox-input` custom element.\n\nThe children of this element are the content of the option and must not contain any interactive elements.\n\n## To Do\n\n- [ ] When the parent is no longer disabled, update the items without their own `[disabled]`",
3922
+ "description": "`use-treeitem` is a custom element that represents a node in a `use-tree` custom element.\n\nThe children of this element are the content of the item, plus any nested `use-treeitem`\nelements representing its children, and must not otherwise contain interactive elements.\n\nFour indicators render alongside an item's content: an expanded/collapsed disclosure glyph\n(only one of the pair shows, depending on state), and a selected/deselected glyph (same).\n\nSwapping an indicator **per instance** is a slot: put your own element in the\n`expanded-indicator`, `collapsed-indicator`, `selected-indicator`, or `deselected-indicator`\nslot and it replaces that indicator's `part(*-default)` entirely for that item. `use-tree`\nalso offers a shortcut for this: slot an icon into `use-tree` itself and it's cloned into every\nitem's matching slot — see `use-tree`'s own docs.\n\nSwapping an indicator **for every item in the app** doesn't need touching this component's\n`render()` at all — style `::part(*-indicator-default)` from an outside stylesheet, the same\nway any shadow DOM part is themed:\n\n```css\nuse-treeitem::part(collapsed-indicator-default) {\n mask-image: url(\"/icons/my-chevron.svg\");\n}\n```\n\n(When the design system's `theme.css` is loaded, this is already wired to tokens — a\nmask-image/color pair per indicator: `--usewc-effect-tree-item-expanded-indicator-mask-image`\n/ `-color`, `--usewc-effect-tree-item-collapsed-indicator-mask-image` / `-color`, and\n`--usewc-effect-selected-indicator-mask-image` / `-color` (`--usewc-effect-deselected-…` for\nthe other state) — so redefining those custom properties has the same effect without writing a\n`::part()` rule at all.)\n\n`::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For\nanything else — different markup, an icon font, logic beyond a mask — set the matching static\nhook: `expandedIcon`, `collapsedIcon`, `selectedIcon`, `deselectedIcon`. Every `<use-treeitem>`\nin the page picks them up; consumers keep writing the same tag, no new element to register or\nremember to use instead. Set them once, as early as possible (an app's entry point, before any\nitems connect) — Lit calls `render()` fresh on every update, so an already-connected instance\nonly reflects the change once something causes it to re-render:\n\n```ts\n// A bare string or TemplateResult is used as-is, on every item:\nUseTreeitem.collapsedIcon = \"▸\";\nUseTreeitem.expandedIcon = \"▾\";\n\n// A function is called on every render instead:\nUseTreeitem.collapsedIcon = (html) => html`<my-icon name=\"chevron-right\"></my-icon>`;\n```\n\nThe function form is called with the component's own `html` tag as its (optional) first\nargument rather than requiring the caller to import one — a page can end up with more than one\ncopy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy\nthan the one this component renders with can silently fail to render. Taking `html` in as a\nparameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`\nthis component already uses. A bare string skips the concern entirely.\n\nA set icon still renders under that indicator's own `part(*-indicator-default)` — the same part\nthe design system's theme masks into the built-in glyph. Combining a static hook with\n`theme.css` means the theme's masking still applies to the custom content; reset the relevant\n`mask-image` token or `::part()` rule if that fights with what was set.\n\nOnly need to change an indicator on **some** items, not every one in the app? Extend the class,\noverride `render()` normally, and register the subclass under a different tag name so it\ncoexists with the built-in element instead of replacing it everywhere.\n\n## To Do\n\n- [ ] When the parent is no longer disabled, update the items without their own `[disabled]`",
3878
3923
  "name": "UseTreeitem",
3879
3924
  "slots": [
3880
3925
  {
3926
+ "description": "Replaces the default disclosure glyph shown when the item is expanded.",
3927
+ "name": "expanded-indicator"
3928
+ },
3929
+ {
3930
+ "description": "Replaces the default disclosure glyph shown when the item is collapsed.",
3931
+ "name": "collapsed-indicator"
3932
+ },
3933
+ {
3934
+ "description": "Replaces the default glyph shown when the item is selected.",
3881
3935
  "name": "selected-indicator"
3882
3936
  },
3883
- {}
3937
+ {
3938
+ "description": "Replaces the default (empty) glyph shown when the item is not selected.",
3939
+ "name": "deselected-indicator"
3940
+ },
3941
+ {
3942
+ "description": "Item content, plus any nested `use-treeitem` children.",
3943
+ "name": "default"
3944
+ }
3884
3945
  ],
3885
3946
  "members": [
3947
+ {
3948
+ "kind": "field",
3949
+ "name": "expandedIcon",
3950
+ "type": {
3951
+ "text": "IconOverride | undefined"
3952
+ },
3953
+ "static": true,
3954
+ "description": "Global override for the expanded-state disclosure indicator. When set, every\n`<use-treeitem>` in the page renders this inside `part(expanded-indicator-default)` instead\nof the built-in `-` character. Unset (the default) keeps the built-in glyph."
3955
+ },
3956
+ {
3957
+ "kind": "field",
3958
+ "name": "collapsedIcon",
3959
+ "type": {
3960
+ "text": "IconOverride | undefined"
3961
+ },
3962
+ "static": true,
3963
+ "description": "Global override for the collapsed-state disclosure indicator — the counterpart to\nexpandedIcon. Unset (the default) keeps the built-in `+` character."
3964
+ },
3965
+ {
3966
+ "kind": "field",
3967
+ "name": "selectedIcon",
3968
+ "type": {
3969
+ "text": "IconOverride | undefined"
3970
+ },
3971
+ "static": true,
3972
+ "description": "Global override for the selected-state indicator. Unset (the default) keeps the built-in ✔\ncharacter."
3973
+ },
3974
+ {
3975
+ "kind": "field",
3976
+ "name": "deselectedIcon",
3977
+ "type": {
3978
+ "text": "IconOverride | undefined"
3979
+ },
3980
+ "static": true,
3981
+ "description": "Global override for the deselected-state indicator — the counterpart to\nselectedIcon. Unset (the default) keeps the built-in (empty) placeholder."
3982
+ },
3886
3983
  {
3887
3984
  "kind": "field",
3888
3985
  "name": "selected",
@@ -833,12 +833,8 @@
833
833
  }
834
834
 
835
835
  @layer use-theme.icon {
836
- use-option::part(selected-indicator-default) {
837
- display: none;
838
- }
839
-
840
- /* Only matches the built-in ▼/▶ glyph — `use-caret` renders a custom `UseCaret.customIcon`
841
- under a different part name so this masking never fights with it. */
836
+ /* Only matches the built-in ▼/▶ glyph — `use-caret` renders a custom `UseCaret.icon` /
837
+ `UseCaret.nestedIcon` under a different part name so this masking never fights with it. */
842
838
  use-caret::part(icon-default) {
843
839
  display: inline-block;
844
840
  overflow: hidden;
@@ -864,6 +860,89 @@
864
860
  use-caret::part(icon) {
865
861
  margin-inline-start: auto;
866
862
  }
863
+
864
+ /* Same masking shape as use-caret's icon-default, shared by every ✔-style selected indicator
865
+ — `use-option` renders its own `selected-indicator-default`, `-custom` swap for the same
866
+ reason use-caret does: real custom content shouldn't be clobbered by this masking. Color is its
867
+ own token, same as use-caret's, so a theme can recolor the checkmark independently of what
868
+ shape it masks. */
869
+ use-option::part(selected-indicator-default),
870
+ use-treeitem::part(selected-indicator-default) {
871
+ display: inline-block;
872
+ overflow: hidden;
873
+ white-space: nowrap;
874
+ text-indent: 100%;
875
+ inline-size: var(--usewc-layout-selected-indicator-size);
876
+ block-size: var(--usewc-layout-selected-indicator-size);
877
+ background-color: var(--usewc-effect-selected-indicator-color, currentColor);
878
+ mask-image: var(--usewc-effect-selected-indicator-mask-image, none);
879
+ mask-size: contain;
880
+ mask-repeat: no-repeat;
881
+ mask-position: center;
882
+ }
883
+
884
+ use-theme-escape use-option::part(selected-indicator-default),
885
+ use-theme-escape use-treeitem::part(selected-indicator-default) {
886
+ all: revert-layer;
887
+ }
888
+
889
+ /* The deselected indicator has no built-in glyph: `--usewc-effect-deselected-indicator-mask-image`
890
+ defaults to the `none` keyword (no mask layer at all — the box would otherwise paint fully
891
+ opaque) and `--usewc-effect-deselected-indicator-color` defaults to `transparent`, so together
892
+ they read as blank out of the box. A theme sets both to show something (an empty checkbox
893
+ outline, for instance) — the mask supplies the shape, the color paints it. Either way this
894
+ occupies the exact same box as its selected sibling, or swapping between the two shifts every
895
+ option/item's content sideways depending on selection state. */
896
+ use-option::part(deselected-indicator-default),
897
+ use-treeitem::part(deselected-indicator-default) {
898
+ display: inline-block;
899
+ overflow: hidden;
900
+ white-space: nowrap;
901
+ text-indent: 100%;
902
+ inline-size: var(--usewc-layout-selected-indicator-size);
903
+ block-size: var(--usewc-layout-selected-indicator-size);
904
+ background-color: var(--usewc-effect-deselected-indicator-color, transparent);
905
+ mask-image: var(--usewc-effect-deselected-indicator-mask-image, none);
906
+ mask-size: contain;
907
+ mask-repeat: no-repeat;
908
+ mask-position: center;
909
+ }
910
+
911
+ use-theme-escape use-option::part(deselected-indicator-default),
912
+ use-theme-escape use-treeitem::part(deselected-indicator-default) {
913
+ all: revert-layer;
914
+ }
915
+
916
+ /* Expanded/collapsed are a state pair, not a checked/unchecked contrast, but still get their
917
+ own color tokens for the same reason the selected indicator does — a theme may want the
918
+ disclosure glyph a different shade than the surrounding text. */
919
+ use-treeitem::part(expanded-indicator-default),
920
+ use-treeitem::part(collapsed-indicator-default) {
921
+ display: inline-block;
922
+ overflow: hidden;
923
+ white-space: nowrap;
924
+ text-indent: 100%;
925
+ inline-size: var(--usewc-layout-tree-item-indicator-size);
926
+ block-size: var(--usewc-layout-tree-item-indicator-size);
927
+ mask-size: contain;
928
+ mask-repeat: no-repeat;
929
+ mask-position: center;
930
+ }
931
+
932
+ use-treeitem::part(expanded-indicator-default) {
933
+ background-color: var(--usewc-effect-tree-item-expanded-indicator-color, currentColor);
934
+ mask-image: var(--usewc-effect-tree-item-expanded-indicator-mask-image, none);
935
+ }
936
+
937
+ use-treeitem::part(collapsed-indicator-default) {
938
+ background-color: var(--usewc-effect-tree-item-collapsed-indicator-color, currentColor);
939
+ mask-image: var(--usewc-effect-tree-item-collapsed-indicator-mask-image, none);
940
+ }
941
+
942
+ use-theme-escape use-treeitem::part(expanded-indicator-default),
943
+ use-theme-escape use-treeitem::part(collapsed-indicator-default) {
944
+ all: revert-layer;
945
+ }
867
946
  }
868
947
 
869
948
  @layer use-theme.menu {
@@ -959,9 +1038,12 @@
959
1038
  }
960
1039
  }
961
1040
 
1041
+ /* use-option's own checkmark now renders in-flow (a slot, not a background-image), so it
1042
+ already reserves its own leading space via the shared gap/padding above — no extra reservation
1043
+ needed here. This block only concerns the native [role="menuitemcheckbox"/"menuitemradio"]
1044
+ items, whose checkmark is still theme.css's own background-image on the host. */
962
1045
  use-menu :is([role="menuitemcheckbox"], [role="menuitemradio"]),
963
1046
  use-listbox-input {
964
- use-option,
965
1047
  &:has(+ :where([role="menuitemcheckbox"], [role="menuitemradio"])),
966
1048
  & + :where([role="menuitemcheckbox"], [role="menuitemradio"]) {
967
1049
  padding-inline-start: calc(var(--usewc-layout-input-padding-inline) * 2 + var(--usewc-layout-selector-width));
@@ -1028,8 +1110,10 @@
1028
1110
  flip-inline flip-block;
1029
1111
  }
1030
1112
 
1031
- use-menu [aria-checked="true"],
1032
- use-option:state(selected) {
1113
+ /* use-option's own checkmark is themed separately, in icon.css, as a mask on its
1114
+ selected-indicator part rather than a background-image on the host — this rule only concerns
1115
+ the native [aria-checked] menu items, which have no shadow part to target. */
1116
+ use-menu [aria-checked="true"] {
1033
1117
  background-image: var(--usewc-effect-menu-item-checkmark-background-image, none);
1034
1118
  /* TODO use logical position */
1035
1119
  background-position: left var(--usewc-layout-input-padding-inline) center;
@@ -44,23 +44,21 @@ use-caret::part(icon-default) {
44
44
  the same effect without writing a `::part()` rule at all.)
45
45
 
46
46
  `::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For
47
- anything else — different markup, an icon font, logic beyond a mask — set the static
48
- `customIcon` hook. Every `<use-caret>` in the page picks it up; consumers keep writing the
49
- same tag, no new element to register or remember to use instead. Set it once, as early as
50
- possible (an app's entry point, before any triggers connect) — Lit calls `render()` fresh on
51
- every update, so an already-connected instance only reflects the change once something causes
52
- it to re-render:
47
+ anything else — different markup, an icon font, logic beyond a mask — set the static `icon`
48
+ and `nestedIcon` hooks. Every `<use-caret>` in the page picks them up; consumers keep writing
49
+ the same tag, no new element to register or remember to use instead. `icon` supplies a
50
+ top-level trigger's glyph, `nestedIcon` a submenu trigger's — set whichever apply; an unset one
51
+ keeps the built-in ▼/▶ for that case. Set them once, as early as possible (an app's entry
52
+ point, before any triggers connect) — Lit calls `render()` fresh on every update, so an
53
+ already-connected instance only reflects the change once something causes it to re-render:
53
54
 
54
55
  ```ts
55
- // A bare string or TemplateResult is used as-is, on every trigger, nested or not:
56
- UseCaret.customIcon = "→";
57
-
58
- // A function is called on every render instead, and told whether this particular trigger is
59
- // nested — the same distinction the built-in ▼/▶ caret already makes for submenus:
60
- UseCaret.customIcon = (html, isNested) =>
61
- isNested
62
- ? html`<my-icon name="chevron-right"></my-icon>`
63
- : html`<my-icon name="chevron-down"></my-icon>`;
56
+ // A bare string or TemplateResult is used as-is, on every trigger of that kind:
57
+ UseCaret.icon = "▾";
58
+ UseCaret.nestedIcon = "▸";
59
+
60
+ // A function is called on every render instead:
61
+ UseCaret.nestedIcon = (html) => html`<my-icon name="chevron-right"></my-icon>`;
64
62
  ```
65
63
 
66
64
  The function form is called with the component's own `html` tag as its (optional) first
@@ -68,8 +66,7 @@ argument rather than requiring the caller to import one — a page can end up wi
68
66
  copy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy
69
67
  than the one this component renders with can silently fail to render. Taking `html` in as a
70
68
  parameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`
71
- this component already uses. A bare string skips the concern entirely, at the cost of not
72
- being able to vary by `isNested`.
69
+ this component already uses. A bare string skips the concern entirely.
73
70
 
74
71
  Only need to change the icon on **some** triggers, not every one in the app? Extend the
75
72
  class, override `render()` normally, and register the subclass under a different tag name so
@@ -88,16 +85,13 @@ customElements.define("my-caret", MyCaret);
88
85
 
89
86
  | Name | Type | Default | Description |
90
87
  | --- | --- | --- | --- |
91
- | customIcon | `\| ((html: HtmlTag, isNested: boolean) => TemplateResult \| string)
92
- \| TemplateResult
93
- \| string \| undefined` | - | Global override for the default caret. When set, every `<use-caret>` in the page
94
- renders this inside `part(icon-default)` instead of the built-in ▼/▶ character. A plain
95
- string or `TemplateResult` is used as-is for every trigger, nested or not — for a caret that
96
- still needs to point sideways on a submenu trigger, use the function form instead: it's
97
- called with the component's own `html` tag and whether *this* trigger is nested, so a
98
- returned template is guaranteed to come from the same `lit-html` instance this component
99
- renders with, and can still vary the same way the built-in caret does. Unset (the default)
100
- keeps the built-in caret. |
88
+ | icon | `((html: HtmlTag) => TemplateResult \| string) \| TemplateResult \| string \| undefined` | - | Global override for a top-level trigger's caret. When set, every non-nested `<use-caret>` in
89
+ the page renders this inside `part(icon-default)` instead of the built-in ▼ character. A
90
+ plain string or `TemplateResult` is used as-is; the function form is called with the
91
+ component's own `html` tag, so a returned template is guaranteed to come from the same
92
+ `lit-html` instance this component renders with. Unset (the default) keeps the built-in ▼. |
93
+ | nestedIcon | `((html: HtmlTag) => TemplateResult \| string) \| TemplateResult \| string \| undefined` | - | Global override for a submenu trigger's caret — the counterpart to icon for a
94
+ `<use-caret>` nested inside a `use-menu`. Unset (the default) keeps the built-in ▶. |
101
95
 
102
96
  ## Slots
103
97
 
@@ -2,6 +2,16 @@
2
2
 
3
3
  Accessible grid component following [WAI-ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
4
4
 
5
+ ## Grid Cell Modes
6
+ The `mode` attribute of the `use-gridcell` element determines how the cell behaves in terms of focus and interaction. The possible values are:
7
+
8
+ - `'none'` - the cell itself is focusable and it does not contain any interactive elements.
9
+ - `'widget'` - the cell itself is focusable and it contains more than one interactive element. To access the interactive elements, the user must press `Enter` or `F2`, and to restore focus to the cell, the user must press `Esc` or `F2`.
10
+ - `'action'` - the cell itself is not focusable and it contains a single interactive element. The user can tab to the interactive elements directly.
11
+
12
+ ## Accessible Label
13
+ Provide `aria-label` or `aria-labelledby` on the `use-grid` element so screen readers can identify the grid.
14
+
5
15
  ## Attributes
6
16
 
7
17
  | Name | Type | Default | Description |
@@ -37,4 +37,3 @@
37
37
  | Name | Description |
38
38
  | --- | --- |
39
39
  | `default` | NodeList of `use-option` elements |
40
- | `arrow` | - |
@@ -1,8 +1,77 @@
1
1
  # `use-option`
2
2
 
3
- `use-option` is a custom element that represents an option in a `use-listbox-input` custom element.
3
+ `use-option` is a custom element that represents an option in a `use-listbox-input` custom
4
+ element.
4
5
 
5
- The children of this element are the content of the option and must not contain any interactive elements.
6
+ The children of this element are the content of the option and must not contain any
7
+ interactive elements.
8
+
9
+ Selection is shown by one of two indicators, swapped based on the option's `selected` state:
10
+ a checkmark when selected, nothing (a reserved-space placeholder) when not.
11
+
12
+ Swapping an indicator **per instance** is a slot: put your own element in the `selected-indicator`
13
+ or `deselected-indicator` slot and it replaces `part(selected-indicator-default)` /
14
+ `part(deselected-indicator-default)` entirely for that option.
15
+
16
+ Swapping an indicator **for every option in the app** doesn't need touching this component's
17
+ `render()` at all — style `::part(selected-indicator-default)` /
18
+ `::part(deselected-indicator-default)` from an outside stylesheet, the same way any shadow DOM
19
+ part is themed:
20
+
21
+ ```css
22
+ use-option::part(selected-indicator-default) {
23
+ mask-image: url("/icons/my-checkmark.svg");
24
+ }
25
+ ```
26
+
27
+ (When the design system's `theme.css` is loaded, this is already wired to a pair of tokens —
28
+ `--usewc-effect-selected-indicator-mask-image` for the shape and
29
+ `--usewc-effect-selected-indicator-color` for what paints it (`--usewc-effect-deselected-…`
30
+ for the other state) — so redefining those custom properties has the same effect without
31
+ writing a `::part()` rule at all.)
32
+
33
+ `::part()` only reaches CSS-expressible changes (a mask, a background, a transform). For
34
+ anything else — different markup, an icon font, logic beyond a mask — set the static
35
+ `selectedIcon` and `deselectedIcon` hooks. Every `<use-option>` in the page picks them up;
36
+ consumers keep writing the same tag, no new element to register or remember to use instead.
37
+ Set them once, as early as possible (an app's entry point, before any options connect) — Lit
38
+ calls `render()` fresh on every update, so an already-connected instance only reflects the
39
+ change once something causes it to re-render:
40
+
41
+ ```ts
42
+ // A bare string or TemplateResult is used as-is, on every option:
43
+ UseOption.selectedIcon = "✓";
44
+ UseOption.deselectedIcon = "";
45
+
46
+ // A function is called on every render instead:
47
+ UseOption.selectedIcon = (html) => html`<my-icon name="check"></my-icon>`;
48
+ ```
49
+
50
+ The function form is called with the component's own `html` tag as its (optional) first
51
+ argument rather than requiring the caller to import one — a page can end up with more than one
52
+ copy of `lit-html` in its module graph, and a `TemplateResult` built from a *different* copy
53
+ than the one this component renders with can silently fail to render. Taking `html` in as a
54
+ parameter sidesteps that: whatever's returned is guaranteed to come from the same `lit-html`
55
+ this component already uses. A bare string skips the concern entirely.
56
+
57
+ A set icon still renders under `part(selected-indicator-default)` /
58
+ `part(deselected-indicator-default)` — the same part the design system's theme masks into the
59
+ built-in glyph. Combining a static hook with `theme.css` means the theme's masking still
60
+ applies to the custom content; reset the relevant `mask-image` token or `::part()` rule if that
61
+ fights with what was set.
62
+
63
+ Only need to change the icon on **some** options, not every one in the app? Extend the class,
64
+ override `render()` normally, and register the subclass under a different tag name so it
65
+ coexists with the built-in element instead of replacing it everywhere:
66
+
67
+ ```ts
68
+ class MyOption extends UseOption {
69
+ override render() {
70
+ return html`<slot></slot><slot name="selected-indicator"><my-icon name="check"></my-icon></slot>`;
71
+ }
72
+ }
73
+ customElements.define("my-option", MyOption);
74
+ ```
6
75
 
7
76
  ## Attributes
8
77
 
@@ -12,6 +81,16 @@ The children of this element are the content of the option and must not contain
12
81
  | disabled | - | - | - |
13
82
  | value | - | - | - |
14
83
 
84
+ ## Properties
85
+
86
+ | Name | Type | Default | Description |
87
+ | --- | --- | --- | --- |
88
+ | selectedIcon | `IconOverride \| undefined` | - | Global override for the selected-state indicator. When set, every `<use-option>` in the page
89
+ renders this inside `part(selected-indicator-default)` instead of the built-in ✔ character.
90
+ Unset (the default) keeps the built-in checkmark. |
91
+ | deselectedIcon | `IconOverride \| undefined` | - | Global override for the deselected-state indicator — the counterpart to selectedIcon.
92
+ Unset (the default) keeps the built-in (empty) placeholder. |
93
+
15
94
  ## Methods
16
95
 
17
96
  | Name | Signature | Description |
@@ -23,5 +102,6 @@ The children of this element are the content of the option and must not contain
23
102
 
24
103
  | Name | Description |
25
104
  | --- | --- |
26
- | `selected-indicator` | - |
27
- | `undefined` | - |
105
+ | `selected-indicator` | Replaces the default checkmark shown when the option is selected. |
106
+ | `deselected-indicator` | Replaces the default (empty) indicator shown when the option is not selected. |
107
+ | `default` | Option content. |
@@ -2,6 +2,13 @@
2
2
 
3
3
  Custom input control for managing selected values in a hierarchy.
4
4
 
5
+ A convenience for distributing a custom indicator to every item at once: slot an icon into
6
+ `use-tree` itself under `expanded-indicator`, `collapsed-indicator`, `selected-indicator`, or
7
+ `deselected-indicator`, and it's cloned into each lazily-discovered `use-treeitem`'s matching
8
+ slot. This is per-item slotting, just done once for the whole tree — it doesn't replace
9
+ `use-treeitem`'s own static hooks (`expandedIcon`, `collapsedIcon`, `selectedIcon`,
10
+ `deselectedIcon`), which still apply for indicators the tree hasn't cloned one over.
11
+
5
12
  ## Attributes
6
13
 
7
14
  | Name | Type | Default | Description |
@@ -35,4 +42,7 @@ Custom input control for managing selected values in a hierarchy.
35
42
  | Name | Description |
36
43
  | --- | --- |
37
44
  | `default` | NodeList of `use-treeitem` elements |
38
- | `arrow` | - |
45
+ | `expanded-indicator` | Cloned into every item's `expanded-indicator` slot. |
46
+ | `collapsed-indicator` | Cloned into every item's `collapsed-indicator` slot. |
47
+ | `selected-indicator` | Cloned into every item's `selected-indicator` slot. |
48
+ | `deselected-indicator` | Cloned into every item's `deselected-indicator` slot. |