@getvitops/core 1.0.0 → 2.1.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 +468 -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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent — the docked banner `<wc-consent>` reveals.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately thin: this partial owns the *dock* (where the thing sits and how it
|
|
5
|
+
* stacks) and nothing else. Everything inside it is composed from patterns that
|
|
6
|
+
* already exist — `.banner`, `.cluster`, `.btn`, `.cta`, the form patterns — so a
|
|
7
|
+
* consumer restyles their consent banner by restyling those, not by learning a
|
|
8
|
+
* second vocabulary.
|
|
9
|
+
*
|
|
10
|
+
* **Shown in the top layer via the Popover API**, for the same reason `.tooltip`
|
|
11
|
+
* is: a plain `position: fixed` banner resolves against the nearest containing
|
|
12
|
+
* block, and any ancestor with `container-type`, `transform`, `filter` or
|
|
13
|
+
* `contain` becomes one. `body { container-type: inline-size }` is a normal thing
|
|
14
|
+
* to write in a framework whose breakpoints are container queries — and it traps a
|
|
15
|
+
* fixed banner mid-page. The top layer escapes the containing-block chain
|
|
16
|
+
* entirely, so the dock is always the viewport and no z-index can lose to
|
|
17
|
+
* anything. `popover="manual"` (not `auto`): a light-dismiss banner would let a
|
|
18
|
+
* stray click count as a decision.
|
|
19
|
+
*
|
|
20
|
+
* The other reason it's fixed-and-floating: the banner appears after hydration,
|
|
21
|
+
* and out of flow it can't push content, so it contributes nothing to CLS. A
|
|
22
|
+
* statically-positioned bar inserted at the top of the document would shift the
|
|
23
|
+
* whole page and cost the metric the deferred script-loading strategy protects.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/* ── Consent — bottom-docked banner shown while a choice is outstanding ────── */
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Layout only — `display` is set by the open/closed rules below.
|
|
30
|
+
*
|
|
31
|
+
* `[popover].consent` (0-2-0) rather than `.consent` (0-1-0) on purpose: this
|
|
32
|
+
* partial is imported *after* popover.css, whose `[popover] { &:not(:popover-open)
|
|
33
|
+
* { display: none } }` sits at 0-1-0. A bare `.consent { display: flex }` would
|
|
34
|
+
* tie on specificity, win on order, and reveal a closed popover.
|
|
35
|
+
*/
|
|
36
|
+
.consent {
|
|
37
|
+
--_p: var(--p-consent, var(--p-consent-group, var(--p-default)));
|
|
38
|
+
--_br: var(--br-consent, var(--br-consent-group, var(--br-default)));
|
|
39
|
+
--_ds: var(--ds-consent, var(--ds-consent-group, var(--ds-default)));
|
|
40
|
+
--_b: var(--b-consent, var(--b-consent-group, var(--b-default)));
|
|
41
|
+
|
|
42
|
+
position: fixed;
|
|
43
|
+
z-index: 100;
|
|
44
|
+
/* The gap is an inset, not a block margin: for an absolutely positioned box the
|
|
45
|
+
offsets resolve against the margin edge, so `inset-block-end: 0` plus a bottom
|
|
46
|
+
margin still sits flush to the viewport edge. Inline centring does come from
|
|
47
|
+
`margin-inline: auto`, where that resolution is the one we want. */
|
|
48
|
+
inset-block-end: 1rem;
|
|
49
|
+
inset-inline: 0;
|
|
50
|
+
inset-block-start: auto;
|
|
51
|
+
margin-inline: auto;
|
|
52
|
+
max-inline-size: min(48rem, calc(100% - 2rem));
|
|
53
|
+
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 1rem;
|
|
57
|
+
padding: var(--_p);
|
|
58
|
+
border: var(--_b);
|
|
59
|
+
border-radius: var(--_br);
|
|
60
|
+
box-shadow: var(--_ds);
|
|
61
|
+
background: var(--color-bg-surface);
|
|
62
|
+
color: var(--color-text-surface);
|
|
63
|
+
|
|
64
|
+
/* Clear an iOS home indicator. */
|
|
65
|
+
padding-block-end: max(var(--_p), env(safe-area-inset-bottom));
|
|
66
|
+
|
|
67
|
+
.consent__body {
|
|
68
|
+
flex: 1 1 20rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.consent__actions {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
gap: 0.5rem;
|
|
75
|
+
margin-inline-start: auto;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* The per-category checkboxes. Two buttons answer a one-category question, so
|
|
79
|
+
`<CookieConsent />` only renders these when there is more than one — a banner
|
|
80
|
+
that demands reading is a banner that gets accepted unread. */
|
|
81
|
+
.consent__options {
|
|
82
|
+
flex-basis: 100%;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-wrap: wrap;
|
|
85
|
+
gap: 1rem;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Open, in the top layer. */
|
|
90
|
+
[popover].consent:popover-open {
|
|
91
|
+
display: flex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* The no-popover path: `<wc-consent>` strips the attribute when `showPopover` is
|
|
95
|
+
missing and falls back to toggling `hidden`, so this needs no `:popover-open`
|
|
96
|
+
support to work. `[hidden]` comes last and wins over both. */
|
|
97
|
+
.consent:not([popover]) {
|
|
98
|
+
display: flex;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.consent[hidden],
|
|
102
|
+
wc-consent[hidden] {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
107
|
+
.consent {
|
|
108
|
+
animation: consent-enter 0.25s ease-out;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@keyframes consent-enter {
|
|
113
|
+
from {
|
|
114
|
+
opacity: 0;
|
|
115
|
+
translate: 0 1rem;
|
|
116
|
+
}
|
|
117
|
+
}
|
package/css/patterns/details.css
CHANGED
|
@@ -42,7 +42,7 @@ details,
|
|
|
42
42
|
*
|
|
43
43
|
* This carried the canonical open/close animation (`block-size: 0 → auto` with
|
|
44
44
|
* `content-visibility` under `allow-discrete`, backed by the
|
|
45
|
-
* `interpolate-size: allow-keywords` in
|
|
45
|
+
* `interpolate-size: allow-keywords` in patterns/reveal.css). In Chrome 149 that
|
|
46
46
|
* deadlocks: `content-visibility` stays `hidden`, so the content has no layout,
|
|
47
47
|
* so `auto` resolves to `0`, so the transition has nothing to run — and the
|
|
48
48
|
* disclosure never opens *at all*. Not "opens without animating": every
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
DISCLOSURE — animatable show/hide (auto ↔ 0), layered by support
|
|
3
|
+
|
|
4
|
+
Lives in `vitops.components` (the default for an unmapped partial), NOT in
|
|
5
|
+
utilities.css where it used to sit. `.reveal` is a container pattern — three
|
|
6
|
+
rules, a state selector and a child contract — and shelving it as a utility
|
|
7
|
+
meant it beat every pattern AND beat the display utilities, so
|
|
8
|
+
`class="reveal hidden"` stayed visible. The tailwind format has always inlined
|
|
9
|
+
it into `@layer components`; this removes the divergence.
|
|
10
|
+
|
|
11
|
+
The ::details-content half of this family is NOT here: patterns/details.css
|
|
12
|
+
owns it. Two copies existed, and the utilities-layer one was winning purely on
|
|
13
|
+
layer, which is how `overflow: clip` beat details.css's `overflow: hidden`.
|
|
14
|
+
|
|
15
|
+
Layer 1 (primary, broadly supported): CSS grid 0fr↔1fr hack. Needs ONE child
|
|
16
|
+
wrapper; the wrapper clips its own content as the track reveals.
|
|
17
|
+
Layer 2 (progressive): interpolate-size + native height:auto animation.
|
|
18
|
+
Layer 3 (native <details>, animating ::details-content): patterns/details.css.
|
|
19
|
+
`.reveal-fade` covers the display:none fade case with the allow-discrete +
|
|
20
|
+
@starting-style idiom (same as nav.css `.drawer-menu`).
|
|
21
|
+
Toggle: consumer sets `.is-open` / `[data-open]` (mirrors .is-active /
|
|
22
|
+
[data-active] in animation.css), OR nests under an [open] element (<details>).
|
|
23
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
24
|
+
:root {
|
|
25
|
+
--reveal-duration: 0.4s;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── Layer 1: grid-rows hack (primary) ──
|
|
29
|
+
Markup:
|
|
30
|
+
<div class="reveal" id="panel"> <!-- the animated track -->
|
|
31
|
+
<div> … </div> <!-- required single wrapper -->
|
|
32
|
+
</div>
|
|
33
|
+
Toggle .is-open / [data-open] on `.reveal` (JS/Lit), or nest under an [open]
|
|
34
|
+
element (e.g. <details>). The wrapper needs min-height:0 + overflow:hidden so
|
|
35
|
+
the content can be clipped to the 0fr track. */
|
|
36
|
+
.reveal {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-rows: 0fr;
|
|
39
|
+
transition: grid-template-rows var(--reveal-duration) var(--interact-easing, ease);
|
|
40
|
+
}
|
|
41
|
+
.reveal > * {
|
|
42
|
+
min-height: 0;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
}
|
|
45
|
+
.reveal.is-open,
|
|
46
|
+
.reveal[data-open],
|
|
47
|
+
:where([open]) > .reveal {
|
|
48
|
+
grid-template-rows: 1fr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ── Layer 2: native height:auto animation (progressive enhancement) ──
|
|
52
|
+
Where interpolate-size is supported, animate height directly (no wrapper
|
|
53
|
+
needed). Guarded so unsupported engines keep the grid hack above. */
|
|
54
|
+
@supports (interpolate-size: allow-keywords) {
|
|
55
|
+
:root {
|
|
56
|
+
interpolate-size: allow-keywords;
|
|
57
|
+
}
|
|
58
|
+
.reveal-h {
|
|
59
|
+
height: auto;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
transition: height var(--reveal-duration) var(--interact-easing, ease);
|
|
62
|
+
}
|
|
63
|
+
.reveal-h:not(.is-open):not([data-open]) {
|
|
64
|
+
height: 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ── display:none fade/slide — allow-discrete + @starting-style ──
|
|
69
|
+
Same idiom as nav.css `.drawer-menu`. Base is display:none; the open state
|
|
70
|
+
restores display and fades in, and `display`/`overlay` are transitioned with
|
|
71
|
+
allow-discrete so the element animates out before it is removed. `overlay` is
|
|
72
|
+
a no-op unless the element is in the top layer (popover/dialog) — harmless.
|
|
73
|
+
Markup: <div class="reveal-fade" [data-open]?> … </div> (toggle .is-open) */
|
|
74
|
+
.reveal-fade {
|
|
75
|
+
display: none;
|
|
76
|
+
opacity: 0;
|
|
77
|
+
transition:
|
|
78
|
+
opacity var(--reveal-duration) var(--interact-easing, ease),
|
|
79
|
+
overlay var(--reveal-duration) allow-discrete,
|
|
80
|
+
display var(--reveal-duration) allow-discrete;
|
|
81
|
+
}
|
|
82
|
+
.reveal-fade.is-open,
|
|
83
|
+
.reveal-fade[data-open] {
|
|
84
|
+
display: block;
|
|
85
|
+
opacity: 1;
|
|
86
|
+
}
|
|
87
|
+
@starting-style {
|
|
88
|
+
.reveal-fade.is-open,
|
|
89
|
+
.reveal-fade[data-open] {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
package/css/patterns/subgrid.css
CHANGED
|
@@ -48,8 +48,13 @@
|
|
|
48
48
|
.stagger (animation.css): sibling-index() lacks only Firefox as of 2026,
|
|
49
49
|
and where it's missing the margin stays 0 and rows fall back to the grid
|
|
50
50
|
gap, rather than producing an invalid margin. The second selector repeats
|
|
51
|
-
the rule at the specificity rhythm forces above.
|
|
52
|
-
|
|
51
|
+
the rule at the specificity rhythm forces above.
|
|
52
|
+
|
|
53
|
+
The test must name a REAL property: `@supports (--x: sibling-index())` is
|
|
54
|
+
always true, because any token stream is a valid custom-property value —
|
|
55
|
+
it guarded nothing, and on an engine without sibling-index() the margin
|
|
56
|
+
went invalid-at-computed-value-time instead of being skipped. */
|
|
57
|
+
@supports (margin-block-start: calc(sibling-index() * 1px)) {
|
|
53
58
|
> *,
|
|
54
59
|
&:is(ul, ol) > li + li {
|
|
55
60
|
--_row-index: round(down, calc((sibling-index() - 1) / var(--_cols)), 1);
|
|
@@ -120,3 +120,40 @@ wc-theme-editor {
|
|
|
120
120
|
color: var(--color-text-danger);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Editor chrome icons.
|
|
126
|
+
*
|
|
127
|
+
* The editor ships as a standalone JS bundle: no astro-icon, no build-time
|
|
128
|
+
* sprite, no `<Icon />`. That is exactly the case `.icon-mask` exists for — a
|
|
129
|
+
* `currentColor`-filled box driven by a `mask-image`, with the glyph as a data
|
|
130
|
+
* URI. It is the same mechanism `.link--icon::before` uses, and the reason it is
|
|
131
|
+
* worth using here rather than typing a character: `×` and `🎨` are TEXT, so
|
|
132
|
+
* they resolve through font fallback and render differently on every platform
|
|
133
|
+
* (the CTA arrow shipped wrong in Chrome for exactly this reason). A mask has no
|
|
134
|
+
* font to fall back to.
|
|
135
|
+
*
|
|
136
|
+
* Scoped under `wc-theme-editor` like everything else here, so a consumer who
|
|
137
|
+
* never loads the editor bundle pays nothing.
|
|
138
|
+
*/
|
|
139
|
+
wc-theme-editor {
|
|
140
|
+
.ed-icon {
|
|
141
|
+
/* Slightly under the 1.25em default: this chrome runs at --text-xs. */
|
|
142
|
+
--icon-size: 1.1em;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Palette — the launcher. */
|
|
146
|
+
.ed-icon--theme {
|
|
147
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='M200.8 53.9A103.4 103.4 0 0 0 128 24h-1.1A104 104 0 0 0 24 128c0 57.3 46.7 104 104 104a24 24 0 0 0 24-24a23.6 23.6 0 0 0-6.2-16.1a8 8 0 0 1 6-13.3H176a56 56 0 0 0 56-56a102.9 102.9 0 0 0-31.2-68.7M176 152h-24.2a24 24 0 0 0-17.9 40a8 8 0 0 1-5.9 13.3A88 88 0 0 1 40 128a88 88 0 0 1 87.1-88h.9a88 88 0 0 1 88 88a40 40 0 0 1-40 24m-96-16a12 12 0 1 1-12-12a12 12 0 0 1 12 12m0-48a12 12 0 1 1-12-12a12 12 0 0 1 12 12m48-16a12 12 0 1 1-12-12a12 12 0 0 1 12 12m48 16a12 12 0 1 1-12-12a12 12 0 0 1 12 12'/%3E%3C/svg%3E");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Close — a real X, not the multiplication sign a keyboard offers. */
|
|
151
|
+
.ed-icon--close {
|
|
152
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='M205.7 194.3a8 8 0 0 1-11.4 11.4L128 139.3l-66.3 66.4a8 8 0 0 1-11.4-11.4l66.4-66.3l-66.4-66.3a8 8 0 0 1 11.4-11.4l66.3 66.4l66.3-66.4a8 8 0 0 1 11.4 11.4L139.3 128Z'/%3E%3C/svg%3E");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Disclosure caret. `.details-icon` supplies the rotation on [open]. */
|
|
156
|
+
.ed-icon--caret {
|
|
157
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='M181.7 133.7l-80 80a8 8 0 0 1-11.4-11.4L164.7 128L90.3 53.7a8 8 0 0 1 11.4-11.4l80 80a8 8 0 0 1 0 11.4'/%3E%3C/svg%3E");
|
|
158
|
+
}
|
|
159
|
+
}
|
package/css/utilities.css
CHANGED
|
@@ -247,107 +247,28 @@
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
/*
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
`.reveal-fade` covers the display:none fade case with the allow-discrete +
|
|
257
|
-
@starting-style idiom (same as nav.css `.drawer-menu`).
|
|
258
|
-
Toggle: consumer sets `.is-open` / `[data-open]` (mirrors .is-active /
|
|
259
|
-
[data-active] in animation.css), OR nests under an [open] element (<details>).
|
|
260
|
-
═══════════════════════════════════════════════════════════════════════════ */
|
|
261
|
-
:root {
|
|
262
|
-
--reveal-duration: 0.4s;
|
|
263
|
-
}
|
|
250
|
+
/* ── text-wrap — per-element opt-in ──
|
|
251
|
+
The typography ROLES own the default (`text-wrap` is a role key, so every
|
|
252
|
+
`.font-*` and every `typography.headings` tag rule carries one, editable via
|
|
253
|
+
`--<role>-tw` and the theme editor). These four are the escape hatch for a
|
|
254
|
+
single element that wants to differ, and for markup with no role class —
|
|
255
|
+
a Bricks-authored heading, say.
|
|
264
256
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<div class="reveal" id="panel"> <!-- the animated track -->
|
|
268
|
-
<div> … </div> <!-- required single wrapper -->
|
|
269
|
-
</div>
|
|
270
|
-
Toggle .is-open / [data-open] on `.reveal` (JS/Lit), or nest under an [open]
|
|
271
|
-
element (e.g. <details>). The wrapper needs min-height:0 + overflow:hidden so
|
|
272
|
-
the content can be clipped to the 0fr track. */
|
|
273
|
-
.reveal {
|
|
274
|
-
display: grid;
|
|
275
|
-
grid-template-rows: 0fr;
|
|
276
|
-
transition: grid-template-rows var(--reveal-duration) var(--interact-easing, ease);
|
|
277
|
-
}
|
|
278
|
-
.reveal > * {
|
|
279
|
-
min-height: 0;
|
|
280
|
-
overflow: hidden;
|
|
281
|
-
}
|
|
282
|
-
.reveal.is-open,
|
|
283
|
-
.reveal[data-open],
|
|
284
|
-
:where([open]) > .reveal {
|
|
285
|
-
grid-template-rows: 1fr;
|
|
286
|
-
}
|
|
257
|
+
Names + values are Tailwind-exact; Tailwind ships them natively, so they are
|
|
258
|
+
in TW_CLASH and the tailwind format drops these copies.
|
|
287
259
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
:
|
|
293
|
-
interpolate-size: allow-keywords;
|
|
294
|
-
}
|
|
295
|
-
.reveal-h {
|
|
296
|
-
height: auto;
|
|
297
|
-
overflow: hidden;
|
|
298
|
-
transition: height var(--reveal-duration) var(--interact-easing, ease);
|
|
299
|
-
}
|
|
300
|
-
.reveal-h:not(.is-open):not([data-open]) {
|
|
301
|
-
height: 0;
|
|
302
|
-
}
|
|
260
|
+
Namespace note: `text-*` is shared with the role colour utilities
|
|
261
|
+
(`text-danger-muted`). These four can only collide if a consumer names a hue
|
|
262
|
+
or colour role `wrap`/`nowrap`/`balance`/`pretty`. */
|
|
263
|
+
.text-wrap {
|
|
264
|
+
text-wrap: wrap;
|
|
303
265
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Markup:
|
|
307
|
-
<details>
|
|
308
|
-
<summary>Toggle</summary>
|
|
309
|
-
… content …
|
|
310
|
-
</details>
|
|
311
|
-
Animate the UA-generated content box directly; interpolate-size (Layer 2)
|
|
312
|
-
lets block-size reach `auto`. Guarded — engines without ::details-content just
|
|
313
|
-
snap open (fully accessible). */
|
|
314
|
-
/* NOTE: the transition that used to live here is gone, for the same reason as in
|
|
315
|
-
patterns/details.css — in Chrome 149 *any* transition on ::details-content
|
|
316
|
-
deadlocks (content-visibility stays hidden → `auto` resolves to 0 → nothing
|
|
317
|
-
runs) and the disclosure never opens. Collapsing still works; only the motion
|
|
318
|
-
is dropped. Don't reinstate it without checking that a first click expands. */
|
|
319
|
-
@supports selector(::details-content) {
|
|
320
|
-
details::details-content {
|
|
321
|
-
overflow: clip;
|
|
322
|
-
block-size: 0;
|
|
323
|
-
}
|
|
324
|
-
details[open]::details-content {
|
|
325
|
-
block-size: auto;
|
|
326
|
-
}
|
|
266
|
+
.text-nowrap {
|
|
267
|
+
text-wrap: nowrap;
|
|
327
268
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
Same idiom as nav.css `.drawer-menu`. Base is display:none; the open state
|
|
331
|
-
restores display and fades in, and `display`/`overlay` are transitioned with
|
|
332
|
-
allow-discrete so the element animates out before it is removed. `overlay` is
|
|
333
|
-
a no-op unless the element is in the top layer (popover/dialog) — harmless.
|
|
334
|
-
Markup: <div class="reveal-fade" [data-open]?> … </div> (toggle .is-open) */
|
|
335
|
-
.reveal-fade {
|
|
336
|
-
display: none;
|
|
337
|
-
opacity: 0;
|
|
338
|
-
transition:
|
|
339
|
-
opacity var(--reveal-duration) var(--interact-easing, ease),
|
|
340
|
-
overlay var(--reveal-duration) allow-discrete,
|
|
341
|
-
display var(--reveal-duration) allow-discrete;
|
|
342
|
-
}
|
|
343
|
-
.reveal-fade.is-open,
|
|
344
|
-
.reveal-fade[data-open] {
|
|
345
|
-
display: block;
|
|
346
|
-
opacity: 1;
|
|
269
|
+
.text-balance {
|
|
270
|
+
text-wrap: balance;
|
|
347
271
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.reveal-fade[data-open] {
|
|
351
|
-
opacity: 0;
|
|
352
|
-
}
|
|
272
|
+
.text-pretty {
|
|
273
|
+
text-wrap: pretty;
|
|
353
274
|
}
|
package/dist/consent.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=[`necessary`,`analytics`,`marketing`,`preferences`],t=`vitops_consent`,n=[`analytics`,`marketing`,`preferences`];function r(e){return{analytics:e,marketing:e,preferences:e}}function i(){return{decided:!1,ts:null,choices:r(!1)}}function a(e,t){for(let n of e.split(`;`)){let e=n.trim();if(e.slice(0,t.length+1)===`${t}=`)return e.slice(t.length+1)}return null}function o(e){if(!e)return i();let t;try{t=JSON.parse(decodeURIComponent(e))}catch{return i()}if(typeof t!=`object`||!t)return i();let a=t;if(a.v!==1||typeof a.c!=`object`||a.c===null)return i();let o=a.c,s=r(!1);for(let e of n)s[e]=o[e]===!0;return{decided:!0,ts:typeof a.ts==`number`?a.ts:null,choices:s}}function s(e){return encodeURIComponent(JSON.stringify({v:1,ts:e.ts??0,c:e.choices}))}function c(e,t){return t===`necessary`?!0:e.decided&&e.choices[t]===!0}function l(e,t){return{decided:!0,ts:t,choices:r(e)}}function u(e,t,r){let i={...e.choices};for(let e of n){let n=t[e];typeof n==`boolean`&&(i[e]=n)}return{decided:!0,ts:r,choices:i}}function d(e,t){return n.filter(n=>c(e,n)&&!c(t,n))}function f(e){return`; path=/; max-age=15552000; SameSite=Lax${e?`; Secure`:``}`}const p=`vitops:consent`,m=`vitops:consent-open`,h=location.protocol===`https:`;let g=o(a(document.cookie,t));const _=new Set,v=new WeakSet,y=new Set,b={idle:[],async:[],interaction:[]},x={idle:!1,async:!0,interaction:!1};function S(e){if(x[e])return;x[e]=!0;let t=b[e];b[e]=[];for(let e of t)e()}function C(e,t){x[e]?t():b[e].push(t)}function w(e){document.readyState===`complete`?e():addEventListener(`load`,e,{once:!0})}w(()=>{let e=globalThis.requestIdleCallback;e?e(()=>S(`idle`),{timeout:3e3}):setTimeout(()=>S(`idle`),1)});{let e=[`pointerdown`,`keydown`,`scroll`,`touchstart`],t=()=>{for(let n of e)removeEventListener(n,t);S(`interaction`)};for(let n of e)addEventListener(n,t,{once:!0,passive:!0});w(()=>setTimeout(()=>S(`interaction`),8e3))}const T=new Set([`type`,`src`,`data-vitops-tag`,`data-src`,`data-strategy`,`data-consent`,`data-consent-src`,`data-consent-cookies`]);function E(t){let n=t.getAttribute(`data-consent`);return e.includes(n??``)?n:`analytics`}function D(e){let t=e.textContent??``,n=e.getAttribute(`data-src`),r=t=>{let n=document.createElement(`script`);for(let{name:t,value:r}of Array.from(e.attributes))T.has(t)||n.setAttribute(t,r);t(n),e.parentNode?.insertBefore(n,e.nextSibling)};t.trim()&&r(e=>e.text=t),n&&r(e=>{e.async=!0,e.src=n})}function O(e){if(!v.has(e)){if(v.add(e),y.add(E(e)),e instanceof HTMLScriptElement)D(e);else{let t=e.getAttribute(`data-consent-src`);t&&e.setAttribute(`src`,t)}e.setAttribute(`data-vitops-activated`,``)}}function k(){let e=document.querySelectorAll(`script[data-vitops-tag]:not([data-vitops-activated]),[data-consent-src]:not([data-vitops-activated])`);for(let t of e){if(!c(g,E(t)))continue;let e=t.getAttribute(`data-strategy`);C(e===`async`||e===`interaction`?e:`idle`,()=>O(t))}}function A(e){let t=new Set;for(let n of e)for(let e of document.querySelectorAll(`[data-consent="${n}"]`))for(let n of(e.getAttribute(`data-consent-cookies`)??``).split(`,`))n.trim()&&t.add(n.trim());if(!t.size)return;let n=document.cookie.split(`;`).map(e=>e.trim().split(`=`)[0]??``),r=location.hostname.split(`.`).slice(-2).join(`.`),i=[``,`; domain=${location.hostname}`,`; domain=.${r}`];for(let e of n)if([...t].some(t=>t.endsWith(`*`)?e.startsWith(t.slice(0,-1)):e===t))for(let t of i)document.cookie=`${e}=; path=/; max-age=0${t}`}function j(e,n){let r=d(g,e),i=g;g=e,n?document.cookie=`${t}=${s(e)}${f(h)}`:document.cookie=`${t}=; path=/; max-age=0`,M(),k(),r.length&&(A(r),r.some(e=>y.has(e)&&c(i,e))&&N.reloadOnRevoke&&location.reload())}function M(){for(let e of _)e(g);document.dispatchEvent(new CustomEvent(p,{detail:g}))}const N={categories:e,reloadOnRevoke:!0,get:()=>g,granted:e=>c(g,e),needed:()=>!g.decided,set:e=>j(u(g,e,Date.now()),!0),acceptAll:()=>j(l(!0,Date.now()),!0),rejectAll:()=>j(l(!1,Date.now()),!0),reset:()=>j(i(),!1),open:()=>document.dispatchEvent(new CustomEvent(m)),subscribe(e){return _.add(e),e(g),()=>void _.delete(e)}};window.vitopsConsent=N,document.addEventListener(`click`,e=>{e.target?.closest?.(`[data-consent-open]`)&&(e.preventDefault(),N.open())}),document.addEventListener(`astro:page-load`,k),k(),M();const P=[`analytics`,`marketing`,`preferences`];var F=class extends HTMLElement{#e;get#t(){return window.vitopsConsent}get#n(){return this.querySelector(`[popover]`)}connectedCallback(){let e=this.#t;if(!e)return;let t=this.#n;t&&typeof t.showPopover!=`function`&&t.removeAttribute(`popover`),this.getAttribute(`reload-on-revoke`)===`false`&&(e.reloadOnRevoke=!1),this.addEventListener(`click`,this.#c),this.addEventListener(`submit`,this.#i),document.addEventListener(m,this.#r),this.#e=e.subscribe(()=>{e.needed()?this.#a():this.#o()})}disconnectedCallback(){this.removeEventListener(`click`,this.#c),this.removeEventListener(`submit`,this.#i),document.removeEventListener(m,this.#r),this.#e?.()}#r=()=>this.#a();#i=e=>e.preventDefault();#a(){this.#s(),this.hidden=!1;let e=this.#n;e?.isConnected&&!e.matches(`:popover-open`)&&e.showPopover()}#o(){this.#n?.hidePopover?.(),this.hidden=!0}#s(){let e=this.#t;if(e)for(let t of P){let n=this.querySelector(`input[name="${t}"]`);n&&(n.checked=e.granted(t))}}#c=e=>{let t=this.#t,n=e.target?.closest?.(`[data-consent-accept],[data-consent-reject],[data-consent-save]`);!t||!n||(e.preventDefault(),n.hasAttribute(`data-consent-accept`)?t.acceptAll():n.hasAttribute(`data-consent-reject`)?t.rejectAll():t.set(this.#l()),this.#o())};#l(){let e={};for(let t of P)e[t]=this.querySelector(`input[name="${t}"]`)?.checked===!0;return e}};customElements.get(`wc-consent`)||customElements.define(`wc-consent`,F);export{e as CATEGORIES,p as CONSENT_EVENT,m as CONSENT_OPEN_EVENT,t as COOKIE_NAME,N as consent};
|
package/dist/deferred.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e){let t=[...e.children];parseFloat(getComputedStyle(e).getPropertyValue(`--stagger-amount`));let n=[],r=e.getBoundingClientRect().top;t.forEach(e=>{let t=Math.round(e.getBoundingClientRect().top-r),i=n.find(e=>Math.abs(e.top-t)<10);i||(i={top:t,items:[]},n.push(i)),i.items.push(e)}),n.forEach(({items:e})=>{e.forEach((e,t)=>{e.style.setProperty(`--_stagger-index`,String(t))})})}let t;function n(){clearTimeout(t),t=setTimeout(()=>{requestAnimationFrame(()=>{document.querySelectorAll(`.stagger`).forEach(e)})},100)}const r=new IntersectionObserver((e,t)=>{e.forEach(e=>{e.isIntersecting&&(e.target.classList.add(`is-active`),t.unobserve(e.target))})});function i(e){let t=e.querySelector(`.horizontal-scroll__track`);if(!t)return;let n=Array.from(t.children).reduce((e,t)=>e+t.offsetWidth,0);t.style.width=`${n}px`}document.addEventListener(`click`,e=>{let t=e.target.closest?.(`a[href^="#"]`);if(!t)return;let n=t.closest(`dialog[open]`);n&&n.close()}),window.addEventListener(`load`,()=>{requestAnimationFrame(()=>{document.querySelectorAll(`.stagger`).forEach(e),window.addEventListener(`resize`,n),document.querySelectorAll(`.horizontal-scroll`).forEach(i),document.querySelectorAll(`.animate-trigger`).forEach(e=>{r.observe(e)});let t=new Date().getFullYear();document.querySelectorAll(`[data-current-year]`).forEach(e=>{e.textContent=t.toString()})})});
|
|
1
|
+
function e(e){let t=[...e.children];parseFloat(getComputedStyle(e).getPropertyValue(`--stagger-amount`));let n=[],r=e.getBoundingClientRect().top;t.forEach(e=>{let t=Math.round(e.getBoundingClientRect().top-r),i=n.find(e=>Math.abs(e.top-t)<10);i||(i={top:t,items:[]},n.push(i)),i.items.push(e)}),n.forEach(({items:e})=>{e.forEach((e,t)=>{e.style.setProperty(`--_stagger-index`,String(t))})})}let t;function n(){clearTimeout(t),t=setTimeout(()=>{requestAnimationFrame(()=>{document.querySelectorAll(`.stagger`).forEach(e)})},100)}const r=new IntersectionObserver((e,t)=>{e.forEach(e=>{if(!e.isIntersecting)return;let n=e.rootBounds,r=e.boundingClientRect;(!n||r.height>=n.height||r.top+r.height/2<=n.bottom)&&(e.target.classList.add(`is-active`),t.unobserve(e.target))})},{rootMargin:`0px 0px -10% 0px`,threshold:[0,.25,.5,.75,1]});function i(e){let t=e.querySelector(`.horizontal-scroll__track`);if(!t)return;let n=Array.from(t.children).reduce((e,t)=>e+t.offsetWidth,0);t.style.width=`${n}px`}document.addEventListener(`click`,e=>{let t=e.target.closest?.(`a[href^="#"]`);if(!t)return;let n=t.closest(`dialog[open]`);n&&n.close()}),window.addEventListener(`load`,()=>{requestAnimationFrame(()=>{document.querySelectorAll(`.stagger`).forEach(e),window.addEventListener(`resize`,n),document.querySelectorAll(`.horizontal-scroll`).forEach(i),document.querySelectorAll(`.animate-trigger`).forEach(e=>{r.observe(e)});let t=new Date().getFullYear();document.querySelectorAll(`[data-current-year]`).forEach(e=>{e.textContent=t.toString()})})});
|
package/dist/editor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
const e=`vitops:ds-overrides`,t=`/__vitops/design-system`,n=(e,t)=>{let n=t.indexOf(`-`),r=n===-1?t:t.slice(0,n),i=n===-1?``:t.slice(n+1);return i===`on`?`--color-${r}-on-${e}`:i.startsWith(`on-`)?`--color-${r}-on-${e}-${i.slice(3)}`:i?`--color-${r}-${e}-${i}`:`--color-${r}-${e}`},r=(e,t)=>`--color-${e}-${t}`;var i=class extends HTMLElement{constructor(...e){super(...e),this.store={light:new Map,dark:new Map},this.roles=new Map,this.roleVars=new Set,this.syncers=new Set,this.scheme=`light`,this.canSave=!1}static{this.observedAttributes=[`manifest`]}connectedCallback(){this.boot()}async boot(){let e=this.getAttribute(`manifest`)??`/vitops/design-manifest.json`;try{let t=await fetch(e);if(!t.ok)throw Error(`manifest ${t.status}`);this.manifest=await t.json()}catch(t){console.warn(`[wc-theme-editor] manifest unavailable at ${e} — editor disabled`,t);return}this.styleEl=document.createElement(`style`),this.styleEl.id=`ds-overrides`,document.head.append(this.styleEl),this.hydrate(),this.render(),this.scheme=this.currentScheme(),new MutationObserver(()=>{let e=this.currentScheme();e!==this.scheme&&(this.scheme=e,this.syncers.forEach(e=>e()))}).observe(document.documentElement,{attributes:!0,attributeFilter:[`data-theme`]}),this.canSave=await this.probeSave(),this.build(this.manifest)}async probeSave(){try{let e=await fetch(t);return e.ok?(await e.json()).writable===!0:!1}catch{return!1}}block(e,t){return t.size?`${e} {\n${[...t].map(([e,t])=>` ${e}: ${t};`).join(`
|
|
2
|
-
`)}\n}\n`:``}serializeCSS(){return this.block(`:root`,this.store.light)+this.block(`:root[data-brx-theme="dark"], :root[data-theme="dark"]`,this.store.dark)}render(){this.styleEl&&(this.styleEl.textContent=this.serializeCSS())}persist(){try{localStorage.setItem(e,JSON.stringify({light:[...this.store.light],dark:[...this.store.dark],roles:[...this.roles]}))}catch{}}hydrate(){try{let t=localStorage.getItem(e);if(!t)return;let n=JSON.parse(t);this.store.light=new Map(n.light??[]),this.store.dark=new Map(n.dark??[]),this.roles=new Map(n.roles??[]);for(let[e,t]of this.roles)this.markRoleVars(e,t)}catch{}}setOverride(e,t,n){let r=this.store[n];t==null||t===``?r.delete(e):r.set(e,t),this.render(),this.persist()}effective(e,t,n){return this.store[t].has(e)?this.store[t].get(e):t===`dark`&&this.store.light.has(e)?this.store.light.get(e):n}reset(){this.store.light.clear(),this.store.dark.clear(),this.roles.clear(),this.roleVars.clear(),this.render(),this.persist(),this.syncers.forEach(e=>e())}currentScheme(){let e=document.documentElement;return e.dataset.theme===`dark`||e.getAttribute(`data-brx-theme`)===`dark`?`dark`:`light`}setScheme(e){this.scheme=e,document.documentElement.dataset.theme=e,document.documentElement.style.colorScheme=`${e} only`;try{localStorage.setItem(`vitops-color-scheme`,e)}catch{}this.syncers.forEach(e=>e())}roleTokenNames(e,t,r){let i=e.colors.roles.find(e=>e.name===t)?.kind??`chromatic`,a=e.colors.roleTokens?.[r]?.[i];if(!a)return[];let o=[];for(let e of[`light`,`dark`])for(let[r,i]of Object.entries(a[e]))o.push([n(t,r),i,e]);return o}markRoleVars(e,t){if(this.manifest)for(let[n]of this.roleTokenNames(this.manifest,e,t))this.roleVars.add(n)}remapRole(e,t,n){let r=this.roleTokenNames(e,t,n);if(r.length){this.roles.set(t,n);for(let[e,t,n]of r)this.roleVars.add(e),this.store[n].set(e,t);this.render(),this.persist()}}deepSet(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e++){let t=r[e];(typeof i[t]!=`object`||i[t]==null)&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}buildPatch(e){let t={},n=[];for(let[n,r]of this.roles){let i=e.colors.roles.find(e=>e.name===n)?.kind??`chromatic`;this.deepSet(t,`colors.roles.${n}`,{hue:r,kind:i})}for(let[r,i]of this.store.light){if(this.roleVars.has(r))continue;let a=e.reverseIndex[r];a?this.deepSet(t,a,i):n.push(r)}for(let e of this.store.dark.keys())this.roleVars.has(e)||n.push(`${e} (dark)`);return{patch:t,skipped:n}}el(e,t={},n=[]){let r=document.createElement(e),i=r;for(let[e,n]of Object.entries(t))n===!0?i.setAttribute(e,``):n!==!1&&i.setAttribute(e,String(n));for(let e of n)i.append(e);return r}row(e,t,n){return this.syncers.add(n),n(),this.el(`label`,{class:`ed-row`},[this.el(`span`,{class:`ed-label`,title:e},[e.replace(/^--/,``)]),t])}textInput(e,t,n=``){let r=this.el(`input`,{type:`text`,class:`ed-input`});return r.addEventListener(`input`,()=>this.setOverride(e,r.value.trim()||null,this.scheme)),this.row(e,r,()=>{r.value=this.store[this.scheme].get(e)??``,r.placeholder=n||t})}colorInput(e,t){let n=this.el(`input`,{type:`color`,class:`ed-color`}),r=this.el(`input`,{type:`text`,class:`ed-input ed-mono`}),i=t=>this.setOverride(e,t||null,`light`);n.addEventListener(`input`,()=>{r.value=n.value,i(n.value)}),r.addEventListener(`input`,()=>{/^#[0-9a-f]{6}$/i.test(r.value)&&(n.value=r.value),i(r.value.trim())});let a=this.el(`span`,{class:`ed-pair`},[n,r]);return this.row(e,a,()=>{let i=this.store.light.get(e)??t;r.value=i,/^#[0-9a-f]{6}$/i.test(i)&&(n.value=i)})}selectInput(e,t){let n=this.el(`select`,{class:`ed-input`});n.append(this.el(`option`,{value:``},[`— default —`]));for(let e of t)n.append(this.el(`option`,{value:e.value},[e.label]));return n.addEventListener(`change`,()=>this.setOverride(e,n.value||null,this.scheme)),this.row(e,n,()=>{n.value=this.store[this.scheme].get(e)??``})}remSlider(e,t){let n=parseFloat(t)||1,r=this.el(`input`,{type:`range`,class:`ed-range`,min:`0`,max:String(Math.max(4,Math.ceil(n*2))),step:`0.05`}),i=this.el(`output`,{class:`ed-out`});r.addEventListener(`input`,()=>{i.textContent=`${r.value}rem`,this.setOverride(e,`${r.value}rem`,this.scheme)});let a=this.el(`span`,{class:`ed-pair`},[r,i]);return this.row(e,a,()=>{r.value=String(parseFloat(this.effective(e,this.scheme,t))||n),i.textContent=this.store[this.scheme].get(e)??`auto`})}slider(e,t){let n=t.toValue??(e=>`${e}${t.unit??``}`),r=t.fromValue??(e=>Number.isNaN(parseFloat(e))?t.def:parseFloat(e)),i=this.el(`input`,{type:`range`,class:`ed-range`,min:String(t.min),max:String(t.max),step:String(t.step)}),a=this.el(`output`,{class:`ed-out`});i.addEventListener(`input`,()=>{let t=n(parseFloat(i.value));a.textContent=t,this.setOverride(e,t,this.scheme)});let o=this.el(`span`,{class:`ed-pair`},[i,a]);return this.row(e,o,()=>{let n=this.store[this.scheme].get(e);i.value=String(n==null?t.def:r(n)),a.textContent=n??`${t.def}${t.unit??``}`})}section(e,t=!1){let n=this.el(`div`,{class:`ed-body rhythm`});return{details:this.el(`details`,{class:`details details--marker-start ed-section`,...t?{open:!0}:{}},[this.el(`summary`,{class:`font-eyebrow`},[this.el(`span`,{class:`details-icon`,"aria-hidden":`true`},[`▸`]),e]),n]),body:n}}hint(e){return this.el(`p`,{class:`font-footnote text-surface-x-muted`},[e])}sub(e){let t=this.section(e);return t.details.classList.add(`ed-sub`),t}buildPalette(e){let{details:t,body:n}=this.section(`Palette`,!0);n.append(this.hint(`Hues feed the semantic roles below — edit a hue and every role mapped to it follows. Hue edits are scheme-global: dark mode re-points which step a token reads, it never redefines the hue itself.`));for(let t of e.colors.ramps){let i=this.sub(t);for(let n of e.colors.steps){let a=e.colors.palette[t]?.[n];a!=null&&i.body.append(this.colorInput(r(t,n),a))}n.append(i.details)}return n.append(this.buildRoles(e)),t}buildRoles(e){let{details:t,body:n}=this.sub(`Semantic roles`);n.append(this.hint(`Point a role at another hue — its whole functional token set (bg / border / solid / on-solid / text) is rewritten for both appearances.`));let r=e.colors.ramps;for(let{name:t,ramp:i}of e.colors.roles){let a=this.el(`select`,{class:`ed-input`});for(let e of r)a.append(this.el(`option`,{value:e},[e]));a.addEventListener(`change`,()=>this.remapRole(e,t,a.value)),n.append(this.row(`--${t}`,a,()=>{a.value=this.roles.get(t)??i}))}return t}buildTypography(e){let{details:t,body:n}=this.section(`Typography`),r=Object.keys(e.fonts).map(e=>({label:e,value:`var(--font-${e})`})),i=e.scales.text.map(e=>({label:e.name,value:`var(--text-${e.name})`})),a={tt:[`none`,`uppercase`,`lowercase`,`capitalize`],fst:[`normal`,`italic`,`oblique`],td:[`none`,`underline`,`line-through`],tw:[`wrap`,`balance`,`pretty`,`nowrap`]};for(let t of e.typography.roles){let o=e.typography.specs[t]??{},s=this.sub(t);for(let[n,{key:c}]of Object.entries(e.typography.hooks)){let e=`--${t}-${n}`,l=o[c]==null?``:String(o[c]);n===`ff`?s.body.append(this.selectInput(e,r)):n===`fs`?s.body.append(this.selectInput(e,i)):a[n]?s.body.append(this.selectInput(e,a[n].map(e=>({label:e,value:e})))):s.body.append(this.textInput(e,l,l||c))}n.append(s.details)}return t}buildSpacing(e){let{details:t,body:n}=this.section(`Spacing`);n.append(this.hint(`Each step overrides to a fixed rem (dropping the fluid clamp). Reset restores it.`));for(let t of e.scales.space)n.append(this.remSlider(`--space-${t.name}`,t.max));return t}buildLayout(){let{details:e,body:t}=this.section(`Layout`);return t.append(this.hint(`Vertical rhythm and the .centered track widths — global structure.`)),t.append(this.slider(`--rhythm-scale`,{min:.5,max:2.5,step:.05,def:1}),this.slider(`--width-measure`,{min:30,max:110,step:1,def:65,unit:`ch`}),this.slider(`--width-breakout`,{min:40,max:150,step:1,def:90,unit:`ch`}),this.slider(`--width-spotlight`,{min:60,max:200,step:1,def:120,unit:`ch`}),this.slider(`--gutter`,{min:0,max:6,step:.1,def:3,toValue:e=>`clamp(1rem, 4cqi, ${e}rem)`,fromValue:e=>{let t=/clamp\([^,]+,[^,]+,\s*([\d.]+)/.exec(e);return t?parseFloat(t[1]):3}})),e}buildPatterns(e){let t=this.section(`Pattern defaults`);for(let[n,r]of Object.entries(e.patterns.defaults))t.body.append(this.textInput(`--${n}-default`,r,r));let n=this.section(`Radii`);for(let[t,r]of Object.entries(e.patterns.radii))n.body.append(this.textInput(`--br-${t}`,r,r));let r=this.section(`Pattern groups`);for(let[t,n]of Object.entries(e.patterns.groups)){let e=this.sub(t);for(let[r,i]of Object.entries(n))e.body.append(this.textInput(`--${r}-${t}`,i,i));r.body.append(e.details)}let i=this.section(`Components`);i.body.append(this.hint(`Per-component geometry — overrides the group defaults for just this component.`));for(let t of e.patterns.items){if(!t.wrappable.length)continue;let e=this.sub(t.name);for(let{sfx:n,prop:r}of t.wrappable){let i=t.base[r]??``;e.body.append(this.textInput(`--${n}-${t.name}`,i,i))}i.body.append(e.details)}let a=this.section(`Shadows`);for(let[t,n]of Object.entries(e.shadows))a.body.append(this.textInput(`--shadow-${t}`,n,n));return[t.details,n.details,r.details,i.details,a.details]}async flash(e,t){let n=e.textContent;e.textContent=t,setTimeout(()=>e.textContent=n,1400)}async copy(e,t){try{await navigator.clipboard.writeText(e),this.flash(t,`Copied ✓`)}catch{this.flash(t,`Copy failed`)}}async save(e,n){let{patch:r}=this.buildPatch(e);if(!Object.keys(r).length)return void this.flash(n,`Nothing to save`);try{let e=await fetch(t,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({patch:r})});if(!e.ok){let t=await e.json().catch(()=>({}));console.error(`[wc-theme-editor] save rejected`,t.errors??e.statusText),this.flash(n,`Rejected ✗`);return}this.reset(),this.flash(n,`Saved ✓`)}catch(e){console.error(`[wc-theme-editor] save failed`,e),this.flash(n,`Failed ✗`)}}buildToolbar(e){let t=this.el(`div`,{class:`cluster-start ed-toolbar`}),n=this.el(`button`,{type:`button`,class:`btn ed-seg`},[`Light`]),r=this.el(`button`,{type:`button`,class:`btn ed-seg`},[`Dark`]),i=()=>{n.classList.toggle(`is-active`,this.scheme===`light`),r.classList.toggle(`is-active`,this.scheme===`dark`)};this.syncers.add(i),n.addEventListener(`click`,()=>this.setScheme(`light`)),r.addEventListener(`click`,()=>this.setScheme(`dark`)),i(),t.append(this.el(`span`,{class:`ed-segwrap`,role:`group`},[n,r]));let a=this.el(`button`,{type:`button`,class:`btn`},[`Copy CSS`]);a.addEventListener(`click`,()=>this.copy(this.serializeCSS()||`/* no overrides yet */`,a));let o=this.el(`button`,{type:`button`,class:`btn`},[`Copy JSON patch`]);if(o.addEventListener(`click`,()=>{let{patch:t,skipped:n}=this.buildPatch(e),r=n.length?`\n// runtime-only (no source mapping): ${n.join(`, `)}`:``;this.copy(JSON.stringify(t,null,2)+r,o)}),t.append(a,o),this.canSave){let n=this.el(`button`,{type:`button`,class:`cta`},[`Save to source`]);n.addEventListener(`click`,()=>void this.save(e,n)),t.append(n)}let s=this.el(`button`,{type:`button`,class:`btn ed-danger`},[`Reset all`]);return s.addEventListener(`click`,()=>this.reset()),t.append(s),t}build(e){let t=`ds-editor-panel`,n=this.el(`div`,{id:t,popover:`manual`,class:`drawer drawer--right drawer--modeless ed-panel bg-surface text-surface rhythm`,"aria-label":`Design-system editor`});n.append(this.el(`header`,{class:`cluster-between`},[this.el(`strong`,{class:`font-heading`},[`Theme editor`]),(()=>{let e=this.el(`button`,{type:`button`,class:`btn`,popovertarget:t,popovertargetaction:`hide`},[`×`]);return e.setAttribute(`aria-label`,`Close editor`),e})()]),this.buildToolbar(e),this.buildPalette(e),this.buildTypography(e),this.buildSpacing(e),this.buildLayout(),...this.buildPatterns(e));let r=this.el(`button`,{type:`button`,class:`cta ed-launch`,popovertarget:t},[`🎨 Theme`]);document.addEventListener(`keydown`,e=>{e.key===`Escape`&&n.matches(`:popover-open`)&&n.hidePopover()}),this.append(r,n)}};customElements.get(`wc-theme-editor`)||customElements.define(`wc-theme-editor`,i);
|
|
2
|
+
`)}\n}\n`:``}serializeCSS(){return this.block(`:root`,this.store.light)+this.block(`:root[data-brx-theme="dark"], :root[data-theme="dark"]`,this.store.dark)}render(){this.styleEl&&(this.styleEl.textContent=this.serializeCSS())}persist(){try{localStorage.setItem(e,JSON.stringify({light:[...this.store.light],dark:[...this.store.dark],roles:[...this.roles]}))}catch{}}hydrate(){try{let t=localStorage.getItem(e);if(!t)return;let n=JSON.parse(t);this.store.light=new Map(n.light??[]),this.store.dark=new Map(n.dark??[]),this.roles=new Map(n.roles??[]);for(let[e,t]of this.roles)this.markRoleVars(e,t)}catch{}}setOverride(e,t,n){let r=this.store[n];t==null||t===``?r.delete(e):r.set(e,t),this.render(),this.persist()}effective(e,t,n){return this.store[t].has(e)?this.store[t].get(e):t===`dark`&&this.store.light.has(e)?this.store.light.get(e):n}reset(){this.store.light.clear(),this.store.dark.clear(),this.roles.clear(),this.roleVars.clear(),this.render(),this.persist(),this.syncers.forEach(e=>e())}currentScheme(){let e=document.documentElement;return e.dataset.theme===`dark`||e.getAttribute(`data-brx-theme`)===`dark`?`dark`:`light`}setScheme(e){this.scheme=e,document.documentElement.dataset.theme=e,document.documentElement.style.colorScheme=`${e} only`;try{localStorage.setItem(`vitops-color-scheme`,e)}catch{}this.syncers.forEach(e=>e())}roleTokenNames(e,t,r){let i=e.colors.roles.find(e=>e.name===t)?.kind??`chromatic`,a=e.colors.roleTokens?.[r]?.[i];if(!a)return[];let o=[];for(let e of[`light`,`dark`])for(let[r,i]of Object.entries(a[e]))o.push([n(t,r),i,e]);return o}markRoleVars(e,t){if(this.manifest)for(let[n]of this.roleTokenNames(this.manifest,e,t))this.roleVars.add(n)}remapRole(e,t,n){let r=this.roleTokenNames(e,t,n);if(r.length){this.roles.set(t,n);for(let[e,t,n]of r)this.roleVars.add(e),this.store[n].set(e,t);this.render(),this.persist()}}deepSet(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e++){let t=r[e];(typeof i[t]!=`object`||i[t]==null)&&(i[t]={}),i=i[t]}i[r[r.length-1]]=n}buildPatch(e){let t={},n=[];for(let[n,r]of this.roles){let i=e.colors.roles.find(e=>e.name===n)?.kind??`chromatic`;this.deepSet(t,`colors.roles.${n}`,{hue:r,kind:i})}for(let[r,i]of this.store.light){if(this.roleVars.has(r))continue;let a=e.reverseIndex[r];a?this.deepSet(t,a,i):n.push(r)}for(let e of this.store.dark.keys())this.roleVars.has(e)||n.push(`${e} (dark)`);return{patch:t,skipped:n}}el(e,t={},n=[]){let r=document.createElement(e),i=r;for(let[e,n]of Object.entries(t))n===!0?i.setAttribute(e,``):n!==!1&&i.setAttribute(e,String(n));for(let e of n)i.append(e);return r}row(e,t,n){return this.syncers.add(n),n(),this.el(`label`,{class:`ed-row`},[this.el(`span`,{class:`ed-label`,title:e},[e.replace(/^--/,``)]),t])}textInput(e,t,n=``){let r=this.el(`input`,{type:`text`,class:`ed-input`});return r.addEventListener(`input`,()=>this.setOverride(e,r.value.trim()||null,this.scheme)),this.row(e,r,()=>{r.value=this.store[this.scheme].get(e)??``,r.placeholder=n||t})}colorInput(e,t){let n=this.el(`input`,{type:`color`,class:`ed-color`}),r=this.el(`input`,{type:`text`,class:`ed-input ed-mono`}),i=t=>this.setOverride(e,t||null,`light`);n.addEventListener(`input`,()=>{r.value=n.value,i(n.value)}),r.addEventListener(`input`,()=>{/^#[0-9a-f]{6}$/i.test(r.value)&&(n.value=r.value),i(r.value.trim())});let a=this.el(`span`,{class:`ed-pair`},[n,r]);return this.row(e,a,()=>{let i=this.store.light.get(e)??t;r.value=i,/^#[0-9a-f]{6}$/i.test(i)&&(n.value=i)})}selectInput(e,t){let n=this.el(`select`,{class:`ed-input`});n.append(this.el(`option`,{value:``},[`— default —`]));for(let e of t)n.append(this.el(`option`,{value:e.value},[e.label]));return n.addEventListener(`change`,()=>this.setOverride(e,n.value||null,this.scheme)),this.row(e,n,()=>{n.value=this.store[this.scheme].get(e)??``})}remSlider(e,t){let n=parseFloat(t)||1,r=this.el(`input`,{type:`range`,class:`ed-range`,min:`0`,max:String(Math.max(4,Math.ceil(n*2))),step:`0.05`}),i=this.el(`output`,{class:`ed-out`});r.addEventListener(`input`,()=>{i.textContent=`${r.value}rem`,this.setOverride(e,`${r.value}rem`,this.scheme)});let a=this.el(`span`,{class:`ed-pair`},[r,i]);return this.row(e,a,()=>{r.value=String(parseFloat(this.effective(e,this.scheme,t))||n),i.textContent=this.store[this.scheme].get(e)??`auto`})}slider(e,t){let n=t.toValue??(e=>`${e}${t.unit??``}`),r=t.fromValue??(e=>Number.isNaN(parseFloat(e))?t.def:parseFloat(e)),i=this.el(`input`,{type:`range`,class:`ed-range`,min:String(t.min),max:String(t.max),step:String(t.step)}),a=this.el(`output`,{class:`ed-out`});i.addEventListener(`input`,()=>{let t=n(parseFloat(i.value));a.textContent=t,this.setOverride(e,t,this.scheme)});let o=this.el(`span`,{class:`ed-pair`},[i,a]);return this.row(e,o,()=>{let n=this.store[this.scheme].get(e);i.value=String(n==null?t.def:r(n)),a.textContent=n??`${t.def}${t.unit??``}`})}section(e,t=!1){let n=this.el(`div`,{class:`ed-body rhythm`});return{details:this.el(`details`,{class:`details details--marker-start ed-section`,...t?{open:!0}:{}},[this.el(`summary`,{class:`font-eyebrow`},[this.el(`span`,{class:`icon-mask details-icon ed-icon ed-icon--caret`,"aria-hidden":`true`}),e]),n]),body:n}}hint(e){return this.el(`p`,{class:`font-footnote text-surface-x-muted`},[e])}sub(e){let t=this.section(e);return t.details.classList.add(`ed-sub`),t}buildPalette(e){let{details:t,body:n}=this.section(`Palette`,!0);n.append(this.hint(`Hues feed the semantic roles below — edit a hue and every role mapped to it follows. Hue edits are scheme-global: dark mode re-points which step a token reads, it never redefines the hue itself.`));for(let t of e.colors.ramps){let i=this.sub(t);for(let n of e.colors.steps){let a=e.colors.palette[t]?.[n];a!=null&&i.body.append(this.colorInput(r(t,n),a))}n.append(i.details)}return n.append(this.buildRoles(e)),t}buildRoles(e){let{details:t,body:n}=this.sub(`Semantic roles`);n.append(this.hint(`Point a role at another hue — its whole functional token set (bg / border / solid / on-solid / text) is rewritten for both appearances.`));let r=e.colors.ramps;for(let{name:t,ramp:i}of e.colors.roles){let a=this.el(`select`,{class:`ed-input`});for(let e of r)a.append(this.el(`option`,{value:e},[e]));a.addEventListener(`change`,()=>this.remapRole(e,t,a.value)),n.append(this.row(`--${t}`,a,()=>{a.value=this.roles.get(t)??i}))}return t}buildTypography(e){let{details:t,body:n}=this.section(`Typography`),r=Object.keys(e.fonts).map(e=>({label:e,value:`var(--font-${e})`})),i=e.scales.text.map(e=>({label:e.name,value:`var(--text-${e.name})`})),a={tt:[`none`,`uppercase`,`lowercase`,`capitalize`],fst:[`normal`,`italic`,`oblique`],td:[`none`,`underline`,`line-through`],tw:[`wrap`,`balance`,`pretty`,`nowrap`]};for(let t of e.typography.roles){let o=e.typography.specs[t]??{},s=this.sub(t);for(let[n,{key:c}]of Object.entries(e.typography.hooks)){let e=`--${t}-${n}`,l=o[c]==null?``:String(o[c]);n===`ff`?s.body.append(this.selectInput(e,r)):n===`fs`?s.body.append(this.selectInput(e,i)):a[n]?s.body.append(this.selectInput(e,a[n].map(e=>({label:e,value:e})))):s.body.append(this.textInput(e,l,l||c))}n.append(s.details)}return t}buildSpacing(e){let{details:t,body:n}=this.section(`Spacing`);n.append(this.hint(`Each step overrides to a fixed rem (dropping the fluid clamp). Reset restores it.`));for(let t of e.scales.space)n.append(this.remSlider(`--space-${t.name}`,t.max));return t}buildLayout(){let{details:e,body:t}=this.section(`Layout`);return t.append(this.hint(`Vertical rhythm and the .centered track widths — global structure.`)),t.append(this.slider(`--rhythm-scale`,{min:.5,max:2.5,step:.05,def:1}),this.slider(`--width-measure`,{min:30,max:110,step:1,def:65,unit:`ch`}),this.slider(`--width-breakout`,{min:40,max:150,step:1,def:90,unit:`ch`}),this.slider(`--width-spotlight`,{min:60,max:200,step:1,def:120,unit:`ch`}),this.slider(`--gutter`,{min:0,max:6,step:.1,def:3,toValue:e=>`clamp(1rem, 4cqi, ${e}rem)`,fromValue:e=>{let t=/clamp\([^,]+,[^,]+,\s*([\d.]+)/.exec(e);return t?parseFloat(t[1]):3}})),e}buildPatterns(e){let t=this.section(`Pattern defaults`);for(let[n,r]of Object.entries(e.patterns.defaults))t.body.append(this.textInput(`--${n}-default`,r,r));let n=this.section(`Radii`);for(let[t,r]of Object.entries(e.patterns.radii))n.body.append(this.textInput(`--br-${t}`,r,r));let r=this.section(`Pattern groups`);for(let[t,n]of Object.entries(e.patterns.groups)){let e=this.sub(t);for(let[r,i]of Object.entries(n))e.body.append(this.textInput(`--${r}-${t}`,i,i));r.body.append(e.details)}let i=this.section(`Components`);i.body.append(this.hint(`Per-component geometry — overrides the group defaults for just this component.`));for(let t of e.patterns.items){if(!t.wrappable.length)continue;let e=this.sub(t.name);for(let{sfx:n,prop:r}of t.wrappable){let i=t.base[r]??``;e.body.append(this.textInput(`--${n}-${t.name}`,i,i))}i.body.append(e.details)}let a=this.section(`Shadows`);for(let[t,n]of Object.entries(e.shadows))a.body.append(this.textInput(`--shadow-${t}`,n,n));return[t.details,n.details,r.details,i.details,a.details]}async flash(e,t){let n=e.textContent;e.textContent=t,setTimeout(()=>e.textContent=n,1400)}async copy(e,t){try{await navigator.clipboard.writeText(e),this.flash(t,`Copied ✓`)}catch{this.flash(t,`Copy failed`)}}async save(e,n){let{patch:r}=this.buildPatch(e);if(!Object.keys(r).length)return void this.flash(n,`Nothing to save`);try{let e=await fetch(t,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({patch:r})});if(!e.ok){let t=await e.json().catch(()=>({}));console.error(`[wc-theme-editor] save rejected`,t.errors??e.statusText),this.flash(n,`Rejected ✗`);return}this.reset(),this.flash(n,`Saved ✓`)}catch(e){console.error(`[wc-theme-editor] save failed`,e),this.flash(n,`Failed ✗`)}}buildToolbar(e){let t=this.el(`div`,{class:`cluster-start ed-toolbar`}),n=this.el(`button`,{type:`button`,class:`btn ed-seg`},[`Light`]),r=this.el(`button`,{type:`button`,class:`btn ed-seg`},[`Dark`]),i=()=>{n.classList.toggle(`is-active`,this.scheme===`light`),r.classList.toggle(`is-active`,this.scheme===`dark`)};this.syncers.add(i),n.addEventListener(`click`,()=>this.setScheme(`light`)),r.addEventListener(`click`,()=>this.setScheme(`dark`)),i(),t.append(this.el(`span`,{class:`ed-segwrap`,role:`group`},[n,r]));let a=this.el(`button`,{type:`button`,class:`btn`},[`Copy CSS`]);a.addEventListener(`click`,()=>this.copy(this.serializeCSS()||`/* no overrides yet */`,a));let o=this.el(`button`,{type:`button`,class:`btn`},[`Copy JSON patch`]);if(o.addEventListener(`click`,()=>{let{patch:t,skipped:n}=this.buildPatch(e),r=n.length?`\n// runtime-only (no source mapping): ${n.join(`, `)}`:``;this.copy(JSON.stringify(t,null,2)+r,o)}),t.append(a,o),this.canSave){let n=this.el(`button`,{type:`button`,class:`cta`},[`Save to source`]);n.addEventListener(`click`,()=>void this.save(e,n)),t.append(n)}let s=this.el(`button`,{type:`button`,class:`btn ed-danger`},[`Reset all`]);return s.addEventListener(`click`,()=>this.reset()),t.append(s),t}build(e){let t=`ds-editor-panel`,n=this.el(`div`,{id:t,popover:`manual`,class:`drawer drawer--right drawer--modeless ed-panel bg-surface text-surface rhythm`,"aria-label":`Design-system editor`});n.append(this.el(`header`,{class:`cluster-between`},[this.el(`strong`,{class:`font-heading`},[`Theme editor`]),(()=>{let e=this.el(`button`,{type:`button`,class:`btn`,popovertarget:t,popovertargetaction:`hide`},[this.el(`span`,{class:`icon-mask ed-icon ed-icon--close`,"aria-hidden":`true`})]);return e.setAttribute(`aria-label`,`Close editor`),e})()]),this.buildToolbar(e),this.buildPalette(e),this.buildTypography(e),this.buildSpacing(e),this.buildLayout(),...this.buildPatterns(e));let r=this.el(`button`,{type:`button`,class:`cta ed-launch`,popovertarget:t},[this.el(`span`,{class:`icon-mask ed-icon ed-icon--theme`,"aria-hidden":`true`}),`Theme`]);document.addEventListener(`keydown`,e=>{e.key===`Escape`&&n.matches(`:popover-open`)&&n.hidePopover()}),this.append(r,n)}};customElements.get(`wc-theme-editor`)||customElements.define(`wc-theme-editor`,i);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/core",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The Vitops design-system framework — CSS partials, Lit web components, and browser polyfills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"exports": {
|
|
13
13
|
"./elements": "./dist/elements.js",
|
|
14
|
+
"./consent": "./dist/consent.js",
|
|
14
15
|
"./editor": "./dist/editor.js",
|
|
15
16
|
"./polyfills": "./dist/polyfills.js",
|
|
16
17
|
"./deferred": "./dist/deferred.js",
|
|
@@ -31,6 +32,9 @@
|
|
|
31
32
|
"lit": "^3.3.3",
|
|
32
33
|
"scroll-timeline-polyfill": "^1.1.0",
|
|
33
34
|
"temporal-polyfill": "^1.0.1",
|
|
34
|
-
"@getvitops/utils": "1.0
|
|
35
|
+
"@getvitops/utils": "2.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"vitest": "^4.1.9"
|
|
35
39
|
}
|
|
36
40
|
}
|