@timmo001/groundwork-ui 0.9.13 → 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.
@@ -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 { padding: 0 calc(var(--gw-space-2) * var(--gw-size)); } :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; } [popover] { position-anchor: --gw-menu; position-try-fallbacks: flip-block, flip-inline; inset: auto; top: anchor(bottom); left: anchor(left); box-sizing: border-box; min-width: 15rem; max-height: min(24rem, 60vh); padding: 0; margin: var(--gw-space-1) 0 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); } `, ]",
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"
@@ -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, or from a more button once the mode and theme are out.\nIn a flex row, such as `gw-page-header`'s actions, it gives up room before\nthe title does. `popover` keeps everything behind the palette button. `cycle` is one button\nshowing the mode in use, which steps to the next mode on each press, for an\napp bar. `inline` lays every control out in place, in one wrapping row with\nReset and Import, for a footer or a settings page; set `justify-content` on\nit to align the row. `modes` picks which modes it offers, such as\n`light dark` without Auto.",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.13",
3
+ "version": "0.9.14",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
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
 
@@ -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, with the text kept for assistive technology */
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
- padding: 0 calc(var(--gw-space-2) * var(--gw-size));
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-block, flip-inline;
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-height: min(24rem, 60vh);
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 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) this.#buttons[0]?.focus();
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`;
@@ -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, or from a more button once the mode and theme are out.
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
- // once only Reset and Import are left in it
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
- : theme || (mode && this.#theme.hidden && !this.gallery)
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
- const button = this.#button.getBoundingClientRect().width;
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,