@timmo001/groundwork-ui 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +427 -9
- package/package.json +1 -1
- package/src/gw-alert.ts +4 -0
- package/src/gw-app-bar.ts +106 -42
- package/src/gw-badge.ts +11 -2
- package/src/gw-banner.ts +4 -0
- package/src/gw-card.ts +6 -3
- package/src/gw-chip.ts +3 -0
- package/src/gw-command-menu.ts +7 -3
- package/src/gw-dialog.ts +15 -5
- package/src/gw-dot.ts +2 -0
- package/src/gw-expansion.ts +7 -2
- package/src/gw-facts.ts +13 -5
- package/src/gw-gauge.ts +11 -5
- package/src/gw-meter.ts +17 -4
- package/src/gw-notification-center.ts +14 -3
- package/src/gw-notification.ts +2 -0
- package/src/gw-option-list.ts +26 -5
- package/src/gw-page-header.ts +7 -3
- package/src/gw-popover.ts +14 -4
- package/src/gw-section-title.ts +3 -1
- package/src/gw-segmented.ts +2 -1
- package/src/gw-skeleton.ts +3 -1
- package/src/gw-spinner.ts +5 -1
- package/src/gw-theme-picker.ts +145 -40
- package/src/gw-toast.ts +3 -1
package/src/gw-option-list.ts
CHANGED
|
@@ -81,6 +81,13 @@ let lastList = 0;
|
|
|
81
81
|
* @attr {boolean} search - Shows a search field above the list.
|
|
82
82
|
* @attr {string} placeholder - The search placeholder. Defaults to `Search`.
|
|
83
83
|
*
|
|
84
|
+
* @csspart search - The search field's row.
|
|
85
|
+
* @csspart listbox - The scrolling list.
|
|
86
|
+
* @csspart option - Each option.
|
|
87
|
+
* @csspart group - Each group heading.
|
|
88
|
+
* @csspart description - An option's second line.
|
|
89
|
+
* @csspart none - The no matches line.
|
|
90
|
+
*
|
|
84
91
|
* @cssprop --gw-option-list-height - The list's maximum height. Defaults to `20rem`.
|
|
85
92
|
*
|
|
86
93
|
* @fires {CustomEvent<{ value: string }>} pick - When an option is picked, with its `value` in `detail`.
|
|
@@ -273,11 +280,15 @@ export class GwOptionList extends GwElement {
|
|
|
273
280
|
|
|
274
281
|
readonly #listbox = h(
|
|
275
282
|
"div",
|
|
276
|
-
{ role: "listbox", tabindex: "-1" },
|
|
283
|
+
{ role: "listbox", part: "listbox", tabindex: "-1" },
|
|
277
284
|
this.#spacer,
|
|
278
285
|
);
|
|
279
286
|
|
|
280
|
-
readonly #none = h(
|
|
287
|
+
readonly #none = h(
|
|
288
|
+
"div",
|
|
289
|
+
{ class: "none", part: "none", hidden: true },
|
|
290
|
+
"No matches",
|
|
291
|
+
);
|
|
281
292
|
|
|
282
293
|
#query = "";
|
|
283
294
|
|
|
@@ -433,7 +444,12 @@ export class GwOptionList extends GwElement {
|
|
|
433
444
|
});
|
|
434
445
|
|
|
435
446
|
root.append(
|
|
436
|
-
h(
|
|
447
|
+
h(
|
|
448
|
+
"div",
|
|
449
|
+
{ class: "search", part: "search" },
|
|
450
|
+
createIcon(searchIcon),
|
|
451
|
+
this.#input,
|
|
452
|
+
),
|
|
437
453
|
this.#listbox,
|
|
438
454
|
this.#none,
|
|
439
455
|
);
|
|
@@ -585,7 +601,7 @@ export class GwOptionList extends GwElement {
|
|
|
585
601
|
if (row.kind === "group") {
|
|
586
602
|
const element = h(
|
|
587
603
|
"div",
|
|
588
|
-
{ class: "row group", role: "presentation" },
|
|
604
|
+
{ class: "row group", part: "group", role: "presentation" },
|
|
589
605
|
row.label,
|
|
590
606
|
);
|
|
591
607
|
|
|
@@ -602,6 +618,7 @@ export class GwOptionList extends GwElement {
|
|
|
602
618
|
"div",
|
|
603
619
|
{
|
|
604
620
|
class: "row",
|
|
621
|
+
part: "option",
|
|
605
622
|
role: "option",
|
|
606
623
|
id: `${this.#id}-${index}`,
|
|
607
624
|
"data-index": index,
|
|
@@ -618,7 +635,11 @@ export class GwOptionList extends GwElement {
|
|
|
618
635
|
h("span", {}, option.label),
|
|
619
636
|
option.description === undefined
|
|
620
637
|
? null
|
|
621
|
-
: h(
|
|
638
|
+
: h(
|
|
639
|
+
"span",
|
|
640
|
+
{ class: "description", part: "description" },
|
|
641
|
+
option.description,
|
|
642
|
+
),
|
|
622
643
|
),
|
|
623
644
|
option.swatches === undefined
|
|
624
645
|
? null
|
package/src/gw-page-header.ts
CHANGED
|
@@ -19,6 +19,10 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
19
19
|
* @attr {string} icon - SVG path data for the icon tile.
|
|
20
20
|
*
|
|
21
21
|
* @slot actions - The page's controls.
|
|
22
|
+
*
|
|
23
|
+
* @csspart tile - The icon tile.
|
|
24
|
+
* @csspart heading - The title.
|
|
25
|
+
* @csspart description - The line under the title.
|
|
22
26
|
*/
|
|
23
27
|
export class GwPageHeader extends GwElement {
|
|
24
28
|
static override properties = {
|
|
@@ -86,11 +90,11 @@ export class GwPageHeader extends GwElement {
|
|
|
86
90
|
|
|
87
91
|
readonly #icon = createIcon("");
|
|
88
92
|
|
|
89
|
-
readonly #tile = h("span", { class: "tile" }, this.#icon);
|
|
93
|
+
readonly #tile = h("span", { class: "tile", part: "tile" }, this.#icon);
|
|
90
94
|
|
|
91
|
-
readonly #heading = h("h1");
|
|
95
|
+
readonly #heading = h("h1", { part: "heading" });
|
|
92
96
|
|
|
93
|
-
readonly #description = h("p");
|
|
97
|
+
readonly #description = h("p", { part: "description" });
|
|
94
98
|
|
|
95
99
|
constructor() {
|
|
96
100
|
super();
|
package/src/gw-popover.ts
CHANGED
|
@@ -38,6 +38,11 @@ const sheetBelow = 600;
|
|
|
38
38
|
* @slot - The content.
|
|
39
39
|
* @slot actions - Buttons; one with a `value` closes it with that value.
|
|
40
40
|
*
|
|
41
|
+
* @csspart surface - The popover's surface.
|
|
42
|
+
* @csspart heading - The heading.
|
|
43
|
+
* @csspart body - The content.
|
|
44
|
+
* @csspart footer - The actions row.
|
|
45
|
+
*
|
|
41
46
|
* @cssprop --gw-popover-width - Its width beside a button. Defaults to `20rem`.
|
|
42
47
|
*
|
|
43
48
|
* @fires {Event} close - When it closes; `returnValue` holds the button's value.
|
|
@@ -187,7 +192,7 @@ export class GwPopover extends GwElement {
|
|
|
187
192
|
/** The value of the action that closed it, or "" for Esc or a click outside. */
|
|
188
193
|
returnValue = "";
|
|
189
194
|
|
|
190
|
-
readonly #heading = h("h2", { id: "heading" });
|
|
195
|
+
readonly #heading = h("h2", { id: "heading", part: "heading" });
|
|
191
196
|
|
|
192
197
|
readonly #closeButton = h(
|
|
193
198
|
"gw-button",
|
|
@@ -204,7 +209,12 @@ export class GwPopover extends GwElement {
|
|
|
204
209
|
|
|
205
210
|
readonly #popover = h(
|
|
206
211
|
"div",
|
|
207
|
-
{
|
|
212
|
+
{
|
|
213
|
+
popover: "manual",
|
|
214
|
+
tabindex: "-1",
|
|
215
|
+
"data-placement": "center",
|
|
216
|
+
part: "surface",
|
|
217
|
+
},
|
|
208
218
|
h(
|
|
209
219
|
"header",
|
|
210
220
|
{},
|
|
@@ -212,11 +222,11 @@ export class GwPopover extends GwElement {
|
|
|
212
222
|
"div",
|
|
213
223
|
{ class: "content" },
|
|
214
224
|
this.#heading,
|
|
215
|
-
h("div", { class: "body", id: "body" }, h("slot")),
|
|
225
|
+
h("div", { class: "body", id: "body", part: "body" }, h("slot")),
|
|
216
226
|
),
|
|
217
227
|
this.#closeButton,
|
|
218
228
|
),
|
|
219
|
-
h("footer", {}, h("slot", { name: "actions" })),
|
|
229
|
+
h("footer", { part: "footer" }, h("slot", { name: "actions" })),
|
|
220
230
|
);
|
|
221
231
|
|
|
222
232
|
readonly #dismissal = new Dismissal(
|
package/src/gw-section-title.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { base } from "./styles.js";
|
|
|
17
17
|
*
|
|
18
18
|
* @slot - The heading text.
|
|
19
19
|
* @slot actions - Buttons at the end of the row.
|
|
20
|
+
*
|
|
21
|
+
* @csspart heading - The heading.
|
|
20
22
|
*/
|
|
21
23
|
export class GwSectionTitle extends GwElement {
|
|
22
24
|
static override properties = {
|
|
@@ -46,7 +48,7 @@ export class GwSectionTitle extends GwElement {
|
|
|
46
48
|
|
|
47
49
|
declare level: number;
|
|
48
50
|
|
|
49
|
-
readonly #heading = h("div", { role: "heading" }, h("slot"));
|
|
51
|
+
readonly #heading = h("div", { role: "heading", part: "heading" }, h("slot"));
|
|
50
52
|
|
|
51
53
|
constructor() {
|
|
52
54
|
super();
|
package/src/gw-segmented.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface Segment {
|
|
|
32
32
|
* @attr {"show"|"hide"|"auto"} labels - Whether option text shows beside icons. Defaults to `show`.
|
|
33
33
|
* @attr {Segment[]} options - The options, as JSON.
|
|
34
34
|
*
|
|
35
|
+
* @csspart option - Each option.
|
|
35
36
|
* @csspart indicator - The pill that slides to the chosen option.
|
|
36
37
|
*
|
|
37
38
|
* @fires {Event} change - When the chosen option changes.
|
|
@@ -267,7 +268,7 @@ export class GwSegmented extends GwElement {
|
|
|
267
268
|
|
|
268
269
|
const label = h(
|
|
269
270
|
"label",
|
|
270
|
-
{ class: option.icon ? "has-icon" : null },
|
|
271
|
+
{ class: option.icon ? "has-icon" : null, part: "option" },
|
|
271
272
|
input,
|
|
272
273
|
option.icon ? createIcon(option.icon) : null,
|
|
273
274
|
h("span", { class: "text" }, option.label),
|
package/src/gw-skeleton.ts
CHANGED
|
@@ -89,6 +89,8 @@ export class GwSkeletonText extends GwSkeleton {
|
|
|
89
89
|
* @summary A placeholder for a paragraph.
|
|
90
90
|
*
|
|
91
91
|
* @attr {number} lines - How many lines. Defaults to 3.
|
|
92
|
+
*
|
|
93
|
+
* @csspart line - Each line.
|
|
92
94
|
*/
|
|
93
95
|
export class GwSkeletonLines extends GwElement {
|
|
94
96
|
static override properties = {
|
|
@@ -129,7 +131,7 @@ export class GwSkeletonLines extends GwElement {
|
|
|
129
131
|
protected override update() {
|
|
130
132
|
this.renderRoot.replaceChildren(
|
|
131
133
|
...Array.from({ length: Math.max(1, this.lines || 1) }, () =>
|
|
132
|
-
h("gw-skeleton-text"),
|
|
134
|
+
h("gw-skeleton-text", { part: "line" }),
|
|
133
135
|
),
|
|
134
136
|
);
|
|
135
137
|
}
|
package/src/gw-spinner.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { base } from "./styles.js";
|
|
|
15
15
|
*
|
|
16
16
|
* @attr {string} label - What's loading, for assistive technology. Defaults to `Loading`.
|
|
17
17
|
*
|
|
18
|
+
* @csspart ring - The spinning ring.
|
|
19
|
+
*
|
|
18
20
|
* @cssprop --gw-spinner-size - The ring's size. Defaults to `1.25rem`.
|
|
19
21
|
*/
|
|
20
22
|
export class GwSpinner extends GwElement {
|
|
@@ -67,7 +69,9 @@ export class GwSpinner extends GwElement {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
protected override create(root: ShadowRoot) {
|
|
70
|
-
root.append(
|
|
72
|
+
root.append(
|
|
73
|
+
h("span", { class: "ring", part: "ring", role: "status" }, this.#label),
|
|
74
|
+
);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
protected override update() {
|
package/src/gw-theme-picker.ts
CHANGED
|
@@ -17,11 +17,22 @@ import { base } from "./styles.js";
|
|
|
17
17
|
const paletteIcon =
|
|
18
18
|
"M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// MDI's sunny, moon and night (a moon with stars), for cycle's button. Auto
|
|
21
|
+
// keeps the palette
|
|
22
|
+
const modeIcons = {
|
|
23
|
+
auto: paletteIcon,
|
|
24
|
+
light:
|
|
25
|
+
"M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M3.36,17L5.12,13.23C5.26,14 5.53,14.78 5.95,15.5C6.37,16.24 6.91,16.86 7.5,17.37L3.36,17M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M20.64,17L16.5,17.36C17.09,16.85 17.62,16.22 18.04,15.5C18.46,14.77 18.73,14 18.87,13.21L20.64,17M12,22L9.59,18.56C10.33,18.83 11.14,19 12,19C12.82,19 13.63,18.83 14.37,18.56L12,22Z",
|
|
26
|
+
dark: "M2 12A10 10 0 0 0 15 21.54A10 10 0 0 1 15 2.46A10 10 0 0 0 2 12Z",
|
|
27
|
+
darker:
|
|
28
|
+
"M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const allModes = [
|
|
32
|
+
{ value: "auto", label: "Auto", icon: modeIcons.auto },
|
|
33
|
+
{ value: "light", label: "Light", icon: modeIcons.light },
|
|
34
|
+
{ value: "dark", label: "Dark", icon: modeIcons.dark },
|
|
35
|
+
{ value: "darker", label: "Darker", icon: modeIcons.darker },
|
|
25
36
|
] as const;
|
|
26
37
|
|
|
27
38
|
/**
|
|
@@ -37,17 +48,28 @@ const modes = [
|
|
|
37
48
|
* `gallery` swaps the theme select for `gw-theme-gallery`'s cards, which
|
|
38
49
|
* preview each theme in the chosen mode.
|
|
39
50
|
*
|
|
51
|
+
* `layout` sets how it shows. `popover`, the default, is the palette button
|
|
52
|
+
* and its popover. `cycle` is one button showing the mode in use, which steps
|
|
53
|
+
* to the next mode on each press, for an app bar. `inline` lays the controls
|
|
54
|
+
* out in place, in one wrapping row with Reset and Import, for a footer or a
|
|
55
|
+
* settings page; set `justify-content` on it to align the row. `modes` picks which modes it offers, such as `light dark` without
|
|
56
|
+
* Auto.
|
|
57
|
+
*
|
|
40
58
|
* @element gw-theme-picker
|
|
41
59
|
* @summary A button opening the page's mode, theme and import.
|
|
42
60
|
*
|
|
61
|
+
* @attr {"popover"|"cycle"|"inline"} layout - How it shows: a button and popover, a button cycling the modes, or the controls in place. Defaults to `popover`.
|
|
62
|
+
* @attr {string} modes - The modes to offer, in order and separated by spaces. Defaults to `auto light dark darker`.
|
|
43
63
|
* @attr {"auto"|"light"|"dark"|"darker"} mode - The mode in use. Defaults to `auto`.
|
|
44
64
|
* @attr {string} theme - The theme in use, by id.
|
|
45
65
|
* @attr {OptionItem[]} themes - The themes to offer, as JSON. The select hides with fewer than two. With `gallery`, a `group` gathers a family's themes.
|
|
46
66
|
* @attr {boolean} gallery - Shows the themes as preview cards rather than a select.
|
|
47
67
|
* @attr {string} status - A line under the controls, such as an import's result.
|
|
48
|
-
* @attr {string} label - The button's accessible name and the popover's heading. Defaults to `Theme`.
|
|
68
|
+
* @attr {string} label - The button's accessible name and the popover's heading. Defaults to `Theme`. With `cycle`, the button's name also gives the mode in use.
|
|
49
69
|
*
|
|
50
70
|
* @csspart gallery - The theme gallery, with `gallery`.
|
|
71
|
+
* @csspart controls - The mode, theme and status.
|
|
72
|
+
* @csspart actions - Reset and Import, with `inline`.
|
|
51
73
|
*
|
|
52
74
|
* @fires {CustomEvent<{ mode: string }>} mode - When a mode is picked.
|
|
53
75
|
* @fires {CustomEvent<{ id: string }>} theme - When a theme is picked.
|
|
@@ -62,6 +84,8 @@ export class GwThemePicker extends GwElement {
|
|
|
62
84
|
gallery: { type: "boolean", reflect: true },
|
|
63
85
|
status: {},
|
|
64
86
|
label: {},
|
|
87
|
+
layout: { reflect: true },
|
|
88
|
+
modes: {},
|
|
65
89
|
} as const;
|
|
66
90
|
|
|
67
91
|
static override styles = [
|
|
@@ -95,6 +119,28 @@ export class GwThemePicker extends GwElement {
|
|
|
95
119
|
font-size: var(--gw-font-size-s);
|
|
96
120
|
color: var(--gw-color-text-secondary);
|
|
97
121
|
}
|
|
122
|
+
|
|
123
|
+
/* One wrapping row: the controls, then Reset and Import. The host is
|
|
124
|
+
the flex container, so a page can set its justify-content */
|
|
125
|
+
:host([layout="inline"]) {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
gap: var(--gw-space-4);
|
|
129
|
+
align-items: center;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:host([layout="inline"]) .controls {
|
|
133
|
+
flex-flow: row wrap;
|
|
134
|
+
align-items: center;
|
|
135
|
+
margin-block-start: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.actions {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
gap: var(--gw-space-2);
|
|
142
|
+
align-items: center;
|
|
143
|
+
}
|
|
98
144
|
`,
|
|
99
145
|
];
|
|
100
146
|
|
|
@@ -104,6 +150,10 @@ export class GwThemePicker extends GwElement {
|
|
|
104
150
|
declare gallery: boolean;
|
|
105
151
|
declare status: string;
|
|
106
152
|
declare label: string;
|
|
153
|
+
/** How it shows: a button and popover, a button cycling the modes, or the controls in place. */
|
|
154
|
+
declare layout: "popover" | "cycle" | "inline";
|
|
155
|
+
/** The modes to offer, in order and separated by spaces. */
|
|
156
|
+
declare modes: string;
|
|
107
157
|
|
|
108
158
|
readonly #button = h("gw-button", {
|
|
109
159
|
variant: "neutral",
|
|
@@ -129,34 +179,27 @@ export class GwThemePicker extends GwElement {
|
|
|
129
179
|
hidden: true,
|
|
130
180
|
});
|
|
131
181
|
|
|
132
|
-
readonly #
|
|
133
|
-
"
|
|
134
|
-
{},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
slot: "actions",
|
|
147
|
-
variant: "neutral",
|
|
148
|
-
appearance: "plain",
|
|
149
|
-
class: "reset",
|
|
150
|
-
},
|
|
151
|
-
"Reset",
|
|
152
|
-
),
|
|
153
|
-
h(
|
|
154
|
-
"gw-button",
|
|
155
|
-
{ slot: "actions", appearance: "outlined", class: "import" },
|
|
156
|
-
"Import",
|
|
157
|
-
),
|
|
182
|
+
readonly #controls = h(
|
|
183
|
+
"div",
|
|
184
|
+
{ class: "controls", part: "controls" },
|
|
185
|
+
this.#mode,
|
|
186
|
+
this.#theme,
|
|
187
|
+
this.#gallery,
|
|
188
|
+
this.#status,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
readonly #reset = h(
|
|
192
|
+
"gw-button",
|
|
193
|
+
{ variant: "neutral", appearance: "plain" },
|
|
194
|
+
"Reset",
|
|
158
195
|
);
|
|
159
196
|
|
|
197
|
+
readonly #import = h("gw-button", { appearance: "outlined" }, "Import");
|
|
198
|
+
|
|
199
|
+
readonly #popover = h("gw-popover");
|
|
200
|
+
|
|
201
|
+
readonly #actions = h("div", { class: "actions", part: "actions" });
|
|
202
|
+
|
|
160
203
|
constructor() {
|
|
161
204
|
super();
|
|
162
205
|
this.mode = "auto";
|
|
@@ -165,22 +208,65 @@ export class GwThemePicker extends GwElement {
|
|
|
165
208
|
this.gallery = false;
|
|
166
209
|
this.status = "";
|
|
167
210
|
this.label = "Theme";
|
|
211
|
+
this.layout = "popover";
|
|
212
|
+
this.modes = "auto light dark darker";
|
|
168
213
|
}
|
|
169
214
|
|
|
170
|
-
/**
|
|
215
|
+
/** The modes on offer, in the order `modes` gives them. */
|
|
216
|
+
#offered() {
|
|
217
|
+
const wanted = String(this.modes).split(/\s+/);
|
|
218
|
+
const offered = allModes.filter(({ value }) => wanted.includes(value));
|
|
219
|
+
|
|
220
|
+
return offered.length > 0 ? offered : allModes;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Opens the popover under the button, or with `cycle`, steps to the next mode. */
|
|
171
224
|
show() {
|
|
225
|
+
if (this.layout === "inline") return;
|
|
226
|
+
|
|
227
|
+
if (this.layout === "cycle") {
|
|
228
|
+
const offered = this.#offered();
|
|
229
|
+
const at = offered.findIndex(({ value }) => value === this.mode);
|
|
230
|
+
const next = offered[(at + 1) % offered.length];
|
|
231
|
+
|
|
232
|
+
if (next !== undefined) {
|
|
233
|
+
this.mode = next.value;
|
|
234
|
+
this.dispatchEvent(this.#detail("mode", { mode: this.mode }));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
172
240
|
this.#popover.show(this.#button);
|
|
173
241
|
|
|
174
242
|
// A custom theme's CSS may have changed since the cards were drawn
|
|
175
243
|
if (this.gallery) this.#gallery.refresh();
|
|
176
244
|
}
|
|
177
245
|
|
|
246
|
+
/** Puts the controls and their buttons in the popover, or in place for `inline`. */
|
|
247
|
+
#place(root: ShadowRoot) {
|
|
248
|
+
const inline = this.layout === "inline";
|
|
249
|
+
|
|
250
|
+
this.#reset.slot = inline ? "" : "actions";
|
|
251
|
+
this.#import.slot = inline ? "" : "actions";
|
|
252
|
+
|
|
253
|
+
if (inline) {
|
|
254
|
+
this.#actions.append(this.#reset, this.#import);
|
|
255
|
+
root.append(this.#controls, this.#actions);
|
|
256
|
+
this.#button.remove();
|
|
257
|
+
this.#popover.remove();
|
|
258
|
+
} else {
|
|
259
|
+
this.#popover.append(this.#controls, this.#reset, this.#import);
|
|
260
|
+
root.prepend(this.#button, this.#popover);
|
|
261
|
+
this.#actions.remove();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
178
265
|
#detail<Detail>(type: string, detail: Detail) {
|
|
179
266
|
return new CustomEvent(type, { detail, bubbles: true, composed: true });
|
|
180
267
|
}
|
|
181
268
|
|
|
182
269
|
protected override create(root: ShadowRoot) {
|
|
183
|
-
this.#mode.options = modes;
|
|
184
270
|
this.#button.addEventListener("click", () => {
|
|
185
271
|
this.show();
|
|
186
272
|
});
|
|
@@ -199,10 +285,10 @@ export class GwThemePicker extends GwElement {
|
|
|
199
285
|
this.theme = this.#gallery.theme;
|
|
200
286
|
this.dispatchEvent(this.#detail("theme", { id: this.theme }));
|
|
201
287
|
});
|
|
202
|
-
this.#
|
|
288
|
+
this.#import.addEventListener("click", () => {
|
|
203
289
|
this.#file.click();
|
|
204
290
|
});
|
|
205
|
-
this.#
|
|
291
|
+
this.#reset.addEventListener("click", () => {
|
|
206
292
|
this.dispatchEvent(new Event("reset", { bubbles: true, composed: true }));
|
|
207
293
|
});
|
|
208
294
|
this.#file.addEventListener("change", async () => {
|
|
@@ -217,14 +303,33 @@ export class GwThemePicker extends GwElement {
|
|
|
217
303
|
this.dispatchEvent(this.#detail("import", { text, name: file.name }));
|
|
218
304
|
});
|
|
219
305
|
|
|
220
|
-
root.append(this.#
|
|
306
|
+
root.append(this.#file);
|
|
307
|
+
this.#place(root);
|
|
221
308
|
}
|
|
222
309
|
|
|
223
|
-
protected override update() {
|
|
224
|
-
|
|
225
|
-
|
|
310
|
+
protected override update(changed: ReadonlySet<string>) {
|
|
311
|
+
const root = this.shadowRoot;
|
|
312
|
+
|
|
313
|
+
if (changed.has("layout") && root !== null) this.#place(root);
|
|
314
|
+
|
|
315
|
+
const offered = this.#offered();
|
|
316
|
+
const current = offered.find(({ value }) => value === this.mode);
|
|
317
|
+
|
|
318
|
+
if (this.layout === "cycle") {
|
|
319
|
+
const name = `${this.label}: ${current?.label ?? this.mode}`;
|
|
320
|
+
|
|
321
|
+
this.#button.label = name;
|
|
322
|
+
this.#button.textContent = name;
|
|
323
|
+
this.#button.icon = current?.icon ?? paletteIcon;
|
|
324
|
+
} else {
|
|
325
|
+
this.#button.label = this.label;
|
|
326
|
+
this.#button.textContent = this.label;
|
|
327
|
+
this.#button.icon = paletteIcon;
|
|
328
|
+
}
|
|
329
|
+
|
|
226
330
|
this.#popover.heading = this.label;
|
|
227
331
|
this.#mode.label = "Mode";
|
|
332
|
+
this.#mode.options = offered.map(({ value, label }) => ({ value, label }));
|
|
228
333
|
this.#mode.value = this.mode;
|
|
229
334
|
this.#theme.options = Array.isArray(this.themes) ? this.themes : [];
|
|
230
335
|
this.#theme.value = this.theme;
|
package/src/gw-toast.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { base, type Tone } from "./styles.js";
|
|
|
22
22
|
* @summary Brief, non-interactive toasts in the bottom end corner.
|
|
23
23
|
*
|
|
24
24
|
* @attr {number} duration - Milliseconds each toast shows for. Defaults to 5000.
|
|
25
|
+
*
|
|
26
|
+
* @csspart toast - Each toast.
|
|
25
27
|
*/
|
|
26
28
|
export class GwToaster extends GwElement {
|
|
27
29
|
static override properties = {
|
|
@@ -111,7 +113,7 @@ export class GwToaster extends GwElement {
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
show(message: string, tone: Tone = "neutral") {
|
|
114
|
-
const toast = h("div", { class: "toast" }, message);
|
|
116
|
+
const toast = h("div", { class: "toast", part: "toast" }, message);
|
|
115
117
|
|
|
116
118
|
toast.style.setProperty(
|
|
117
119
|
"--gw-toast-tone",
|