@getvitops/core 1.0.0 → 2.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 +460 -0
- package/README.md +18 -4
- package/css/animation.css +145 -34
- package/css/global.css +19 -3
- package/css/index.css +8 -0
- package/css/layout-utilities.css +764 -0
- package/css/layout.css +50 -584
- package/css/patterns/consent.css +117 -0
- package/css/patterns/details.css +1 -1
- package/css/patterns/reveal.css +92 -0
- package/css/patterns/subgrid.css +7 -2
- package/css/patterns/theme-editor.css +37 -0
- package/css/utilities.css +19 -98
- package/dist/consent.js +1 -0
- package/dist/deferred.js +1 -1
- package/dist/editor.js +1 -1
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,465 @@
|
|
|
1
1
|
# @getvitops/core
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- bf453b0: The framework now ships a border-box reset.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
*,
|
|
11
|
+
*::before,
|
|
12
|
+
*::after {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**This is a behavioural change for the `css` and `bricks` formats.** If your project does not
|
|
18
|
+
already set border-box — Tailwind's preflight does, as does normalize and almost every modern base
|
|
19
|
+
stylesheet — elements with padding or a border now measure their declared width inclusive of it, so
|
|
20
|
+
some boxes will come out narrower than before. The `tailwind` format is unaffected: preflight
|
|
21
|
+
already did this.
|
|
22
|
+
|
|
23
|
+
The framework used to refuse this on the grounds that a global reset silently reflows consumer
|
|
24
|
+
layouts, and that reasoning was sound when nothing was layered. Two things changed:
|
|
25
|
+
- **It is layered.** The rule lives in `vitops.base`, the lowest of the three framework layers, so
|
|
26
|
+
it loses to any unlayered consumer CSS. The opt-out is one rule in your own stylesheet:
|
|
27
|
+
`*, *::before, *::after { box-sizing: content-box }`.
|
|
28
|
+
- **Patterns are only correct under it.** `.split`'s ratio is a flex basis, which sizes the border
|
|
29
|
+
box; under content-box a padded column came out wider than its sibling by exactly its padding.
|
|
30
|
+
Stating the assumption once beats every pattern re-asserting it and the ones that forget being
|
|
31
|
+
quietly wrong.
|
|
32
|
+
|
|
33
|
+
- bf453b0: Utilities now beat patterns, in every format. Cascade layers are assigned by what a rule **is**,
|
|
34
|
+
not by which file it happens to live in.
|
|
35
|
+
|
|
36
|
+
`vitops.base → vitops.components → vitops.utilities` has existed for a while, and its whole purpose
|
|
37
|
+
is to let `class="card bg-danger-muted"` work without `!important`. But the layer was chosen per
|
|
38
|
+
**partial**, and `layout.css` was one file holding both the structural patterns (`.rhythm`,
|
|
39
|
+
`.centered`, `.split`) and roughly three quarters of the framework's utilities (`.m-*`, `.flex-*`,
|
|
40
|
+
`.items-*`, `.justify-*`, `.text-*`, `.split-<a>-<b>`, track placement). Unmapped, it defaulted to
|
|
41
|
+
`vitops.components` — so all of those utilities were shelved _below_ the patterns they are meant to
|
|
42
|
+
override, and quietly did nothing. `utilities.css` had the mirror-image problem: it held the
|
|
43
|
+
`.reveal` component family, which therefore outranked every pattern _and_ the display utilities.
|
|
44
|
+
|
|
45
|
+
Both are now split — `layout.css` / `layout-utilities.css`, and `patterns/reveal.css` — and the
|
|
46
|
+
classification is asserted across formats by a shared `LAYER_CONTRACT`, so the two halves cannot
|
|
47
|
+
drift apart again.
|
|
48
|
+
|
|
49
|
+
**What visibly changes.** Each of these was silent before and now takes effect. That is the point,
|
|
50
|
+
but if you wrote one of these combinations and never noticed it did nothing, you will see a change:
|
|
51
|
+
|
|
52
|
+
| markup | before | after |
|
|
53
|
+
| ---------------------------------------- | ------------------------------------------ | ---------------------- |
|
|
54
|
+
| `class="table text-center"` | `.table { text-align: start }` won | `.text-center` wins |
|
|
55
|
+
| `class="banner items-start"` | `.banner`'s centring won | the utility wins |
|
|
56
|
+
| `class="cluster-between justify-center"` | `.cluster-between` won | `.justify-center` wins |
|
|
57
|
+
| `class="icon justify-start"` | `.icon { justify-content: center }` won | `.justify-start` wins |
|
|
58
|
+
| `class="combobox flex-row"` | `.combobox { flex-direction: column }` won | `.flex-row` wins |
|
|
59
|
+
| `class="media items-center"` | `.media { align-items: flex-start }` won | `.items-center` wins |
|
|
60
|
+
| `<details>` first content child + `m-0` | `details.css`'s `> summary + *` margin won | `.m-0` wins |
|
|
61
|
+
| `class="split flex-col"` | worked, but on source order | works by layer |
|
|
62
|
+
|
|
63
|
+
The reveal family moves the other way, having been mis-shelved as a utility: `class="reveal hidden"`
|
|
64
|
+
and `class="reveal-fade block"` used to keep the reveal's own `display` and now lose to the display
|
|
65
|
+
utility. The duplicate `details::details-content` rules in `utilities.css` are deleted —
|
|
66
|
+
`patterns/details.css` was always the owner, and its selector is a superset — so the collapsed
|
|
67
|
+
content box is now `overflow: hidden` rather than `clip`.
|
|
68
|
+
|
|
69
|
+
**Removed: the bare `<bp>-split` classes** (`sm-split`, `md-split`, `lg-split`, `xl-split`).
|
|
70
|
+
`.split` is a pattern now, and `@utility` cannot live in a cascade layer — measured against
|
|
71
|
+
tailwindcss@4.3.3, it throws inside `@layer` _and_ inside a file imported with `layer(…)`, and a
|
|
72
|
+
`@custom-variant` can't reach a components-layer class either — so `@md:split` became impossible and
|
|
73
|
+
the css/bricks counterpart had to go with it. Use `md-flex-row` for "become a row at md"; it says
|
|
74
|
+
the same thing in every format. The one thing lost is resetting a bare ratio back to equal at a
|
|
75
|
+
breakpoint, which had no usages; apply the ratio at the breakpoint instead.
|
|
76
|
+
|
|
77
|
+
**Also fixed, in the tailwind format only** — three divergences found while auditing the layers:
|
|
78
|
+
- `body` was emitted with `container-type` but not `container-name: body`, so the
|
|
79
|
+
`@container body (…)` queries in the scroll/TOC patterns never matched: `.toc-layout` and
|
|
80
|
+
`.toc-sidebar` were stuck in their narrow layout.
|
|
81
|
+
- `grid-auto` was missing the `:is(ul, ol) > li + li` margin reset, so a `<ul class="grid-auto">`
|
|
82
|
+
inside `.rhythm` got a stray top margin on every item but the first.
|
|
83
|
+
- **`.sticky` was being deleted outright.** `sticky` is a Tailwind utility name, and the strip that
|
|
84
|
+
defers those to Tailwind matched a rule's leading class — taking `patterns/sticky.css` with it,
|
|
85
|
+
including `--sticky-offset`, the z-index wiring and every `.sticky--bottom` / `--inline-start` /
|
|
86
|
+
`--inline-end` variant. Components whose names collide with Tailwind utilities are now allowlisted.
|
|
87
|
+
|
|
88
|
+
**Migration.** If a layout utility on a patterned element now "suddenly works" and you preferred the
|
|
89
|
+
old result, remove the utility — it was never doing anything. To keep a pattern winning over the
|
|
90
|
+
framework's utilities, write the declaration in your own stylesheet: unlayered CSS still beats all
|
|
91
|
+
three framework layers.
|
|
92
|
+
|
|
93
|
+
### Minor Changes
|
|
94
|
+
|
|
95
|
+
- 4756788: Add `<Analytics />` and a general-purpose consent gate.
|
|
96
|
+
|
|
97
|
+
`getvitops({ analytics })` configures Google Analytics 4, Microsoft Clarity, Matomo and Plausible;
|
|
98
|
+
`<Analytics />` emits their tags. Nothing touches the critical path — `strategy` defaults to `'idle'`,
|
|
99
|
+
which loads every tag after `load` on an idle callback (`'async'` and `'interaction'` are the other
|
|
100
|
+
options), and no `preconnect` is emitted, since warming a third-party connection during parse is the
|
|
101
|
+
cost `idle` exists to avoid.
|
|
102
|
+
|
|
103
|
+
`getvitops({ consent })` adds the gate: `@getvitops/core/consent`, a 2.3 KB gzipped Lit-free bundle,
|
|
104
|
+
plus `<CookieConsent />`.
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
vitops({
|
|
108
|
+
analytics: { googleAnalytics: 'G-XXXXXXXXXX', plausible: 'acme.com' },
|
|
109
|
+
consent: { policyUrl: '/legal/cookies' },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Consent is not an analytics feature.** The gate is general — mark anything
|
|
114
|
+
`data-consent="<category>"` and it waits on the same choice, so A/B assignment, personalisation and
|
|
115
|
+
third-party embeds use it too, and a site can enable the banner with no analytics at all. Categories
|
|
116
|
+
are `necessary` / `analytics` / `marketing` / `preferences`, and `window.vitopsConsent` plus a
|
|
117
|
+
`vitops:consent` event on `document` are how your own code reads the answer.
|
|
118
|
+
|
|
119
|
+
**Which category a provider needs is derived, not declared.** It follows from whether that provider
|
|
120
|
+
sets cookies, which follows from its own config: Matomo runs cookieless by default (`disableCookies`)
|
|
121
|
+
and so needs no banner; `cookies: true` opts in and moves it behind `analytics`. You can't mark
|
|
122
|
+
Google Analytics `necessary` to skip the banner — but you can pick a genuinely cookieless provider.
|
|
123
|
+
|
|
124
|
+
Gated tags render as `<script type="text/plain">` with the URL on `data-src`, so an undecided or
|
|
125
|
+
declining visitor's page issues **no third-party request at all**. For Google Analytics that is basic
|
|
126
|
+
consent mode rather than Consent Mode v2 advanced: nothing reaches Google until the visitor accepts.
|
|
127
|
+
Clarity additionally receives `clarity('consentv2', …)`, because Microsoft enforces the signal
|
|
128
|
+
separately for EEA/UK/CH traffic. Nothing is stored until a choice is made — the banner can't be the
|
|
129
|
+
thing that needs consent — and revoking clears the provider's cookies and reloads, because an
|
|
130
|
+
already-executing tracker can't be unloaded any other way.
|
|
131
|
+
|
|
132
|
+
The banner is shown in the top layer via `popover="manual"`, like `.tooltip`: a plain fixed banner
|
|
133
|
+
resolves against the nearest containing block, and `body { container-type: inline-size }` — ordinary
|
|
134
|
+
in a framework whose breakpoints are container queries — would otherwise trap it mid-page.
|
|
135
|
+
|
|
136
|
+
**The site config gained `analytics.clarityId` and `analytics.matomo`**, so `vitops legal` discloses
|
|
137
|
+
them. Clarity and Matomo join the processor table with their real cookie names, cookieless Matomo
|
|
138
|
+
asserts positively that it sets none, and a Clarity site's privacy policy now describes session
|
|
139
|
+
replay rather than filing it under page-view analytics. Configure `legal` alongside `analytics` and
|
|
140
|
+
the Astro integration cross-checks the two, naming any provider you'd otherwise run without
|
|
141
|
+
disclosing; it also warns when a cookie-setting provider has no `consent` gate.
|
|
142
|
+
|
|
143
|
+
Existing configs are unaffected — both options are off unless provided. One behaviour change if you
|
|
144
|
+
use the ad-conversion tracking script: `packages/astro/src/scripts/tracking.ts` now waits for
|
|
145
|
+
`marketing` consent before writing its 90-day `_ac` click-ID cookie, when the gate is present. With
|
|
146
|
+
no gate on the page it behaves exactly as before.
|
|
147
|
+
|
|
148
|
+
- bf453b0: Animation families that never actually animated, and a driver that fired before you could see it.
|
|
149
|
+
|
|
150
|
+
Each of these looked correct in source and failed somewhere else — in the bundler, in hit-testing,
|
|
151
|
+
or in the difference between a time-based and a progress-based timeline. Nothing here needs a
|
|
152
|
+
markup change.
|
|
153
|
+
|
|
154
|
+
**Changed: entrances are now timed off the element's midpoint.** Every driver used to key off a
|
|
155
|
+
fraction of the element's _own height_, which meant the same class behaved differently on a 4rem
|
|
156
|
+
card and a full-bleed section — and on small elements it was over before they appeared
|
|
157
|
+
(`entry 20%` is about 17px of scroll for a 5.5rem tile). Motion now starts once the element's
|
|
158
|
+
**midpoint is 10% of the viewport in**, and a one-shot entrance completes at **25%**. This applies
|
|
159
|
+
to `animate-view` and to the `.is-active` observer that drives `animate-trigger` and the
|
|
160
|
+
`active-<fx>` transition variants, so everything on a page starts at the same moment on screen.
|
|
161
|
+
|
|
162
|
+
The pivot is `entry 50%` — the one point that means "midpoint on the viewport edge" for an element
|
|
163
|
+
of any height — plus a viewport length. Shift the window with `--anim-start` / `--anim-end`, or
|
|
164
|
+
replace it with `--anim-range`. `--stagger-range-step` is now a viewport length (`5vh`) to match.
|
|
165
|
+
|
|
166
|
+
**Fixed: every journey ran on a truncated range.** The generator emitted `animation-range: entry
|
|
167
|
+
exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than the spec's `exit
|
|
168
|
+
100%`, so the bundle shipped `entry exit 0%`. Journeys reach their `100%` keyframe — the hidden
|
|
169
|
+
`from` state — as the element hits the top of the viewport, which is to say they faded out while
|
|
170
|
+
still fully on screen. Journeys now run `entry calc(50% + 10vh) → exit 100%`: they start on the same
|
|
171
|
+
midpoint pivot as everything else and keep the full entry → hold → exit arc, so the hold sits in the
|
|
172
|
+
middle of the crossing. `animation-effects.test.ts` asserts on the **bundled** css, because the
|
|
173
|
+
emitter was right the whole time.
|
|
174
|
+
|
|
175
|
+
**Fixed: `slide-journey` had no distance to travel.** `animations.journeys.base.slide` was `{}` in
|
|
176
|
+
both `defaultConfig()` and the shipped example, so the keyframe animated `translate: 0 → 0`. It now
|
|
177
|
+
declares `translate-y-from: var(--slide-distance, 2rem)` — the same var the `slide-up` effect uses,
|
|
178
|
+
so one knob tunes both. If your own config has an empty `slide` base, add the same line.
|
|
179
|
+
|
|
180
|
+
**Fixed: `reveal-*` on hover was unreachable.** A `hover-reveal-left` element rests at `clip-path:
|
|
181
|
+
inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting — so it had zero
|
|
182
|
+
hittable area and could never receive the hover that would reveal it. The state variants now match
|
|
183
|
+
the element **or its direct parent**, mirroring what `animation.css` already did for the trigger
|
|
184
|
+
driver (`:is(.is-active, [data-active]) > .animate-trigger`):
|
|
185
|
+
|
|
186
|
+
```css
|
|
187
|
+
.hover-<fx>:hover, :hover > .hover-<fx> { … }
|
|
188
|
+
.focus-<fx>:focus-visible, :focus-within > .focus-<fx> { … }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Specificity is unchanged (0-2-0), so nothing reorders. **Behaviour change:** a `hover-<fx>` element
|
|
192
|
+
that is a direct child of a hovered element now flips with its parent. Wrap it in an intermediate
|
|
193
|
+
element if you need the old element-only behaviour.
|
|
194
|
+
|
|
195
|
+
**Fixed: `.stagger` did nothing on a scroll-driven timeline.** It offsets children with
|
|
196
|
+
`animation-delay`, which is time-based and is ignored outright on a `view()` / `scroll()` timeline,
|
|
197
|
+
so `.stagger > .animate-view` arrived all at once. It now also offsets each child's view
|
|
198
|
+
`animation-range` by the same index, so one class works under both driver families — tune the
|
|
199
|
+
scroll-driven step with `--stagger-range-step` (default `5vh`). Journeys declare their own range and
|
|
200
|
+
opt out by construction.
|
|
201
|
+
|
|
202
|
+
Two smaller faults found alongside it: `@supports (--x: sibling-index())` is **always true** — any
|
|
203
|
+
token stream is a valid custom-property value — so the guard around `sibling-index()` guarded
|
|
204
|
+
nothing in both `.stagger` and `.subgrid`'s row index, and on an engine without support the
|
|
205
|
+
declaration went invalid rather than being skipped. Both now test a real property. And the CSS path
|
|
206
|
+
was 1-based while the JS fallback wrote 0-based, so the two disagreed by one step wherever both ran.
|
|
207
|
+
|
|
208
|
+
**Fixed: the pre-paint `<html>` class script was missing entirely.** `<Head />` now emits it again,
|
|
209
|
+
outside the `webComponents` block since it gates stylesheet behaviour rather than the element
|
|
210
|
+
runtime. It does two things `animation.css` depends on and that three other files documented as
|
|
211
|
+
already shipping:
|
|
212
|
+
- `no-js` → `js`, so `.animate-trigger` is paused only where JS can un-pause it;
|
|
213
|
+
- `.no-scroll-timeline` when `animation-timeline: view()` is unsupported, which is what cancels
|
|
214
|
+
`.animate-view` / `.animate-scroll`. Scroll-driven animations are deliberately **not** polyfilled,
|
|
215
|
+
so without this flag those elements sit at their `from` keyframe — `opacity: 0`, i.e. invisible
|
|
216
|
+
content — on any engine without support. It had been dropped in the move to publishable packages,
|
|
217
|
+
leaving the cancel rule as dead code.
|
|
218
|
+
|
|
219
|
+
**New: `hover-size-grow` and friends exist.** The layout effects (`size-grow`, `size-shrink`) were
|
|
220
|
+
the one family with no state variants, on the grounds that `.transition` doesn't cover `height`.
|
|
221
|
+
That wasn't a platform limit — `height: 0 → auto` just needs `interpolate-size: allow-keywords`,
|
|
222
|
+
which the framework already sets on `:root`, and which the `layout` **keyframe** depended on
|
|
223
|
+
equally. So the exclusion bought no portability; it only made `hover-size-grow` a class the docs
|
|
224
|
+
advertised and the stylesheet didn't define. `.transition` now declares `height` inside an
|
|
225
|
+
`@supports (interpolate-size: allow-keywords)` block, and the generator emits the full
|
|
226
|
+
`hover-`/`focus-`/`active-` set for layout effects, carrying each effect's own `overflow: clip` so a
|
|
227
|
+
collapsed box hides its content instead of spilling it. Where `interpolate-size` is missing, the
|
|
228
|
+
height simply doesn't transition — the same degradation the keyframe path has. (For the record:
|
|
229
|
+
`transition-behavior: allow-discrete` is _not_ the tool here — it only lets genuinely discrete
|
|
230
|
+
properties transition, and would snap at the midpoint.)
|
|
231
|
+
|
|
232
|
+
`.transition` switched from the `transition` shorthand to `transition-property` +
|
|
233
|
+
`transition-duration` + `transition-timing-function`, so the gated block can append `height` without
|
|
234
|
+
restating the list. If you override `.transition`'s timing, override the longhands.
|
|
235
|
+
|
|
236
|
+
The generated class reference (`vitops docs classes`) was the upstream source of that mismatch — it
|
|
237
|
+
listed every effect under "with the state prefixes above", including the layout ones. It now states
|
|
238
|
+
which stage needs the feature gate, when each driver plays, and how `stagger` composes with both.
|
|
239
|
+
|
|
240
|
+
- bf453b0: New `gap-*` utilities over the fluid space scale, in all three formats.
|
|
241
|
+
|
|
242
|
+
`gap-<name>`, `gap-x-<name>` (column) and `gap-y-<name>` (row), for every step of your
|
|
243
|
+
`spaceScale` — `gap-2xs` … `gap-7xl` — each breakpoint-prefixable (`md-gap-l` in css/bricks,
|
|
244
|
+
`@md:gap-l` in tailwind).
|
|
245
|
+
|
|
246
|
+
There was no gap utility at all before. `vitops docs css` advertised a `g` class that was never
|
|
247
|
+
emitted, so the honest answer for a css or bricks consumer was an inline `style="gap: …"`. Tailwind
|
|
248
|
+
did not fill the hole either: the fluid steps are deliberately kept **out** of Tailwind's
|
|
249
|
+
`--spacing-*` namespace, because named keys there shadow the size scales (`max-w-7xl` would resolve
|
|
250
|
+
to `var(--spacing-7xl)` and collapse layouts), so `gap-l` was not something Tailwind could derive.
|
|
251
|
+
Measured against tailwindcss@4.3.3: the emitted `@utility gap-l` is honoured, accepts variants, and
|
|
252
|
+
coexists with the built-in numeric `gap-4`, which keeps Tailwind's own multiplier.
|
|
253
|
+
|
|
254
|
+
The whole matrix is emitted rather than a plausible subset. An undefined step produces no rule and
|
|
255
|
+
no error in either format, so a missing `md-gap-x-2xl` would be indistinguishable from a working
|
|
256
|
+
one.
|
|
257
|
+
|
|
258
|
+
In the css and bricks bundles these land in `vitops.utilities`, so `class="cluster gap-l"` beats
|
|
259
|
+
the pattern's own gap.
|
|
260
|
+
|
|
261
|
+
- 04a51d8: Icons: one semantic vocabulary across icon sets, with the bundle derived from your source.
|
|
262
|
+
|
|
263
|
+
Name icons by meaning — `<Icon name="menu" />` resolves to `fa7-solid:bars`, `ph:list` or
|
|
264
|
+
`lucide:menu` depending on the set you configure, so swapping sets is a config edit rather than a
|
|
265
|
+
find-and-replace. A name containing `:` still passes through untouched, which is the escape hatch
|
|
266
|
+
for a set-specific glyph.
|
|
267
|
+
|
|
268
|
+
**New `icons` option on `getvitops()`.** Configures the sets once per site and, under
|
|
269
|
+
`output: 'server'`, derives astro-icon's `include` map by scanning your source — the list most
|
|
270
|
+
projects end up maintaining by hand. On a static build no `include` is passed at all, because
|
|
271
|
+
astro-icon is already zero-config there and a list could only drop a glyph the scan couldn't see.
|
|
272
|
+
Names you declare that don't resolve fail the build; names only the scan found just warn; names
|
|
273
|
+
computed at runtime are reported with file and line so you can declare them.
|
|
274
|
+
|
|
275
|
+
**New `<Icon />` component**, and a real fix with it: `Popover`, `Details` and `Drawer` imported
|
|
276
|
+
`astro-icon/components` at module scope, so the _optional_ peer was resolved whether or not an icon
|
|
277
|
+
ever rendered — hard-failing anyone who hadn't installed it. Engines now load dynamically. The
|
|
278
|
+
per-component `iconResolver` prop still works but is deprecated in favour of the integration option.
|
|
279
|
+
|
|
280
|
+
**New SVG sprite** (`icons.sprite: true` in your site config) for consumers that can't run an icon
|
|
281
|
+
integration — Bricks/WordPress, EmDash renderers, plain HTML. `<use href="…/icons.svg#ph--list">`,
|
|
282
|
+
no JavaScript and no icon-API request. Every semantic name also gets a set-independent `icon-<name>`
|
|
283
|
+
alias, so sprite markup survives an icon-set change. WordPress gets `vitops_icon()`, a
|
|
284
|
+
`[vitops_icon]` shortcode and a **Vitops → Icon** Bricks element.
|
|
285
|
+
|
|
286
|
+
**New `vitops icons`** command: reports which icons your source uses, which names couldn't be
|
|
287
|
+
resolved, and which are computed at runtime; `--sprite` builds the sprite, `--json` for CI.
|
|
288
|
+
|
|
289
|
+
**Renamed, and completed to all four directions.** Chevrons and arrows are the
|
|
290
|
+
one family whose _meaning is_ its direction, and that direction is physical, not
|
|
291
|
+
logical — a chevron in a details marker points down in every writing mode. So they
|
|
292
|
+
are named for where they point: `expand-more`/`expand-less` →
|
|
293
|
+
`chevron-down`/`chevron-up`, and `arrow-forward`/`arrow-back` →
|
|
294
|
+
`arrow-right`/`arrow-left`. `chevron-left`/`chevron-right` keep their names.
|
|
295
|
+
`arrow-up`/`arrow-down` are new, so both families now cover all four directions.
|
|
296
|
+
`lightning` is new.
|
|
297
|
+
|
|
298
|
+
If you passed any of the old names to `<Icon />`, `resolveIcon` or an `icons`
|
|
299
|
+
config, update them. They fail loudly rather than silently: an unresolvable
|
|
300
|
+
declared name throws, and `vitops icons` reports scanned ones.
|
|
301
|
+
|
|
302
|
+
**Fixed three Font Awesome mappings that named no real glyph** and so rendered an
|
|
303
|
+
empty box: `login`/`logout` were mapped to `login`/`logout` (Font Awesome calls
|
|
304
|
+
them `right-to-bracket`/`right-from-bracket`) and `backup` to `backup`
|
|
305
|
+
(`cloud-arrow-up`). Every value in all four UI sets is now checked against the
|
|
306
|
+
installed collection.
|
|
307
|
+
|
|
308
|
+
**Phosphor (`ph`) joins the semantic map**, with all 83 names verified against the real icon set.
|
|
309
|
+
Phosphor keeps every weight in one collection and varies the name (`list`, `list-bold`), unlike Font
|
|
310
|
+
Awesome's per-weight collections, so `resolveIcon` and `generateIconInclude` gained a `weight`
|
|
311
|
+
option for sets shaped that way.
|
|
312
|
+
|
|
313
|
+
Fixes: `site.icons` was a closed object, so any icon collection not in its hand-written key list was
|
|
314
|
+
silently dropped during validation — your config passed and the icons never bundled. It accepts any
|
|
315
|
+
collection name now.
|
|
316
|
+
|
|
317
|
+
- bf453b0: Detect JavaScript and scroll-driven timelines in CSS, so no-JS visitors stop getting invisible
|
|
318
|
+
content.
|
|
319
|
+
|
|
320
|
+
`animation.css` gated two rules on classes that had to be put on `<html>`: `no-js`, which the
|
|
321
|
+
framework expected an author to write into their own markup, and `.no-scroll-timeline`, which an
|
|
322
|
+
inline `<head>` snippet was supposed to set. **That snippet never shipped.** Three source comments
|
|
323
|
+
described it and nothing implemented it, so on every Astro and Bricks site the `:root:not(.no-js)`
|
|
324
|
+
gate matched unconditionally and `.animate-trigger` stayed `animation-play-state: paused` with no
|
|
325
|
+
`IntersectionObserver` coming to release it. An entrance animation that never runs is `opacity: 0`.
|
|
326
|
+
|
|
327
|
+
Both gates are now platform queries:
|
|
328
|
+
|
|
329
|
+
```css
|
|
330
|
+
@media (scripting: enabled) {
|
|
331
|
+
.animate-trigger {
|
|
332
|
+
animation-play-state: paused;
|
|
333
|
+
}
|
|
334
|
+
/* …released by .is-active */
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@supports not (animation-timeline: view()) {
|
|
338
|
+
:where(.animate-view, .animate-scroll) {
|
|
339
|
+
animation: none;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Nothing to install, nothing to remember, and correct for consumers the old approach could not reach —
|
|
345
|
+
a Bricks site, or anyone rendering their own `<head>`. It also fails the right way round: an engine
|
|
346
|
+
that doesn't know either feature drops the block and the animation simply runs, costing an
|
|
347
|
+
enhancement rather than hiding the page.
|
|
348
|
+
|
|
349
|
+
**Nothing to do to adopt this.** Drop `class="no-js"` from your `<html>` and any script that removes
|
|
350
|
+
it if you like — both are inert now, not harmful. `<Head />` no longer emits a class-flipping script.
|
|
351
|
+
|
|
352
|
+
Support is baseline: `scripting` shipped in Firefox 113, Safari 17 and Chrome 120, all in 2023.
|
|
353
|
+
|
|
354
|
+
- bf453b0: `.split` gets a stacking convention, a reversal, and a ratio that actually holds.
|
|
355
|
+
|
|
356
|
+
**Fixed: a padded column silently broke the ratio.** `.split > *` was `flex: 1` — a zero basis plus
|
|
357
|
+
a grow factor — and `flex-grow` shares out only the _free_ space, which a child's padding is not
|
|
358
|
+
part of. So a padded column came out wider than its sibling by exactly its horizontal padding, and
|
|
359
|
+
`.split-1-2` quietly stopped being 1:2. Measured in Chrome: an equal `.split` 1000px wide with 40px
|
|
360
|
+
of padding a side on one child resolved 540/460, and `split-1-2` resolved 387/613. The ratio is now
|
|
361
|
+
a **flex basis**, which sizes the border box, so padding sits inside the share: 500/500 and 333/667.
|
|
362
|
+
Nothing to change in your markup — ratios that looked slightly off now land where they read.
|
|
363
|
+
|
|
364
|
+
Two consequences worth knowing. The basis applies only to a **two-child** split, because a ratio is
|
|
365
|
+
a pair contract (only `:first-child` and `:last-child` ever carried one) and with three or more
|
|
366
|
+
children a middle child would collapse to zero; those keep the previous behaviour. And the ratio
|
|
367
|
+
only holds on **border-box** children — a basis sizes the border box, and under content-box the
|
|
368
|
+
padding lands outside the share again, reproducing the exact defect. That is what the new global
|
|
369
|
+
border-box reset is for (see its own note); `.split > *` restates it so the pattern still holds if
|
|
370
|
+
you override the reset in a layer of your own.
|
|
371
|
+
|
|
372
|
+
**Fixed: long unbreakable content stretched a column.** `.split > *` now carries
|
|
373
|
+
`min-inline-size: 0`. A flex item's automatic minimum is its min-content size, so a URL, a code span
|
|
374
|
+
or a `<pre>` used to push its column past its share — and every consumer re-derived `min-w-0` to
|
|
375
|
+
stop it. It belongs to the pattern.
|
|
376
|
+
|
|
377
|
+
**Stacking is now a two-class idiom, using `flex-col` you already have.**
|
|
378
|
+
|
|
379
|
+
```html
|
|
380
|
+
<div class="split flex-col md-split-1-2 gap-l"></div>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Stacked below 48rem, 1:2 above. What happens when there isn't room for two columns was the question
|
|
384
|
+
`.split` never answered, so every project re-derived
|
|
385
|
+
`flex flex-col gap-… lg-flex-row lg-items-start lg-split`. The `sm-`/`md-`/`lg-`/`xl-` ratio classes
|
|
386
|
+
now assert `flex-direction: row`, which is what un-stacks the pair at the breakpoint. There is
|
|
387
|
+
deliberately **no** `.split-stack`: it would be `flex-col` under a second name.
|
|
388
|
+
|
|
389
|
+
`.flex-col` wins because `.split` is a **pattern** and every utility sits one cascade layer above
|
|
390
|
+
it — see the layering note in this release. `.md-split-1-2` then wins because a breakpoint-scoped
|
|
391
|
+
utility sorts after an unscoped one, which is stable in both formats. (`.split` itself now states
|
|
392
|
+
`flex-direction: row`, so a host that defaults an element to `column` — Bricks' block element —
|
|
393
|
+
can no longer stack it by accident, as long as that host's rule is itself layered.)
|
|
394
|
+
|
|
395
|
+
While stacked the ratio goes inert on its own and needs no reset: a percentage flex basis resolved
|
|
396
|
+
against an auto-height column container behaves as `content`. It does apply if you give the split a
|
|
397
|
+
definite `block-size`, which is a ratio in the block axis — what asking for one on a fixed-height
|
|
398
|
+
column means.
|
|
399
|
+
|
|
400
|
+
**New `.split-reverse`** (breakpoint-prefixable) — swaps the two panels. It is `order` on the first
|
|
401
|
+
child rather than `row-reverse`, so it reverses on **whichever axis the split is currently on**:
|
|
402
|
+
bare, it swaps the columns in a row _and_ the rows in a stack; scoped to a breakpoint
|
|
403
|
+
(`md-split-reverse`) it swaps only once there are two columns — put the media first in source and it
|
|
404
|
+
leads on mobile while sitting on the right at width. The ratio stays attached to the source-first
|
|
405
|
+
child, not to visual position.
|
|
406
|
+
|
|
407
|
+
> **Accessibility.** Reversing puts visual order out of step with DOM order, and focus order follows
|
|
408
|
+
> the DOM (WCAG 2.4.3 Focus Order) — so a keyboard user tabs through a reversed split in the order it
|
|
409
|
+
> is written, not the order it is seen. Put focusable content in **only one** of the two panels. The
|
|
410
|
+
> pattern declares `reading-flow: flex-visual`, which fixes this properly where it is supported;
|
|
411
|
+
> support is not yet broad enough to rely on, and an unknown property is inert, so treat it as an
|
|
412
|
+
> enhancement rather than a guarantee.
|
|
413
|
+
|
|
414
|
+
**New `.flex-row-reverse` / `.flex-col-reverse`** (breakpoint-prefixable) in the `css` and `bricks`
|
|
415
|
+
formats. These names were already in the list the `tailwind` format defers to Tailwind for, but
|
|
416
|
+
they were never defined here — so a mirrored row was inexpressible in framework classes in the two
|
|
417
|
+
formats with no fallback.
|
|
418
|
+
|
|
419
|
+
### Patch Changes
|
|
420
|
+
|
|
421
|
+
- bf453b0: Make `text-wrap` a stated decision on every type role, emit the `typography.headings` bindings in the tailwind format, and add the four `text-{wrap,nowrap,balance,pretty}` utilities.
|
|
422
|
+
|
|
423
|
+
`text-wrap: balance` for headings and `pretty` for copy belong to the **type role**, not to a
|
|
424
|
+
reset — the role already owns the property (`--<role>-tw`, editable in the live theme editor),
|
|
425
|
+
and `typography.headings` projects it onto bare `h1`/`body` so unclassed markup gets it with no
|
|
426
|
+
class at all. Three things stopped that working end to end.
|
|
427
|
+
|
|
428
|
+
**Omitting `text-wrap` was never "inherit".** Like `style`, `text-transform` and
|
|
429
|
+
`text-decoration`, it is emitted on every role at its identity value so that applying one role
|
|
430
|
+
class over another fully resets it. So a role that left it out emitted `text-wrap: wrap` and
|
|
431
|
+
**cancelled** the `pretty` it would otherwise inherit from a `body`-mapped role — captions and
|
|
432
|
+
footnotes quietly opted out of the thing they most benefit from. The behaviour is unchanged
|
|
433
|
+
(the reset is what makes role classes composable); what changed is that the schema description
|
|
434
|
+
now says so, and the shipped configs state the value on every role rather than leaving it to
|
|
435
|
+
the identity.
|
|
436
|
+
|
|
437
|
+
`vitops init` now scaffolds `balance` on `display`/`heading` and `pretty` on `body`. If you
|
|
438
|
+
already have a `design-system.json`, add `text-wrap` to each role deliberately — `balance` for
|
|
439
|
+
heading-like roles, `pretty` for copy, `wrap` for short single-line labels.
|
|
440
|
+
|
|
441
|
+
**The tailwind format dropped `typography.headings` entirely.** It emitted the
|
|
442
|
+
`@utility font-<role>` half of the typography layer and none of the bare-element bindings, so a
|
|
443
|
+
Tailwind consumer's `<h1>` and `<body>` carried no role styling at all — no family, no size, no
|
|
444
|
+
`text-wrap` — while the css and bricks formats styled them. They are now emitted into Tailwind's
|
|
445
|
+
`base` layer, which keeps `font-<role>` and the patterns able to override them.
|
|
446
|
+
|
|
447
|
+
**This visibly changes existing tailwind sites.** Elements named in your `typography.headings`
|
|
448
|
+
map start picking up their role's typography, which for most projects is the styling they were
|
|
449
|
+
missing — but if you have been compensating with your own `h1`/`body` rules, they now stack.
|
|
450
|
+
Your own unlayered CSS still wins; utilities layered by Tailwind will not. Either drop the
|
|
451
|
+
compensating rules or remove the entry from `typography.headings`.
|
|
452
|
+
|
|
453
|
+
**New utilities** `text-wrap`, `text-nowrap`, `text-balance`, `text-pretty` (css/bricks only —
|
|
454
|
+
Tailwind ships these natively, so they are in `TW_CLASH` and the tailwind bundle drops our
|
|
455
|
+
copies). They are the per-element escape hatch for markup that carries no role class, such as a
|
|
456
|
+
Bricks-authored heading.
|
|
457
|
+
|
|
458
|
+
- Updated dependencies [04a51d8]
|
|
459
|
+
- Updated dependencies [bf453b0]
|
|
460
|
+
- Updated dependencies [bf453b0]
|
|
461
|
+
- @getvitops/utils@2.0.0
|
|
462
|
+
|
|
3
463
|
## 1.0.0
|
|
4
464
|
|
|
5
465
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -10,8 +10,23 @@ turns into platform output and that `@getvitops/bricks` / `@getvitops/astro` bui
|
|
|
10
10
|
- **Browser polyfills** — a feature-detected loader (Anchor Positioning, Popover, Scroll
|
|
11
11
|
Timeline, …) used by both the CSS patterns and the web components.
|
|
12
12
|
|
|
13
|
+
## Most consumers never install this package
|
|
14
|
+
|
|
15
|
+
`@getvitops/astro` copies the JS bundles into your `public/vitops/` and links them from
|
|
16
|
+
`<Head />`, and `@getvitops/generator` inlines the CSS partials into the stylesheet it emits
|
|
17
|
+
from your `design-system.json`. Both reach core through their own dependency, so a normal
|
|
18
|
+
site lists neither the package nor the imports below.
|
|
19
|
+
|
|
13
20
|
## Subpath exports
|
|
14
21
|
|
|
22
|
+
Reach for these only when you're bypassing the integration and the generator. Install it
|
|
23
|
+
directly first — core is otherwise a transitive dependency, and under pnpm (or any strict
|
|
24
|
+
`node_modules` layout) these specifiers won't resolve from your app code:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm i -D @getvitops/core
|
|
28
|
+
```
|
|
29
|
+
|
|
15
30
|
```ts
|
|
16
31
|
import '@getvitops/core/polyfills'; // feature-detected polyfill loader (load high in <head>)
|
|
17
32
|
import '@getvitops/core/elements'; // register the Lit web components
|
|
@@ -22,11 +37,10 @@ import '@getvitops/core/deferred'; // late progressive-enhancement behaviour
|
|
|
22
37
|
@import '@getvitops/core/css/index.css'; /* framework partials (needs generated tokens) */
|
|
23
38
|
```
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
[`@getvitops/generator`](https://www.npmjs.com/package/@getvitops/generator) (via the
|
|
40
|
+
The CSS import in particular is inert on its own: the partials resolve against the token layer
|
|
41
|
+
[`@getvitops/generator`](https://www.npmjs.com/package/@getvitops/generator) emits (via the
|
|
27
42
|
[CLI](https://www.npmjs.com/package/@getvitops/cli) or
|
|
28
|
-
[Vite plugin](https://www.npmjs.com/package/@getvitops/vite))
|
|
29
|
-
from their `design-system.json`, and load these JS bundles for interactivity.
|
|
43
|
+
[Vite plugin](https://www.npmjs.com/package/@getvitops/vite)), so you need that output too.
|
|
30
44
|
|
|
31
45
|
## Changelog
|
|
32
46
|
|