@getvitops/core 5.0.0 → 6.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,63 @@
1
1
  # @getvitops/core
2
2
 
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Carousel rework: a scroll strip that is operable and discoverable with no JS in every engine, and
8
+ real controls where the scroll-marker pseudo-elements are missing.
9
+
10
+ `::scroll-button()`, `::scroll-marker` and `::scroll-marker-group` are still Chromium-only, and
11
+ there is no usable polyfill (the one prototype targets the superseded syntax and only re-parses
12
+ inline `<style>` elements). So the carousel no longer assumes them: `<wc-carousel>` builds real
13
+ prev/next `<button>`s and a real dot nav where they are absent, styled from the same declarations
14
+ as the native ones. Which branch is live is decided **by the stylesheet** — one `@supports` sets
15
+ `--carousel-native-nav: 1` and the element reads it back — so the condition is written once and
16
+ the two cannot disagree.
17
+
18
+ **Breaking — `.carousel` is now a shell and `.carousel__track` is the scroller** (all formats). The
19
+ hint and the controls need a home outside the scrollport, and absolute positioning against the
20
+ shell replaces `position: fixed` + `anchor-name`/`position-area`, which pinned controls to a
21
+ viewport corner in any engine without anchor positioning. The 5.x single-element form is still
22
+ styled via `:not(:has(> .carousel__track))` and is promoted by the element at runtime, but it is
23
+ deprecated: wrap your slides in `<ul class="carousel__track">`.
24
+
25
+ **Breaking — looping is opt-in.** `<wc-carousel loop>` clones the slides; the default no longer
26
+ does. Cloning triples the markup, duplicates every image element and makes the scrollbar report 3×
27
+ the real content. `autoplay` implies `loop`. Migration: add `loop`.
28
+
29
+ **Breaking — `[part='autoplay']` is now `.carousel__autoplay`** (aliased for one release; `part`
30
+ only crosses a shadow boundary and this element is light DOM, so it was never a part). The button
31
+ is also a child of the shell rather than the track, where it used to occupy a slide-width column.
32
+
33
+ **Breaking — the host role is `group`, not `region`.** `region` is a landmark, so every carousel
34
+ grew an entry in the landmark list. Pass `landmark` to `<Carousel />` where it really is one.
35
+
36
+ **Changed (visual)** — the scrollbar is visible by default (`scrollbar-width: thin`, was `none`)
37
+ and a "Scroll for more" hint renders above the strip; both dim once the visitor scrolls, via a
38
+ scroll-progress timeline where available and a `data-scrolled` attribute otherwise. Opt out with
39
+ `.carousel--no-scrollbar` and `hint={false}`. `@getvitops/core`'s `global.css` also gains a narrow
40
+ media reset (`max-inline-size: 100%` on replaced elements, `block-size: auto` on `img`/`video`) —
41
+ deliberately without `display: block`, which would change inline images in prose.
42
+
43
+ **Added** — `@getvitops/astro/components/Carousel.astro`, a tier-3 wrapper emitting semantic
44
+ `<ul>`/`<li>`/`<figure>` markup with the right per-slide image priorities: the first slide eager
45
+ and high (the LCP candidate), the rest `lazy`/`low`/`async`. `loading="lazy"` is honoured for
46
+ images clipped by a horizontal scroll container, so off-screen slides defer with no JavaScript at
47
+ all. Also new: `carousel--markers-below` and `carousel--no-scrollbar`, and
48
+ `--carousel-slide-aspect`, which keeps slide heights equal whatever each image's aspect ratio is.
49
+
50
+ **Fixed** — `::scroll-button()` glyphs sat off-centre in their circles (`global.css`'s `*` reset
51
+ does not reach a pseudo-element, so the button was `content-box` with the UA's `padding: 1px 6px`
52
+ and `inline-size` + `aspect-ratio` sized an ellipse); the hover state was a no-op (it set the same
53
+ token as the base rule); slide heights varied with each image's aspect ratio; navigation used
54
+ physical `left`/`right` and so put "previous" on the wrong side in RTL; cloned slides duplicated
55
+ their `id`s; and `scrollBy(clientWidth)` overshot whenever `--carousel-slide-size` was not `100%`.
56
+
57
+ ### Patch Changes
58
+
59
+ - @getvitops/utils@6.0.0
60
+
3
61
  ## 5.0.0
4
62
 
5
63
  ### Major Changes
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
@@ -1,112 +1,418 @@
1
1
  /**
2
- * Carousel — CSS-only scroll-snap carousel migrated from old-css-lib. Uses
3
- * ::scroll-button(), ::scroll-marker-group, ::column, scroll-state() and
4
- * anchor positioning (all polyfilled). Fully operable as a scroll-snap strip
5
- * without JS; the Lit component adds autoplay + any marker fallback.
2
+ * Carousel — a scroll-snap strip that is fully operable, and *discoverable*, with
3
+ * no JavaScript in every engine, and that uses the native scroll-marker machinery
4
+ * (`::scroll-button()`, `::scroll-marker`, `::scroll-marker-group`, `::column`)
5
+ * wherever it exists.
6
+ *
7
+ * As of Aug 2026 that machinery is Chromium-135+ only — Safari is landing it,
8
+ * Firefox has it behind flags — and there is no usable polyfill: the only
9
+ * candidate (flackr/carousel's `polyfill/polyfill.js`) is a 2024 prototype on the
10
+ * superseded syntax that re-parses inline `<style>` elements and so never sees a
11
+ * linked stylesheet. So `WCCarousel` builds real `<button>`s and a real dot nav
12
+ * instead, and this file styles BOTH paths identically. See the `@supports` split
13
+ * below for why they can't be one selector list.
14
+ *
15
+ * ── Structure ────────────────────────────────────────────────────────────────
16
+ * .carousel shell. Not a scroller. The <wc-carousel> tag.
17
+ * ├ .carousel__track the scroll container (a <ul role="list"> from Astro).
18
+ * │ └ .carousel__slide a slide — rules apply to any direct child of the track.
19
+ * │ └ .carousel__media aspect-ratio box the image covers.
20
+ * ├ .carousel__hint "Scroll for more" — OUTSIDE the scroller, so it can't
21
+ * │ scroll away with the slides. That is the whole reason
22
+ * │ the shell exists.
23
+ * ├ .carousel__button JS-built prev/next (fallback path only).
24
+ * ├ .carousel__markers JS-built dot nav (fallback path only).
25
+ * └ .carousel__autoplay play/pause toggle, built by the element.
26
+ *
27
+ * The shell also replaces the previous `position: fixed` + `anchor-name` /
28
+ * `position-area` placement of the controls. That was wrong three ways: an engine
29
+ * without anchor positioning pinned the real autoplay button to a VIEWPORT corner
30
+ * (the oddbird polyfill supports neither `position-area` nor a pseudo-element
31
+ * anchor, so the postrender polyfill never rescued it); `anchor-scope: --carousel`
32
+ * was one global name doing per-instance work; and the shell is already outside
33
+ * the scrollport, so `position: absolute` against it neither scrolls nor clips
34
+ * without any of that machinery.
35
+ *
36
+ * ── Modifiers ────────────────────────────────────────────────────────────────
37
+ * carousel--scroll-buttons prev/next
38
+ * carousel--scroll-markers dot nav
39
+ * carousel--auto-pages CSS multicol paging (`::column`) — Chromium-only,
40
+ * degrades to a plain multicol strip elsewhere
41
+ * carousel--inert dim + inert the off-screen slides
42
+ * carousel--force-stop `scroll-snap-stop: always`
43
+ * carousel--no-scrollbar opt out of the visible scrollbar
44
+ * carousel--markers-below dots below the strip instead of overlaid
6
45
  */
7
46
 
47
+ /* An unregistered custom element is `display: inline`, and this one wraps a block
48
+ scroller — the KNOWN GAP recorded in js/elements.ts. The grid lives on
49
+ `.carousel`, which is what an author actually writes; the tag only needs to
50
+ stop being inline before the element upgrades. */
51
+ wc-carousel {
52
+ display: block;
53
+ }
54
+
55
+ /* ── Feature probe ───────────────────────────────────────────────────────────
56
+ ONE declaration of each condition. `WCCarousel` does NOT re-run these with
57
+ `CSS.supports`; it reads the resolved custom property back off the element, so
58
+ the stylesheet and the component cannot disagree about which branch is live.
59
+ Two hand-synced condition strings — one here, one in JS — is the drift this
60
+ avoids, and its failure mode is silent duplicate controls sitting under working
61
+ native ones.
62
+
63
+ It also answers the harder question correctly. The real question is not "does
64
+ this engine support ::scroll-button()" but "did MY stylesheet's native branch
65
+ apply". A page that loads elements.js without the framework CSS has no
66
+ pseudo-elements to collide with — the probe reads empty and the fallback is
67
+ built. `CSS.supports` would have said yes and built nothing. */
68
+ .carousel {
69
+ --carousel-native-nav: 0;
70
+ --carousel-scroll-timeline: 0;
71
+ }
72
+
73
+ /* `(scroll-marker-group: after)` is ANDed in because `selector()` support for a
74
+ functional pseudo-element is the weaker signal of the two. */
75
+ @supports selector(::scroll-marker) and selector(::scroll-button(inline-end)) and
76
+ (scroll-marker-group: after) {
77
+ .carousel {
78
+ --carousel-native-nav: 1;
79
+ }
80
+ }
81
+
82
+ @supports (animation-timeline: scroll()) {
83
+ .carousel {
84
+ --carousel-scroll-timeline: 1;
85
+ }
86
+ }
87
+
88
+ /* ── Shell ───────────────────────────────────────────────────────────────────*/
89
+
8
90
  .carousel {
91
+ position: relative; /* containing block for both control paths */
92
+ display: grid;
93
+ grid-template-columns: minmax(0, 1fr);
94
+ row-gap: var(--carousel-shell-gap, 0.5rem);
95
+
96
+ /* Hoist the track's scroll-progress timeline so the hint — a SIBLING of the
97
+ scroller, not a descendant — can read it. This is why the hint fade is not a
98
+ `scroll-state()` container query: a scroll-state query's rules apply to
99
+ descendants of the scroll container, and the hint deliberately isn't one.
100
+ `timeline-scope` also makes it order-independent, so the hint may precede the
101
+ track in source. */
102
+ timeline-scope: --carousel-scroll;
103
+ }
104
+
105
+ /* ── Track ───────────────────────────────────────────────────────────────────
106
+ The second selector is the deprecated 5.x single-element form, where
107
+ `.carousel` IS the scroller and the slides are its bare children. It has
108
+ nowhere to put the hint or the fallback controls, so it is kept only for one
109
+ major; migration is a single wrapper element. A selector list rather than a
110
+ `:is()` because `:has()` is Baseline and this stays readable. */
111
+ .carousel__track,
112
+ .carousel:not(:has(> .carousel__track)) {
113
+ margin: 0;
114
+ padding: var(--carousel-padding, 0);
115
+ list-style: none;
116
+ max-inline-size: 100%;
9
117
  overflow-x: auto;
10
- scroll-behavior: smooth;
11
118
  overscroll-behavior-x: contain;
12
- scrollbar-width: none;
119
+ scroll-behavior: smooth;
13
120
  scroll-snap-type: x mandatory;
14
- scroll-padding: var(--carousel-scroll-padding, 1rem);
15
- padding: var(--carousel-padding, 1rem);
16
- max-inline-size: 100%;
17
- anchor-name: --carousel;
18
- anchor-scope: --carousel;
121
+ scroll-padding-inline: var(--carousel-scroll-padding, 0px);
122
+
123
+ /* The timeline the hint and the scrollbar fade read, declared here and
124
+ consumed on the shell's other children through `timeline-scope`. */
125
+ scroll-timeline: --carousel-scroll inline;
126
+
127
+ /* For `carousel--inert`, whose targets genuinely ARE descendants of the
128
+ scroller — the one place a scroll-state query is applicable here. */
129
+ container-type: scroll-state;
130
+
131
+ /* A thin, styled scrollbar is the default affordance. `scrollbar-width: none`
132
+ removed the only no-JS signal that the strip scrolls at all, on the
133
+ assumption the ::scroll-button()s were present — the exact assumption that
134
+ fails in every engine but one. `.carousel--no-scrollbar` opts out. */
135
+ scrollbar-width: thin;
136
+ scrollbar-color: var(--carousel-scrollbar-thumb, var(--color-bg-surface-bold))
137
+ var(--carousel-scrollbar-track, transparent);
138
+ }
139
+
140
+ .carousel--no-scrollbar > .carousel__track,
141
+ .carousel--no-scrollbar:not(:has(> .carousel__track)) {
142
+ scrollbar-width: none;
143
+
144
+ &::-webkit-scrollbar {
145
+ display: none;
146
+ }
147
+ }
19
148
 
20
- /* Non-paged (default) — grid layout */
21
- &:not(.carousel--auto-pages) {
149
+ /* Non-paged (default) — one grid column per slide. */
150
+ .carousel:not(.carousel--auto-pages) > .carousel__track,
151
+ .carousel:not(.carousel--auto-pages):not(:has(> .carousel__track)) {
152
+ display: grid;
153
+ grid-auto-flow: column;
154
+ grid-auto-columns: var(--carousel-slide-size, 100%);
155
+ gap: var(--carousel-gap, 1rem);
156
+ /* Equal-height slides whatever the media. */
157
+ align-items: stretch;
158
+
159
+ > * {
160
+ scroll-snap-align: center;
161
+ min-inline-size: 0;
162
+ /* A slide is a stretched grid item, so its media box has a definite
163
+ block-size for `object-fit: cover` to fill. */
22
164
  display: grid;
23
- grid-auto-flow: column;
24
- grid-auto-columns: var(--carousel-slide-size, 100%);
25
- gap: var(--carousel-gap, 1rem);
165
+ grid-template-rows: auto;
166
+ align-content: start;
167
+ }
168
+ }
26
169
 
27
- > * {
28
- scroll-snap-align: center;
29
- }
170
+ /* Paged — CSS multicol. `::column` is left ungated: an engine without it renders
171
+ a plain multicol strip, which still scrolls and snaps. */
172
+ .carousel--auto-pages > .carousel__track,
173
+ .carousel--auto-pages:not(:has(> .carousel__track)) {
174
+ columns: var(--carousel-columns, 1);
175
+ text-align: center;
30
176
 
31
- > [part='autoplay'] {
32
- scroll-snap-align: unset;
33
- }
177
+ > * {
178
+ break-inside: avoid;
179
+ display: inline-grid;
34
180
  }
35
181
 
36
- /* Paged — CSS columns */
37
- &.carousel--auto-pages {
38
- columns: var(--carousel-columns, 1);
39
- text-align: center;
182
+ &::column {
183
+ scroll-snap-align: center;
184
+ }
185
+ }
40
186
 
41
- > * {
42
- break-inside: avoid;
43
- display: inline-grid;
44
- }
187
+ /* ── Slide media ─────────────────────────────────────────────────────────────
188
+ Equal-height slides regardless of image aspect ratio. This file previously had
189
+ no `img` rule at all and global.css no media reset, so a portrait photo in a
190
+ strip of landscapes set the row height for every slide and the rest floated in
191
+ it. The fix is the one `patterns/media.css` and `patterns/subgrid.css` already
192
+ use: give the media a box with a known ratio and let the image cover it. */
193
+ .carousel__media {
194
+ aspect-ratio: var(--carousel-slide-aspect, 16 / 9);
195
+ overflow: hidden;
196
+ margin: 0;
197
+ border-radius: var(--br-carousel-group);
198
+
199
+ > :is(img, picture, video, svg, canvas) {
200
+ display: block;
201
+ inline-size: 100%;
202
+ block-size: 100%;
203
+ object-fit: var(--carousel-media-fit, cover);
204
+ object-position: var(--carousel-media-position, center);
205
+ }
206
+ }
45
207
 
46
- &::column {
47
- scroll-snap-align: center;
48
- }
208
+ /* Belt and braces for a slide that doesn't use the media wrapper. */
209
+ .carousel__track img {
210
+ max-inline-size: 100%;
211
+ }
212
+
213
+ .carousel__caption {
214
+ margin-block-start: 0.5rem;
215
+ color: var(--color-text-surface-muted);
216
+ }
217
+
218
+ /* ── Scroll buttons ──────────────────────────────────────────────────────────
219
+ Two blocks declaring the same thing, and they cannot be merged into one
220
+ selector list: a selector list is NOT forgiving, so one unparseable complex
221
+ selector drops the WHOLE rule. `.carousel__button, …::scroll-button(*) { … }`
222
+ would take the fallback button's styling down with the pseudo-element Firefox
223
+ doesn't know. `:is()` is no rescue either — it cannot contain pseudo-elements.
224
+ `carousel-parity.test.ts` asserts the two blocks declare the same properties. */
225
+
226
+ /* Fallback path — real <button>s built by WCCarousel when the probe reads 0. */
227
+ .carousel__button {
228
+ position: absolute;
229
+ inset-block: 0;
230
+ block-size: fit-content;
231
+ margin-block: auto;
232
+ z-index: var(--z-tier-raised);
233
+ box-sizing: border-box;
234
+ padding: 0;
235
+ border: 0;
236
+ line-height: 1;
237
+ display: grid;
238
+ place-content: center;
239
+ place-items: center;
240
+ inline-size: var(--carousel-button-size, 2.75rem);
241
+ aspect-ratio: 1;
242
+ border-radius: 50%;
243
+ font-size: var(--carousel-button-glyph, 1.5rem);
244
+ cursor: pointer;
245
+ color: var(--carousel-button-color, var(--color-text-surface));
246
+ background-color: var(--carousel-button-bg, var(--color-bg-surface-muted));
247
+ box-shadow: var(--ds-carousel-group);
248
+ transition:
249
+ background-color 0.2s ease,
250
+ opacity 0.2s ease;
251
+
252
+ &:hover {
253
+ /* A REAL hover step. The old rule set the same `--color-bg-surface-muted` the
254
+ base rule sets, so hover was a no-op — the token migration flattened two
255
+ distinct oklch literals onto one variable. `muted` → `bold` is one ordinal
256
+ step on the surface ladder and reads in both schemes. */
257
+ background-color: var(--carousel-button-bg-hover, var(--color-bg-surface-bold));
258
+ }
259
+
260
+ &:focus-visible {
261
+ outline: 2px solid var(--color-border-focus);
262
+ outline-offset: 2px;
49
263
  }
50
264
 
51
- /* Scroll buttons — anchor-positioned */
52
- &.carousel--scroll-buttons {
265
+ &:disabled {
266
+ cursor: not-allowed;
267
+ opacity: 0.35;
268
+ }
269
+ }
270
+
271
+ .carousel__button--prev {
272
+ inset-inline-start: var(--carousel-button-inset, 0.5rem);
273
+ }
274
+
275
+ .carousel__button--next {
276
+ inset-inline-end: var(--carousel-button-inset, 0.5rem);
277
+ }
278
+
279
+ /* Native path. */
280
+ @supports selector(::scroll-button(inline-end)) {
281
+ .carousel--scroll-buttons > .carousel__track,
282
+ .carousel--scroll-buttons:not(:has(> .carousel__track)) {
53
283
  &::scroll-button(*) {
54
- position: fixed;
55
- position-anchor: --carousel;
56
- position-visibility: always;
57
- cursor: pointer;
58
- font-size: 1.5rem;
59
- inline-size: 44px;
60
- aspect-ratio: 1;
61
- display: flex;
62
- place-items: center;
284
+ position: absolute;
285
+ inset-block: 0;
286
+ block-size: fit-content;
287
+ margin-block: auto;
288
+ z-index: var(--z-tier-raised);
289
+ /* global.css's reset is `*, *::before, *::after` — it does NOT reach a
290
+ ::scroll-button(), so this pseudo is content-box carrying Chrome's UA
291
+ `padding: 1px 6px`. `inline-size: 44px` + `aspect-ratio: 1` therefore
292
+ sized the CONTENT box, giving a 56×46 border box: an ellipse with the
293
+ glyph off-centre. That, plus `line-height: normal` letting a 1.5rem
294
+ glyph ride its own half-leading, is the whole centring bug. `grid`
295
+ rather than `flex` because `place-items`' justify-items half is inert in
296
+ a flex container. */
297
+ box-sizing: border-box;
298
+ padding: 0;
299
+ border: 0;
300
+ line-height: 1;
301
+ display: grid;
63
302
  place-content: center;
64
- border: none;
303
+ place-items: center;
304
+ inline-size: var(--carousel-button-size, 2.75rem);
305
+ aspect-ratio: 1;
65
306
  border-radius: 50%;
66
- color: var(--carousel-button-color, currentColor);
67
- background: var(--color-bg-surface-muted);
68
- opacity: 0.9;
307
+ font-size: var(--carousel-button-glyph, 1.5rem);
308
+ cursor: pointer;
309
+ color: var(--carousel-button-color, var(--color-text-surface));
310
+ background-color: var(--carousel-button-bg, var(--color-bg-surface-muted));
311
+ box-shadow: var(--ds-carousel-group);
69
312
  transition:
70
- opacity 0.2s ease,
71
- background 0.2s ease;
313
+ background-color 0.2s ease,
314
+ opacity 0.2s ease;
72
315
  }
73
316
 
74
317
  &::scroll-button(*):hover {
75
- opacity: 1;
76
- background: var(--color-bg-surface-muted);
318
+ background-color: var(--carousel-button-bg-hover, var(--color-bg-surface-bold));
77
319
  }
78
320
 
79
321
  &::scroll-button(*):focus-visible {
80
- outline: 2px solid currentColor;
322
+ outline: 2px solid var(--color-border-focus);
81
323
  outline-offset: 2px;
82
324
  }
83
325
 
84
326
  &::scroll-button(*):disabled {
85
327
  cursor: not-allowed;
86
- opacity: 0.2;
328
+ opacity: 0.35;
329
+ }
330
+
331
+ /* Logical, not physical. `(left)`/`(right)` put "previous" on the wrong side
332
+ in an RTL document and pointed the arrow the wrong way. Single guillemets
333
+ centre far better in a circle than `\2190`/`\2192`, whose side bearings are
334
+ asymmetric. The alt text takes a var() so it can be localised without
335
+ re-declaring `content`. */
336
+ &::scroll-button(inline-start) {
337
+ inset-inline-start: var(--carousel-button-inset, 0.5rem);
338
+ content: '\2039' / var(--carousel-label-prev, 'Previous');
87
339
  }
88
340
 
89
- &::scroll-button(left) {
90
- position-area: inline-start center;
91
- content: '\2190' / 'Previous';
341
+ &::scroll-button(inline-end) {
342
+ inset-inline-end: var(--carousel-button-inset, 0.5rem);
343
+ content: '\203A' / var(--carousel-label-next, 'Next');
92
344
  }
93
345
 
94
- &::scroll-button(right) {
95
- position-area: inline-end center;
96
- content: '\2192' / 'Next';
346
+ &:dir(rtl)::scroll-button(inline-start) {
347
+ content: '\203A' / var(--carousel-label-prev, 'Previous');
348
+ }
349
+
350
+ &:dir(rtl)::scroll-button(inline-end) {
351
+ content: '\2039' / var(--carousel-label-next, 'Next');
97
352
  }
98
353
  }
354
+ }
355
+
356
+ /* ── Scroll markers ──────────────────────────────────────────────────────────
357
+ Same two-block split, same reason. With the shell in place
358
+ `scroll-marker-group: after` generates the group box after the scroller in the
359
+ shell's flow — so unlike the previous version it needs no anchor and no
360
+ `position: fixed`, just an absolutely positioned flex row against the shell.
361
+ `.carousel--markers-below` flows them under the strip instead. */
362
+
363
+ .carousel__markers {
364
+ position: absolute;
365
+ inset-inline: 0;
366
+ inset-block-end: var(--carousel-marker-offset, 1rem);
367
+ z-index: var(--z-tier-raised);
368
+ display: flex;
369
+ align-items: center;
370
+ justify-content: center;
371
+ gap: 0.5rem;
372
+ padding-block: 0.5rem;
373
+ }
374
+
375
+ .carousel--markers-below > .carousel__markers {
376
+ position: static;
377
+ inset: auto;
378
+ }
99
379
 
100
- /* Scroll markers — anchor-positioned group */
101
- &.carousel--scroll-markers {
380
+ .carousel__marker {
381
+ box-sizing: border-box;
382
+ padding: 0;
383
+ border: 0;
384
+ inline-size: var(--carousel-marker-size, 0.75rem);
385
+ aspect-ratio: 1;
386
+ border-radius: 50%;
387
+ background-color: var(--carousel-marker-color, currentColor);
388
+ opacity: 0.4;
389
+ cursor: pointer;
390
+ transition: opacity 0.2s ease;
391
+
392
+ &[aria-selected='true'] {
393
+ opacity: 1;
394
+ background-color: var(
395
+ --carousel-marker-active-color,
396
+ var(--carousel-marker-color, currentColor)
397
+ );
398
+ }
399
+
400
+ &:focus-visible {
401
+ outline: 2px solid var(--color-border-focus);
402
+ outline-offset: 2px;
403
+ }
404
+ }
405
+
406
+ @supports selector(::scroll-marker) {
407
+ .carousel--scroll-markers > .carousel__track,
408
+ .carousel--scroll-markers:not(:has(> .carousel__track)) {
102
409
  scroll-marker-group: after;
103
410
 
104
411
  &::scroll-marker-group {
105
- position: fixed;
106
- position-anchor: --carousel;
107
- position-area: block-end;
108
- position-visibility: always;
109
- margin-block-end: var(--carousel-marker-offset, 1rem);
412
+ position: absolute;
413
+ inset-inline: 0;
414
+ inset-block-end: var(--carousel-marker-offset, 1rem);
415
+ z-index: var(--z-tier-raised);
110
416
  display: flex;
111
417
  align-items: center;
112
418
  justify-content: center;
@@ -114,156 +420,273 @@
114
420
  padding-block: 0.5rem;
115
421
  }
116
422
 
117
- /* Non-paged: per-item markers */
118
- &:not(.carousel--auto-pages) {
119
- > *::scroll-marker {
120
- content: '';
121
- inline-size: 0.75rem;
122
- block-size: 0.75rem;
123
- border-radius: 50%;
124
- background-color: var(--carousel-marker-color, currentColor);
125
- opacity: 0.4;
126
- cursor: pointer;
127
- transition: opacity 0.2s ease;
128
- }
129
-
130
- > [data-carousel-clone]::scroll-marker,
131
- > [part='autoplay']::scroll-marker {
132
- content: none;
133
- }
134
-
135
- > *::scroll-marker:target-current {
136
- opacity: 1;
137
- background-color: var(
138
- --carousel-marker-active-color,
139
- var(--carousel-marker-color, currentColor)
140
- );
141
- }
142
-
143
- > *::scroll-marker:focus-visible {
144
- outline: 2px solid currentColor;
145
- outline-offset: 2px;
146
- }
423
+ /* Non-paged: one marker per slide. */
424
+ > *::scroll-marker {
425
+ content: '';
426
+ box-sizing: border-box;
427
+ padding: 0;
428
+ border: 0;
429
+ inline-size: var(--carousel-marker-size, 0.75rem);
430
+ aspect-ratio: 1;
431
+ border-radius: 50%;
432
+ background-color: var(--carousel-marker-color, currentColor);
433
+ opacity: 0.4;
434
+ cursor: pointer;
435
+ transition: opacity 0.2s ease;
147
436
  }
148
437
 
149
- /* Paged: per-column markers */
150
- &.carousel--auto-pages {
151
- &::column::scroll-marker {
152
- content: '';
153
- inline-size: 0.75rem;
154
- block-size: 0.75rem;
155
- border-radius: 50%;
156
- background-color: var(--carousel-marker-color, currentColor);
157
- opacity: 0.4;
158
- cursor: pointer;
159
- transition: opacity 0.2s ease;
160
- }
161
-
162
- &::column::scroll-marker:target-current {
163
- opacity: 1;
164
- background-color: var(
165
- --carousel-marker-active-color,
166
- var(--carousel-marker-color, currentColor)
167
- );
168
- }
169
-
170
- &::column::scroll-marker:focus-visible {
171
- outline: 2px solid currentColor;
172
- outline-offset: 2px;
173
- }
438
+ /* A clone is a duplicate of a slide the visitor can already reach, so it must
439
+ not add a dot. The autoplay button now lives on the shell, not in the
440
+ track, so it no longer needs suppressing here. */
441
+ > [data-carousel-clone]::scroll-marker {
442
+ content: none;
174
443
  }
175
- }
176
444
 
177
- /* Inert — dim/disable off-screen content */
178
- &.carousel--inert {
179
- &:not(.carousel--auto-pages) > * {
180
- container-type: scroll-state;
445
+ > *::scroll-marker:target-current {
446
+ opacity: 1;
447
+ background-color: var(
448
+ --carousel-marker-active-color,
449
+ var(--carousel-marker-color, currentColor)
450
+ );
451
+ }
181
452
 
182
- > * {
183
- transition: opacity 0.5s ease;
453
+ > *::scroll-marker:focus-visible {
454
+ outline: 2px solid var(--color-border-focus);
455
+ outline-offset: 2px;
456
+ }
457
+ }
184
458
 
185
- @container not scroll-state(snapped) {
186
- interactivity: inert;
187
- opacity: var(--carousel-inert-opacity, 0.25);
188
- }
189
- }
459
+ /* Paged: one marker per column. Separate block because `::column::scroll-marker`
460
+ is a different origin and would otherwise double up with the `> *` rule. */
461
+ .carousel--scroll-markers.carousel--auto-pages > .carousel__track,
462
+ .carousel--scroll-markers.carousel--auto-pages:not(:has(> .carousel__track)) {
463
+ > *::scroll-marker {
464
+ content: none;
190
465
  }
191
466
 
192
- &.carousel--auto-pages > * {
193
- animation: carousel-offscreen-inert linear both;
194
- animation-timeline: view(x);
467
+ &::column::scroll-marker {
468
+ content: '';
469
+ box-sizing: border-box;
470
+ inline-size: var(--carousel-marker-size, 0.75rem);
471
+ aspect-ratio: 1;
472
+ border-radius: 50%;
473
+ background-color: var(--carousel-marker-color, currentColor);
474
+ opacity: 0.4;
475
+ cursor: pointer;
476
+ transition: opacity 0.2s ease;
195
477
  }
196
- }
197
478
 
198
- /* Autoplay — play/pause toggle (enhanced by the Lit component) */
199
- & > [part='autoplay'] {
200
- position: fixed;
201
- position-anchor: --carousel;
202
- position-area: block-end inline-end;
203
- position-visibility: always;
204
- margin-block-end: var(--carousel-marker-offset, 1rem);
205
- margin-inline-end: 0.5rem;
206
- z-index: var(--z-tier-raised);
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- inline-size: 2rem;
211
- block-size: 2rem;
212
- border: none;
213
- border-radius: 50%;
214
- background: var(--color-bg-surface-muted);
215
- color: var(--carousel-button-color, currentColor);
216
- opacity: 0.7;
217
- cursor: pointer;
218
- font-size: 0.75rem;
219
- transition: opacity 0.2s ease;
220
-
221
- &:hover {
479
+ &::column::scroll-marker:target-current {
222
480
  opacity: 1;
481
+ background-color: var(
482
+ --carousel-marker-active-color,
483
+ var(--carousel-marker-color, currentColor)
484
+ );
223
485
  }
224
486
 
225
- &:focus-visible {
226
- outline: 2px solid currentColor;
487
+ &::column::scroll-marker:focus-visible {
488
+ outline: 2px solid var(--color-border-focus);
227
489
  outline-offset: 2px;
228
490
  }
229
491
  }
230
492
 
231
- /* Force stop — always stop at each snap point */
232
- &.carousel--force-stop {
233
- &:not(.carousel--auto-pages) > * {
234
- scroll-snap-stop: always;
493
+ /* `--markers-below` has to reach the native group too, not just the fallback —
494
+ otherwise the modifier silently does nothing in the one browser that has the
495
+ pseudo-elements. */
496
+ .carousel--markers-below > .carousel__track::scroll-marker-group,
497
+ .carousel--markers-below:not(:has(> .carousel__track))::scroll-marker-group {
498
+ position: static;
499
+ inset: auto;
500
+ }
501
+
502
+ /* The native group replaces the JS one; if both somehow exist, the native one
503
+ wins and the fallback is hidden rather than stacked on top of it. */
504
+ .carousel--scroll-markers > .carousel__markers {
505
+ display: none;
506
+ }
507
+ }
508
+
509
+ .carousel--markers-below {
510
+ --carousel-marker-offset: 0px;
511
+ }
512
+
513
+ /* ── Scroll-for-more hint ────────────────────────────────────────────────────*/
514
+
515
+ /* Above the strip and right-aligned, not overlaid on it: "Scroll for more" printed
516
+ across the first slide's image is frequently unreadable, and the shell exists so
517
+ the hint has somewhere to go that isn't the scrollport.
518
+
519
+ The row assignment is deliberate rather than incidental. Grid places explicitly
520
+ positioned items before auto-placed ones, so claiming row 1 here pushes the
521
+ track — which claims no row — to row 2. Omit the hint and nothing claims row 1,
522
+ the track auto-places there, and no empty track is left behind to collect
523
+ `row-gap`. That is why the track is NOT given `grid-row: 2` to match. */
524
+ .carousel__hint {
525
+ grid-row: 1;
526
+ grid-column: 1;
527
+ justify-self: end;
528
+ align-self: start;
529
+ margin: var(--carousel-hint-inset, 0.5rem);
530
+ z-index: var(--z-tier-raised);
531
+ pointer-events: none;
532
+ color: var(--color-text-surface-x-muted);
533
+ transition: opacity 0.3s ease;
534
+ }
535
+
536
+ @keyframes carousel-hint-dim {
537
+ to {
538
+ opacity: var(--carousel-hint-dimmed, 0.2);
539
+ }
540
+ }
541
+
542
+ @keyframes carousel-scrollbar-dim {
543
+ to {
544
+ scrollbar-color: var(--carousel-scrollbar-thumb-dim, var(--color-bg-surface-muted)) transparent;
545
+ }
546
+ }
547
+
548
+ /* Pure CSS, no JS: both read the TRACK's scroll progress through the timeline the
549
+ shell hoisted with `timeline-scope`. */
550
+ @supports (animation-timeline: scroll()) {
551
+ .carousel__hint {
552
+ animation: carousel-hint-dim linear both;
553
+ animation-timeline: --carousel-scroll;
554
+ animation-range: 0 var(--carousel-hint-range, 12%);
555
+ }
556
+
557
+ .carousel__track {
558
+ animation: carousel-scrollbar-dim linear both;
559
+ animation-timeline: --carousel-scroll;
560
+ animation-range: 0 var(--carousel-hint-range, 12%);
561
+ }
562
+ }
563
+
564
+ /* Engines with no scroll timelines get the same effect from the attribute
565
+ WCCarousel sets on first scroll — mirroring how animation.css cancels under
566
+ `@supports not (animation-timeline: view())`. */
567
+ @supports not (animation-timeline: scroll()) {
568
+ .carousel[data-scrolled] .carousel__hint {
569
+ opacity: var(--carousel-hint-dimmed, 0.2);
570
+ }
571
+
572
+ .carousel[data-scrolled] > .carousel__track {
573
+ scrollbar-color: var(--carousel-scrollbar-thumb-dim, var(--color-bg-surface-muted)) transparent;
574
+ }
575
+ }
576
+
577
+ /* ── Autoplay toggle ─────────────────────────────────────────────────────────
578
+ A child of the SHELL, not the track. It used to be appended into the grid track
579
+ where it occupied a slide-width column, papered over with `scroll-snap-align:
580
+ unset` and a suppressed marker. `[part='autoplay']` is kept as a deprecated
581
+ alias for one release: `part` only crosses a shadow boundary and this element is
582
+ light DOM, so that was never a part — just an arbitrary attribute selector. */
583
+ .carousel__autoplay,
584
+ .carousel > [part='autoplay'] {
585
+ position: absolute;
586
+ inset-block-end: var(--carousel-marker-offset, 1rem);
587
+ inset-inline-end: 0.5rem;
588
+ z-index: var(--z-tier-raised);
589
+ box-sizing: border-box;
590
+ padding: 0;
591
+ border: 0;
592
+ line-height: 1;
593
+ display: grid;
594
+ place-content: center;
595
+ inline-size: 2rem;
596
+ aspect-ratio: 1;
597
+ border-radius: 50%;
598
+ background-color: var(--carousel-button-bg, var(--color-bg-surface-muted));
599
+ color: var(--carousel-button-color, var(--color-text-surface));
600
+ opacity: 0.7;
601
+ cursor: pointer;
602
+ font-size: 0.75rem;
603
+ transition: opacity 0.2s ease;
604
+
605
+ &:hover {
606
+ opacity: 1;
607
+ }
608
+
609
+ &:focus-visible {
610
+ outline: 2px solid var(--color-border-focus);
611
+ outline-offset: 2px;
612
+ }
613
+ }
614
+
615
+ /* ── Inert off-screen slides ─────────────────────────────────────────────────
616
+ `interactivity: inert` is gated separately from the dimming: an engine without
617
+ it should dim only, rather than dim AND leave the content silently focusable in
618
+ a way the author didn't ask for. */
619
+
620
+ .carousel--inert:not(.carousel--auto-pages) > .carousel__track > *,
621
+ .carousel--inert:not(.carousel--auto-pages):not(:has(> .carousel__track)) > * {
622
+ container-type: scroll-state;
623
+
624
+ > * {
625
+ transition: opacity 0.5s ease;
626
+
627
+ @container not scroll-state(snapped) {
628
+ opacity: var(--carousel-inert-opacity, 0.25);
235
629
  }
630
+ }
631
+ }
236
632
 
237
- &.carousel--auto-pages::column {
238
- scroll-snap-stop: always;
633
+ @supports (interactivity: inert) {
634
+ .carousel--inert:not(.carousel--auto-pages) > .carousel__track > * > *,
635
+ .carousel--inert:not(.carousel--auto-pages):not(:has(> .carousel__track)) > * > * {
636
+ @container not scroll-state(snapped) {
637
+ interactivity: inert;
239
638
  }
240
639
  }
241
640
  }
242
641
 
642
+ .carousel--inert.carousel--auto-pages > .carousel__track > *,
643
+ .carousel--inert.carousel--auto-pages:not(:has(> .carousel__track)) > * {
644
+ animation: carousel-offscreen-inert linear both;
645
+ animation-timeline: view(x);
646
+ }
647
+
243
648
  @keyframes carousel-offscreen-inert {
244
649
  entry 0%,
245
650
  exit 100% {
246
- interactivity: inert;
247
651
  opacity: var(--carousel-inert-opacity, 0.25);
248
652
  }
249
653
 
250
654
  entry 100%,
251
655
  exit 0% {
252
- interactivity: auto;
253
656
  opacity: 1;
254
657
  }
255
658
  }
256
659
 
660
+ /* ── Force stop ──────────────────────────────────────────────────────────────*/
661
+
662
+ .carousel--force-stop:not(.carousel--auto-pages) > .carousel__track > *,
663
+ .carousel--force-stop:not(.carousel--auto-pages):not(:has(> .carousel__track)) > * {
664
+ scroll-snap-stop: always;
665
+ }
666
+
667
+ .carousel--force-stop.carousel--auto-pages > .carousel__track::column,
668
+ .carousel--force-stop.carousel--auto-pages:not(:has(> .carousel__track))::column {
669
+ scroll-snap-stop: always;
670
+ }
671
+
257
672
  @media (prefers-reduced-motion: reduce) {
258
- .carousel {
673
+ .carousel__track,
674
+ .carousel:not(:has(> .carousel__track)) {
259
675
  scroll-behavior: auto;
676
+ }
260
677
 
261
- &.carousel--inert:not(.carousel--auto-pages) > * > * {
262
- transition: none;
263
- }
678
+ .carousel__hint,
679
+ .carousel__button,
680
+ .carousel__marker,
681
+ .carousel__autoplay {
682
+ transition: none;
683
+ }
264
684
 
265
- &.carousel--inert.carousel--auto-pages > * {
266
- animation: none;
267
- }
685
+ .carousel--inert > .carousel__track > * > * {
686
+ transition: none;
687
+ }
688
+
689
+ .carousel--inert.carousel--auto-pages > .carousel__track > * {
690
+ animation: none;
268
691
  }
269
692
  }
package/dist/elements.js CHANGED
@@ -543,7 +543,7 @@ let X=!1;const Dt=new q.subtle.Watcher(async()=>{X||(X=!0,queueMicrotask(()=>{X=
543
543
  >
544
544
  <span class=${e} part="icon" aria-hidden="true"></span>
545
545
  </button>
546
- `}};customElements.get(`wc-copy`)||customElements.define(`wc-copy`,Pt);function $(e,t){t()||queueMicrotask(()=>{e.isConnected&&t()})}var Ft=class extends Q{static{this.properties={autoplay:{type:Number,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=!1;#r=!1;#i=null;#a=!1;#o=null;#s=!1;init(){this.autoplay=0}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`region`),this.hasAttribute(`aria-roledescription`)||this.setAttribute(`aria-roledescription`,`carousel`),$(this,()=>this.#c())}#c(){return this.#e.length>=2?!0:(this.#e=[...this.children].filter(e=>!e.hasAttribute(`slot`)),this.#e.length<2?!1:(this.#n=this.classList.contains(`carousel--auto-pages`),this.#n||(this.#l(),this.#f(),this.addEventListener(`scrollsnapchange`,this.#m)),!this.autoplay&&this.hasAttribute(`autoplay`)&&(this.autoplay=Number(this.getAttribute(`autoplay`))||0),this.#s=matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.autoplay>0&&(this.#b(),this.#s?(this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#x()):this.#h(),this.addEventListener(`pointerenter`,this.#_),this.addEventListener(`pointerleave`,this.#v),this.addEventListener(`focusin`,this.#_),this.addEventListener(`focusout`,this.#v)),!0))}disconnectedCallback(){super.disconnectedCallback(),this.#n||(this.removeEventListener(`scrollsnapchange`,this.#m),this.#d()),this.removeEventListener(`pointerenter`,this.#_),this.removeEventListener(`pointerleave`,this.#v),this.removeEventListener(`focusin`,this.#_),this.removeEventListener(`focusout`,this.#v),this.#g(),this.#o&&=(this.#o.removeEventListener(`click`,this.#y),this.#o.hasAttribute(`slot`)||this.#o.remove(),null),this.removeAttribute(`aria-live`)}#l(){let e=[],t=[];for(let n=0;n<this.#e.length;n++){let r=this.#e[n];e.push(this.#u(r,n)),t.push(this.#u(r,n))}for(let t=e.length-1;t>=0;t--)this.prepend(e[t]);for(let e of t)this.append(e);this.#t=[...e,...t]}#u(e,t){let n=e.cloneNode(!0);n.setAttribute(`data-carousel-clone`,String(t)),n.setAttribute(`aria-hidden`,`true`);for(let e of[n,...n.querySelectorAll(`[tabindex], a, button, input, select, textarea`)])e.tabIndex=-1;return n}#d(){for(let e of this.#t)e.remove();this.#t=[]}#f(){requestAnimationFrame(()=>{let e=this.#e[0];e&&(this.style.scrollBehavior=`auto`,this.scrollLeft=e.offsetLeft-this.offsetLeft-(parseFloat(getComputedStyle(this).scrollPaddingLeft)||0)+(parseFloat(getComputedStyle(this).paddingLeft)||0),requestAnimationFrame(()=>{this.style.scrollBehavior=``}))})}#p(){this.scrollBy({left:this.clientWidth,behavior:`smooth`})}#m=e=>{if(this.#r){this.#r=!1;return}let t=e.snapTargetInline;if(!t||!t.hasAttribute(`data-carousel-clone`))return;let n=Number(t.getAttribute(`data-carousel-clone`)),r=this.#e[n];if(!r)return;let i=r.offsetLeft-t.offsetLeft;this.#r=!0,this.style.scrollBehavior=`auto`,this.scrollLeft+=i,requestAnimationFrame(()=>{this.style.scrollBehavior=``})};#h(){this.#i||this.autoplay<=0||(this.#a=!1,this.#i=setInterval(()=>this.#p(),this.autoplay),this.setAttribute(`aria-live`,`off`),this.#x())}#g(){this.#i&&=(clearInterval(this.#i),null)}#_=()=>{this.#g(),this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#x()};#v=()=>{this.#s||this.#h()};#y=()=>{this.#a?(this.#s=!1,this.#h()):this.#_()};#b(){let e=this.querySelector(`[slot="autoplay"]`);if(e)this.#o=e;else{let e=document.createElement(`button`);e.type=`button`,this.append(e),this.#o=e}this.#o.setAttribute(`part`,`autoplay`),this.#o.addEventListener(`click`,this.#y),this.#x()}#x(){this.#o&&(this.#o.setAttribute(`aria-label`,this.#a?`Play`:`Pause`),this.#o.setAttribute(`aria-pressed`,String(!this.#a)),this.#o.hasAttribute(`slot`)||(this.#o.textContent=this.#a?`▶`:`⏸`))}};customElements.get(`wc-carousel`)||customElements.define(`wc-carousel`,Ft);var It=class extends Q{static{this.properties={breakpoint:{type:String,reflect:!0},projection:{type:Boolean,reflect:!0},singular:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=[];#r=null;#i=null;#a=null;#o=[];#s=[];#c=0;#l=!1;#u=null;#d=0;init(){this.breakpoint=`40rem`,this.projection=!1,this.singular=!1}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),$(this,()=>this.#f())}#f(){return this.#r?!0:(this.#p(),this.#e.length===0?!1:(this.#h(),this.projection&&(this.#m(),this.#g(),this.#_(),this.singular&&(this.classList.add(`entries--singular`),this.#y(0))),this.#d=this.#x(),this.#S(),!0))}disconnectedCallback(){super.disconnectedCallback(),this.#u?.disconnect(),this.#u=null,this.#a?.remove(),this.#a=null,this.#o=[],this.#i?this.#i.remove():this.#r?.remove(),this.#r=null,this.#i=null,this.#s=[],this.classList.remove(`entries--wide`,`entries--projection`,`entries--singular`)}#p(){this.#e=[];let e=new Set,t=new Map,n=null;for(let r of this.children){let i=r.tagName.toLowerCase();if(/^h[1-6]$/.test(i))n=r;else if(i===`dl`&&n){let i=new Map,a=r.querySelectorAll(`dt`),o=r.querySelectorAll(`dd`);for(let n=0;n<a.length;n++){let r=a[n],s=r.textContent?.trim()??``,c=o[n]?.innerHTML??``;if(s){i.set(s,c),e.add(s);let n=r.getAttribute(`data-label`);n&&!t.has(s)&&t.set(s,n)}}this.#e.push({title:n.innerHTML,heading:n,dl:r,properties:i}),n=null}}this.#t=[...e],this.#n=this.#t.map(e=>t.get(e)??e)}#m(){this.classList.add(`entries--projection`)}#h(){let e=document.createElement(`table`);e.classList.add(`entries__table`),e.setAttribute(`aria-hidden`,`true`);let t=document.createElement(`thead`),n=document.createElement(`tr`),r=document.createElement(`th`);r.textContent=``,n.appendChild(r),this.#s=[];for(let e of this.#t){let t=document.createElement(`th`);t.textContent=e,n.appendChild(t),this.#s.push(t)}t.appendChild(n),e.appendChild(t);let i=document.createElement(`tbody`);for(let e of this.#e){let t=document.createElement(`tr`),n=document.createElement(`td`);n.innerHTML=e.title,n.style.fontWeight=`600`,t.appendChild(n);for(let n of this.#t){let r=document.createElement(`td`);r.innerHTML=e.properties.get(n)??``,t.appendChild(r)}i.appendChild(t)}e.appendChild(i),this.appendChild(e),this.#r=e}#g(){if(!this.#r)return;let e=document.createElement(`div`);e.classList.add(`entries__scroll`),this.#r.replaceWith(e),e.appendChild(this.#r),this.#i=e}#_(){let e=document.createElement(`nav`);e.classList.add(`entries__nav`),e.setAttribute(`aria-label`,`Column selector`),e.setAttribute(`aria-hidden`,`true`),this.#o=this.#t.map((t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.classList.add(`entries__nav-btn`),r.textContent=this.#n[n]??t,r.setAttribute(`aria-pressed`,String(n===0)),r.addEventListener(`click`,()=>this.#v(n)),e.appendChild(r),r}),this.#i.before(e),this.#a=e}#v(e){this.#c=e;for(let t=0;t<this.#o.length;t++)this.#o[t]?.setAttribute(`aria-pressed`,String(t===e));if(this.singular)this.#y(e);else{let t=this.#s[e];t&&this.#i&&t.scrollIntoView({behavior:`smooth`,block:`nearest`,inline:`start`})}this.dispatchEvent(new CustomEvent(`entries-column-change`,{detail:{column:e,header:this.#t[e]},bubbles:!0,composed:!0}))}#y(e){if(!this.#r)return;let t=e+1;for(let e of this.#r.rows)for(let n=1;n<e.cells.length;n++){let r=e.cells[n];r&&(r.hidden=n!==t)}}#b(){if(this.#r)for(let e of this.#r.rows)for(let t=1;t<e.cells.length;t++){let n=e.cells[t];n&&(n.hidden=!1)}}#x(){let e=this.breakpoint||`40rem`,t=document.createElement(`div`);t.style.width=e,t.style.position=`absolute`,t.style.visibility=`hidden`,this.appendChild(t);let n=t.offsetWidth;return t.remove(),n}#S(){this.#u=new ResizeObserver(this.#C),this.#u.observe(this)}#C=e=>{let t=e[0];if(!t)return;let n=t.contentBoxSize?.[0]?.inlineSize??t.contentRect.width;this.#l=n>=this.#d,this.#l?(this.classList.add(`entries--wide`),this.singular&&this.projection&&this.#b()):(this.classList.remove(`entries--wide`),this.singular&&this.projection&&this.#y(this.#c))}};customElements.get(`wc-entries`)||customElements.define(`wc-entries`,It);const Lt=`vitops:consent`;var Rt=class e extends Q{static{this.styles=[Q.styles,o`
546
+ `}};customElements.get(`wc-copy`)||customElements.define(`wc-copy`,Pt);function Ft(e){let t=t=>e(t).trim()===`1`;return{nativeNav:t(`--carousel-native-nav`),scrollTimeline:t(`--carousel-scroll-timeline`)}}function It(e,t){let n=e.slides>=2;return{clone:n&&!e.paged&&(e.loop||e.autoplay>0),play:e.autoplay>0&&!e.reducedMotion,buildButtons:n&&e.wantsButtons&&!t.nativeNav,buildMarkers:n&&e.wantsMarkers&&!t.nativeNav,trackScrolled:!t.scrollTimeline}}function Lt(e){return Math.abs(e)>4}function Rt(e,t){return`Slide ${e+1} of ${t}`}const zt=Object.freeze({loading:`lazy`,fetchpriority:`low`,decoding:`async`});function $(e,t){t()||queueMicrotask(()=>{e.isConnected&&t()})}var Bt=class extends Q{static{this.properties={autoplay:{type:Number,reflect:!0},loop:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}render(){return D}#e=!1;#t=null;#n=[];#r=[];#i=null;#a=[];#o=null;#s=[];#c=null;#l=!1;#u=null;#d=null;#f=null;#p=!1;#m=!1;#h=!1;#g=0;constructor(){super(),this.autoplay=0,this.loop=!1}connectedCallback(){super.connectedCallback(),$(this,()=>this.#_())}#_(){if(this.parentElement?.closest(`wc-carousel`)||this.#e)return!0;let e=this.#v();if(!e||(this.#t=e,this.#n=[...e.children].filter(e=>e instanceof HTMLElement&&!e.hasAttribute(`slot`)),this.#n.length<2))return!1;!this.autoplay&&this.hasAttribute(`autoplay`)&&(this.autoplay=Number(this.getAttribute(`autoplay`))||0),!this.loop&&this.hasAttribute(`loop`)&&(this.loop=!0),this.#m=matchMedia(`(prefers-reduced-motion: reduce)`).matches;let t=Ft(e=>getComputedStyle(this).getPropertyValue(e)),n=It({loop:this.loop,autoplay:this.autoplay,paged:this.classList.contains(`carousel--auto-pages`),wantsButtons:this.classList.contains(`carousel--scroll-buttons`),wantsMarkers:this.classList.contains(`carousel--scroll-markers`),slides:this.#n.length,reducedMotion:this.#m},t);return this.#i=n,this.#y(e),n.clone&&this.#b(),n.buildButtons&&this.#k(),n.buildMarkers&&this.#A(),(n.trackScrolled||n.clone||this.#a.length||this.#s.length)&&(e.addEventListener(`scroll`,this.#E,{passive:!0}),e.addEventListener(`scrollsnapchange`,this.#D),e.addEventListener(`scrollend`,this.#D)),n.clone&&this.#T(),this.autoplay>0&&this.#N(n),this.#u=new ResizeObserver(this.#M),this.#u.observe(e),this.#M(),this.#e=!0,!0}#v(){let e=this.querySelector(`:scope > .carousel__track`);if(e)return e;if(!this.classList.contains(`carousel`)||!this.children.length)return null;let t=document.createElement(`div`);return t.className=`carousel__track`,t.append(...this.childNodes),this.append(t),t}#y(e){this.hasAttribute(`role`)||this.setAttribute(`role`,`group`),this.hasAttribute(`aria-roledescription`)||this.setAttribute(`aria-roledescription`,`carousel`),e.hasAttribute(`tabindex`)||(e.tabIndex=0)}disconnectedCallback(){super.disconnectedCallback();let e=this.#t;this.#u?.disconnect(),this.#u=null,e&&(e.removeEventListener(`scroll`,this.#E),e.removeEventListener(`scrollsnapchange`,this.#D),e.removeEventListener(`scrollend`,this.#D)),this.#f&&clearTimeout(this.#f),this.#f=null,this.#L(),this.removeEventListener(`pointerenter`,this.#R),this.removeEventListener(`pointerleave`,this.#z),this.removeEventListener(`focusin`,this.#R),this.removeEventListener(`focusout`,this.#z),this.#c&&(this.#c.removeEventListener(`click`,this.#B),this.#l&&this.#c.remove(),this.#c=null,this.#l=!1);for(let e of this.#a)e.remove();this.#a=[],this.#o?.remove(),this.#o=null,this.#s=[];for(let e of this.#r)e.remove();this.#r=[],this.removeAttribute(`data-scrolled`),this.removeAttribute(`aria-live`),this.#n=[],this.#i=null,this.#e=!1}#b(){let e=this.#n.map((e,t)=>this.#x(e,t)),t=this.#n.map((e,t)=>this.#x(e,t));for(let t=e.length-1;t>=0;t--)this.#t?.prepend(e[t]);for(let e of t)this.#t?.append(e);this.#r=[...e,...t]}#x(e,t){let n=e.cloneNode(!0);n.setAttribute(`data-carousel-clone`,String(t)),n.setAttribute(`aria-hidden`,`true`),n.setAttribute(`inert`,``),n.removeAttribute(`id`);for(let e of n.querySelectorAll(`[id]`))e.removeAttribute(`id`);for(let e of n.querySelectorAll(`img`))for(let[t,n]of Object.entries(zt))e.setAttribute(t,n);return n}#S(){return this.#t?[...this.#t.children].filter(e=>e instanceof HTMLElement&&!e.hasAttribute(`slot`)):[]}#C(){let e=this.#t,t=this.#S();if(!e||!t.length)return 0;let n=e.getBoundingClientRect(),r=n.left+n.width/2,i=0,a=1/0;return t.forEach((e,t)=>{let n=e.getBoundingClientRect(),o=Math.abs(n.left+n.width/2-r);o<a&&(a=o,i=t)}),i}#w(e){let t=this.#S();if(!t.length)return;let n=this.#C()+e;t[Math.max(0,Math.min(t.length-1,n))]?.scrollIntoView({inline:`center`,block:`nearest`,behavior:`smooth`})}#T(){requestAnimationFrame(()=>{let e=this.#n[0];e&&(this.#h=!0,e.scrollIntoView({inline:`center`,block:`nearest`,behavior:`instant`}),requestAnimationFrame(()=>{this.#h=!1,this.#O()}))})}#E=()=>{this.#i?.trackScrolled&&!this.hasAttribute(`data-scrolled`)&&Lt(this.#t?.scrollLeft??0)&&this.setAttribute(`data-scrolled`,``),this.#O(),this.#f&&clearTimeout(this.#f),this.#f=setTimeout(this.#D,150)};#D=()=>{this.#f&&clearTimeout(this.#f),this.#f=null,this.#O();let e=this.#t;if(!e||!this.#i?.clone||this.#h)return;let t=this.#S()[this.#C()],n=t?.getAttribute(`data-carousel-clone`);if(!t||n==null)return;let r=this.#n[Number(n)];if(!r)return;let i=r.getBoundingClientRect().left-t.getBoundingClientRect().left;if(!i)return;this.#h=!0;let a=e.style.scrollBehavior;e.style.scrollBehavior=`auto`,e.scrollLeft+=i,requestAnimationFrame(()=>{e.style.scrollBehavior=a,this.#h=!1,this.#O()})};#O(){let e=this.#S();if(!e.length)return;let t=this.#C(),n=this.#i?.clone?t%this.#n.length:t;if(this.#g=n,this.#s.forEach((e,t)=>{let r=t===n;e.setAttribute(`aria-selected`,String(r)),e.tabIndex=r?0:-1}),this.#a.length&&!this.#i?.clone){let[n,r]=this.#a;n&&(n.disabled=t<=0),r&&(r.disabled=t>=e.length-1)}}#k(){let e=(e,t,n,r)=>{let i=document.createElement(`button`);return i.type=`button`,i.className=`carousel__button carousel__button--${e}`,i.setAttribute(`aria-label`,t),i.textContent=n,i.addEventListener(`click`,()=>this.#w(r)),i},t=getComputedStyle(this).direction===`rtl`;this.#a=[e(`prev`,`Previous`,t?`›`:`‹`,-1),e(`next`,`Next`,t?`‹`:`›`,1)],this.append(...this.#a)}#A(){let e=document.createElement(`div`);e.className=`carousel__markers`,e.setAttribute(`role`,`tablist`),e.setAttribute(`aria-label`,this.getAttribute(`aria-label`)??`Slides`),this.#s=this.#n.map((e,t)=>{let n=document.createElement(`button`);return n.type=`button`,n.className=`carousel__marker`,n.setAttribute(`role`,`tab`),n.setAttribute(`aria-label`,Rt(t,this.#n.length)),n.setAttribute(`aria-selected`,String(t===0)),n.tabIndex=t===0?0:-1,e.id&&n.setAttribute(`aria-controls`,e.id),n.addEventListener(`click`,()=>{e.scrollIntoView({inline:`center`,block:`nearest`,behavior:`smooth`})}),n}),e.addEventListener(`keydown`,this.#j),e.append(...this.#s),this.append(e),this.#o=e}#j=e=>{let t=e.key===`ArrowRight`||e.key===`ArrowDown`?1:e.key===`ArrowLeft`||e.key===`ArrowUp`?-1:0;if(!t)return;e.preventDefault();let n=this.#s.length,r=this.#s[(this.#g+t+n)%n];r?.focus(),r?.click()};#M=()=>{let e=this.#t;if(!e)return;let t=this.querySelector(`:scope > .carousel__hint`);t&&(t.hidden=e.scrollWidth<=e.clientWidth+1)};#N(e){this.#P(),e.play?this.#I():(this.#p=!0,this.setAttribute(`aria-live`,`polite`),this.#F()),this.addEventListener(`pointerenter`,this.#R),this.addEventListener(`pointerleave`,this.#z),this.addEventListener(`focusin`,this.#R),this.addEventListener(`focusout`,this.#z)}#P(){let e=this.querySelector(`[slot="autoplay"]`);if(e)this.#c=e,this.#l=!1;else{let e=document.createElement(`button`);e.type=`button`,e.className=`carousel__autoplay`,this.append(e),this.#c=e,this.#l=!0}this.#c.addEventListener(`click`,this.#B),this.#F()}#F(){let e=this.#c;e&&(e.setAttribute(`aria-label`,this.#p?`Play`:`Pause`),e.setAttribute(`aria-pressed`,String(!this.#p)),this.#l&&(e.textContent=this.#p?`▶`:`⏸`))}#I(){this.#d||this.autoplay<=0||(this.#p=!1,this.#d=setInterval(()=>this.#w(1),this.autoplay),this.setAttribute(`aria-live`,`off`),this.#F())}#L(){this.#d&&clearInterval(this.#d),this.#d=null}#R=()=>{!this.#d&&this.#p||(this.#L(),this.#p=!0,this.setAttribute(`aria-live`,`polite`),this.#F())};#z=()=>{this.#m||this.#I()};#B=()=>{this.#p?(this.#m=!1,this.#I()):this.#R()}};customElements.get(`wc-carousel`)||customElements.define(`wc-carousel`,Bt);var Vt=class extends Q{static{this.properties={breakpoint:{type:String,reflect:!0},projection:{type:Boolean,reflect:!0},singular:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=[];#r=null;#i=null;#a=null;#o=[];#s=[];#c=0;#l=!1;#u=null;#d=0;init(){this.breakpoint=`40rem`,this.projection=!1,this.singular=!1}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),$(this,()=>this.#f())}#f(){return this.#r?!0:(this.#p(),this.#e.length===0?!1:(this.#h(),this.projection&&(this.#m(),this.#g(),this.#_(),this.singular&&(this.classList.add(`entries--singular`),this.#y(0))),this.#d=this.#x(),this.#S(),!0))}disconnectedCallback(){super.disconnectedCallback(),this.#u?.disconnect(),this.#u=null,this.#a?.remove(),this.#a=null,this.#o=[],this.#i?this.#i.remove():this.#r?.remove(),this.#r=null,this.#i=null,this.#s=[],this.classList.remove(`entries--wide`,`entries--projection`,`entries--singular`)}#p(){this.#e=[];let e=new Set,t=new Map,n=null;for(let r of this.children){let i=r.tagName.toLowerCase();if(/^h[1-6]$/.test(i))n=r;else if(i===`dl`&&n){let i=new Map,a=r.querySelectorAll(`dt`),o=r.querySelectorAll(`dd`);for(let n=0;n<a.length;n++){let r=a[n],s=r.textContent?.trim()??``,c=o[n]?.innerHTML??``;if(s){i.set(s,c),e.add(s);let n=r.getAttribute(`data-label`);n&&!t.has(s)&&t.set(s,n)}}this.#e.push({title:n.innerHTML,heading:n,dl:r,properties:i}),n=null}}this.#t=[...e],this.#n=this.#t.map(e=>t.get(e)??e)}#m(){this.classList.add(`entries--projection`)}#h(){let e=document.createElement(`table`);e.classList.add(`entries__table`),e.setAttribute(`aria-hidden`,`true`);let t=document.createElement(`thead`),n=document.createElement(`tr`),r=document.createElement(`th`);r.textContent=``,n.appendChild(r),this.#s=[];for(let e of this.#t){let t=document.createElement(`th`);t.textContent=e,n.appendChild(t),this.#s.push(t)}t.appendChild(n),e.appendChild(t);let i=document.createElement(`tbody`);for(let e of this.#e){let t=document.createElement(`tr`),n=document.createElement(`td`);n.innerHTML=e.title,n.style.fontWeight=`600`,t.appendChild(n);for(let n of this.#t){let r=document.createElement(`td`);r.innerHTML=e.properties.get(n)??``,t.appendChild(r)}i.appendChild(t)}e.appendChild(i),this.appendChild(e),this.#r=e}#g(){if(!this.#r)return;let e=document.createElement(`div`);e.classList.add(`entries__scroll`),this.#r.replaceWith(e),e.appendChild(this.#r),this.#i=e}#_(){let e=document.createElement(`nav`);e.classList.add(`entries__nav`),e.setAttribute(`aria-label`,`Column selector`),e.setAttribute(`aria-hidden`,`true`),this.#o=this.#t.map((t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.classList.add(`entries__nav-btn`),r.textContent=this.#n[n]??t,r.setAttribute(`aria-pressed`,String(n===0)),r.addEventListener(`click`,()=>this.#v(n)),e.appendChild(r),r}),this.#i.before(e),this.#a=e}#v(e){this.#c=e;for(let t=0;t<this.#o.length;t++)this.#o[t]?.setAttribute(`aria-pressed`,String(t===e));if(this.singular)this.#y(e);else{let t=this.#s[e];t&&this.#i&&t.scrollIntoView({behavior:`smooth`,block:`nearest`,inline:`start`})}this.dispatchEvent(new CustomEvent(`entries-column-change`,{detail:{column:e,header:this.#t[e]},bubbles:!0,composed:!0}))}#y(e){if(!this.#r)return;let t=e+1;for(let e of this.#r.rows)for(let n=1;n<e.cells.length;n++){let r=e.cells[n];r&&(r.hidden=n!==t)}}#b(){if(this.#r)for(let e of this.#r.rows)for(let t=1;t<e.cells.length;t++){let n=e.cells[t];n&&(n.hidden=!1)}}#x(){let e=this.breakpoint||`40rem`,t=document.createElement(`div`);t.style.width=e,t.style.position=`absolute`,t.style.visibility=`hidden`,this.appendChild(t);let n=t.offsetWidth;return t.remove(),n}#S(){this.#u=new ResizeObserver(this.#C),this.#u.observe(this)}#C=e=>{let t=e[0];if(!t)return;let n=t.contentBoxSize?.[0]?.inlineSize??t.contentRect.width;this.#l=n>=this.#d,this.#l?(this.classList.add(`entries--wide`),this.singular&&this.projection&&this.#b()):(this.classList.remove(`entries--wide`),this.singular&&this.projection&&this.#y(this.#c))}};customElements.get(`wc-entries`)||customElements.define(`wc-entries`,Vt);const Ht=`vitops:consent`;var Ut=class e extends Q{static{this.styles=[Q.styles,o`
547
547
  :host {
548
548
  display: none;
549
549
  }
@@ -641,7 +641,7 @@ let X=!1;const Dt=new q.subtle.Watcher(async()=>{X||(X=!0,queueMicrotask(()=>{X=
641
641
  transition: none;
642
642
  }
643
643
  }
644
- `]}static{this.properties={scheme:{type:String,reflect:!0}}}constructor(){super(),this._mediaQuery=null,this._boundMediaHandler=null,this._onConsent=()=>{window.vitopsConsent?.granted(`preferences`)&&this._write()},this.init()}init(){this.scheme=e.readStored()??`system`}static{this.STORAGE_KEY=`vitops-color-scheme`}static readStored(){try{let t=localStorage.getItem(e.STORAGE_KEY);return t===`light`||t===`dark`||t===`system`?t:null}catch{return null}}_persist(){let e=window.vitopsConsent;(!e||e.require(`preferences`))&&this._write()}_write(){try{this.scheme===`system`?localStorage.removeItem(e.STORAGE_KEY):localStorage.setItem(e.STORAGE_KEY,this.scheme)}catch{}}connectedCallback(){super.connectedCallback(),this._mediaQuery=window.matchMedia(`(prefers-color-scheme: dark)`),this._boundMediaHandler=this._handleMediaChange.bind(this),this._mediaQuery.addEventListener(`change`,this._boundMediaHandler),document.addEventListener(Lt,this._onConsent),this._applyColorScheme()}disconnectedCallback(){super.disconnectedCallback(),this._mediaQuery&&this._boundMediaHandler&&this._mediaQuery.removeEventListener(`change`,this._boundMediaHandler),document.removeEventListener(Lt,this._onConsent)}_handleMediaChange(e){this.scheme===`system`&&this.requestUpdate()}_applyColorScheme(){let e=document.documentElement;this.scheme===`system`?(e.style.removeProperty(`color-scheme`),delete e.dataset.theme):(e.style.colorScheme=`${this.scheme} only`,e.dataset.theme=this.scheme)}_handleSegmentClick(e){this.scheme!==e&&(this.scheme=e,this._applyColorScheme(),this._persist(),this.dispatchEvent(new CustomEvent(`scheme-change`,{detail:{scheme:this.scheme},bubbles:!0,composed:!0})))}updated(e){e.has(`scheme`)&&this._applyColorScheme()}render(){return Z`
644
+ `]}static{this.properties={scheme:{type:String,reflect:!0}}}constructor(){super(),this._mediaQuery=null,this._boundMediaHandler=null,this._onConsent=()=>{window.vitopsConsent?.granted(`preferences`)&&this._write()},this.init()}init(){this.scheme=e.readStored()??`system`}static{this.STORAGE_KEY=`vitops-color-scheme`}static readStored(){try{let t=localStorage.getItem(e.STORAGE_KEY);return t===`light`||t===`dark`||t===`system`?t:null}catch{return null}}_persist(){let e=window.vitopsConsent;(!e||e.require(`preferences`))&&this._write()}_write(){try{this.scheme===`system`?localStorage.removeItem(e.STORAGE_KEY):localStorage.setItem(e.STORAGE_KEY,this.scheme)}catch{}}connectedCallback(){super.connectedCallback(),this._mediaQuery=window.matchMedia(`(prefers-color-scheme: dark)`),this._boundMediaHandler=this._handleMediaChange.bind(this),this._mediaQuery.addEventListener(`change`,this._boundMediaHandler),document.addEventListener(Ht,this._onConsent),this._applyColorScheme()}disconnectedCallback(){super.disconnectedCallback(),this._mediaQuery&&this._boundMediaHandler&&this._mediaQuery.removeEventListener(`change`,this._boundMediaHandler),document.removeEventListener(Ht,this._onConsent)}_handleMediaChange(e){this.scheme===`system`&&this.requestUpdate()}_applyColorScheme(){let e=document.documentElement;this.scheme===`system`?(e.style.removeProperty(`color-scheme`),delete e.dataset.theme):(e.style.colorScheme=`${this.scheme} only`,e.dataset.theme=this.scheme)}_handleSegmentClick(e){this.scheme!==e&&(this.scheme=e,this._applyColorScheme(),this._persist(),this.dispatchEvent(new CustomEvent(`scheme-change`,{detail:{scheme:this.scheme},bubbles:!0,composed:!0})))}updated(e){e.has(`scheme`)&&this._applyColorScheme()}render(){return Z`
645
645
  <div class="toggle__group" role="group" aria-label="Color scheme" part="group">
646
646
  <button
647
647
  class="toggle__segment"
@@ -674,7 +674,7 @@ let X=!1;const Dt=new q.subtle.Watcher(async()=>{X||(X=!0,queueMicrotask(()=>{X=
674
674
  <span class="toggle__label">Dark</span>
675
675
  </button>
676
676
  </div>
677
- `}};customElements.get(`wc-color-scheme-toggle`)||customElements.define(`wc-color-scheme-toggle`,Rt);var zt=class extends Q{static{this.styles=[Q.styles,o`
677
+ `}};customElements.get(`wc-color-scheme-toggle`)||customElements.define(`wc-color-scheme-toggle`,Ut);var Wt=class extends Q{static{this.styles=[Q.styles,o`
678
678
  :host {
679
679
  --_gap: var(--multi-field-gap, 0.5rem);
680
680
  --_input-padding: var(--multi-field-input-padding, 0.5rem 0.75rem);
@@ -884,4 +884,4 @@ let X=!1;const Dt=new q.subtle.Watcher(async()=>{X||(X=!0,queueMicrotask(()=>{X=
884
884
  </button>
885
885
  </div>
886
886
  </div>
887
- `}};customElements.get(`wc-multi-field`)||customElements.define(`wc-multi-field`,zt);var Bt=class extends Q{createRenderRoot(){return this}#e=null;#t;connectedCallback(){super.connectedCallback(),$(this,()=>this.#n())}#n(){if(this.#e)return!0;let e=this.querySelector(`.marquee__content`);return e?(this.#e=e,this.style.setProperty(`--_marquee-min`,`auto`),this.#r(),this.#t=new ResizeObserver(()=>this.#r()),this.#t.observe(this),!0):!1}disconnectedCallback(){this.#t?.disconnect(),super.disconnectedCallback()}#r(){let e=this.#e;if(!e)return;for(let e of this.querySelectorAll(`[data-marquee-clone]`))e.remove();let t=Number.parseFloat(getComputedStyle(this).columnGap)||0,n=e.getBoundingClientRect().width;if(n<=0)return;this.style.setProperty(`--_marquee-distance`,`${n+t}px`);let r=Math.ceil(this.getBoundingClientRect().width/(n+t))+1;for(let t=0;t<r;t++){let t=e.cloneNode(!0);t.dataset.marqueeClone=``,t.setAttribute(`aria-hidden`,`true`),this.append(t)}}};customElements.get(`wc-marquee`)||customElements.define(`wc-marquee`,Bt);function Vt(e,t){let n=t.trim().toLowerCase();if(!n)return{keep:new Set,hits:0};let r=new Map,i=new Map;for(let t of e){r.set(t.key,t);let e=i.get(t.parent);e?e.push(t):i.set(t.parent,[t])}let a=new Set,o=0,s=e=>{for(let t of i.get(e)??[])a.has(t.key)||(a.add(t.key),s(t.key))};for(let t of e){if(!t.own.includes(n))continue;o++,a.add(t.key),s(t.key);let e=new Set([t.key]),i=r.get(t.parent);for(;i&&!e.has(i.key);)a.add(i.key),e.add(i.key),i=r.get(i.parent)}return{keep:a,hits:o}}var Ht=class extends Q{static{this.properties={label:{type:String,reflect:!0},nofilter:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}render(){return D}#e=null;#t=[];#n=null;#r=null;#i=null;#a=new WeakMap;#o=!1;constructor(){super(),this.label=`Filter`,this.nofilter=!1}connectedCallback(){super.connectedCallback(),$(this,()=>this.#s())}#s(){if(this.#e)return!0;if(!this.isConnected)return!1;if(this.parentElement?.closest(`wc-tree`))return!0;let e=this.querySelector(`.tree`);return e?(this.#e=e,this.#c(),this.#t.length?(this.#f(),window.addEventListener(`hashchange`,this.#m),requestAnimationFrame(()=>this.#h()),!0):(this.#e=null,!1)):!1}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener(`hashchange`,this.#m),this.#n?.remove(),this.#n=null,this.#r=null,this.#i=null,this.#t=[],this.#e=null}#c(){let e=this.#e?.querySelectorAll(`.tree__item`)??[];this.#t=[...e].map(e=>({key:e,own:this.#l(e),parent:e.parentElement?.closest(`.tree__item`)??null}))}#l(e){let t=[];for(let n of[`:scope > summary`,`:scope > .tree__content`,`:scope > details > summary`]){let r=e.querySelector(n);r?.textContent&&t.push(r.textContent)}for(let n of e.querySelectorAll(`:scope > .tree__desc, :scope > p, :scope > details > .tree__desc, :scope > details > p`))n.textContent&&t.push(n.textContent);return t.join(` `).toLowerCase()}#u(e){return e instanceof HTMLDetailsElement?e:e.querySelector(`:scope > details`)}#d(){return[...this.#e?.querySelectorAll(`details`)??[]]}#f(){let e=document.createElement(`div`);if(e.className=`tree__toolbar cluster`,!this.nofilter){let t=document.createElement(`input`);t.type=`search`,t.className=`tree__filter`,t.placeholder=this.label,t.setAttribute(`aria-label`,this.label),t.addEventListener(`input`,()=>this.filter(t.value));let n=document.createElement(`div`);n.className=`input-group tree__filter-group`,n.appendChild(t),e.appendChild(n),this.#r=t}for(let[t,n]of[[`Expand all`,!0],[`Collapse all`,!1]]){let r=document.createElement(`button`);r.type=`button`,r.className=`btn`,r.textContent=t,r.addEventListener(`click`,()=>this.toggleAll(n)),e.appendChild(r)}let t=document.createElement(`span`);t.className=`tree__count font-footnote`,t.setAttribute(`role`,`status`),t.setAttribute(`aria-live`,`polite`),e.appendChild(t),this.#i=t,this.#e?.before(e),this.#n=e}toggleAll(e){for(let t of this.#d())t.open=e}filter(e){let t=e.trim().toLowerCase();if(!t){for(let{key:e}of this.#t)e.hidden=!1;if(this.#o){for(let e of this.#d())e.open=this.#a.get(e)??!1;this.#o=!1}this.#p(``);return}if(!this.#o){for(let e of this.#d())this.#a.set(e,e.open);this.#o=!0}let{keep:n,hits:r}=Vt(this.#t,t);for(let{key:e}of this.#t)e.hidden=!n.has(e);for(let e of n){let t=this.#u(e);t&&(t.open=!0)}this.#p(r===1?`1 match`:`${r} matches`),this.dispatchEvent(new CustomEvent(`tree-filter`,{detail:{query:t,matches:r},bubbles:!0}))}#p(e){this.#i&&(this.#i.textContent=e)}#m=()=>this.#h();#h(){let e=decodeURIComponent(location.hash.slice(1));if(!e)return;let t=this.#e?.ownerDocument.getElementById(e);if(!t||!this.contains(t))return;let n=this.#u(t);n&&(n.open=!0);for(let e=t;e&&(e instanceof HTMLDetailsElement&&(e.open=!0),e!==this);e=e.parentElement);this.#r?.value&&this.filter(this.#r.value=``),t.scrollIntoView({block:`start`,behavior:`smooth`})}};customElements.get(`wc-tree`)||customElements.define(`wc-tree`,Ht);function Ut(e){return!e.hasLink||e.onInteractive||e.platformHandled?!1:e.moved<=6}const Wt=[`a[href]`,`button`,`input`,`select`,`textarea`,`label`,`summary`,`details`,`audio[controls]`,`video[controls]`,`[contenteditable]`,`[role="button"]`,`[role="link"]`,`[onclick]`,`[data-no-card-link]`].join(`,`);function Gt(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}var Kt=class extends Q{static{this.properties={item:{type:String,reflect:!0}}}createRenderRoot(){return this}render(){return D}#e=[];#t;constructor(){super(),this.item=`.card`}connectedCallback(){super.connectedCallback(),$(this,()=>this.#n())}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`pointerdown`,this.#i),this.removeEventListener(`click`,this.#a);for(let e of this.#e)e.removeAttribute(`data-card-link`);this.#e=[]}#n(){if(this.parentElement?.closest(`wc-cards`)||this.#e.length)return!0;let e=[...this.querySelectorAll(this.item)];if(!e.length)return!1;if(this.#e=e.filter(e=>this.#r(e)),!this.#e.length)return!0;for(let e of this.#e)e.setAttribute(`data-card-link`,``);return this.addEventListener(`pointerdown`,this.#i),this.addEventListener(`click`,this.#a),!0}#r(e){return e.querySelector(`a[href][data-card-link-target]`)??e.querySelector(`a[href]`)}#i=e=>{this.#t={x:e.clientX,y:e.clientY}};#a=e=>{let t=e.target,n=t?.closest(`[data-card-link]`);if(!n||!this.contains(n))return;let r=this.#r(n);if(r&&t?.closest(`a[href]`)===r)return;let i=Ut({moved:this.#t?Gt(this.#t,{x:e.clientX,y:e.clientY}):0,onInteractive:!!t?.closest(Wt),platformHandled:e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0,hasLink:!!r});this.#t=void 0,!(!i||!r)&&r.click()}};customElements.get(`wc-cards`)||customElements.define(`wc-cards`,Kt);
887
+ `}};customElements.get(`wc-multi-field`)||customElements.define(`wc-multi-field`,Wt);var Gt=class extends Q{createRenderRoot(){return this}#e=null;#t;connectedCallback(){super.connectedCallback(),$(this,()=>this.#n())}#n(){if(this.#e)return!0;let e=this.querySelector(`.marquee__content`);return e?(this.#e=e,this.style.setProperty(`--_marquee-min`,`auto`),this.#r(),this.#t=new ResizeObserver(()=>this.#r()),this.#t.observe(this),!0):!1}disconnectedCallback(){this.#t?.disconnect(),super.disconnectedCallback()}#r(){let e=this.#e;if(!e)return;for(let e of this.querySelectorAll(`[data-marquee-clone]`))e.remove();let t=Number.parseFloat(getComputedStyle(this).columnGap)||0,n=e.getBoundingClientRect().width;if(n<=0)return;this.style.setProperty(`--_marquee-distance`,`${n+t}px`);let r=Math.ceil(this.getBoundingClientRect().width/(n+t))+1;for(let t=0;t<r;t++){let t=e.cloneNode(!0);t.dataset.marqueeClone=``,t.setAttribute(`aria-hidden`,`true`),this.append(t)}}};customElements.get(`wc-marquee`)||customElements.define(`wc-marquee`,Gt);function Kt(e,t){let n=t.trim().toLowerCase();if(!n)return{keep:new Set,hits:0};let r=new Map,i=new Map;for(let t of e){r.set(t.key,t);let e=i.get(t.parent);e?e.push(t):i.set(t.parent,[t])}let a=new Set,o=0,s=e=>{for(let t of i.get(e)??[])a.has(t.key)||(a.add(t.key),s(t.key))};for(let t of e){if(!t.own.includes(n))continue;o++,a.add(t.key),s(t.key);let e=new Set([t.key]),i=r.get(t.parent);for(;i&&!e.has(i.key);)a.add(i.key),e.add(i.key),i=r.get(i.parent)}return{keep:a,hits:o}}var qt=class extends Q{static{this.properties={label:{type:String,reflect:!0},nofilter:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}render(){return D}#e=null;#t=[];#n=null;#r=null;#i=null;#a=new WeakMap;#o=!1;constructor(){super(),this.label=`Filter`,this.nofilter=!1}connectedCallback(){super.connectedCallback(),$(this,()=>this.#s())}#s(){if(this.#e)return!0;if(!this.isConnected)return!1;if(this.parentElement?.closest(`wc-tree`))return!0;let e=this.querySelector(`.tree`);return e?(this.#e=e,this.#c(),this.#t.length?(this.#f(),window.addEventListener(`hashchange`,this.#m),requestAnimationFrame(()=>this.#h()),!0):(this.#e=null,!1)):!1}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener(`hashchange`,this.#m),this.#n?.remove(),this.#n=null,this.#r=null,this.#i=null,this.#t=[],this.#e=null}#c(){let e=this.#e?.querySelectorAll(`.tree__item`)??[];this.#t=[...e].map(e=>({key:e,own:this.#l(e),parent:e.parentElement?.closest(`.tree__item`)??null}))}#l(e){let t=[];for(let n of[`:scope > summary`,`:scope > .tree__content`,`:scope > details > summary`]){let r=e.querySelector(n);r?.textContent&&t.push(r.textContent)}for(let n of e.querySelectorAll(`:scope > .tree__desc, :scope > p, :scope > details > .tree__desc, :scope > details > p`))n.textContent&&t.push(n.textContent);return t.join(` `).toLowerCase()}#u(e){return e instanceof HTMLDetailsElement?e:e.querySelector(`:scope > details`)}#d(){return[...this.#e?.querySelectorAll(`details`)??[]]}#f(){let e=document.createElement(`div`);if(e.className=`tree__toolbar cluster`,!this.nofilter){let t=document.createElement(`input`);t.type=`search`,t.className=`tree__filter`,t.placeholder=this.label,t.setAttribute(`aria-label`,this.label),t.addEventListener(`input`,()=>this.filter(t.value));let n=document.createElement(`div`);n.className=`input-group tree__filter-group`,n.appendChild(t),e.appendChild(n),this.#r=t}for(let[t,n]of[[`Expand all`,!0],[`Collapse all`,!1]]){let r=document.createElement(`button`);r.type=`button`,r.className=`btn`,r.textContent=t,r.addEventListener(`click`,()=>this.toggleAll(n)),e.appendChild(r)}let t=document.createElement(`span`);t.className=`tree__count font-footnote`,t.setAttribute(`role`,`status`),t.setAttribute(`aria-live`,`polite`),e.appendChild(t),this.#i=t,this.#e?.before(e),this.#n=e}toggleAll(e){for(let t of this.#d())t.open=e}filter(e){let t=e.trim().toLowerCase();if(!t){for(let{key:e}of this.#t)e.hidden=!1;if(this.#o){for(let e of this.#d())e.open=this.#a.get(e)??!1;this.#o=!1}this.#p(``);return}if(!this.#o){for(let e of this.#d())this.#a.set(e,e.open);this.#o=!0}let{keep:n,hits:r}=Kt(this.#t,t);for(let{key:e}of this.#t)e.hidden=!n.has(e);for(let e of n){let t=this.#u(e);t&&(t.open=!0)}this.#p(r===1?`1 match`:`${r} matches`),this.dispatchEvent(new CustomEvent(`tree-filter`,{detail:{query:t,matches:r},bubbles:!0}))}#p(e){this.#i&&(this.#i.textContent=e)}#m=()=>this.#h();#h(){let e=decodeURIComponent(location.hash.slice(1));if(!e)return;let t=this.#e?.ownerDocument.getElementById(e);if(!t||!this.contains(t))return;let n=this.#u(t);n&&(n.open=!0);for(let e=t;e&&(e instanceof HTMLDetailsElement&&(e.open=!0),e!==this);e=e.parentElement);this.#r?.value&&this.filter(this.#r.value=``),t.scrollIntoView({block:`start`,behavior:`smooth`})}};customElements.get(`wc-tree`)||customElements.define(`wc-tree`,qt);function Jt(e){return!e.hasLink||e.onInteractive||e.platformHandled?!1:e.moved<=6}const Yt=[`a[href]`,`button`,`input`,`select`,`textarea`,`label`,`summary`,`details`,`audio[controls]`,`video[controls]`,`[contenteditable]`,`[role="button"]`,`[role="link"]`,`[onclick]`,`[data-no-card-link]`].join(`,`);function Xt(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}var Zt=class extends Q{static{this.properties={item:{type:String,reflect:!0}}}createRenderRoot(){return this}render(){return D}#e=[];#t;constructor(){super(),this.item=`.card`}connectedCallback(){super.connectedCallback(),$(this,()=>this.#n())}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`pointerdown`,this.#i),this.removeEventListener(`click`,this.#a);for(let e of this.#e)e.removeAttribute(`data-card-link`);this.#e=[]}#n(){if(this.parentElement?.closest(`wc-cards`)||this.#e.length)return!0;let e=[...this.querySelectorAll(this.item)];if(!e.length)return!1;if(this.#e=e.filter(e=>this.#r(e)),!this.#e.length)return!0;for(let e of this.#e)e.setAttribute(`data-card-link`,``);return this.addEventListener(`pointerdown`,this.#i),this.addEventListener(`click`,this.#a),!0}#r(e){return e.querySelector(`a[href][data-card-link-target]`)??e.querySelector(`a[href]`)}#i=e=>{this.#t={x:e.clientX,y:e.clientY}};#a=e=>{let t=e.target,n=t?.closest(`[data-card-link]`);if(!n||!this.contains(n))return;let r=this.#r(n);if(r&&t?.closest(`a[href]`)===r)return;let i=Jt({moved:this.#t?Xt(this.#t,{x:e.clientX,y:e.clientY}):0,onInteractive:!!t?.closest(Yt),platformHandled:e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0,hasLink:!!r});this.#t=void 0,!(!i||!r)&&r.click()}};customElements.get(`wc-cards`)||customElements.define(`wc-cards`,Zt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/core",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "The Vitops design-system framework — CSS partials, Lit web components, and browser polyfills.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -32,7 +32,7 @@
32
32
  "lit": "^3.3.3",
33
33
  "scroll-timeline-polyfill": "^1.1.0",
34
34
  "temporal-polyfill": "^1.0.1",
35
- "@getvitops/utils": "5.0.0"
35
+ "@getvitops/utils": "6.0.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "happy-dom": "^20.0.0",