@getvitops/core 6.0.0 → 7.0.0
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/CHANGELOG.md +220 -0
- package/css/animation.css +9 -0
- package/css/index.css +2 -0
- package/css/patterns/counter.css +50 -0
- package/css/patterns/forms.css +1 -1
- package/css/patterns/gallery.css +60 -0
- package/css/patterns/lightbox.css +22 -41
- package/css/patterns/list.css +1 -1
- package/css/patterns/tag.css +1 -2
- package/css/patterns/tree.css +111 -8
- package/css/utilities.css +17 -0
- package/dist/elements.js +7 -844
- package/dist/polyfills/invokers-polyfill.js +1 -0
- package/dist/polyfills/template-shadowroot.js +1 -14
- package/dist/polyfills.js +1 -1
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,225 @@
|
|
|
1
1
|
# @getvitops/core
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- aad43b7: A gallery/lightbox that is a fully working modal lightbox with **zero JavaScript**, plus
|
|
8
|
+
`ImageMetadata` support in `<Carousel />`.
|
|
9
|
+
|
|
10
|
+
Only `@getvitops/core`, `generator` and `astro` change here. This changeset makes **no
|
|
11
|
+
consumer-facing change** to `utils`, `cli` or `vite` — they still ship in this release because
|
|
12
|
+
the six share one version, but from a different changeset (`localbusiness-schema.md`) that
|
|
13
|
+
touches `utils` and `generator` for an unrelated reason.
|
|
14
|
+
|
|
15
|
+
**Breaking — `css`/`bricks`/`tailwind` (all formats):** the `.lightbox` thumbnail grid is now
|
|
16
|
+
`.gallery`, so it composes with the dialog it opens (`.lightbox-dialog`, unchanged) rather than
|
|
17
|
+
sharing its name. `vitops lint --fix` cannot cover this: `movedTokens()` renames colour token
|
|
18
|
+
names only, and there is no class-rename mechanism in `@getvitops/cli`. Migrate by hand:
|
|
19
|
+
|
|
20
|
+
| Old | New |
|
|
21
|
+
| ------------------ | ----------------- |
|
|
22
|
+
| `.lightbox` | `.gallery` |
|
|
23
|
+
| `.lightbox__item` | `.gallery__item` |
|
|
24
|
+
| `.lightbox__thumb` | `.gallery__thumb` |
|
|
25
|
+
|
|
26
|
+
`.lightbox-dialog` and its `__content` / `__image` / `__close` / `__nav` / `__nav-button` /
|
|
27
|
+
`__caption` parts are unchanged.
|
|
28
|
+
|
|
29
|
+
**Added — `css`/`bricks`/`tailwind` (all formats):** `patterns/gallery.css` — the `.gallery` grid,
|
|
30
|
+
now on the token cascade (`--br-gallery` etc.) it only half-had before.
|
|
31
|
+
|
|
32
|
+
**Added — `wc`:** `<wc-gallery>` (`@getvitops/core/elements`). Wraps a `.gallery` grid of
|
|
33
|
+
`command="show-modal"` triggers and the `.lightbox-dialog`s they open. With no JS, each trigger is
|
|
34
|
+
a real Invoker Commands button for its own dialog — open and close both work. `<wc-gallery>` adds
|
|
35
|
+
what the fallback deliberately cannot contain: prev/next, arrow keys, swipe (horizontal to step,
|
|
36
|
+
vertical to dismiss), an announced image counter, neighbour preload, and a thumbnail → full-image
|
|
37
|
+
`document.startViewTransition` morph where supported. Prev/next can't exist in the fallback — a
|
|
38
|
+
second `show-modal` invoker inside an already-open dialog stacks a second modal rather than
|
|
39
|
+
replacing the first — so it is generated by the element, never authored, the same rule
|
|
40
|
+
`<wc-tree>`'s search field follows.
|
|
41
|
+
|
|
42
|
+
**Added — `astro`:** `<Gallery />` (`@getvitops/astro/components/Gallery.astro`) — emits a
|
|
43
|
+
`.gallery` grid plus one `.lightbox-dialog` per image and its own `<wc-gallery>`; do not wrap it in
|
|
44
|
+
one. Images take `string | ImageMetadata`, rendered through a new internal `<ResponsiveImage />`
|
|
45
|
+
helper: a plain string renders the same raw `<img>` every component here used before, unchanged;
|
|
46
|
+
an `astro:assets` import renders through `<Picture />` for AVIF/WebP derivatives.
|
|
47
|
+
|
|
48
|
+
**Changed — `astro`:** `<Carousel />`'s `CarouselSlide.src` now also accepts `ImageMetadata`,
|
|
49
|
+
through the same `<ResponsiveImage />` helper, plus a new `formats` prop. Every existing
|
|
50
|
+
string-`src` call site renders byte-identical markup — purely additive.
|
|
51
|
+
|
|
52
|
+
**Added — `wc`:** the Invoker Commands API (`<button command commandfor>`) polyfill
|
|
53
|
+
(`invokers-polyfill`, feature-detected via `@getvitops/core/polyfills`) — resolves the TODO
|
|
54
|
+
`polyfills.ts` carried since 4.0. `<wc-gallery>`'s fallback and `<Drawer />` (which already emitted
|
|
55
|
+
`command="show-modal"` unconditionally) both now get real cross-browser coverage rather than
|
|
56
|
+
depending on it being "too new to rely on yet." Fetched only in an engine that doesn't support the
|
|
57
|
+
API natively, so a current browser downloads none of it.
|
|
58
|
+
|
|
59
|
+
**Migration:** rename `.lightbox` → `.gallery` and `.lightbox__item`/`.lightbox__thumb` →
|
|
60
|
+
`.gallery__item`/`.gallery__thumb` wherever authored (see table above); everything else is
|
|
61
|
+
additive. Consumers using the CSS pattern with no JS need no other change — the fallback markup is
|
|
62
|
+
unchanged in shape, only the class names moved.
|
|
63
|
+
|
|
64
|
+
- eb9ac79: `<wc-tree>` gains WAI-ARIA tree semantics and roving-tabindex keyboard navigation, and the
|
|
65
|
+
keyboard-navigation code behind it lands as shared utilities the next roving-tabindex component
|
|
66
|
+
(Dropdown, Tabs, Combobox) can reuse.
|
|
67
|
+
|
|
68
|
+
**Breaking — `wc` (`@getvitops/core`).** On upgrade, `<wc-tree>` now unwraps every branch's
|
|
69
|
+
`<details>`/`<summary>` into a plain, JS-owned structure (`role="tree"` on the root,
|
|
70
|
+
`role="treeitem"` on every item, `role="group"` on every nested list, a roving `tabindex`, and
|
|
71
|
+
`aria-expanded` mirroring expand state). This is a deliberate, named exception to the framework's
|
|
72
|
+
normal "parse the fallback and augment it in place" rule — the alternative, an ARIA-attribute
|
|
73
|
+
overlay left on top of the native `<details>`/`<summary>` structure, was tried first and rejected
|
|
74
|
+
because `<summary>` stays focusable regardless, which leaves a permanent duplicate `role="button"`
|
|
75
|
+
node inside every branch. The no-JS fallback is completely unaffected: it is still native nested
|
|
76
|
+
`<details>`, exactly as `<Tree />` has always rendered it, and `enhance={false}` still gets the
|
|
77
|
+
bare fragment with no `<wc-tree>` at all.
|
|
78
|
+
|
|
79
|
+
Two concrete things break for a consumer that reached into the enhanced DOM directly:
|
|
80
|
+
- A script holding a reference to the original `<details>`/`<summary>` nodes — including one
|
|
81
|
+
captured via `getElementById` before upgrade, since the flat markup shape's `<details>` carries
|
|
82
|
+
the item's `id` — loses that reference once JS runs. `tree-toggle` (mirroring the existing
|
|
83
|
+
`tree-filter` custom event) is the replacement surface for reacting to expand/collapse.
|
|
84
|
+
- `<wc-tree>` becomes a single tab stop. Tabbing through every row in a large tree no longer
|
|
85
|
+
works; arrow keys replace it (Up/Down between visible nodes, Right/Left to open/close a branch or
|
|
86
|
+
step in/out, Home/End, type-ahead), matching the WAI-ARIA tree pattern. Left/Right flip under
|
|
87
|
+
`direction: rtl`.
|
|
88
|
+
|
|
89
|
+
**Fixed — `wc` (`@getvitops/core`).** `<wc-carousel>`'s dot-nav arrow keys now follow writing
|
|
90
|
+
direction. In an RTL document, `ArrowLeft` moves the roving tabindex to the visually-left dot and
|
|
91
|
+
`ArrowRight` to the visually-right one — previously both were index-based and moved the focus ring
|
|
92
|
+
against the direction the dots are actually drawn in. `ArrowUp`/`ArrowDown` are unchanged and never
|
|
93
|
+
flip (`direction` is a horizontal-axis property). The prev/next buttons are unchanged too — they
|
|
94
|
+
still step to the previous/next slide in both directions, and only their glyph flips. Also fixed:
|
|
95
|
+
two arrow presses faster than the dot nav's smooth scroll can settle now reliably advance two dots
|
|
96
|
+
instead of sometimes landing on the same one twice.
|
|
97
|
+
|
|
98
|
+
**Changed — `css`/`bricks`/`tailwind` (all formats).** `patterns/tree.css` gains a `.tree__summary`
|
|
99
|
+
row class (the enhanced-state replacement for `<summary>`), a hand-rolled expand/collapse animation
|
|
100
|
+
replacing the native `<details>` one that no longer applies post-upgrade, and a focus ring scoped to
|
|
101
|
+
the row rather than the whole item (the roving tabindex now lands on `.tree__item`, whose box wraps
|
|
102
|
+
its entire subtree once expanded). Visually equivalent to before; no consumer class rename needed.
|
|
103
|
+
|
|
104
|
+
**Added — `wc` (`@getvitops/core`, internal).** `web-components/utils/keynav.ts` (generic
|
|
105
|
+
key-decoding + type-ahead primitives) and `utils/tree-nav.ts` (tree-specific navigation decisions)
|
|
106
|
+
are new pure modules, and `utils/RovingTabindex.ts` is a new shared `ReactiveController` — modelled
|
|
107
|
+
on the existing `DragController` — that owns roving-tabindex bookkeeping for any keyboard-navigable
|
|
108
|
+
group. `<wc-carousel>`'s dot nav now runs on it too, and `<wc-gallery>` reuses `keynav.ts`'s
|
|
109
|
+
`stepIndex`. None of the three are in `@getvitops/core`'s public `exports` map. Only
|
|
110
|
+
`RovingTabindex` is re-exported from `web-components/index.ts`, the same way `DragController`
|
|
111
|
+
already is, not as new published API; `keynav.ts` and `tree-nav.ts` stay internal modules imported
|
|
112
|
+
directly by path (as `RovingTabindex.ts` and `gallery.ts` already do) since neither is consumed
|
|
113
|
+
outside the components that already import them directly.
|
|
114
|
+
|
|
115
|
+
**This changeset makes no consumer-facing change in `@getvitops/generator` beyond documentation
|
|
116
|
+
output.** `tiers.ts`'s `tree` entry gains `tree__summary` to its class list and describes the new
|
|
117
|
+
keyboard navigation and ARIA semantics — this changes what `vitops docs components` /
|
|
118
|
+
`concepts/components.md` render for the `tree` pattern, nothing else. `generator`'s real change in
|
|
119
|
+
this release is unrelated to `wc-tree` — see `localbusiness-schema.md`.
|
|
120
|
+
|
|
121
|
+
### Minor Changes
|
|
122
|
+
|
|
123
|
+
- a6a8b39: `radii.pill` / `--br-pill` is gone. `999px` is a shape, not a design decision, so it no longer
|
|
124
|
+
has a config-editable token of its own — it's now the `rounded-full` utility, named and valued
|
|
125
|
+
to match Tailwind's own class, alongside a new `rounded-none`.
|
|
126
|
+
|
|
127
|
+
**Breaking — `css`/`bricks` only** (the tailwind format never emitted `--br-pill`; Tailwind
|
|
128
|
+
already ships `rounded-full`/`rounded-none` natively and always has). A consumer who overrode
|
|
129
|
+
`--br-pill` directly, or set `radii.pill` in their own `design-system.json` expecting it to
|
|
130
|
+
reshape the switch track, the facet-list count or the badge-indicator bubble, has to migrate:
|
|
131
|
+
- The switch track, facet count and badge indicator each already had their own consumer hook
|
|
132
|
+
(`--br-switch-track`, `--br-count`, `--br-badge-indicator`) and now fall back straight to the
|
|
133
|
+
literal `999px` instead of chaining through `--br-pill`. Override the specific hook instead of
|
|
134
|
+
the removed shared one.
|
|
135
|
+
- `.badge` is unaffected — it already resolved to `999px` and still does, now via its own
|
|
136
|
+
override (`patterns.items.badge.overrides.br`) rather than a shared primitive.
|
|
137
|
+
- Anything that wants a fully round corner outside those three patterns should reach for
|
|
138
|
+
`class="rounded-full"` (or `rounded-none` to zero one out) rather than a `--br-*` variable.
|
|
139
|
+
|
|
140
|
+
This is **not** reachable by `vitops lint --fix` — its token-rename table is built from colour
|
|
141
|
+
role names, and there is no equivalent static table for a shape primitive going away — so this
|
|
142
|
+
entry is the migration.
|
|
143
|
+
|
|
144
|
+
`radii.circle` / `--br-circle` (50%) is untouched: `status`, the radio and the switch thumb all
|
|
145
|
+
depend on it, and Tailwind has no equivalent to defer to.
|
|
146
|
+
|
|
147
|
+
- 7b391b3: Add `<wc-counter>` — animates a number from a start value to the value already in its fallback
|
|
148
|
+
text, on intersection. The stat/percentage figure in a media tile ("0 → 94%"), a KPI row, a
|
|
149
|
+
metrics band.
|
|
150
|
+
|
|
151
|
+
No consumer-facing change in this release outside `core`, `generator` and `astro` — `utils`,
|
|
152
|
+
`cli` and `vite` still ship in this release only because the six share one version.
|
|
153
|
+
|
|
154
|
+
**Added — `css`/`bricks`/`tailwind` (all formats):** `.counter` / `.counter__value` /
|
|
155
|
+
`.counter__presentation` (`patterns/counter.css`). A registered `--_n` custom property drives the
|
|
156
|
+
count via a plain CSS `@keyframes` animation — the interpolation and the easing curve are CSS's,
|
|
157
|
+
never duplicated in JS.
|
|
158
|
+
|
|
159
|
+
**Added — `wc` (`@getvitops/core`).** `<wc-counter>` parses the final value out of its own
|
|
160
|
+
`.counter__value` fallback text — never from a separate attribute, so the no-JS reading and the
|
|
161
|
+
animated reading can't disagree — and renders it back through `Intl.NumberFormat`, seeded from the
|
|
162
|
+
fallback text's own separators (a grouped `1,284` never animates through ungrouped intermediates).
|
|
163
|
+
`counter()` cannot do that part: no decimals, no locale grouping, which is the one thing here that
|
|
164
|
+
genuinely needs JavaScript.
|
|
165
|
+
- Triggers on intersection via a single shared `IntersectionObserver`, not on upgrade — a counter
|
|
166
|
+
below the fold that finished before the visitor scrolled to it would have done nothing.
|
|
167
|
+
- `prefers-reduced-motion: reduce` means don't animate at all, checked before the observer is even
|
|
168
|
+
created, with `patterns/counter.css`'s own reduced-motion query as a second belt.
|
|
169
|
+
- The accessible name stays the final value for the whole animation: `.counter__value` is toggled
|
|
170
|
+
to the framework's `.sr-only` utility while running, never hidden — `display: none`/`visibility:
|
|
171
|
+
hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
|
|
172
|
+
screen reader must never be read ~60 intermediate numbers. The animated text lives in a separate
|
|
173
|
+
`aria-hidden` presentation span it never reaches.
|
|
174
|
+
- `data-from` (default `0`), `data-duration` (ms, default `1200`), `data-easing` (`linear` |
|
|
175
|
+
`ease-in` | `ease-out` | `float-sine` | `float-bounce` — maps onto the real
|
|
176
|
+
`--custom-ease-*`/`--ease-float-*` tokens, so a curve edited in `animation.css` cannot drift out
|
|
177
|
+
of step with this element), `data-decimals` (inferred from the fallback text when absent).
|
|
178
|
+
- Never leaves an intermediate value on screen as the resting state: disconnecting mid-animation
|
|
179
|
+
restores the fallback immediately, and it was never mutated in the first place — only the
|
|
180
|
+
presentation span was ever written to.
|
|
181
|
+
|
|
182
|
+
**Added — `astro`.** `<Counter value="94%" />` emits its own `<wc-counter>` — do NOT wrap it in
|
|
183
|
+
one, same rule as `Gallery`/`Carousel`/`Tree`. `value` is the only source of the final figure;
|
|
184
|
+
there is no separate `to` prop, for the same one-source-of-truth reason the element itself parses
|
|
185
|
+
its fallback rather than taking a duplicate attribute. `enhance={false}` emits the bare fallback
|
|
186
|
+
for a page that cannot run `elements.js`.
|
|
187
|
+
|
|
188
|
+
### Patch Changes
|
|
189
|
+
|
|
190
|
+
- **Added — `utils`.** `itemListGraph()`, a pure schema.org `ItemList` JSON-LD builder — a sibling
|
|
191
|
+
of `breadcrumbGraph()`/`localBusinessGraph()`. Extracted from `@getvitops/astro`'s `<Carousel />`
|
|
192
|
+
so any consumer can build a valid `ItemList` (Google's "all-in-one page" carousel format:
|
|
193
|
+
`ListItem.item` carrying `@type`/`name`/`url`) for content that isn't one of the six Google
|
|
194
|
+
carousel rich-result types (`Article`/`Recipe`/`Movie`/`Course`/`Restaurant`/`Product`) — the
|
|
195
|
+
entity type is no longer constrained to that set.
|
|
196
|
+
|
|
197
|
+
**Changed — `astro`.** `<Carousel />`'s `CarouselItem['type']` now accepts any string, not just
|
|
198
|
+
the six carousel rich-result types — non-breaking, the union only widens. It's a thin wrapper
|
|
199
|
+
over `itemListGraph()` now; behaviour for existing callers is unchanged.
|
|
200
|
+
|
|
201
|
+
No consumer-facing change in this release outside `utils` and `astro` — `generator`, `cli` and
|
|
202
|
+
`vite` still ship in this release only because the six share one version.
|
|
203
|
+
|
|
204
|
+
**Fixed — `core` (`css`/`bricks`/`tailwind` build output, all formats).** The CSS and HTML
|
|
205
|
+
authored inside Lit's `css`/`html` tagged templates in `<wc-*>` component sources previously
|
|
206
|
+
survived JS minification verbatim — a JS minifier treats a template literal's cooked text as
|
|
207
|
+
opaque, so `elements.js` shipped ~690 lines of indented, unminified CSS (and the equivalent in
|
|
208
|
+
HTML) inside an otherwise-minified 886-line file. Two source-level build transforms now collapse
|
|
209
|
+
both before bundling: `postcss` + `postcss-lit` + `cssnano` for `css` templates, `minify-html-literals`
|
|
210
|
+
(`conservativeCollapse: true`, so whitespace between inline elements in light-DOM components never
|
|
211
|
+
collapses to zero) for `html` templates. `elements.js` drops from 886 lines / ~101 KB to 49 lines /
|
|
212
|
+
~96 KB. A second build step deduplicates the 17 `@license` comment blocks (only 6 are textually
|
|
213
|
+
distinct) down to one verbatim copy of each — required by BSD-3/MIT/Apache-2.0's notice-retention
|
|
214
|
+
terms either way, just no longer repeated once per source file that carries it. No API change;
|
|
215
|
+
build-time only, and none of the added tooling (`postcss`, `postcss-lit`, `cssnano`,
|
|
216
|
+
`minify-html-literals`) is a runtime or published dependency.
|
|
217
|
+
|
|
218
|
+
- Updated dependencies [c0c092b]
|
|
219
|
+
- Updated dependencies
|
|
220
|
+
- Updated dependencies [2b50f9e]
|
|
221
|
+
- @getvitops/utils@7.0.0
|
|
222
|
+
|
|
3
223
|
## 6.0.0
|
|
4
224
|
|
|
5
225
|
### Major Changes
|
package/css/animation.css
CHANGED
|
@@ -71,6 +71,15 @@
|
|
|
71
71
|
animation-duration: var(--animation-duration-xslow);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/* The gallery/lightbox thumbnail → full-image morph (see patterns/lightbox.css)
|
|
75
|
+
needs a shorter duration than a whole-page navigation. Scoped to `*` because a
|
|
76
|
+
repo-wide `::view-transition-old/new` selector belongs at the engine layer, not
|
|
77
|
+
inside the one pattern partial that happens to use it first. */
|
|
78
|
+
::view-transition-old(*),
|
|
79
|
+
::view-transition-new(*) {
|
|
80
|
+
animation-duration: 0.3s;
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
/* ===========================================
|
|
75
84
|
* Keyframes - Layout Stage (most expensive)
|
|
76
85
|
* =========================================== */
|
package/css/index.css
CHANGED
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
panel surface navshell's desktop block has to undo. */
|
|
44
44
|
@import './patterns/navshell.css';
|
|
45
45
|
@import './patterns/notification.css';
|
|
46
|
+
@import './patterns/gallery.css';
|
|
46
47
|
@import './patterns/lightbox.css';
|
|
47
48
|
@import './patterns/overlay.css';
|
|
48
49
|
/* was forms.css */
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
@import './patterns/text-effects.css';
|
|
70
71
|
@import './patterns/svg.css';
|
|
71
72
|
@import './patterns/marquee.css';
|
|
73
|
+
@import './patterns/counter.css';
|
|
72
74
|
@import './patterns/nav.css';
|
|
73
75
|
@import './patterns/carousel.css';
|
|
74
76
|
@import './patterns/copy.css';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counter — animates a number from a start value to the value already in its
|
|
3
|
+
* fallback text (`.counter__value`), e.g. the "0 → 94%" stat in a media tile.
|
|
4
|
+
* Consumes the generated token cascade (src/css/generated/tokens.css) only for
|
|
5
|
+
* `--custom-ease-out` (animation.css's engine layer).
|
|
6
|
+
*
|
|
7
|
+
* `<wc-counter>` owns the reading; this partial owns the interpolation. `--_n`
|
|
8
|
+
* is a registered custom property so the browser can smoothly animate it as a
|
|
9
|
+
* number — `counter()` cannot do decimals or locale grouping, so JS mirrors
|
|
10
|
+
* `--_n` into `.counter__presentation`'s text each frame via
|
|
11
|
+
* `Intl.NumberFormat`, formatted the same way the fallback text was. CSS still
|
|
12
|
+
* owns the interpolation, the easing curve and (as a second belt alongside the
|
|
13
|
+
* JS check) `prefers-reduced-motion`.
|
|
14
|
+
*
|
|
15
|
+
* `.counter__value` is toggled to the framework's `.sr-only` utility while
|
|
16
|
+
* running, not hidden — an element pulled out of the accessibility tree via
|
|
17
|
+
* `display: none`/`visibility: hidden` stops contributing to the accessible
|
|
18
|
+
* name, and that name must read the final value for the whole animation, not
|
|
19
|
+
* just at rest.
|
|
20
|
+
*/
|
|
21
|
+
@property --_n {
|
|
22
|
+
syntax: '<number>';
|
|
23
|
+
initial-value: 0;
|
|
24
|
+
inherits: false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.counter__presentation {
|
|
28
|
+
display: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
wc-counter[running] .counter__presentation {
|
|
32
|
+
display: inline;
|
|
33
|
+
animation: counter-run var(--counter-duration, 1.2s) var(--counter-easing, var(--custom-ease-out))
|
|
34
|
+
forwards;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@keyframes counter-run {
|
|
38
|
+
from {
|
|
39
|
+
--_n: var(--counter-from, 0);
|
|
40
|
+
}
|
|
41
|
+
to {
|
|
42
|
+
--_n: var(--counter-to);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (prefers-reduced-motion: reduce) {
|
|
47
|
+
wc-counter[running] .counter__presentation {
|
|
48
|
+
animation: none;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/css/patterns/forms.css
CHANGED
|
@@ -287,7 +287,7 @@ input[type='color'] {
|
|
|
287
287
|
position: relative;
|
|
288
288
|
inline-size: var(--_switch-width);
|
|
289
289
|
block-size: var(--_switch-height);
|
|
290
|
-
border-radius: var(--br-switch-track,
|
|
290
|
+
border-radius: var(--br-switch-track, 999px);
|
|
291
291
|
background: var(--color-bg-surface-muted);
|
|
292
292
|
transition: background-color 0.2s ease;
|
|
293
293
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gallery — thumbnail grid whose triggers open a `.lightbox-dialog` (patterns/lightbox.css).
|
|
3
|
+
* Consumes the generated token cascade (src/css/generated/tokens.css).
|
|
4
|
+
*
|
|
5
|
+
* Split out of what used to be `.lightbox`'s grid half. The two are separate
|
|
6
|
+
* concerns — "a grid of images" and "view one full-screen" — and only the dialog
|
|
7
|
+
* half needs the `lightbox` name; the grid never was one.
|
|
8
|
+
*
|
|
9
|
+
* `<wc-gallery>` wraps this and augments the per-image `.lightbox-dialog`s it finds
|
|
10
|
+
* with prev/next, arrow keys, swipe and a counter. With no JS each `.gallery__trigger`
|
|
11
|
+
* is a real `command="show-modal" commandfor="…"` invoker for its own dialog, so the
|
|
12
|
+
* grid + dialogs already form a working modal lightbox before any script runs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
wc-gallery {
|
|
16
|
+
/* Unregistered custom elements default to `display: inline`, which forms an
|
|
17
|
+
inline box around this grid's block content until `elements.js` upgrades it. */
|
|
18
|
+
display: block;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.gallery {
|
|
22
|
+
--_br: var(--br-gallery, var(--br-gallery-group, var(--br-default)));
|
|
23
|
+
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--gallery-thumb-size, 150px), 1fr));
|
|
26
|
+
gap: var(--gallery-gap, 0.5rem);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.gallery__item {
|
|
30
|
+
aspect-ratio: var(--gallery-aspect, 1);
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
border-radius: var(--_br);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.gallery__trigger {
|
|
36
|
+
display: block;
|
|
37
|
+
inline-size: 100%;
|
|
38
|
+
block-size: 100%;
|
|
39
|
+
margin: 0;
|
|
40
|
+
padding: 0;
|
|
41
|
+
background: none;
|
|
42
|
+
border: none;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
|
|
45
|
+
&:focus-visible {
|
|
46
|
+
outline: 2px solid var(--gallery-focus-color, currentColor);
|
|
47
|
+
outline-offset: 2px;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.gallery__thumb {
|
|
52
|
+
inline-size: 100%;
|
|
53
|
+
block-size: 100%;
|
|
54
|
+
object-fit: cover;
|
|
55
|
+
transition: scale 0.3s ease;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.gallery__trigger:hover .gallery__thumb {
|
|
59
|
+
scale: 1.05;
|
|
60
|
+
}
|
|
@@ -1,42 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightbox —
|
|
2
|
+
* Lightbox — the full-screen `<dialog>` a gallery trigger opens.
|
|
3
3
|
* Consumes the generated token cascade (src/css/generated/tokens.css).
|
|
4
|
+
*
|
|
5
|
+
* The thumbnail grid that drives this moved to `patterns/gallery.css`
|
|
6
|
+
* (`.gallery` / `.gallery__item` / `.gallery__trigger` / `.gallery__thumb`) — this
|
|
7
|
+
* partial owns only the dialog a trigger opens. `<Gallery />` emits both; this
|
|
8
|
+
* file has no opinion on what triggers it.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
/* ── Lightbox —
|
|
7
|
-
.lightbox {
|
|
8
|
-
--_br: var(--br-lightbox, var(--br-lightbox-group, var(--br-default)));
|
|
9
|
-
|
|
10
|
-
display: grid;
|
|
11
|
-
grid-template-columns: repeat(auto-fill, minmax(var(--lightbox-thumb-size, 150px), 1fr));
|
|
12
|
-
gap: var(--lightbox-gap, 0.5rem);
|
|
13
|
-
|
|
14
|
-
.lightbox__item {
|
|
15
|
-
position: relative;
|
|
16
|
-
aspect-ratio: var(--lightbox-aspect, 1);
|
|
17
|
-
overflow: hidden;
|
|
18
|
-
border-radius: var(--_br);
|
|
19
|
-
cursor: pointer;
|
|
20
|
-
|
|
21
|
-
&:focus-visible {
|
|
22
|
-
outline: 2px solid var(--lightbox-focus-color, currentColor);
|
|
23
|
-
outline-offset: 2px;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.lightbox__thumb {
|
|
28
|
-
inline-size: 100%;
|
|
29
|
-
block-size: 100%;
|
|
30
|
-
object-fit: cover;
|
|
31
|
-
transition: scale 0.3s ease;
|
|
32
|
-
view-transition-name: match-element;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.lightbox__item:hover .lightbox__thumb {
|
|
36
|
-
scale: 1.05;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
11
|
+
/* ── Lightbox dialog — full-screen, w/ view transitions ─────────────────────── */
|
|
40
12
|
dialog.lightbox-dialog {
|
|
41
13
|
position: fixed;
|
|
42
14
|
inset: 0;
|
|
@@ -56,6 +28,14 @@ dialog.lightbox-dialog {
|
|
|
56
28
|
::backdrop rule, so it keeps the shared fade timing. */
|
|
57
29
|
--scrim: transparent;
|
|
58
30
|
|
|
31
|
+
/* The entry, as effect vars rather than a keyframe — same approach as
|
|
32
|
+
dialog.css, and for the same reason: the top-layer driver in animation.css
|
|
33
|
+
then animates this OUT too, which an `animation:` on `[open]` never does
|
|
34
|
+
(it only ever plays once, on open). */
|
|
35
|
+
--opacity-from: 0;
|
|
36
|
+
--scale-x-from: 0.95;
|
|
37
|
+
--scale-y-from: 0.95;
|
|
38
|
+
|
|
59
39
|
&[open] {
|
|
60
40
|
display: grid;
|
|
61
41
|
}
|
|
@@ -73,7 +53,13 @@ dialog.lightbox-dialog {
|
|
|
73
53
|
max-inline-size: 100%;
|
|
74
54
|
max-block-size: 90vh;
|
|
75
55
|
object-fit: contain;
|
|
76
|
-
|
|
56
|
+
|
|
57
|
+
/* `<wc-gallery>` assigns `view-transition-name: lightbox-active` to this and
|
|
58
|
+
the originating thumbnail at the moment it opens, then clears it on close —
|
|
59
|
+
that shared, runtime-assigned name is what morphs one into the other.
|
|
60
|
+
`match-element` (the auto-generated per-element name) does NOT do this: it
|
|
61
|
+
names each element uniquely, so it can never morph a thumbnail into a
|
|
62
|
+
different `<img>`. Do not put a `view-transition-name` here statically. */
|
|
77
63
|
}
|
|
78
64
|
|
|
79
65
|
.lightbox-dialog__close {
|
|
@@ -149,8 +135,3 @@ dialog.lightbox-dialog {
|
|
|
149
135
|
text-align: center;
|
|
150
136
|
}
|
|
151
137
|
}
|
|
152
|
-
|
|
153
|
-
::view-transition-old(*),
|
|
154
|
-
::view-transition-new(*) {
|
|
155
|
-
animation-duration: 0.3s;
|
|
156
|
-
}
|
package/css/patterns/list.css
CHANGED
package/css/patterns/tag.css
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
/* ── Badge indicator — numeric notification bubble (distinct from .badge) ──── */
|
|
30
30
|
.badge-indicator {
|
|
31
31
|
--_size: var(--badge-size, 1.25rem);
|
|
32
|
-
--_br: var(--br-badge, var(--br-badge-group, var(--br-default)));
|
|
33
32
|
|
|
34
33
|
display: inline-flex;
|
|
35
34
|
align-items: center;
|
|
@@ -40,7 +39,7 @@
|
|
|
40
39
|
font-size: var(--fs-badge, var(--fs-badge-group, var(--fs-default)));
|
|
41
40
|
font-weight: 600;
|
|
42
41
|
line-height: 1;
|
|
43
|
-
border-radius: var(--br-badge-indicator,
|
|
42
|
+
border-radius: var(--br-badge-indicator, 999px);
|
|
44
43
|
background: var(--color-text-danger);
|
|
45
44
|
color: #fff;
|
|
46
45
|
|
package/css/patterns/tree.css
CHANGED
|
@@ -44,10 +44,16 @@ wc-tree {
|
|
|
44
44
|
position: relative;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/* Nested list indent. Two shapes are supported
|
|
48
|
-
`<details>`, or be an `<li>` wrapping one — the latter is
|
|
49
|
-
emits, because `<ul>`/`<li>` is what gives assistive tech
|
|
50
|
-
|
|
47
|
+
/* Nested list indent. Two shapes are supported IN THE NO-JS FALLBACK: the
|
|
48
|
+
item may BE the `<details>`, or be an `<li>` wrapping one — the latter is
|
|
49
|
+
what `<Tree />` emits, because `<ul>`/`<li>` is what gives assistive tech
|
|
50
|
+
list position and depth. `<wc-tree>` unwraps both shapes to one structure
|
|
51
|
+
on upgrade (`.tree__item` > `.tree__summary`/`.tree__content` + optional
|
|
52
|
+
nested `.tree`), which is why the first selector below already matches
|
|
53
|
+
the ENHANCED state for both former shapes with no changes — only the
|
|
54
|
+
second (`.tree__item > details > .tree`) is fallback-only now, kept for
|
|
55
|
+
the no-JS list shape. Same split applies to every other
|
|
56
|
+
`details`/`summary`-keyed selector in this file below.
|
|
51
57
|
|
|
52
58
|
Indent is ONE `--tree-indent` per level, not two. This used to set both
|
|
53
59
|
`margin-inline-start` and `padding-inline-start` to it, putting the guide
|
|
@@ -85,6 +91,7 @@ wc-tree {
|
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
.tree__content,
|
|
94
|
+
.tree__summary,
|
|
88
95
|
details.tree__item > summary,
|
|
89
96
|
.tree__item > details > summary {
|
|
90
97
|
display: flex;
|
|
@@ -100,6 +107,26 @@ wc-tree {
|
|
|
100
107
|
padding-inline-start: var(--_leaf-indent);
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
/* `.tree__summary` — the enhanced-state row `<wc-tree>` builds on upgrade,
|
|
111
|
+
replacing `<summary>`. Not `.tree__content`: that class's
|
|
112
|
+
`padding-inline-start: var(--_leaf-indent)` exists specifically to make a
|
|
113
|
+
TOGGLE-LESS row line up with one that has a toggle — applying it to a row
|
|
114
|
+
that DOES have a toggle would double-indent every branch, the mirror
|
|
115
|
+
image of the 24px-misalignment bug the comment above this block
|
|
116
|
+
documents fixing. */
|
|
117
|
+
.tree__summary {
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
/* Only source of this in the enhanced state — `<summary>`'s own
|
|
120
|
+
`user-select: none` came from `details.css`'s bare `details` selector,
|
|
121
|
+
which no longer matches once `<details>` is gone. Without it, a
|
|
122
|
+
drag-select across a label also fires a `click` and toggles the row. */
|
|
123
|
+
user-select: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Fallback-only from here: no-JS renders `<details>`/`<summary>`, which
|
|
127
|
+
`<wc-tree>` unwraps on upgrade (see the class docblock in `WCTree.ts`).
|
|
128
|
+
Kept for the two SSR'd shapes; nothing in the enhanced state matches
|
|
129
|
+
these. */
|
|
103
130
|
details.tree__item > summary,
|
|
104
131
|
.tree__item > details > summary {
|
|
105
132
|
cursor: pointer;
|
|
@@ -127,6 +154,13 @@ wc-tree {
|
|
|
127
154
|
transition: transform 0.2s ease;
|
|
128
155
|
}
|
|
129
156
|
|
|
157
|
+
/* Enhanced state: `aria-expanded` is the mirror `#setExpanded` writes
|
|
158
|
+
alongside the group's `hidden`, kept in sync with it — see WCTree.ts. */
|
|
159
|
+
.tree__item[aria-expanded='true'] > .tree__summary .tree__toggle {
|
|
160
|
+
transform: rotate(90deg);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Fallback-only — see the note above `details.tree__item > summary`. */
|
|
130
164
|
details.tree__item[open] > summary .tree__toggle,
|
|
131
165
|
.tree__item > details[open] > summary .tree__toggle {
|
|
132
166
|
transform: rotate(90deg);
|
|
@@ -165,8 +199,12 @@ wc-tree {
|
|
|
165
199
|
transition: opacity 0.15s ease;
|
|
166
200
|
}
|
|
167
201
|
|
|
168
|
-
|
|
169
|
-
|
|
202
|
+
/* `>`, not a descendant selector: `.tree__item` now carries the roving
|
|
203
|
+
tabindex directly, so arrow-keying to a deep item would otherwise light
|
|
204
|
+
up every ANCESTOR branch's own actions row too, not just the row hovered
|
|
205
|
+
or focused. */
|
|
206
|
+
.tree__item:hover > .tree__actions,
|
|
207
|
+
.tree__item:focus-within > .tree__actions {
|
|
170
208
|
opacity: 1;
|
|
171
209
|
}
|
|
172
210
|
|
|
@@ -174,6 +212,65 @@ wc-tree {
|
|
|
174
212
|
opacity: 0.5;
|
|
175
213
|
}
|
|
176
214
|
|
|
215
|
+
/* Focus now lands on `.tree__item` itself (the roving tabindex target), a
|
|
216
|
+
block box that — for an expanded branch — wraps its entire subtree. An
|
|
217
|
+
outline on it would ring every descendant, so it's suppressed there and
|
|
218
|
+
drawn on the ROW instead. */
|
|
219
|
+
.tree__item:focus {
|
|
220
|
+
outline: none;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* `:focus-visible` alone under-covers a real case: script-moved focus only
|
|
224
|
+
matches `:focus-visible` if the PREVIOUSLY focused element did, so a
|
|
225
|
+
mouse click (which doesn't match) followed immediately by an arrow key
|
|
226
|
+
shows no ring at all in Chrome/Safari until the visitor tabs fully out
|
|
227
|
+
and back in. `[data-tree-keyboard]` is `<wc-tree>`'s own signal — set on
|
|
228
|
+
any handled navigation keydown, cleared on `pointerdown` — and covers
|
|
229
|
+
exactly that gap; `happy-dom` implements neither `:focus-visible` nor
|
|
230
|
+
layout, so this needs a manual browser check, not a unit test. */
|
|
231
|
+
.tree__item:focus-visible > .tree__summary,
|
|
232
|
+
.tree__item:focus-visible > .tree__content,
|
|
233
|
+
&[data-tree-keyboard] .tree__item:focus > .tree__summary,
|
|
234
|
+
&[data-tree-keyboard] .tree__item:focus > .tree__content {
|
|
235
|
+
outline: 2px solid var(--tree-focus-color, currentColor);
|
|
236
|
+
/* Inset, not the pattern's usual positive offset: the nested group below
|
|
237
|
+
clips its content (`overflow: clip`, next rule), so a positive offset
|
|
238
|
+
on the first or last row of an expanded subtree would be cut off by
|
|
239
|
+
its own ancestor. */
|
|
240
|
+
outline-offset: -2px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Animated collapse — the enhanced-state replacement for `details.css`'s
|
|
244
|
+
`::details-content` transition, which no longer applies once `<details>`
|
|
245
|
+
is gone. Keyed on `[hidden]` itself, NOT on `[aria-expanded]`: the
|
|
246
|
+
browser's own find-in-page / fragment-navigation reveal removes `hidden`
|
|
247
|
+
directly or CSS anchored to a JS-set `aria-expanded` mirror would leave a
|
|
248
|
+
UA-revealed branch visually still collapsed until a `beforematch` handler
|
|
249
|
+
caught up (see WCTree.ts). Same idiom `details.css` verified working, and
|
|
250
|
+
the same warning applies: the tell of a regression is a branch that
|
|
251
|
+
doesn't open on the FIRST click, not a syntax error — check a real
|
|
252
|
+
interaction. */
|
|
253
|
+
.tree__item > .tree {
|
|
254
|
+
/* `clip` + a small margin, not `hidden`: an `overflow: hidden` group
|
|
255
|
+
would clip the focus ring (`outline-offset: -2px` above still needs a
|
|
256
|
+
few px of headroom) of its own first/last child row. */
|
|
257
|
+
overflow: clip;
|
|
258
|
+
overflow-clip-margin: 3px;
|
|
259
|
+
|
|
260
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
261
|
+
block-size: auto;
|
|
262
|
+
transition:
|
|
263
|
+
block-size var(--animation-duration) var(--custom-ease-out),
|
|
264
|
+
content-visibility var(--animation-duration) allow-discrete;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
269
|
+
.tree__item > .tree[hidden] {
|
|
270
|
+
block-size: 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
177
274
|
&.tree--lines .tree__item {
|
|
178
275
|
position: relative;
|
|
179
276
|
|
|
@@ -195,10 +292,16 @@ wc-tree {
|
|
|
195
292
|
|
|
196
293
|
/* A node's own prose. `.tree__label` is `nowrap` + ellipsis for file-tree-style
|
|
197
294
|
single-line names, so a description can't live in it — it sits below the
|
|
198
|
-
summary row and wraps. Indented to the label, past the toggle.
|
|
295
|
+
summary row and wraps. Indented to the label, past the toggle.
|
|
296
|
+
|
|
297
|
+
`margin-inline-end` is set here rather than left to come from an
|
|
298
|
+
ancestor: a BRANCH description used to get it incidentally from
|
|
299
|
+
`details.css`'s `<details>` margins (now gone — see the class docblock in
|
|
300
|
+
`WCTree.ts`), while a LEAF description, never inside a `<details>`, never
|
|
301
|
+
got one at all. Self-contained now, and consistent between the two. */
|
|
199
302
|
.tree__desc {
|
|
200
303
|
margin-block: 0.125rem 0.25rem;
|
|
201
|
-
margin-inline
|
|
304
|
+
margin-inline: var(--_leaf-indent) 0.25rem;
|
|
202
305
|
color: var(--color-text-surface-muted);
|
|
203
306
|
text-wrap: pretty;
|
|
204
307
|
}
|