@timmo001/groundwork-ui 0.5.0 → 0.7.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 +641 -27
- package/package.json +1 -1
- package/src/fields.ts +32 -6
- package/src/gw-alert.ts +4 -0
- package/src/gw-app-bar.ts +106 -42
- package/src/gw-badge.ts +40 -20
- package/src/gw-banner.ts +4 -0
- package/src/gw-button.ts +8 -24
- package/src/gw-card.ts +102 -6
- package/src/gw-chip.ts +29 -7
- 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-input.ts +7 -0
- package/src/gw-menu-button.ts +17 -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 +21 -8
- package/src/gw-select.ts +7 -0
- package/src/gw-setting-row.ts +21 -5
- 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/styles.ts +25 -0
package/src/gw-card.ts
CHANGED
|
@@ -15,6 +15,12 @@ import { base } from "./styles.js";
|
|
|
15
15
|
* stand in for the content, and for the heading until it's known. The card
|
|
16
16
|
* glides to its new height and the content fades in once it's loaded.
|
|
17
17
|
*
|
|
18
|
+
* For a link the page's HTML has to carry, such as one with a `target` or
|
|
19
|
+
* `rel`, put an `<a>` in the `heading` slot instead of `href`. A click
|
|
20
|
+
* anywhere on the card that isn't on another control follows it, the card
|
|
21
|
+
* shows the link's hover and focus, and the link stays a real one in the
|
|
22
|
+
* page, so the rest of the card's content stays plain text.
|
|
23
|
+
*
|
|
18
24
|
* @element gw-card
|
|
19
25
|
* @summary A surface for one group of content, or a link to more.
|
|
20
26
|
*
|
|
@@ -25,9 +31,13 @@ import { base } from "./styles.js";
|
|
|
25
31
|
* @attr {number} lines - How many skeleton lines while loading. Defaults to 3.
|
|
26
32
|
*
|
|
27
33
|
* @slot - The content.
|
|
34
|
+
* @slot heading - The heading, in place of `heading`. An `<a>` here makes the whole card follow it.
|
|
28
35
|
* @slot actions - Buttons beside the heading.
|
|
29
36
|
*
|
|
30
37
|
* @csspart card - The card's surface.
|
|
38
|
+
* @csspart header - The row with the heading and actions.
|
|
39
|
+
* @csspart heading - The heading.
|
|
40
|
+
* @csspart body - The content.
|
|
31
41
|
*/
|
|
32
42
|
export class GwCard extends GwElement {
|
|
33
43
|
static override properties = {
|
|
@@ -68,10 +78,32 @@ export class GwCard extends GwElement {
|
|
|
68
78
|
transition: border-color var(--gw-motion-fast) var(--gw-motion-easing);
|
|
69
79
|
}
|
|
70
80
|
|
|
71
|
-
a.card:hover
|
|
81
|
+
a.card:hover,
|
|
82
|
+
.card.linked:hover {
|
|
72
83
|
border-color: var(--gw-color-border-accent);
|
|
73
84
|
}
|
|
74
85
|
|
|
86
|
+
/* A card following a link in its heading slot */
|
|
87
|
+
.card.linked {
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
transition: border-color var(--gw-motion-fast) var(--gw-motion-easing);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.card.focused {
|
|
93
|
+
outline: var(--gw-focus-width) solid var(--gw-color-focus);
|
|
94
|
+
outline-offset: var(--gw-focus-offset);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
::slotted([slot="heading"]) {
|
|
98
|
+
font: inherit;
|
|
99
|
+
color: inherit;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
::slotted(a[slot="heading"]) {
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
outline: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
75
107
|
header {
|
|
76
108
|
display: flex;
|
|
77
109
|
gap: var(--gw-space-2);
|
|
@@ -105,16 +137,24 @@ export class GwCard extends GwElement {
|
|
|
105
137
|
|
|
106
138
|
readonly #titleSkeleton = h("gw-skeleton-text");
|
|
107
139
|
|
|
140
|
+
readonly #headingSlot = h("slot", { name: "heading" });
|
|
141
|
+
|
|
108
142
|
readonly #header = h(
|
|
109
143
|
"header",
|
|
110
|
-
{},
|
|
111
|
-
h(
|
|
144
|
+
{ part: "header" },
|
|
145
|
+
h(
|
|
146
|
+
"h2",
|
|
147
|
+
{ part: "heading" },
|
|
148
|
+
this.#title,
|
|
149
|
+
this.#headingSlot,
|
|
150
|
+
this.#titleSkeleton,
|
|
151
|
+
),
|
|
112
152
|
h("slot", { name: "actions" }),
|
|
113
153
|
);
|
|
114
154
|
|
|
115
155
|
readonly #skeleton = h("gw-skeleton-lines");
|
|
116
156
|
|
|
117
|
-
readonly #body = h("div", { class: "body" }, h("slot"));
|
|
157
|
+
readonly #body = h("div", { class: "body", part: "body" }, h("slot"));
|
|
118
158
|
|
|
119
159
|
#card: HTMLElement = h("section", { class: "card", part: "card" });
|
|
120
160
|
|
|
@@ -129,6 +169,57 @@ export class GwCard extends GwElement {
|
|
|
129
169
|
this.flush = false;
|
|
130
170
|
this.loading = false;
|
|
131
171
|
this.lines = 3;
|
|
172
|
+
|
|
173
|
+
// A click on the card, but not on another control in it, follows the
|
|
174
|
+
// link in the heading slot
|
|
175
|
+
this.addEventListener("click", (event) => {
|
|
176
|
+
const link = this.#link();
|
|
177
|
+
|
|
178
|
+
if (link === null || this.loading) return;
|
|
179
|
+
|
|
180
|
+
const path = event.composedPath();
|
|
181
|
+
const own = path.slice(0, path.indexOf(this));
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
own.some(
|
|
185
|
+
(node) =>
|
|
186
|
+
node instanceof HTMLElement &&
|
|
187
|
+
node.matches(
|
|
188
|
+
"a, button, input, select, textarea, label, [tabindex], gw-button",
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Selecting text in the card shouldn't open the link
|
|
196
|
+
if (getSelection()?.toString()) return;
|
|
197
|
+
|
|
198
|
+
link.click();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const focus = () => {
|
|
202
|
+
this.#card.classList.toggle(
|
|
203
|
+
"focused",
|
|
204
|
+
this.#link()?.matches(":focus-visible") === true,
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.addEventListener("focusin", focus);
|
|
209
|
+
this.addEventListener("focusout", focus);
|
|
210
|
+
|
|
211
|
+
this.#headingSlot.addEventListener("slotchange", () => {
|
|
212
|
+
this.requestUpdate("heading");
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The `<a>` in the heading slot, or holding it, when there is one. */
|
|
217
|
+
#link(): HTMLAnchorElement | null {
|
|
218
|
+
const heading = this.querySelector(":scope > [slot='heading']");
|
|
219
|
+
|
|
220
|
+
if (heading instanceof HTMLAnchorElement) return heading;
|
|
221
|
+
|
|
222
|
+
return heading?.querySelector("a") ?? null;
|
|
132
223
|
}
|
|
133
224
|
|
|
134
225
|
protected override create(root: ShadowRoot) {
|
|
@@ -160,9 +251,14 @@ export class GwCard extends GwElement {
|
|
|
160
251
|
if (this.#card instanceof HTMLAnchorElement) this.#card.href = this.href;
|
|
161
252
|
|
|
162
253
|
setAttribute(this.#card, "aria-busy", this.loading ? "true" : null);
|
|
254
|
+
|
|
255
|
+
const slotted = this.querySelector(":scope > [slot='heading']") !== null;
|
|
256
|
+
|
|
257
|
+
this.#card.classList.toggle("linked", this.#link() !== null && !this.href);
|
|
163
258
|
this.#title.textContent = this.heading;
|
|
164
|
-
this.#titleSkeleton.hidden =
|
|
165
|
-
|
|
259
|
+
this.#titleSkeleton.hidden =
|
|
260
|
+
this.heading !== "" || slotted || !this.loading;
|
|
261
|
+
this.#header.hidden = !this.heading && !slotted && !this.loading;
|
|
166
262
|
this.#skeleton.lines = this.lines;
|
|
167
263
|
this.#skeleton.hidden = !this.loading;
|
|
168
264
|
this.#body.hidden = this.loading;
|
package/src/gw-chip.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
8
|
import "./gw-skeleton.js";
|
|
9
9
|
import { fadeIn } from "./motion.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
base,
|
|
12
|
+
controlSizes,
|
|
13
|
+
createIcon,
|
|
14
|
+
setIconPath,
|
|
15
|
+
type Tone,
|
|
16
|
+
} from "./styles.js";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* A short status or category, tinted by its `tone`. While `loading`, a
|
|
@@ -19,42 +25,56 @@ import { base, createIcon, setIconPath, type Tone } from "./styles.js";
|
|
|
19
25
|
* @attr {"neutral"|"accent"|"info"|"success"|"warning"|"danger"|"important"} tone - The tint. Defaults to `neutral`.
|
|
20
26
|
* @attr {string} icon - SVG path data for an icon before the text.
|
|
21
27
|
* @attr {boolean} loading - Shows a skeleton for the text.
|
|
28
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The size, around 32px tall at medium. Defaults to `medium`.
|
|
22
29
|
*
|
|
23
30
|
* @slot - The text.
|
|
24
31
|
* @slot icon - An icon before the text, in place of `icon`.
|
|
25
32
|
*
|
|
26
33
|
* @csspart chip - The chip.
|
|
34
|
+
*
|
|
35
|
+
* @cssprop --gw-tone-fill - The tint, for a tone of your own. `tone` sets it.
|
|
36
|
+
* @cssprop --gw-tone-border - The border. `tone` sets it.
|
|
37
|
+
* @cssprop --gw-chip-scale - Scales the chip, around 1 for medium.
|
|
38
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
27
39
|
*/
|
|
28
40
|
export class GwChip extends GwElement {
|
|
29
41
|
static override properties = {
|
|
30
42
|
tone: { reflect: true },
|
|
31
43
|
icon: {},
|
|
32
44
|
loading: { type: "boolean", reflect: true },
|
|
45
|
+
size: { reflect: true },
|
|
33
46
|
} as const;
|
|
34
47
|
|
|
35
48
|
static override styles = [
|
|
36
49
|
base,
|
|
50
|
+
controlSizes("chip"),
|
|
37
51
|
css`
|
|
38
52
|
:host {
|
|
39
53
|
display: inline-flex;
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
/* 32px, with 12px padding */
|
|
56
|
+
/* 32px at medium, with 12px padding */
|
|
43
57
|
.chip {
|
|
58
|
+
--gw-size: var(--gw-chip-scale, 1);
|
|
59
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
60
|
+
--gw-icon-size: calc(1rem * var(--gw-scale));
|
|
61
|
+
|
|
44
62
|
display: inline-flex;
|
|
45
|
-
gap: var(--gw-space-2);
|
|
63
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
46
64
|
align-items: center;
|
|
47
65
|
box-sizing: border-box;
|
|
48
|
-
height: 2rem;
|
|
49
|
-
padding: 0 var(--gw-space-3);
|
|
50
|
-
font-size:
|
|
66
|
+
height: calc(2rem * var(--gw-scale));
|
|
67
|
+
padding: 0 calc(var(--gw-space-3) * var(--gw-size));
|
|
68
|
+
font-size: max(
|
|
69
|
+
var(--gw-font-size-2xs),
|
|
70
|
+
calc(var(--gw-font-size-xs) * var(--gw-size))
|
|
71
|
+
);
|
|
51
72
|
font-weight: var(--gw-font-weight-strong);
|
|
52
73
|
color: var(--gw-color-text-strong);
|
|
53
74
|
background: var(--gw-tone-fill, var(--gw-color-fill-neutral-resting));
|
|
54
75
|
border: var(--gw-border-width) solid
|
|
55
76
|
var(--gw-tone-border, var(--gw-color-border-quiet));
|
|
56
77
|
border-radius: var(--gw-radius-chip);
|
|
57
|
-
--gw-icon-size: 1rem;
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
.content {
|
|
@@ -69,6 +89,7 @@ export class GwChip extends GwElement {
|
|
|
69
89
|
/** SVG path data for an icon before the text, instead of the `icon` slot. */
|
|
70
90
|
declare icon: string;
|
|
71
91
|
declare loading: boolean;
|
|
92
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
72
93
|
|
|
73
94
|
readonly #icon = createIcon("");
|
|
74
95
|
|
|
@@ -90,6 +111,7 @@ export class GwChip extends GwElement {
|
|
|
90
111
|
this.tone = "neutral";
|
|
91
112
|
this.icon = "";
|
|
92
113
|
this.loading = false;
|
|
114
|
+
this.size = "medium";
|
|
93
115
|
}
|
|
94
116
|
|
|
95
117
|
protected override create(root: ShadowRoot) {
|
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",
|
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(
|
|
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-input.ts
CHANGED
|
@@ -25,6 +25,10 @@ import { LightElement, fieldId } from "./fields.js";
|
|
|
25
25
|
* @attr {string} error - An error under the field, which marks it invalid.
|
|
26
26
|
* @attr {boolean} required - Requires a value.
|
|
27
27
|
* @attr {boolean} disabled - Disables the field.
|
|
28
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The field's size, 40px tall at medium. Defaults to `medium`.
|
|
29
|
+
*
|
|
30
|
+
* @cssprop --gw-field-scale - Scales the field, around 1 for medium.
|
|
31
|
+
* @cssprop --gw-control-scale - Scales its height with the theme's density. The theme sets it.
|
|
28
32
|
*/
|
|
29
33
|
export class GwInput extends LightElement {
|
|
30
34
|
static override properties = {
|
|
@@ -39,6 +43,7 @@ export class GwInput extends LightElement {
|
|
|
39
43
|
error: {},
|
|
40
44
|
required: { type: "boolean" },
|
|
41
45
|
disabled: { type: "boolean" },
|
|
46
|
+
size: { reflect: true },
|
|
42
47
|
} as const;
|
|
43
48
|
|
|
44
49
|
declare label: string;
|
|
@@ -53,6 +58,7 @@ export class GwInput extends LightElement {
|
|
|
53
58
|
declare error: string;
|
|
54
59
|
declare required: boolean;
|
|
55
60
|
declare disabled: boolean;
|
|
61
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
56
62
|
|
|
57
63
|
readonly #id = fieldId();
|
|
58
64
|
|
|
@@ -79,6 +85,7 @@ export class GwInput extends LightElement {
|
|
|
79
85
|
this.error = "";
|
|
80
86
|
this.required = false;
|
|
81
87
|
this.disabled = false;
|
|
88
|
+
this.size = "medium";
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
protected override create(root: HTMLElement) {
|
package/src/gw-menu-button.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { GwElement, css, h } from "./element.js";
|
|
8
|
-
import { base, createIcon, setIconPath } from "./styles.js";
|
|
8
|
+
import { base, controlSizes, createIcon, setIconPath } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
export interface MenuItem {
|
|
11
11
|
readonly value: string;
|
|
@@ -38,9 +38,13 @@ export interface MenuItem {
|
|
|
38
38
|
* @attr {string} empty - What the menu says with no items. Defaults to `Nothing here yet`.
|
|
39
39
|
* @attr {MenuItem[]} items - The items, as JSON.
|
|
40
40
|
* @attr {MenuItem[]} actions - Actions pinned under the items, as JSON.
|
|
41
|
+
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The trigger's size, 24 to 56px tall. Defaults to `medium`.
|
|
41
42
|
*
|
|
42
43
|
* @csspart trigger - The button that opens the menu.
|
|
43
44
|
*
|
|
45
|
+
* @cssprop --gw-menu-button-scale - Scales the trigger, around 1 for medium.
|
|
46
|
+
* @cssprop --gw-control-scale - Scales its height and icon with the theme's density. The theme sets it.
|
|
47
|
+
*
|
|
44
48
|
* @fires {CustomEvent<{ value: string }>} select - When an item or action is chosen, with its `value` in `detail`.
|
|
45
49
|
*/
|
|
46
50
|
export class GwMenuButton extends GwElement {
|
|
@@ -50,10 +54,12 @@ export class GwMenuButton extends GwElement {
|
|
|
50
54
|
empty: {},
|
|
51
55
|
items: { type: "json" },
|
|
52
56
|
actions: { type: "json" },
|
|
57
|
+
size: { reflect: true },
|
|
53
58
|
} as const;
|
|
54
59
|
|
|
55
60
|
static override styles = [
|
|
56
61
|
base,
|
|
62
|
+
controlSizes("menu-button"),
|
|
57
63
|
css`
|
|
58
64
|
:host {
|
|
59
65
|
position: relative;
|
|
@@ -62,14 +68,18 @@ export class GwMenuButton extends GwElement {
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
.trigger {
|
|
71
|
+
--gw-size: var(--gw-menu-button-scale, 1);
|
|
72
|
+
--gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1));
|
|
73
|
+
--gw-icon-size: calc(1.25rem * var(--gw-scale));
|
|
74
|
+
|
|
65
75
|
display: inline-flex;
|
|
66
|
-
gap: var(--gw-space-2);
|
|
76
|
+
gap: calc(var(--gw-space-2) * var(--gw-size));
|
|
67
77
|
align-items: center;
|
|
68
78
|
box-sizing: border-box;
|
|
69
|
-
height: 2.5rem;
|
|
70
|
-
padding: 0 var(--gw-space-4);
|
|
79
|
+
height: calc(2.5rem * var(--gw-scale));
|
|
80
|
+
padding: 0 calc(var(--gw-space-4) * var(--gw-size));
|
|
71
81
|
font: inherit;
|
|
72
|
-
font-size: var(--gw-font-size-s);
|
|
82
|
+
font-size: calc(var(--gw-font-size-s) * var(--gw-size));
|
|
73
83
|
font-weight: var(--gw-font-weight-strong);
|
|
74
84
|
color: var(--gw-color-text-strong);
|
|
75
85
|
cursor: pointer;
|
|
@@ -233,6 +243,7 @@ export class GwMenuButton extends GwElement {
|
|
|
233
243
|
declare empty: string;
|
|
234
244
|
declare items: readonly MenuItem[];
|
|
235
245
|
declare actions: readonly MenuItem[];
|
|
246
|
+
declare size: "xs" | "small" | "medium" | "large" | "xl";
|
|
236
247
|
|
|
237
248
|
readonly #trigger = h("button", {
|
|
238
249
|
class: "trigger",
|
|
@@ -264,6 +275,7 @@ export class GwMenuButton extends GwElement {
|
|
|
264
275
|
this.empty = "Nothing here yet";
|
|
265
276
|
this.items = [];
|
|
266
277
|
this.actions = [];
|
|
278
|
+
this.size = "medium";
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
/** The enabled items then actions, in the order arrow keys move through them. */
|
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", {
|
|
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(
|
|
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(
|
|
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(
|
|
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() {
|
package/src/gw-notification.ts
CHANGED
|
@@ -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 {
|