@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.
@@ -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("div", { class: "none", hidden: true }, "No matches");
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("div", { class: "search" }, createIcon(searchIcon), this.#input),
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("span", { class: "description" }, option.description),
638
+ : h(
639
+ "span",
640
+ { class: "description", part: "description" },
641
+ option.description,
642
+ ),
622
643
  ),
623
644
  option.swatches === undefined
624
645
  ? null
@@ -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
- { popover: "manual", tabindex: "-1", "data-placement": "center" },
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(
@@ -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();
@@ -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),
@@ -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(h("span", { class: "ring", role: "status" }, this.#label));
72
+ root.append(
73
+ h("span", { class: "ring", part: "ring", role: "status" }, this.#label),
74
+ );
71
75
  }
72
76
 
73
77
  protected override update() {
@@ -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
- const modes = [
21
- { value: "auto", label: "Auto" },
22
- { value: "light", label: "Light" },
23
- { value: "dark", label: "Dark" },
24
- { value: "darker", label: "Darker" },
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 #popover = h(
133
- "gw-popover",
134
- {},
135
- h(
136
- "div",
137
- { class: "controls" },
138
- this.#mode,
139
- this.#theme,
140
- this.#gallery,
141
- this.#status,
142
- ),
143
- h(
144
- "gw-button",
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
- /** Opens the popover under the button. */
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.#popover.querySelector(".import")?.addEventListener("click", () => {
288
+ this.#import.addEventListener("click", () => {
203
289
  this.#file.click();
204
290
  });
205
- this.#popover.querySelector(".reset")?.addEventListener("click", () => {
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.#button, this.#popover, this.#file);
306
+ root.append(this.#file);
307
+ this.#place(root);
221
308
  }
222
309
 
223
- protected override update() {
224
- this.#button.label = this.label;
225
- this.#button.textContent = this.label;
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",