@c2n/button-group 0.0.8 → 0.0.10
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.md +10 -3
- package/dist/button-group.js +94 -9
- package/package.json +7 -5
- package/types/src/button-group.d.ts +30 -1
- package/types/src/button-group.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @c2n/button-group
|
|
2
2
|
|
|
3
|
-
Button group built with Lit:
|
|
3
|
+
Button group built with Lit: attach `c2-button` / `c2-icon-button` items into one joined control or a polished segmented selector.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @c2n/button-group
|
|
@@ -21,11 +21,18 @@ npm install @c2n/button-group
|
|
|
21
21
|
<c2-button value="week">Week</c2-button>
|
|
22
22
|
<c2-button value="month">Month</c2-button>
|
|
23
23
|
</c2-button-group>
|
|
24
|
+
|
|
25
|
+
<c2-button-group appearance="segmented" size="m" value="week">
|
|
26
|
+
<c2-button value="day">Day</c2-button>
|
|
27
|
+
<c2-button value="week">Week</c2-button>
|
|
28
|
+
<c2-button value="month">Month</c2-button>
|
|
29
|
+
</c2-button-group>
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
- **Layout**: items share borders and only the outside corners are rounded (`--c2-button-group--border-radius`); a divider (`--c2-button-group__divider--width` / `--color`) separates them. `--c2-button-group--border` puts one border on every item for an outlined group. A positive `--c2-button-group--gap` separates the items into individually rounded buttons. `orientation="vertical"` stacks them.
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
33
|
+
- **Segmented**: `appearance="segmented"` provides an inset track and animated single-selection indicator. It selects the first enabled item by default, supports `size="s|m|l"`, and can fill its container with `stretched`.
|
|
34
|
+
- **Selection**: `selection="single"` keeps exactly one item pressed, `selection="multiple"` toggles items independently. Items identify themselves with `value` (their index otherwise); the group's `value` is the pressed value or a comma-separated list. `change` fires on pointer or keyboard selection with `detail.value`; setting `value` from code is silent.
|
|
35
|
+
- **Accessibility**: `role="group"` (name it with `aria-label`); pressed items carry `selected`, which `c2-button` and `c2-icon-button` announce as `aria-pressed`. Single selection supports Arrow, Home and End keys and skips disabled items.
|
|
29
36
|
- **Disabled**: `disabled` on the group disables every item and restores them when cleared, leaving items you disabled yourself alone.
|
|
30
37
|
|
|
31
38
|
Colours and sizes come from the buttons' own variables set on the group (`--c2-button__container--*`, including the `__selected--*` pressed state added for groups; `--c2-icon-button--*` for icon items). `--c2-button-group__item--flex: 1 1 0` makes every item the same width. The full list is in `custom-elements.json` and on the docs site.
|
package/dist/button-group.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { LitElement, html, isServer, unsafeCSS } from "lit";
|
|
2
|
-
import { property } from "lit/decorators.js";
|
|
1
|
+
import { LitElement, html, isServer, nothing, unsafeCSS } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
3
4
|
import { customElement } from "@c2n/core/element-helper.js";
|
|
4
5
|
import "@c2n/button";
|
|
6
|
+
import "@c2n/icon-button";
|
|
5
7
|
//#region src/button-group.scss?inline
|
|
6
|
-
var button_group_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n align-items: stretch;\n gap: var(--c2-button-group--gap,0px);\n vertical-align: middle;\n max-width: 100%;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n:host([orientation=vertical]) {\n flex-direction: column;\n align-items: stretch;\n}\n\n::slotted(*) {\n position: relative;\n flex: var(--c2-button-group__item--flex,0 0 auto);\n min-width: 0;\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n::slotted(*) {\n --c2-button__container--border: var(--c2-button-group--border,1px solid transparent);\n --c2-icon-button--border: var(--c2-button-group--border,1px solid transparent);\n}\n\n::slotted(:only-child) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host(:dir(rtl)) ::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host(:dir(rtl)) ::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:not(:first-child)) {\n margin-inline-start: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000));\n box-shadow: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000)) 0 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n:host(:dir(rtl)) ::slotted(:not(:first-child)) {\n box-shadow: max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000) 0 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n:host([orientation=vertical]) ::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host([orientation=vertical]) ::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px);\n}\n\n:host([orientation=vertical]) ::slotted(:not(:first-child)) {\n margin-inline-start: 0;\n margin-block-start: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000));\n box-shadow: 0 calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000)) 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n::slotted(:hover),\n::slotted(:focus-within),\n::slotted([selected]) {\n z-index: 1;\n}";
|
|
8
|
+
var button_group_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n align-items: stretch;\n gap: var(--c2-button-group--gap,0px);\n vertical-align: middle;\n max-width: 100%;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.indicator {\n display: none;\n}\n\n:host([orientation=vertical]) {\n flex-direction: column;\n align-items: stretch;\n}\n\n::slotted(*) {\n position: relative;\n flex: var(--c2-button-group__item--flex,0 0 auto);\n min-width: 0;\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n::slotted(*) {\n --c2-button__container--border: var(--c2-button-group--border,1px solid transparent);\n --c2-icon-button--border: var(--c2-button-group--border,1px solid transparent);\n}\n\n::slotted(:only-child) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host(:dir(rtl)) ::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host(:dir(rtl)) ::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px);\n}\n\n::slotted(:not(:first-child)) {\n margin-inline-start: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000));\n box-shadow: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000)) 0 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n:host(:dir(rtl)) ::slotted(:not(:first-child)) {\n box-shadow: max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000) 0 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n:host([orientation=vertical]) ::slotted(:first-child:not(:only-child)) {\n --c2-button__container--border-radius: var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n --c2-icon-button--border-radius: var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000);\n}\n\n:host([orientation=vertical]) ::slotted(:last-child:not(:only-child)) {\n --c2-button__container--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px);\n --c2-icon-button--border-radius: min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) min(var(--c2-button-group--border-radius,6px), var(--c2-button-group--gap,0px) * 10000) var(--c2-button-group--border-radius,6px) var(--c2-button-group--border-radius,6px);\n}\n\n:host([orientation=vertical]) ::slotted(:not(:first-child)) {\n margin-inline-start: 0;\n margin-block-start: calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000));\n box-shadow: 0 calc(-1 * max(0px, var(--c2-button-group__divider--width,1px) - var(--c2-button-group--gap,0px) * 10000)) 0 0 var(--c2-button-group__divider--color,rgba(255, 255, 255, 0.35));\n}\n\n::slotted(:hover),\n::slotted(:focus-within),\n::slotted([selected]) {\n z-index: 1;\n}\n\n:host([appearance=segmented]) {\n position: relative;\n box-sizing: border-box;\n padding: var(--c2-button-group__segmented--padding,3px);\n gap: var(--c2-button-group__segmented--gap,2px);\n border: var(--c2-button-group__segmented--border,1px solid transparent);\n border-radius: var(--c2-button-group--border-radius,6px);\n background-color: var(--c2-button-group__segmented--background-color,#f4f4f5);\n isolation: isolate;\n}\n\n:host([appearance=segmented][stretched]) {\n display: flex;\n width: 100%;\n}\n\n:host([appearance=segmented]) .indicator {\n position: absolute;\n inset: 0 auto auto 0;\n z-index: 0;\n display: block;\n box-sizing: border-box;\n pointer-events: none;\n border: var(--c2-button-group__indicator--border,1px solid rgba(24, 24, 27, 0.08));\n border-radius: var(--c2-button-group__indicator--border-radius,6px);\n background-color: var(--c2-button-group__indicator--background-color,#ffffff);\n box-shadow: var(--c2-button-group__indicator--box-shadow,0 1px 3px rgba(24, 24, 27, 0.16));\n will-change: transform;\n transition: transform var(--c2-button-group__indicator--transition-duration,200ms) cubic-bezier(0.2, 0, 0, 1), width var(--c2-button-group__indicator--transition-duration,200ms) cubic-bezier(0.2, 0, 0, 1), height var(--c2-button-group__indicator--transition-duration,200ms) cubic-bezier(0.2, 0, 0, 1);\n}\n\n:host([appearance=segmented]) ::slotted(*) {\n z-index: 1;\n margin: 0;\n box-shadow: none;\n flex: var(--c2-button-group__item--flex,0 0 auto);\n --c2-button__container--border: 1px solid transparent;\n --c2-button__container--border-radius: var(--c2-button-group__indicator--border-radius,6px);\n --c2-button__container--background-color: transparent;\n --c2-button__container--color: var(--c2-button-group__segmented-item--color,#52525b);\n --c2-button__container__hover--background-color: var(--c2-button-group__segmented-item__hover--background-color,rgba(0, 0, 0, 0.05));\n --c2-button__container__active--background-color: var(--c2-button-group__segmented-item__active--background-color,rgba(0, 0, 0, 0.08));\n --c2-button__container__selected--background-color: transparent;\n --c2-button__container__selected--color: var(--c2-button-group__segmented-item__selected--color,#18181b);\n --c2-button__container__selected--border: 1px solid transparent;\n --c2-icon-button--border: 1px solid transparent;\n --c2-icon-button--border-radius: var(--c2-button-group__indicator--border-radius,6px);\n --c2-icon-button--background-color: transparent;\n --c2-icon-button__hover--background-color: var(--c2-button-group__segmented-item__hover--background-color,rgba(0, 0, 0, 0.05));\n --c2-icon-button__selected--background-color: transparent;\n}\n\n:host([appearance=segmented][stretched]) ::slotted(*) {\n flex: 1 1 0;\n}\n\n:host([appearance=segmented][size=s]) ::slotted(*) {\n --c2-button__container--height: 28px;\n --c2-button__container--padding-left: 10px;\n --c2-button__container--padding-right: 10px;\n --c2-button__container--font-size: 12px;\n --c2-icon-button__state-layer--size: 28px;\n --c2-icon-button__icon--width: 16px;\n --c2-icon-button__icon--height: 16px;\n}\n\n:host([appearance=segmented][size=m]) ::slotted(*) {\n --c2-button__container--height: 32px;\n --c2-button__container--padding-left: 14px;\n --c2-button__container--padding-right: 14px;\n --c2-button__container--font-size: 14px;\n --c2-icon-button__state-layer--size: 32px;\n --c2-icon-button__icon--width: 18px;\n --c2-icon-button__icon--height: 18px;\n}\n\n:host([appearance=segmented][size=l]) ::slotted(*) {\n --c2-button__container--height: 40px;\n --c2-button__container--padding-left: 18px;\n --c2-button__container--padding-right: 18px;\n --c2-button__container--font-size: 15px;\n --c2-icon-button__state-layer--size: 40px;\n --c2-icon-button__icon--width: 20px;\n --c2-icon-button__icon--height: 20px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n :host([appearance=segmented]) .indicator {\n transition: none;\n }\n}";
|
|
7
9
|
//#endregion
|
|
8
10
|
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
9
11
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -25,14 +27,19 @@ var ButtonGroup = class ButtonGroup extends LitElement {
|
|
|
25
27
|
this.value = "";
|
|
26
28
|
this.disabled = false;
|
|
27
29
|
this.orientation = "horizontal";
|
|
30
|
+
this.appearance = "joined";
|
|
31
|
+
this.size = "m";
|
|
32
|
+
this.stretched = false;
|
|
33
|
+
this.indicatorStyle = {};
|
|
28
34
|
this.handleClick = (event) => {
|
|
29
|
-
|
|
35
|
+
const selection = this.effectiveSelection;
|
|
36
|
+
if (selection === "none" || this.disabled) return;
|
|
30
37
|
const items = this.items;
|
|
31
38
|
const item = event.composedPath().find((node) => node instanceof HTMLElement && node.parentElement === this);
|
|
32
39
|
if (!item || item.hasAttribute("disabled")) return;
|
|
33
40
|
const value = this.itemValue(item, items.indexOf(item));
|
|
34
41
|
let next;
|
|
35
|
-
if (
|
|
42
|
+
if (selection === "single") {
|
|
36
43
|
if (this.value === value) return;
|
|
37
44
|
next = value;
|
|
38
45
|
} else {
|
|
@@ -46,7 +53,39 @@ var ButtonGroup = class ButtonGroup extends LitElement {
|
|
|
46
53
|
composed: true
|
|
47
54
|
}));
|
|
48
55
|
};
|
|
49
|
-
|
|
56
|
+
this.handleKeyDown = (event) => {
|
|
57
|
+
const directionKeys = this.orientation === "vertical" ? ["ArrowUp", "ArrowDown"] : ["ArrowLeft", "ArrowRight"];
|
|
58
|
+
if (this.effectiveSelection !== "single" || this.disabled || ![
|
|
59
|
+
...directionKeys,
|
|
60
|
+
"Home",
|
|
61
|
+
"End"
|
|
62
|
+
].includes(event.key)) return;
|
|
63
|
+
const enabledItems = this.items.filter((item) => !item.hasAttribute("disabled"));
|
|
64
|
+
if (!enabledItems.length) return;
|
|
65
|
+
const current = event.composedPath().find((node) => node instanceof HTMLElement && enabledItems.includes(node));
|
|
66
|
+
if (!current) return;
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
const currentIndex = enabledItems.indexOf(current);
|
|
69
|
+
const isRtl = getComputedStyle(this).direction === "rtl";
|
|
70
|
+
let nextIndex;
|
|
71
|
+
if (event.key === "Home") nextIndex = 0;
|
|
72
|
+
else if (event.key === "End") nextIndex = enabledItems.length - 1;
|
|
73
|
+
else nextIndex = (currentIndex + (event.key === "ArrowUp" || event.key === "ArrowLeft" && !isRtl || event.key === "ArrowRight" && isRtl ? -1 : 1) + enabledItems.length) % enabledItems.length;
|
|
74
|
+
const nextItem = enabledItems[nextIndex];
|
|
75
|
+
const value = this.itemValue(nextItem, this.items.indexOf(nextItem));
|
|
76
|
+
nextItem.focus();
|
|
77
|
+
if (this.value === value) return;
|
|
78
|
+
this.value = value;
|
|
79
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
80
|
+
detail: { value },
|
|
81
|
+
bubbles: true,
|
|
82
|
+
composed: true
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
if (!isServer) {
|
|
86
|
+
this.addEventListener("click", this.handleClick);
|
|
87
|
+
this.addEventListener("keydown", this.handleKeyDown);
|
|
88
|
+
}
|
|
50
89
|
}
|
|
51
90
|
connectedCallback() {
|
|
52
91
|
super.connectedCallback();
|
|
@@ -54,12 +93,16 @@ var ButtonGroup = class ButtonGroup extends LitElement {
|
|
|
54
93
|
if (!isServer) {
|
|
55
94
|
this.observer ??= new MutationObserver(() => this.syncItems());
|
|
56
95
|
this.observer.observe(this, { childList: true });
|
|
96
|
+
this.resizeObserver ??= new ResizeObserver(() => this.queueIndicatorUpdate());
|
|
97
|
+
this.resizeObserver.observe(this);
|
|
57
98
|
this.syncItems();
|
|
58
99
|
}
|
|
59
100
|
}
|
|
60
101
|
disconnectedCallback() {
|
|
61
102
|
super.disconnectedCallback();
|
|
62
103
|
this.observer?.disconnect();
|
|
104
|
+
this.resizeObserver?.disconnect();
|
|
105
|
+
if (this.indicatorFrame !== void 0) cancelAnimationFrame(this.indicatorFrame);
|
|
63
106
|
}
|
|
64
107
|
/** The item elements, in order. */
|
|
65
108
|
get items() {
|
|
@@ -73,12 +116,21 @@ var ButtonGroup = class ButtonGroup extends LitElement {
|
|
|
73
116
|
return item.getAttribute("value") ?? String(index);
|
|
74
117
|
}
|
|
75
118
|
updated(changed) {
|
|
76
|
-
if (changed.has("value") || changed.has("selection") || changed.has("disabled")) this.syncItems();
|
|
119
|
+
if (changed.has("value") || changed.has("selection") || changed.has("disabled") || changed.has("appearance")) this.syncItems();
|
|
120
|
+
if (changed.has("value") || changed.has("selection") || changed.has("appearance") || changed.has("orientation") || changed.has("size") || changed.has("stretched")) this.queueIndicatorUpdate();
|
|
121
|
+
}
|
|
122
|
+
get effectiveSelection() {
|
|
123
|
+
return this.appearance === "segmented" ? "single" : this.selection;
|
|
77
124
|
}
|
|
78
125
|
/** Mirrors `value`, `selection` and `disabled` onto the items. */
|
|
79
126
|
syncItems() {
|
|
80
127
|
const items = this.items;
|
|
81
|
-
const
|
|
128
|
+
const selection = this.effectiveSelection;
|
|
129
|
+
const selectable = selection !== "none";
|
|
130
|
+
if (selection === "single" && this.appearance === "segmented" && !this.value) {
|
|
131
|
+
const firstEnabledIndex = items.findIndex((item) => !item.hasAttribute("disabled"));
|
|
132
|
+
if (firstEnabledIndex >= 0) this.value = this.itemValue(items[firstEnabledIndex], firstEnabledIndex);
|
|
133
|
+
}
|
|
82
134
|
const selected = new Set(this.selectedValues);
|
|
83
135
|
items.forEach((item, index) => {
|
|
84
136
|
if (selectable) item.setAttribute("toggle", "");
|
|
@@ -95,9 +147,35 @@ var ButtonGroup = class ButtonGroup extends LitElement {
|
|
|
95
147
|
item.removeAttribute(GROUP_DISABLED);
|
|
96
148
|
}
|
|
97
149
|
});
|
|
150
|
+
this.resizeObserver?.disconnect();
|
|
151
|
+
this.resizeObserver?.observe(this);
|
|
152
|
+
items.forEach((item) => this.resizeObserver?.observe(item));
|
|
153
|
+
this.queueIndicatorUpdate();
|
|
154
|
+
}
|
|
155
|
+
queueIndicatorUpdate() {
|
|
156
|
+
if (isServer) return;
|
|
157
|
+
if (this.indicatorFrame !== void 0) cancelAnimationFrame(this.indicatorFrame);
|
|
158
|
+
this.indicatorFrame = requestAnimationFrame(() => {
|
|
159
|
+
this.indicatorFrame = void 0;
|
|
160
|
+
const selected = this.items.find((item, index) => this.itemValue(item, index) === this.value);
|
|
161
|
+
if (this.appearance !== "segmented" || !selected) {
|
|
162
|
+
this.indicatorStyle = {};
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const hostRect = this.getBoundingClientRect();
|
|
166
|
+
const itemRect = selected.getBoundingClientRect();
|
|
167
|
+
this.indicatorStyle = {
|
|
168
|
+
width: `${itemRect.width}px`,
|
|
169
|
+
height: `${itemRect.height}px`,
|
|
170
|
+
transform: `translate3d(${itemRect.left - hostRect.left - this.clientLeft}px, ${itemRect.top - hostRect.top - this.clientTop}px, 0)`
|
|
171
|
+
};
|
|
172
|
+
});
|
|
98
173
|
}
|
|
99
174
|
render() {
|
|
100
|
-
return html
|
|
175
|
+
return html`
|
|
176
|
+
<span class="indicator" style=${Object.keys(this.indicatorStyle).length ? styleMap(this.indicatorStyle) : nothing} aria-hidden="true"></span>
|
|
177
|
+
<slot @slotchange=${this.syncItems}></slot>
|
|
178
|
+
`;
|
|
101
179
|
}
|
|
102
180
|
};
|
|
103
181
|
__decorate([property({ reflect: true })], ButtonGroup.prototype, "selection", void 0);
|
|
@@ -107,10 +185,17 @@ __decorate([property({
|
|
|
107
185
|
reflect: true
|
|
108
186
|
})], ButtonGroup.prototype, "disabled", void 0);
|
|
109
187
|
__decorate([property({ reflect: true })], ButtonGroup.prototype, "orientation", void 0);
|
|
188
|
+
__decorate([property({ reflect: true })], ButtonGroup.prototype, "appearance", void 0);
|
|
189
|
+
__decorate([property({ reflect: true })], ButtonGroup.prototype, "size", void 0);
|
|
190
|
+
__decorate([property({
|
|
191
|
+
type: Boolean,
|
|
192
|
+
reflect: true
|
|
193
|
+
})], ButtonGroup.prototype, "stretched", void 0);
|
|
110
194
|
__decorate([property({
|
|
111
195
|
type: String,
|
|
112
196
|
attribute: "aria-label"
|
|
113
197
|
})], ButtonGroup.prototype, "ariaLabel", void 0);
|
|
198
|
+
__decorate([state()], ButtonGroup.prototype, "indicatorStyle", void 0);
|
|
114
199
|
ButtonGroup = __decorate([customElement("c2-button-group")], ButtonGroup);
|
|
115
200
|
//#endregion
|
|
116
201
|
export { ButtonGroup };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c2n/button-group",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/button-group.js",
|
|
6
6
|
"exports": {
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"type-check": {
|
|
54
54
|
"dependencies": [
|
|
55
55
|
"../../core:build",
|
|
56
|
-
"../button:build"
|
|
56
|
+
"../button:build",
|
|
57
|
+
"../icon-button:build"
|
|
57
58
|
],
|
|
58
59
|
"command": "tsc -p tsconfig.lib.json --composite false"
|
|
59
60
|
},
|
|
@@ -65,13 +66,14 @@
|
|
|
65
66
|
}
|
|
66
67
|
},
|
|
67
68
|
"dependencies": {
|
|
68
|
-
"@c2n/button": "0.0.
|
|
69
|
-
"@c2n/core": "0.0.
|
|
69
|
+
"@c2n/button": "0.0.10",
|
|
70
|
+
"@c2n/core": "0.0.10",
|
|
71
|
+
"@c2n/icon-button": "0.0.10",
|
|
70
72
|
"lit": "3.3.3"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
73
75
|
"@c2n/config": "*"
|
|
74
76
|
},
|
|
75
77
|
"customElements": "custom-elements.json",
|
|
76
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "8d57710b3345fcb01cdb4cb15d2d43ba8bf21642"
|
|
77
79
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { LitElement, type PropertyValues } from 'lit';
|
|
2
2
|
import type { TypedAddEventListener, TypedRemoveEventListener } from '@c2n/core/event-helper.js';
|
|
3
3
|
import '@c2n/button';
|
|
4
|
+
import '@c2n/icon-button';
|
|
4
5
|
export type ButtonGroupSelection = 'none' | 'single' | 'multiple';
|
|
5
6
|
export type ButtonGroupOrientation = 'horizontal' | 'vertical';
|
|
7
|
+
export type ButtonGroupAppearance = 'joined' | 'segmented';
|
|
8
|
+
export type ButtonGroupSize = 's' | 'm' | 'l';
|
|
6
9
|
/** Events fired by {@link ButtonGroup}, keyed for `addEventListener`. */
|
|
7
10
|
export interface ButtonGroupEventMap {
|
|
8
11
|
change: CustomEvent<{
|
|
@@ -16,7 +19,8 @@ export interface ButtonGroup {
|
|
|
16
19
|
/**
|
|
17
20
|
* Attaches related buttons into one control: shared borders, outside corners only, a divider between items. Children
|
|
18
21
|
* are `c2-button` or `c2-icon-button` elements (any element works for layout). With `selection="single"` the group is
|
|
19
|
-
* a segmented control that keeps one item pressed; with `selection="multiple"` each item toggles independently.
|
|
22
|
+
* a segmented control that keeps one item pressed; with `selection="multiple"` each item toggles independently. Set
|
|
23
|
+
* `orientation="vertical"` to stack either appearance and use Up/Down keyboard navigation. The
|
|
20
24
|
* pressed items carry `selected` (and `aria-pressed`), the group reports them in `value` and fires `change`.
|
|
21
25
|
*
|
|
22
26
|
* @tag c2-button-group
|
|
@@ -31,6 +35,19 @@ export interface ButtonGroup {
|
|
|
31
35
|
* @cssproperty {pixel} [--c2-button-group__divider--width=1px] - Width of the line between attached items; also how much their borders overlap.
|
|
32
36
|
* @cssproperty {color} [--c2-button-group__divider--color=rgba(255, 255, 255, 0.35)] - Colour of the line between attached items. Match the border colour for outlined groups, `transparent` for none.
|
|
33
37
|
* @cssproperty {flex} [--c2-button-group__item--flex=0 0 auto] - `1 1 0` makes every item grow to the same width.
|
|
38
|
+
* @cssproperty {color} [--c2-button-group__segmented--background-color=#f4f4f5] - Track background in the segmented appearance.
|
|
39
|
+
* @cssproperty {border} [--c2-button-group__segmented--border=1px solid transparent] - Track border in the segmented appearance.
|
|
40
|
+
* @cssproperty {padding} [--c2-button-group__segmented--padding=3px] - Space around the segmented items.
|
|
41
|
+
* @cssproperty {pixel} [--c2-button-group__segmented--gap=2px] - Space between segmented items.
|
|
42
|
+
* @cssproperty {color} [--c2-button-group__segmented-item--color=#52525b] - Segmented item text colour.
|
|
43
|
+
* @cssproperty {color} [--c2-button-group__segmented-item__hover--background-color=rgba(0, 0, 0, 0.05)] - Segmented item hover background.
|
|
44
|
+
* @cssproperty {color} [--c2-button-group__segmented-item__active--background-color=rgba(0, 0, 0, 0.08)] - Segmented item pressed background.
|
|
45
|
+
* @cssproperty {color} [--c2-button-group__segmented-item__selected--color=#18181b] - Selected segmented item text colour.
|
|
46
|
+
* @cssproperty {color} [--c2-button-group__indicator--background-color=#ffffff] - Sliding selection indicator background.
|
|
47
|
+
* @cssproperty {border} [--c2-button-group__indicator--border=1px solid rgba(24, 24, 27, 0.08)] - Sliding selection indicator border.
|
|
48
|
+
* @cssproperty {border-radius} [--c2-button-group__indicator--border-radius=6px] - Sliding selection indicator corner radius.
|
|
49
|
+
* @cssproperty {box-shadow} [--c2-button-group__indicator--box-shadow=0 1px 3px rgba(24, 24, 27, 0.16)] - Sliding selection indicator shadow.
|
|
50
|
+
* @cssproperty {duration} [--c2-button-group__indicator--transition-duration=200ms] - Sliding selection indicator animation duration.
|
|
34
51
|
* @slotcomponent c2-button
|
|
35
52
|
* @slotcomponent c2-icon-button
|
|
36
53
|
*/
|
|
@@ -44,9 +61,18 @@ export declare class ButtonGroup extends LitElement {
|
|
|
44
61
|
disabled: boolean;
|
|
45
62
|
/** Stacks the items vertically. */
|
|
46
63
|
orientation: ButtonGroupOrientation;
|
|
64
|
+
/** `segmented` adds an inset track and sliding selected surface and always uses single selection. */
|
|
65
|
+
appearance: ButtonGroupAppearance;
|
|
66
|
+
/** Segmented control size. */
|
|
67
|
+
size: ButtonGroupSize;
|
|
68
|
+
/** Makes the group and every item share the available width. */
|
|
69
|
+
stretched: boolean;
|
|
47
70
|
/** Accessible name of the group. */
|
|
48
71
|
ariaLabel: string;
|
|
49
72
|
private observer?;
|
|
73
|
+
private resizeObserver?;
|
|
74
|
+
private indicatorFrame?;
|
|
75
|
+
private indicatorStyle;
|
|
50
76
|
constructor();
|
|
51
77
|
connectedCallback(): void;
|
|
52
78
|
disconnectedCallback(): void;
|
|
@@ -56,9 +82,12 @@ export declare class ButtonGroup extends LitElement {
|
|
|
56
82
|
get selectedValues(): string[];
|
|
57
83
|
private itemValue;
|
|
58
84
|
updated(changed: PropertyValues<this>): void;
|
|
85
|
+
private get effectiveSelection();
|
|
59
86
|
/** Mirrors `value`, `selection` and `disabled` onto the items. */
|
|
60
87
|
private syncItems;
|
|
61
88
|
private handleClick;
|
|
89
|
+
private handleKeyDown;
|
|
90
|
+
private queueIndicatorUpdate;
|
|
62
91
|
render(): import("lit-html").TemplateResult<1>;
|
|
63
92
|
}
|
|
64
93
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../src/button-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../../src/button-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsC,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAIzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAEhG,OAAO,aAAa,CAAA;AAEpB,OAAO,kBAAkB,CAAA;AAGzB,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAA;AACjE,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,UAAU,CAAA;AAC9D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,WAAW,CAAA;AAC1D,MAAM,MAAM,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAI7C,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,EAAE,qBAAqB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAA;IACzE,mBAAmB,EAAE,wBAAwB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAA;CAChF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBACa,WAAY,SAAQ,UAAU;IACzC,OAAgB,MAAM,0BAAoB;IAE1C,sHAAsH;IACzF,SAAS,EAAE,oBAAoB,CAAS;IAErE,0IAA0I;IAC7G,KAAK,SAAK;IAEvC,2BAA2B;IACiB,QAAQ,UAAQ;IAE5D,mCAAmC;IACN,WAAW,EAAE,sBAAsB,CAAe;IAE/E,qGAAqG;IACxE,UAAU,EAAE,qBAAqB,CAAW;IAEzE,8BAA8B;IACD,IAAI,EAAE,eAAe,CAAM;IAExD,gEAAgE;IACpB,SAAS,UAAQ;IAE7D,oCAAoC;IAE3B,SAAS,EAAG,MAAM,CAAA;IAE3B,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,cAAc,CAAC,CAAQ;IAEtB,OAAO,CAAC,cAAc,CAA6B;;IAUnD,iBAAiB;IAYjB,oBAAoB;IAO7B,mCAAmC;IACnC,IAAI,KAAK,IAAI,WAAW,EAAE,CAEzB;IAED,mCAAmC;IACnC,IAAI,cAAc,IAAI,MAAM,EAAE,CAE7B;IAED,OAAO,CAAC,SAAS;IAIR,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAa9C,OAAO,KAAK,kBAAkB,GAE7B;IAED,kEAAkE;IAClE,OAAO,CAAC,SAAS;IA+BjB,OAAO,CAAC,WAAW,CAiBlB;IAED,OAAO,CAAC,aAAa,CAwBpB;IAED,OAAO,CAAC,oBAAoB;IAqBnB,MAAM;CAMhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,iBAAiB,EAAE,WAAW,CAAA;KAC/B;CACF"}
|