@timmo001/groundwork-ui 0.9.11 → 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 +81 -11
- package/package.json +1 -1
- package/src/gw-app-bar.ts +5 -3
- package/src/gw-page-header.ts +34 -5
- package/src/gw-segmented.ts +4 -1
- 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"
|
|
@@ -13353,7 +13353,7 @@
|
|
|
13353
13353
|
"declarations": [
|
|
13354
13354
|
{
|
|
13355
13355
|
"kind": "class",
|
|
13356
|
-
"description": "A page's title, with an optional icon tile and description, and its
|
|
13356
|
+
"description": "A page's title, with an optional icon tile and description, a back button\nor similar in the `start` slot, and its controls at the top right in the\n`actions` slot.",
|
|
13357
13357
|
"name": "GwPageHeader",
|
|
13358
13358
|
"cssParts": [
|
|
13359
13359
|
{
|
|
@@ -13370,6 +13370,10 @@
|
|
|
13370
13370
|
}
|
|
13371
13371
|
],
|
|
13372
13372
|
"slots": [
|
|
13373
|
+
{
|
|
13374
|
+
"description": "Before the icon tile, such as a back button.",
|
|
13375
|
+
"name": "start"
|
|
13376
|
+
},
|
|
13373
13377
|
{
|
|
13374
13378
|
"description": "The page's controls.",
|
|
13375
13379
|
"name": "actions"
|
|
@@ -13396,7 +13400,7 @@
|
|
|
13396
13400
|
"text": "readonly CSSStyleSheet[]"
|
|
13397
13401
|
},
|
|
13398
13402
|
"static": true,
|
|
13399
|
-
"default": "[ base, css` :host { display: flex; gap: var(--gw-space-3); align-items: flex-start; } .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; } `, ]",
|
|
13400
13404
|
"inheritedFrom": {
|
|
13401
13405
|
"name": "GwElement",
|
|
13402
13406
|
"module": "src/element.ts"
|
|
@@ -13432,6 +13436,18 @@
|
|
|
13432
13436
|
"privacy": "private",
|
|
13433
13437
|
"readonly": true
|
|
13434
13438
|
},
|
|
13439
|
+
{
|
|
13440
|
+
"kind": "field",
|
|
13441
|
+
"name": "#startSlot",
|
|
13442
|
+
"privacy": "private",
|
|
13443
|
+
"readonly": true
|
|
13444
|
+
},
|
|
13445
|
+
{
|
|
13446
|
+
"kind": "field",
|
|
13447
|
+
"name": "#start",
|
|
13448
|
+
"privacy": "private",
|
|
13449
|
+
"readonly": true
|
|
13450
|
+
},
|
|
13435
13451
|
{
|
|
13436
13452
|
"kind": "field",
|
|
13437
13453
|
"name": "#tile",
|
|
@@ -19599,7 +19615,7 @@
|
|
|
19599
19615
|
"declarations": [
|
|
19600
19616
|
{
|
|
19601
19617
|
"kind": "class",
|
|
19602
|
-
"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.",
|
|
19603
19619
|
"name": "GwThemePicker",
|
|
19604
19620
|
"cssParts": [
|
|
19605
19621
|
{
|
|
@@ -19636,7 +19652,7 @@
|
|
|
19636
19652
|
"text": "readonly CSSStyleSheet[]"
|
|
19637
19653
|
},
|
|
19638
19654
|
"static": true,
|
|
19639
|
-
"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; } `, ]",
|
|
19640
19656
|
"inheritedFrom": {
|
|
19641
19657
|
"name": "GwElement",
|
|
19642
19658
|
"module": "src/element.ts"
|
|
@@ -19694,10 +19710,10 @@
|
|
|
19694
19710
|
"kind": "field",
|
|
19695
19711
|
"name": "layout",
|
|
19696
19712
|
"type": {
|
|
19697
|
-
"text": "\"popover\" | \"cycle\" | \"inline\""
|
|
19713
|
+
"text": "\"auto\" | \"popover\" | \"cycle\" | \"inline\""
|
|
19698
19714
|
},
|
|
19699
|
-
"description": "How it shows: a button and popover, a button cycling the modes, or
|
|
19700
|
-
"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\""
|
|
19701
19717
|
},
|
|
19702
19718
|
{
|
|
19703
19719
|
"kind": "field",
|
|
@@ -19799,6 +19815,60 @@
|
|
|
19799
19815
|
],
|
|
19800
19816
|
"description": "Puts the controls and their buttons in the popover, or in place for `inline`."
|
|
19801
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
|
+
},
|
|
19802
19872
|
{
|
|
19803
19873
|
"kind": "method",
|
|
19804
19874
|
"name": "#detail",
|
|
@@ -20163,9 +20233,9 @@
|
|
|
20163
20233
|
"attributes": [
|
|
20164
20234
|
{
|
|
20165
20235
|
"type": {
|
|
20166
|
-
"text": "\"popover\"|\"cycle\"|\"inline\""
|
|
20236
|
+
"text": "\"auto\"|\"popover\"|\"cycle\"|\"inline\""
|
|
20167
20237
|
},
|
|
20168
|
-
"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`.",
|
|
20169
20239
|
"name": "layout"
|
|
20170
20240
|
},
|
|
20171
20241
|
{
|
|
@@ -20224,7 +20294,7 @@
|
|
|
20224
20294
|
},
|
|
20225
20295
|
"tagName": "gw-theme-picker",
|
|
20226
20296
|
"customElement": true,
|
|
20227
|
-
"summary": "
|
|
20297
|
+
"summary": "The page's mode, theme and import, in place or in a popover."
|
|
20228
20298
|
}
|
|
20229
20299
|
],
|
|
20230
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
|
@@ -8,8 +8,9 @@ import { GwElement, css, h } from "./element.js";
|
|
|
8
8
|
import { base, createIcon, setIconPath } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* A page's title, with an optional icon tile and description,
|
|
12
|
-
* controls at the top right in the
|
|
11
|
+
* A page's title, with an optional icon tile and description, a back button
|
|
12
|
+
* or similar in the `start` slot, and its controls at the top right in the
|
|
13
|
+
* `actions` slot.
|
|
13
14
|
*
|
|
14
15
|
* @element gw-page-header
|
|
15
16
|
* @summary A page's title, icon, description and actions.
|
|
@@ -18,6 +19,7 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
18
19
|
* @attr {string} description - A line under the title.
|
|
19
20
|
* @attr {string} icon - SVG path data for the icon tile.
|
|
20
21
|
*
|
|
22
|
+
* @slot start - Before the icon tile, such as a back button.
|
|
21
23
|
* @slot actions - The page's controls.
|
|
22
24
|
*
|
|
23
25
|
* @csspart tile - The icon tile.
|
|
@@ -40,6 +42,17 @@ export class GwPageHeader extends GwElement {
|
|
|
40
42
|
align-items: flex-start;
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
.start {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex: none;
|
|
48
|
+
align-items: center;
|
|
49
|
+
min-height: 3rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.start[hidden] {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
.tile {
|
|
44
57
|
display: grid;
|
|
45
58
|
flex: none;
|
|
@@ -54,7 +67,7 @@ export class GwPageHeader extends GwElement {
|
|
|
54
67
|
|
|
55
68
|
.titles {
|
|
56
69
|
display: flex;
|
|
57
|
-
flex: 1;
|
|
70
|
+
flex: 0 1 auto;
|
|
58
71
|
flex-direction: column;
|
|
59
72
|
gap: var(--gw-space-0-5);
|
|
60
73
|
min-width: 0;
|
|
@@ -74,12 +87,19 @@ export class GwPageHeader extends GwElement {
|
|
|
74
87
|
color: var(--gw-color-text-secondary);
|
|
75
88
|
}
|
|
76
89
|
|
|
90
|
+
/* Takes the room the titles leave, and gives it up before them, down
|
|
91
|
+
to its items' smallest */
|
|
77
92
|
.actions {
|
|
78
93
|
display: flex;
|
|
79
|
-
flex:
|
|
94
|
+
flex: 1 1000 auto;
|
|
80
95
|
gap: var(--gw-space-1);
|
|
81
96
|
align-items: center;
|
|
82
|
-
|
|
97
|
+
justify-content: flex-end;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Centered on the tile when there is one */
|
|
101
|
+
.tile:not([hidden]) ~ .actions {
|
|
102
|
+
min-height: 3rem;
|
|
83
103
|
}
|
|
84
104
|
`,
|
|
85
105
|
];
|
|
@@ -90,6 +110,11 @@ export class GwPageHeader extends GwElement {
|
|
|
90
110
|
|
|
91
111
|
readonly #icon = createIcon("");
|
|
92
112
|
|
|
113
|
+
readonly #startSlot = h("slot", { name: "start" });
|
|
114
|
+
|
|
115
|
+
// Centered on the tile's height, and out of the row while it's empty
|
|
116
|
+
readonly #start = h("div", { class: "start", hidden: true }, this.#startSlot);
|
|
117
|
+
|
|
93
118
|
readonly #tile = h("span", { class: "tile", part: "tile" }, this.#icon);
|
|
94
119
|
|
|
95
120
|
readonly #heading = h("h1", { part: "heading" });
|
|
@@ -104,7 +129,11 @@ export class GwPageHeader extends GwElement {
|
|
|
104
129
|
}
|
|
105
130
|
|
|
106
131
|
protected override create(root: ShadowRoot) {
|
|
132
|
+
this.#startSlot.addEventListener("slotchange", () => {
|
|
133
|
+
this.#start.hidden = this.#startSlot.assignedElements().length === 0;
|
|
134
|
+
});
|
|
107
135
|
root.append(
|
|
136
|
+
this.#start,
|
|
108
137
|
this.#tile,
|
|
109
138
|
h("div", { class: "titles" }, this.#heading, this.#description),
|
|
110
139
|
h("div", { class: "actions" }, h("slot", { name: "actions" })),
|
package/src/gw-segmented.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface Segment {
|
|
|
12
12
|
readonly label: string;
|
|
13
13
|
/** SVG path data for an icon beside, or instead of, the label. */
|
|
14
14
|
readonly icon?: string;
|
|
15
|
+
/** A longer description, shown as the option's tooltip. */
|
|
16
|
+
readonly hint?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -297,7 +299,8 @@ export class GwSegmented extends GwElement {
|
|
|
297
299
|
setAttribute(
|
|
298
300
|
label,
|
|
299
301
|
"title",
|
|
300
|
-
option.
|
|
302
|
+
option.hint ??
|
|
303
|
+
(option.icon && this.labels !== "show" ? option.label : null),
|
|
301
304
|
);
|
|
302
305
|
}
|
|
303
306
|
|
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
|
|