@timmo001/groundwork-ui 0.9.19 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +100 -11
- package/package.json +1 -1
- package/src/gw-button.ts +4 -0
- package/src/gw-menu-button.ts +46 -8
- package/src/gw-skeleton.ts +11 -2
- package/src/gw-spinner.ts +6 -1
- package/src/styles.ts +8 -0
package/custom-elements.json
CHANGED
|
@@ -3145,7 +3145,7 @@
|
|
|
3145
3145
|
"text": "readonly CSSStyleSheet[]"
|
|
3146
3146
|
},
|
|
3147
3147
|
"static": true,
|
|
3148
|
-
"default": "[ base, controlSizes(\"button\"), css` :host { --gw-button-tone: var(--gw-color-accent); display: inline-block; } :host([variant=\"neutral\"]) { --gw-button-tone: var(--gw-color-text-secondary); --gw-button-ink: var(--gw-color-text-strong); } :host([variant=\"info\"]) { --gw-button-tone: var(--gw-color-info); } :host([variant=\"success\"]) { --gw-button-tone: var(--gw-color-success); } :host([variant=\"warning\"]) { --gw-button-tone: var(--gw-color-warning); } :host([variant=\"danger\"]) { --gw-button-tone: var(--gw-color-danger); } .button { --gw-size: var(--gw-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.125rem * var(--gw-scale)); position: relative; display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; justify-content: center; min-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); line-height: 1; color: var(--gw-button-ink, var(--gw-button-tone)); text-decoration: none; text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); white-space: nowrap; cursor: pointer; 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), scale var(--gw-motion-fast) var(--gw-motion-easing); } /* A slight press, so a click feels physical */ .button:active { scale: 0.97; } .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } .button:active { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } /* A solid fill, with text that flips between black and white by the fill's lightness so it reads on every theme's colors */ :host([appearance=\"accent\"]) .button { color: oklch( from var(--gw-button-tone) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-button-tone); } :host([appearance=\"accent\"]) .button:hover { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.2) c h ); } :host([appearance=\"accent\"]) .button:active { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.35) c h ); } /* The tint lowers the tone's contrast, so the text leans toward the strongest text color to stay readable on it */ :host([appearance=\"filled\"]) .button { color: var( --gw-button-ink, color-mix( in oklch, var(--gw-button-tone) 70%, var(--gw-color-text-strong) ) ); background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } :host([appearance=\"filled\"]) .button:active { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } :host([appearance=\"outlined\"]) .button { border-color: var(--gw-button-tone); } /* A slotted link carries the text; the pill draws its focus ring */ ::slotted(a) { color: inherit; text-decoration: none; outline: none; } .button.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :host([appearance=\"plain\"]) .button.current, :host([appearance=\"outlined\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } .button:disabled, .button[aria-disabled=\"true\"] { color: var(--gw-color-text-disabled); pointer-events: none; cursor: not-allowed; background: var(--gw-color-fill-neutral-disabled); border-color: transparent; } :host([appearance=\"plain\"]) .button:disabled, :host([appearance=\"plain\"]) .button[aria-disabled=\"true\"] { background: transparent; } ::slotted([slot=\"icon\"]), .icon { flex: none; width: var(--gw-icon-size); height: var(--gw-icon-size); fill: currentColor; } /* The icon and the ring share one cell and crossfade */ .lead { display: grid; place-items: center; } /* The icon slot is display: contents, so its slotted icon is a grid item of its own and needs the shared cell too */ .lead > *, ::slotted([slot=\"icon\"]) { grid-area: 1 / 1; } /* A color dot in the icon's place */ .swatch { width: calc(var(--gw-icon-size) * 0.6); height: calc(var(--gw-icon-size) * 0.6); border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .lead > *, .text, .ring { transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1); } /* Sized to the icon's drawn area, which is inset in its 24px box */ .ring { width: calc(var(--gw-icon-size) * 0.8); height: calc(var(--gw-icon-size) * 0.8); border: calc(2px * var(--gw-scale)) solid color-mix(in oklch, currentColor 25%, transparent); border-top-color: currentColor; border-radius: 50%; opacity: 0; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Without an icon, the ring sits over the hidden text */ .ring.over { position: absolute; inset: 0; margin: auto; } :host([loading]) .button { cursor: progress; } :host([loading]) .lead > .ring, :host([loading]) .button:not(.has-icon) > .ring.over { opacity: 1; } :host([loading]) .lead > :not(.ring), :host([loading]) .button:not(.has-icon) > .text { opacity: 0; } .button:not(.has-icon) > .lead { display: none; } /* Icon only, to save space: square, with the text kept for assistive technology. auto does this on narrow screens */ :host([labels=\"hide\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"hide\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } @media (width < 40rem) { :host([labels=\"auto\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"auto\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } } `, ]",
|
|
3148
|
+
"default": "[ base, controlSizes(\"button\"), css` :host { --gw-button-tone: var(--gw-color-accent); display: inline-block; } :host([variant=\"neutral\"]) { --gw-button-tone: var(--gw-color-text-secondary); --gw-button-ink: var(--gw-color-text-strong); } :host([variant=\"info\"]) { --gw-button-tone: var(--gw-color-info); } :host([variant=\"success\"]) { --gw-button-tone: var(--gw-color-success); } :host([variant=\"warning\"]) { --gw-button-tone: var(--gw-color-warning); } :host([variant=\"danger\"]) { --gw-button-tone: var(--gw-color-danger); } .button { --gw-size: var(--gw-button-scale, 1); --gw-scale: calc(var(--gw-size) * var(--gw-control-scale, 1)); --gw-icon-size: calc(1.125rem * var(--gw-scale)); position: relative; display: inline-flex; gap: calc(var(--gw-space-2) * var(--gw-size)); align-items: center; justify-content: center; min-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); line-height: 1; color: var(--gw-button-ink, var(--gw-button-tone)); text-decoration: none; text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); white-space: nowrap; cursor: pointer; 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), scale var(--gw-motion-fast) var(--gw-motion-easing); } /* A slight press, so a click feels physical */ .button:active { scale: 0.97; } .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } .button:active { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } /* A solid fill, with text that flips between black and white by the fill's lightness so it reads on every theme's colors */ :host([appearance=\"accent\"]) .button { color: oklch( from var(--gw-button-tone) clamp(0, (0.66 - l) * 1000, 1) 0 0 ); background: var(--gw-button-tone); } :host([appearance=\"accent\"]) .button:hover { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.2) c h ); } :host([appearance=\"accent\"]) .button:active { background: oklch( from var(--gw-button-tone) calc(l + (0.6 - l) * 0.35) c h ); } /* The tint lowers the tone's contrast, so the text leans toward the strongest text color to stay readable on it */ :host([appearance=\"filled\"]) .button { color: var( --gw-button-ink, color-mix( in oklch, var(--gw-button-tone) 70%, var(--gw-color-text-strong) ) ); background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button:hover { background: color-mix(in oklch, var(--gw-button-tone) 24%, transparent); } :host([appearance=\"filled\"]) .button:active { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } :host([appearance=\"outlined\"]) .button { border-color: var(--gw-button-tone); } /* A slotted link carries the text; the pill draws its focus ring */ ::slotted(a) { color: inherit; text-decoration: none; outline: none; } .button.focused { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } :host([appearance=\"plain\"]) .button.current, :host([appearance=\"outlined\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([appearance=\"filled\"]) .button.current { background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent); } .button:disabled, .button[aria-disabled=\"true\"] { color: var(--gw-color-text-disabled); pointer-events: none; cursor: not-allowed; background: var(--gw-color-fill-neutral-disabled); border-color: transparent; } :host([appearance=\"plain\"]) .button:disabled, :host([appearance=\"plain\"]) .button[aria-disabled=\"true\"] { background: transparent; } ::slotted([slot=\"icon\"]), .icon { flex: none; width: var(--gw-icon-size); height: var(--gw-icon-size); fill: currentColor; } /* The icon and the ring share one cell and crossfade */ .lead { display: grid; place-items: center; } /* The icon slot is display: contents, so its slotted icon is a grid item of its own and needs the shared cell too */ .lead > *, ::slotted([slot=\"icon\"]) { grid-area: 1 / 1; } /* A color dot in the icon's place */ .swatch { width: calc(var(--gw-icon-size) * 0.6); height: calc(var(--gw-icon-size) * 0.6); border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .lead > *, .text, .ring { transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1); } /* Sized to the icon's drawn area, which is inset in its 24px box */ .ring { width: calc(var(--gw-icon-size) * 0.8); height: calc(var(--gw-icon-size) * 0.8); border: calc(2px * var(--gw-scale)) solid color-mix(in oklch, currentColor 25%, transparent); border-top-color: currentColor; border-radius: 50%; opacity: 0; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Without an icon, the ring sits over the hidden text */ .ring.over { position: absolute; inset: 0; margin: auto; } :host([loading]) .button { cursor: progress; } /* The icon stays for --gw-motion-delay, so a quick action never flashes the ring, then they crossfade; ending is immediate */ :host([loading]) .lead > .ring, :host([loading]) .button:not(.has-icon) > .ring.over { opacity: 1; transition-delay: var(--gw-motion-delay, 500ms); } :host([loading]) .lead > :not(.ring), :host([loading]) .button:not(.has-icon) > .text { opacity: 0; transition-delay: var(--gw-motion-delay, 500ms); } .button:not(.has-icon) > .lead { display: none; } /* Icon only, to save space: square, with the text kept for assistive technology. auto does this on narrow screens */ :host([labels=\"hide\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"hide\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } @media (width < 40rem) { :host([labels=\"auto\"]) .button.has-icon { min-width: calc(2.5rem * var(--gw-scale)); padding: 0; } :host([labels=\"auto\"]) .has-icon .text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } } `, ]",
|
|
3149
3149
|
"inheritedFrom": {
|
|
3150
3150
|
"name": "GwElement",
|
|
3151
3151
|
"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); } /* While a chosen action runs, a ring takes the icon's place
|
|
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); } /* While a chosen action runs, a ring takes the icon's place: they share one cell and crossfade after --gw-motion-delay, so a quick action never flashes it and nothing moves. Without an icon, the ring sits over the hidden text. */ .lead { display: grid; flex: none; place-items: center; } .trigger:not(.has-icon) > .lead { display: contents; } .lead > * { grid-area: 1 / 1; } .lead > *, .label { transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1); } .ring { width: calc(var(--gw-icon-size) * 0.8); height: calc(var(--gw-icon-size) * 0.8); border: calc(2px * var(--gw-scale)) solid color-mix(in oklch, currentColor 25%, transparent); border-top-color: currentColor; border-radius: 50%; opacity: 0; animation: spin 0.9s linear infinite; } .trigger:not(.has-icon) .ring { position: absolute; inset: 0; margin: auto; } .trigger { position: relative; } @keyframes spin { to { transform: rotate(360deg); } } :host([loading]) .trigger { cursor: progress; } :host([loading]) .ring { opacity: 1; transition-delay: var(--gw-motion-delay, 500ms); } :host([loading]) .lead > .icon, :host([loading]) .trigger:not(.has-icon) > .label { opacity: 0; transition-delay: var(--gw-motion-delay, 500ms); } /* 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; } .item[role=\"menuitemcheckbox\"] .box { flex: none; color: var(--gw-color-text-secondary); } .item[role=\"menuitemcheckbox\"][aria-checked=\"true\"] .box { color: var(--gw-color-accent); } .group { display: flex; flex-direction: column; gap: var(--gw-space-0-5); } .group:not(:first-child) { padding-block-start: var(--gw-space-1); margin-block-start: var(--gw-space-0-5); border-block-start: max(var(--gw-border-width), 1px) solid var(--gw-color-border-normal); } .group-heading { padding: var(--gw-space-1) var(--gw-space-3) 0; margin: 0; font-size: var(--gw-font-size-xs); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); } .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"
|
|
@@ -11121,6 +11121,12 @@
|
|
|
11121
11121
|
"kind": "class",
|
|
11122
11122
|
"description": "A labeled bar for a value in a range, such as disk use. Past `high` it\nturns warning, and past `critical` danger, unless a `tone` is set. While\n`loading`, the track pulses and a skeleton stands in for the value.",
|
|
11123
11123
|
"name": "GwMeter",
|
|
11124
|
+
"cssProperties": [
|
|
11125
|
+
{
|
|
11126
|
+
"description": "The bar's height. Defaults to `0.5rem`.",
|
|
11127
|
+
"name": "--gw-meter-height"
|
|
11128
|
+
}
|
|
11129
|
+
],
|
|
11124
11130
|
"cssParts": [
|
|
11125
11131
|
{
|
|
11126
11132
|
"description": "The row with the label and value.",
|
|
@@ -11137,6 +11143,10 @@
|
|
|
11137
11143
|
{
|
|
11138
11144
|
"description": "The filled part of the bar.",
|
|
11139
11145
|
"name": "fill"
|
|
11146
|
+
},
|
|
11147
|
+
{
|
|
11148
|
+
"description": "The line under the bar.",
|
|
11149
|
+
"name": "caption"
|
|
11140
11150
|
}
|
|
11141
11151
|
],
|
|
11142
11152
|
"members": [
|
|
@@ -11147,7 +11157,7 @@
|
|
|
11147
11157
|
"text": "Properties"
|
|
11148
11158
|
},
|
|
11149
11159
|
"static": true,
|
|
11150
|
-
"default": "{ label: {}, value: { type: \"number\" }, min: { type: \"number\" }, max: { type: \"number\" }, high: { type: \"number\" }, critical: { type: \"number\" }, display: {}, tone: { reflect: true }, loading: { type: \"boolean\", reflect: true }, }",
|
|
11160
|
+
"default": "{ label: {}, value: { type: \"number\" }, min: { type: \"number\" }, max: { type: \"number\" }, high: { type: \"number\" }, critical: { type: \"number\" }, inverted: { type: \"boolean\" }, display: {}, caption: {}, tone: { reflect: true }, loading: { type: \"boolean\", reflect: true }, }",
|
|
11151
11161
|
"inheritedFrom": {
|
|
11152
11162
|
"name": "GwElement",
|
|
11153
11163
|
"module": "src/element.ts"
|
|
@@ -11160,7 +11170,7 @@
|
|
|
11160
11170
|
"text": "readonly CSSStyleSheet[]"
|
|
11161
11171
|
},
|
|
11162
11172
|
"static": true,
|
|
11163
|
-
"default": "[ base, css` :host { display: block; } .top { display: flex; gap: var(--gw-space-2); justify-content: space-between; margin-block-end: var(--gw-space-1); font-size: var(--gw-font-size-s); } .value { font-variant-numeric: var(--gw-font-figures); color: var(--gw-color-text-strong); } .track { height: 0.5rem; overflow: hidden; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-quiet); border-radius: var(--gw-radius-pill); } .fill { height: 100%; background: var(--gw-meter-color, var(--gw-color-fill-accent-resting)); border-radius: inherit; transition: width var(--gw-motion-slow) var(--gw-motion-easing); } .bar { min-height: 0; height: 0.5rem; --gw-skeleton-radius: var(--gw-radius-pill); } `, ]",
|
|
11173
|
+
"default": "[ base, css` :host { display: block; } .top { display: flex; gap: var(--gw-space-2); justify-content: space-between; margin-block-end: var(--gw-space-1); font-size: var(--gw-font-size-s); } .value { font-variant-numeric: var(--gw-font-figures); color: var(--gw-color-text-strong); } .track { height: var(--gw-meter-height, 0.5rem); overflow: hidden; background: var(--gw-color-surface-lower); border: var(--gw-border-width) solid var(--gw-color-border-quiet); border-radius: var(--gw-radius-pill); } .fill { height: 100%; background: var(--gw-meter-color, var(--gw-color-fill-accent-resting)); border-radius: inherit; transition: width var(--gw-motion-slow) var(--gw-motion-easing); } .bar { min-height: 0; height: var(--gw-meter-height, 0.5rem); --gw-skeleton-radius: var(--gw-radius-pill); } .caption { display: block; margin-block-start: var(--gw-space-1); overflow: hidden; font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); text-overflow: ellipsis; white-space: nowrap; } .caption[hidden] { display: none; } `, ]",
|
|
11164
11174
|
"inheritedFrom": {
|
|
11165
11175
|
"name": "GwElement",
|
|
11166
11176
|
"module": "src/element.ts"
|
|
@@ -11178,7 +11188,7 @@
|
|
|
11178
11188
|
"kind": "field",
|
|
11179
11189
|
"name": "value",
|
|
11180
11190
|
"type": {
|
|
11181
|
-
"text": "number"
|
|
11191
|
+
"text": "number | undefined"
|
|
11182
11192
|
},
|
|
11183
11193
|
"default": "0"
|
|
11184
11194
|
},
|
|
@@ -11214,6 +11224,14 @@
|
|
|
11214
11224
|
},
|
|
11215
11225
|
"default": "undefined"
|
|
11216
11226
|
},
|
|
11227
|
+
{
|
|
11228
|
+
"kind": "field",
|
|
11229
|
+
"name": "inverted",
|
|
11230
|
+
"type": {
|
|
11231
|
+
"text": "boolean"
|
|
11232
|
+
},
|
|
11233
|
+
"default": "false"
|
|
11234
|
+
},
|
|
11217
11235
|
{
|
|
11218
11236
|
"kind": "field",
|
|
11219
11237
|
"name": "display",
|
|
@@ -11222,6 +11240,14 @@
|
|
|
11222
11240
|
},
|
|
11223
11241
|
"default": "\"\""
|
|
11224
11242
|
},
|
|
11243
|
+
{
|
|
11244
|
+
"kind": "field",
|
|
11245
|
+
"name": "caption",
|
|
11246
|
+
"type": {
|
|
11247
|
+
"text": "string"
|
|
11248
|
+
},
|
|
11249
|
+
"default": "\"\""
|
|
11250
|
+
},
|
|
11225
11251
|
{
|
|
11226
11252
|
"kind": "field",
|
|
11227
11253
|
"name": "tone",
|
|
@@ -11286,6 +11312,12 @@
|
|
|
11286
11312
|
"privacy": "private",
|
|
11287
11313
|
"readonly": true
|
|
11288
11314
|
},
|
|
11315
|
+
{
|
|
11316
|
+
"kind": "field",
|
|
11317
|
+
"name": "#caption",
|
|
11318
|
+
"privacy": "private",
|
|
11319
|
+
"readonly": true
|
|
11320
|
+
},
|
|
11289
11321
|
{
|
|
11290
11322
|
"kind": "field",
|
|
11291
11323
|
"name": "#rendered",
|
|
@@ -11295,6 +11327,25 @@
|
|
|
11295
11327
|
},
|
|
11296
11328
|
"default": "false"
|
|
11297
11329
|
},
|
|
11330
|
+
{
|
|
11331
|
+
"kind": "field",
|
|
11332
|
+
"name": "#known",
|
|
11333
|
+
"privacy": "private",
|
|
11334
|
+
"readonly": true
|
|
11335
|
+
},
|
|
11336
|
+
{
|
|
11337
|
+
"kind": "method",
|
|
11338
|
+
"name": "#worse",
|
|
11339
|
+
"privacy": "private",
|
|
11340
|
+
"parameters": [
|
|
11341
|
+
{
|
|
11342
|
+
"name": "limit",
|
|
11343
|
+
"type": {
|
|
11344
|
+
"text": "number | undefined"
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
]
|
|
11348
|
+
},
|
|
11298
11349
|
{
|
|
11299
11350
|
"kind": "field",
|
|
11300
11351
|
"name": "#color",
|
|
@@ -11625,7 +11676,7 @@
|
|
|
11625
11676
|
"type": {
|
|
11626
11677
|
"text": "number"
|
|
11627
11678
|
},
|
|
11628
|
-
"description": "The value. Defaults to 0.",
|
|
11679
|
+
"description": "The value. Defaults to 0. Leave it unset or `NaN` for an unknown value, shown as `-` with an empty bar.",
|
|
11629
11680
|
"name": "value"
|
|
11630
11681
|
},
|
|
11631
11682
|
{
|
|
@@ -11656,6 +11707,13 @@
|
|
|
11656
11707
|
"description": "Past this it turns danger.",
|
|
11657
11708
|
"name": "critical"
|
|
11658
11709
|
},
|
|
11710
|
+
{
|
|
11711
|
+
"type": {
|
|
11712
|
+
"text": "boolean"
|
|
11713
|
+
},
|
|
11714
|
+
"description": "Lower is worse, such as for a battery: at or below `high` it turns warning, and at or below `critical` danger.",
|
|
11715
|
+
"name": "inverted"
|
|
11716
|
+
},
|
|
11659
11717
|
{
|
|
11660
11718
|
"type": {
|
|
11661
11719
|
"text": "string"
|
|
@@ -11663,6 +11721,13 @@
|
|
|
11663
11721
|
"description": "The value as shown, such as `42 GB`. Defaults to a percentage.",
|
|
11664
11722
|
"name": "display"
|
|
11665
11723
|
},
|
|
11724
|
+
{
|
|
11725
|
+
"type": {
|
|
11726
|
+
"text": "string"
|
|
11727
|
+
},
|
|
11728
|
+
"description": "A quiet line under the bar.",
|
|
11729
|
+
"name": "caption"
|
|
11730
|
+
},
|
|
11666
11731
|
{
|
|
11667
11732
|
"type": {
|
|
11668
11733
|
"text": "\"neutral\"|\"accent\"|\"info\"|\"success\"|\"warning\"|\"danger\"|\"important\""
|
|
@@ -17216,7 +17281,7 @@
|
|
|
17216
17281
|
"declarations": [
|
|
17217
17282
|
{
|
|
17218
17283
|
"kind": "class",
|
|
17219
|
-
"description": "A placeholder for what's loading, sized by its container or by `width`\nand `height` in CSS. The shapes below extend it, and components with a\n`loading` attribute draw their own from them, so a spinner is only needed\nfor an action in progress. It's hidden from assistive technology, so mark\nthe region it sits in `aria-busy`.",
|
|
17284
|
+
"description": "A placeholder for what's loading, sized by its container or by `width`\nand `height` in CSS. The shapes below extend it, and components with a\n`loading` attribute draw their own from them, so a spinner is only needed\nfor an action in progress. It stays invisible for `--gw-motion-delay`, then\nfades in and pulses, so a quick load never flashes it; it keeps its size\nthroughout, so nothing moves. It's hidden from assistive technology, so mark\nthe region it sits in `aria-busy`.",
|
|
17220
17285
|
"name": "GwSkeleton",
|
|
17221
17286
|
"cssProperties": [
|
|
17222
17287
|
{
|
|
@@ -17226,6 +17291,10 @@
|
|
|
17226
17291
|
{
|
|
17227
17292
|
"description": "The corners. Defaults to the control radius.",
|
|
17228
17293
|
"name": "--gw-skeleton-radius"
|
|
17294
|
+
},
|
|
17295
|
+
{
|
|
17296
|
+
"description": "How long it waits before fading in. Defaults to `500ms`; set `0ms` to show it at once.",
|
|
17297
|
+
"name": "--gw-motion-delay"
|
|
17229
17298
|
}
|
|
17230
17299
|
],
|
|
17231
17300
|
"members": [
|
|
@@ -17236,7 +17305,7 @@
|
|
|
17236
17305
|
"text": "readonly CSSStyleSheet[]"
|
|
17237
17306
|
},
|
|
17238
17307
|
"static": true,
|
|
17239
|
-
"default": "[ base, css` :host { display: block; min-height: 1rem; background: var( --gw-skeleton-color, var(--gw-color-fill-neutral-resting) ); border-radius: var(--gw-skeleton-radius, var(--gw-radius-control)); animation: pulse 2s ease-in-out infinite; } @keyframes pulse { 50% { opacity: 0.45; } } @media (forced-colors: active) { :host { background: GrayText; } } `, ]",
|
|
17308
|
+
"default": "[ base, css` :host { display: block; min-height: 1rem; background: var( --gw-skeleton-color, var(--gw-color-fill-neutral-resting) ); border-radius: var(--gw-skeleton-radius, var(--gw-radius-control)); /* Waits, fades in, then pulses, so a quick load never flashes it */ animation: gw-appear var(--gw-motion-slow, 350ms) cubic-bezier(0.4, 0, 0.2, 1) var(--gw-motion-delay, 500ms) backwards, pulse 2s ease-in-out calc(var(--gw-motion-delay, 500ms) + var(--gw-motion-slow, 350ms)) infinite; } @keyframes pulse { 50% { opacity: 0.45; } } @media (forced-colors: active) { :host { background: GrayText; } } `, ]",
|
|
17240
17309
|
"inheritedFrom": {
|
|
17241
17310
|
"name": "GwElement",
|
|
17242
17311
|
"module": "src/element.ts"
|
|
@@ -17599,6 +17668,14 @@
|
|
|
17599
17668
|
"name": "GwSkeleton",
|
|
17600
17669
|
"module": "src/gw-skeleton.ts"
|
|
17601
17670
|
}
|
|
17671
|
+
},
|
|
17672
|
+
{
|
|
17673
|
+
"description": "How long it waits before fading in. Defaults to `500ms`; set `0ms` to show it at once.",
|
|
17674
|
+
"name": "--gw-motion-delay",
|
|
17675
|
+
"inheritedFrom": {
|
|
17676
|
+
"name": "GwSkeleton",
|
|
17677
|
+
"module": "src/gw-skeleton.ts"
|
|
17678
|
+
}
|
|
17602
17679
|
}
|
|
17603
17680
|
],
|
|
17604
17681
|
"members": [
|
|
@@ -18346,6 +18423,14 @@
|
|
|
18346
18423
|
"name": "GwSkeleton",
|
|
18347
18424
|
"module": "src/gw-skeleton.ts"
|
|
18348
18425
|
}
|
|
18426
|
+
},
|
|
18427
|
+
{
|
|
18428
|
+
"description": "How long it waits before fading in. Defaults to `500ms`; set `0ms` to show it at once.",
|
|
18429
|
+
"name": "--gw-motion-delay",
|
|
18430
|
+
"inheritedFrom": {
|
|
18431
|
+
"name": "GwSkeleton",
|
|
18432
|
+
"module": "src/gw-skeleton.ts"
|
|
18433
|
+
}
|
|
18349
18434
|
}
|
|
18350
18435
|
],
|
|
18351
18436
|
"members": [
|
|
@@ -18737,12 +18822,16 @@
|
|
|
18737
18822
|
"declarations": [
|
|
18738
18823
|
{
|
|
18739
18824
|
"kind": "class",
|
|
18740
|
-
"description": "A spinning ring while something loads, announced by its `label`.",
|
|
18825
|
+
"description": "A spinning ring while something loads, announced by its `label`. It keeps\nits space but stays invisible for `--gw-motion-delay`, then fades in, so a\nquick wait never flashes it.",
|
|
18741
18826
|
"name": "GwSpinner",
|
|
18742
18827
|
"cssProperties": [
|
|
18743
18828
|
{
|
|
18744
18829
|
"description": "The ring's size. Defaults to `1.25rem`.",
|
|
18745
18830
|
"name": "--gw-spinner-size"
|
|
18831
|
+
},
|
|
18832
|
+
{
|
|
18833
|
+
"description": "How long it waits before fading in. Defaults to `500ms`.",
|
|
18834
|
+
"name": "--gw-motion-delay"
|
|
18746
18835
|
}
|
|
18747
18836
|
],
|
|
18748
18837
|
"cssParts": [
|
|
@@ -18772,7 +18861,7 @@
|
|
|
18772
18861
|
"text": "readonly CSSStyleSheet[]"
|
|
18773
18862
|
},
|
|
18774
18863
|
"static": true,
|
|
18775
|
-
"default": "[ base, css` :host { display: inline-flex; width: var(--gw-spinner-size, 1.25rem); height: var(--gw-spinner-size, 1.25rem); color: var(--gw-color-accent); } .ring { width: 100%; height: 100%; border: 2px solid var(--gw-color-border-quiet); border-top-color: currentColor; border-radius: 50%; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); } `, ]",
|
|
18864
|
+
"default": "[ base, css` :host { display: inline-flex; width: var(--gw-spinner-size, 1.25rem); height: var(--gw-spinner-size, 1.25rem); color: var(--gw-color-accent); animation: gw-appear var(--gw-motion-slow, 350ms) cubic-bezier(0.4, 0, 0.2, 1) var(--gw-motion-delay, 500ms) backwards; } .ring { width: 100%; height: 100%; border: 2px solid var(--gw-color-border-quiet); border-top-color: currentColor; border-radius: 50%; animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); } `, ]",
|
|
18776
18865
|
"inheritedFrom": {
|
|
18777
18866
|
"name": "GwElement",
|
|
18778
18867
|
"module": "src/element.ts"
|
|
@@ -22959,7 +23048,7 @@
|
|
|
22959
23048
|
{
|
|
22960
23049
|
"kind": "variable",
|
|
22961
23050
|
"name": "base",
|
|
22962
|
-
"default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } :host([hidden]), [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } /* :host too, since * in a shadow root doesn't match it, and the skeletons pulse on their host */ @media (prefers-reduced-motion: reduce) { :host, *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
|
|
23051
|
+
"default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; } :host([hidden]), [hidden] { display: none !important; } :focus-visible { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } .icon { flex: none; width: var(--gw-icon-size, 1.25rem); height: var(--gw-icon-size, 1.25rem); fill: currentColor; } :host([tone=\"accent\"]) { --gw-tone: var(--gw-color-accent); --gw-tone-fill: var(--gw-color-fill-accent-quiet); --gw-tone-border: var(--gw-color-border-accent); } :host([tone=\"info\"]) { --gw-tone: var(--gw-color-info); --gw-tone-fill: var(--gw-color-fill-info-quiet); --gw-tone-border: var(--gw-color-border-info); } :host([tone=\"success\"]) { --gw-tone: var(--gw-color-success); --gw-tone-fill: var(--gw-color-fill-success-quiet); --gw-tone-border: var(--gw-color-border-success); } :host([tone=\"warning\"]) { --gw-tone: var(--gw-color-warning); --gw-tone-fill: var(--gw-color-fill-warning-quiet); --gw-tone-border: var(--gw-color-border-warning); } :host([tone=\"danger\"]) { --gw-tone: var(--gw-color-danger); --gw-tone-fill: var(--gw-color-fill-danger-quiet); --gw-tone-border: var(--gw-color-border-danger); } :host([tone=\"important\"]) { --gw-tone: var(--gw-color-important); --gw-tone-fill: var(--gw-color-fill-important-quiet); --gw-tone-border: var(--gw-color-border-important); } /* A loading placeholder's entrance: hidden for --gw-motion-delay, so a quick load never flashes one, then a fade. Use it with appear below. */ @keyframes gw-appear { from { opacity: 0; } } /* :host too, since * in a shadow root doesn't match it, and the skeletons pulse on their host */ @media (prefers-reduced-motion: reduce) { :host, *, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; } } `",
|
|
22963
23052
|
"description": "What every component shares: box sizing, the focus ring, icons and the\ntones that set `--gw-tone` from a `tone` attribute."
|
|
22964
23053
|
},
|
|
22965
23054
|
{
|
package/package.json
CHANGED
package/src/gw-button.ts
CHANGED
|
@@ -295,14 +295,18 @@ export class GwButton extends GwElement {
|
|
|
295
295
|
cursor: progress;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
/* The icon stays for --gw-motion-delay, so a quick action never
|
|
299
|
+
flashes the ring, then they crossfade; ending is immediate */
|
|
298
300
|
:host([loading]) .lead > .ring,
|
|
299
301
|
:host([loading]) .button:not(.has-icon) > .ring.over {
|
|
300
302
|
opacity: 1;
|
|
303
|
+
transition-delay: var(--gw-motion-delay, 500ms);
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
:host([loading]) .lead > :not(.ring),
|
|
304
307
|
:host([loading]) .button:not(.has-icon) > .text {
|
|
305
308
|
opacity: 0;
|
|
309
|
+
transition-delay: var(--gw-motion-delay, 500ms);
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
.button:not(.has-icon) > .lead {
|
package/src/gw-menu-button.ts
CHANGED
|
@@ -139,19 +139,50 @@ export class GwMenuButton extends GwElement {
|
|
|
139
139
|
background: var(--gw-color-fill-neutral-hovered);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
/* While a chosen action runs, a ring takes the icon's place
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
/* While a chosen action runs, a ring takes the icon's place: they share
|
|
143
|
+
one cell and crossfade after --gw-motion-delay, so a quick action
|
|
144
|
+
never flashes it and nothing moves. Without an icon, the ring sits
|
|
145
|
+
over the hidden text. */
|
|
146
|
+
.lead {
|
|
147
|
+
display: grid;
|
|
145
148
|
flex: none;
|
|
149
|
+
place-items: center;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.trigger:not(.has-icon) > .lead {
|
|
153
|
+
display: contents;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.lead > * {
|
|
157
|
+
grid-area: 1 / 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.lead > *,
|
|
161
|
+
.label {
|
|
162
|
+
transition: opacity var(--gw-motion-normal) cubic-bezier(0.4, 0, 0.2, 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ring {
|
|
146
166
|
width: calc(var(--gw-icon-size) * 0.8);
|
|
147
167
|
height: calc(var(--gw-icon-size) * 0.8);
|
|
148
168
|
border: calc(2px * var(--gw-scale)) solid
|
|
149
169
|
color-mix(in oklch, currentColor 25%, transparent);
|
|
150
170
|
border-top-color: currentColor;
|
|
151
171
|
border-radius: 50%;
|
|
172
|
+
opacity: 0;
|
|
152
173
|
animation: spin 0.9s linear infinite;
|
|
153
174
|
}
|
|
154
175
|
|
|
176
|
+
.trigger:not(.has-icon) .ring {
|
|
177
|
+
position: absolute;
|
|
178
|
+
inset: 0;
|
|
179
|
+
margin: auto;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.trigger {
|
|
183
|
+
position: relative;
|
|
184
|
+
}
|
|
185
|
+
|
|
155
186
|
@keyframes spin {
|
|
156
187
|
to {
|
|
157
188
|
transform: rotate(360deg);
|
|
@@ -163,11 +194,14 @@ export class GwMenuButton extends GwElement {
|
|
|
163
194
|
}
|
|
164
195
|
|
|
165
196
|
:host([loading]) .ring {
|
|
166
|
-
|
|
197
|
+
opacity: 1;
|
|
198
|
+
transition-delay: var(--gw-motion-delay, 500ms);
|
|
167
199
|
}
|
|
168
200
|
|
|
169
|
-
:host([loading]) .
|
|
170
|
-
|
|
201
|
+
:host([loading]) .lead > .icon,
|
|
202
|
+
:host([loading]) .trigger:not(.has-icon) > .label {
|
|
203
|
+
opacity: 0;
|
|
204
|
+
transition-delay: var(--gw-motion-delay, 500ms);
|
|
171
205
|
}
|
|
172
206
|
|
|
173
207
|
/* The other appearances draw the trigger as gw-button draws a neutral
|
|
@@ -689,8 +723,12 @@ export class GwMenuButton extends GwElement {
|
|
|
689
723
|
|
|
690
724
|
protected override create(root: ShadowRoot) {
|
|
691
725
|
this.#trigger.append(
|
|
692
|
-
|
|
693
|
-
|
|
726
|
+
h(
|
|
727
|
+
"span",
|
|
728
|
+
{ class: "lead" },
|
|
729
|
+
this.#triggerIcon,
|
|
730
|
+
h("span", { class: "ring", "aria-hidden": "true" }),
|
|
731
|
+
),
|
|
694
732
|
this.#triggerText,
|
|
695
733
|
h("span", { class: "chevron", "aria-hidden": "true" }),
|
|
696
734
|
);
|
package/src/gw-skeleton.ts
CHANGED
|
@@ -12,7 +12,9 @@ import { base } from "./styles.js";
|
|
|
12
12
|
* A placeholder for what's loading, sized by its container or by `width`
|
|
13
13
|
* and `height` in CSS. The shapes below extend it, and components with a
|
|
14
14
|
* `loading` attribute draw their own from them, so a spinner is only needed
|
|
15
|
-
* for an action in progress. It
|
|
15
|
+
* for an action in progress. It stays invisible for `--gw-motion-delay`, then
|
|
16
|
+
* fades in and pulses, so a quick load never flashes it; it keeps its size
|
|
17
|
+
* throughout, so nothing moves. It's hidden from assistive technology, so mark
|
|
16
18
|
* the region it sits in `aria-busy`.
|
|
17
19
|
*
|
|
18
20
|
* @element gw-skeleton
|
|
@@ -20,6 +22,7 @@ import { base } from "./styles.js";
|
|
|
20
22
|
*
|
|
21
23
|
* @cssprop --gw-skeleton-color - The fill. Defaults to the neutral fill.
|
|
22
24
|
* @cssprop --gw-skeleton-radius - The corners. Defaults to the control radius.
|
|
25
|
+
* @cssprop --gw-motion-delay - How long it waits before fading in. Defaults to `500ms`; set `0ms` to show it at once.
|
|
23
26
|
*/
|
|
24
27
|
export class GwSkeleton extends GwElement {
|
|
25
28
|
static override styles: readonly CSSStyleSheet[] = [
|
|
@@ -33,7 +36,13 @@ export class GwSkeleton extends GwElement {
|
|
|
33
36
|
var(--gw-color-fill-neutral-resting)
|
|
34
37
|
);
|
|
35
38
|
border-radius: var(--gw-skeleton-radius, var(--gw-radius-control));
|
|
36
|
-
|
|
39
|
+
/* Waits, fades in, then pulses, so a quick load never flashes it */
|
|
40
|
+
animation:
|
|
41
|
+
gw-appear var(--gw-motion-slow, 350ms) cubic-bezier(0.4, 0, 0.2, 1)
|
|
42
|
+
var(--gw-motion-delay, 500ms) backwards,
|
|
43
|
+
pulse 2s ease-in-out
|
|
44
|
+
calc(var(--gw-motion-delay, 500ms) + var(--gw-motion-slow, 350ms))
|
|
45
|
+
infinite;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
@keyframes pulse {
|
package/src/gw-spinner.ts
CHANGED
|
@@ -8,7 +8,9 @@ import { GwElement, css, h } from "./element.js";
|
|
|
8
8
|
import { base } from "./styles.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* A spinning ring while something loads, announced by its `label`.
|
|
11
|
+
* A spinning ring while something loads, announced by its `label`. It keeps
|
|
12
|
+
* its space but stays invisible for `--gw-motion-delay`, then fades in, so a
|
|
13
|
+
* quick wait never flashes it.
|
|
12
14
|
*
|
|
13
15
|
* @element gw-spinner
|
|
14
16
|
* @summary A spinning ring for a wait a skeleton can't stand in for.
|
|
@@ -18,6 +20,7 @@ import { base } from "./styles.js";
|
|
|
18
20
|
* @csspart ring - The spinning ring.
|
|
19
21
|
*
|
|
20
22
|
* @cssprop --gw-spinner-size - The ring's size. Defaults to `1.25rem`.
|
|
23
|
+
* @cssprop --gw-motion-delay - How long it waits before fading in. Defaults to `500ms`.
|
|
21
24
|
*/
|
|
22
25
|
export class GwSpinner extends GwElement {
|
|
23
26
|
static override properties = {
|
|
@@ -32,6 +35,8 @@ export class GwSpinner extends GwElement {
|
|
|
32
35
|
width: var(--gw-spinner-size, 1.25rem);
|
|
33
36
|
height: var(--gw-spinner-size, 1.25rem);
|
|
34
37
|
color: var(--gw-color-accent);
|
|
38
|
+
animation: gw-appear var(--gw-motion-slow, 350ms)
|
|
39
|
+
cubic-bezier(0.4, 0, 0.2, 1) var(--gw-motion-delay, 500ms) backwards;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
.ring {
|
package/src/styles.ts
CHANGED
|
@@ -101,6 +101,14 @@ export const base = css`
|
|
|
101
101
|
--gw-tone-border: var(--gw-color-border-important);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/* A loading placeholder's entrance: hidden for --gw-motion-delay, so a
|
|
105
|
+
quick load never flashes one, then a fade. Use it with appear below. */
|
|
106
|
+
@keyframes gw-appear {
|
|
107
|
+
from {
|
|
108
|
+
opacity: 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
104
112
|
/* :host too, since * in a shadow root doesn't match it, and the skeletons
|
|
105
113
|
pulse on their host */
|
|
106
114
|
@media (prefers-reduced-motion: reduce) {
|