@timmo001/groundwork-ui 0.9.12 → 0.9.14
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 +74 -14
- package/package.json +1 -1
- package/src/gw-app-bar.ts +5 -3
- package/src/gw-dialog.ts +9 -0
- package/src/gw-menu-button.ts +37 -9
- package/src/gw-page-header.ts +5 -3
- package/src/gw-popover.ts +11 -0
- package/src/gw-select.ts +17 -0
- package/src/gw-theme-picker.ts +238 -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"
|
|
@@ -6414,7 +6414,7 @@
|
|
|
6414
6414
|
"text": "readonly CSSStyleSheet[]"
|
|
6415
6415
|
},
|
|
6416
6416
|
"static": true,
|
|
6417
|
-
"default": "[ base, dismissalStyles, css` dialog { width: min(32rem, calc(100vw - 2 * var(--gw-space-3))); padding: 0; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); } dialog::backdrop { background: rgb(0 0 0 / 50%); } /* Fades and lifts in and out, the backdrop with it */ dialog, dialog::backdrop { transition: opacity var(--gw-motion-normal) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } dialog, dialog::backdrop { opacity: 0; } dialog { scale: 0.96; } dialog[open], dialog[open]::backdrop { opacity: 1; } dialog[open] { scale: 1; } @starting-style { dialog[open], dialog[open]::backdrop { opacity: 0; } dialog[open] { scale: 0.96; } } header { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-3) var(--gw-space-3) 0 var(--gw-space-4); } h2 { flex: 1; margin: 0; font-size: var(--gw-font-size-l); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { padding: var(--gw-space-3) var(--gw-space-4); font-size: var(--gw-font-size-s); } footer { display: flex; flex-wrap: wrap; gap: var(--gw-space-1); justify-content: flex-end; padding: 0 var(--gw-space-4) var(--gw-space-4); } dialog > .discard { padding: var(--gw-space-4); } `, ]",
|
|
6417
|
+
"default": "[ base, dismissalStyles, css` dialog { width: min(32rem, calc(100vw - 2 * var(--gw-space-3))); max-height: calc(100dvh - 2 * var(--gw-space-3)); padding: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); } dialog::backdrop { background: rgb(0 0 0 / 50%); } /* Fades and lifts in and out, the backdrop with it */ dialog, dialog::backdrop { transition: opacity var(--gw-motion-normal) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } dialog, dialog::backdrop { opacity: 0; } dialog { scale: 0.96; } dialog[open], dialog[open]::backdrop { opacity: 1; } dialog[open] { /* The heading and actions stay put while a long body scrolls */ display: flex; flex-direction: column; scale: 1; } @starting-style { dialog[open], dialog[open]::backdrop { opacity: 0; } dialog[open] { scale: 0.96; } } header { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-3) var(--gw-space-3) 0 var(--gw-space-4); } h2 { flex: 1; margin: 0; font-size: var(--gw-font-size-l); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { flex: 1 1 auto; min-height: 0; padding: var(--gw-space-3) var(--gw-space-4); overflow-y: auto; overscroll-behavior: contain; font-size: var(--gw-font-size-s); } footer { display: flex; flex-wrap: wrap; gap: var(--gw-space-1); justify-content: flex-end; padding: 0 var(--gw-space-4) var(--gw-space-4); } dialog > .discard { padding: var(--gw-space-4); } `, ]",
|
|
6418
6418
|
"inheritedFrom": {
|
|
6419
6419
|
"name": "GwElement",
|
|
6420
6420
|
"module": "src/element.ts"
|
|
@@ -10441,7 +10441,7 @@
|
|
|
10441
10441
|
"text": "readonly CSSStyleSheet[]"
|
|
10442
10442
|
},
|
|
10443
10443
|
"static": true,
|
|
10444
|
-
"default": "[ base, controlSizes(\"menu-button\"), css` :host { position: relative; display: inline-block; anchor-scope: --gw-menu; } .trigger { --gw-size: var(--gw-menu-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.25rem * var(--gw-scale)); display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; box-sizing: border-box; height: calc(2.5rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-4) * var(--gw-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-size)); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); cursor: pointer; background: var(--gw-color-fill-neutral-resting); border: var(--gw-border-width) solid var(--gw-border-color); border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); anchor-name: --gw-menu; } .trigger:hover { background: var(--gw-color-fill-neutral-hovered); } /* The other appearances draw the trigger as gw-button draws a neutral one, a pill, so it sits in a row of buttons without a seam */ :host([appearance=\"accent\"]) .trigger, :host([appearance=\"outlined\"]) .trigger, :host([appearance=\"plain\"]) .trigger { background: transparent; border: max(var(--gw-border-width), 1px) solid transparent; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing), border-color var(--gw-motion-fast) var(--gw-motion-easing), color var(--gw-motion-fast) var(--gw-motion-easing); } :host([appearance=\"outlined\"]) .trigger { border-color: var(--gw-color-text-secondary); } :host([appearance=\"outlined\"]) .trigger:hover, :host([appearance=\"plain\"]) .trigger:hover { background: color-mix( in oklch, var(--gw-color-text-secondary) 16%, transparent ); } /* A solid fill, with text that flips between black and white by the fill's lightness, as gw-button's accent does */ :host([appearance=\"accent\"]) .trigger { color: oklch( from var(--gw-color-accent) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-color-accent); } :host([appearance=\"accent\"]) .trigger:hover { background: oklch( from var(--gw-color-accent) calc(l + (0.6 - l) * 0.2) c h ); } :host([active]:not([appearance=\"accent\"])) .trigger { color: var(--gw-color-accent); border-color: var(--gw-color-accent); } /* Icon only, square, with the text kept for assistive technology */ :host([labels=\"hide\"]) .trigger.has-icon {
|
|
10444
|
+
"default": "[ base, controlSizes(\"menu-button\"), css` :host { position: relative; display: inline-block; anchor-scope: --gw-menu; } .trigger { --gw-size: var(--gw-menu-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.25rem * var(--gw-scale)); display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; box-sizing: border-box; height: calc(2.5rem * var(--gw-scale)); padding: 0 calc(var(--gw-space-4) * var(--gw-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-size)); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); cursor: pointer; background: var(--gw-color-fill-neutral-resting); border: var(--gw-border-width) solid var(--gw-border-color); border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); anchor-name: --gw-menu; } .trigger:hover { background: var(--gw-color-fill-neutral-hovered); } /* The other appearances draw the trigger as gw-button draws a neutral one, a pill, so it sits in a row of buttons without a seam */ :host([appearance=\"accent\"]) .trigger, :host([appearance=\"outlined\"]) .trigger, :host([appearance=\"plain\"]) .trigger { background: transparent; border: max(var(--gw-border-width), 1px) solid transparent; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing), border-color var(--gw-motion-fast) var(--gw-motion-easing), color var(--gw-motion-fast) var(--gw-motion-easing); } :host([appearance=\"outlined\"]) .trigger { border-color: var(--gw-color-text-secondary); } :host([appearance=\"outlined\"]) .trigger:hover, :host([appearance=\"plain\"]) .trigger:hover { background: color-mix( in oklch, var(--gw-color-text-secondary) 16%, transparent ); } /* A solid fill, with text that flips between black and white by the fill's lightness, as gw-button's accent does */ :host([appearance=\"accent\"]) .trigger { color: oklch( from var(--gw-color-accent) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-color-accent); } :host([appearance=\"accent\"]) .trigger:hover { background: oklch( from var(--gw-color-accent) calc(l + (0.6 - l) * 0.2) c h ); } :host([active]:not([appearance=\"accent\"])) .trigger { color: var(--gw-color-accent); border-color: var(--gw-color-accent); } /* Icon only, square like an icon-only gw-button, such as a ⋮ for more actions, with the text kept for assistive technology */ :host([labels=\"hide\"]) .trigger.has-icon { justify-content: center; min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"hide\"]) .has-icon .chevron { display: none; } :host([labels=\"hide\"]) .has-icon .label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } @media (width < 40rem) { :host([labels=\"auto\"]) .trigger.has-icon { padding: 0 calc(var(--gw-space-2) * var(--gw-size)); } :host([labels=\"auto\"]) .has-icon .label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } } .chevron { width: 0.4rem; height: 0.4rem; margin-inline-start: var(--gw-space-1); border-inline-end: 1.5px solid currentColor; border-block-end: 1.5px solid currentColor; rotate: 45deg; translate: 0 -0.125rem; transition: rotate var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .trigger[aria-expanded=\"true\"] .chevron { rotate: 225deg; translate: 0 0.125rem; } /* Below the button down to the window's edge, or above it when there's more room there, growing to its content and scrolling past that */ [popover] { position-anchor: --gw-menu; position-try-fallbacks: flip-inline; /* The area is the room on that side, so 100% is all of it */ position-area: block-end span-inline-end; inset: auto; box-sizing: border-box; min-width: 15rem; max-width: calc(100vw - 2 * var(--gw-space-2)); max-height: min(24rem, calc(100% - 2 * var(--gw-space-2))); padding: 0; margin: var(--gw-space-1) 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-l); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.25rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } [popover]:popover-open { display: flex; flex-direction: column; opacity: 1; translate: 0 0; } @starting-style { [popover]:popover-open { opacity: 0; translate: 0 -0.25rem; } } /* The items scroll; the actions stay put below them, so \"Add\" is always in reach rather than at the end of a long list */ .items, .actions { display: flex; flex-direction: column; gap: var(--gw-space-0-5); padding: var(--gw-space-1); } .items { flex: 1; min-height: 0; overflow-y: auto; } .actions { border-block-start: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); } .empty { padding: var(--gw-space-3); margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } .item { display: flex; gap: var(--gw-space-3); align-items: center; box-sizing: border-box; min-height: 2.5rem; padding: var(--gw-space-2) var(--gw-space-3); font: inherit; font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); text-align: start; cursor: pointer; background: transparent; border: 0; border-radius: var(--gw-radius-m); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); --gw-icon-size: 1.125rem; } .item:hover, .item:focus-visible { background: var(--gw-color-fill-neutral-hovered); outline: none; } .item:focus-visible { box-shadow: inset 0 0 0 var(--gw-focus-width) var(--gw-color-focus); } .item:disabled { color: var(--gw-color-text-disabled); cursor: not-allowed; background: transparent; } .action { font-weight: var(--gw-font-weight-strong); color: var(--gw-color-accent); } /* Holds the tick's place on every choice, so the labels line up */ .tick { flex: none; width: var(--gw-icon-size); height: var(--gw-icon-size); color: var(--gw-color-accent); } .item[aria-checked=\"false\"] .tick { visibility: hidden; } .separator { flex: none; height: 0; margin: var(--gw-space-0-5) calc(-1 * var(--gw-space-1)); border: 0; border-block-start: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); } .text { display: flex; flex-direction: column; gap: var(--gw-space-0-5); } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } `, ]",
|
|
10445
10445
|
"inheritedFrom": {
|
|
10446
10446
|
"name": "GwElement",
|
|
10447
10447
|
"module": "src/element.ts"
|
|
@@ -11026,7 +11026,7 @@
|
|
|
11026
11026
|
"type": {
|
|
11027
11027
|
"text": "\"show\"|\"hide\"|\"auto\""
|
|
11028
11028
|
},
|
|
11029
|
-
"description": "Whether the trigger's text shows beside its icon: always, never, or not on narrow screens. Defaults to `show`.",
|
|
11029
|
+
"description": "Whether the trigger's text shows beside its icon: always, never (a square icon button without the chevron, such as ⋮ for more actions), or not on narrow screens. Defaults to `show`.",
|
|
11030
11030
|
"name": "labels"
|
|
11031
11031
|
}
|
|
11032
11032
|
],
|
|
@@ -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"
|
|
@@ -13884,7 +13884,7 @@
|
|
|
13884
13884
|
"text": "readonly CSSStyleSheet[]"
|
|
13885
13885
|
},
|
|
13886
13886
|
"static": true,
|
|
13887
|
-
"default": "[ base, dismissalStyles, css` [popover] { position: fixed; inset: unset; box-sizing: border-box; width: min(var(--gw-popover-width, 20rem), calc(100vw - 2 * ${gap}px)); padding: var(--gw-space-4); margin: 0; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 0.96; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } [popover]:popover-open { opacity: 1; scale: 1; } @starting-style { [popover]:popover-open { opacity: 0; scale: 0.96; } } /* Grows from the corner nearest the button */ [data-placement=\"below\"] { transform-origin: top right; } [data-placement=\"above\"] { transform-origin: bottom right; } [data-placement=\"below\"][data-side=\"start\"] { transform-origin: top left; } [data-placement=\"above\"][data-side=\"start\"] { transform-origin: bottom left; } [data-placement=\"center\"] { inset: 0; margin: auto; height: fit-content; } [data-placement=\"center\"]::backdrop { background: rgb(0 0 0 / 40%); } /* A bottom sheet on narrow screens, sliding up from the edge */ [data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: calc( var(--gw-space-4) + env(safe-area-inset-bottom) ); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; } [data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { [data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } [data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } header { display: flex; gap: var(--gw-space-2); align-items: flex-start; margin: calc(var(--gw-space-2) * -1) calc(var(--gw-space-2) * -1) var(--gw-space-2) 0; } header .content { flex: 1; min-width: 0; padding-block-start: var(--gw-space-2); } h2 { margin: 0 0 var(--gw-space-2); font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { font-size: var(--gw-font-size-s); } ::slotted(p) { margin: 0; } footer { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); justify-content: flex-end; margin-block-start: var(--gw-space-4); } `, ]",
|
|
13887
|
+
"default": "[ base, dismissalStyles, css` [popover] { position: fixed; inset: unset; box-sizing: border-box; width: min(var(--gw-popover-width, 20rem), calc(100vw - 2 * ${gap}px)); /* Never past the window, scrolling within it; placed popovers get the room on their side of the button */ max-height: calc(100dvh - 2 * ${gap}px); overflow-y: auto; overscroll-behavior: contain; padding: var(--gw-space-4); margin: 0; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 0.96; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } [popover]:popover-open { opacity: 1; scale: 1; } @starting-style { [popover]:popover-open { opacity: 0; scale: 0.96; } } /* Grows from the corner nearest the button */ [data-placement=\"below\"] { transform-origin: top right; } [data-placement=\"above\"] { transform-origin: bottom right; } [data-placement=\"below\"][data-side=\"start\"] { transform-origin: top left; } [data-placement=\"above\"][data-side=\"start\"] { transform-origin: bottom left; } [data-placement=\"center\"] { inset: 0; margin: auto; height: fit-content; } [data-placement=\"center\"]::backdrop { background: rgb(0 0 0 / 40%); } /* A bottom sheet on narrow screens, sliding up from the edge */ [data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: calc( var(--gw-space-4) + env(safe-area-inset-bottom) ); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; } [data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { [data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } [data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } header { display: flex; gap: var(--gw-space-2); align-items: flex-start; margin: calc(var(--gw-space-2) * -1) calc(var(--gw-space-2) * -1) var(--gw-space-2) 0; } header .content { flex: 1; min-width: 0; padding-block-start: var(--gw-space-2); } h2 { margin: 0 0 var(--gw-space-2); font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { font-size: var(--gw-font-size-s); } ::slotted(p) { margin: 0; } footer { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); justify-content: flex-end; margin-block-start: var(--gw-space-4); } `, ]",
|
|
13888
13888
|
"inheritedFrom": {
|
|
13889
13889
|
"name": "GwElement",
|
|
13890
13890
|
"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. Once the mode and theme are in place and only Reset and\nImport are left, they're a ⋮ menu instead.\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",
|
|
@@ -19784,6 +19784,12 @@
|
|
|
19784
19784
|
"privacy": "private",
|
|
19785
19785
|
"readonly": true
|
|
19786
19786
|
},
|
|
19787
|
+
{
|
|
19788
|
+
"kind": "field",
|
|
19789
|
+
"name": "#menu",
|
|
19790
|
+
"privacy": "private",
|
|
19791
|
+
"readonly": true
|
|
19792
|
+
},
|
|
19787
19793
|
{
|
|
19788
19794
|
"kind": "field",
|
|
19789
19795
|
"name": "#actions",
|
|
@@ -19815,6 +19821,60 @@
|
|
|
19815
19821
|
],
|
|
19816
19822
|
"description": "Puts the controls and their buttons in the popover, or in place for `inline`."
|
|
19817
19823
|
},
|
|
19824
|
+
{
|
|
19825
|
+
"kind": "method",
|
|
19826
|
+
"name": "#arrange",
|
|
19827
|
+
"privacy": "private",
|
|
19828
|
+
"parameters": [
|
|
19829
|
+
{
|
|
19830
|
+
"name": "root",
|
|
19831
|
+
"type": {
|
|
19832
|
+
"text": "ShadowRoot"
|
|
19833
|
+
}
|
|
19834
|
+
}
|
|
19835
|
+
],
|
|
19836
|
+
"description": "With `auto`, moves the mode and theme in place or back into the popover, as `#fits` says."
|
|
19837
|
+
},
|
|
19838
|
+
{
|
|
19839
|
+
"kind": "method",
|
|
19840
|
+
"name": "#fit",
|
|
19841
|
+
"privacy": "private",
|
|
19842
|
+
"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."
|
|
19843
|
+
},
|
|
19844
|
+
{
|
|
19845
|
+
"kind": "field",
|
|
19846
|
+
"name": "#fits",
|
|
19847
|
+
"privacy": "private",
|
|
19848
|
+
"type": {
|
|
19849
|
+
"text": "object"
|
|
19850
|
+
},
|
|
19851
|
+
"default": "{ mode: false, theme: false }"
|
|
19852
|
+
},
|
|
19853
|
+
{
|
|
19854
|
+
"kind": "field",
|
|
19855
|
+
"name": "#fitting",
|
|
19856
|
+
"privacy": "private",
|
|
19857
|
+
"type": {
|
|
19858
|
+
"text": "number"
|
|
19859
|
+
},
|
|
19860
|
+
"default": "0"
|
|
19861
|
+
},
|
|
19862
|
+
{
|
|
19863
|
+
"kind": "field",
|
|
19864
|
+
"name": "#measuring",
|
|
19865
|
+
"privacy": "private",
|
|
19866
|
+
"type": {
|
|
19867
|
+
"text": "boolean"
|
|
19868
|
+
},
|
|
19869
|
+
"default": "false"
|
|
19870
|
+
},
|
|
19871
|
+
{
|
|
19872
|
+
"kind": "field",
|
|
19873
|
+
"name": "#resize",
|
|
19874
|
+
"privacy": "private",
|
|
19875
|
+
"readonly": true,
|
|
19876
|
+
"default": "new ResizeObserver(() => { void this.#fit(); })"
|
|
19877
|
+
},
|
|
19818
19878
|
{
|
|
19819
19879
|
"kind": "method",
|
|
19820
19880
|
"name": "#detail",
|
|
@@ -20179,9 +20239,9 @@
|
|
|
20179
20239
|
"attributes": [
|
|
20180
20240
|
{
|
|
20181
20241
|
"type": {
|
|
20182
|
-
"text": "\"popover\"|\"cycle\"|\"inline\""
|
|
20242
|
+
"text": "\"auto\"|\"popover\"|\"cycle\"|\"inline\""
|
|
20183
20243
|
},
|
|
20184
|
-
"description": "How it shows: a button and popover, a button cycling the modes, or
|
|
20244
|
+
"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
20245
|
"name": "layout"
|
|
20186
20246
|
},
|
|
20187
20247
|
{
|
|
@@ -20240,7 +20300,7 @@
|
|
|
20240
20300
|
},
|
|
20241
20301
|
"tagName": "gw-theme-picker",
|
|
20242
20302
|
"customElement": true,
|
|
20243
|
-
"summary": "
|
|
20303
|
+
"summary": "The page's mode, theme and import, in place or in a popover."
|
|
20244
20304
|
}
|
|
20245
20305
|
],
|
|
20246
20306
|
"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-dialog.ts
CHANGED
|
@@ -50,7 +50,9 @@ export class GwDialog extends GwElement {
|
|
|
50
50
|
css`
|
|
51
51
|
dialog {
|
|
52
52
|
width: min(32rem, calc(100vw - 2 * var(--gw-space-3)));
|
|
53
|
+
max-height: calc(100dvh - 2 * var(--gw-space-3));
|
|
53
54
|
padding: 0;
|
|
55
|
+
overflow: hidden;
|
|
54
56
|
color: var(--gw-color-text-primary);
|
|
55
57
|
background: var(--gw-color-surface-overlay);
|
|
56
58
|
border: var(--gw-border-width) solid var(--gw-color-border-normal);
|
|
@@ -87,6 +89,9 @@ export class GwDialog extends GwElement {
|
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
dialog[open] {
|
|
92
|
+
/* The heading and actions stay put while a long body scrolls */
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-direction: column;
|
|
90
95
|
scale: 1;
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -117,7 +122,11 @@ export class GwDialog extends GwElement {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
.body {
|
|
125
|
+
flex: 1 1 auto;
|
|
126
|
+
min-height: 0;
|
|
120
127
|
padding: var(--gw-space-3) var(--gw-space-4);
|
|
128
|
+
overflow-y: auto;
|
|
129
|
+
overscroll-behavior: contain;
|
|
121
130
|
font-size: var(--gw-font-size-s);
|
|
122
131
|
}
|
|
123
132
|
|
package/src/gw-menu-button.ts
CHANGED
|
@@ -55,7 +55,7 @@ const tick = "M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z";
|
|
|
55
55
|
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The trigger's size, 24 to 56px tall. Defaults to `medium`.
|
|
56
56
|
* @attr {"accent"|"filled"|"outlined"|"plain"} appearance - How the trigger is drawn: `filled` is a neutral fill with a border, and the others match a neutral `gw-button` of that appearance, so it sits in a row of them. Defaults to `filled`.
|
|
57
57
|
* @attr {boolean} active - Marks the trigger as set, such as a grouping that's on, with an accent border and text.
|
|
58
|
-
* @attr {"show"|"hide"|"auto"} labels - Whether the trigger's text shows beside its icon: always, never, or not on narrow screens. Defaults to `show`.
|
|
58
|
+
* @attr {"show"|"hide"|"auto"} labels - Whether the trigger's text shows beside its icon: always, never (a square icon button without the chevron, such as ⋮ for more actions), or not on narrow screens. Defaults to `show`.
|
|
59
59
|
*
|
|
60
60
|
* @csspart trigger - The button that opens the menu.
|
|
61
61
|
*
|
|
@@ -163,9 +163,16 @@ export class GwMenuButton extends GwElement {
|
|
|
163
163
|
border-color: var(--gw-color-accent);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
/* Icon only, square,
|
|
166
|
+
/* Icon only, square like an icon-only gw-button, such as a ⋮ for more
|
|
167
|
+
actions, with the text kept for assistive technology */
|
|
167
168
|
:host([labels="hide"]) .trigger.has-icon {
|
|
168
|
-
|
|
169
|
+
justify-content: center;
|
|
170
|
+
min-width: calc(2.5rem * var(--gw-scale));
|
|
171
|
+
padding: 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:host([labels="hide"]) .has-icon .chevron {
|
|
175
|
+
display: none;
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
:host([labels="hide"]) .has-icon .label {
|
|
@@ -210,17 +217,20 @@ export class GwMenuButton extends GwElement {
|
|
|
210
217
|
translate: 0 0.125rem;
|
|
211
218
|
}
|
|
212
219
|
|
|
220
|
+
/* Below the button down to the window's edge, or above it when there's
|
|
221
|
+
more room there, growing to its content and scrolling past that */
|
|
213
222
|
[popover] {
|
|
214
223
|
position-anchor: --gw-menu;
|
|
215
|
-
position-try-fallbacks: flip-
|
|
224
|
+
position-try-fallbacks: flip-inline;
|
|
225
|
+
/* The area is the room on that side, so 100% is all of it */
|
|
226
|
+
position-area: block-end span-inline-end;
|
|
216
227
|
inset: auto;
|
|
217
|
-
top: anchor(bottom);
|
|
218
|
-
left: anchor(left);
|
|
219
228
|
box-sizing: border-box;
|
|
220
229
|
min-width: 15rem;
|
|
221
|
-
max-
|
|
230
|
+
max-width: calc(100vw - 2 * var(--gw-space-2));
|
|
231
|
+
max-height: min(24rem, calc(100% - 2 * var(--gw-space-2)));
|
|
222
232
|
padding: 0;
|
|
223
|
-
margin: var(--gw-space-1) 0
|
|
233
|
+
margin: var(--gw-space-1) 0;
|
|
224
234
|
overflow: hidden;
|
|
225
235
|
color: var(--gw-color-text-primary);
|
|
226
236
|
background: var(--gw-color-surface-overlay);
|
|
@@ -429,7 +439,25 @@ export class GwMenuButton extends GwElement {
|
|
|
429
439
|
|
|
430
440
|
this.#trigger.setAttribute("aria-expanded", String(expanded));
|
|
431
441
|
|
|
432
|
-
if (expanded)
|
|
442
|
+
if (!expanded) return;
|
|
443
|
+
|
|
444
|
+
// Below the button while the menu fits there or there's more room than
|
|
445
|
+
// above it; the area's height then caps the menu, which scrolls
|
|
446
|
+
const popover = this.#popover;
|
|
447
|
+
const rect = this.#trigger.getBoundingClientRect();
|
|
448
|
+
const below = window.innerHeight - rect.bottom;
|
|
449
|
+
|
|
450
|
+
popover.style.positionArea = "";
|
|
451
|
+
|
|
452
|
+
const items = this.#items;
|
|
453
|
+
|
|
454
|
+
const height =
|
|
455
|
+
popover.offsetHeight - items.clientHeight + items.scrollHeight;
|
|
456
|
+
|
|
457
|
+
const fitsBelow = below >= height + 16 || below >= rect.top;
|
|
458
|
+
|
|
459
|
+
popover.style.positionArea = fitsBelow ? "" : "block-start span-inline-end";
|
|
460
|
+
this.#buttons[0]?.focus({ preventScroll: true });
|
|
433
461
|
};
|
|
434
462
|
|
|
435
463
|
readonly #onKeydown = (event: KeyboardEvent) => {
|
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-popover.ts
CHANGED
|
@@ -64,6 +64,11 @@ export class GwPopover extends GwElement {
|
|
|
64
64
|
inset: unset;
|
|
65
65
|
box-sizing: border-box;
|
|
66
66
|
width: min(var(--gw-popover-width, 20rem), calc(100vw - 2 * ${gap}px));
|
|
67
|
+
/* Never past the window, scrolling within it; placed popovers get
|
|
68
|
+
the room on their side of the button */
|
|
69
|
+
max-height: calc(100dvh - 2 * ${gap}px);
|
|
70
|
+
overflow-y: auto;
|
|
71
|
+
overscroll-behavior: contain;
|
|
67
72
|
padding: var(--gw-space-4);
|
|
68
73
|
margin: 0;
|
|
69
74
|
color: var(--gw-color-text-primary);
|
|
@@ -304,14 +309,20 @@ export class GwPopover extends GwElement {
|
|
|
304
309
|
if (anchor === undefined || placement === "sheet") {
|
|
305
310
|
popover.style.top = "";
|
|
306
311
|
popover.style.left = "";
|
|
312
|
+
popover.style.maxHeight = "";
|
|
307
313
|
popover.dataset.placement = placement;
|
|
308
314
|
|
|
309
315
|
return;
|
|
310
316
|
}
|
|
311
317
|
|
|
318
|
+
popover.style.maxHeight = "";
|
|
319
|
+
|
|
312
320
|
const rect = anchor.getBoundingClientRect();
|
|
313
321
|
const below = window.innerHeight - rect.bottom - gap;
|
|
314
322
|
const fitsBelow = below >= popover.offsetHeight || below >= rect.top;
|
|
323
|
+
const room = fitsBelow ? below - gap : rect.top - 2 * gap;
|
|
324
|
+
|
|
325
|
+
if (popover.offsetHeight > room) popover.style.maxHeight = `${room}px`;
|
|
315
326
|
|
|
316
327
|
// Lined up with the button's edge on its side of the screen, so the
|
|
317
328
|
// popover opens toward the space rather than across the content
|
package/src/gw-select.ts
CHANGED
|
@@ -252,9 +252,26 @@ export class GwSelect extends LightElement {
|
|
|
252
252
|
const width = Math.max(rect.width, 224);
|
|
253
253
|
|
|
254
254
|
popup.style.width = `${Math.min(width, window.innerWidth - 2 * gap)}px`;
|
|
255
|
+
this.#list.style.removeProperty("--gw-option-list-height");
|
|
255
256
|
|
|
256
257
|
const below = window.innerHeight - rect.bottom - gap;
|
|
257
258
|
const fitsBelow = below >= popup.offsetHeight || below >= rect.top;
|
|
259
|
+
const room = fitsBelow ? below - gap : rect.top - 2 * gap;
|
|
260
|
+
|
|
261
|
+
// A window that can't grow past the page, such as an extension's popup,
|
|
262
|
+
// would crop the list, so it scrolls within the room there is
|
|
263
|
+
if (popup.offsetHeight > room) {
|
|
264
|
+
const listbox = this.#list.shadowRoot?.querySelector('[role="listbox"]');
|
|
265
|
+
|
|
266
|
+
const rest =
|
|
267
|
+
popup.offsetHeight -
|
|
268
|
+
(listbox instanceof HTMLElement ? listbox.offsetHeight : 0);
|
|
269
|
+
|
|
270
|
+
this.#list.style.setProperty(
|
|
271
|
+
"--gw-option-list-height",
|
|
272
|
+
`${Math.max(room - rest, 96)}px`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
258
275
|
|
|
259
276
|
popup.dataset.placement = fitsBelow ? "below" : "above";
|
|
260
277
|
popup.style.top = `${fitsBelow ? rect.bottom + gap : Math.max(gap, rect.top - gap - popup.offsetHeight)}px`;
|
package/src/gw-theme-picker.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
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
|
|
6
6
|
*/
|
|
7
7
|
import { GwElement, css, h } from "./element.js";
|
|
8
8
|
import "./gw-button.js";
|
|
9
|
+
import "./gw-menu-button.js";
|
|
9
10
|
import type { OptionItem } from "./gw-option-list.js";
|
|
10
11
|
import "./gw-popover.js";
|
|
11
12
|
import "./gw-segmented.js";
|
|
@@ -17,10 +18,10 @@ import { base } from "./styles.js";
|
|
|
17
18
|
const paletteIcon =
|
|
18
19
|
"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
20
|
|
|
20
|
-
// MDI's sunny, moon and night (a moon with stars)
|
|
21
|
-
// keeps the palette
|
|
21
|
+
// MDI's theme-light-dark, sunny, moon and night (a moon with stars). Cycle's
|
|
22
|
+
// button keeps the palette for auto
|
|
22
23
|
const modeIcons = {
|
|
23
|
-
auto:
|
|
24
|
+
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
25
|
light:
|
|
25
26
|
"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
27
|
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 +36,15 @@ const allModes = [
|
|
|
35
36
|
{ value: "darker", label: "Darker", icon: modeIcons.darker },
|
|
36
37
|
] as const;
|
|
37
38
|
|
|
39
|
+
// MDI's dots-vertical, for the overflow once the mode and theme are in place
|
|
40
|
+
const moreIcon =
|
|
41
|
+
"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";
|
|
42
|
+
|
|
38
43
|
/**
|
|
39
|
-
*
|
|
40
|
-
* (auto, light, dark or Darker),
|
|
41
|
-
* file (a Groundwork theme's `.json`
|
|
44
|
+
* The page's mode, theme and import, in place where there's room and in a
|
|
45
|
+
* popover where there isn't. Offers the mode (auto, light, dark or Darker),
|
|
46
|
+
* the theme, and buttons to import a theme file (a Groundwork theme's `.json`
|
|
47
|
+
* or an Omarchy `colors.toml`) or reset.
|
|
42
48
|
*
|
|
43
49
|
* It only asks: it fires `mode`, `theme`, `import` and `reset`, and the page
|
|
44
50
|
* applies them and sets `mode`, `theme` and `themes` back. `pageTheme` from
|
|
@@ -48,17 +54,24 @@ const allModes = [
|
|
|
48
54
|
* `gallery` swaps the theme select for `gw-theme-gallery`'s cards, which
|
|
49
55
|
* preview each theme in the chosen mode.
|
|
50
56
|
*
|
|
51
|
-
* `layout` sets how it shows. `
|
|
52
|
-
* and
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
+
* `layout` sets how it shows. `auto`, the default, lays the mode out as icons
|
|
58
|
+
* and the theme as a select in place while they fit the room it's given,
|
|
59
|
+
* then moves them into the popover, the theme first, as the room runs out.
|
|
60
|
+
* Reset, Import, the gallery and `status` stay in the popover, opened from a
|
|
61
|
+
* palette button. Once the mode and theme are in place and only Reset and
|
|
62
|
+
* Import are left, they're a ⋮ menu instead.
|
|
63
|
+
* In a flex row, such as `gw-page-header`'s actions, it gives up room before
|
|
64
|
+
* the title does. `popover` keeps everything behind the palette button. `cycle` is one button
|
|
65
|
+
* showing the mode in use, which steps to the next mode on each press, for an
|
|
66
|
+
* app bar. `inline` lays every control out in place, in one wrapping row with
|
|
67
|
+
* Reset and Import, for a footer or a settings page; set `justify-content` on
|
|
68
|
+
* it to align the row. `modes` picks which modes it offers, such as
|
|
69
|
+
* `light dark` without Auto.
|
|
57
70
|
*
|
|
58
71
|
* @element gw-theme-picker
|
|
59
|
-
* @summary
|
|
72
|
+
* @summary The page's mode, theme and import, in place or in a popover.
|
|
60
73
|
*
|
|
61
|
-
* @attr {"popover"|"cycle"|"inline"} layout - How it shows: a button and popover, a button cycling the modes, or
|
|
74
|
+
* @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
75
|
* @attr {string} modes - The modes to offer, in order and separated by spaces. Defaults to `auto light dark darker`.
|
|
63
76
|
* @attr {"auto"|"light"|"dark"|"darker"} mode - The mode in use. Defaults to `auto`.
|
|
64
77
|
* @attr {string} theme - The theme in use, by id.
|
|
@@ -141,6 +154,21 @@ export class GwThemePicker extends GwElement {
|
|
|
141
154
|
gap: var(--gw-space-2);
|
|
142
155
|
align-items: center;
|
|
143
156
|
}
|
|
157
|
+
|
|
158
|
+
/* Asks for the room to have all of it in place, but gives it up down
|
|
159
|
+
to the button before a flex row's other items shrink */
|
|
160
|
+
:host([layout="auto"]) {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex: 0 1000 var(--gw-theme-picker-fit, auto);
|
|
163
|
+
gap: var(--gw-space-1);
|
|
164
|
+
align-items: center;
|
|
165
|
+
min-width: 2.5rem;
|
|
166
|
+
contain: inline-size;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
:host([layout="auto"]) > * {
|
|
170
|
+
flex: none;
|
|
171
|
+
}
|
|
144
172
|
`,
|
|
145
173
|
];
|
|
146
174
|
|
|
@@ -150,8 +178,8 @@ export class GwThemePicker extends GwElement {
|
|
|
150
178
|
declare gallery: boolean;
|
|
151
179
|
declare status: string;
|
|
152
180
|
declare label: string;
|
|
153
|
-
/** How it shows: a button and popover, a button cycling the modes, or
|
|
154
|
-
declare layout: "popover" | "cycle" | "inline";
|
|
181
|
+
/** 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. */
|
|
182
|
+
declare layout: "auto" | "popover" | "cycle" | "inline";
|
|
155
183
|
/** The modes to offer, in order and separated by spaces. */
|
|
156
184
|
declare modes: string;
|
|
157
185
|
|
|
@@ -198,6 +226,18 @@ export class GwThemePicker extends GwElement {
|
|
|
198
226
|
|
|
199
227
|
readonly #popover = h("gw-popover");
|
|
200
228
|
|
|
229
|
+
// Reset and Import on their own, once nothing else is left in the popover
|
|
230
|
+
readonly #menu = h("gw-menu-button", {
|
|
231
|
+
appearance: "plain",
|
|
232
|
+
icon: moreIcon,
|
|
233
|
+
labels: "hide",
|
|
234
|
+
hidden: true,
|
|
235
|
+
items: JSON.stringify([
|
|
236
|
+
{ value: "reset", label: "Reset" },
|
|
237
|
+
{ value: "import", label: "Import a theme file" },
|
|
238
|
+
]),
|
|
239
|
+
});
|
|
240
|
+
|
|
201
241
|
readonly #actions = h("div", { class: "actions", part: "actions" });
|
|
202
242
|
|
|
203
243
|
constructor() {
|
|
@@ -208,7 +248,7 @@ export class GwThemePicker extends GwElement {
|
|
|
208
248
|
this.gallery = false;
|
|
209
249
|
this.status = "";
|
|
210
250
|
this.label = "Theme";
|
|
211
|
-
this.layout = "
|
|
251
|
+
this.layout = "auto";
|
|
212
252
|
this.modes = "auto light dark darker";
|
|
213
253
|
}
|
|
214
254
|
|
|
@@ -251,15 +291,159 @@ export class GwThemePicker extends GwElement {
|
|
|
251
291
|
this.#import.slot = inline ? "" : "actions";
|
|
252
292
|
|
|
253
293
|
if (inline) {
|
|
294
|
+
this.#controls.replaceChildren(
|
|
295
|
+
this.#mode,
|
|
296
|
+
this.#theme,
|
|
297
|
+
this.#gallery,
|
|
298
|
+
this.#status,
|
|
299
|
+
);
|
|
254
300
|
this.#actions.append(this.#reset, this.#import);
|
|
255
301
|
root.append(this.#controls, this.#actions);
|
|
256
302
|
this.#button.remove();
|
|
303
|
+
this.#menu.remove();
|
|
257
304
|
this.#popover.remove();
|
|
258
305
|
} else {
|
|
259
|
-
|
|
260
|
-
|
|
306
|
+
if (this.#controls.parentNode !== this.#popover) {
|
|
307
|
+
this.#popover.append(this.#controls, this.#reset, this.#import);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (this.#button.parentNode !== root) {
|
|
311
|
+
root.prepend(this.#button, this.#menu, this.#popover);
|
|
312
|
+
}
|
|
313
|
+
|
|
261
314
|
this.#actions.remove();
|
|
262
315
|
}
|
|
316
|
+
|
|
317
|
+
this.#arrange(root);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** With `auto`, moves the mode and theme in place or back into the popover, as `#fits` says. */
|
|
321
|
+
#arrange(root: ShadowRoot) {
|
|
322
|
+
// #fit has them in place to measure
|
|
323
|
+
if (this.#measuring) return;
|
|
324
|
+
|
|
325
|
+
const auto = this.layout === "auto";
|
|
326
|
+
const mode = auto && this.#fits.mode;
|
|
327
|
+
const theme = mode && this.#fits.theme;
|
|
328
|
+
|
|
329
|
+
if (mode && this.#mode.parentNode !== root) {
|
|
330
|
+
root.insertBefore(this.#mode, this.#button);
|
|
331
|
+
} else if (!mode && this.#mode.parentNode !== this.#controls) {
|
|
332
|
+
this.#controls.prepend(this.#mode);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (theme && this.#theme.parentNode !== root) {
|
|
336
|
+
root.insertBefore(this.#theme, this.#button);
|
|
337
|
+
} else if (!theme && this.#theme.parentNode !== this.#controls) {
|
|
338
|
+
this.#controls.insertBefore(this.#theme, this.#gallery);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
this.#mode.labels = mode ? "hide" : "show";
|
|
342
|
+
this.#theme.inlineLabel = theme;
|
|
343
|
+
// The popover's heading names it once the mode is out of it
|
|
344
|
+
this.#theme.labelHidden = mode && !theme;
|
|
345
|
+
// The palette while the popover holds the theme; the overflow dots once
|
|
346
|
+
// the theme is in place, as a menu of Reset and Import when they're all
|
|
347
|
+
// that's left
|
|
348
|
+
const more = theme || (mode && this.#theme.hidden && !this.gallery);
|
|
349
|
+
const menu = more && !this.status;
|
|
350
|
+
|
|
351
|
+
this.#button.icon =
|
|
352
|
+
this.layout === "cycle"
|
|
353
|
+
? this.#button.icon
|
|
354
|
+
: more
|
|
355
|
+
? moreIcon
|
|
356
|
+
: paletteIcon;
|
|
357
|
+
|
|
358
|
+
if (menu && this.#menu.hidden) this.#popover.close();
|
|
359
|
+
|
|
360
|
+
this.#button.hidden = menu;
|
|
361
|
+
this.#menu.hidden = !menu;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* With `auto`, measures the mode and theme in place, sizes the host to fit
|
|
366
|
+
* them all, then keeps in place what the room it gets allows, the mode
|
|
367
|
+
* first. The rest go into the popover.
|
|
368
|
+
*/
|
|
369
|
+
async #fit() {
|
|
370
|
+
const root = this.shadowRoot;
|
|
371
|
+
|
|
372
|
+
if (this.layout !== "auto" || root === null || !this.isConnected) return;
|
|
373
|
+
|
|
374
|
+
const run = ++this.#fitting;
|
|
375
|
+
const theme = !this.#theme.hidden;
|
|
376
|
+
|
|
377
|
+
this.#measuring = true;
|
|
378
|
+
|
|
379
|
+
if (this.#mode.parentNode !== root) {
|
|
380
|
+
this.#mode.labels = "hide";
|
|
381
|
+
root.insertBefore(this.#mode, this.#button);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (theme && this.#theme.parentNode !== root) {
|
|
385
|
+
this.#theme.inlineLabel = true;
|
|
386
|
+
this.#theme.labelHidden = false;
|
|
387
|
+
root.insertBefore(this.#theme, this.#button);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Drawn in place before measuring, still before the next paint
|
|
391
|
+
await Promise.all([this.#mode.updateComplete, this.#theme.updateComplete]);
|
|
392
|
+
|
|
393
|
+
if (run !== this.#fitting) return;
|
|
394
|
+
|
|
395
|
+
this.#measuring = false;
|
|
396
|
+
|
|
397
|
+
if (this.layout !== "auto") {
|
|
398
|
+
this.#arrange(root);
|
|
399
|
+
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const gap = Number.parseFloat(getComputedStyle(this).columnGap) || 0;
|
|
404
|
+
|
|
405
|
+
// Whichever of the button and the menu shows; both are square
|
|
406
|
+
const button = Math.max(
|
|
407
|
+
this.#button.getBoundingClientRect().width,
|
|
408
|
+
this.#menu.getBoundingClientRect().width,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const mode = this.#mode.getBoundingClientRect().width + gap;
|
|
412
|
+
const select = theme ? this.#theme.getBoundingClientRect().width + gap : 0;
|
|
413
|
+
|
|
414
|
+
this.style.setProperty(
|
|
415
|
+
"--gw-theme-picker-fit",
|
|
416
|
+
`${Math.ceil(button + mode + select)}px`,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
const room = this.getBoundingClientRect().width + 0.5;
|
|
420
|
+
|
|
421
|
+
this.#fits = {
|
|
422
|
+
mode: room >= button + mode,
|
|
423
|
+
theme: theme && room >= button + mode + select,
|
|
424
|
+
};
|
|
425
|
+
this.#arrange(root);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#fits = { mode: false, theme: false };
|
|
429
|
+
|
|
430
|
+
#fitting = 0;
|
|
431
|
+
|
|
432
|
+
#measuring = false;
|
|
433
|
+
|
|
434
|
+
readonly #resize = new ResizeObserver(() => {
|
|
435
|
+
void this.#fit();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
override connectedCallback() {
|
|
439
|
+
super.connectedCallback();
|
|
440
|
+
this.#resize.observe(this);
|
|
441
|
+
void document.fonts.ready.then(() => this.#fit());
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
override disconnectedCallback() {
|
|
445
|
+
super.disconnectedCallback();
|
|
446
|
+
this.#resize.disconnect();
|
|
263
447
|
}
|
|
264
448
|
|
|
265
449
|
#detail<Detail>(type: string, detail: Detail) {
|
|
@@ -291,6 +475,17 @@ export class GwThemePicker extends GwElement {
|
|
|
291
475
|
this.#reset.addEventListener("click", () => {
|
|
292
476
|
this.dispatchEvent(new Event("reset", { bubbles: true, composed: true }));
|
|
293
477
|
});
|
|
478
|
+
this.#menu.addEventListener("select", (event) => {
|
|
479
|
+
event.stopPropagation();
|
|
480
|
+
|
|
481
|
+
if (event instanceof CustomEvent && event.detail.value === "import") {
|
|
482
|
+
this.#file.click();
|
|
483
|
+
} else {
|
|
484
|
+
this.dispatchEvent(
|
|
485
|
+
new Event("reset", { bubbles: true, composed: true }),
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
294
489
|
this.#file.addEventListener("change", async () => {
|
|
295
490
|
const file = this.#file.files?.[0];
|
|
296
491
|
|
|
@@ -320,16 +515,23 @@ export class GwThemePicker extends GwElement {
|
|
|
320
515
|
|
|
321
516
|
this.#button.label = name;
|
|
322
517
|
this.#button.textContent = name;
|
|
323
|
-
this.#button.icon =
|
|
518
|
+
this.#button.icon =
|
|
519
|
+
current === undefined || current.value === "auto"
|
|
520
|
+
? paletteIcon
|
|
521
|
+
: current.icon;
|
|
324
522
|
} else {
|
|
325
523
|
this.#button.label = this.label;
|
|
326
524
|
this.#button.textContent = this.label;
|
|
327
|
-
this.#button.icon = paletteIcon;
|
|
328
525
|
}
|
|
329
526
|
|
|
330
527
|
this.#popover.heading = this.label;
|
|
528
|
+
this.#menu.label = this.label;
|
|
331
529
|
this.#mode.label = "Mode";
|
|
332
|
-
this.#mode.options = offered.map(({ value, label }) => ({
|
|
530
|
+
this.#mode.options = offered.map(({ value, label, icon }) => ({
|
|
531
|
+
value,
|
|
532
|
+
label,
|
|
533
|
+
icon,
|
|
534
|
+
}));
|
|
333
535
|
this.#mode.value = this.mode;
|
|
334
536
|
this.#theme.options = Array.isArray(this.themes) ? this.themes : [];
|
|
335
537
|
this.#theme.value = this.theme;
|
|
@@ -341,6 +543,19 @@ export class GwThemePicker extends GwElement {
|
|
|
341
543
|
this.#gallery.mode = this.mode === "auto" ? "" : this.mode;
|
|
342
544
|
this.#status.textContent = this.status;
|
|
343
545
|
this.#status.hidden = !this.status;
|
|
546
|
+
|
|
547
|
+
if (root === null) return;
|
|
548
|
+
|
|
549
|
+
this.#arrange(root);
|
|
550
|
+
|
|
551
|
+
// Measures again for the select's new value or the new set of controls
|
|
552
|
+
if (
|
|
553
|
+
["layout", "modes", "theme", "themes", "gallery"].some((name) =>
|
|
554
|
+
changed.has(name),
|
|
555
|
+
)
|
|
556
|
+
) {
|
|
557
|
+
void this.#fit();
|
|
558
|
+
}
|
|
344
559
|
}
|
|
345
560
|
}
|
|
346
561
|
|