@getvitops/core 5.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 CHANGED
@@ -1,5 +1,283 @@
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
+
223
+ ## 6.0.0
224
+
225
+ ### Major Changes
226
+
227
+ - Carousel rework: a scroll strip that is operable and discoverable with no JS in every engine, and
228
+ real controls where the scroll-marker pseudo-elements are missing.
229
+
230
+ `::scroll-button()`, `::scroll-marker` and `::scroll-marker-group` are still Chromium-only, and
231
+ there is no usable polyfill (the one prototype targets the superseded syntax and only re-parses
232
+ inline `<style>` elements). So the carousel no longer assumes them: `<wc-carousel>` builds real
233
+ prev/next `<button>`s and a real dot nav where they are absent, styled from the same declarations
234
+ as the native ones. Which branch is live is decided **by the stylesheet** — one `@supports` sets
235
+ `--carousel-native-nav: 1` and the element reads it back — so the condition is written once and
236
+ the two cannot disagree.
237
+
238
+ **Breaking — `.carousel` is now a shell and `.carousel__track` is the scroller** (all formats). The
239
+ hint and the controls need a home outside the scrollport, and absolute positioning against the
240
+ shell replaces `position: fixed` + `anchor-name`/`position-area`, which pinned controls to a
241
+ viewport corner in any engine without anchor positioning. The 5.x single-element form is still
242
+ styled via `:not(:has(> .carousel__track))` and is promoted by the element at runtime, but it is
243
+ deprecated: wrap your slides in `<ul class="carousel__track">`.
244
+
245
+ **Breaking — looping is opt-in.** `<wc-carousel loop>` clones the slides; the default no longer
246
+ does. Cloning triples the markup, duplicates every image element and makes the scrollbar report 3×
247
+ the real content. `autoplay` implies `loop`. Migration: add `loop`.
248
+
249
+ **Breaking — `[part='autoplay']` is now `.carousel__autoplay`** (aliased for one release; `part`
250
+ only crosses a shadow boundary and this element is light DOM, so it was never a part). The button
251
+ is also a child of the shell rather than the track, where it used to occupy a slide-width column.
252
+
253
+ **Breaking — the host role is `group`, not `region`.** `region` is a landmark, so every carousel
254
+ grew an entry in the landmark list. Pass `landmark` to `<Carousel />` where it really is one.
255
+
256
+ **Changed (visual)** — the scrollbar is visible by default (`scrollbar-width: thin`, was `none`)
257
+ and a "Scroll for more" hint renders above the strip; both dim once the visitor scrolls, via a
258
+ scroll-progress timeline where available and a `data-scrolled` attribute otherwise. Opt out with
259
+ `.carousel--no-scrollbar` and `hint={false}`. `@getvitops/core`'s `global.css` also gains a narrow
260
+ media reset (`max-inline-size: 100%` on replaced elements, `block-size: auto` on `img`/`video`) —
261
+ deliberately without `display: block`, which would change inline images in prose.
262
+
263
+ **Added** — `@getvitops/astro/components/Carousel.astro`, a tier-3 wrapper emitting semantic
264
+ `<ul>`/`<li>`/`<figure>` markup with the right per-slide image priorities: the first slide eager
265
+ and high (the LCP candidate), the rest `lazy`/`low`/`async`. `loading="lazy"` is honoured for
266
+ images clipped by a horizontal scroll container, so off-screen slides defer with no JavaScript at
267
+ all. Also new: `carousel--markers-below` and `carousel--no-scrollbar`, and
268
+ `--carousel-slide-aspect`, which keeps slide heights equal whatever each image's aspect ratio is.
269
+
270
+ **Fixed** — `::scroll-button()` glyphs sat off-centre in their circles (`global.css`'s `*` reset
271
+ does not reach a pseudo-element, so the button was `content-box` with the UA's `padding: 1px 6px`
272
+ and `inline-size` + `aspect-ratio` sized an ellipse); the hover state was a no-op (it set the same
273
+ token as the base rule); slide heights varied with each image's aspect ratio; navigation used
274
+ physical `left`/`right` and so put "previous" on the wrong side in RTL; cloned slides duplicated
275
+ their `id`s; and `scrollBy(clientWidth)` overshot whenever `--carousel-slide-size` was not `100%`.
276
+
277
+ ### Patch Changes
278
+
279
+ - @getvitops/utils@6.0.0
280
+
3
281
  ## 5.0.0
4
282
 
5
283
  ### 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/global.css CHANGED
@@ -26,6 +26,30 @@
26
26
  box-sizing: border-box;
27
27
  }
28
28
 
29
+ /* Media never overflows its container. `:where()` so it carries zero specificity
30
+ and this file's layer (`vitops.base`) so it loses to everything, including a
31
+ consumer's unlayered rule.
32
+
33
+ Deliberately NOT the classic `img { display: block }` half of this reset. That
34
+ is the part that breaks things here: inline images in prose lose baseline
35
+ alignment, `patterns/icon.css`'s `.icon > img` and marquee logo runs break onto
36
+ their own lines, and `layout.css`'s
37
+ `.rhythm > :where(p, h1…h6, ul, ol) + :where(figure, img, picture, video)`
38
+ rules start spacing images that were previously inline. Every framework pattern
39
+ that wants a block image already declares it (media.css, subgrid.css,
40
+ carousel.css), which is the right place for it.
41
+
42
+ `svg` is excluded from `block-size: auto` because `patterns/svg.css` ships a
43
+ bare `svg` rule and SVGs commonly size from presentation attributes;
44
+ `max-inline-size` alone is safe there. */
45
+ :where(img, svg, video, canvas, iframe, embed, object) {
46
+ max-inline-size: 100%;
47
+ }
48
+
49
+ :where(img, video) {
50
+ block-size: auto;
51
+ }
52
+
29
53
  /* Drop the UA's 8px body margin. The framework owns page gutters via `.centered`
30
54
  (--gutter), so the UA default just offsets every full-bleed surface — sticky
31
55
  headers and `bg-*` bands end up inset by 8px with a sliver of canvas showing
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';