@m3e/button-group 1.0.0-rc.1 → 1.0.0-rc.2

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/eslint.config.mjs DELETED
@@ -1,13 +0,0 @@
1
- import eslint from "@eslint/js";
2
- import tseslint from "typescript-eslint";
3
- import { fileURLToPath } from "url";
4
- import { dirname } from "path";
5
-
6
- export default tseslint.config(eslint.configs.recommended, tseslint.configs.recommended, {
7
- languageOptions: {
8
- parserOptions: {
9
- project: true,
10
- tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
11
- },
12
- },
13
- });
package/rollup.config.js DELETED
@@ -1,32 +0,0 @@
1
- import resolve from "@rollup/plugin-node-resolve";
2
- import terser from "@rollup/plugin-terser";
3
- import typescript from "@rollup/plugin-typescript";
4
-
5
- const banner = `/**
6
- * @license MIT
7
- * Copyright (c) 2025 matraic
8
- * See LICENSE file in the project root for full license text.
9
- */`;
10
-
11
- export default [
12
- {
13
- input: "src/index.ts",
14
- output: [
15
- {
16
- file: "dist/index.js",
17
- format: "esm",
18
- sourcemap: true,
19
- banner: banner,
20
- },
21
- {
22
- file: "dist/index.min.js",
23
- format: "esm",
24
- sourcemap: true,
25
- banner: banner,
26
- plugins: [terser({ mangle: true })],
27
- },
28
- ],
29
- external: ["@m3e/core", "@m3e/button", "@m3e/icon-button", "lit"],
30
- plugins: [resolve(), typescript()],
31
- },
32
- ];
@@ -1,385 +0,0 @@
1
- import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
2
- import { customElement, property, query, queryAssignedElements } from "lit/decorators.js";
3
-
4
- import { PressedController, Role, isSelectedMixin, DesignToken } from "@m3e/core";
5
- import { M3eButtonElement } from "@m3e/button";
6
- import { M3eIconButtonElement } from "@m3e/icon-button";
7
-
8
- import { ButtonGroupVariant } from "./ButtonGroupVariant";
9
- import { ButtonGroupSize } from "./ButtonGroupSize";
10
-
11
- /**
12
- * @summary
13
- * Organizes buttons and adds interactions between them.
14
- *
15
- * @description
16
- * The `m3e-button-group` component arranges multiple buttons into a unified, expressive layout,
17
- * supporting both `standard` and `connected` variants. It enables seamless, accessible grouping
18
- * of actions, adapts to various sizes, and ensures consistent spacing, shape, and alignment.
19
- * Designed according to Material 3 principles, it empowers users to interact with related actions
20
- * in a visually harmonious and intuitive way.
21
- *
22
- * @example
23
- * The following example illustrates a standard button group.
24
- * ``` html
25
- * <m3e-button-group>
26
- * <m3e-icon-button variant="tonal" toggle><m3e-icon name="format_bold"></m3e-icon></m3e-icon-button>
27
- * <m3e-icon-button variant="tonal" toggle><m3e-icon name="format_italic"></m3e-icon></m3e-icon-button>
28
- * <m3e-icon-button variant="tonal" toggle><m3e-icon name="format_underlined"></m3e-icon></m3e-icon-button>
29
- * </m3e-button-group>
30
- * ```
31
- * @example
32
- * The next example illustrates a connected button group.
33
- * ```html
34
- * <m3e-button-group variant="connected">
35
- * <m3e-button variant="tonal" shape="square" toggle>Start</m3e-button>
36
- * <m3e-button variant="tonal" shape="square" toggle>Directions</m3e-button>
37
- * <m3e-button variant="tonal" shape="square" toggle>Share</m3e-button>
38
- * </m3e-button-group>
39
- * ```
40
- *
41
- * @tag m3e-button-group
42
- *
43
- * @slot - Renders the buttons of the group.
44
- *
45
- * @attr multi - Whether multiple toggle buttons can be selected.
46
- * @attr size - The size of the group.
47
- * @attr variant - The appearance variant of the group.
48
- *
49
- * @cssprop --m3e-standard-button-group-extra-small-spacing - Spacing between buttons in standard variant, extra-small size.
50
- * @cssprop --m3e-standard-button-group-small-spacing - Spacing between buttons in standard variant, small size.
51
- * @cssprop --m3e-standard-button-group-medium-spacing - Spacing between buttons in standard variant, medium size.
52
- * @cssprop --m3e-standard-button-group-large-spacing - Spacing between buttons in standard variant, large size.
53
- * @cssprop --m3e-standard-button-group-extra-large-spacing - Spacing between buttons in standard variant, extra-large size.
54
- * @cssprop --m3e-connected-button-group-spacing - Spacing between buttons in connected variant.
55
- * @cssprop --m3e-connected-button-group-extra-small-inner-shape - Corner shape for connected variant, extra-small size.
56
- * @cssprop --m3e-connected-button-group-extra-small-inner-pressed-shape - Pressed corner shape for connected variant, extra-small size.
57
- * @cssprop --m3e-connected-button-group-small-inner-shape - Corner shape for connected variant, small size.
58
- * @cssprop --m3e-connected-button-group-small-inner-pressed-shape - Pressed corner shape for connected variant, small size.
59
- * @cssprop --m3e-connected-button-group-medium-inner-shape - Corner shape for connected variant, medium size.
60
- * @cssprop --m3e-connected-button-group-medium-inner-pressed-shape - Pressed corner shape for connected variant, medium size.
61
- * @cssprop --m3e-connected-button-group-large-inner-shape - Corner shape for connected variant, large size.
62
- * @cssprop --m3e-connected-button-group-large-inner-pressed-shape - Pressed corner shape for connected variant, large size.
63
- * @cssprop --m3e-connected-button-group-extra-large-inner-shape - Corner shape for connected variant, extra-large size.
64
- * @cssprop --m3e-connected-button-group-extra-large-inner-pressed-shape - Pressed corner shape for connected variant, extra-large size.
65
- */
66
- @customElement("m3e-button-group")
67
- export class M3eButtonGroupElement extends Role(LitElement, "group") {
68
- /** The styles of the element. */
69
- static override styles: CSSResultGroup = css`
70
- :host {
71
- display: flex;
72
- vertical-align: middle;
73
- flex-wrap: nowrap;
74
- align-items: center;
75
- }
76
- .base {
77
- display: flex;
78
- vertical-align: middle;
79
- flex-wrap: nowrap;
80
- align-items: center;
81
- }
82
- :host([variant="standard"]) {
83
- justify-content: center;
84
- }
85
- :host([variant="connected"]) .base {
86
- flex: 1 1 auto;
87
- }
88
- :host([variant="standard"]) .base {
89
- width: fit-content;
90
- flex: none;
91
- }
92
- :host([variant="standard"]) .base.pressed {
93
- justify-content: space-between;
94
- width: var(--_button-group-width);
95
- }
96
- :host([variant="standard"][size="extra-small"]) .base {
97
- column-gap: var(--m3e-standard-button-group-extra-small-spacing, 1.125rem);
98
- }
99
- :host([variant="standard"][size="small"]) .base {
100
- column-gap: var(--m3e-standard-button-group-small-spacing, 0.75rem);
101
- }
102
- :host([variant="standard"][size="medium"]).base {
103
- column-gap: var(--m3e-standard-button-group-medium-spacing, 0.5rem);
104
- }
105
- :host([variant="standard"][size="large"]) .base {
106
- column-gap: var(--m3e-standard-button-group-large-spacing, 0.5rem);
107
- }
108
- :host([variant="standard"][size="extra-large"]) .base {
109
- column-gap: var(--m3e-standard-button-group-extra-large-spacing, 0.5rem);
110
- }
111
- :host([variant="connected"]) .base {
112
- column-gap: var(--m3e-connected-button-group-spacing, 0.125rem);
113
- }
114
- :host([variant="connected"][size="extra-small"]) ::slotted(:first-child[size="extra-small"]),
115
- :host([variant="connected"][size="extra-small"])
116
- ::slotted(:not(:first-child):not(:last-child)[size="extra-small"]) {
117
- --_button-rounded-end-shape: var(
118
- --m3e-connected-button-group-extra-small-inner-shape,
119
- ${DesignToken.shape.corner.small}
120
- );
121
- --_button-square-end-shape: var(
122
- --m3e-connected-button-group-extra-small-inner-shape,
123
- ${DesignToken.shape.corner.small}
124
- );
125
- --_button-square-end-pressed-shape: var(
126
- --m3e-connected-button-group-extra-small-inner-pressed-shape,
127
- ${DesignToken.shape.corner.extraSmall}
128
- );
129
- }
130
- :host([variant="connected"][size="extra-small"]) ::slotted(:last-child[size="extra-small"]),
131
- :host([variant="connected"][size="extra-small"])
132
- ::slotted(:not(:first-child):not(:last-child)[size="extra-small"]) {
133
- --_button-rounded-start-shape: var(
134
- --m3e-connected-button-group-extra-small-inner-shape,
135
- ${DesignToken.shape.corner.small}
136
- );
137
- --_button-square-start-shape: var(
138
- --m3e-connected-button-group-extra-small-inner-shape,
139
- ${DesignToken.shape.corner.small}
140
- );
141
- --_button-square-start-pressed-shape: var(
142
- --m3e-connected-button-group-extra-small-inner-pressed-shape,
143
- ${DesignToken.shape.corner.extraSmall}
144
- );
145
- }
146
- :host([variant="connected"][size="small"]) ::slotted(:first-child[size="small"]),
147
- :host([variant="connected"][size="small"]) ::slotted(:not(:first-child):not(:last-child)[size="small"]) {
148
- --_button-rounded-end-shape: var(
149
- --m3e-connected-button-group-small-inner-shape,
150
- ${DesignToken.shape.corner.small}
151
- );
152
- --_button-square-end-shape: var(
153
- --m3e-connected-button-group-small-inner-shape,
154
- ${DesignToken.shape.corner.small}
155
- );
156
- --_button-end-shape-pressed-morph: var(
157
- --m3e-connected-button-group-small-inner-pressed-shape,
158
- ${DesignToken.shape.corner.extraSmall}
159
- );
160
- }
161
- :host([variant="connected"][size="small"]) ::slotted(:last-child[size="small"]),
162
- :host([variant="connected"][size="small"]) ::slotted(:not(:first-child):not(:last-child)[size="small"]) {
163
- --_button-rounded-start-shape: var(
164
- --m3e-connected-button-group-small-inner-shape,
165
- ${DesignToken.shape.corner.small}
166
- );
167
- --_button-square-start-shape: var(
168
- --m3e-connected-button-group-small-inner-shape,
169
- ${DesignToken.shape.corner.small}
170
- );
171
- --_button-start-shape-pressed-morph: var(
172
- --m3e-connected-button-group-small-inner-pressed-shape,
173
- ${DesignToken.shape.corner.extraSmall}
174
- );
175
- }
176
- :host([variant="connected"][size="medium"]) ::slotted(:first-child[size="medium"]),
177
- :host([variant="connected"][size="medium"]) ::slotted(:not(:first-child):not(:last-child)[size="medium"]) {
178
- --_button-rounded-end-shape: var(
179
- --m3e-connected-button-group-medium-inner-shape,
180
- ${DesignToken.shape.corner.small}
181
- );
182
- --_button-square-end-shape: var(
183
- --m3e-connected-button-group-medium-inner-shape,
184
- ${DesignToken.shape.corner.small}
185
- );
186
- --_button-square-end-pressed-shape: var(
187
- --m3e-connected-button-group-medium-inner-pressed-shape,
188
- ${DesignToken.shape.corner.extraSmall}
189
- );
190
- }
191
- :host([variant="connected"][size="medium"]) ::slotted(:last-child[size="medium"]),
192
- :host([variant="connected"][size="medium"]) ::slotted(:not(:first-child):not(:last-child)[size="medium"]) {
193
- --_button-rounded-start-shape: var(
194
- --m3e-connected-button-group-medium-inner-shape,
195
- ${DesignToken.shape.corner.small}
196
- );
197
- --_button-square-start-shape: var(
198
- --m3e-connected-button-group-medium-inner-shape,
199
- ${DesignToken.shape.corner.small}
200
- );
201
- --_button-square-start-pressed-shape: var(
202
- --m3e-connected-button-group-medium-inner-pressed-shape,
203
- ${DesignToken.shape.corner.extraSmall}
204
- );
205
- }
206
- :host([variant="connected"][size="large"]) ::slotted(:first-child[size="large"]),
207
- :host([variant="connected"][size="large"]) ::slotted(:not(:first-child):not(:last-child)[size="large"]) {
208
- --_button-rounded-end-shape: var(
209
- --m3e-connected-button-group-large-inner-shape,
210
- ${DesignToken.shape.corner.large}
211
- );
212
- --_button-square-end-shape: var(
213
- --m3e-connected-button-group-large-inner-shape,
214
- ${DesignToken.shape.corner.large}
215
- );
216
- --_button-square-end-pressed-shape: var(
217
- --m3e-connected-button-group-large-inner-pressed-shape,
218
- ${DesignToken.shape.corner.medium}
219
- );
220
- }
221
- :host([variant="connected"][size="large"]) ::slotted(:last-child[size="large"]),
222
- :host([variant="connected"][size="large"]) ::slotted(:not(:first-child):not(:last-child)[size="large"]) {
223
- --_button-rounded-start-shape: var(
224
- --m3e-connected-button-group-large-inner-shape,
225
- ${DesignToken.shape.corner.large}
226
- );
227
- --_button-square-start-shape: var(
228
- --m3e-connected-button-group-large-inner-shape,
229
- ${DesignToken.shape.corner.large}
230
- );
231
- --_button-square-start-pressed-shape: var(
232
- --m3e-connected-button-group-large-inner-pressed-shape,
233
- ${DesignToken.shape.corner.medium}
234
- );
235
- }
236
- :host([variant="connected"][size="extra-large"]) ::slotted(:first-child[size="extra-large"]),
237
- :host([variant="connected"][size="extra-large"])
238
- ::slotted(:not(:first-child):not(:last-child)[size="extra-large"]) {
239
- --_button-rounded-end-shape: var(
240
- --m3e-connected-button-group-extra-large-inner-shape,
241
- ${DesignToken.shape.corner.largeIncreased}
242
- );
243
- --_button-square-end-shape: var(
244
- --m3e-connected-button-group-extra-large-inner-shape,
245
- ${DesignToken.shape.corner.largeIncreased}
246
- );
247
- --_button-square-end-pressed-shape: var(
248
- --m3e-connected-button-group-extra-large-inner-pressed-shape,
249
- ${DesignToken.shape.corner.large}
250
- );
251
- }
252
- :host([variant="connected"][size="extra-large"]) ::slotted(:last-child[size="extra-large"]),
253
- :host([variant="connected"][size="extra-large"])
254
- ::slotted(:not(:first-child):not(:last-child)[size="extra-large"]) {
255
- --_button-rounded-start-shape: var(
256
- --m3e-connected-button-group-extra-large-inner-shape,
257
- ${DesignToken.shape.corner.largeIncreased}
258
- );
259
- --_button-square-start-shape: var(
260
- --m3e-connected-button-group-extra-large-inner-shape,
261
- ${DesignToken.shape.corner.largeIncreased}
262
- );
263
- --_button-square-start-pressed-shape: var(
264
- --m3e-connected-button-group-extra-large-inner-pressed-shape,
265
- ${DesignToken.shape.corner.large}
266
- );
267
- }
268
- `;
269
-
270
- /** @private */ readonly #pressedController = new PressedController(this, {
271
- target: null,
272
- capture: true,
273
- isPressedKey: (key) => key === " " || key === "Enter",
274
- callback: (pressed) => {
275
- if (!this._base) return;
276
- if (!pressed || this.variant === "connected") {
277
- this._base.style.removeProperty("--_button-group-width");
278
- this._base.classList.remove("pressed");
279
- } else {
280
- this._base.classList.add("pressed");
281
- this._base.style.setProperty("--_button-group-width", `${this._base.getBoundingClientRect().width}px`);
282
- }
283
- },
284
- });
285
-
286
- @query(".base") private readonly _base?: HTMLElement;
287
-
288
- /**
289
- * The appearance variant of the group.
290
- * @default "standard"
291
- */
292
- @property({ reflect: true }) variant: ButtonGroupVariant = "standard";
293
-
294
- /**
295
- * The size of the group.
296
- * @default "small"
297
- */
298
- @property({ reflect: true }) size: ButtonGroupSize = "small";
299
-
300
- /**
301
- * Whether multiple toggle buttons can be selected.
302
- * @default false
303
- */
304
- @property({ type: Boolean }) multi = false;
305
-
306
- /** The buttons contained by the group. */
307
- @queryAssignedElements({ slot: "", selector: "m3e-button,m3e-icon-button", flatten: true })
308
- readonly buttons!: ReadonlyArray<M3eButtonElement | M3eIconButtonElement>;
309
-
310
- /** @inheritdoc */
311
- override disconnectedCallback(): void {
312
- super.disconnectedCallback();
313
- this._base?.style.removeProperty("--_button-group-width");
314
- this._base?.classList.remove("pressed");
315
- }
316
-
317
- /** @inheritdoc */
318
- protected override update(changedProperties: PropertyValues<this>): void {
319
- super.update(changedProperties);
320
-
321
- if (changedProperties.has("multi") || changedProperties.has("variant")) {
322
- this.#updateButtons();
323
- }
324
- if (changedProperties.has("variant")) {
325
- this._base?.style.removeProperty("--_button-group-width");
326
- }
327
- }
328
-
329
- /** @inheritdoc */
330
- protected override render(): unknown {
331
- return html`<div class="base">
332
- <slot @slotchange="${this.#updateButtons}" @change="${this.#handleChange}"></slot>
333
- </div>`;
334
- }
335
-
336
- /** @private */
337
- #updateButtons(): void {
338
- for (const target of this.#pressedController.targets) {
339
- this.#pressedController.unobserve(target);
340
- }
341
- const canToggle = [...this.buttons].some((x) => x.toggle);
342
- this.role = canToggle && !this.multi ? "radiogroup" : "group";
343
-
344
- const buttonRole = this.role === "radiogroup" ? "radio" : "button";
345
-
346
- for (const button of this.buttons) {
347
- this.#pressedController.observe(button);
348
- button.classList.toggle("-connected", this.variant === "connected");
349
- button.classList.add("-grouped");
350
-
351
- if (button.role !== buttonRole && button.toggle) {
352
- const checked = !button.toggle ? null : button.selected ? "true" : "false";
353
- button.role = buttonRole;
354
- if (button.role === "button") {
355
- button.ariaPressed = checked;
356
- button.ariaChecked = null;
357
- } else {
358
- button.ariaChecked = checked;
359
- button.ariaPressed = null;
360
- }
361
- }
362
- }
363
- }
364
-
365
- /** @private */
366
- #handleChange(e: Event): void {
367
- if (this.multi || !(e.target instanceof HTMLElement)) return;
368
- if (e.target.tagName === "M3E-BUTTON" || e.target.tagName === "M3E-ICON-BUTTON") {
369
- if (!isSelectedMixin(e.target) || !e.target.selected) {
370
- return;
371
- }
372
-
373
- for (const button of this.buttons) {
374
- if (button === e.target || !button.selected) continue;
375
- button.selected = false;
376
- }
377
- }
378
- }
379
- }
380
-
381
- declare global {
382
- interface HTMLElementTagNameMap {
383
- "m3e-button-group": M3eButtonGroupElement;
384
- }
385
- }
@@ -1,2 +0,0 @@
1
- /** Specifies the possible sizes of a button a group. */
2
- export type ButtonGroupSize = "extra-small" | "small" | "medium" | "large" | "extra-large";
@@ -1,2 +0,0 @@
1
- /** Specifies the possible appearance variants of a button group. */
2
- export type ButtonGroupVariant = "standard" | "connected";
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from "./ButtonGroupElement";
2
- export * from "./ButtonGroupSize";
3
- export * from "./ButtonGroupVariant";
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist/src"
6
- },
7
- "include": ["src/**/*.ts", "**/*.mjs", "**/*.js"],
8
- "exclude": []
9
- }