@timmo001/groundwork-ui 0.9.15 → 0.9.17
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 +50 -13
- package/package.json +1 -1
- package/src/element.ts +2 -38
- package/src/gw-command-menu.ts +9 -3
- package/src/gw-option-list.ts +9 -2
- package/src/gw-theme-gallery.ts +13 -29
- package/src/gw-theme-picker.ts +19 -1
- package/src/index.ts +2 -0
- package/src/scrollbars.ts +148 -0
- package/src/styles.ts +0 -99
package/custom-elements.json
CHANGED
|
@@ -5899,7 +5899,7 @@
|
|
|
5899
5899
|
"text": "readonly CSSStyleSheet[]"
|
|
5900
5900
|
},
|
|
5901
5901
|
"static": true,
|
|
5902
|
-
"default": "[ base, css` dialog { top: 12vh; width: min(36rem, calc(100vw - 2 * var(--gw-space-3))); max-height: 70vh; padding: 0; margin-block: 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-
|
|
5902
|
+
"default": "[ base, css` dialog { top: 12vh; width: min(36rem, calc(100vw - 2 * var(--gw-space-3))); max-height: 70vh; padding: 0; margin-block: 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-loud); border-radius: var(--gw-radius-panel); box-shadow: var(--gw-shadow-l); --gw-option-list-height: min(24rem, 56vh); } dialog::backdrop { background: rgb(0 0 0 / 60%); } dialog, dialog::backdrop { opacity: 0; transition: opacity var(--gw-motion-normal) var(--gw-motion-easing), scale var(--gw-motion-normal) var(--gw-motion-easing), translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } dialog { scale: 0.96; } dialog[open], dialog[open]::backdrop { opacity: 1; } dialog[open] { scale: 1; } @starting-style { dialog[open], dialog[open]::backdrop { opacity: 0; } dialog[open] { scale: 0.96; } } dialog[data-placement=\"sheet\"] { inset: auto 0 0; width: 100%; max-width: none; padding-block-end: env(safe-area-inset-bottom); border-end-start-radius: 0; border-end-end-radius: 0; scale: 1; translate: 0 100%; --gw-option-list-height: 60vh; } dialog[data-placement=\"sheet\"][open] { opacity: 1; translate: 0 0; } @starting-style { dialog[data-placement=\"sheet\"][open] { translate: 0 100%; } } footer { display: flex; gap: var(--gw-space-3); padding: var(--gw-space-2) var(--gw-space-3); font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); border-block-start: var(--gw-border-width) solid var(--gw-color-border-quiet); } kbd { padding: var(--gw-space-0-5) calc(var(--gw-space-1) * 1.5); font: inherit; line-height: 1; background: color-mix( in oklab, var(--gw-color-surface-overlay), var(--gw-color-text-primary) 8% ); border: 1px solid var(--gw-color-border-normal); border-radius: var(--gw-radius-control); } dialog[data-placement=\"sheet\"] footer { display: none; } `, ]",
|
|
5903
5903
|
"inheritedFrom": {
|
|
5904
5904
|
"name": "GwElement",
|
|
5905
5905
|
"module": "src/element.ts"
|
|
@@ -12667,7 +12667,7 @@
|
|
|
12667
12667
|
"text": "readonly CSSStyleSheet[]"
|
|
12668
12668
|
},
|
|
12669
12669
|
"static": true,
|
|
12670
|
-
"default": "[ base, css` :host { display: flex; flex-direction: column; min-height: 0; } .search { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-1) var(--gw-space-3); color: var(--gw-color-text-secondary); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } :host(:not([search])) .search { display: none; } input { flex: 1; min-width: 0; height: 2.5rem; padding: 0; font: inherit; font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: none; border: 0; outline: none; } /* The search field is always focused while the list is open, so the row's divider marks it rather than a ring */ input:focus-visible { outline: none; } input::placeholder { color: var(--gw-color-text-secondary); } [role=\"listbox\"] { position: relative; max-height: var(--gw-option-list-height, 20rem); overflow-y: auto; overscroll-behavior: contain; outline: none; } .spacer { position: relative; } .row { position: absolute; inset-inline: ${listPadding}px; box-sizing: border-box; display: flex; gap: var(--gw-space-3); align-items: center; padding: 0 var(--gw-space-2); } .group { align-items: flex-end; padding-block-end: var(--gw-space-1); font-size: var(--gw-font-size-xs); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); } [role=\"option\"] { font-size: var(--gw-font-size-s); cursor: pointer; border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } [role=\"option\"][data-active] { background: var(--gw-color-
|
|
12670
|
+
"default": "[ base, css` :host { display: flex; flex-direction: column; min-height: 0; } .search { display: flex; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-1) var(--gw-space-3); color: var(--gw-color-text-secondary); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } :host(:not([search])) .search { display: none; } input { flex: 1; min-width: 0; height: 2.5rem; padding: 0; font: inherit; font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); background: none; border: 0; outline: none; } /* The search field is always focused while the list is open, so the row's divider marks it rather than a ring */ input:focus-visible { outline: none; } input::placeholder { color: var(--gw-color-text-secondary); } [role=\"listbox\"] { position: relative; max-height: var(--gw-option-list-height, 20rem); overflow-y: auto; overscroll-behavior: contain; outline: none; } .spacer { position: relative; } .row { position: absolute; inset-inline: ${listPadding}px; box-sizing: border-box; display: flex; gap: var(--gw-space-3); align-items: center; padding: 0 var(--gw-space-2); } .group { align-items: flex-end; padding-block-end: var(--gw-space-1); font-size: var(--gw-font-size-xs); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); } [role=\"option\"] { font-size: var(--gw-font-size-s); color: var(--gw-color-text-strong); cursor: pointer; border-radius: var(--gw-radius-control); transition: background-color var(--gw-motion-fast) var(--gw-motion-easing); } /* Mixed from the text color, so the active row stands out on any surface the list sits on, overlays included */ [role=\"option\"][data-active] { background: color-mix( in oklab, transparent, var(--gw-color-text-primary) 12% ); } [role=\"option\"][aria-selected=\"true\"] { color: var(--gw-color-text-strong); } [role=\"option\"] > .icon { flex: none; color: var(--gw-color-text-primary); } [role=\"option\"] > .check { color: var(--gw-color-accent); } .text { display: flex; flex: 1; flex-direction: column; min-width: 0; } .text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .description { font-size: var(--gw-font-size-xs); color: var(--gw-color-text-secondary); } .swatches { display: flex; flex: none; } .swatches span, .swatch { width: 0.875rem; height: 0.875rem; margin-inline-start: -0.25rem; border: 1px solid color-mix(in oklab, var(--gw-color-text-primary) 20%, transparent); border-radius: 50%; } .swatch { flex: none; width: 0.625rem; height: 0.625rem; margin-inline: 0.125rem; } .none { padding: var(--gw-space-4); font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); text-align: center; } .none[hidden] { display: none; } `, ]",
|
|
12671
12671
|
"inheritedFrom": {
|
|
12672
12672
|
"name": "GwElement",
|
|
12673
12673
|
"module": "src/element.ts"
|
|
@@ -19076,7 +19076,7 @@
|
|
|
19076
19076
|
"declarations": [
|
|
19077
19077
|
{
|
|
19078
19078
|
"kind": "class",
|
|
19079
|
-
"description": "Picks a theme from a grid of cards, each a small picture of an app in that\ntheme's colors: a rail, a panel and the status tones. For a settings page,\nor inside `gw-theme-picker` with `gallery`.\n\nThemes with a `group` sit together under its name,
|
|
19079
|
+
"description": "Picks a theme from a grid of cards, each a small picture of an app in that\ntheme's colors: a rail, a panel and the status tones. For a settings page,\nor inside `gw-theme-picker` with `gallery`.\n\nThemes with a `group` sit together under its name, so a family's light and\ndark variants line up. Themes with none gather where the first of them\ncomes: with no heading when that's first, and under \"Other\" after a family. Previews draw in\n`mode`, or the mode of the page or themed container it sits in, from the\ntheme CSS already on the page, including a custom theme `pageTheme`\nimported. Call `refresh()` after that CSS changes.\n\nIt's a `<fieldset>` of native radio buttons, so arrow keys move the choice.\nIt fires `theme`.",
|
|
19080
19080
|
"name": "GwThemeGallery",
|
|
19081
19081
|
"cssProperties": [
|
|
19082
19082
|
{
|
|
@@ -19123,7 +19123,7 @@
|
|
|
19123
19123
|
"text": "readonly CSSStyleSheet[]"
|
|
19124
19124
|
},
|
|
19125
19125
|
"static": true,
|
|
19126
|
-
"default": "[ base, css` :host { display: block; } fieldset { display: flex; flex-direction: column; gap: var(--gw-space-4); min-width: 0; padding: 0; margin: 0; border: 0; } legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .group { display: grid; grid-template-columns: repeat( auto-fill, minmax(var(--gw-theme-gallery-min, 9rem), 1fr) ); gap: var(--gw-space-3); } .group-name { grid-column: 1 / -1; margin: 0; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); } label { position: relative; display: flex; flex-direction: column; gap: var(--gw-space-2); padding: var(--gw-space-2); cursor: pointer; background: var(--gw-color-surface-raised); border: 2px solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } label:hover { border-color: var(--gw-color-border-accent); } label:has(input:checked) { background: var(--gw-color-fill-accent-quiet); border-color: var(--gw-color-accent); } label:has(input:focus-visible) { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } input { position: absolute; inset: 0; margin: 0; cursor: pointer; opacity: 0; } /* A small app in the theme's own colors */ .preview { display: grid; grid-template-columns: 22% 1fr; gap: 6px; height: 5.5rem; padding: 6px; background: var(--p-bg); border-radius: calc(var(--gw-radius-panel) - 2px); } .rail { display: flex; flex-direction: column; gap: 4px; padding: 4px; } .rail i { height: 5px; background: var(--p-muted); border-radius: 3px; opacity: 0.5; } .rail i:first-child { background: var(--p-accent); opacity: 1; } .panel { display: flex; flex-direction: column; gap: 5px; min-width: 0; padding: 6px; background: var(--p-surface); border: 1px solid var(--p-border); border-radius: min(var(--p-radius), 6px); } .line { width: 60%; height: 5px; background: var(--p-text); border-radius: 3px; } .line.short { width: 40%; background: var(--p-muted); } .bars { display: flex; flex: 1; gap: 3px; align-items: flex-end; } .bars i { flex: 1; border-radius: 2px 2px 0 0; } .name { display: flex; flex-direction: column; padding: 0 var(--gw-space-1) var(--gw-space-1); font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); }
|
|
19126
|
+
"default": "[ base, css` :host { display: block; } fieldset { display: flex; flex-direction: column; gap: var(--gw-space-4); min-width: 0; padding: 0; margin: 0; border: 0; } legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .group { display: grid; grid-template-columns: repeat( auto-fill, minmax(var(--gw-theme-gallery-min, 9rem), 1fr) ); gap: var(--gw-space-3); } .group-name { grid-column: 1 / -1; margin: 0; font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-secondary); text-transform: var(--gw-label-transform); letter-spacing: var(--gw-label-tracking); } label { position: relative; display: flex; flex-direction: column; gap: var(--gw-space-2); padding: var(--gw-space-2); cursor: pointer; background: var(--gw-color-surface-raised); border: 2px solid var(--gw-color-border-normal); border-radius: var(--gw-radius-panel); transition: border-color var(--gw-motion-fast) var(--gw-motion-easing), background-color var(--gw-motion-fast) var(--gw-motion-easing); } label:hover { border-color: var(--gw-color-border-accent); } label:has(input:checked) { background: var(--gw-color-fill-accent-quiet); border-color: var(--gw-color-accent); } label:has(input:focus-visible) { outline: var(--gw-focus-width) solid var(--gw-color-focus); outline-offset: var(--gw-focus-offset); } input { position: absolute; inset: 0; margin: 0; cursor: pointer; opacity: 0; } /* A small app in the theme's own colors */ .preview { display: grid; grid-template-columns: 22% 1fr; gap: 6px; height: 5.5rem; padding: 6px; background: var(--p-bg); border-radius: calc(var(--gw-radius-panel) - 2px); } .rail { display: flex; flex-direction: column; gap: 4px; padding: 4px; } .rail i { height: 5px; background: var(--p-muted); border-radius: 3px; opacity: 0.5; } .rail i:first-child { background: var(--p-accent); opacity: 1; } .panel { display: flex; flex-direction: column; gap: 5px; min-width: 0; padding: 6px; background: var(--p-surface); border: 1px solid var(--p-border); border-radius: min(var(--p-radius), 6px); } .line { width: 60%; height: 5px; background: var(--p-text); border-radius: 3px; } .line.short { width: 40%; background: var(--p-muted); } .bars { display: flex; flex: 1; gap: 3px; align-items: flex-end; } .bars i { flex: 1; border-radius: 2px 2px 0 0; } .name { display: flex; flex-direction: column; padding: 0 var(--gw-space-1) var(--gw-space-1); font-size: var(--gw-font-size-s); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } label:has(input:checked) .name { color: var(--gw-color-accent); } .check { position: absolute; inset-block-start: var(--gw-space-3); inset-inline-end: var(--gw-space-3); display: none; place-items: center; width: 1.5rem; height: 1.5rem; color: var(--gw-color-on-accent); background: var(--gw-color-fill-accent-resting); border-radius: 50%; --gw-icon-size: 1rem; } label:has(input:checked) .check { display: grid; } `, ]",
|
|
19127
19127
|
"inheritedFrom": {
|
|
19128
19128
|
"name": "GwElement",
|
|
19129
19129
|
"module": "src/element.ts"
|
|
@@ -19229,12 +19229,6 @@
|
|
|
19229
19229
|
"type": {
|
|
19230
19230
|
"text": "OptionItem"
|
|
19231
19231
|
}
|
|
19232
|
-
},
|
|
19233
|
-
{
|
|
19234
|
-
"name": "inOther",
|
|
19235
|
-
"type": {
|
|
19236
|
-
"text": "boolean"
|
|
19237
|
-
}
|
|
19238
19232
|
}
|
|
19239
19233
|
]
|
|
19240
19234
|
},
|
|
@@ -19615,7 +19609,7 @@
|
|
|
19615
19609
|
"declarations": [
|
|
19616
19610
|
{
|
|
19617
19611
|
"kind": "class",
|
|
19618
|
-
"description": "The page's mode, theme and import, in place where there's room and in a\npopover where there isn't. Offers the mode (auto, light, dark or Darker),\nthe theme, and buttons to import a theme file (a Groundwork theme's `.json`\nor an Omarchy `colors.toml`) or reset.\n\nIt only asks: it fires `mode`, `theme`, `import` and `reset`, and the page\napplies them and sets `mode`, `theme` and `themes` back. `pageTheme` from\n`@timmo001/groundwork-tokens/page` does all of that. Show an import's\nresult, such as contrast misses, in `status`.\n\n`gallery` swaps the theme select for `gw-theme-gallery`'s cards, which\npreview each theme in the chosen mode.\n\n`layout` sets how it shows. `auto`, the default, lays the mode out as icons\nand the theme as a select in place while they fit the room it's given,\nthen moves them into the popover, the theme first, as the room runs out.\nReset, Import, the gallery and `status` stay in the popover, opened from a\npalette button. Once the mode and theme are in place and only Reset and\nImport are left, they're a ⋮ menu instead.\nIn a flex row, such as `gw-page-header`'s actions, it gives up room before\nthe title does. `popover` keeps everything behind the palette button. `cycle` is one button\nshowing the mode in use, which steps to the next mode on each press, for an\napp bar. `inline` lays every control out in place, in one wrapping row with\nReset and Import, for a footer or a settings page; set `justify-content` on\nit to align the row. `modes` picks which modes it offers, such as\n`light dark` without Auto.",
|
|
19612
|
+
"description": "The page's mode, theme and import, in place where there's room and in a\npopover where there isn't. Offers the mode (auto, light, dark or Darker),\nthe theme, and buttons to import a theme file (a Groundwork theme's `.json`\nor an Omarchy `colors.toml`) or reset.\n\nIt only asks: it fires `mode`, `theme`, `import` and `reset`, and the page\napplies them and sets `mode`, `theme` and `themes` back. `pageTheme` from\n`@timmo001/groundwork-tokens/page` does all of that. Show an import's\nresult, such as contrast misses, in `status`.\n\n`gallery` swaps the theme select for `gw-theme-gallery`'s cards, which\npreview each theme in the chosen mode.\n\n`layout` sets how it shows. `auto`, the default, lays the mode out as icons\nand the theme as a select in place while they fit the room it's given,\nthen moves them into the popover, the theme first, as the room runs out.\nReset, Import, the gallery and `status` stay in the popover, opened from a\npalette button. Once the mode and theme are in place and only Reset and\nImport are left, they're a ⋮ menu instead.\nIn a flex row, such as `gw-page-header`'s actions, it gives up room before\nthe title does. `popover` keeps everything behind the palette button. `cycle` is one button\nshowing the mode in use, which steps to the next mode on each press, for an\napp bar. `inline` lays every control out in place, in one wrapping row with\nReset and Import, for a footer or a settings page; set `justify-content` on\nit to align the row. With `gallery`, the cards take a row of their own\nunder it. `modes` picks which modes it offers, such as\n`light dark` without Auto.",
|
|
19619
19613
|
"name": "GwThemePicker",
|
|
19620
19614
|
"cssParts": [
|
|
19621
19615
|
{
|
|
@@ -19652,7 +19646,7 @@
|
|
|
19652
19646
|
"text": "readonly CSSStyleSheet[]"
|
|
19653
19647
|
},
|
|
19654
19648
|
"static": true,
|
|
19655
|
-
"default": "[ base, css` :host { display: inline-block; } :host([gallery]) gw-popover { --gw-popover-width: 34rem; } .gallery { max-height: min(24rem, 50vh); overflow: auto; /* Room for the cards' focus rings */ padding: var(--gw-space-1); margin: calc(var(--gw-space-1) * -1); } .controls { display: flex; flex-direction: column; gap: var(--gw-space-4); margin-block-start: var(--gw-space-2); } .status { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* One wrapping row: the controls, then Reset and Import. The host is the flex container, so a page can set its justify-content */ :host([layout=\"inline\"]) { display: flex; flex-wrap: wrap; gap: var(--gw-space-4); align-items: center; } :host([layout=\"inline\"]) .controls { flex-flow: row wrap; align-items: center; margin-block-start: 0; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); align-items: center; } /* Asks for the room to have all of it in place, but gives it up down to the button before a flex row's other items shrink */ :host([layout=\"auto\"]) { display: flex; flex: 0 1000 var(--gw-theme-picker-fit, auto); gap: var(--gw-space-1); align-items: center; min-width: 2.5rem; contain: inline-size; } :host([layout=\"auto\"]) > * { flex: none; } `, ]",
|
|
19649
|
+
"default": "[ base, css` :host { display: inline-block; } :host([gallery]) gw-popover { --gw-popover-width: 34rem; } .gallery { max-height: min(24rem, 50vh); overflow: auto; /* Room for the cards' focus rings */ padding: var(--gw-space-1); margin: calc(var(--gw-space-1) * -1); } .controls { display: flex; flex-direction: column; gap: var(--gw-space-4); margin-block-start: var(--gw-space-2); } .status { margin: 0; font-size: var(--gw-font-size-s); color: var(--gw-color-text-secondary); } /* One wrapping row: the controls, then Reset and Import. The host is the flex container, so a page can set its justify-content */ :host([layout=\"inline\"]) { display: flex; flex-wrap: wrap; gap: var(--gw-space-4); align-items: center; } :host([layout=\"inline\"]) .controls { flex-flow: row wrap; align-items: center; margin-block-start: 0; } /* The cards get a row of their own under the mode, Reset and Import */ :host([layout=\"inline\"][gallery]) .controls { display: contents; } :host([layout=\"inline\"]) .gallery { flex: 1 0 100%; order: 1; max-height: none; overflow: visible; } :host([layout=\"inline\"][gallery]) .status { flex: 1 0 100%; order: 2; } .actions { display: flex; flex-wrap: wrap; gap: var(--gw-space-2); align-items: center; } /* Asks for the room to have all of it in place, but gives it up down to the button before a flex row's other items shrink */ :host([layout=\"auto\"]) { display: flex; flex: 0 1000 var(--gw-theme-picker-fit, auto); gap: var(--gw-space-1); align-items: center; min-width: 2.5rem; contain: inline-size; } :host([layout=\"auto\"]) > * { flex: none; } `, ]",
|
|
19656
19650
|
"inheritedFrom": {
|
|
19657
19651
|
"name": "GwElement",
|
|
19658
19652
|
"module": "src/element.ts"
|
|
@@ -20762,6 +20756,14 @@
|
|
|
20762
20756
|
"module": "src/styles.js"
|
|
20763
20757
|
}
|
|
20764
20758
|
},
|
|
20759
|
+
{
|
|
20760
|
+
"kind": "js",
|
|
20761
|
+
"name": "*",
|
|
20762
|
+
"declaration": {
|
|
20763
|
+
"name": "*",
|
|
20764
|
+
"module": "src/scrollbars.js"
|
|
20765
|
+
}
|
|
20766
|
+
},
|
|
20765
20767
|
{
|
|
20766
20768
|
"kind": "js",
|
|
20767
20769
|
"name": "*",
|
|
@@ -22839,6 +22841,41 @@
|
|
|
22839
22841
|
}
|
|
22840
22842
|
]
|
|
22841
22843
|
},
|
|
22844
|
+
{
|
|
22845
|
+
"kind": "javascript-module",
|
|
22846
|
+
"path": "src/scrollbars.ts",
|
|
22847
|
+
"declarations": [
|
|
22848
|
+
{
|
|
22849
|
+
"kind": "variable",
|
|
22850
|
+
"name": "scrollbars",
|
|
22851
|
+
"default": "new CSSStyleSheet()",
|
|
22852
|
+
"description": "The page's scrollbars for a shadow root: a small rounded thumb on a clear\ntrack with no arrow buttons, squaring off at the ends of its track. Every\n`gw-` element adopts it; add it to an app's own components' styles to match.\nThe `--gw-scrollbar-*` properties restyle it as they do the page's."
|
|
22853
|
+
},
|
|
22854
|
+
{
|
|
22855
|
+
"kind": "function",
|
|
22856
|
+
"name": "followPageScroll",
|
|
22857
|
+
"description": "Sets `--gw-scrollbar-top`, `-bottom`, `-left` and `-right` on the root as\nthe page scrolls, 0 against that end and 1 elsewhere, so the window's\nscrollbar thumb squares off at the ends like the others. The window's\nscrollbar doesn't repaint for the scroll-driven animation the themes give\nother scrollers. Every `gw-` element starts it when it's defined."
|
|
22858
|
+
}
|
|
22859
|
+
],
|
|
22860
|
+
"exports": [
|
|
22861
|
+
{
|
|
22862
|
+
"kind": "js",
|
|
22863
|
+
"name": "scrollbars",
|
|
22864
|
+
"declaration": {
|
|
22865
|
+
"name": "scrollbars",
|
|
22866
|
+
"module": "src/scrollbars.ts"
|
|
22867
|
+
}
|
|
22868
|
+
},
|
|
22869
|
+
{
|
|
22870
|
+
"kind": "js",
|
|
22871
|
+
"name": "followPageScroll",
|
|
22872
|
+
"declaration": {
|
|
22873
|
+
"name": "followPageScroll",
|
|
22874
|
+
"module": "src/scrollbars.ts"
|
|
22875
|
+
}
|
|
22876
|
+
}
|
|
22877
|
+
]
|
|
22878
|
+
},
|
|
22842
22879
|
{
|
|
22843
22880
|
"kind": "javascript-module",
|
|
22844
22881
|
"path": "src/styles.ts",
|
|
@@ -22859,7 +22896,7 @@
|
|
|
22859
22896
|
{
|
|
22860
22897
|
"kind": "variable",
|
|
22861
22898
|
"name": "base",
|
|
22862
|
-
"default": "css` :host { font-family: var(--gw-font-sans); color: var(--gw-color-text-primary); } *, *::before, *::after { box-sizing: border-box; }
|
|
22899
|
+
"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; } } `",
|
|
22863
22900
|
"description": "What every component shares: box sizing, the focus ring, icons and the\ntones that set `--gw-tone` from a `tone` attribute."
|
|
22864
22901
|
},
|
|
22865
22902
|
{
|
package/package.json
CHANGED
package/src/element.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
|
+
import { followPageScroll, scrollbars } from "./scrollbars.js";
|
|
8
9
|
|
|
9
10
|
/** How a property reads its attribute and writes it back. */
|
|
10
11
|
export interface PropertyOptions {
|
|
@@ -136,43 +137,6 @@ const toAttribute = (value: PropertyValue, type: PropertyOptions["type"]) => {
|
|
|
136
137
|
const attributeName = (name: string, options: PropertyOptions) =>
|
|
137
138
|
options.attribute === undefined ? name.toLowerCase() : options.attribute;
|
|
138
139
|
|
|
139
|
-
let followingPageScroll = false;
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Sets `--gw-scrollbar-top`, `-bottom`, `-left` and `-right` on the root as
|
|
143
|
-
* the page scrolls, 0 against that end and 1 elsewhere, so the window's
|
|
144
|
-
* scrollbar thumb squares off at the ends like the others. The window's
|
|
145
|
-
* scrollbar doesn't repaint for the scroll-driven animation the themes give
|
|
146
|
-
* other scrollers.
|
|
147
|
-
*/
|
|
148
|
-
const followPageScroll = () => {
|
|
149
|
-
if (followingPageScroll) return;
|
|
150
|
-
|
|
151
|
-
followingPageScroll = true;
|
|
152
|
-
|
|
153
|
-
const root = document.documentElement;
|
|
154
|
-
|
|
155
|
-
const set = (edge: string, against: boolean) => {
|
|
156
|
-
const value = against ? "0" : "1";
|
|
157
|
-
|
|
158
|
-
if (root.style.getPropertyValue(`--gw-scrollbar-${edge}`) !== value)
|
|
159
|
-
root.style.setProperty(`--gw-scrollbar-${edge}`, value);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const update = () => {
|
|
163
|
-
const page = document.scrollingElement ?? root;
|
|
164
|
-
|
|
165
|
-
set("top", page.scrollTop <= 0);
|
|
166
|
-
set("bottom", page.scrollTop + page.clientHeight >= page.scrollHeight - 1);
|
|
167
|
-
set("left", page.scrollLeft <= 0);
|
|
168
|
-
set("right", page.scrollLeft + page.clientWidth >= page.scrollWidth - 1);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
update();
|
|
172
|
-
addEventListener("scroll", update, { passive: true });
|
|
173
|
-
new ResizeObserver(update).observe(root);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
140
|
/**
|
|
177
141
|
* The base for every `gw-` element, with no framework underneath.
|
|
178
142
|
*
|
|
@@ -263,7 +227,7 @@ export class GwElement extends HTMLElement {
|
|
|
263
227
|
if (own.shadow) {
|
|
264
228
|
const root = this.attachShadow(own.shadowRootOptions);
|
|
265
229
|
|
|
266
|
-
root.adoptedStyleSheets = [...own.styles];
|
|
230
|
+
root.adoptedStyleSheets = [scrollbars, ...own.styles];
|
|
267
231
|
this.renderRoot = root;
|
|
268
232
|
} else {
|
|
269
233
|
this.renderRoot = this;
|
package/src/gw-command-menu.ts
CHANGED
|
@@ -56,14 +56,14 @@ export class GwCommandMenu extends GwElement {
|
|
|
56
56
|
color: var(--gw-color-text-primary);
|
|
57
57
|
background: var(--gw-color-surface-overlay);
|
|
58
58
|
border: max(var(--gw-border-width), 1px) solid
|
|
59
|
-
var(--gw-color-border-
|
|
59
|
+
var(--gw-color-border-loud);
|
|
60
60
|
border-radius: var(--gw-radius-panel);
|
|
61
61
|
box-shadow: var(--gw-shadow-l);
|
|
62
62
|
--gw-option-list-height: min(24rem, 56vh);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
dialog::backdrop {
|
|
66
|
-
background: rgb(0 0 0 /
|
|
66
|
+
background: rgb(0 0 0 / 60%);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
dialog,
|
|
@@ -135,8 +135,14 @@ export class GwCommandMenu extends GwElement {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
kbd {
|
|
138
|
-
padding: 0 var(--gw-space-1);
|
|
138
|
+
padding: var(--gw-space-0-5) calc(var(--gw-space-1) * 1.5);
|
|
139
139
|
font: inherit;
|
|
140
|
+
line-height: 1;
|
|
141
|
+
background: color-mix(
|
|
142
|
+
in oklab,
|
|
143
|
+
var(--gw-color-surface-overlay),
|
|
144
|
+
var(--gw-color-text-primary) 8%
|
|
145
|
+
);
|
|
140
146
|
border: 1px solid var(--gw-color-border-normal);
|
|
141
147
|
border-radius: var(--gw-radius-control);
|
|
142
148
|
}
|
package/src/gw-option-list.ts
CHANGED
|
@@ -181,14 +181,21 @@ export class GwOptionList extends GwElement {
|
|
|
181
181
|
|
|
182
182
|
[role="option"] {
|
|
183
183
|
font-size: var(--gw-font-size-s);
|
|
184
|
+
color: var(--gw-color-text-strong);
|
|
184
185
|
cursor: pointer;
|
|
185
186
|
border-radius: var(--gw-radius-control);
|
|
186
187
|
transition: background-color var(--gw-motion-fast)
|
|
187
188
|
var(--gw-motion-easing);
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
/* Mixed from the text color, so the active row stands out on any
|
|
192
|
+
surface the list sits on, overlays included */
|
|
190
193
|
[role="option"][data-active] {
|
|
191
|
-
background:
|
|
194
|
+
background: color-mix(
|
|
195
|
+
in oklab,
|
|
196
|
+
transparent,
|
|
197
|
+
var(--gw-color-text-primary) 12%
|
|
198
|
+
);
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
[role="option"][aria-selected="true"] {
|
|
@@ -197,7 +204,7 @@ export class GwOptionList extends GwElement {
|
|
|
197
204
|
|
|
198
205
|
[role="option"] > .icon {
|
|
199
206
|
flex: none;
|
|
200
|
-
color: var(--gw-color-text-
|
|
207
|
+
color: var(--gw-color-text-primary);
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
[role="option"] > .check {
|
package/src/gw-theme-gallery.ts
CHANGED
|
@@ -72,29 +72,23 @@ const closestWith = (start: Element, attribute: string) => {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Themes in groups, in the order they come.
|
|
76
|
-
*
|
|
75
|
+
* Themes in groups, in the order they come. Themes with no group gather where
|
|
76
|
+
* the first of them came: with no heading when that's first, and under
|
|
77
|
+
* "Other" after a named group.
|
|
77
78
|
*/
|
|
78
79
|
const grouped = (themes: readonly OptionItem[]) => {
|
|
79
|
-
if (!themes.some((theme) => theme.group)) return [["", themes] as const];
|
|
80
|
-
|
|
81
80
|
const groups = new Map<string, OptionItem[]>();
|
|
82
81
|
|
|
83
82
|
for (const theme of themes) {
|
|
84
|
-
const name = theme.group ??
|
|
83
|
+
const name = theme.group ?? "";
|
|
85
84
|
|
|
86
85
|
groups.set(name, [...(groups.get(name) ?? []), theme]);
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
([name, members]) =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const rest = themes.filter(
|
|
94
|
-
(theme) => !own.some(([name]) => name === theme.group),
|
|
88
|
+
return [...groups].map(
|
|
89
|
+
([name, members], index) =>
|
|
90
|
+
[name === "" && index > 0 ? other : name, members] as const,
|
|
95
91
|
);
|
|
96
|
-
|
|
97
|
-
return rest.length === 0 ? own : [...own, [other, rest] as const];
|
|
98
92
|
};
|
|
99
93
|
|
|
100
94
|
/**
|
|
@@ -102,8 +96,9 @@ const grouped = (themes: readonly OptionItem[]) => {
|
|
|
102
96
|
* theme's colors: a rail, a panel and the status tones. For a settings page,
|
|
103
97
|
* or inside `gw-theme-picker` with `gallery`.
|
|
104
98
|
*
|
|
105
|
-
* Themes with a `group` sit together under its name,
|
|
106
|
-
*
|
|
99
|
+
* Themes with a `group` sit together under its name, so a family's light and
|
|
100
|
+
* dark variants line up. Themes with none gather where the first of them
|
|
101
|
+
* comes: with no heading when that's first, and under "Other" after a family. Previews draw in
|
|
107
102
|
* `mode`, or the mode of the page or themed container it sits in, from the
|
|
108
103
|
* theme CSS already on the page, including a custom theme `pageTheme`
|
|
109
104
|
* imported. Call `refresh()` after that CSS changes.
|
|
@@ -292,12 +287,6 @@ export class GwThemeGallery extends GwElement {
|
|
|
292
287
|
color: var(--gw-color-text-strong);
|
|
293
288
|
}
|
|
294
289
|
|
|
295
|
-
.name small {
|
|
296
|
-
font-size: var(--gw-font-size-xs);
|
|
297
|
-
font-weight: normal;
|
|
298
|
-
color: var(--gw-color-text-secondary);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
290
|
label:has(input:checked) .name {
|
|
302
291
|
color: var(--gw-color-accent);
|
|
303
292
|
}
|
|
@@ -393,7 +382,7 @@ export class GwThemeGallery extends GwElement {
|
|
|
393
382
|
this.#modeRoot.disconnect();
|
|
394
383
|
}
|
|
395
384
|
|
|
396
|
-
#card(theme: OptionItem
|
|
385
|
+
#card(theme: OptionItem) {
|
|
397
386
|
const input = h("input", {
|
|
398
387
|
type: "radio",
|
|
399
388
|
name: this.#name,
|
|
@@ -440,12 +429,7 @@ export class GwThemeGallery extends GwElement {
|
|
|
440
429
|
{ part: "card" },
|
|
441
430
|
input,
|
|
442
431
|
preview,
|
|
443
|
-
h(
|
|
444
|
-
"span",
|
|
445
|
-
{ class: "name", part: "name" },
|
|
446
|
-
theme.label,
|
|
447
|
-
inOther && theme.group ? h("small", {}, theme.group) : null,
|
|
448
|
-
),
|
|
432
|
+
h("span", { class: "name", part: "name" }, theme.label),
|
|
449
433
|
h("span", { class: "check" }, createIcon(checkIcon)),
|
|
450
434
|
);
|
|
451
435
|
|
|
@@ -472,7 +456,7 @@ export class GwThemeGallery extends GwElement {
|
|
|
472
456
|
"div",
|
|
473
457
|
{ class: "group" },
|
|
474
458
|
name ? h("h3", { class: "group-name", part: "group" }, name) : null,
|
|
475
|
-
...members.map((theme) => this.#card(theme
|
|
459
|
+
...members.map((theme) => this.#card(theme)),
|
|
476
460
|
),
|
|
477
461
|
),
|
|
478
462
|
);
|
package/src/gw-theme-picker.ts
CHANGED
|
@@ -65,7 +65,8 @@ const moreIcon =
|
|
|
65
65
|
* showing the mode in use, which steps to the next mode on each press, for an
|
|
66
66
|
* app bar. `inline` lays every control out in place, in one wrapping row with
|
|
67
67
|
* Reset and Import, for a footer or a settings page; set `justify-content` on
|
|
68
|
-
* it to align the row. `
|
|
68
|
+
* it to align the row. With `gallery`, the cards take a row of their own
|
|
69
|
+
* under it. `modes` picks which modes it offers, such as
|
|
69
70
|
* `light dark` without Auto.
|
|
70
71
|
*
|
|
71
72
|
* @element gw-theme-picker
|
|
@@ -148,6 +149,23 @@ export class GwThemePicker extends GwElement {
|
|
|
148
149
|
margin-block-start: 0;
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/* The cards get a row of their own under the mode, Reset and Import */
|
|
153
|
+
:host([layout="inline"][gallery]) .controls {
|
|
154
|
+
display: contents;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
:host([layout="inline"]) .gallery {
|
|
158
|
+
flex: 1 0 100%;
|
|
159
|
+
order: 1;
|
|
160
|
+
max-height: none;
|
|
161
|
+
overflow: visible;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
:host([layout="inline"][gallery]) .status {
|
|
165
|
+
flex: 1 0 100%;
|
|
166
|
+
order: 2;
|
|
167
|
+
}
|
|
168
|
+
|
|
151
169
|
.actions {
|
|
152
170
|
display: flex;
|
|
153
171
|
flex-wrap: wrap;
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The small scrollbars `themes.css` gives the page, as a sheet for shadow
|
|
3
|
+
* roots, which the page's rules can't reach, and the page scroll tracking
|
|
4
|
+
* that squares the window's thumb off at its ends.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The page's scrollbars for a shadow root: a small rounded thumb on a clear
|
|
11
|
+
* track with no arrow buttons, squaring off at the ends of its track. Every
|
|
12
|
+
* `gw-` element adopts it; add it to an app's own components' styles to match.
|
|
13
|
+
* The `--gw-scrollbar-*` properties restyle it as they do the page's.
|
|
14
|
+
*/
|
|
15
|
+
export const scrollbars = new CSSStyleSheet();
|
|
16
|
+
|
|
17
|
+
scrollbars.replaceSync(`
|
|
18
|
+
@supports not selector(::-webkit-scrollbar) {
|
|
19
|
+
* {
|
|
20
|
+
scrollbar-width: thin;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
* {
|
|
25
|
+
animation:
|
|
26
|
+
gw-scrollbar-edges-y linear both,
|
|
27
|
+
gw-scrollbar-edges-x linear both;
|
|
28
|
+
animation-timeline: scroll(self y), scroll(self x);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@keyframes gw-scrollbar-edges-y {
|
|
32
|
+
0% {
|
|
33
|
+
--gw-scrollbar-top: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
1% {
|
|
37
|
+
--gw-scrollbar-top: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
99% {
|
|
41
|
+
--gw-scrollbar-bottom: 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
100% {
|
|
45
|
+
--gw-scrollbar-bottom: 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes gw-scrollbar-edges-x {
|
|
50
|
+
0% {
|
|
51
|
+
--gw-scrollbar-left: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
1% {
|
|
55
|
+
--gw-scrollbar-left: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
99% {
|
|
59
|
+
--gw-scrollbar-right: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
100% {
|
|
63
|
+
--gw-scrollbar-right: 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
::-webkit-scrollbar {
|
|
68
|
+
width: var(--gw-scrollbar-size, 0.4rem);
|
|
69
|
+
height: var(--gw-scrollbar-size, 0.4rem);
|
|
70
|
+
background: var(--gw-scrollbar-track, transparent);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
::-webkit-scrollbar-thumb {
|
|
74
|
+
background: var(
|
|
75
|
+
--gw-scrollbar-thumb,
|
|
76
|
+
color-mix(
|
|
77
|
+
in oklab,
|
|
78
|
+
var(--gw-color-text-secondary, currentColor) 45%,
|
|
79
|
+
transparent
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
border-radius:
|
|
83
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1))
|
|
84
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1))
|
|
85
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1))
|
|
86
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
::-webkit-scrollbar-thumb:horizontal {
|
|
90
|
+
border-radius:
|
|
91
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1))
|
|
92
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
93
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
94
|
+
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
::-webkit-scrollbar-thumb:hover {
|
|
98
|
+
background: var(
|
|
99
|
+
--gw-scrollbar-thumb,
|
|
100
|
+
color-mix(
|
|
101
|
+
in oklab,
|
|
102
|
+
var(--gw-color-text-secondary, currentColor) 70%,
|
|
103
|
+
transparent
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
::-webkit-scrollbar-corner {
|
|
109
|
+
background: transparent;
|
|
110
|
+
}
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
let followingPageScroll = false;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sets `--gw-scrollbar-top`, `-bottom`, `-left` and `-right` on the root as
|
|
117
|
+
* the page scrolls, 0 against that end and 1 elsewhere, so the window's
|
|
118
|
+
* scrollbar thumb squares off at the ends like the others. The window's
|
|
119
|
+
* scrollbar doesn't repaint for the scroll-driven animation the themes give
|
|
120
|
+
* other scrollers. Every `gw-` element starts it when it's defined.
|
|
121
|
+
*/
|
|
122
|
+
export const followPageScroll = () => {
|
|
123
|
+
if (followingPageScroll) return;
|
|
124
|
+
|
|
125
|
+
followingPageScroll = true;
|
|
126
|
+
|
|
127
|
+
const root = document.documentElement;
|
|
128
|
+
|
|
129
|
+
const set = (edge: string, against: boolean) => {
|
|
130
|
+
const value = against ? "0" : "1";
|
|
131
|
+
|
|
132
|
+
if (root.style.getPropertyValue(`--gw-scrollbar-${edge}`) !== value)
|
|
133
|
+
root.style.setProperty(`--gw-scrollbar-${edge}`, value);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const update = () => {
|
|
137
|
+
const page = document.scrollingElement ?? root;
|
|
138
|
+
|
|
139
|
+
set("top", page.scrollTop <= 0);
|
|
140
|
+
set("bottom", page.scrollTop + page.clientHeight >= page.scrollHeight - 1);
|
|
141
|
+
set("left", page.scrollLeft <= 0);
|
|
142
|
+
set("right", page.scrollLeft + page.clientWidth >= page.scrollWidth - 1);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
update();
|
|
146
|
+
addEventListener("scroll", update, { passive: true });
|
|
147
|
+
new ResizeObserver(update).observe(root);
|
|
148
|
+
};
|
package/src/styles.ts
CHANGED
|
@@ -48,105 +48,6 @@ export const base = css`
|
|
|
48
48
|
box-sizing: border-box;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/* The page's small scrollbars, as the themes set them, for scrolling parts
|
|
52
|
-
inside components, which the page's rules can't reach. Each scroller
|
|
53
|
-
follows its own scroll for the thumb's square ends */
|
|
54
|
-
@supports not selector(::-webkit-scrollbar) {
|
|
55
|
-
* {
|
|
56
|
-
scrollbar-width: thin;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
* {
|
|
61
|
-
animation:
|
|
62
|
-
gw-scrollbar-edges-y linear both,
|
|
63
|
-
gw-scrollbar-edges-x linear both;
|
|
64
|
-
animation-timeline: scroll(self y), scroll(self x);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@keyframes gw-scrollbar-edges-y {
|
|
68
|
-
0% {
|
|
69
|
-
--gw-scrollbar-top: 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
1% {
|
|
73
|
-
--gw-scrollbar-top: 1;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
99% {
|
|
77
|
-
--gw-scrollbar-bottom: 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
100% {
|
|
81
|
-
--gw-scrollbar-bottom: 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@keyframes gw-scrollbar-edges-x {
|
|
86
|
-
0% {
|
|
87
|
-
--gw-scrollbar-left: 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
1% {
|
|
91
|
-
--gw-scrollbar-left: 1;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
99% {
|
|
95
|
-
--gw-scrollbar-right: 1;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
100% {
|
|
99
|
-
--gw-scrollbar-right: 0;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
::-webkit-scrollbar {
|
|
104
|
-
width: var(--gw-scrollbar-size, 0.4rem);
|
|
105
|
-
height: var(--gw-scrollbar-size, 0.4rem);
|
|
106
|
-
background: var(--gw-scrollbar-track, transparent);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
::-webkit-scrollbar-thumb {
|
|
110
|
-
background: var(
|
|
111
|
-
--gw-scrollbar-thumb,
|
|
112
|
-
color-mix(
|
|
113
|
-
in oklab,
|
|
114
|
-
var(--gw-color-text-secondary, currentColor) 45%,
|
|
115
|
-
transparent
|
|
116
|
-
)
|
|
117
|
-
);
|
|
118
|
-
border-radius: calc(
|
|
119
|
-
var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1)
|
|
120
|
-
)
|
|
121
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-top, 1))
|
|
122
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1))
|
|
123
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-bottom, 1));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
::-webkit-scrollbar-thumb:horizontal {
|
|
127
|
-
border-radius: calc(
|
|
128
|
-
var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1)
|
|
129
|
-
)
|
|
130
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
131
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-right, 1))
|
|
132
|
-
calc(var(--gw-scrollbar-size, 0.4rem) * var(--gw-scrollbar-left, 1));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
::-webkit-scrollbar-thumb:hover {
|
|
136
|
-
background: var(
|
|
137
|
-
--gw-scrollbar-thumb,
|
|
138
|
-
color-mix(
|
|
139
|
-
in oklab,
|
|
140
|
-
var(--gw-color-text-secondary, currentColor) 70%,
|
|
141
|
-
transparent
|
|
142
|
-
)
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
::-webkit-scrollbar-corner {
|
|
147
|
-
background: transparent;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
51
|
:host([hidden]),
|
|
151
52
|
[hidden] {
|
|
152
53
|
display: none !important;
|