@timmo001/groundwork-ui 0.9.29 → 0.9.30

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.
@@ -660,7 +660,7 @@
660
660
  {
661
661
  "kind": "variable",
662
662
  "name": "fieldStyles",
663
- "default": "css` :where(gw-input, gw-select, gw-switch, gw-checkbox) { display: flex; flex-direction: column; gap: var(--gw-space-2); font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } :where(gw-input, gw-select) .gw-field-heading { display: flex; gap: var(--gw-space-1); align-items: center; } :where(gw-input, gw-select) .gw-field-heading > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } /* The (i) button by the label that opens a field's longer help */ :where(gw-input, gw-select) .gw-info { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } :where(gw-input, gw-select) .gw-info:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } :where(gw-input, gw-select) .gw-info:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) .gw-info[hidden] { display: none; } :where(gw-input, gw-select) .gw-info > .icon { width: 1rem; height: 1rem; fill: currentColor; } /* Sizes run xs to xl through --gw-field-scale, around 1 for medium, and the control's height follows the theme's density too */ :where(gw-input, gw-select)[size=\"xs\"] { --gw-field-scale: 0.6; } :where(gw-input, gw-select)[size=\"small\"] { --gw-field-scale: 0.8; } :where(gw-input, gw-select)[size=\"large\"] { --gw-field-scale: 1.2; } :where(gw-input, gw-select)[size=\"xl\"] { --gw-field-scale: 1.4; } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger) { --gw-field-size: var(--gw-field-scale, 1); --gw-icon-size: calc( 1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); width: 100%; min-height: calc( 2.5rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); padding: 0 calc(var(--gw-space-3) * var(--gw-field-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-field-size)); color: var(--gw-color-text-strong); background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-control); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } /* An icon inside the field, before the text, with the text moved clear of it */ gw-input .gw-input-control { position: relative; display: flex; align-items: center; } gw-input .gw-input-control > .icon { position: absolute; inset-inline-start: calc(var(--gw-space-3) * var(--gw-field-scale, 1)); width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); color: var(--gw-color-text-secondary); pointer-events: none; fill: currentColor; } gw-input .gw-input-control > .icon[hidden] { display: none; } gw-input .gw-input-control > .icon:not([hidden]) + input { padding-inline-start: calc( (var(--gw-space-3) * 2 + 1.125rem) * var(--gw-field-scale, 1) ); } /* A textarea keeps the field's padding on every side and grows by its corner, never sideways out of the layout */ gw-input textarea { display: block; padding-block: calc(var(--gw-space-2) * var(--gw-field-size)); line-height: 1.5; resize: vertical; } gw-input[monospace] :where(input, textarea) { font-family: var(--gw-font-mono); } gw-input .gw-optional { font-weight: var(--gw-font-weight-regular); color: var(--gw-color-text-secondary); } gw-input .gw-optional[hidden] { display: none; } /* The show and hide button sits inside the end of a password field, with the text kept clear of it */ gw-input .gw-reveal { position: absolute; inset-inline-end: calc(var(--gw-space-1) * var(--gw-field-scale, 1)); display: grid; place-items: center; width: calc(2rem * var(--gw-field-scale, 1)); height: calc(2rem * var(--gw-field-scale, 1)); padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-control); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-input .gw-reveal:hover:not(:disabled) { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-input .gw-reveal:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-input .gw-reveal:disabled { cursor: not-allowed; opacity: 0.5; } gw-input .gw-reveal[hidden] { display: none; } gw-input .gw-reveal > .icon { width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); fill: currentColor; } gw-input .gw-input-control:has(> .gw-reveal:not([hidden])) > input { padding-inline-end: calc( (var(--gw-space-1) + 2rem + var(--gw-space-1)) * var(--gw-field-scale, 1) ); } /* The trigger shows the chosen option, with its icon, and a chevron with room around it that turns over while the list is open */ gw-select .gw-select-trigger { display: flex; gap: calc(var(--gw-space-2) * var(--gw-field-size)); align-items: center; padding-inline-end: calc(var(--gw-space-3) * var(--gw-field-size)); text-align: start; cursor: pointer; } gw-select .gw-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* With an inline label the select sits among toolbar controls, so it's a pill that fits its content, like the toggles and buttons beside it */ gw-select[inline-label] { display: inline-flex; } gw-select[inline-label] .gw-select-trigger { width: auto; padding-inline: calc(var(--gw-space-4) * var(--gw-field-size)) calc(var(--gw-space-3) * var(--gw-field-size)); border-radius: var(--gw-radius-pill); } /* An inline label reads as a quiet prefix to the value */ gw-select .gw-select-inline-label { flex: none; color: var(--gw-color-text-secondary); } gw-select .gw-select-inline-label[hidden] { display: none; } gw-select .gw-select-value[data-placeholder] { color: var(--gw-color-text-secondary); } gw-select .gw-select-trigger > .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } gw-select .gw-select-trigger > .icon:last-child { color: var(--gw-color-text-secondary); transition: rotate var(--gw-motion-normal) var(--gw-motion-easing); } gw-select[open] .gw-select-trigger > .icon:last-child { rotate: 180deg; } gw-select .gw-select-trigger > .icon[hidden] { display: none; } /* The list opens under the trigger, growing from it, or as a bottom sheet sliding up on phones */ gw-select .gw-select-popup { position: fixed; inset: unset; box-sizing: border-box; max-width: calc(100vw - 12px); padding: 0; margin: 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-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 1 0.96; transform-origin: top; 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; } gw-select .gw-select-popup[data-placement=\"above\"] { transform-origin: bottom; } gw-select .gw-select-popup:popover-open { opacity: 1; scale: 1; } @starting-style { gw-select .gw-select-popup:popover-open { opacity: 0; scale: 1 0.96; } } gw-select .gw-select-popup[data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: env(safe-area-inset-bottom); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; --gw-option-list-height: 60vh; } gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } gw-select .gw-select-popup[data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } /* With multiple, a full-width Clear under the list empties the choice */ gw-select .gw-select-clear { display: block; width: 100%; min-height: 2.5rem; padding: 0 var(--gw-space-3); font: inherit; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-block-start: var(--gw-border-width) solid var(--gw-color-border-quiet); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-select .gw-select-clear:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-select .gw-select-clear:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-select .gw-select-clear[hidden] { display: none; } :where(gw-input) .gw-input-control:focus-within > .icon { color: var(--gw-color-text-strong); } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger):hover { border-color: var(--gw-color-border-loud); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :where(input, textarea, .gw-select-trigger):focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) [aria-invalid=\"true\"] { border-color: var(--gw-color-border-danger); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :disabled { color: var(--gw-color-text-disabled); cursor: not-allowed; } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-error { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-danger); } :where(gw-switch, gw-checkbox) > label { display: inline-flex; gap: var(--gw-space-3); align-items: center; font-size: var(--gw-font-size-s); cursor: pointer; } :where(gw-input, gw-switch, gw-checkbox, gw-select) .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } gw-checkbox input { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: var(--gw-color-fill-accent-resting); } gw-switch input { position: relative; flex: none; width: 2.5rem; height: 1.5rem; margin: 0; cursor: pointer; appearance: none; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-loud); border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input::after { position: absolute; inset-block-start: 0.1875rem; inset-inline-start: 0.1875rem; width: 1rem; height: 1rem; content: \"\"; background: var(--gw-color-text-secondary); border-radius: 50%; transition: translate var(--gw-motion-normal) var(--gw-motion-easing), background-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input:checked { background: var(--gw-color-fill-accent-resting); border-color: transparent; } gw-switch input:checked::after { translate: 1rem 0; background: var(--gw-color-on-accent); } gw-switch input:disabled { opacity: 0.5; } gw-range { display: flex; flex-direction: column; gap: var(--gw-space-2); min-width: 0; font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } gw-range > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } gw-range .gw-range-row { display: flex; gap: var(--gw-space-3); align-items: center; } gw-range .gw-range-control { position: relative; display: block; flex: 1; min-width: 0; } /* The value beside the track, with readout */ gw-range .gw-range-readout { min-width: 5ch; font-size: var(--gw-font-size-s); font-variant-numeric: tabular-nums; color: var(--gw-color-text-secondary); text-align: end; white-space: nowrap; } gw-range .gw-range-readout[hidden] { display: none; } gw-range input { display: block; width: 100%; margin: 0; accent-color: var(--gw-color-fill-accent-resting); cursor: pointer; } gw-range input:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } gw-range input:disabled { cursor: not-allowed; opacity: 0.5; } /* The value above the thumb while it's held */ gw-range .gw-range-bubble { position: absolute; bottom: calc(100% + var(--gw-space-1)); padding: var(--gw-space-1) var(--gw-space-2); font-size: var(--gw-font-size-xs); font-variant-numeric: tabular-nums; color: var(--gw-color-on-accent); white-space: nowrap; pointer-events: none; background: var(--gw-color-fill-accent-resting); border-radius: var(--gw-radius-control); box-shadow: var(--gw-shadow-s); translate: -50% 0; } gw-range .gw-range-bubble[hidden] { display: none; } gw-range .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } gw-range .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } `",
663
+ "default": "css` :where(gw-input, gw-select, gw-switch, gw-checkbox) { display: flex; flex-direction: column; gap: var(--gw-space-2); font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } :where(gw-input, gw-select) .gw-field-heading { display: flex; gap: var(--gw-space-1); align-items: center; } :where(gw-input, gw-select) .gw-field-heading > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } /* The (i) button by the label that opens a field's longer help */ :where(gw-input, gw-select) .gw-info { display: inline-grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } :where(gw-input, gw-select) .gw-info:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } :where(gw-input, gw-select) .gw-info:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) .gw-info[hidden] { display: none; } :where(gw-input, gw-select) .gw-info > .icon { width: 1rem; height: 1rem; fill: currentColor; } /* Sizes run xs to xl through --gw-field-scale, around 1 for medium, and the control's height follows the theme's density too */ :where(gw-input, gw-select)[size=\"xs\"] { --gw-field-scale: 0.6; } :where(gw-input, gw-select)[size=\"small\"] { --gw-field-scale: 0.8; } :where(gw-input, gw-select)[size=\"large\"] { --gw-field-scale: 1.2; } :where(gw-input, gw-select)[size=\"xl\"] { --gw-field-scale: 1.4; } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger) { --gw-field-size: var(--gw-field-scale, 1); --gw-icon-size: calc( 1.25rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); width: 100%; min-height: calc( 2.5rem * var(--gw-field-size) * var(--gw-control-scale, 1) ); padding: 0 calc(var(--gw-space-3) * var(--gw-field-size)); font: inherit; font-size: calc(var(--gw-font-size-s) * var(--gw-field-size)); color: var(--gw-color-text-strong); background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-control); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } /* An icon inside the field, before the text, with the text moved clear of it */ gw-input .gw-input-control { position: relative; display: flex; align-items: center; } gw-input .gw-input-control > .icon { position: absolute; inset-inline-start: calc(var(--gw-space-3) * var(--gw-field-scale, 1)); width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); color: var(--gw-color-text-secondary); pointer-events: none; fill: currentColor; } gw-input .gw-input-control > .icon[hidden] { display: none; } gw-input .gw-input-control > .icon:not([hidden]) + input { padding-inline-start: calc( (var(--gw-space-3) * 2 + 1.125rem) * var(--gw-field-scale, 1) ); } /* A textarea keeps the field's padding on every side and grows by its corner, never sideways out of the layout */ gw-input textarea { display: block; /* Pill-shaped controls would make a tall box a stadium, so it rounds no more than a panel */ border-radius: min(var(--gw-radius-control), var(--gw-radius-panel)); padding-block: calc(var(--gw-space-2) * var(--gw-field-size)); line-height: 1.5; resize: vertical; } gw-input[monospace] :where(input, textarea) { font-family: var(--gw-font-mono); } gw-input .gw-optional { font-weight: var(--gw-font-weight-regular); color: var(--gw-color-text-secondary); } gw-input .gw-optional[hidden] { display: none; } /* The show and hide button sits inside the end of a password field, with the text kept clear of it */ gw-input .gw-reveal { position: absolute; inset-inline-end: calc(var(--gw-space-1) * var(--gw-field-scale, 1)); display: grid; place-items: center; width: calc(2rem * var(--gw-field-scale, 1)); height: calc(2rem * var(--gw-field-scale, 1)); padding: 0; color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-control); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-input .gw-reveal:hover:not(:disabled) { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-input .gw-reveal:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-input .gw-reveal:disabled { cursor: not-allowed; opacity: 0.5; } gw-input .gw-reveal[hidden] { display: none; } gw-input .gw-reveal > .icon { width: calc(1.125rem * var(--gw-field-scale, 1)); height: calc(1.125rem * var(--gw-field-scale, 1)); fill: currentColor; } gw-input .gw-input-control:has(> .gw-reveal:not([hidden])) > input { padding-inline-end: calc( (var(--gw-space-1) + 2rem + var(--gw-space-1)) * var(--gw-field-scale, 1) ); } /* The trigger shows the chosen option, with its icon, and a chevron with room around it that turns over while the list is open */ gw-select .gw-select-trigger { display: flex; gap: calc(var(--gw-space-2) * var(--gw-field-size)); align-items: center; padding-inline-end: calc(var(--gw-space-3) * var(--gw-field-size)); text-align: start; cursor: pointer; } gw-select .gw-select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* With an inline label the select sits among toolbar controls, so it's a pill that fits its content, like the toggles and buttons beside it */ gw-select[inline-label] { display: inline-flex; } gw-select[inline-label] .gw-select-trigger { width: auto; padding-inline: calc(var(--gw-space-4) * var(--gw-field-size)) calc(var(--gw-space-3) * var(--gw-field-size)); border-radius: var(--gw-radius-pill); } /* An inline label reads as a quiet prefix to the value */ gw-select .gw-select-inline-label { flex: none; color: var(--gw-color-text-secondary); } gw-select .gw-select-inline-label[hidden] { display: none; } gw-select .gw-select-value[data-placeholder] { color: var(--gw-color-text-secondary); } gw-select .gw-select-trigger > .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } gw-select .gw-select-trigger > .icon:last-child { color: var(--gw-color-text-secondary); transition: rotate var(--gw-motion-normal) var(--gw-motion-easing); } gw-select[open] .gw-select-trigger > .icon:last-child { rotate: 180deg; } gw-select .gw-select-trigger > .icon[hidden] { display: none; } /* The list opens under the trigger, growing from it, or as a bottom sheet sliding up on phones */ gw-select .gw-select-popup { position: fixed; inset: unset; box-sizing: border-box; max-width: calc(100vw - 12px); padding: 0; margin: 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-panel); box-shadow: var(--gw-shadow-l); opacity: 0; scale: 1 0.96; transform-origin: top; 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; } gw-select .gw-select-popup[data-placement=\"above\"] { transform-origin: bottom; } gw-select .gw-select-popup:popover-open { opacity: 1; scale: 1; } @starting-style { gw-select .gw-select-popup:popover-open { opacity: 0; scale: 1 0.96; } } gw-select .gw-select-popup[data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: env(safe-area-inset-bottom); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; --gw-option-list-height: 60vh; } gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 0; } @starting-style { gw-select .gw-select-popup[data-placement=\"sheet\"]:popover-open { translate: 0 100%; } } gw-select .gw-select-popup[data-placement=\"sheet\"]::backdrop { background: rgb(0 0 0 / 40%); } /* With multiple, a full-width Clear under the list empties the choice */ gw-select .gw-select-clear { display: block; width: 100%; min-height: 2.5rem; padding: 0 var(--gw-space-3); font: inherit; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); cursor: pointer; background: none; border: 0; border-block-start: var(--gw-border-width) solid var(--gw-color-border-quiet); transition: color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } gw-select .gw-select-clear:hover { color: var(--gw-color-text-strong); background: var(--gw-color-fill-neutral-hovered); } gw-select .gw-select-clear:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: calc(var(--gw-focus-width) * -1); } gw-select .gw-select-clear[hidden] { display: none; } :where(gw-input) .gw-input-control:focus-within > .icon { color: var(--gw-color-text-strong); } :where(gw-input, gw-select) :where(input, textarea, .gw-select-trigger):hover { border-color: var(--gw-color-border-loud); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :where(input, textarea, .gw-select-trigger):focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :where(gw-input, gw-select) [aria-invalid=\"true\"] { border-color: var(--gw-color-border-danger); } :where(gw-input, gw-select, gw-switch, gw-checkbox) :disabled { color: var(--gw-color-text-disabled); cursor: not-allowed; } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } :where(gw-input, gw-select, gw-switch, gw-checkbox) .gw-error { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-danger); } :where(gw-switch, gw-checkbox) > label { display: inline-flex; gap: var(--gw-space-3); align-items: center; font-size: var(--gw-font-size-s); cursor: pointer; } :where(gw-input, gw-switch, gw-checkbox, gw-select) .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } gw-checkbox input { width: 1.125rem; height: 1.125rem; margin: 0; accent-color: var(--gw-color-fill-accent-resting); } gw-switch input { position: relative; flex: none; width: 2.5rem; height: 1.5rem; margin: 0; cursor: pointer; appearance: none; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-loud); border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input::after { position: absolute; inset-block-start: 0.1875rem; inset-inline-start: 0.1875rem; width: 1rem; height: 1rem; content: \"\"; background: var(--gw-color-text-secondary); border-radius: 50%; transition: translate var(--gw-motion-normal) var(--gw-motion-easing), background-color var(--gw-motion-normal) var(--gw-motion-easing); } gw-switch input:checked { background: var(--gw-color-fill-accent-resting); border-color: transparent; } gw-switch input:checked::after { translate: 1rem 0; background: var(--gw-color-on-accent); } gw-switch input:disabled { opacity: 0.5; } gw-range { display: flex; flex-direction: column; gap: var(--gw-space-2); min-width: 0; font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } gw-range > label { font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } gw-range .gw-range-row { display: flex; gap: var(--gw-space-3); align-items: center; } gw-range .gw-range-control { position: relative; display: block; flex: 1; min-width: 0; } /* The value beside the track, with readout */ gw-range .gw-range-readout { min-width: 5ch; font-size: var(--gw-font-size-s); font-variant-numeric: tabular-nums; color: var(--gw-color-text-secondary); text-align: end; white-space: nowrap; } gw-range .gw-range-readout[hidden] { display: none; } gw-range input { display: block; width: 100%; margin: 0; accent-color: var(--gw-color-fill-accent-resting); cursor: pointer; } gw-range input:focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } gw-range input:disabled { cursor: not-allowed; opacity: 0.5; } /* The value above the thumb while it's held */ gw-range .gw-range-bubble { position: absolute; bottom: calc(100% + var(--gw-space-1)); padding: var(--gw-space-1) var(--gw-space-2); font-size: var(--gw-font-size-xs); font-variant-numeric: tabular-nums; color: var(--gw-color-on-accent); white-space: nowrap; pointer-events: none; background: var(--gw-color-fill-accent-resting); border-radius: var(--gw-radius-control); box-shadow: var(--gw-shadow-s); translate: -50% 0; } gw-range .gw-range-bubble[hidden] { display: none; } gw-range .gw-hint { margin: 0; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } gw-range .gw-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } `",
664
664
  "description": "Field components render their native control in light DOM, never in a\nshadow root: password managers and `<label for>` can't reach across one,\nand a form only submits controls it can see. Their styles are adopted into\nwhichever document or shadow root they sit in."
665
665
  },
666
666
  {
@@ -1107,7 +1107,7 @@
1107
1107
  "text": "readonly CSSStyleSheet[]"
1108
1108
  },
1109
1109
  "static": true,
1110
- "default": "[ base, css` :host { display: block; } .alert { display: flex; gap: var(--gw-space-2); align-items: flex-start; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-tone-fill, var(--gw-color-fill-info-quiet)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-info)); border-inline-start-width: calc(var(--gw-border-width) * 4); border-radius: var(--gw-radius-control); } ::slotted(p) { margin: 0; } ::slotted([slot=\"actions\"]) { align-self: center; } ::slotted([slot=\"icon\"]), .icon { flex: none; color: var(--gw-tone, var(--gw-color-info)); } .body { flex: 1; min-width: 0; } strong { display: block; font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } `, ]",
1110
+ "default": "[ base, css` :host { display: block; } .alert { display: flex; gap: var(--gw-space-2); align-items: flex-start; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-tone-fill, var(--gw-color-fill-info-quiet)); border: var(--gw-border-width) solid var(--gw-tone-border, var(--gw-color-border-info)); border-inline-start-width: calc(var(--gw-border-width) * 4); border-radius: min(var(--gw-radius-control), var(--gw-radius-panel)); } ::slotted(p) { margin: 0; } ::slotted([slot=\"actions\"]) { align-self: center; } ::slotted([slot=\"icon\"]), .icon { flex: none; color: var(--gw-tone, var(--gw-color-info)); } .body { flex: 1; min-width: 0; } strong { display: block; font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } `, ]",
1111
1111
  "inheritedFrom": {
1112
1112
  "name": "GwElement",
1113
1113
  "module": "src/element.ts"
@@ -3836,7 +3836,7 @@
3836
3836
  "text": "Properties"
3837
3837
  },
3838
3838
  "static": true,
3839
- "default": "{ heading: {}, description: {}, icon: {}, level: { type: \"number\" }, href: {}, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
3839
+ "default": "{ heading: {}, description: {}, icon: {}, level: { type: \"number\" }, href: {}, titleLink: { attribute: \"title-link\", type: \"boolean\", reflect: true }, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
3840
3840
  "inheritedFrom": {
3841
3841
  "name": "GwElement",
3842
3842
  "module": "src/element.ts"
@@ -3849,7 +3849,7 @@
3849
3849
  "text": "readonly CSSStyleSheet[]"
3850
3850
  },
3851
3851
  "static": true,
3852
- "default": "[ base, css` :host { display: block; } .card { display: flex; flex-direction: column; gap: var(--gw-space-3); height: 100%; padding: var(--gw-space-4); color: inherit; text-decoration: none; background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-s); } :host([flush]) .card { padding: 0; overflow: hidden; } a.card { transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } a.card:hover, .card.linked:hover { border-color: var(--gw-color-border-accent); } /* A card following a link in its heading slot */ .card.linked { cursor: pointer; transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } .card.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } ::slotted([slot=\"heading\"]) { font: inherit; color: inherit; } ::slotted(a[slot=\"heading\"]) { text-decoration: none; outline: none; } header { display: flex; gap: var(--gw-space-2); align-items: center; } header > .icon { flex-shrink: 0; margin-inline-end: var(--gw-space-1); color: var(--gw-color-accent); } .titles { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } .title { margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } /* The divider runs to the card's edges */ footer:not([hidden]) { display: flex; flex-direction: column; gap: inherit; margin-inline: calc(-1 * var(--gw-space-4)); padding: var(--gw-space-3) var(--gw-space-4) 0; border-top: var(--gw-border-width) solid var(--gw-color-border-normal); } :host([flush]) footer:not([hidden]) { margin-inline: 0; padding-bottom: var(--gw-space-3); } .body:not([hidden]) { display: flex; flex-direction: column; gap: inherit; } `, ]",
3852
+ "default": "[ base, css` :host { display: block; } .card { display: flex; flex-direction: column; gap: var(--gw-space-3); height: 100%; padding: var(--gw-space-4); color: inherit; text-decoration: none; background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-s); } :host([flush]) .card { padding: 0; overflow: hidden; } a.card { transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } a.card:hover, .card.linked:hover { border-color: var(--gw-color-border-accent); } /* A card following a link in its heading slot */ .card.linked { cursor: pointer; transition: border-color var(--gw-motion-fast) var(--gw-motion-easing); } .card.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } ::slotted([slot=\"heading\"]) { font: inherit; color: inherit; } ::slotted(a[slot=\"heading\"]) { text-decoration: none; outline: none; } /* With title-link, only the heading is the link */ .title a { color: inherit; text-decoration: none; border-radius: var(--gw-radius-chip); } .title a[href]:hover, :host([title-link]) ::slotted(a[slot=\"heading\"]:hover) { color: var(--gw-color-accent); text-decoration: underline; } .title a[href]:focus-visible, :host([title-link]) ::slotted(a[slot=\"heading\"]:focus-visible) { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } header { display: flex; gap: var(--gw-space-2); align-items: center; } header > .icon { flex-shrink: 0; margin-inline-end: var(--gw-space-1); color: var(--gw-color-accent); } .titles { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } .title { margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } /* The divider runs to the card's edges */ footer:not([hidden]) { display: flex; flex-direction: column; gap: inherit; margin-inline: calc(-1 * var(--gw-space-4)); padding: var(--gw-space-3) var(--gw-space-4) 0; border-top: var(--gw-border-width) solid var(--gw-color-border-normal); } :host([flush]) footer:not([hidden]) { margin-inline: 0; padding-bottom: var(--gw-space-3); } .body:not([hidden]) { display: flex; flex-direction: column; gap: inherit; } `, ]",
3853
3853
  "inheritedFrom": {
3854
3854
  "name": "GwElement",
3855
3855
  "module": "src/element.ts"
@@ -3897,6 +3897,15 @@
3897
3897
  },
3898
3898
  "default": "\"\""
3899
3899
  },
3900
+ {
3901
+ "kind": "field",
3902
+ "name": "titleLink",
3903
+ "type": {
3904
+ "text": "boolean"
3905
+ },
3906
+ "description": "Makes only the heading the link, rather than the whole card.",
3907
+ "default": "false"
3908
+ },
3900
3909
  {
3901
3910
  "kind": "field",
3902
3911
  "name": "flush",
@@ -3928,6 +3937,12 @@
3928
3937
  "privacy": "private",
3929
3938
  "readonly": true
3930
3939
  },
3940
+ {
3941
+ "kind": "field",
3942
+ "name": "#titleAnchor",
3943
+ "privacy": "private",
3944
+ "readonly": true
3945
+ },
3931
3946
  {
3932
3947
  "kind": "field",
3933
3948
  "name": "#titleSkeleton",
@@ -4397,6 +4412,13 @@
4397
4412
  "description": "Makes the whole card a link to this address.",
4398
4413
  "name": "href"
4399
4414
  },
4415
+ {
4416
+ "type": {
4417
+ "text": "boolean"
4418
+ },
4419
+ "description": "Makes only the heading the link, from `href` or an `<a>` in the `heading` slot, so the rest of the card stays plain.",
4420
+ "name": "title-link"
4421
+ },
4400
4422
  {
4401
4423
  "type": {
4402
4424
  "text": "boolean"
@@ -6068,7 +6090,7 @@
6068
6090
  "text": "Properties"
6069
6091
  },
6070
6092
  "static": true,
6071
- "default": "{ options: { type: \"json\" }, label: {}, placeholder: {}, shortcut: {}, open: { type: \"boolean\", reflect: true }, }",
6093
+ "default": "{ options: { type: \"json\" }, value: {}, label: {}, placeholder: {}, shortcut: {}, open: { type: \"boolean\", reflect: true }, }",
6072
6094
  "inheritedFrom": {
6073
6095
  "name": "GwElement",
6074
6096
  "module": "src/element.ts"
@@ -6095,6 +6117,15 @@
6095
6117
  },
6096
6118
  "default": "[]"
6097
6119
  },
6120
+ {
6121
+ "kind": "field",
6122
+ "name": "value",
6123
+ "type": {
6124
+ "text": "string"
6125
+ },
6126
+ "description": "The current command, marked and selected when the menu opens.",
6127
+ "default": "\"\""
6128
+ },
6098
6129
  {
6099
6130
  "kind": "field",
6100
6131
  "name": "label",
@@ -6486,6 +6517,13 @@
6486
6517
  "description": "The commands, as JSON.",
6487
6518
  "name": "options"
6488
6519
  },
6520
+ {
6521
+ "type": {
6522
+ "text": "string"
6523
+ },
6524
+ "description": "The current command, such as the page you're on. It's marked, and selected and scrolled into view each time the menu opens.",
6525
+ "name": "value"
6526
+ },
6489
6527
  {
6490
6528
  "type": {
6491
6529
  "text": "string"
@@ -12714,7 +12752,7 @@
12714
12752
  "text": "readonly CSSStyleSheet[]"
12715
12753
  },
12716
12754
  "static": true,
12717
- "default": "[ base, css` :host { display: block; } .card { display: grid; grid-template-columns: auto 1fr auto; gap: var(--gw-space-1) var(--gw-space-2); align-items: start; padding: var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-quiet); border-radius: var(--gw-radius-control); } /* An inset stripe, so unread and read cards line up */ :host([unread]) .card { box-shadow: inset calc(var(--gw-border-width) * 4) 0 0 var(--gw-tone, var(--gw-color-accent)); } :host([unread]:dir(rtl)) .card { box-shadow: inset calc(var(--gw-border-width) * -4) 0 0 var(--gw-tone, var(--gw-color-accent)); } .card > .icon { margin-block-start: 0.1em; color: var(--gw-tone, var(--gw-color-text-secondary)); } .card > .icon[hidden] { display: none; } .body { display: grid; gap: var(--gw-space-1); min-width: 0; } strong { font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } time { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } time:empty { display: none; } ::slotted(p) { margin: 0; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-1); margin-block-start: var(--gw-space-1); } .close { margin: calc(var(--gw-space-1) * -1); } `, ]",
12755
+ "default": "[ base, css` :host { display: block; } .card { display: grid; grid-template-columns: auto 1fr auto; gap: var(--gw-space-1) var(--gw-space-2); align-items: start; padding: var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-primary); background: var(--gw-color-surface-raised); border: var(--gw-border-width) solid var(--gw-color-border-quiet); border-radius: min(var(--gw-radius-control), var(--gw-radius-panel)); } /* An inset stripe, so unread and read cards line up */ :host([unread]) .card { box-shadow: inset calc(var(--gw-border-width) * 4) 0 0 var(--gw-tone, var(--gw-color-accent)); } :host([unread]:dir(rtl)) .card { box-shadow: inset calc(var(--gw-border-width) * -4) 0 0 var(--gw-tone, var(--gw-color-accent)); } .card > .icon { margin-block-start: 0.1em; color: var(--gw-tone, var(--gw-color-text-secondary)); } .card > .icon[hidden] { display: none; } .body { display: grid; gap: var(--gw-space-1); min-width: 0; } strong { font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } time { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } time:empty { display: none; } ::slotted(p) { margin: 0; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-1); margin-block-start: var(--gw-space-1); } .close { margin: calc(var(--gw-space-1) * -1); } `, ]",
12718
12756
  "inheritedFrom": {
12719
12757
  "name": "GwElement",
12720
12758
  "module": "src/element.ts"
@@ -21629,7 +21667,7 @@
21629
21667
  "text": "readonly CSSStyleSheet[]"
21630
21668
  },
21631
21669
  "static": true,
21632
- "default": "[ base, css` :host { position: fixed; inset: auto var(--gw-space-4) var(--gw-space-4) auto; width: min(22rem, calc(100vw - 2 * var(--gw-space-4))); padding: 0; margin: 0; overflow: visible; color: inherit; pointer-events: none; user-select: none; background: none; border: 0; } :host(:dir(rtl)) { inset: auto auto var(--gw-space-4) var(--gw-space-4); } :host(:not(:popover-open)) { display: none; } .list { display: flex; flex-direction: column; gap: var(--gw-space-1); align-items: flex-end; } .toast { max-width: 100%; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: var(--gw-color-surface-overlay); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-inline-start: calc(var(--gw-border-width) * 4) solid var(--gw-toast-tone); border-radius: var(--gw-radius-control); box-shadow: var(--gw-shadow-m); } `, ]",
21670
+ "default": "[ base, css` :host { position: fixed; inset: auto var(--gw-space-4) var(--gw-space-4) auto; width: min(22rem, calc(100vw - 2 * var(--gw-space-4))); padding: 0; margin: 0; overflow: visible; color: inherit; pointer-events: none; user-select: none; background: none; border: 0; } :host(:dir(rtl)) { inset: auto auto var(--gw-space-4) var(--gw-space-4); } :host(:not(:popover-open)) { display: none; } .list { display: flex; flex-direction: column; gap: var(--gw-space-1); align-items: flex-end; } .toast { max-width: 100%; padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: var(--gw-color-surface-overlay); border: var(--gw-border-width) solid var(--gw-color-border-normal); border-inline-start: calc(var(--gw-border-width) * 4) solid var(--gw-toast-tone); border-radius: min(var(--gw-radius-control), var(--gw-radius-panel)); box-shadow: var(--gw-shadow-m); } `, ]",
21633
21671
  "inheritedFrom": {
21634
21672
  "name": "GwElement",
21635
21673
  "module": "src/element.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.29",
3
+ "version": "0.9.30",
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/fields.ts CHANGED
@@ -140,6 +140,9 @@ export const fieldStyles = css`
140
140
  corner, never sideways out of the layout */
141
141
  gw-input textarea {
142
142
  display: block;
143
+ /* Pill-shaped controls would make a tall box a stadium, so it rounds no
144
+ more than a panel */
145
+ border-radius: min(var(--gw-radius-control), var(--gw-radius-panel));
143
146
  padding-block: calc(var(--gw-space-2) * var(--gw-field-size));
144
147
  line-height: 1.5;
145
148
  resize: vertical;
package/src/gw-alert.ts CHANGED
@@ -70,7 +70,7 @@ export class GwAlert extends GwElement {
70
70
  border: var(--gw-border-width) solid
71
71
  var(--gw-tone-border, var(--gw-color-border-info));
72
72
  border-inline-start-width: calc(var(--gw-border-width) * 4);
73
- border-radius: var(--gw-radius-control);
73
+ border-radius: min(var(--gw-radius-control), var(--gw-radius-panel));
74
74
  }
75
75
 
76
76
  ::slotted(p) {
package/src/gw-card.ts CHANGED
@@ -29,6 +29,7 @@ import { base, createIcon, setIconPath } from "./styles.js";
29
29
  * @attr {string} icon - SVG path data for an icon before the heading, such as on a card that links to a section.
30
30
  * @attr {number} level - The heading's level for assistive tech, 2 by default. Use 3 for a card under a section heading.
31
31
  * @attr {string} href - Makes the whole card a link to this address.
32
+ * @attr {boolean} title-link - Makes only the heading the link, from `href` or an `<a>` in the `heading` slot, so the rest of the card stays plain.
32
33
  * @attr {boolean} flush - Drops the padding, for content that runs to the edges.
33
34
  * @attr {boolean} loading - Shows skeleton lines for the content and any unknown heading.
34
35
  * @attr {number} lines - How many skeleton lines while loading. Defaults to 3.
@@ -52,6 +53,7 @@ export class GwCard extends GwElement {
52
53
  icon: {},
53
54
  level: { type: "number" },
54
55
  href: {},
56
+ titleLink: { attribute: "title-link", type: "boolean", reflect: true },
55
57
  flush: { type: "boolean", reflect: true },
56
58
  loading: { type: "boolean", reflect: true },
57
59
  lines: { type: "number" },
@@ -113,6 +115,25 @@ export class GwCard extends GwElement {
113
115
  outline: none;
114
116
  }
115
117
 
118
+ /* With title-link, only the heading is the link */
119
+ .title a {
120
+ color: inherit;
121
+ text-decoration: none;
122
+ border-radius: var(--gw-radius-chip);
123
+ }
124
+
125
+ .title a[href]:hover,
126
+ :host([title-link]) ::slotted(a[slot="heading"]:hover) {
127
+ color: var(--gw-color-accent);
128
+ text-decoration: underline;
129
+ }
130
+
131
+ .title a[href]:focus-visible,
132
+ :host([title-link]) ::slotted(a[slot="heading"]:focus-visible) {
133
+ outline: var(--gw-focus-width) solid var(--gw-color-focus);
134
+ outline-offset: var(--gw-focus-offset);
135
+ }
136
+
116
137
  header {
117
138
  display: flex;
118
139
  gap: var(--gw-space-2);
@@ -175,6 +196,8 @@ export class GwCard extends GwElement {
175
196
  /** The heading's level for assistive tech. */
176
197
  declare level: number;
177
198
  declare href: string;
199
+ /** Makes only the heading the link, rather than the whole card. */
200
+ declare titleLink: boolean;
178
201
  declare flush: boolean;
179
202
  declare loading: boolean;
180
203
  /** How many lines of skeleton stand in for the content while loading. */
@@ -182,6 +205,9 @@ export class GwCard extends GwElement {
182
205
 
183
206
  readonly #title = h("span");
184
207
 
208
+ // A link only with title-link and href; without an href it's plain text
209
+ readonly #titleAnchor = h("a", {}, this.#title);
210
+
185
211
  readonly #titleSkeleton = h("gw-skeleton-text");
186
212
 
187
213
  readonly #headingSlot = h("slot", { name: "heading" });
@@ -189,7 +215,7 @@ export class GwCard extends GwElement {
189
215
  readonly #heading = h(
190
216
  "div",
191
217
  { class: "title", part: "heading", role: "heading", "aria-level": "2" },
192
- this.#title,
218
+ this.#titleAnchor,
193
219
  this.#headingSlot,
194
220
  this.#titleSkeleton,
195
221
  );
@@ -242,6 +268,7 @@ export class GwCard extends GwElement {
242
268
  this.icon = "";
243
269
  this.level = 2;
244
270
  this.href = "";
271
+ this.titleLink = false;
245
272
  this.flush = false;
246
273
  this.loading = false;
247
274
  this.lines = 3;
@@ -251,7 +278,7 @@ export class GwCard extends GwElement {
251
278
  this.addEventListener("click", (event) => {
252
279
  const link = this.#link();
253
280
 
254
- if (link === null || this.loading) return;
281
+ if (link === null || this.loading || this.titleLink) return;
255
282
 
256
283
  const path = event.composedPath();
257
284
  const own = path.slice(0, path.indexOf(this));
@@ -277,7 +304,7 @@ export class GwCard extends GwElement {
277
304
  const focus = () => {
278
305
  this.#card.classList.toggle(
279
306
  "focused",
280
- this.#link()?.matches(":focus-visible") === true,
307
+ !this.titleLink && this.#link()?.matches(":focus-visible") === true,
281
308
  );
282
309
  };
283
310
 
@@ -327,11 +354,13 @@ export class GwCard extends GwElement {
327
354
 
328
355
  const before = animate ? this.offsetHeight : 0;
329
356
 
357
+ const cardLink = Boolean(this.href) && !this.titleLink;
358
+
330
359
  if (
331
- changed.has("href") &&
332
- (this.#card.tagName === "A") !== Boolean(this.href)
360
+ (changed.has("href") || changed.has("titleLink")) &&
361
+ (this.#card.tagName === "A") !== cardLink
333
362
  ) {
334
- const next = this.href
363
+ const next = cardLink
335
364
  ? h("a", { class: "card", part: "card" })
336
365
  : h("section", { class: "card", part: "card" });
337
366
 
@@ -342,11 +371,20 @@ export class GwCard extends GwElement {
342
371
 
343
372
  if (this.#card instanceof HTMLAnchorElement) this.#card.href = this.href;
344
373
 
374
+ setAttribute(
375
+ this.#titleAnchor,
376
+ "href",
377
+ this.titleLink && this.href ? this.href : null,
378
+ );
379
+
345
380
  setAttribute(this.#card, "aria-busy", this.loading ? "true" : null);
346
381
 
347
382
  const slotted = this.querySelector(":scope > [slot='heading']") !== null;
348
383
 
349
- this.#card.classList.toggle("linked", this.#link() !== null && !this.href);
384
+ this.#card.classList.toggle(
385
+ "linked",
386
+ this.#link() !== null && !this.href && !this.titleLink,
387
+ );
350
388
  this.#title.textContent = this.heading;
351
389
  this.#heading.setAttribute("aria-level", String(this.level || 2));
352
390
  this.#description.textContent = this.description;
@@ -23,6 +23,7 @@ const sheetBelow = 600;
23
23
  * @summary A searchable list of commands on Cmd+K or Ctrl+K.
24
24
  *
25
25
  * @attr {OptionItem[]} options - The commands, as JSON.
26
+ * @attr {string} value - The current command, such as the page you're on. It's marked, and selected and scrolled into view each time the menu opens.
26
27
  * @attr {string} label - The accessible name. Defaults to `Commands`.
27
28
  * @attr {string} placeholder - The search placeholder. Defaults to `Search commands`.
28
29
  * @attr {string} shortcut - The key that opens it with Cmd or Ctrl, such as `mod+k`, or `none`. Defaults to `mod+k`.
@@ -37,6 +38,7 @@ const sheetBelow = 600;
37
38
  export class GwCommandMenu extends GwElement {
38
39
  static override properties = {
39
40
  options: { type: "json" },
41
+ value: {},
40
42
  label: {},
41
43
  placeholder: {},
42
44
  shortcut: {},
@@ -154,6 +156,8 @@ export class GwCommandMenu extends GwElement {
154
156
  ];
155
157
 
156
158
  declare options: readonly OptionItem[];
159
+ /** The current command, marked and selected when the menu opens. */
160
+ declare value: string;
157
161
  declare label: string;
158
162
  declare placeholder: string;
159
163
  /** `mod+k` (the default) opens it on Cmd+K or Ctrl+K; `none` turns that off. */
@@ -178,6 +182,7 @@ export class GwCommandMenu extends GwElement {
178
182
  constructor() {
179
183
  super();
180
184
  this.options = [];
185
+ this.value = "";
181
186
  this.label = "Commands";
182
187
  this.placeholder = "Search commands";
183
188
  this.shortcut = "mod+k";
@@ -268,6 +273,7 @@ export class GwCommandMenu extends GwElement {
268
273
  const dialog = this.#dialog;
269
274
 
270
275
  list.options = Array.isArray(this.options) ? this.options : [];
276
+ list.value = this.value;
271
277
  list.label = this.label;
272
278
  list.placeholder = this.placeholder;
273
279
  dialog.setAttribute("aria-label", this.label);
@@ -66,7 +66,7 @@ export class GwNotification extends GwElement {
66
66
  color: var(--gw-color-text-primary);
67
67
  background: var(--gw-color-surface-raised);
68
68
  border: var(--gw-border-width) solid var(--gw-color-border-quiet);
69
- border-radius: var(--gw-radius-control);
69
+ border-radius: min(var(--gw-radius-control), var(--gw-radius-panel));
70
70
  }
71
71
 
72
72
  /* An inset stripe, so unread and read cards line up */
@@ -557,8 +557,10 @@ export class GwOptionList extends GwElement {
557
557
  const headed =
558
558
  previous?.kind === "group" ? (this.#tops[position - 1] ?? top) : top;
559
559
 
560
+ // A third of the way down, so what's above it shows too without
561
+ // the option sitting at either edge
560
562
  if (center) {
561
- listbox.scrollTop = Math.max(0, top - listbox.clientHeight / 2);
563
+ listbox.scrollTop = Math.max(0, top - listbox.clientHeight / 3);
562
564
  } else if (headed - listPadding < listbox.scrollTop) {
563
565
  listbox.scrollTop = Math.max(0, headed - listPadding);
564
566
  } else if (
@@ -472,6 +472,15 @@ export class GwThemePicker extends GwElement {
472
472
  this.#button.addEventListener("click", () => {
473
473
  this.show();
474
474
  });
475
+
476
+ // A theme or mode applies at once, so it isn't an edit for the popover
477
+ // to ask about discarding, and a click outside still closes it
478
+ for (const type of ["input", "change"]) {
479
+ this.#controls.addEventListener(type, (event) => {
480
+ if (event.target !== this.#controls) event.stopPropagation();
481
+ });
482
+ }
483
+
475
484
  this.#mode.addEventListener("change", (event) => {
476
485
  event.stopPropagation();
477
486
  this.mode = this.#mode.value;
package/src/gw-toast.ts CHANGED
@@ -71,7 +71,7 @@ export class GwToaster extends GwElement {
71
71
  border: var(--gw-border-width) solid var(--gw-color-border-normal);
72
72
  border-inline-start: calc(var(--gw-border-width) * 4) solid
73
73
  var(--gw-toast-tone);
74
- border-radius: var(--gw-radius-control);
74
+ border-radius: min(var(--gw-radius-control), var(--gw-radius-panel));
75
75
  box-shadow: var(--gw-shadow-m);
76
76
  }
77
77
  `,