@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-theme-picker.ts
CHANGED
|
@@ -17,11 +17,22 @@ import { base } from "./styles.js";
|
|
|
17
17
|
const paletteIcon =
|
|
18
18
|
"M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// MDI's sunny, moon and night (a moon with stars), for cycle's button. Auto
|
|
21
|
+
// keeps the palette
|
|
22
|
+
const modeIcons = {
|
|
23
|
+
auto: paletteIcon,
|
|
24
|
+
light:
|
|
25
|
+
"M12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,2L14.39,5.42C13.65,5.15 12.84,5 12,5C11.16,5 10.35,5.15 9.61,5.42L12,2M3.34,7L7.5,6.65C6.9,7.16 6.36,7.78 5.94,8.5C5.5,9.24 5.25,10 5.11,10.79L3.34,7M3.36,17L5.12,13.23C5.26,14 5.53,14.78 5.95,15.5C6.37,16.24 6.91,16.86 7.5,17.37L3.36,17M20.65,7L18.88,10.79C18.74,10 18.47,9.23 18.05,8.5C17.63,7.78 17.1,7.15 16.5,6.64L20.65,7M20.64,17L16.5,17.36C17.09,16.85 17.62,16.22 18.04,15.5C18.46,14.77 18.73,14 18.87,13.21L20.64,17M12,22L9.59,18.56C10.33,18.83 11.14,19 12,19C12.82,19 13.63,18.83 14.37,18.56L12,22Z",
|
|
26
|
+
dark: "M2 12A10 10 0 0 0 15 21.54A10 10 0 0 1 15 2.46A10 10 0 0 0 2 12Z",
|
|
27
|
+
darker:
|
|
28
|
+
"M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const allModes = [
|
|
32
|
+
{ value: "auto", label: "Auto", icon: modeIcons.auto },
|
|
33
|
+
{ value: "light", label: "Light", icon: modeIcons.light },
|
|
34
|
+
{ value: "dark", label: "Dark", icon: modeIcons.dark },
|
|
35
|
+
{ value: "darker", label: "Darker", icon: modeIcons.darker },
|
|
25
36
|
] as const;
|
|
26
37
|
|
|
27
38
|
/**
|
|
@@ -37,17 +48,28 @@ const modes = [
|
|
|
37
48
|
* `gallery` swaps the theme select for `gw-theme-gallery`'s cards, which
|
|
38
49
|
* preview each theme in the chosen mode.
|
|
39
50
|
*
|
|
51
|
+
* `layout` sets how it shows. `popover`, the default, is the palette button
|
|
52
|
+
* and its popover. `cycle` is one button showing the mode in use, which steps
|
|
53
|
+
* to the next mode on each press, for an app bar. `inline` lays the controls
|
|
54
|
+
* out in place, in one wrapping row with Reset and Import, for a footer or a
|
|
55
|
+
* settings page; set `justify-content` on it to align the row. `modes` picks which modes it offers, such as `light dark` without
|
|
56
|
+
* Auto.
|
|
57
|
+
*
|
|
40
58
|
* @element gw-theme-picker
|
|
41
59
|
* @summary A button opening the page's mode, theme and import.
|
|
42
60
|
*
|
|
61
|
+
* @attr {"popover"|"cycle"|"inline"} layout - How it shows: a button and popover, a button cycling the modes, or the controls in place. Defaults to `popover`.
|
|
62
|
+
* @attr {string} modes - The modes to offer, in order and separated by spaces. Defaults to `auto light dark darker`.
|
|
43
63
|
* @attr {"auto"|"light"|"dark"|"darker"} mode - The mode in use. Defaults to `auto`.
|
|
44
64
|
* @attr {string} theme - The theme in use, by id.
|
|
45
65
|
* @attr {OptionItem[]} themes - The themes to offer, as JSON. The select hides with fewer than two. With `gallery`, a `group` gathers a family's themes.
|
|
46
66
|
* @attr {boolean} gallery - Shows the themes as preview cards rather than a select.
|
|
47
67
|
* @attr {string} status - A line under the controls, such as an import's result.
|
|
48
|
-
* @attr {string} label - The button's accessible name and the popover's heading. Defaults to `Theme`.
|
|
68
|
+
* @attr {string} label - The button's accessible name and the popover's heading. Defaults to `Theme`. With `cycle`, the button's name also gives the mode in use.
|
|
49
69
|
*
|
|
50
70
|
* @csspart gallery - The theme gallery, with `gallery`.
|
|
71
|
+
* @csspart controls - The mode, theme and status.
|
|
72
|
+
* @csspart actions - Reset and Import, with `inline`.
|
|
51
73
|
*
|
|
52
74
|
* @fires {CustomEvent<{ mode: string }>} mode - When a mode is picked.
|
|
53
75
|
* @fires {CustomEvent<{ id: string }>} theme - When a theme is picked.
|
|
@@ -62,6 +84,8 @@ export class GwThemePicker extends GwElement {
|
|
|
62
84
|
gallery: { type: "boolean", reflect: true },
|
|
63
85
|
status: {},
|
|
64
86
|
label: {},
|
|
87
|
+
layout: { reflect: true },
|
|
88
|
+
modes: {},
|
|
65
89
|
} as const;
|
|
66
90
|
|
|
67
91
|
static override styles = [
|
|
@@ -95,6 +119,28 @@ export class GwThemePicker extends GwElement {
|
|
|
95
119
|
font-size: var(--gw-font-size-s);
|
|
96
120
|
color: var(--gw-color-text-secondary);
|
|
97
121
|
}
|
|
122
|
+
|
|
123
|
+
/* One wrapping row: the controls, then Reset and Import. The host is
|
|
124
|
+
the flex container, so a page can set its justify-content */
|
|
125
|
+
:host([layout="inline"]) {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-wrap: wrap;
|
|
128
|
+
gap: var(--gw-space-4);
|
|
129
|
+
align-items: center;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:host([layout="inline"]) .controls {
|
|
133
|
+
flex-flow: row wrap;
|
|
134
|
+
align-items: center;
|
|
135
|
+
margin-block-start: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.actions {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
gap: var(--gw-space-2);
|
|
142
|
+
align-items: center;
|
|
143
|
+
}
|
|
98
144
|
`,
|
|
99
145
|
];
|
|
100
146
|
|
|
@@ -104,6 +150,10 @@ export class GwThemePicker extends GwElement {
|
|
|
104
150
|
declare gallery: boolean;
|
|
105
151
|
declare status: string;
|
|
106
152
|
declare label: string;
|
|
153
|
+
/** How it shows: a button and popover, a button cycling the modes, or the controls in place. */
|
|
154
|
+
declare layout: "popover" | "cycle" | "inline";
|
|
155
|
+
/** The modes to offer, in order and separated by spaces. */
|
|
156
|
+
declare modes: string;
|
|
107
157
|
|
|
108
158
|
readonly #button = h("gw-button", {
|
|
109
159
|
variant: "neutral",
|
|
@@ -129,34 +179,27 @@ export class GwThemePicker extends GwElement {
|
|
|
129
179
|
hidden: true,
|
|
130
180
|
});
|
|
131
181
|
|
|
132
|
-
readonly #
|
|
133
|
-
"
|
|
134
|
-
{},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
slot: "actions",
|
|
147
|
-
variant: "neutral",
|
|
148
|
-
appearance: "plain",
|
|
149
|
-
class: "reset",
|
|
150
|
-
},
|
|
151
|
-
"Reset",
|
|
152
|
-
),
|
|
153
|
-
h(
|
|
154
|
-
"gw-button",
|
|
155
|
-
{ slot: "actions", appearance: "outlined", class: "import" },
|
|
156
|
-
"Import",
|
|
157
|
-
),
|
|
182
|
+
readonly #controls = h(
|
|
183
|
+
"div",
|
|
184
|
+
{ class: "controls", part: "controls" },
|
|
185
|
+
this.#mode,
|
|
186
|
+
this.#theme,
|
|
187
|
+
this.#gallery,
|
|
188
|
+
this.#status,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
readonly #reset = h(
|
|
192
|
+
"gw-button",
|
|
193
|
+
{ variant: "neutral", appearance: "plain" },
|
|
194
|
+
"Reset",
|
|
158
195
|
);
|
|
159
196
|
|
|
197
|
+
readonly #import = h("gw-button", { appearance: "outlined" }, "Import");
|
|
198
|
+
|
|
199
|
+
readonly #popover = h("gw-popover");
|
|
200
|
+
|
|
201
|
+
readonly #actions = h("div", { class: "actions", part: "actions" });
|
|
202
|
+
|
|
160
203
|
constructor() {
|
|
161
204
|
super();
|
|
162
205
|
this.mode = "auto";
|
|
@@ -165,22 +208,65 @@ export class GwThemePicker extends GwElement {
|
|
|
165
208
|
this.gallery = false;
|
|
166
209
|
this.status = "";
|
|
167
210
|
this.label = "Theme";
|
|
211
|
+
this.layout = "popover";
|
|
212
|
+
this.modes = "auto light dark darker";
|
|
168
213
|
}
|
|
169
214
|
|
|
170
|
-
/**
|
|
215
|
+
/** The modes on offer, in the order `modes` gives them. */
|
|
216
|
+
#offered() {
|
|
217
|
+
const wanted = String(this.modes).split(/\s+/);
|
|
218
|
+
const offered = allModes.filter(({ value }) => wanted.includes(value));
|
|
219
|
+
|
|
220
|
+
return offered.length > 0 ? offered : allModes;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Opens the popover under the button, or with `cycle`, steps to the next mode. */
|
|
171
224
|
show() {
|
|
225
|
+
if (this.layout === "inline") return;
|
|
226
|
+
|
|
227
|
+
if (this.layout === "cycle") {
|
|
228
|
+
const offered = this.#offered();
|
|
229
|
+
const at = offered.findIndex(({ value }) => value === this.mode);
|
|
230
|
+
const next = offered[(at + 1) % offered.length];
|
|
231
|
+
|
|
232
|
+
if (next !== undefined) {
|
|
233
|
+
this.mode = next.value;
|
|
234
|
+
this.dispatchEvent(this.#detail("mode", { mode: this.mode }));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
172
240
|
this.#popover.show(this.#button);
|
|
173
241
|
|
|
174
242
|
// A custom theme's CSS may have changed since the cards were drawn
|
|
175
243
|
if (this.gallery) this.#gallery.refresh();
|
|
176
244
|
}
|
|
177
245
|
|
|
246
|
+
/** Puts the controls and their buttons in the popover, or in place for `inline`. */
|
|
247
|
+
#place(root: ShadowRoot) {
|
|
248
|
+
const inline = this.layout === "inline";
|
|
249
|
+
|
|
250
|
+
this.#reset.slot = inline ? "" : "actions";
|
|
251
|
+
this.#import.slot = inline ? "" : "actions";
|
|
252
|
+
|
|
253
|
+
if (inline) {
|
|
254
|
+
this.#actions.append(this.#reset, this.#import);
|
|
255
|
+
root.append(this.#controls, this.#actions);
|
|
256
|
+
this.#button.remove();
|
|
257
|
+
this.#popover.remove();
|
|
258
|
+
} else {
|
|
259
|
+
this.#popover.append(this.#controls, this.#reset, this.#import);
|
|
260
|
+
root.prepend(this.#button, this.#popover);
|
|
261
|
+
this.#actions.remove();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
178
265
|
#detail<Detail>(type: string, detail: Detail) {
|
|
179
266
|
return new CustomEvent(type, { detail, bubbles: true, composed: true });
|
|
180
267
|
}
|
|
181
268
|
|
|
182
269
|
protected override create(root: ShadowRoot) {
|
|
183
|
-
this.#mode.options = modes;
|
|
184
270
|
this.#button.addEventListener("click", () => {
|
|
185
271
|
this.show();
|
|
186
272
|
});
|
|
@@ -199,10 +285,10 @@ export class GwThemePicker extends GwElement {
|
|
|
199
285
|
this.theme = this.#gallery.theme;
|
|
200
286
|
this.dispatchEvent(this.#detail("theme", { id: this.theme }));
|
|
201
287
|
});
|
|
202
|
-
this.#
|
|
288
|
+
this.#import.addEventListener("click", () => {
|
|
203
289
|
this.#file.click();
|
|
204
290
|
});
|
|
205
|
-
this.#
|
|
291
|
+
this.#reset.addEventListener("click", () => {
|
|
206
292
|
this.dispatchEvent(new Event("reset", { bubbles: true, composed: true }));
|
|
207
293
|
});
|
|
208
294
|
this.#file.addEventListener("change", async () => {
|
|
@@ -217,14 +303,33 @@ export class GwThemePicker extends GwElement {
|
|
|
217
303
|
this.dispatchEvent(this.#detail("import", { text, name: file.name }));
|
|
218
304
|
});
|
|
219
305
|
|
|
220
|
-
root.append(this.#
|
|
306
|
+
root.append(this.#file);
|
|
307
|
+
this.#place(root);
|
|
221
308
|
}
|
|
222
309
|
|
|
223
|
-
protected override update() {
|
|
224
|
-
|
|
225
|
-
|
|
310
|
+
protected override update(changed: ReadonlySet<string>) {
|
|
311
|
+
const root = this.shadowRoot;
|
|
312
|
+
|
|
313
|
+
if (changed.has("layout") && root !== null) this.#place(root);
|
|
314
|
+
|
|
315
|
+
const offered = this.#offered();
|
|
316
|
+
const current = offered.find(({ value }) => value === this.mode);
|
|
317
|
+
|
|
318
|
+
if (this.layout === "cycle") {
|
|
319
|
+
const name = `${this.label}: ${current?.label ?? this.mode}`;
|
|
320
|
+
|
|
321
|
+
this.#button.label = name;
|
|
322
|
+
this.#button.textContent = name;
|
|
323
|
+
this.#button.icon = current?.icon ?? paletteIcon;
|
|
324
|
+
} else {
|
|
325
|
+
this.#button.label = this.label;
|
|
326
|
+
this.#button.textContent = this.label;
|
|
327
|
+
this.#button.icon = paletteIcon;
|
|
328
|
+
}
|
|
329
|
+
|
|
226
330
|
this.#popover.heading = this.label;
|
|
227
331
|
this.#mode.label = "Mode";
|
|
332
|
+
this.#mode.options = offered.map(({ value, label }) => ({ value, label }));
|
|
228
333
|
this.#mode.value = this.mode;
|
|
229
334
|
this.#theme.options = Array.isArray(this.themes) ? this.themes : [];
|
|
230
335
|
this.#theme.value = this.theme;
|
package/src/gw-toast.ts
CHANGED
|
@@ -22,6 +22,8 @@ import { base, type Tone } from "./styles.js";
|
|
|
22
22
|
* @summary Brief, non-interactive toasts in the bottom end corner.
|
|
23
23
|
*
|
|
24
24
|
* @attr {number} duration - Milliseconds each toast shows for. Defaults to 5000.
|
|
25
|
+
*
|
|
26
|
+
* @csspart toast - Each toast.
|
|
25
27
|
*/
|
|
26
28
|
export class GwToaster extends GwElement {
|
|
27
29
|
static override properties = {
|
|
@@ -111,7 +113,7 @@ export class GwToaster extends GwElement {
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
show(message: string, tone: Tone = "neutral") {
|
|
114
|
-
const toast = h("div", { class: "toast" }, message);
|
|
116
|
+
const toast = h("div", { class: "toast", part: "toast" }, message);
|
|
115
117
|
|
|
116
118
|
toast.style.setProperty(
|
|
117
119
|
"--gw-toast-tone",
|
package/src/styles.ts
CHANGED
|
@@ -7,6 +7,31 @@ import { css } from "./element.js";
|
|
|
7
7
|
|
|
8
8
|
const svgNamespace = "http://www.w3.org/2000/svg";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The `xs` to `xl` presets on a `size` attribute, as `--gw-<name>-scale`
|
|
12
|
+
* around 1 for medium, so an app can also set any size between or beyond
|
|
13
|
+
* them. A control multiplies its height and icon by `--gw-control-scale`
|
|
14
|
+
* too, which follows the theme's density, and its text only by its own
|
|
15
|
+
* scale, since spacing and type already follow density on their own.
|
|
16
|
+
*/
|
|
17
|
+
export const controlSizes = (name: string) => css`
|
|
18
|
+
:host([size="xs"]) {
|
|
19
|
+
--gw-${name}-scale: 0.6;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
:host([size="small"]) {
|
|
23
|
+
--gw-${name}-scale: 0.8;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:host([size="large"]) {
|
|
27
|
+
--gw-${name}-scale: 1.2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:host([size="xl"]) {
|
|
31
|
+
--gw-${name}-scale: 1.4;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
10
35
|
/**
|
|
11
36
|
* What every component shares: box sizing, the focus ring, icons and the
|
|
12
37
|
* tones that set `--gw-tone` from a `tone` attribute.
|