@timmo001/groundwork-ui 0.9.13 → 0.9.15
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 +13 -7
- package/package.json +1 -1
- package/src/element.ts +39 -0
- package/src/gw-dialog.ts +9 -0
- package/src/gw-menu-button.ts +37 -9
- package/src/gw-popover.ts +11 -0
- package/src/gw-select.ts +17 -0
- package/src/gw-sheet.ts +0 -1
- package/src/gw-theme-picker.ts +48 -6
- package/src/styles.ts +99 -0
package/custom-elements.json
CHANGED
|
@@ -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
|
],
|
|
@@ -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"
|
|
@@ -16675,7 +16675,7 @@
|
|
|
16675
16675
|
"text": "readonly CSSStyleSheet[]"
|
|
16676
16676
|
},
|
|
16677
16677
|
"static": true,
|
|
16678
|
-
"default": "[ base, css` [popover] { position: fixed; box-sizing: border-box; flex-direction: column; padding: 0; margin: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: 0 solid var(--gw-color-border-normal); box-shadow: var(--gw-shadow-l); transition: 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; translate: 0 0; } /* The page dims as the sheet slides in, and back as it leaves */ [popover]::backdrop { background: rgb(0 0 0 / 0%); transition: background-color 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::backdrop { background: rgb(0 0 0 / 40%); } @starting-style { [popover]:popover-open::backdrop { background: rgb(0 0 0 / 0%); } } /* Full height from the start or end edge */ [data-side=\"start\"], [data-side=\"end\"] { inset: 0 auto; width: min(var(--gw-sheet-width, 20rem), 100vw); height: 100%; max-height: none; } [data-side=\"start\"] { inset-inline-start: 0; border-inline-end-width: max(var(--gw-border-width), 1px); translate: -100% 0; } [data-side=\"end\"] { inset-inline-end: 0; border-inline-start-width: max(var(--gw-border-width), 1px); translate: 100% 0; } :host(:dir(rtl)) [data-side=\"start\"] { translate: 100% 0; } :host(:dir(rtl)) [data-side=\"end\"] { translate: -100% 0; } /* Up from the bottom, with room for the phone's home bar */ [data-side=\"bottom\"] { inset: auto 0 0; width: 100%; max-width: none; max-height: var(--gw-sheet-height, 85vh); padding-block-end: env(safe-area-inset-bottom); border-block-start-width: max(var(--gw-border-width), 1px); border-start-start-radius: var(--gw-radius-panel); border-start-end-radius: var(--gw-radius-panel); translate: 0 100%; } @starting-style { [data-side=\"start\"]:popover-open { translate: -100% 0; } [data-side=\"end\"]:popover-open { translate: 100% 0; } [data-side=\"bottom\"]:popover-open { translate: 0 100%; } } header { display: flex; flex: none; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-2) var(--gw-space-3); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } h2 { flex: 1; min-width: 0; margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-3); min-height: 0; padding: var(--gw-space-4); /* It scrolls down only, so content reaching into its padding never makes it scroll sideways */ overflow: hidden auto; overscroll-behavior: contain;
|
|
16678
|
+
"default": "[ base, css` [popover] { position: fixed; box-sizing: border-box; flex-direction: column; padding: 0; margin: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: 0 solid var(--gw-color-border-normal); box-shadow: var(--gw-shadow-l); transition: 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; translate: 0 0; } /* The page dims as the sheet slides in, and back as it leaves */ [popover]::backdrop { background: rgb(0 0 0 / 0%); transition: background-color 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::backdrop { background: rgb(0 0 0 / 40%); } @starting-style { [popover]:popover-open::backdrop { background: rgb(0 0 0 / 0%); } } /* Full height from the start or end edge */ [data-side=\"start\"], [data-side=\"end\"] { inset: 0 auto; width: min(var(--gw-sheet-width, 20rem), 100vw); height: 100%; max-height: none; } [data-side=\"start\"] { inset-inline-start: 0; border-inline-end-width: max(var(--gw-border-width), 1px); translate: -100% 0; } [data-side=\"end\"] { inset-inline-end: 0; border-inline-start-width: max(var(--gw-border-width), 1px); translate: 100% 0; } :host(:dir(rtl)) [data-side=\"start\"] { translate: 100% 0; } :host(:dir(rtl)) [data-side=\"end\"] { translate: -100% 0; } /* Up from the bottom, with room for the phone's home bar */ [data-side=\"bottom\"] { inset: auto 0 0; width: 100%; max-width: none; max-height: var(--gw-sheet-height, 85vh); padding-block-end: env(safe-area-inset-bottom); border-block-start-width: max(var(--gw-border-width), 1px); border-start-start-radius: var(--gw-radius-panel); border-start-end-radius: var(--gw-radius-panel); translate: 0 100%; } @starting-style { [data-side=\"start\"]:popover-open { translate: -100% 0; } [data-side=\"end\"]:popover-open { translate: 100% 0; } [data-side=\"bottom\"]:popover-open { translate: 0 100%; } } header { display: flex; flex: none; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-2) var(--gw-space-3); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } h2 { flex: 1; min-width: 0; margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-3); min-height: 0; padding: var(--gw-space-4); /* It scrolls down only, so content reaching into its padding never makes it scroll sideways */ overflow: hidden auto; overscroll-behavior: contain; } `, ]",
|
|
16679
16679
|
"inheritedFrom": {
|
|
16680
16680
|
"name": "GwElement",
|
|
16681
16681
|
"module": "src/element.ts"
|
|
@@ -19615,7 +19615,7 @@
|
|
|
19615
19615
|
"declarations": [
|
|
19616
19616
|
{
|
|
19617
19617
|
"kind": "class",
|
|
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
|
|
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
|
{
|
|
@@ -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",
|
|
@@ -22853,7 +22859,7 @@
|
|
|
22853
22859
|
{
|
|
22854
22860
|
"kind": "variable",
|
|
22855
22861
|
"name": "base",
|
|
22856
|
-
"default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } :host([hidden]), [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } /* :host too, since * in a shadow root doesn't match it, and the skeletons pulse on their host */ @media (prefers-reduced-motion: reduce) { :host, *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
|
|
22862
|
+
"default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } /* The page's small scrollbars, as the themes set them, for scrolling parts inside components, which the page's rules can't reach. Each scroller follows its own scroll for the thumb's square ends */ @supports not selector(::-webkit-scrollbar) { * { scrollbar-width: thin; } } * { animation: gw-scrollbar-edges-y linear both, gw-scrollbar-edges-x linear both; animation-timeline: scroll(self y), scroll(self x); } @keyframes gw-scrollbar-edges-y { 0% { --gw-scrollbar-top: 0; } 1% { --gw-scrollbar-top: 1; } 99% { --gw-scrollbar-bottom: 1; } 100% { --gw-scrollbar-bottom: 0; } } @keyframes gw-scrollbar-edges-x { 0% { --gw-scrollbar-left: 0; } 1% { --gw-scrollbar-left: 1; } 99% { --gw-scrollbar-right: 1; } 100% { --gw-scrollbar-right: 0; } } ::-webkit-scrollbar { width: var(--gw-scrollbar-size, 0.4rem); height: var(--gw-scrollbar-size, 0.4rem); background: var(--gw-scrollbar-track, transparent); } ::-webkit-scrollbar-thumb { background: var( --gw-scrollbar-thumb, color-mix( in oklab, var(--gw-color-text-secondary, currentColor) 45%, transparent ) ); border-radius: calc( var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1) ) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1)) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1)) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1)); } ::-webkit-scrollbar-thumb:horizontal { border-radius: calc( var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1) ) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1)) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1)) calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1)); } ::-webkit-scrollbar-thumb:hover { background: var( --gw-scrollbar-thumb, color-mix( in oklab, var(--gw-color-text-secondary, currentColor) 70%, transparent ) ); } ::-webkit-scrollbar-corner { background: transparent; } :host([hidden]), [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } /* :host too, since * in a shadow root doesn't match it, and the skeletons pulse on their host */ @media (prefers-reduced-motion: reduce) { :host, *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
|
|
22857
22863
|
"description": "What every component shares: box sizing, the focus ring, icons and the\ntones that set `--gw-tone` from a `tone` attribute."
|
|
22858
22864
|
},
|
|
22859
22865
|
{
|
package/package.json
CHANGED
package/src/element.ts
CHANGED
|
@@ -136,6 +136,43 @@ const toAttribute = (value: PropertyValue, type: PropertyOptions["type"]) => {
|
|
|
136
136
|
const attributeName = (name: string, options: PropertyOptions) =>
|
|
137
137
|
options.attribute === undefined ? name.toLowerCase() : options.attribute;
|
|
138
138
|
|
|
139
|
+
let followingPageScroll = false;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Sets `--gw-scrollbar-top`, `-bottom`, `-left` and `-right` on the root as
|
|
143
|
+
* the page scrolls, 0 against that end and 1 elsewhere, so the window's
|
|
144
|
+
* scrollbar thumb squares off at the ends like the others. The window's
|
|
145
|
+
* scrollbar doesn't repaint for the scroll-driven animation the themes give
|
|
146
|
+
* other scrollers.
|
|
147
|
+
*/
|
|
148
|
+
const followPageScroll = () => {
|
|
149
|
+
if (followingPageScroll) return;
|
|
150
|
+
|
|
151
|
+
followingPageScroll = true;
|
|
152
|
+
|
|
153
|
+
const root = document.documentElement;
|
|
154
|
+
|
|
155
|
+
const set = (edge: string, against: boolean) => {
|
|
156
|
+
const value = against ? "0" : "1";
|
|
157
|
+
|
|
158
|
+
if (root.style.getPropertyValue(`--gw-scrollbar-${edge}`) !== value)
|
|
159
|
+
root.style.setProperty(`--gw-scrollbar-${edge}`, value);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const update = () => {
|
|
163
|
+
const page = document.scrollingElement ?? root;
|
|
164
|
+
|
|
165
|
+
set("top", page.scrollTop <= 0);
|
|
166
|
+
set("bottom", page.scrollTop + page.clientHeight >= page.scrollHeight - 1);
|
|
167
|
+
set("left", page.scrollLeft <= 0);
|
|
168
|
+
set("right", page.scrollLeft + page.clientWidth >= page.scrollWidth - 1);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
update();
|
|
172
|
+
addEventListener("scroll", update, { passive: true });
|
|
173
|
+
new ResizeObserver(update).observe(root);
|
|
174
|
+
};
|
|
175
|
+
|
|
139
176
|
/**
|
|
140
177
|
* The base for every `gw-` element, with no framework underneath.
|
|
141
178
|
*
|
|
@@ -182,6 +219,8 @@ export class GwElement extends HTMLElement {
|
|
|
182
219
|
}
|
|
183
220
|
|
|
184
221
|
if (!customElements.get(tag)) customElements.define(tag, this);
|
|
222
|
+
|
|
223
|
+
followPageScroll();
|
|
185
224
|
}
|
|
186
225
|
|
|
187
226
|
readonly #values = new Map<string, PropertyValue>();
|
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-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-sheet.ts
CHANGED
package/src/gw-theme-picker.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
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";
|
|
@@ -57,7 +58,8 @@ const moreIcon =
|
|
|
57
58
|
* and the theme as a select in place while they fit the room it's given,
|
|
58
59
|
* then moves them into the popover, the theme first, as the room runs out.
|
|
59
60
|
* Reset, Import, the gallery and `status` stay in the popover, opened from a
|
|
60
|
-
* palette button
|
|
61
|
+
* palette button. Once the mode and theme are in place and only Reset and
|
|
62
|
+
* Import are left, they're a ⋮ menu instead.
|
|
61
63
|
* In a flex row, such as `gw-page-header`'s actions, it gives up room before
|
|
62
64
|
* the title does. `popover` keeps everything behind the palette button. `cycle` is one button
|
|
63
65
|
* showing the mode in use, which steps to the next mode on each press, for an
|
|
@@ -224,6 +226,18 @@ export class GwThemePicker extends GwElement {
|
|
|
224
226
|
|
|
225
227
|
readonly #popover = h("gw-popover");
|
|
226
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
|
+
|
|
227
241
|
readonly #actions = h("div", { class: "actions", part: "actions" });
|
|
228
242
|
|
|
229
243
|
constructor() {
|
|
@@ -286,6 +300,7 @@ export class GwThemePicker extends GwElement {
|
|
|
286
300
|
this.#actions.append(this.#reset, this.#import);
|
|
287
301
|
root.append(this.#controls, this.#actions);
|
|
288
302
|
this.#button.remove();
|
|
303
|
+
this.#menu.remove();
|
|
289
304
|
this.#popover.remove();
|
|
290
305
|
} else {
|
|
291
306
|
if (this.#controls.parentNode !== this.#popover) {
|
|
@@ -293,7 +308,7 @@ export class GwThemePicker extends GwElement {
|
|
|
293
308
|
}
|
|
294
309
|
|
|
295
310
|
if (this.#button.parentNode !== root) {
|
|
296
|
-
root.prepend(this.#button, this.#popover);
|
|
311
|
+
root.prepend(this.#button, this.#menu, this.#popover);
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
this.#actions.remove();
|
|
@@ -327,14 +342,23 @@ export class GwThemePicker extends GwElement {
|
|
|
327
342
|
this.#theme.inlineLabel = theme;
|
|
328
343
|
// The popover's heading names it once the mode is out of it
|
|
329
344
|
this.#theme.labelHidden = mode && !theme;
|
|
330
|
-
// The palette while the popover holds the theme; the overflow dots
|
|
331
|
-
//
|
|
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
|
+
|
|
332
351
|
this.#button.icon =
|
|
333
352
|
this.layout === "cycle"
|
|
334
353
|
? this.#button.icon
|
|
335
|
-
:
|
|
354
|
+
: more
|
|
336
355
|
? moreIcon
|
|
337
356
|
: paletteIcon;
|
|
357
|
+
|
|
358
|
+
if (menu && this.#menu.hidden) this.#popover.close();
|
|
359
|
+
|
|
360
|
+
this.#button.hidden = menu;
|
|
361
|
+
this.#menu.hidden = !menu;
|
|
338
362
|
}
|
|
339
363
|
|
|
340
364
|
/**
|
|
@@ -377,7 +401,13 @@ export class GwThemePicker extends GwElement {
|
|
|
377
401
|
}
|
|
378
402
|
|
|
379
403
|
const gap = Number.parseFloat(getComputedStyle(this).columnGap) || 0;
|
|
380
|
-
|
|
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
|
+
|
|
381
411
|
const mode = this.#mode.getBoundingClientRect().width + gap;
|
|
382
412
|
const select = theme ? this.#theme.getBoundingClientRect().width + gap : 0;
|
|
383
413
|
|
|
@@ -445,6 +475,17 @@ export class GwThemePicker extends GwElement {
|
|
|
445
475
|
this.#reset.addEventListener("click", () => {
|
|
446
476
|
this.dispatchEvent(new Event("reset", { bubbles: true, composed: true }));
|
|
447
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
|
+
});
|
|
448
489
|
this.#file.addEventListener("change", async () => {
|
|
449
490
|
const file = this.#file.files?.[0];
|
|
450
491
|
|
|
@@ -484,6 +525,7 @@ export class GwThemePicker extends GwElement {
|
|
|
484
525
|
}
|
|
485
526
|
|
|
486
527
|
this.#popover.heading = this.label;
|
|
528
|
+
this.#menu.label = this.label;
|
|
487
529
|
this.#mode.label = "Mode";
|
|
488
530
|
this.#mode.options = offered.map(({ value, label, icon }) => ({
|
|
489
531
|
value,
|
package/src/styles.ts
CHANGED
|
@@ -48,6 +48,105 @@ export const base = css`
|
|
|
48
48
|
box-sizing: border-box;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/* The page's small scrollbars, as the themes set them, for scrolling parts
|
|
52
|
+
inside components, which the page's rules can't reach. Each scroller
|
|
53
|
+
follows its own scroll for the thumb's square ends */
|
|
54
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
55
|
+
* {
|
|
56
|
+
scrollbar-width: thin;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
* {
|
|
61
|
+
animation:
|
|
62
|
+
gw-scrollbar-edges-y linear both,
|
|
63
|
+
gw-scrollbar-edges-x linear both;
|
|
64
|
+
animation-timeline: scroll(self y), scroll(self x);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@keyframes gw-scrollbar-edges-y {
|
|
68
|
+
0% {
|
|
69
|
+
--gw-scrollbar-top: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
1% {
|
|
73
|
+
--gw-scrollbar-top: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
99% {
|
|
77
|
+
--gw-scrollbar-bottom: 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
100% {
|
|
81
|
+
--gw-scrollbar-bottom: 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@keyframes gw-scrollbar-edges-x {
|
|
86
|
+
0% {
|
|
87
|
+
--gw-scrollbar-left: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
1% {
|
|
91
|
+
--gw-scrollbar-left: 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
99% {
|
|
95
|
+
--gw-scrollbar-right: 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
100% {
|
|
99
|
+
--gw-scrollbar-right: 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
::-webkit-scrollbar {
|
|
104
|
+
width: var(--gw-scrollbar-size, 0.4rem);
|
|
105
|
+
height: var(--gw-scrollbar-size, 0.4rem);
|
|
106
|
+
background: var(--gw-scrollbar-track, transparent);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
::-webkit-scrollbar-thumb {
|
|
110
|
+
background: var(
|
|
111
|
+
--gw-scrollbar-thumb,
|
|
112
|
+
color-mix(
|
|
113
|
+
in oklab,
|
|
114
|
+
var(--gw-color-text-secondary, currentColor) 45%,
|
|
115
|
+
transparent
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
border-radius: calc(
|
|
119
|
+
var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1)
|
|
120
|
+
)
|
|
121
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1))
|
|
122
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1))
|
|
123
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
::-webkit-scrollbar-thumb:horizontal {
|
|
127
|
+
border-radius: calc(
|
|
128
|
+
var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1)
|
|
129
|
+
)
|
|
130
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
131
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
132
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
::-webkit-scrollbar-thumb:hover {
|
|
136
|
+
background: var(
|
|
137
|
+
--gw-scrollbar-thumb,
|
|
138
|
+
color-mix(
|
|
139
|
+
in oklab,
|
|
140
|
+
var(--gw-color-text-secondary, currentColor) 70%,
|
|
141
|
+
transparent
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
::-webkit-scrollbar-corner {
|
|
147
|
+
background: transparent;
|
|
148
|
+
}
|
|
149
|
+
|
|
51
150
|
:host([hidden]),
|
|
52
151
|
[hidden] {
|
|
53
152
|
display: none !important;
|