@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/gw-alert.ts CHANGED
@@ -23,6 +23,10 @@ import { base, type Tone } from "./styles.js";
23
23
  * @slot actions - Buttons beside the message.
24
24
  *
25
25
  * @csspart alert - The alert.
26
+ *
27
+ * @cssprop --gw-tone - The icon color, for a tone of your own. `tone` sets it.
28
+ * @cssprop --gw-tone-fill - The background. `tone` sets it.
29
+ * @cssprop --gw-tone-border - The border. `tone` sets it.
26
30
  */
27
31
  export class GwAlert extends GwElement {
28
32
  static override properties = {
package/src/gw-app-bar.ts CHANGED
@@ -13,21 +13,20 @@ const menuIcon = "M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z";
13
13
  const closeIcon =
14
14
  "M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z";
15
15
 
16
- /** Below this width the nav moves into a menu. */
17
- const menuBelow = 600;
18
-
19
16
  /**
20
17
  * A site or app header pinned to the top of the page, with a brand, nav
21
18
  * links and actions, full width with its content kept to the content width.
22
19
  * It's transparent at first, then gets a blurred surface and a bottom border
23
20
  * once the page scrolls, or once the element
24
21
  * `reveal-after` names scrolls out of view, such as a hero. With
25
- * `reveal-after` the brand stays hidden until then, so a large logo in the
26
- * hero hands over to the small one in the bar.
22
+ * `reveal-after` the brand stays hidden, taking no room, until then, so a
23
+ * large logo in the hero hands over to the small one in the bar. The nav
24
+ * slides over as it opens.
27
25
  *
28
26
  * `overlay` lays it over the top of the page rather than taking room, for a
29
- * page that opens with a hero. Below 600px wide the nav moves into a menu
30
- * opened from a button, which closes on Esc, a click outside or a link.
27
+ * page that opens with a hero. Below `menu-below`, 600px wide by default,
28
+ * the nav moves into a menu opened from a button, which closes on Esc, a
29
+ * click outside or a link. Raise it when the nav and actions need more room.
31
30
  *
32
31
  * @element gw-app-bar
33
32
  * @summary A header pinned to the top, floating once the page scrolls.
@@ -35,6 +34,8 @@ const menuBelow = 600;
35
34
  * @attr {string} reveal-after - A CSS selector for the element, such as a hero, to scroll past before the bar floats and the brand shows.
36
35
  * @attr {boolean} overlay - Lays the bar over the top of the page instead of taking room.
37
36
  * @attr {boolean} floating - Whether it floats. Set by the bar as the page scrolls.
37
+ * @attr {number} menu-below - The viewport width in pixels below which the nav moves into a menu. Defaults to `600`.
38
+ * @attr {boolean} collapsed - Whether the nav is in the menu. Set by the bar from `menu-below`.
38
39
  * @attr {boolean} menu-open - Whether the nav menu is open on narrow screens.
39
40
  * @attr {string} menu-label - The menu button's accessible name. Defaults to `Menu`.
40
41
  *
@@ -43,7 +44,9 @@ const menuBelow = 600;
43
44
  * @slot actions - Icon links and buttons at the end.
44
45
  *
45
46
  * @csspart bar - The bar.
47
+ * @csspart brand - The brand's container.
46
48
  * @csspart nav - The nav links' container, the menu panel on narrow screens.
49
+ * @csspart actions - The actions' container.
47
50
  *
48
51
  * @fires {Event} toggle - When the nav menu opens or closes.
49
52
  */
@@ -54,6 +57,8 @@ export class GwAppBar extends GwElement {
54
57
  floating: { type: "boolean", reflect: true },
55
58
  menuOpen: { type: "boolean", attribute: "menu-open", reflect: true },
56
59
  menuLabel: { attribute: "menu-label" },
60
+ menuBelow: { type: "number", attribute: "menu-below" },
61
+ collapsed: { type: "boolean", reflect: true },
57
62
  } as const;
58
63
 
59
64
  static override styles = [
@@ -97,24 +102,43 @@ export class GwAppBar extends GwElement {
97
102
  backdrop-filter: blur(12px) saturate(1.4);
98
103
  }
99
104
 
105
+ /* A one-column grid, so a hidden brand can fold its width to nothing
106
+ and the nav slides over as it opens, rather than leaving a gap */
100
107
  .brand {
101
- display: flex;
108
+ display: grid;
102
109
  flex: none;
110
+ grid-template-columns: 1fr;
103
111
  align-items: center;
104
112
  transition:
113
+ grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing),
114
+ margin var(--gw-motion-slow) var(--gw-motion-easing),
105
115
  opacity var(--gw-motion-normal) var(--gw-motion-easing),
106
116
  translate var(--gw-motion-normal) var(--gw-motion-easing);
107
117
  }
108
118
 
119
+ .brand > slot {
120
+ display: flex;
121
+ align-items: center;
122
+ min-width: 0;
123
+ overflow: clip;
124
+ overflow-clip-margin: calc(
125
+ var(--gw-focus-width) + var(--gw-focus-offset)
126
+ );
127
+ }
128
+
109
129
  :host([reveal-after]:not([floating])) .brand {
130
+ grid-template-columns: 0fr;
131
+ margin-inline-end: calc(var(--gw-space-3) * -1);
110
132
  pointer-events: none;
111
133
  visibility: hidden;
112
134
  opacity: 0;
113
135
  translate: 0 -0.25rem;
114
136
  transition:
137
+ grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing),
138
+ margin var(--gw-motion-slow) var(--gw-motion-easing),
115
139
  opacity var(--gw-motion-normal) var(--gw-motion-easing),
116
140
  translate var(--gw-motion-normal) var(--gw-motion-easing),
117
- visibility var(--gw-motion-normal) allow-discrete;
141
+ visibility var(--gw-motion-slow) allow-discrete;
118
142
  }
119
143
 
120
144
  .nav {
@@ -152,37 +176,37 @@ export class GwAppBar extends GwElement {
152
176
  background: var(--gw-color-fill-neutral-hovered);
153
177
  }
154
178
 
155
- @media (max-width: ${menuBelow - 0.02}px) {
156
- .menu {
157
- display: inline-grid;
158
- }
159
-
160
- .nav {
161
- position: absolute;
162
- inset-block-start: 100%;
163
- inset-inline: 0;
164
- flex-direction: column;
165
- gap: var(--gw-space-0-5);
166
- align-items: stretch;
167
- padding: var(--gw-space-2) var(--gw-space-3);
168
- visibility: hidden;
169
- background: var(--gw-color-surface-default);
170
- border-block-end: max(var(--gw-border-width), 1px) solid
171
- var(--gw-color-border-quiet);
172
- box-shadow: var(--gw-shadow-m);
173
- opacity: 0;
174
- translate: 0 -0.5rem;
175
- transition:
176
- opacity var(--gw-motion-fast) var(--gw-motion-easing),
177
- translate var(--gw-motion-normal) var(--gw-motion-easing),
178
- visibility var(--gw-motion-normal) allow-discrete;
179
- }
180
-
181
- :host([menu-open]) .nav {
182
- visibility: visible;
183
- opacity: 1;
184
- translate: 0 0;
185
- }
179
+ /* Below menu-below, which sets collapsed, the nav folds into a menu */
180
+ :host([collapsed]) .menu {
181
+ display: inline-grid;
182
+ }
183
+
184
+ :host([collapsed]) .nav {
185
+ position: absolute;
186
+ inset-block-start: 100%;
187
+ inset-inline: 0;
188
+ flex-direction: column;
189
+ gap: var(--gw-space-0-5);
190
+ align-items: stretch;
191
+ padding: var(--gw-space-2)
192
+ max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2);
193
+ visibility: hidden;
194
+ background: var(--gw-color-surface-default);
195
+ border-block-end: max(var(--gw-border-width), 1px) solid
196
+ var(--gw-color-border-quiet);
197
+ box-shadow: var(--gw-shadow-m);
198
+ opacity: 0;
199
+ translate: 0 -0.5rem;
200
+ transition:
201
+ opacity var(--gw-motion-fast) var(--gw-motion-easing),
202
+ translate var(--gw-motion-normal) var(--gw-motion-easing),
203
+ visibility var(--gw-motion-normal) allow-discrete;
204
+ }
205
+
206
+ :host([collapsed][menu-open]) .nav {
207
+ visibility: visible;
208
+ opacity: 1;
209
+ translate: 0 0;
186
210
  }
187
211
  `,
188
212
  ];
@@ -192,6 +216,18 @@ export class GwAppBar extends GwElement {
192
216
  declare floating: boolean;
193
217
  declare menuOpen: boolean;
194
218
  declare menuLabel: string;
219
+ /** The viewport width in pixels below which the nav moves into a menu. */
220
+ declare menuBelow: number;
221
+ /** Whether the nav is in the menu. Set by the bar from `menuBelow`. */
222
+ declare collapsed: boolean;
223
+
224
+ #narrow: MediaQueryList | undefined;
225
+
226
+ readonly #onNarrow = () => {
227
+ this.collapsed = this.#narrow?.matches === true;
228
+
229
+ if (!this.collapsed) this.toggleMenu(false);
230
+ };
195
231
 
196
232
  readonly #menuIcon = createIcon(menuIcon);
197
233
 
@@ -214,10 +250,23 @@ export class GwAppBar extends GwElement {
214
250
  this.floating = false;
215
251
  this.menuOpen = false;
216
252
  this.menuLabel = "Menu";
253
+ this.menuBelow = 600;
254
+ this.collapsed = false;
255
+ }
256
+
257
+ /** Follows the viewport across `menuBelow`, setting `collapsed`. */
258
+ #watchWidth() {
259
+ this.#narrow?.removeEventListener("change", this.#onNarrow);
260
+ this.#narrow = window.matchMedia(
261
+ `(max-width: ${(Number(this.menuBelow) || 600) - 0.02}px)`,
262
+ );
263
+ this.#narrow.addEventListener("change", this.#onNarrow);
264
+ this.#onNarrow();
217
265
  }
218
266
 
219
267
  override connectedCallback() {
220
268
  super.connectedCallback();
269
+ this.#watchWidth();
221
270
  document.addEventListener("keydown", this.#onKeydown);
222
271
  document.addEventListener("pointerdown", this.#onOutside, true);
223
272
 
@@ -232,6 +281,8 @@ export class GwAppBar extends GwElement {
232
281
 
233
282
  override disconnectedCallback() {
234
283
  this.#unwatch();
284
+ this.#narrow?.removeEventListener("change", this.#onNarrow);
285
+ this.#narrow = undefined;
235
286
  document.removeEventListener("keydown", this.#onKeydown);
236
287
  document.removeEventListener("pointerdown", this.#onOutside, true);
237
288
  super.disconnectedCallback();
@@ -317,10 +368,19 @@ export class GwAppBar extends GwElement {
317
368
  h(
318
369
  "div",
319
370
  { class: "bar", part: "bar" },
320
- h("div", { class: "brand" }, h("slot", { name: "brand" })),
371
+ // The menu button leads, so a revealed brand opens beside it
321
372
  this.#menu,
373
+ h(
374
+ "div",
375
+ { class: "brand", part: "brand" },
376
+ h("slot", { name: "brand" }),
377
+ ),
322
378
  this.#nav,
323
- h("div", { class: "actions" }, h("slot", { name: "actions" })),
379
+ h(
380
+ "div",
381
+ { class: "actions", part: "actions" },
382
+ h("slot", { name: "actions" }),
383
+ ),
324
384
  ),
325
385
  );
326
386
  }
@@ -331,6 +391,10 @@ export class GwAppBar extends GwElement {
331
391
  setIconPath(this.#menuIcon, this.menuOpen ? closeIcon : menuIcon);
332
392
 
333
393
  if (changed.has("revealAfter") && this.isConnected) this.#watch();
394
+
395
+ if (changed.has("menuBelow") && this.#narrow !== undefined) {
396
+ this.#watchWidth();
397
+ }
334
398
  }
335
399
  }
336
400
 
package/src/gw-badge.ts CHANGED
@@ -27,6 +27,10 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
27
27
  * @slot - The value.
28
28
  *
29
29
  * @csspart badge - The badge.
30
+ * @csspart label - The label above the value.
31
+ * @csspart value - The value.
32
+ *
33
+ * @cssprop --gw-tone - The icon's color, for a tone of your own. `tone` sets it.
30
34
  */
31
35
  export class GwBadge extends GwElement {
32
36
  static override properties = {
@@ -113,7 +117,7 @@ export class GwBadge extends GwElement {
113
117
 
114
118
  readonly #iconSkeleton = h("gw-skeleton-icon");
115
119
 
116
- readonly #label = h("span", { class: "label" });
120
+ readonly #label = h("span", { class: "label", part: "label" });
117
121
 
118
122
  readonly #skeleton = h("gw-skeleton-text", {
119
123
  style: "--gw-skeleton-width: 3rem",
@@ -138,7 +142,12 @@ export class GwBadge extends GwElement {
138
142
  "span",
139
143
  { class: "info" },
140
144
  this.#label,
141
- h("span", { class: "content" }, this.#skeleton, this.#value),
145
+ h(
146
+ "span",
147
+ { class: "content", part: "value" },
148
+ this.#skeleton,
149
+ this.#value,
150
+ ),
142
151
  ),
143
152
  );
144
153
  root.append(this.#badge);
package/src/gw-banner.ts CHANGED
@@ -27,6 +27,10 @@ import { base, type Tone } from "./styles.js";
27
27
  * @slot actions - One button, opening what it's about.
28
28
  *
29
29
  * @csspart bar - The bar.
30
+ *
31
+ * @cssprop --gw-tone - The text and icon color, for a tone of your own. `tone` sets it.
32
+ * @cssprop --gw-tone-fill - The background. `tone` sets it.
33
+ * @cssprop --gw-tone-border - The border. `tone` sets it.
30
34
  */
31
35
  export class GwBanner extends GwElement {
32
36
  static override properties = {
package/src/gw-card.ts CHANGED
@@ -28,6 +28,9 @@ import { base } from "./styles.js";
28
28
  * @slot actions - Buttons beside the heading.
29
29
  *
30
30
  * @csspart card - The card's surface.
31
+ * @csspart header - The row with the heading and actions.
32
+ * @csspart heading - The heading.
33
+ * @csspart body - The content.
31
34
  */
32
35
  export class GwCard extends GwElement {
33
36
  static override properties = {
@@ -107,14 +110,14 @@ export class GwCard extends GwElement {
107
110
 
108
111
  readonly #header = h(
109
112
  "header",
110
- {},
111
- h("h2", {}, this.#title, this.#titleSkeleton),
113
+ { part: "header" },
114
+ h("h2", { part: "heading" }, this.#title, this.#titleSkeleton),
112
115
  h("slot", { name: "actions" }),
113
116
  );
114
117
 
115
118
  readonly #skeleton = h("gw-skeleton-lines");
116
119
 
117
- readonly #body = h("div", { class: "body" }, h("slot"));
120
+ readonly #body = h("div", { class: "body", part: "body" }, h("slot"));
118
121
 
119
122
  #card: HTMLElement = h("section", { class: "card", part: "card" });
120
123
 
package/src/gw-chip.ts CHANGED
@@ -24,6 +24,9 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
24
24
  * @slot icon - An icon before the text, in place of `icon`.
25
25
  *
26
26
  * @csspart chip - The chip.
27
+ *
28
+ * @cssprop --gw-tone-fill - The tint, for a tone of your own. `tone` sets it.
29
+ * @cssprop --gw-tone-border - The border. `tone` sets it.
27
30
  */
28
31
  export class GwChip extends GwElement {
29
32
  static override properties = {
@@ -28,6 +28,10 @@ const sheetBelow = 600;
28
28
  * @attr {string} shortcut - The key that opens it with Cmd or Ctrl, such as `mod+k`, or `none`. Defaults to `mod+k`.
29
29
  * @attr {boolean} open - Whether it shows.
30
30
  *
31
+ * @csspart dialog - The menu's surface.
32
+ * @csspart list - The `gw-option-list` of commands.
33
+ * @csspart footer - The row of key hints.
34
+ *
31
35
  * @fires {CustomEvent<{ value: string }>} pick - When a command is picked, with its `value` in `detail`.
32
36
  */
33
37
  export class GwCommandMenu extends GwElement {
@@ -150,15 +154,15 @@ export class GwCommandMenu extends GwElement {
150
154
  declare shortcut: string;
151
155
  declare open: boolean;
152
156
 
153
- readonly #list = h("gw-option-list", { search: true });
157
+ readonly #list = h("gw-option-list", { search: true, part: "list" });
154
158
 
155
159
  readonly #dialog = h(
156
160
  "dialog",
157
- {},
161
+ { part: "dialog" },
158
162
  this.#list,
159
163
  h(
160
164
  "footer",
161
- { "aria-hidden": "true" },
165
+ { part: "footer", "aria-hidden": "true" },
162
166
  h("span", {}, h("kbd", {}, "↑"), " ", h("kbd", {}, "↓"), " to move"),
163
167
  h("span", {}, h("kbd", {}, "Enter"), " to pick"),
164
168
  h("span", {}, h("kbd", {}, "Esc"), " to close"),
package/src/gw-dialog.ts CHANGED
@@ -28,6 +28,12 @@ const closeIcon =
28
28
  * @slot - The content.
29
29
  * @slot actions - Buttons; one with a `value` closes it with that value.
30
30
  *
31
+ * @csspart dialog - The dialog's surface.
32
+ * @csspart header - The row with the heading and close button.
33
+ * @csspart heading - The heading.
34
+ * @csspart body - The content.
35
+ * @csspart footer - The actions row.
36
+ *
31
37
  * @fires {Event} close - When it closes; `returnValue` holds the button's value.
32
38
  */
33
39
  export class GwDialog extends GwElement {
@@ -139,9 +145,13 @@ export class GwDialog extends GwElement {
139
145
  this.open = false;
140
146
  }
141
147
 
142
- readonly #heading = h("h2", { id: "heading" });
148
+ readonly #heading = h("h2", { id: "heading", part: "heading" });
143
149
 
144
- readonly #footer = h("footer", {}, h("slot", { name: "actions" }));
150
+ readonly #footer = h(
151
+ "footer",
152
+ { part: "footer" },
153
+ h("slot", { name: "actions" }),
154
+ );
145
155
 
146
156
  readonly #closeButton = h(
147
157
  "gw-button",
@@ -158,9 +168,9 @@ export class GwDialog extends GwElement {
158
168
 
159
169
  readonly #dialog = h(
160
170
  "dialog",
161
- { "aria-labelledby": "heading" },
162
- h("header", {}, this.#heading, this.#closeButton),
163
- h("div", { class: "body" }, h("slot")),
171
+ { "aria-labelledby": "heading", part: "dialog" },
172
+ h("header", { part: "header" }, this.#heading, this.#closeButton),
173
+ h("div", { class: "body", part: "body" }, h("slot")),
164
174
  this.#footer,
165
175
  );
166
176
 
package/src/gw-dot.ts CHANGED
@@ -17,6 +17,8 @@ import { base, type Tone } from "./styles.js";
17
17
  * @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - The color. Defaults to `neutral`.
18
18
  * @attr {string} label - What it means, for assistive technology.
19
19
  * @attr {boolean} pulse - Pulses, for something live.
20
+ *
21
+ * @cssprop --gw-tone - The color, for a tone of your own. `tone` sets it.
20
22
  */
21
23
  export class GwDot extends GwElement {
22
24
  static override properties = {
@@ -25,6 +25,8 @@ import { base } from "./styles.js";
25
25
  *
26
26
  * @csspart details - The native `<details>`.
27
27
  * @csspart summary - The summary row.
28
+ * @csspart heading - The heading.
29
+ * @csspart description - The line under the heading.
28
30
  * @csspart content - The content's wrapper.
29
31
  *
30
32
  * @fires {Event} toggle - When it opens or closes.
@@ -169,9 +171,12 @@ export class GwExpansion extends GwElement {
169
171
  declare description: string;
170
172
  declare open: boolean;
171
173
 
172
- readonly #heading = h("span", { class: "heading" });
174
+ readonly #heading = h("span", { class: "heading", part: "heading" });
173
175
 
174
- readonly #description = h("span", { class: "description" });
176
+ readonly #description = h("span", {
177
+ class: "description",
178
+ part: "description",
179
+ });
175
180
 
176
181
  readonly #details = h(
177
182
  "details",
package/src/gw-facts.ts CHANGED
@@ -28,6 +28,10 @@ const skeletonWidths = ["60%", "40%", "75%", "50%", "65%"];
28
28
  * @attr {Fact[]} items - The pairs, as JSON.
29
29
  * @attr {boolean} loading - Shows skeleton pairs, keeping any labels already known.
30
30
  * @attr {number} rows - How many skeleton pairs while loading. Defaults to 4.
31
+ *
32
+ * @csspart list - The description list.
33
+ * @csspart label - Each label.
34
+ * @csspart value - Each value.
31
35
  */
32
36
  export class GwFacts extends GwElement {
33
37
  static override properties = {
@@ -69,7 +73,7 @@ export class GwFacts extends GwElement {
69
73
  declare loading: boolean;
70
74
  declare rows: number;
71
75
 
72
- readonly #list = h("dl");
76
+ readonly #list = h("dl", { part: "list" });
73
77
 
74
78
  #rendered = false;
75
79
 
@@ -97,13 +101,13 @@ export class GwFacts extends GwElement {
97
101
  ...Array.from({ length: count }, (_, index) => [
98
102
  h(
99
103
  "dt",
100
- {},
104
+ { part: "label" },
101
105
  items[index]?.label ??
102
106
  h("gw-skeleton-text", { style: "--gw-skeleton-width: 5rem" }),
103
107
  ),
104
108
  h(
105
109
  "dd",
106
- {},
110
+ { part: "value" },
107
111
  h("gw-skeleton-text", {
108
112
  style: `--gw-skeleton-width: ${skeletonWidths[index % skeletonWidths.length]}`,
109
113
  }),
@@ -114,8 +118,12 @@ export class GwFacts extends GwElement {
114
118
  this.#list.removeAttribute("aria-busy");
115
119
  this.#list.replaceChildren(
116
120
  ...items.flatMap((item) => [
117
- h("dt", {}, item.label),
118
- h("dd", {}, loaded ? h("span", {}, item.value) : item.value),
121
+ h("dt", { part: "label" }, item.label),
122
+ h(
123
+ "dd",
124
+ { part: "value" },
125
+ loaded ? h("span", {}, item.value) : item.value,
126
+ ),
119
127
  ]),
120
128
  );
121
129
  }
package/src/gw-gauge.ts CHANGED
@@ -76,6 +76,12 @@ const circle = (className: string) => {
76
76
  * @attr {boolean} inverted - Lower is worse, such as for a battery.
77
77
  * @attr {boolean} loading - Pulses the track and shows a skeleton for the reading.
78
78
  *
79
+ * @csspart dial - The dial, with the arc and the reading.
80
+ * @csspart value - The reading's number.
81
+ * @csspart unit - The unit after it.
82
+ * @csspart label - What it measures.
83
+ * @csspart caption - The line under the label.
84
+ *
79
85
  * @cssprop --gw-gauge-size - The dial's width. Defaults to `8.5rem`.
80
86
  * @cssprop --gw-gauge-color - The arc's color, overriding the thresholds.
81
87
  */
@@ -208,11 +214,11 @@ export class GwGauge extends GwElement {
208
214
 
209
215
  readonly #fill = circle("fill");
210
216
 
211
- readonly #dial = h("div", { class: "dial", role: "meter" });
217
+ readonly #dial = h("div", { class: "dial", part: "dial", role: "meter" });
212
218
 
213
- readonly #value = h("span", { class: "value" });
219
+ readonly #value = h("span", { class: "value", part: "value" });
214
220
 
215
- readonly #unit = h("span", { class: "unit" });
221
+ readonly #unit = h("span", { class: "unit", part: "unit" });
216
222
 
217
223
  readonly #shown = h("span", { class: "shown" }, this.#value, this.#unit);
218
224
 
@@ -220,9 +226,9 @@ export class GwGauge extends GwElement {
220
226
  style: "--gw-skeleton-width: 2em",
221
227
  });
222
228
 
223
- readonly #label = h("span", { class: "label" });
229
+ readonly #label = h("span", { class: "label", part: "label" });
224
230
 
225
- readonly #caption = h("span", { class: "caption" });
231
+ readonly #caption = h("span", { class: "caption", part: "caption" });
226
232
 
227
233
  readonly #captionSkeleton = h("gw-skeleton-text", {
228
234
  style: "--gw-skeleton-width: 5rem",
package/src/gw-meter.ts CHANGED
@@ -26,6 +26,11 @@ import { base, type Tone } from "./styles.js";
26
26
  * @attr {string} display - The value as shown, such as `42 GB`. Defaults to a percentage.
27
27
  * @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - A fixed color, overriding `high` and `critical`.
28
28
  * @attr {boolean} loading - Pulses the track and shows a skeleton for the value.
29
+ *
30
+ * @csspart top - The row with the label and value.
31
+ * @csspart value - The value as shown.
32
+ * @csspart track - The bar's track.
33
+ * @csspart fill - The filled part of the bar.
29
34
  */
30
35
  export class GwMeter extends GwElement {
31
36
  static override properties = {
@@ -93,7 +98,7 @@ export class GwMeter extends GwElement {
93
98
  declare tone: Tone | undefined;
94
99
  declare loading: boolean;
95
100
 
96
- readonly #top = h("div", { class: "top" });
101
+ readonly #top = h("div", { class: "top", part: "top" });
97
102
 
98
103
  readonly #label = h("span");
99
104
 
@@ -101,15 +106,23 @@ export class GwMeter extends GwElement {
101
106
  style: "--gw-skeleton-width: 6rem",
102
107
  });
103
108
 
104
- readonly #value = h("span", { class: "value", "aria-hidden": "true" });
109
+ readonly #value = h("span", {
110
+ class: "value",
111
+ part: "value",
112
+ "aria-hidden": "true",
113
+ });
105
114
 
106
115
  readonly #valueSkeleton = h("gw-skeleton-text", {
107
116
  style: "--gw-skeleton-width: 2.5rem",
108
117
  });
109
118
 
110
- readonly #fill = h("div", { class: "fill" });
119
+ readonly #fill = h("div", { class: "fill", part: "fill" });
111
120
 
112
- readonly #track = h("div", { class: "track", role: "meter" }, this.#fill);
121
+ readonly #track = h(
122
+ "div",
123
+ { class: "track", part: "track", role: "meter" },
124
+ this.#fill,
125
+ );
113
126
 
114
127
  readonly #bar = h("gw-skeleton", { class: "bar" });
115
128
 
@@ -29,6 +29,10 @@ const emptyIcon =
29
29
  *
30
30
  * @slot - The `gw-notification` cards.
31
31
  *
32
+ * @csspart header - The row with the heading and Clear all.
33
+ * @csspart heading - The heading.
34
+ * @csspart list - The list of cards.
35
+ *
32
36
  * @fires {Event} clear - When Clear all is pressed.
33
37
  */
34
38
  export class GwNotificationCenter extends GwElement {
@@ -75,7 +79,7 @@ export class GwNotificationCenter extends GwElement {
75
79
 
76
80
  declare heading: string;
77
81
 
78
- readonly #heading = h("h2");
82
+ readonly #heading = h("h2", { part: "heading" });
79
83
 
80
84
  readonly #clear = h(
81
85
  "gw-button",
@@ -83,7 +87,11 @@ export class GwNotificationCenter extends GwElement {
83
87
  "Clear all",
84
88
  );
85
89
 
86
- readonly #list = h("div", { class: "list", role: "list" }, h("slot"));
90
+ readonly #list = h(
91
+ "div",
92
+ { class: "list", part: "list", role: "list" },
93
+ h("slot"),
94
+ );
87
95
 
88
96
  readonly #empty = h(
89
97
  "gw-empty-state",
@@ -138,7 +146,10 @@ export class GwNotificationCenter extends GwElement {
138
146
  this.#clear.addEventListener("click", () => {
139
147
  this.clear();
140
148
  });
141
- root.append(h("header", {}, this.#heading, this.#clear), this.#body);
149
+ root.append(
150
+ h("header", { part: "header" }, this.#heading, this.#clear),
151
+ this.#body,
152
+ );
142
153
  }
143
154
 
144
155
  protected override update() {
@@ -34,6 +34,8 @@ const closeIcon =
34
34
  *
35
35
  * @csspart card - The card.
36
36
  *
37
+ * @cssprop --gw-tone - The icon's color, for a tone of your own. `tone` sets it.
38
+ *
37
39
  * @fires {Event} dismiss - When the close button is pressed. Cancel it to keep the card.
38
40
  */
39
41
  export class GwNotification extends GwElement {