@timmo001/groundwork-ui 0.4.1 → 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 +1027 -18
- package/package.json +2 -2
- package/src/events.ts +3 -0
- 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-button.ts +91 -12
- 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 +17 -5
- 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-gallery.ts +493 -0
- package/src/gw-theme-picker.ts +187 -34
- package/src/gw-toast.ts +3 -1
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmo001/groundwork-ui",
|
|
3
|
-
"version": "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",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"README.md",
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
|
-
"customElements": "
|
|
17
|
+
"customElements": "custom-elements.json",
|
|
18
18
|
"sideEffects": true,
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public",
|
package/src/events.ts
CHANGED
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
|
|
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
|
|
30
|
-
* opened from a button, which closes on Esc, a
|
|
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:
|
|
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-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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-button.ts
CHANGED
|
@@ -12,6 +12,11 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
12
12
|
* theme and mode around it. With `href` it renders a link that looks the
|
|
13
13
|
* same. Put an icon in the `icon` slot.
|
|
14
14
|
*
|
|
15
|
+
* For a link the page's HTML has to carry, such as one with `rel="me"`, a
|
|
16
|
+
* `target` or `aria-current="page"`, put a plain `<a>` inside instead of
|
|
17
|
+
* `href`. The button styles it, a click anywhere on the pill follows it, and
|
|
18
|
+
* `aria-current` on it gets a current style.
|
|
19
|
+
*
|
|
15
20
|
* `variant` picks the color and `appearance` how it's applied, from most to
|
|
16
21
|
* least important: `accent` (a solid fill), `filled` (a soft tint),
|
|
17
22
|
* `outlined` (a stroke) or `plain` (text only). Every button is a pill.
|
|
@@ -35,10 +40,10 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
35
40
|
* @attr {string} icon - SVG path data for an icon before the text.
|
|
36
41
|
* @attr {"show"|"hide"|"auto"} labels - Whether the text shows beside an icon: always, never, or not on narrow screens. Defaults to `show`.
|
|
37
42
|
*
|
|
38
|
-
* @slot - The text
|
|
43
|
+
* @slot - The text, or an `<a>` holding it, in place of `href`.
|
|
39
44
|
* @slot icon - An icon before the text, in place of `icon`.
|
|
40
45
|
*
|
|
41
|
-
* @csspart button - The native button or link.
|
|
46
|
+
* @csspart button - The native button or link, or the pill around a slotted link.
|
|
42
47
|
*
|
|
43
48
|
* @cssprop --gw-button-scale - Scales every dimension, around 1 for medium.
|
|
44
49
|
* @cssprop --gw-button-tone - The color, set by `variant`.
|
|
@@ -204,6 +209,27 @@ export class GwButton extends GwElement {
|
|
|
204
209
|
border-color: var(--gw-button-tone);
|
|
205
210
|
}
|
|
206
211
|
|
|
212
|
+
/* A slotted link carries the text; the pill draws its focus ring */
|
|
213
|
+
::slotted(a) {
|
|
214
|
+
color: inherit;
|
|
215
|
+
text-decoration: none;
|
|
216
|
+
outline: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.button.focused {
|
|
220
|
+
outline: var(--gw-focus-width) solid var(--gw-color-focus);
|
|
221
|
+
outline-offset: var(--gw-focus-offset);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
:host([appearance="plain"]) .button.current,
|
|
225
|
+
:host([appearance="outlined"]) .button.current {
|
|
226
|
+
background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
:host([appearance="filled"]) .button.current {
|
|
230
|
+
background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent);
|
|
231
|
+
}
|
|
232
|
+
|
|
207
233
|
.button:disabled,
|
|
208
234
|
.button[aria-disabled="true"] {
|
|
209
235
|
color: var(--gw-color-text-disabled);
|
|
@@ -350,10 +376,18 @@ export class GwButton extends GwElement {
|
|
|
350
376
|
|
|
351
377
|
readonly #over = h("span", { class: "ring over" });
|
|
352
378
|
|
|
353
|
-
#control: HTMLButtonElement | HTMLAnchorElement = h(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
379
|
+
#control: HTMLButtonElement | HTMLAnchorElement | HTMLSpanElement = h(
|
|
380
|
+
"button",
|
|
381
|
+
{
|
|
382
|
+
class: "button",
|
|
383
|
+
part: "button",
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
/** A plain `<a>` the page put inside, which the button wraps instead of `href`. */
|
|
388
|
+
#link(): HTMLAnchorElement | null {
|
|
389
|
+
return this.querySelector(":scope > a:not([slot])");
|
|
390
|
+
}
|
|
357
391
|
|
|
358
392
|
constructor() {
|
|
359
393
|
super();
|
|
@@ -373,12 +407,43 @@ export class GwButton extends GwElement {
|
|
|
373
407
|
if (this.loading) {
|
|
374
408
|
event.preventDefault();
|
|
375
409
|
event.stopImmediatePropagation();
|
|
410
|
+
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// A click on the pill around a slotted link follows the link
|
|
415
|
+
const link = this.#link();
|
|
416
|
+
|
|
417
|
+
if (link !== null && !event.composedPath().includes(link)) {
|
|
418
|
+
link.click();
|
|
376
419
|
}
|
|
377
420
|
});
|
|
378
421
|
|
|
422
|
+
const focus = () => {
|
|
423
|
+
this.#control.classList.toggle(
|
|
424
|
+
"focused",
|
|
425
|
+
this.#link()?.matches(":focus-visible") === true,
|
|
426
|
+
);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
this.addEventListener("focusin", focus);
|
|
430
|
+
this.addEventListener("focusout", focus);
|
|
431
|
+
|
|
379
432
|
this.#iconSlot.addEventListener("slotchange", () => {
|
|
380
433
|
this.requestUpdate("icon");
|
|
381
434
|
});
|
|
435
|
+
|
|
436
|
+
this.#text.addEventListener("slotchange", () => {
|
|
437
|
+
this.requestUpdate("href");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
new MutationObserver(() => {
|
|
441
|
+
this.requestUpdate("href");
|
|
442
|
+
}).observe(this, {
|
|
443
|
+
subtree: true,
|
|
444
|
+
attributes: true,
|
|
445
|
+
attributeFilter: ["aria-current"],
|
|
446
|
+
});
|
|
382
447
|
}
|
|
383
448
|
|
|
384
449
|
protected override create(root: ShadowRoot) {
|
|
@@ -387,13 +452,16 @@ export class GwButton extends GwElement {
|
|
|
387
452
|
}
|
|
388
453
|
|
|
389
454
|
protected override update(changed: ReadonlySet<string>) {
|
|
455
|
+
const link = this.#link();
|
|
456
|
+
|
|
390
457
|
if (changed.has("href")) {
|
|
391
|
-
const wanted = this.href ? "A" : "BUTTON";
|
|
458
|
+
const wanted = link ? "SPAN" : this.href ? "A" : "BUTTON";
|
|
392
459
|
|
|
393
460
|
if (this.#control.tagName !== wanted) {
|
|
394
|
-
const next = this.href
|
|
395
|
-
|
|
396
|
-
:
|
|
461
|
+
const next = h(link ? "span" : this.href ? "a" : "button", {
|
|
462
|
+
class: "button",
|
|
463
|
+
part: "button",
|
|
464
|
+
});
|
|
397
465
|
|
|
398
466
|
next.append(...this.#control.childNodes);
|
|
399
467
|
this.#control.replaceWith(next);
|
|
@@ -407,6 +475,11 @@ export class GwButton extends GwElement {
|
|
|
407
475
|
this.icon !== "" || this.querySelector("[slot='icon']") !== null;
|
|
408
476
|
|
|
409
477
|
control.classList.toggle("has-icon", hasIcon);
|
|
478
|
+
control.classList.toggle(
|
|
479
|
+
"current",
|
|
480
|
+
link !== null &&
|
|
481
|
+
(link.getAttribute("aria-current") ?? "false") !== "false",
|
|
482
|
+
);
|
|
410
483
|
setIconPath(this.#icon, this.icon);
|
|
411
484
|
this.#icon.style.display = this.icon ? "" : "none";
|
|
412
485
|
setAttribute(
|
|
@@ -416,13 +489,19 @@ export class GwButton extends GwElement {
|
|
|
416
489
|
? this.textContent.trim() || null
|
|
417
490
|
: null,
|
|
418
491
|
);
|
|
419
|
-
setAttribute(control, "aria-label", this.label || null);
|
|
420
492
|
setAttribute(control, "aria-busy", this.loading ? "true" : null);
|
|
421
493
|
|
|
494
|
+
if (control instanceof HTMLSpanElement) {
|
|
495
|
+
// The slotted link is the control, so it carries the name
|
|
496
|
+
setAttribute(control, "aria-label", null);
|
|
497
|
+
} else {
|
|
498
|
+
setAttribute(control, "aria-label", this.label || null);
|
|
499
|
+
}
|
|
500
|
+
|
|
422
501
|
if (control instanceof HTMLAnchorElement) {
|
|
423
502
|
setAttribute(control, "href", this.disabled ? null : this.href);
|
|
424
503
|
setAttribute(control, "aria-disabled", this.disabled ? "true" : null);
|
|
425
|
-
} else {
|
|
504
|
+
} else if (control instanceof HTMLButtonElement) {
|
|
426
505
|
control.type = this.type;
|
|
427
506
|
control.disabled = this.disabled;
|
|
428
507
|
}
|
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 = {
|
package/src/gw-command-menu.ts
CHANGED
|
@@ -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(
|
|
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 = {
|
package/src/gw-expansion.ts
CHANGED
|
@@ -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", {
|
|
176
|
+
readonly #description = h("span", {
|
|
177
|
+
class: "description",
|
|
178
|
+
part: "description",
|
|
179
|
+
});
|
|
175
180
|
|
|
176
181
|
readonly #details = h(
|
|
177
182
|
"details",
|