@timmo001/groundwork-ui 0.9.12 → 0.9.13
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 +64 -10
- package/package.json +1 -1
- package/src/gw-app-bar.ts +5 -3
- package/src/gw-page-header.ts +5 -3
- package/src/gw-theme-picker.ts +196 -23
package/custom-elements.json
CHANGED
|
@@ -1540,7 +1540,7 @@
|
|
|
1540
1540
|
"text": "readonly CSSStyleSheet[]"
|
|
1541
1541
|
},
|
|
1542
1542
|
"static": true,
|
|
1543
|
-
"default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } .nav { display: flex; flex:
|
|
1543
|
+
"default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } /* The nav keeps its room; the actions take the rest, which a theme picker fills or gives up by moving its controls into its popover */ .nav { display: flex; flex: none; gap: var(--gw-space-1); align-items: center; min-width: 0; } .actions { display: flex; flex: 1 1 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } .menu { display: none; place-items: center; width: 2.5rem; height: 2.5rem; padding: 0; color: var(--gw-color-text-strong); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } .menu:hover { background: var(--gw-color-fill-neutral-hovered); } /* Below menu-below, which sets collapsed, the nav folds into a menu */ :host([collapsed]) .menu { display: inline-grid; } :host([collapsed]) .nav { position: absolute; inset-block-start: 100%; inset-inline: 0; flex-direction: column; gap: var(--gw-space-0-5); align-items: stretch; padding: var(--gw-space-2) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); visibility: hidden; background: var(--gw-color-surface-default); border-block-end: max(var(--gw-border-width), 1px) solid var(--gw-color-border-quiet); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.5rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-normal) allow-discrete; } :host([collapsed][menu-open]) .nav { visibility: visible; opacity: 1; translate: 0 0; } `, ]",
|
|
1544
1544
|
"inheritedFrom": {
|
|
1545
1545
|
"name": "GwElement",
|
|
1546
1546
|
"module": "src/element.ts"
|
|
@@ -13400,7 +13400,7 @@
|
|
|
13400
13400
|
"text": "readonly CSSStyleSheet[]"
|
|
13401
13401
|
},
|
|
13402
13402
|
"static": true,
|
|
13403
|
-
"default": "[ base, css` :host { display: flex; gap: var(--gw-space-3); align-items: flex-start; } .start { display: flex; flex: none; align-items: center; min-height: 3rem; } .start[hidden] { display: none; } .tile { display: grid; flex: none; place-items: center; width: 3rem; height: 3rem; color: var(--gw-color-accent); background: var(--gw-color-fill-accent-quiet); border-radius: var(--gw-radius-l); --gw-icon-size: 1.625rem; } .titles { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } h1 { margin: 0; font-size: var(--gw-font-size-2xl); font-weight: var(--gw-font-weight-strong); line-height: 1.2; color: var(--gw-color-text-strong); } p { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } .actions { display: flex; flex:
|
|
13403
|
+
"default": "[ base, css` :host { display: flex; gap: var(--gw-space-3); align-items: flex-start; } .start { display: flex; flex: none; align-items: center; min-height: 3rem; } .start[hidden] { display: none; } .tile { display: grid; flex: none; place-items: center; width: 3rem; height: 3rem; color: var(--gw-color-accent); background: var(--gw-color-fill-accent-quiet); border-radius: var(--gw-radius-l); --gw-icon-size: 1.625rem; } .titles { display: flex; flex: 0 1 auto; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } h1 { margin: 0; font-size: var(--gw-font-size-2xl); font-weight: var(--gw-font-weight-strong); line-height: 1.2; color: var(--gw-color-text-strong); } p { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* Takes the room the titles leave, and gives it up before them, down to its items' smallest */ .actions { display: flex; flex: 1 1000 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } /* Centered on the tile when there is one */ .tile:not([hidden]) ~ .actions { min-height: 3rem; } `, ]",
|
|
13404
13404
|
"inheritedFrom": {
|
|
13405
13405
|
"name": "GwElement",
|
|
13406
13406
|
"module": "src/element.ts"
|
|
@@ -19615,7 +19615,7 @@
|
|
|
19615
19615
|
"declarations": [
|
|
19616
19616
|
{
|
|
19617
19617
|
"kind": "class",
|
|
19618
|
-
"description": "
|
|
19618
|
+
"description": "The page's mode, theme and import, in place where there's room and in a\npopover where there isn't. Offers the mode (auto, light, dark or Darker),\nthe theme, and buttons to import a theme file (a Groundwork theme's `.json`\nor an Omarchy `colors.toml`) or reset.\n\nIt only asks: it fires `mode`, `theme`, `import` and `reset`, and the page\napplies them and sets `mode`, `theme` and `themes` back. `pageTheme` from\n`@timmo001/groundwork-tokens/page` does all of that. Show an import's\nresult, such as contrast misses, in `status`.\n\n`gallery` swaps the theme select for `gw-theme-gallery`'s cards, which\npreview each theme in the chosen mode.\n\n`layout` sets how it shows. `auto`, the default, lays the mode out as icons\nand the theme as a select in place while they fit the room it's given,\nthen moves them into the popover, the theme first, as the room runs out.\nReset, Import, the gallery and `status` stay in the popover, opened from a\npalette button, or from a more button once the mode and theme are out.\nIn a flex row, such as `gw-page-header`'s actions, it gives up room before\nthe title does. `popover` keeps everything behind the palette button. `cycle` is one button\nshowing the mode in use, which steps to the next mode on each press, for an\napp bar. `inline` lays every control out in place, in one wrapping row with\nReset and Import, for a footer or a settings page; set `justify-content` on\nit to align the row. `modes` picks which modes it offers, such as\n`light dark` without Auto.",
|
|
19619
19619
|
"name": "GwThemePicker",
|
|
19620
19620
|
"cssParts": [
|
|
19621
19621
|
{
|
|
@@ -19652,7 +19652,7 @@
|
|
|
19652
19652
|
"text": "readonly CSSStyleSheet[]"
|
|
19653
19653
|
},
|
|
19654
19654
|
"static": true,
|
|
19655
|
-
"default": "[ base, css` :host { display: inline-block; } :host([gallery]) gw-popover { --gw-popover-width: 34rem; } .gallery { max-height: min(24rem, 50vh); overflow: auto; /* Room for the cards' focus rings */ padding: var(--gw-space-1); margin: calc(var(--gw-space-1) * -1); } .controls { display: flex; flex-direction: column; gap: var(--gw-space-4); margin-block-start: var(--gw-space-2); } .status { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* One wrapping row: the controls, then Reset and Import. The host is the flex container, so a page can set its justify-content */ :host([layout=\"inline\"]) { display: flex; flex-wrap: wrap; gap: var(--gw-space-4); align-items: center; } :host([layout=\"inline\"]) .controls { flex-flow: row wrap; align-items: center; margin-block-start: 0; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); align-items: center; } `, ]",
|
|
19655
|
+
"default": "[ base, css` :host { display: inline-block; } :host([gallery]) gw-popover { --gw-popover-width: 34rem; } .gallery { max-height: min(24rem, 50vh); overflow: auto; /* Room for the cards' focus rings */ padding: var(--gw-space-1); margin: calc(var(--gw-space-1) * -1); } .controls { display: flex; flex-direction: column; gap: var(--gw-space-4); margin-block-start: var(--gw-space-2); } .status { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* One wrapping row: the controls, then Reset and Import. The host is the flex container, so a page can set its justify-content */ :host([layout=\"inline\"]) { display: flex; flex-wrap: wrap; gap: var(--gw-space-4); align-items: center; } :host([layout=\"inline\"]) .controls { flex-flow: row wrap; align-items: center; margin-block-start: 0; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); align-items: center; } /* Asks for the room to have all of it in place, but gives it up down to the button before a flex row's other items shrink */ :host([layout=\"auto\"]) { display: flex; flex: 0 1000 var(--gw-theme-picker-fit, auto); gap: var(--gw-space-1); align-items: center; min-width: 2.5rem; contain: inline-size; } :host([layout=\"auto\"]) > * { flex: none; } `, ]",
|
|
19656
19656
|
"inheritedFrom": {
|
|
19657
19657
|
"name": "GwElement",
|
|
19658
19658
|
"module": "src/element.ts"
|
|
@@ -19710,10 +19710,10 @@
|
|
|
19710
19710
|
"kind": "field",
|
|
19711
19711
|
"name": "layout",
|
|
19712
19712
|
"type": {
|
|
19713
|
-
"text": "\"popover\" | \"cycle\" | \"inline\""
|
|
19713
|
+
"text": "\"auto\" | \"popover\" | \"cycle\" | \"inline\""
|
|
19714
19714
|
},
|
|
19715
|
-
"description": "How it shows: a button and popover, a button cycling the modes, or
|
|
19716
|
-
"default": "\"
|
|
19715
|
+
"description": "How it shows: in place while it fits then in a popover, a button and popover, a button cycling the modes, or every control in place.",
|
|
19716
|
+
"default": "\"auto\""
|
|
19717
19717
|
},
|
|
19718
19718
|
{
|
|
19719
19719
|
"kind": "field",
|
|
@@ -19815,6 +19815,60 @@
|
|
|
19815
19815
|
],
|
|
19816
19816
|
"description": "Puts the controls and their buttons in the popover, or in place for `inline`."
|
|
19817
19817
|
},
|
|
19818
|
+
{
|
|
19819
|
+
"kind": "method",
|
|
19820
|
+
"name": "#arrange",
|
|
19821
|
+
"privacy": "private",
|
|
19822
|
+
"parameters": [
|
|
19823
|
+
{
|
|
19824
|
+
"name": "root",
|
|
19825
|
+
"type": {
|
|
19826
|
+
"text": "ShadowRoot"
|
|
19827
|
+
}
|
|
19828
|
+
}
|
|
19829
|
+
],
|
|
19830
|
+
"description": "With `auto`, moves the mode and theme in place or back into the popover, as `#fits` says."
|
|
19831
|
+
},
|
|
19832
|
+
{
|
|
19833
|
+
"kind": "method",
|
|
19834
|
+
"name": "#fit",
|
|
19835
|
+
"privacy": "private",
|
|
19836
|
+
"description": "With `auto`, measures the mode and theme in place, sizes the host to fit\nthem all, then keeps in place what the room it gets allows, the mode\nfirst. The rest go into the popover."
|
|
19837
|
+
},
|
|
19838
|
+
{
|
|
19839
|
+
"kind": "field",
|
|
19840
|
+
"name": "#fits",
|
|
19841
|
+
"privacy": "private",
|
|
19842
|
+
"type": {
|
|
19843
|
+
"text": "object"
|
|
19844
|
+
},
|
|
19845
|
+
"default": "{ mode: false, theme: false }"
|
|
19846
|
+
},
|
|
19847
|
+
{
|
|
19848
|
+
"kind": "field",
|
|
19849
|
+
"name": "#fitting",
|
|
19850
|
+
"privacy": "private",
|
|
19851
|
+
"type": {
|
|
19852
|
+
"text": "number"
|
|
19853
|
+
},
|
|
19854
|
+
"default": "0"
|
|
19855
|
+
},
|
|
19856
|
+
{
|
|
19857
|
+
"kind": "field",
|
|
19858
|
+
"name": "#measuring",
|
|
19859
|
+
"privacy": "private",
|
|
19860
|
+
"type": {
|
|
19861
|
+
"text": "boolean"
|
|
19862
|
+
},
|
|
19863
|
+
"default": "false"
|
|
19864
|
+
},
|
|
19865
|
+
{
|
|
19866
|
+
"kind": "field",
|
|
19867
|
+
"name": "#resize",
|
|
19868
|
+
"privacy": "private",
|
|
19869
|
+
"readonly": true,
|
|
19870
|
+
"default": "new ResizeObserver(() => { void this.#fit(); })"
|
|
19871
|
+
},
|
|
19818
19872
|
{
|
|
19819
19873
|
"kind": "method",
|
|
19820
19874
|
"name": "#detail",
|
|
@@ -20179,9 +20233,9 @@
|
|
|
20179
20233
|
"attributes": [
|
|
20180
20234
|
{
|
|
20181
20235
|
"type": {
|
|
20182
|
-
"text": "\"popover\"|\"cycle\"|\"inline\""
|
|
20236
|
+
"text": "\"auto\"|\"popover\"|\"cycle\"|\"inline\""
|
|
20183
20237
|
},
|
|
20184
|
-
"description": "How it shows: a button and popover, a button cycling the modes, or
|
|
20238
|
+
"description": "How it shows: in place while it fits then in a popover, a button and popover, a button cycling the modes, or every control in place. Defaults to `auto`.",
|
|
20185
20239
|
"name": "layout"
|
|
20186
20240
|
},
|
|
20187
20241
|
{
|
|
@@ -20240,7 +20294,7 @@
|
|
|
20240
20294
|
},
|
|
20241
20295
|
"tagName": "gw-theme-picker",
|
|
20242
20296
|
"customElement": true,
|
|
20243
|
-
"summary": "
|
|
20297
|
+
"summary": "The page's mode, theme and import, in place or in a popover."
|
|
20244
20298
|
}
|
|
20245
20299
|
],
|
|
20246
20300
|
"exports": [
|
package/package.json
CHANGED
package/src/gw-app-bar.ts
CHANGED
|
@@ -141,9 +141,11 @@ export class GwAppBar extends GwElement {
|
|
|
141
141
|
visibility var(--gw-motion-slow) allow-discrete;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/* The nav keeps its room; the actions take the rest, which a theme
|
|
145
|
+
picker fills or gives up by moving its controls into its popover */
|
|
144
146
|
.nav {
|
|
145
147
|
display: flex;
|
|
146
|
-
flex:
|
|
148
|
+
flex: none;
|
|
147
149
|
gap: var(--gw-space-1);
|
|
148
150
|
align-items: center;
|
|
149
151
|
min-width: 0;
|
|
@@ -151,10 +153,10 @@ export class GwAppBar extends GwElement {
|
|
|
151
153
|
|
|
152
154
|
.actions {
|
|
153
155
|
display: flex;
|
|
154
|
-
flex:
|
|
156
|
+
flex: 1 1 auto;
|
|
155
157
|
gap: var(--gw-space-1);
|
|
156
158
|
align-items: center;
|
|
157
|
-
|
|
159
|
+
justify-content: flex-end;
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
.menu {
|
package/src/gw-page-header.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class GwPageHeader extends GwElement {
|
|
|
67
67
|
|
|
68
68
|
.titles {
|
|
69
69
|
display: flex;
|
|
70
|
-
flex: 1;
|
|
70
|
+
flex: 0 1 auto;
|
|
71
71
|
flex-direction: column;
|
|
72
72
|
gap: var(--gw-space-0-5);
|
|
73
73
|
min-width: 0;
|
|
@@ -87,12 +87,14 @@ export class GwPageHeader extends GwElement {
|
|
|
87
87
|
color: var(--gw-color-text-secondary);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/* Takes the room the titles leave, and gives it up before them, down
|
|
91
|
+
to its items' smallest */
|
|
90
92
|
.actions {
|
|
91
93
|
display: flex;
|
|
92
|
-
flex:
|
|
94
|
+
flex: 1 1000 auto;
|
|
93
95
|
gap: var(--gw-space-1);
|
|
94
96
|
align-items: center;
|
|
95
|
-
|
|
97
|
+
justify-content: flex-end;
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
/* Centered on the tile when there is one */
|
package/src/gw-theme-picker.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `gw-theme-picker`:
|
|
2
|
+
* `gw-theme-picker`: the page's mode, theme and import, in place or in a popover.
|
|
3
3
|
* Docs: https://groundwork.timmo.dev/components/actions/#theme-picker
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
@@ -17,10 +17,10 @@ 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
|
-
// MDI's sunny, moon and night (a moon with stars)
|
|
21
|
-
// keeps the palette
|
|
20
|
+
// MDI's theme-light-dark, sunny, moon and night (a moon with stars). Cycle's
|
|
21
|
+
// button keeps the palette for auto
|
|
22
22
|
const modeIcons = {
|
|
23
|
-
auto:
|
|
23
|
+
auto: "M7.5,2C5.71,3.15 4.5,5.18 4.5,7.5C4.5,9.82 5.71,11.85 7.53,13C4.46,13 2,10.54 2,7.5A5.5,5.5 0 0,1 7.5,2M19.07,3.5L20.5,4.93L4.93,20.5L3.5,19.07L19.07,3.5M12.89,5.93L11.41,5L9.97,6L10.39,4.3L9,3.24L10.75,3.12L11.33,1.47L12,3.1L13.73,3.13L12.38,4.26L12.89,5.93M9.59,9.54L8.43,8.81L7.31,9.59L7.65,8.27L6.56,7.44L7.92,7.35L8.37,6.06L8.88,7.33L10.24,7.36L9.19,8.23L9.59,9.54M19,13.5A5.5,5.5 0 0,1 13.5,19C12.28,19 11.15,18.6 10.24,17.93L17.93,10.24C18.6,11.15 19,12.28 19,13.5M14.6,20.08L17.37,18.93L17.13,22.28L14.6,20.08M18.93,17.38L20.08,14.61L22.28,17.15L18.93,17.38M20.08,12.42L18.94,9.64L22.28,9.88L20.08,12.42M9.63,18.93L12.4,20.08L9.87,22.27L9.63,18.93Z",
|
|
24
24
|
light:
|
|
25
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
26
|
dark: "M2 12A10 10 0 0 0 15 21.54A10 10 0 0 1 15 2.46A10 10 0 0 0 2 12Z",
|
|
@@ -35,10 +35,15 @@ const allModes = [
|
|
|
35
35
|
{ value: "darker", label: "Darker", icon: modeIcons.darker },
|
|
36
36
|
] as const;
|
|
37
37
|
|
|
38
|
+
// MDI's dots-vertical, for the overflow once the mode and theme are in place
|
|
39
|
+
const moreIcon =
|
|
40
|
+
"M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z";
|
|
41
|
+
|
|
38
42
|
/**
|
|
39
|
-
*
|
|
40
|
-
* (auto, light, dark or Darker),
|
|
41
|
-
* file (a Groundwork theme's `.json`
|
|
43
|
+
* The page's mode, theme and import, in place where there's room and in a
|
|
44
|
+
* popover where there isn't. Offers the mode (auto, light, dark or Darker),
|
|
45
|
+
* the theme, and buttons to import a theme file (a Groundwork theme's `.json`
|
|
46
|
+
* or an Omarchy `colors.toml`) or reset.
|
|
42
47
|
*
|
|
43
48
|
* It only asks: it fires `mode`, `theme`, `import` and `reset`, and the page
|
|
44
49
|
* applies them and sets `mode`, `theme` and `themes` back. `pageTheme` from
|
|
@@ -48,17 +53,23 @@ const allModes = [
|
|
|
48
53
|
* `gallery` swaps the theme select for `gw-theme-gallery`'s cards, which
|
|
49
54
|
* preview each theme in the chosen mode.
|
|
50
55
|
*
|
|
51
|
-
* `layout` sets how it shows. `
|
|
52
|
-
* and
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
56
|
+
* `layout` sets how it shows. `auto`, the default, lays the mode out as icons
|
|
57
|
+
* and the theme as a select in place while they fit the room it's given,
|
|
58
|
+
* then moves them into the popover, the theme first, as the room runs out.
|
|
59
|
+
* Reset, Import, the gallery and `status` stay in the popover, opened from a
|
|
60
|
+
* palette button, or from a more button once the mode and theme are out.
|
|
61
|
+
* In a flex row, such as `gw-page-header`'s actions, it gives up room before
|
|
62
|
+
* the title does. `popover` keeps everything behind the palette button. `cycle` is one button
|
|
63
|
+
* showing the mode in use, which steps to the next mode on each press, for an
|
|
64
|
+
* app bar. `inline` lays every control out in place, in one wrapping row with
|
|
65
|
+
* Reset and Import, for a footer or a settings page; set `justify-content` on
|
|
66
|
+
* it to align the row. `modes` picks which modes it offers, such as
|
|
67
|
+
* `light dark` without Auto.
|
|
57
68
|
*
|
|
58
69
|
* @element gw-theme-picker
|
|
59
|
-
* @summary
|
|
70
|
+
* @summary The page's mode, theme and import, in place or in a popover.
|
|
60
71
|
*
|
|
61
|
-
* @attr {"popover"|"cycle"|"inline"} layout - How it shows: a button and popover, a button cycling the modes, or
|
|
72
|
+
* @attr {"auto"|"popover"|"cycle"|"inline"} layout - How it shows: in place while it fits then in a popover, a button and popover, a button cycling the modes, or every control in place. Defaults to `auto`.
|
|
62
73
|
* @attr {string} modes - The modes to offer, in order and separated by spaces. Defaults to `auto light dark darker`.
|
|
63
74
|
* @attr {"auto"|"light"|"dark"|"darker"} mode - The mode in use. Defaults to `auto`.
|
|
64
75
|
* @attr {string} theme - The theme in use, by id.
|
|
@@ -141,6 +152,21 @@ export class GwThemePicker extends GwElement {
|
|
|
141
152
|
gap: var(--gw-space-2);
|
|
142
153
|
align-items: center;
|
|
143
154
|
}
|
|
155
|
+
|
|
156
|
+
/* Asks for the room to have all of it in place, but gives it up down
|
|
157
|
+
to the button before a flex row's other items shrink */
|
|
158
|
+
:host([layout="auto"]) {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex: 0 1000 var(--gw-theme-picker-fit, auto);
|
|
161
|
+
gap: var(--gw-space-1);
|
|
162
|
+
align-items: center;
|
|
163
|
+
min-width: 2.5rem;
|
|
164
|
+
contain: inline-size;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
:host([layout="auto"]) > * {
|
|
168
|
+
flex: none;
|
|
169
|
+
}
|
|
144
170
|
`,
|
|
145
171
|
];
|
|
146
172
|
|
|
@@ -150,8 +176,8 @@ export class GwThemePicker extends GwElement {
|
|
|
150
176
|
declare gallery: boolean;
|
|
151
177
|
declare status: string;
|
|
152
178
|
declare label: string;
|
|
153
|
-
/** How it shows: a button and popover, a button cycling the modes, or
|
|
154
|
-
declare layout: "popover" | "cycle" | "inline";
|
|
179
|
+
/** How it shows: in place while it fits then in a popover, a button and popover, a button cycling the modes, or every control in place. */
|
|
180
|
+
declare layout: "auto" | "popover" | "cycle" | "inline";
|
|
155
181
|
/** The modes to offer, in order and separated by spaces. */
|
|
156
182
|
declare modes: string;
|
|
157
183
|
|
|
@@ -208,7 +234,7 @@ export class GwThemePicker extends GwElement {
|
|
|
208
234
|
this.gallery = false;
|
|
209
235
|
this.status = "";
|
|
210
236
|
this.label = "Theme";
|
|
211
|
-
this.layout = "
|
|
237
|
+
this.layout = "auto";
|
|
212
238
|
this.modes = "auto light dark darker";
|
|
213
239
|
}
|
|
214
240
|
|
|
@@ -251,15 +277,143 @@ export class GwThemePicker extends GwElement {
|
|
|
251
277
|
this.#import.slot = inline ? "" : "actions";
|
|
252
278
|
|
|
253
279
|
if (inline) {
|
|
280
|
+
this.#controls.replaceChildren(
|
|
281
|
+
this.#mode,
|
|
282
|
+
this.#theme,
|
|
283
|
+
this.#gallery,
|
|
284
|
+
this.#status,
|
|
285
|
+
);
|
|
254
286
|
this.#actions.append(this.#reset, this.#import);
|
|
255
287
|
root.append(this.#controls, this.#actions);
|
|
256
288
|
this.#button.remove();
|
|
257
289
|
this.#popover.remove();
|
|
258
290
|
} else {
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
if (this.#controls.parentNode !== this.#popover) {
|
|
292
|
+
this.#popover.append(this.#controls, this.#reset, this.#import);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (this.#button.parentNode !== root) {
|
|
296
|
+
root.prepend(this.#button, this.#popover);
|
|
297
|
+
}
|
|
298
|
+
|
|
261
299
|
this.#actions.remove();
|
|
262
300
|
}
|
|
301
|
+
|
|
302
|
+
this.#arrange(root);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** With `auto`, moves the mode and theme in place or back into the popover, as `#fits` says. */
|
|
306
|
+
#arrange(root: ShadowRoot) {
|
|
307
|
+
// #fit has them in place to measure
|
|
308
|
+
if (this.#measuring) return;
|
|
309
|
+
|
|
310
|
+
const auto = this.layout === "auto";
|
|
311
|
+
const mode = auto && this.#fits.mode;
|
|
312
|
+
const theme = mode && this.#fits.theme;
|
|
313
|
+
|
|
314
|
+
if (mode && this.#mode.parentNode !== root) {
|
|
315
|
+
root.insertBefore(this.#mode, this.#button);
|
|
316
|
+
} else if (!mode && this.#mode.parentNode !== this.#controls) {
|
|
317
|
+
this.#controls.prepend(this.#mode);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (theme && this.#theme.parentNode !== root) {
|
|
321
|
+
root.insertBefore(this.#theme, this.#button);
|
|
322
|
+
} else if (!theme && this.#theme.parentNode !== this.#controls) {
|
|
323
|
+
this.#controls.insertBefore(this.#theme, this.#gallery);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.#mode.labels = mode ? "hide" : "show";
|
|
327
|
+
this.#theme.inlineLabel = theme;
|
|
328
|
+
// The popover's heading names it once the mode is out of it
|
|
329
|
+
this.#theme.labelHidden = mode && !theme;
|
|
330
|
+
// The palette while the popover holds the theme; the overflow dots
|
|
331
|
+
// once only Reset and Import are left in it
|
|
332
|
+
this.#button.icon =
|
|
333
|
+
this.layout === "cycle"
|
|
334
|
+
? this.#button.icon
|
|
335
|
+
: theme || (mode && this.#theme.hidden && !this.gallery)
|
|
336
|
+
? moreIcon
|
|
337
|
+
: paletteIcon;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* With `auto`, measures the mode and theme in place, sizes the host to fit
|
|
342
|
+
* them all, then keeps in place what the room it gets allows, the mode
|
|
343
|
+
* first. The rest go into the popover.
|
|
344
|
+
*/
|
|
345
|
+
async #fit() {
|
|
346
|
+
const root = this.shadowRoot;
|
|
347
|
+
|
|
348
|
+
if (this.layout !== "auto" || root === null || !this.isConnected) return;
|
|
349
|
+
|
|
350
|
+
const run = ++this.#fitting;
|
|
351
|
+
const theme = !this.#theme.hidden;
|
|
352
|
+
|
|
353
|
+
this.#measuring = true;
|
|
354
|
+
|
|
355
|
+
if (this.#mode.parentNode !== root) {
|
|
356
|
+
this.#mode.labels = "hide";
|
|
357
|
+
root.insertBefore(this.#mode, this.#button);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (theme && this.#theme.parentNode !== root) {
|
|
361
|
+
this.#theme.inlineLabel = true;
|
|
362
|
+
this.#theme.labelHidden = false;
|
|
363
|
+
root.insertBefore(this.#theme, this.#button);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Drawn in place before measuring, still before the next paint
|
|
367
|
+
await Promise.all([this.#mode.updateComplete, this.#theme.updateComplete]);
|
|
368
|
+
|
|
369
|
+
if (run !== this.#fitting) return;
|
|
370
|
+
|
|
371
|
+
this.#measuring = false;
|
|
372
|
+
|
|
373
|
+
if (this.layout !== "auto") {
|
|
374
|
+
this.#arrange(root);
|
|
375
|
+
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const gap = Number.parseFloat(getComputedStyle(this).columnGap) || 0;
|
|
380
|
+
const button = this.#button.getBoundingClientRect().width;
|
|
381
|
+
const mode = this.#mode.getBoundingClientRect().width + gap;
|
|
382
|
+
const select = theme ? this.#theme.getBoundingClientRect().width + gap : 0;
|
|
383
|
+
|
|
384
|
+
this.style.setProperty(
|
|
385
|
+
"--gw-theme-picker-fit",
|
|
386
|
+
`${Math.ceil(button + mode + select)}px`,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const room = this.getBoundingClientRect().width + 0.5;
|
|
390
|
+
|
|
391
|
+
this.#fits = {
|
|
392
|
+
mode: room >= button + mode,
|
|
393
|
+
theme: theme && room >= button + mode + select,
|
|
394
|
+
};
|
|
395
|
+
this.#arrange(root);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
#fits = { mode: false, theme: false };
|
|
399
|
+
|
|
400
|
+
#fitting = 0;
|
|
401
|
+
|
|
402
|
+
#measuring = false;
|
|
403
|
+
|
|
404
|
+
readonly #resize = new ResizeObserver(() => {
|
|
405
|
+
void this.#fit();
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
override connectedCallback() {
|
|
409
|
+
super.connectedCallback();
|
|
410
|
+
this.#resize.observe(this);
|
|
411
|
+
void document.fonts.ready.then(() => this.#fit());
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
override disconnectedCallback() {
|
|
415
|
+
super.disconnectedCallback();
|
|
416
|
+
this.#resize.disconnect();
|
|
263
417
|
}
|
|
264
418
|
|
|
265
419
|
#detail<Detail>(type: string, detail: Detail) {
|
|
@@ -320,16 +474,22 @@ export class GwThemePicker extends GwElement {
|
|
|
320
474
|
|
|
321
475
|
this.#button.label = name;
|
|
322
476
|
this.#button.textContent = name;
|
|
323
|
-
this.#button.icon =
|
|
477
|
+
this.#button.icon =
|
|
478
|
+
current === undefined || current.value === "auto"
|
|
479
|
+
? paletteIcon
|
|
480
|
+
: current.icon;
|
|
324
481
|
} else {
|
|
325
482
|
this.#button.label = this.label;
|
|
326
483
|
this.#button.textContent = this.label;
|
|
327
|
-
this.#button.icon = paletteIcon;
|
|
328
484
|
}
|
|
329
485
|
|
|
330
486
|
this.#popover.heading = this.label;
|
|
331
487
|
this.#mode.label = "Mode";
|
|
332
|
-
this.#mode.options = offered.map(({ value, label }) => ({
|
|
488
|
+
this.#mode.options = offered.map(({ value, label, icon }) => ({
|
|
489
|
+
value,
|
|
490
|
+
label,
|
|
491
|
+
icon,
|
|
492
|
+
}));
|
|
333
493
|
this.#mode.value = this.mode;
|
|
334
494
|
this.#theme.options = Array.isArray(this.themes) ? this.themes : [];
|
|
335
495
|
this.#theme.value = this.theme;
|
|
@@ -341,6 +501,19 @@ export class GwThemePicker extends GwElement {
|
|
|
341
501
|
this.#gallery.mode = this.mode === "auto" ? "" : this.mode;
|
|
342
502
|
this.#status.textContent = this.status;
|
|
343
503
|
this.#status.hidden = !this.status;
|
|
504
|
+
|
|
505
|
+
if (root === null) return;
|
|
506
|
+
|
|
507
|
+
this.#arrange(root);
|
|
508
|
+
|
|
509
|
+
// Measures again for the select's new value or the new set of controls
|
|
510
|
+
if (
|
|
511
|
+
["layout", "modes", "theme", "themes", "gallery"].some((name) =>
|
|
512
|
+
changed.has(name),
|
|
513
|
+
)
|
|
514
|
+
) {
|
|
515
|
+
void this.#fit();
|
|
516
|
+
}
|
|
344
517
|
}
|
|
345
518
|
}
|
|
346
519
|
|