@timmo001/groundwork-ui 0.9.25 → 0.9.27
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 +118 -13
- package/package.json +1 -1
- package/src/gw-app-bar.ts +39 -19
- package/src/gw-button.ts +19 -1
- package/src/gw-card.ts +39 -4
- package/src/gw-facts.ts +8 -2
- package/src/gw-input.ts +5 -5
- package/src/gw-page-header.ts +37 -2
package/custom-elements.json
CHANGED
|
@@ -1485,7 +1485,7 @@
|
|
|
1485
1485
|
"declarations": [
|
|
1486
1486
|
{
|
|
1487
1487
|
"kind": "class",
|
|
1488
|
-
"description": "A site or app header pinned to the top of the page, with a brand, nav\nlinks and actions, full width with its content kept to the content width.\nIt's transparent at first, then gets a blurred surface and a bottom border\nonce the page scrolls, or once the element\n`reveal-after` names scrolls out of view, such as a hero. With\n`reveal-after` the brand stays hidden, taking no room, until then, so a\nlarge logo in the hero hands over to the small one in the bar. The nav\nslides over as it opens.\n\n`overlay` lays it over the top of the page rather than taking room, for a\npage that opens with a hero. Below `menu-below`, 600px wide by default,\nthe nav moves into a menu opened from a button, which closes on Esc, a\nclick outside or a link. Raise it when the nav and actions need more room.",
|
|
1488
|
+
"description": "A site or app header pinned to the top of the page, with a brand, nav\nlinks and actions, full width with its content kept to the content width.\nIt's transparent at first, then gets a blurred surface and a bottom border\nonce the page scrolls, or once the element\n`reveal-after` names scrolls out of view, such as a hero. With\n`reveal-after` the brand stays hidden, taking no room, until then, so a\nlarge logo in the hero hands over to the small one in the bar. The nav\nslides over as it opens.\n\n`overlay` lays it over the top of the page rather than taking room, for a\npage that opens with a hero. Below `menu-below`, 600px wide by default,\nthe nav moves into a menu opened from a button, which closes on Esc, a\nclick outside or a link. Raise it when the nav and actions need more room.\nWith no nav links there's no menu button, so a bar of only a brand and\nactions keeps its layout at every width.",
|
|
1489
1489
|
"name": "GwAppBar",
|
|
1490
1490
|
"cssParts": [
|
|
1491
1491
|
{
|
|
@@ -1540,7 +1540,7 @@
|
|
|
1540
1540
|
"text": "readonly CSSStyleSheet[]"
|
|
1541
1541
|
},
|
|
1542
1542
|
"static": true,
|
|
1543
|
-
"default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } /* The nav keeps its room; the actions take the rest, which a theme picker fills or gives up by moving its controls into its popover */ .nav { display: flex; flex: none; gap: var(--gw-space-1); align-items: center; min-width: 0; } .actions { display: flex; flex: 1 1 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } .menu { display: none; place-items: center; width: 2.5rem; height: 2.5rem; padding: 0; color: var(--gw-color-text-strong); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } .menu:hover { background: var(--gw-color-fill-neutral-hovered); } /* Below menu-below, which sets collapsed, the nav folds into a menu */ :host([collapsed]) .menu { display: inline-grid; } :host([collapsed]) .nav { position: absolute; inset-block-start: 100%; inset-inline: 0; flex-direction: column; gap: var(--gw-space-0-5); align-items: stretch; padding: var(--gw-space-2) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); visibility: hidden; background: var(--gw-color-surface-default); border-block-end: max(var(--gw-border-width), 1px) solid var(--gw-color-border-quiet); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.5rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-normal) allow-discrete; } :host([collapsed][menu-open]) .nav { visibility: visible; opacity: 1; translate: 0 0; } `, ]",
|
|
1543
|
+
"default": "[ base, css` :host { position: sticky; inset-block-start: 0; z-index: 50; display: block; } :host([overlay]) { position: fixed; inset-inline: 0; } .bar { position: relative; display: flex; gap: var(--gw-space-3); align-items: center; min-height: 3.5rem; padding: var(--gw-space-1) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); border-block-end: max(var(--gw-border-width), 1px) solid transparent; transition: background-color var(--gw-motion-normal) var(--gw-motion-easing), border-color var(--gw-motion-normal) var(--gw-motion-easing), backdrop-filter var(--gw-motion-normal) var(--gw-motion-easing); } :host([floating]) .bar, :host([menu-open]) .bar { background: color-mix( in oklab, var(--gw-color-surface-default) 82%, transparent ); border-color: var(--gw-color-border-quiet); backdrop-filter: blur(12px) saturate(1.4); } /* A one-column grid, so a hidden brand can fold its width to nothing and the nav slides over as it opens, rather than leaving a gap */ .brand { display: grid; flex: none; grid-template-columns: 1fr; align-items: center; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing); } .brand > slot { display: flex; align-items: center; min-width: 0; overflow: clip; overflow-clip-margin: calc( var(--gw-focus-width) + var(--gw-focus-offset) ); } :host([reveal-after]:not([floating])) .brand { grid-template-columns: 0fr; margin-inline-end: calc(var(--gw-space-3) * -1); pointer-events: none; visibility: hidden; opacity: 0; translate: 0 -0.25rem; transition: grid-template-columns var(--gw-motion-slow) var(--gw-motion-easing), margin var(--gw-motion-slow) var(--gw-motion-easing), opacity var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-slow) allow-discrete; } /* The nav keeps its room; the actions take the rest, which a theme picker fills or gives up by moving its controls into its popover */ .nav { display: flex; flex: none; gap: var(--gw-space-1); align-items: center; min-width: 0; } .actions { display: flex; flex: 1 1 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } .menu { display: none; place-items: center; width: 2.5rem; height: 2.5rem; padding: 0; color: var(--gw-color-text-strong); cursor: pointer; background: none; border: 0; border-radius: var(--gw-radius-pill); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } .menu:hover { background: var(--gw-color-fill-neutral-hovered); } /* Below menu-below, which sets collapsed, the nav folds into a menu, unless there's no nav to fold */ :host([collapsed]) .bar:not(.no-nav) .menu { display: inline-grid; } .no-nav .nav { display: none; } :host([collapsed]) .nav { position: absolute; inset-block-start: 100%; inset-inline: 0; flex-direction: column; gap: var(--gw-space-0-5); align-items: stretch; padding: var(--gw-space-2) max(var(--gw-space-3), (100% - var(--gw-content-width)) / 2); visibility: hidden; background: var(--gw-color-surface-default); border-block-end: max(var(--gw-border-width), 1px) solid var(--gw-color-border-quiet); box-shadow: var(--gw-shadow-m); opacity: 0; translate: 0 -0.5rem; transition: opacity var(--gw-motion-fast) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), visibility var(--gw-motion-normal) allow-discrete; } :host([collapsed][menu-open]) .nav { visibility: visible; opacity: 1; translate: 0 0; } `, ]",
|
|
1544
1544
|
"inheritedFrom": {
|
|
1545
1545
|
"name": "GwElement",
|
|
1546
1546
|
"module": "src/element.ts"
|
|
@@ -1630,6 +1630,12 @@
|
|
|
1630
1630
|
"privacy": "private",
|
|
1631
1631
|
"readonly": true
|
|
1632
1632
|
},
|
|
1633
|
+
{
|
|
1634
|
+
"kind": "field",
|
|
1635
|
+
"name": "#navSlot",
|
|
1636
|
+
"privacy": "private",
|
|
1637
|
+
"readonly": true
|
|
1638
|
+
},
|
|
1633
1639
|
{
|
|
1634
1640
|
"kind": "field",
|
|
1635
1641
|
"name": "#nav",
|
|
@@ -1639,6 +1645,12 @@
|
|
|
1639
1645
|
},
|
|
1640
1646
|
"readonly": true
|
|
1641
1647
|
},
|
|
1648
|
+
{
|
|
1649
|
+
"kind": "field",
|
|
1650
|
+
"name": "#bar",
|
|
1651
|
+
"privacy": "private",
|
|
1652
|
+
"readonly": true
|
|
1653
|
+
},
|
|
1642
1654
|
{
|
|
1643
1655
|
"kind": "field",
|
|
1644
1656
|
"name": "#observer",
|
|
@@ -3118,7 +3130,7 @@
|
|
|
3118
3130
|
"text": "Properties"
|
|
3119
3131
|
},
|
|
3120
3132
|
"static": true,
|
|
3121
|
-
"default": "{ variant: { reflect: true }, appearance: { reflect: true }, size: { reflect: true }, type: {}, href: {}, label: {}, disabled: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, icon: {}, swatch: {}, labels: { reflect: true }, }",
|
|
3133
|
+
"default": "{ variant: { reflect: true }, appearance: { reflect: true }, size: { reflect: true }, type: {}, href: {}, label: {}, disabled: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, icon: {}, swatch: {}, labels: { reflect: true }, toggle: { type: \"boolean\", reflect: true }, pressed: { type: \"boolean\", reflect: true }, }",
|
|
3122
3134
|
"inheritedFrom": {
|
|
3123
3135
|
"name": "GwElement",
|
|
3124
3136
|
"module": "src/element.ts"
|
|
@@ -3145,7 +3157,7 @@
|
|
|
3145
3157
|
"text": "readonly CSSStyleSheet[]"
|
|
3146
3158
|
},
|
|
3147
3159
|
"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; } /* 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; } } `, ]",
|
|
3160
|
+
"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, :host([toggle][pressed]:is([appearance=\"plain\"], [appearance=\"outlined\"])) .button { background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent); } :host([toggle][pressed][appearance=\"filled\"]) .button, :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
3161
|
"inheritedFrom": {
|
|
3150
3162
|
"name": "GwElement",
|
|
3151
3163
|
"module": "src/element.ts"
|
|
@@ -3243,6 +3255,24 @@
|
|
|
3243
3255
|
"description": "Whether the text shows beside an icon: always, never, or not on narrow screens.",
|
|
3244
3256
|
"default": "\"show\""
|
|
3245
3257
|
},
|
|
3258
|
+
{
|
|
3259
|
+
"kind": "field",
|
|
3260
|
+
"name": "toggle",
|
|
3261
|
+
"type": {
|
|
3262
|
+
"text": "boolean"
|
|
3263
|
+
},
|
|
3264
|
+
"description": "Makes it a toggle button, announced as pressed or not.",
|
|
3265
|
+
"default": "false"
|
|
3266
|
+
},
|
|
3267
|
+
{
|
|
3268
|
+
"kind": "field",
|
|
3269
|
+
"name": "pressed",
|
|
3270
|
+
"type": {
|
|
3271
|
+
"text": "boolean"
|
|
3272
|
+
},
|
|
3273
|
+
"description": "Whether a toggle button is on.",
|
|
3274
|
+
"default": "false"
|
|
3275
|
+
},
|
|
3246
3276
|
{
|
|
3247
3277
|
"kind": "field",
|
|
3248
3278
|
"name": "#icon",
|
|
@@ -3679,6 +3709,20 @@
|
|
|
3679
3709
|
},
|
|
3680
3710
|
"description": "Whether the text shows beside an icon: always, never, or not on narrow screens. Defaults to `show`.",
|
|
3681
3711
|
"name": "labels"
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
"type": {
|
|
3715
|
+
"text": "boolean"
|
|
3716
|
+
},
|
|
3717
|
+
"description": "Makes it a toggle button, which assistive technology announces as pressed or not.",
|
|
3718
|
+
"name": "toggle"
|
|
3719
|
+
},
|
|
3720
|
+
{
|
|
3721
|
+
"type": {
|
|
3722
|
+
"text": "boolean"
|
|
3723
|
+
},
|
|
3724
|
+
"description": "Whether a toggle button is on. It shows the same tint as a current link.",
|
|
3725
|
+
"name": "pressed"
|
|
3682
3726
|
}
|
|
3683
3727
|
],
|
|
3684
3728
|
"superclass": {
|
|
@@ -3761,7 +3805,7 @@
|
|
|
3761
3805
|
"text": "Properties"
|
|
3762
3806
|
},
|
|
3763
3807
|
"static": true,
|
|
3764
|
-
"default": "{ heading: {}, description: {}, level: { type: \"number\" }, href: {}, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
|
|
3808
|
+
"default": "{ heading: {}, description: {}, icon: {}, level: { type: \"number\" }, href: {}, flush: { type: \"boolean\", reflect: true }, loading: { type: \"boolean\", reflect: true }, lines: { type: \"number\" }, }",
|
|
3765
3809
|
"inheritedFrom": {
|
|
3766
3810
|
"name": "GwElement",
|
|
3767
3811
|
"module": "src/element.ts"
|
|
@@ -3774,7 +3818,7 @@
|
|
|
3774
3818
|
"text": "readonly CSSStyleSheet[]"
|
|
3775
3819
|
},
|
|
3776
3820
|
"static": true,
|
|
3777
|
-
"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; } .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; } `, ]",
|
|
3821
|
+
"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; } `, ]",
|
|
3778
3822
|
"inheritedFrom": {
|
|
3779
3823
|
"name": "GwElement",
|
|
3780
3824
|
"module": "src/element.ts"
|
|
@@ -3796,6 +3840,15 @@
|
|
|
3796
3840
|
},
|
|
3797
3841
|
"default": "\"\""
|
|
3798
3842
|
},
|
|
3843
|
+
{
|
|
3844
|
+
"kind": "field",
|
|
3845
|
+
"name": "icon",
|
|
3846
|
+
"type": {
|
|
3847
|
+
"text": "string"
|
|
3848
|
+
},
|
|
3849
|
+
"description": "SVG path data for an icon before the heading.",
|
|
3850
|
+
"default": "\"\""
|
|
3851
|
+
},
|
|
3799
3852
|
{
|
|
3800
3853
|
"kind": "field",
|
|
3801
3854
|
"name": "level",
|
|
@@ -3868,6 +3921,12 @@
|
|
|
3868
3921
|
"privacy": "private",
|
|
3869
3922
|
"readonly": true
|
|
3870
3923
|
},
|
|
3924
|
+
{
|
|
3925
|
+
"kind": "field",
|
|
3926
|
+
"name": "#icon",
|
|
3927
|
+
"privacy": "private",
|
|
3928
|
+
"readonly": true
|
|
3929
|
+
},
|
|
3871
3930
|
{
|
|
3872
3931
|
"kind": "field",
|
|
3873
3932
|
"name": "#header",
|
|
@@ -3898,6 +3957,21 @@
|
|
|
3898
3957
|
"privacy": "private",
|
|
3899
3958
|
"readonly": true
|
|
3900
3959
|
},
|
|
3960
|
+
{
|
|
3961
|
+
"kind": "field",
|
|
3962
|
+
"name": "#bodySlot",
|
|
3963
|
+
"privacy": "private",
|
|
3964
|
+
"readonly": true
|
|
3965
|
+
},
|
|
3966
|
+
{
|
|
3967
|
+
"kind": "field",
|
|
3968
|
+
"name": "#empty",
|
|
3969
|
+
"privacy": "private",
|
|
3970
|
+
"type": {
|
|
3971
|
+
"text": "boolean"
|
|
3972
|
+
},
|
|
3973
|
+
"default": "true"
|
|
3974
|
+
},
|
|
3901
3975
|
{
|
|
3902
3976
|
"kind": "field",
|
|
3903
3977
|
"name": "#body",
|
|
@@ -4271,6 +4345,13 @@
|
|
|
4271
4345
|
"description": "A line under the heading.",
|
|
4272
4346
|
"name": "description"
|
|
4273
4347
|
},
|
|
4348
|
+
{
|
|
4349
|
+
"type": {
|
|
4350
|
+
"text": "string"
|
|
4351
|
+
},
|
|
4352
|
+
"description": "SVG path data for an icon before the heading, such as on a card that links to a section.",
|
|
4353
|
+
"name": "icon"
|
|
4354
|
+
},
|
|
4274
4355
|
{
|
|
4275
4356
|
"type": {
|
|
4276
4357
|
"text": "number"
|
|
@@ -8319,6 +8400,12 @@
|
|
|
8319
8400
|
"name": "value"
|
|
8320
8401
|
}
|
|
8321
8402
|
],
|
|
8403
|
+
"slots": [
|
|
8404
|
+
{
|
|
8405
|
+
"description": "`<dt>` and `<dd>` pairs, in place of `items`, for values with markup such as chips or code.",
|
|
8406
|
+
"name": ""
|
|
8407
|
+
}
|
|
8408
|
+
],
|
|
8322
8409
|
"members": [
|
|
8323
8410
|
{
|
|
8324
8411
|
"kind": "field",
|
|
@@ -8340,7 +8427,7 @@
|
|
|
8340
8427
|
"text": "readonly CSSStyleSheet[]"
|
|
8341
8428
|
},
|
|
8342
8429
|
"static": true,
|
|
8343
|
-
"default": "[ base, css` :host { display: block; } dl { display: grid; grid-template-columns: minmax(7rem, max-content) 1fr; gap: var(--gw-space-1) var(--gw-space-3); margin: 0; font-size: var(--gw-font-size-s); } dt { color: var(--gw-color-text-secondary); } dd { min-width: 0; margin: 0; font-variant-numeric: var(--gw-font-figures); color: var(--gw-color-text-strong); overflow-wrap: anywhere; } `, ]",
|
|
8430
|
+
"default": "[ base, css` :host { display: block; } dl { display: grid; grid-template-columns: minmax(7rem, max-content) 1fr; gap: var(--gw-space-1) var(--gw-space-3); margin: 0; font-size: var(--gw-font-size-s); } dt, ::slotted(dt) { color: var(--gw-color-text-secondary); } dd, ::slotted(dd) { min-width: 0; margin: 0; font-variant-numeric: var(--gw-font-figures); color: var(--gw-color-text-strong); overflow-wrap: anywhere; } `, ]",
|
|
8344
8431
|
"inheritedFrom": {
|
|
8345
8432
|
"name": "GwElement",
|
|
8346
8433
|
"module": "src/element.ts"
|
|
@@ -9857,7 +9944,7 @@
|
|
|
9857
9944
|
"text": "Properties"
|
|
9858
9945
|
},
|
|
9859
9946
|
"static": true,
|
|
9860
|
-
"default": "{ label: {}, name: {}, type: {}, value: {}, placeholder: {}, autocomplete: {}, inputmode: {}, min: {}, max: {}, step: {}, hint: {}, info: {}, error: {}, required: { type: \"boolean\" }, optional: { type: \"boolean\" }, disabled: { type: \"boolean\" }, multiline: { type: \"boolean\", reflect: true }, rows: { type: \"number\" }, monospace: { type: \"boolean\", reflect: true }, reveal: { type: \"boolean\" }, size: { reflect: true }, icon: {},
|
|
9947
|
+
"default": "{ label: {}, name: {}, type: {}, value: {}, placeholder: {}, autocomplete: {}, inputmode: {}, min: {}, max: {}, step: {}, hint: {}, info: {}, error: {}, required: { type: \"boolean\" }, optional: { type: \"boolean\" }, disabled: { type: \"boolean\" }, multiline: { type: \"boolean\", reflect: true }, rows: { type: \"number\" }, monospace: { type: \"boolean\", reflect: true }, reveal: { type: \"boolean\" }, size: { reflect: true }, icon: {}, labelHidden: { type: \"boolean\", attribute: \"label-hidden\", reflect: true }, }",
|
|
9861
9948
|
"inheritedFrom": {
|
|
9862
9949
|
"name": "GwElement",
|
|
9863
9950
|
"module": "src/element.ts"
|
|
@@ -10052,7 +10139,7 @@
|
|
|
10052
10139
|
},
|
|
10053
10140
|
{
|
|
10054
10141
|
"kind": "field",
|
|
10055
|
-
"name": "
|
|
10142
|
+
"name": "labelHidden",
|
|
10056
10143
|
"type": {
|
|
10057
10144
|
"text": "boolean"
|
|
10058
10145
|
},
|
|
@@ -10627,7 +10714,7 @@
|
|
|
10627
10714
|
"text": "boolean"
|
|
10628
10715
|
},
|
|
10629
10716
|
"description": "Keeps the label as the field's accessible name but doesn't show it, for a field in a toolbar whose icon and placeholder say what it's for.",
|
|
10630
|
-
"name": "
|
|
10717
|
+
"name": "label-hidden"
|
|
10631
10718
|
}
|
|
10632
10719
|
],
|
|
10633
10720
|
"superclass": {
|
|
@@ -13769,7 +13856,7 @@
|
|
|
13769
13856
|
"text": "Properties"
|
|
13770
13857
|
},
|
|
13771
13858
|
"static": true,
|
|
13772
|
-
"default": "{ heading: {}, description: {}, icon: {}, }",
|
|
13859
|
+
"default": "{ heading: {}, description: {}, icon: {}, level: { type: \"number\", reflect: true }, }",
|
|
13773
13860
|
"inheritedFrom": {
|
|
13774
13861
|
"name": "GwElement",
|
|
13775
13862
|
"module": "src/element.ts"
|
|
@@ -13782,7 +13869,7 @@
|
|
|
13782
13869
|
"text": "readonly CSSStyleSheet[]"
|
|
13783
13870
|
},
|
|
13784
13871
|
"static": true,
|
|
13785
|
-
"default": "[ base, css` :host { display: flex; gap: var(--gw-space-3); align-items: flex-start; } .start { display: flex; flex: none; align-items: center; min-height: 3rem; } .start[hidden] { display: none; } .tile { display: grid; flex: none; place-items: center; width: 3rem; height: 3rem; color: var(--gw-color-accent); background: var(--gw-color-fill-accent-quiet); border-radius: var(--gw-radius-l); --gw-icon-size: 1.625rem; } .titles { display: flex; flex: 0 1 auto; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; }
|
|
13872
|
+
"default": "[ base, css` :host { display: flex; gap: var(--gw-space-3); align-items: flex-start; } .start { display: flex; flex: none; align-items: center; min-height: 3rem; } .start[hidden] { display: none; } .tile { display: grid; flex: none; place-items: center; width: 3rem; height: 3rem; color: var(--gw-color-accent); background: var(--gw-color-fill-accent-quiet); border-radius: var(--gw-radius-l); --gw-icon-size: 1.625rem; } .titles { display: flex; flex: 0 1 auto; flex-direction: column; gap: var(--gw-space-0-5); min-width: 0; } :host(:not([level=\"1\"])) .tile { width: 2.5rem; height: 2.5rem; border-radius: var(--gw-radius-m); --gw-icon-size: 1.375rem; } :host(:not([level=\"1\"])) .heading { font-size: var(--gw-font-size-l); } :host(:not([level=\"1\"])) .tile:not([hidden]) ~ .actions { min-height: 2.5rem; } .heading { margin: 0; font-size: var(--gw-font-size-2xl); font-weight: var(--gw-font-weight-strong); line-height: 1.2; color: var(--gw-color-text-strong); } p { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* Takes the room the titles leave, and gives it up before them, down to its items' smallest */ .actions { display: flex; flex: 1 1000 auto; gap: var(--gw-space-1); align-items: center; justify-content: flex-end; } /* Centered on the tile when there is one */ .tile:not([hidden]) ~ .actions { min-height: 3rem; } `, ]",
|
|
13786
13873
|
"inheritedFrom": {
|
|
13787
13874
|
"name": "GwElement",
|
|
13788
13875
|
"module": "src/element.ts"
|
|
@@ -13812,6 +13899,15 @@
|
|
|
13812
13899
|
},
|
|
13813
13900
|
"default": "\"\""
|
|
13814
13901
|
},
|
|
13902
|
+
{
|
|
13903
|
+
"kind": "field",
|
|
13904
|
+
"name": "level",
|
|
13905
|
+
"type": {
|
|
13906
|
+
"text": "number"
|
|
13907
|
+
},
|
|
13908
|
+
"description": "The heading level, 1 to 6.",
|
|
13909
|
+
"default": "1"
|
|
13910
|
+
},
|
|
13815
13911
|
{
|
|
13816
13912
|
"kind": "field",
|
|
13817
13913
|
"name": "#icon",
|
|
@@ -13840,7 +13936,9 @@
|
|
|
13840
13936
|
"kind": "field",
|
|
13841
13937
|
"name": "#heading",
|
|
13842
13938
|
"privacy": "private",
|
|
13843
|
-
"
|
|
13939
|
+
"type": {
|
|
13940
|
+
"text": "HTMLHeadingElement"
|
|
13941
|
+
}
|
|
13844
13942
|
},
|
|
13845
13943
|
{
|
|
13846
13944
|
"kind": "field",
|
|
@@ -14181,6 +14279,13 @@
|
|
|
14181
14279
|
},
|
|
14182
14280
|
"description": "SVG path data for the icon tile.",
|
|
14183
14281
|
"name": "icon"
|
|
14282
|
+
},
|
|
14283
|
+
{
|
|
14284
|
+
"type": {
|
|
14285
|
+
"text": "number"
|
|
14286
|
+
},
|
|
14287
|
+
"description": "The heading level, 1 to 6. Defaults to 1. From 2 the header is smaller, for a section of a page that reads like a page of its own.",
|
|
14288
|
+
"name": "level"
|
|
14184
14289
|
}
|
|
14185
14290
|
],
|
|
14186
14291
|
"superclass": {
|
package/package.json
CHANGED
package/src/gw-app-bar.ts
CHANGED
|
@@ -27,6 +27,8 @@ const closeIcon =
|
|
|
27
27
|
* page that opens with a hero. Below `menu-below`, 600px wide by default,
|
|
28
28
|
* the nav moves into a menu opened from a button, which closes on Esc, a
|
|
29
29
|
* click outside or a link. Raise it when the nav and actions need more room.
|
|
30
|
+
* With no nav links there's no menu button, so a bar of only a brand and
|
|
31
|
+
* actions keeps its layout at every width.
|
|
30
32
|
*
|
|
31
33
|
* @element gw-app-bar
|
|
32
34
|
* @summary A header pinned to the top, floating once the page scrolls.
|
|
@@ -178,11 +180,16 @@ export class GwAppBar extends GwElement {
|
|
|
178
180
|
background: var(--gw-color-fill-neutral-hovered);
|
|
179
181
|
}
|
|
180
182
|
|
|
181
|
-
/* Below menu-below, which sets collapsed, the nav folds into a menu
|
|
182
|
-
|
|
183
|
+
/* Below menu-below, which sets collapsed, the nav folds into a menu,
|
|
184
|
+
unless there's no nav to fold */
|
|
185
|
+
:host([collapsed]) .bar:not(.no-nav) .menu {
|
|
183
186
|
display: inline-grid;
|
|
184
187
|
}
|
|
185
188
|
|
|
189
|
+
.no-nav .nav {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
186
193
|
:host([collapsed]) .nav {
|
|
187
194
|
position: absolute;
|
|
188
195
|
inset-block-start: 100%;
|
|
@@ -239,12 +246,16 @@ export class GwAppBar extends GwElement {
|
|
|
239
246
|
this.#menuIcon,
|
|
240
247
|
);
|
|
241
248
|
|
|
249
|
+
readonly #navSlot = h("slot");
|
|
250
|
+
|
|
242
251
|
readonly #nav: HTMLDivElement = h(
|
|
243
252
|
"div",
|
|
244
253
|
{ class: "nav", part: "nav" },
|
|
245
|
-
|
|
254
|
+
this.#navSlot,
|
|
246
255
|
);
|
|
247
256
|
|
|
257
|
+
readonly #bar = h("div", { class: "bar no-nav", part: "bar" });
|
|
258
|
+
|
|
248
259
|
#observer: IntersectionObserver | undefined;
|
|
249
260
|
|
|
250
261
|
#frame = 0;
|
|
@@ -263,8 +274,10 @@ export class GwAppBar extends GwElement {
|
|
|
263
274
|
/** Follows the viewport across `menuBelow`, setting `collapsed`. */
|
|
264
275
|
#watchWidth() {
|
|
265
276
|
this.#narrow?.removeEventListener("change", this.#onNarrow);
|
|
277
|
+
const below = Number(this.menuBelow);
|
|
278
|
+
|
|
266
279
|
this.#narrow = window.matchMedia(
|
|
267
|
-
`(max-width: ${(Number(
|
|
280
|
+
`(max-width: ${(Number.isFinite(below) ? below : 600) - 0.02}px)`,
|
|
268
281
|
);
|
|
269
282
|
this.#narrow.addEventListener("change", this.#onNarrow);
|
|
270
283
|
this.#onNarrow();
|
|
@@ -370,25 +383,32 @@ export class GwAppBar extends GwElement {
|
|
|
370
383
|
}
|
|
371
384
|
});
|
|
372
385
|
|
|
373
|
-
|
|
386
|
+
// Without nav links there's nothing to fold, so no menu button
|
|
387
|
+
this.#navSlot.addEventListener("slotchange", () => {
|
|
388
|
+
this.#bar.classList.toggle(
|
|
389
|
+
"no-nav",
|
|
390
|
+
this.#navSlot
|
|
391
|
+
.assignedNodes()
|
|
392
|
+
.every(
|
|
393
|
+
(node) =>
|
|
394
|
+
node.nodeType === Node.TEXT_NODE &&
|
|
395
|
+
node.textContent?.trim() === "",
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
this.#bar.append(
|
|
401
|
+
// The menu button leads, so a revealed brand opens beside it
|
|
402
|
+
this.#menu,
|
|
403
|
+
h("div", { class: "brand", part: "brand" }, h("slot", { name: "brand" })),
|
|
404
|
+
this.#nav,
|
|
374
405
|
h(
|
|
375
406
|
"div",
|
|
376
|
-
{ class: "
|
|
377
|
-
|
|
378
|
-
this.#menu,
|
|
379
|
-
h(
|
|
380
|
-
"div",
|
|
381
|
-
{ class: "brand", part: "brand" },
|
|
382
|
-
h("slot", { name: "brand" }),
|
|
383
|
-
),
|
|
384
|
-
this.#nav,
|
|
385
|
-
h(
|
|
386
|
-
"div",
|
|
387
|
-
{ class: "actions", part: "actions" },
|
|
388
|
-
h("slot", { name: "actions" }),
|
|
389
|
-
),
|
|
407
|
+
{ class: "actions", part: "actions" },
|
|
408
|
+
h("slot", { name: "actions" }),
|
|
390
409
|
),
|
|
391
410
|
);
|
|
411
|
+
root.append(this.#bar);
|
|
392
412
|
}
|
|
393
413
|
|
|
394
414
|
protected override update(changed: ReadonlySet<string>) {
|
package/src/gw-button.ts
CHANGED
|
@@ -40,6 +40,8 @@ import { base, controlSizes, createIcon, setIconPath } from "./styles.js";
|
|
|
40
40
|
* @attr {string} icon - SVG path data for an icon before the text.
|
|
41
41
|
* @attr {string} swatch - A CSS color, drawn as a small round dot in the icon's place, such as a tag's own color.
|
|
42
42
|
* @attr {"show"|"hide"|"auto"} labels - Whether the text shows beside an icon: always, never, or not on narrow screens. Defaults to `show`.
|
|
43
|
+
* @attr {boolean} toggle - Makes it a toggle button, which assistive technology announces as pressed or not.
|
|
44
|
+
* @attr {boolean} pressed - Whether a toggle button is on. It shows the same tint as a current link.
|
|
43
45
|
*
|
|
44
46
|
* @slot - The text, or an `<a>` holding it, in place of `href`.
|
|
45
47
|
* @slot icon - An icon before the text, in place of `icon`.
|
|
@@ -64,6 +66,8 @@ export class GwButton extends GwElement {
|
|
|
64
66
|
icon: {},
|
|
65
67
|
swatch: {},
|
|
66
68
|
labels: { reflect: true },
|
|
69
|
+
toggle: { type: "boolean", reflect: true },
|
|
70
|
+
pressed: { type: "boolean", reflect: true },
|
|
67
71
|
} as const;
|
|
68
72
|
|
|
69
73
|
/** Focusing the element focuses its native button, such as from a dialog opening. */
|
|
@@ -208,10 +212,13 @@ export class GwButton extends GwElement {
|
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
:host([appearance="plain"]) .button.current,
|
|
211
|
-
:host([appearance="outlined"]) .button.current
|
|
215
|
+
:host([appearance="outlined"]) .button.current,
|
|
216
|
+
:host([toggle][pressed]:is([appearance="plain"], [appearance="outlined"]))
|
|
217
|
+
.button {
|
|
212
218
|
background: color-mix(in oklch, var(--gw-button-tone) 16%, transparent);
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
:host([toggle][pressed][appearance="filled"]) .button,
|
|
215
222
|
:host([appearance="filled"]) .button.current {
|
|
216
223
|
background: color-mix(in oklch, var(--gw-button-tone) 32%, transparent);
|
|
217
224
|
}
|
|
@@ -363,6 +370,10 @@ export class GwButton extends GwElement {
|
|
|
363
370
|
declare swatch: string;
|
|
364
371
|
/** Whether the text shows beside an icon: always, never, or not on narrow screens. */
|
|
365
372
|
declare labels: "show" | "hide" | "auto";
|
|
373
|
+
/** Makes it a toggle button, announced as pressed or not. */
|
|
374
|
+
declare toggle: boolean;
|
|
375
|
+
/** Whether a toggle button is on. */
|
|
376
|
+
declare pressed: boolean;
|
|
366
377
|
|
|
367
378
|
readonly #icon = createIcon("");
|
|
368
379
|
|
|
@@ -403,6 +414,8 @@ export class GwButton extends GwElement {
|
|
|
403
414
|
this.icon = "";
|
|
404
415
|
this.swatch = "";
|
|
405
416
|
this.labels = "show";
|
|
417
|
+
this.toggle = false;
|
|
418
|
+
this.pressed = false;
|
|
406
419
|
this.variant = "brand";
|
|
407
420
|
this.appearance = "accent";
|
|
408
421
|
this.size = "medium";
|
|
@@ -537,6 +550,11 @@ export class GwButton extends GwElement {
|
|
|
537
550
|
} else if (control instanceof HTMLButtonElement) {
|
|
538
551
|
control.type = this.type;
|
|
539
552
|
control.disabled = this.disabled;
|
|
553
|
+
setAttribute(
|
|
554
|
+
control,
|
|
555
|
+
"aria-pressed",
|
|
556
|
+
this.toggle ? String(this.pressed) : null,
|
|
557
|
+
);
|
|
540
558
|
}
|
|
541
559
|
}
|
|
542
560
|
}
|
package/src/gw-card.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { GwElement, css, h, setAttribute } from "./element.js";
|
|
8
8
|
import "./gw-skeleton.js";
|
|
9
9
|
import { animateHeight, fadeIn } from "./motion.js";
|
|
10
|
-
import { base } from "./styles.js";
|
|
10
|
+
import { base, createIcon, setIconPath } from "./styles.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* A surface for one group of content, with an optional heading and actions.
|
|
@@ -26,6 +26,7 @@ import { base } from "./styles.js";
|
|
|
26
26
|
*
|
|
27
27
|
* @attr {string} heading - The card's heading.
|
|
28
28
|
* @attr {string} description - A line under the heading.
|
|
29
|
+
* @attr {string} icon - SVG path data for an icon before the heading, such as on a card that links to a section.
|
|
29
30
|
* @attr {number} level - The heading's level for assistive tech, 2 by default. Use 3 for a card under a section heading.
|
|
30
31
|
* @attr {string} href - Makes the whole card a link to this address.
|
|
31
32
|
* @attr {boolean} flush - Drops the padding, for content that runs to the edges.
|
|
@@ -48,6 +49,7 @@ export class GwCard extends GwElement {
|
|
|
48
49
|
static override properties = {
|
|
49
50
|
heading: {},
|
|
50
51
|
description: {},
|
|
52
|
+
icon: {},
|
|
51
53
|
level: { type: "number" },
|
|
52
54
|
href: {},
|
|
53
55
|
flush: { type: "boolean", reflect: true },
|
|
@@ -117,6 +119,12 @@ export class GwCard extends GwElement {
|
|
|
117
119
|
align-items: center;
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
header > .icon {
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
margin-inline-end: var(--gw-space-1);
|
|
125
|
+
color: var(--gw-color-accent);
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
.titles {
|
|
121
129
|
display: flex;
|
|
122
130
|
flex: 1;
|
|
@@ -162,6 +170,8 @@ export class GwCard extends GwElement {
|
|
|
162
170
|
|
|
163
171
|
declare heading: string;
|
|
164
172
|
declare description: string;
|
|
173
|
+
/** SVG path data for an icon before the heading. */
|
|
174
|
+
declare icon: string;
|
|
165
175
|
/** The heading's level for assistive tech. */
|
|
166
176
|
declare level: number;
|
|
167
177
|
declare href: string;
|
|
@@ -189,9 +199,12 @@ export class GwCard extends GwElement {
|
|
|
189
199
|
part: "description",
|
|
190
200
|
});
|
|
191
201
|
|
|
202
|
+
readonly #icon = createIcon("");
|
|
203
|
+
|
|
192
204
|
readonly #header: HTMLElement = h(
|
|
193
205
|
"header",
|
|
194
206
|
{ part: "header" },
|
|
207
|
+
this.#icon,
|
|
195
208
|
h("div", { class: "titles" }, this.#heading, this.#description),
|
|
196
209
|
h("slot", { name: "actions" }),
|
|
197
210
|
);
|
|
@@ -206,10 +219,14 @@ export class GwCard extends GwElement {
|
|
|
206
219
|
|
|
207
220
|
readonly #skeleton = h("gw-skeleton-lines");
|
|
208
221
|
|
|
222
|
+
readonly #bodySlot = h("slot");
|
|
223
|
+
|
|
224
|
+
#empty = true;
|
|
225
|
+
|
|
209
226
|
readonly #body: HTMLDivElement = h(
|
|
210
227
|
"div",
|
|
211
228
|
{ class: "body", part: "body" },
|
|
212
|
-
|
|
229
|
+
this.#bodySlot,
|
|
213
230
|
);
|
|
214
231
|
|
|
215
232
|
#card: HTMLElement = h("section", { class: "card", part: "card" });
|
|
@@ -222,6 +239,7 @@ export class GwCard extends GwElement {
|
|
|
222
239
|
super();
|
|
223
240
|
this.heading = "";
|
|
224
241
|
this.description = "";
|
|
242
|
+
this.icon = "";
|
|
225
243
|
this.level = 2;
|
|
226
244
|
this.href = "";
|
|
227
245
|
this.flush = false;
|
|
@@ -274,6 +292,17 @@ export class GwCard extends GwElement {
|
|
|
274
292
|
this.#footer.hidden =
|
|
275
293
|
this.#footerSlot.assignedNodes({ flatten: true }).length === 0;
|
|
276
294
|
});
|
|
295
|
+
|
|
296
|
+
// A card with only a header, such as a link to a section, has no body gap
|
|
297
|
+
this.#bodySlot.addEventListener("slotchange", () => {
|
|
298
|
+
this.#empty = this.#bodySlot
|
|
299
|
+
.assignedNodes({ flatten: true })
|
|
300
|
+
.every(
|
|
301
|
+
(node) =>
|
|
302
|
+
node.nodeType === Node.TEXT_NODE && !node.textContent?.trim(),
|
|
303
|
+
);
|
|
304
|
+
this.requestUpdate("loading");
|
|
305
|
+
});
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
/** The `<a>` in the heading slot, or holding it, when there is one. */
|
|
@@ -322,14 +351,20 @@ export class GwCard extends GwElement {
|
|
|
322
351
|
this.#heading.setAttribute("aria-level", String(this.level || 2));
|
|
323
352
|
this.#description.textContent = this.description;
|
|
324
353
|
this.#description.hidden = !this.description;
|
|
354
|
+
setIconPath(this.#icon, this.icon);
|
|
355
|
+
this.#icon.toggleAttribute("hidden", !this.icon);
|
|
325
356
|
this.#titleSkeleton.hidden =
|
|
326
357
|
this.heading !== "" || slotted || !this.loading;
|
|
327
358
|
this.#header.hidden =
|
|
328
|
-
!this.heading &&
|
|
359
|
+
!this.heading &&
|
|
360
|
+
!this.description &&
|
|
361
|
+
!this.icon &&
|
|
362
|
+
!slotted &&
|
|
363
|
+
!this.loading;
|
|
329
364
|
this.#heading.hidden = !this.heading && !slotted && !this.loading;
|
|
330
365
|
this.#skeleton.lines = this.lines;
|
|
331
366
|
this.#skeleton.hidden = !this.loading;
|
|
332
|
-
this.#body.hidden = this.loading;
|
|
367
|
+
this.#body.hidden = this.loading || this.#empty;
|
|
333
368
|
|
|
334
369
|
if (animate) {
|
|
335
370
|
animateHeight(this, before);
|
package/src/gw-facts.ts
CHANGED
|
@@ -29,6 +29,8 @@ const skeletonWidths = ["60%", "40%", "75%", "50%", "65%"];
|
|
|
29
29
|
* @attr {boolean} loading - Shows skeleton pairs, keeping any labels already known.
|
|
30
30
|
* @attr {number} rows - How many skeleton pairs while loading. Defaults to 4.
|
|
31
31
|
*
|
|
32
|
+
* @slot - `<dt>` and `<dd>` pairs, in place of `items`, for values with markup such as chips or code.
|
|
33
|
+
*
|
|
32
34
|
* @csspart list - The description list.
|
|
33
35
|
* @csspart label - Each label.
|
|
34
36
|
* @csspart value - Each value.
|
|
@@ -55,11 +57,13 @@ export class GwFacts extends GwElement {
|
|
|
55
57
|
font-size: var(--gw-font-size-s);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
dt
|
|
60
|
+
dt,
|
|
61
|
+
::slotted(dt) {
|
|
59
62
|
color: var(--gw-color-text-secondary);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
dd
|
|
65
|
+
dd,
|
|
66
|
+
::slotted(dd) {
|
|
63
67
|
min-width: 0;
|
|
64
68
|
margin: 0;
|
|
65
69
|
font-variant-numeric: var(--gw-font-figures);
|
|
@@ -117,6 +121,8 @@ export class GwFacts extends GwElement {
|
|
|
117
121
|
} else {
|
|
118
122
|
this.#list.removeAttribute("aria-busy");
|
|
119
123
|
this.#list.replaceChildren(
|
|
124
|
+
// The slot is display: contents, so slotted pairs join the grid
|
|
125
|
+
h("slot"),
|
|
120
126
|
...items.flatMap((item) => [
|
|
121
127
|
h("dt", { part: "label" }, item.label),
|
|
122
128
|
h(
|
package/src/gw-input.ts
CHANGED
|
@@ -46,7 +46,7 @@ const hideIcon =
|
|
|
46
46
|
* @attr {boolean} reveal - On a password field, adds a button at the end to show and hide what's typed.
|
|
47
47
|
* @attr {"xs"|"small"|"medium"|"large"|"xl"} size - The field's size, 40px tall at medium. Defaults to `medium`.
|
|
48
48
|
* @attr {string} icon - SVG path data for an icon inside the field, before the text, such as a magnifier on a search.
|
|
49
|
-
* @attr {boolean}
|
|
49
|
+
* @attr {boolean} label-hidden - Keeps the label as the field's accessible name but doesn't show it, for a field in a toolbar whose icon and placeholder say what it's for.
|
|
50
50
|
*
|
|
51
51
|
* @cssprop --gw-field-scale - Scales the field, around 1 for medium.
|
|
52
52
|
* @cssprop --gw-control-scale - Scales its height with the theme's density. The theme sets it.
|
|
@@ -75,7 +75,7 @@ export class GwInput extends LightElement {
|
|
|
75
75
|
reveal: { type: "boolean" },
|
|
76
76
|
size: { reflect: true },
|
|
77
77
|
icon: {},
|
|
78
|
-
|
|
78
|
+
labelHidden: { type: "boolean", attribute: "label-hidden", reflect: true },
|
|
79
79
|
} as const;
|
|
80
80
|
|
|
81
81
|
declare label: string;
|
|
@@ -112,7 +112,7 @@ export class GwInput extends LightElement {
|
|
|
112
112
|
/** SVG path data for an icon inside the field, before the text. */
|
|
113
113
|
declare icon: string;
|
|
114
114
|
/** Keeps the label as the accessible name without showing it. */
|
|
115
|
-
declare
|
|
115
|
+
declare labelHidden: boolean;
|
|
116
116
|
|
|
117
117
|
readonly #id = fieldId();
|
|
118
118
|
|
|
@@ -184,7 +184,7 @@ export class GwInput extends LightElement {
|
|
|
184
184
|
this.reveal = false;
|
|
185
185
|
this.size = "medium";
|
|
186
186
|
this.icon = "";
|
|
187
|
-
this.
|
|
187
|
+
this.labelHidden = false;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
protected override create(root: HTMLElement) {
|
|
@@ -259,7 +259,7 @@ export class GwInput extends LightElement {
|
|
|
259
259
|
this.#label.replaceChildren(this.label, this.#optional);
|
|
260
260
|
this.#optional.hidden = !this.optional;
|
|
261
261
|
this.#info.update(this.label, this.info);
|
|
262
|
-
this.#info.heading.classList.toggle("gw-visually-hidden", this.
|
|
262
|
+
this.#info.heading.classList.toggle("gw-visually-hidden", this.labelHidden);
|
|
263
263
|
setIconPath(this.#icon, this.icon);
|
|
264
264
|
this.#icon.toggleAttribute("hidden", !this.icon);
|
|
265
265
|
this.#input.type = reveal && this.#revealed ? "text" : this.type;
|
package/src/gw-page-header.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { GwElement, css, h } from "./element.js";
|
|
8
8
|
import { base, createIcon, setIconPath } from "./styles.js";
|
|
9
9
|
|
|
10
|
+
const headingTags = ["h1", "h2", "h3", "h4", "h5", "h6"] as const;
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* A page's title, with an optional icon tile and description, a back button
|
|
12
14
|
* or similar in the `start` slot, and its controls at the top right in the
|
|
@@ -18,6 +20,7 @@ import { base, createIcon, setIconPath } from "./styles.js";
|
|
|
18
20
|
* @attr {string} heading - The page's title.
|
|
19
21
|
* @attr {string} description - A line under the title.
|
|
20
22
|
* @attr {string} icon - SVG path data for the icon tile.
|
|
23
|
+
* @attr {number} level - The heading level, 1 to 6. Defaults to 1. From 2 the header is smaller, for a section of a page that reads like a page of its own.
|
|
21
24
|
*
|
|
22
25
|
* @slot start - Before the icon tile, such as a back button.
|
|
23
26
|
* @slot actions - The page's controls.
|
|
@@ -31,6 +34,7 @@ export class GwPageHeader extends GwElement {
|
|
|
31
34
|
heading: {},
|
|
32
35
|
description: {},
|
|
33
36
|
icon: {},
|
|
37
|
+
level: { type: "number", reflect: true },
|
|
34
38
|
} as const;
|
|
35
39
|
|
|
36
40
|
static override styles = [
|
|
@@ -73,7 +77,22 @@ export class GwPageHeader extends GwElement {
|
|
|
73
77
|
min-width: 0;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
:host(:not([level="1"])) .tile {
|
|
81
|
+
width: 2.5rem;
|
|
82
|
+
height: 2.5rem;
|
|
83
|
+
border-radius: var(--gw-radius-m);
|
|
84
|
+
--gw-icon-size: 1.375rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
:host(:not([level="1"])) .heading {
|
|
88
|
+
font-size: var(--gw-font-size-l);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:host(:not([level="1"])) .tile:not([hidden]) ~ .actions {
|
|
92
|
+
min-height: 2.5rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.heading {
|
|
77
96
|
margin: 0;
|
|
78
97
|
font-size: var(--gw-font-size-2xl);
|
|
79
98
|
font-weight: var(--gw-font-weight-strong);
|
|
@@ -107,6 +126,8 @@ export class GwPageHeader extends GwElement {
|
|
|
107
126
|
declare heading: string;
|
|
108
127
|
declare description: string;
|
|
109
128
|
declare icon: string;
|
|
129
|
+
/** The heading level, 1 to 6. */
|
|
130
|
+
declare level: number;
|
|
110
131
|
|
|
111
132
|
readonly #icon = createIcon("");
|
|
112
133
|
|
|
@@ -117,7 +138,7 @@ export class GwPageHeader extends GwElement {
|
|
|
117
138
|
|
|
118
139
|
readonly #tile = h("span", { class: "tile", part: "tile" }, this.#icon);
|
|
119
140
|
|
|
120
|
-
|
|
141
|
+
#heading: HTMLHeadingElement = h("h1", { class: "heading", part: "heading" });
|
|
121
142
|
|
|
122
143
|
readonly #description = h("p", { part: "description" });
|
|
123
144
|
|
|
@@ -126,6 +147,7 @@ export class GwPageHeader extends GwElement {
|
|
|
126
147
|
this.heading = "";
|
|
127
148
|
this.description = "";
|
|
128
149
|
this.icon = "";
|
|
150
|
+
this.level = 1;
|
|
129
151
|
}
|
|
130
152
|
|
|
131
153
|
protected override create(root: ShadowRoot) {
|
|
@@ -141,6 +163,19 @@ export class GwPageHeader extends GwElement {
|
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
protected override update() {
|
|
166
|
+
const level = Math.min(6, Math.max(1, Math.round(this.level) || 1));
|
|
167
|
+
const tag = headingTags[level - 1] ?? "h1";
|
|
168
|
+
|
|
169
|
+
if (this.#heading.localName !== tag) {
|
|
170
|
+
const next = h(tag, {
|
|
171
|
+
class: "heading",
|
|
172
|
+
part: "heading",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
this.#heading.replaceWith(next);
|
|
176
|
+
this.#heading = next;
|
|
177
|
+
}
|
|
178
|
+
|
|
144
179
|
setIconPath(this.#icon, this.icon);
|
|
145
180
|
this.#tile.hidden = !this.icon;
|
|
146
181
|
this.#heading.textContent = this.heading;
|