@getvitops/core 8.0.1 → 8.2.0-alpha.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 +174 -10
- package/css/layout-utilities.css +13 -0
- package/css/layout.css +31 -0
- package/css/patterns/banner.css +7 -0
- package/css/patterns/cluster.css +19 -0
- package/css/patterns/comment.css +18 -0
- package/css/patterns/gallery.css +16 -0
- package/css/patterns/list.css +17 -0
- package/css/patterns/marquee.css +6 -0
- package/css/patterns/masonry.css +18 -0
- package/dist/consent.js +1 -1
- package/dist/deferred.js +1 -1
- package/dist/editor.js +2 -2
- package/dist/elements.js +4 -4
- package/dist/polyfills/adoptedStyleSheets.js +1 -1
- package/dist/polyfills/css-anchor-positioning-fn.js +4 -4
- package/dist/polyfills/global.esm.js +1 -1
- package/dist/polyfills/invokers-polyfill.js +1 -1
- package/dist/polyfills/popover.js +2 -2
- package/dist/polyfills/webcomponents-bundle.js +9 -9
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,164 @@
|
|
|
1
1
|
# @getvitops/core
|
|
2
2
|
|
|
3
|
+
## 8.2.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
8
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
9
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
10
|
+
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
11
|
+
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
12
|
+
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
13
|
+
`page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
|
|
14
|
+
secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
|
|
15
|
+
reaches the git-tracked `site.json` through your repo's normal review path rather than
|
|
16
|
+
becoming a second, unreviewed source of truth. Until that's configured, the live half still
|
|
17
|
+
works; only the auto-commit half is skipped (and says so in the admin page). The save route
|
|
18
|
+
runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
|
|
19
|
+
synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
|
|
20
|
+
|
|
21
|
+
Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
|
|
22
|
+
EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
|
|
23
|
+
Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
|
|
24
|
+
auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
|
|
25
|
+
plugin-install time.
|
|
26
|
+
|
|
27
|
+
**Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
|
|
28
|
+
capability is now also granted whenever `designSystemEditor` is set, independent of the
|
|
29
|
+
existing `scripts: 'fragments'` option (which governs something unrelated — web-component
|
|
30
|
+
script delivery). No change if you don't use `designSystemEditor`.
|
|
31
|
+
|
|
32
|
+
Also new, supporting the above:
|
|
33
|
+
- `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
|
|
34
|
+
`/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
|
|
35
|
+
`design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
|
|
36
|
+
standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
|
|
37
|
+
into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
|
|
38
|
+
`mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
|
|
39
|
+
(re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
|
|
40
|
+
subtree" implementation shared by the dev endpoint and the new plugin route. New
|
|
41
|
+
`site.deployment.repo`/`branch` config fields.
|
|
42
|
+
- `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
|
|
43
|
+
wrapper over GitHub's Contents API.
|
|
44
|
+
- `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
|
|
45
|
+
unchanged — `/__vitops/design-system`) so the same element can target the new plugin
|
|
46
|
+
route instead of the dev-only Vite endpoint.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
|
|
51
|
+
both projects' GA releases.
|
|
52
|
+
|
|
53
|
+
**Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
|
|
54
|
+
`emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
|
|
55
|
+
`^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
|
|
56
|
+
the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
|
|
57
|
+
code here needed to change, only the declared range.
|
|
58
|
+
|
|
59
|
+
The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
|
|
60
|
+
the version bump:
|
|
61
|
+
- `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
|
|
62
|
+
`css.format: 'tailwind'` without installing either, which threw on build.
|
|
63
|
+
- `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
|
|
64
|
+
`vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
|
|
65
|
+
the build never generated.
|
|
66
|
+
- `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
|
|
67
|
+
`site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
|
|
68
|
+
migration sequence explicitly, rather than relying solely on first-request auto-migration. This
|
|
69
|
+
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
70
|
+
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
71
|
+
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
72
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
73
|
+
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
74
|
+
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
75
|
+
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
76
|
+
Uncomment it once scaffolded into its own repo.
|
|
77
|
+
|
|
78
|
+
No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
|
|
79
|
+
to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
|
|
80
|
+
losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
|
|
81
|
+
now names the bad key instead of reporting a bare "Invalid input").
|
|
82
|
+
|
|
83
|
+
Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
|
|
84
|
+
(`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
|
|
85
|
+
has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
|
|
86
|
+
by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
|
|
87
|
+
hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
|
|
88
|
+
`postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
|
|
89
|
+
`node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
|
|
90
|
+
hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
|
|
91
|
+
(the same path `astro sync` would generate for real). Both are gitignored and only ever exist
|
|
92
|
+
inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
|
|
93
|
+
Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
|
|
94
|
+
evolving default) and making the template a real pnpm workspace member (would change how its
|
|
95
|
+
`@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
|
|
96
|
+
|
|
97
|
+
**Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
|
|
98
|
+
`AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
|
|
99
|
+
`.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
|
|
100
|
+
repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
|
|
101
|
+
compose with vite-plus's own `defineConfig`). A type-level shim
|
|
102
|
+
(`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
|
|
103
|
+
runtime `vp test` failure for this one file is still open.
|
|
104
|
+
|
|
105
|
+
## 8.1.0
|
|
106
|
+
|
|
107
|
+
### Minor Changes
|
|
108
|
+
|
|
109
|
+
- **Added — `.no-rhythm` utility (all formats).**
|
|
110
|
+
|
|
111
|
+
A `<ul>`/`<ol>` inside a `.rhythm` container picks up a top margin on every item but the first
|
|
112
|
+
(`.rhythm :where(ul, ol) > li + li`), which is correct for prose but wrong for a list whose item
|
|
113
|
+
spacing is already a `gap`-based grid or flex wrap the framework has no pattern for. `.gallery`,
|
|
114
|
+
`.tag-list`, `.cluster`, `.masonry`, `.comment-thread`, `.subgrid` and `.grid-auto` already
|
|
115
|
+
neutralise this automatically; `.no-rhythm` is the same escape hatch for a consumer's own custom
|
|
116
|
+
list — add it alongside your own class (`<ul class="my-cards no-rhythm">`) to opt out. It wins by
|
|
117
|
+
cascade layer (`vitops.utilities`, later than every pattern's `vitops.components`), the same
|
|
118
|
+
guarantee a colour utility uses to beat a pattern's own background — no selector tricks needed on
|
|
119
|
+
your end.
|
|
120
|
+
|
|
121
|
+
### Patch Changes
|
|
122
|
+
|
|
123
|
+
- **Fixed — `.banner[hidden]` now actually hides (all formats).**
|
|
124
|
+
|
|
125
|
+
`.banner`'s own `display: flex` and the UA `[hidden] { display: none }` rule are both 0-1-0
|
|
126
|
+
specificity, and the author stylesheet wins that tie by source order — so `<p class="banner" hidden>`
|
|
127
|
+
rendered visibly instead of hiding. Added an explicit `.banner[hidden] { display: none }` override.
|
|
128
|
+
No markup change required.
|
|
129
|
+
|
|
130
|
+
- **Added — `vitops init --site` and `vitops setup`, the scripted client-onboarding path.**
|
|
131
|
+
|
|
132
|
+
`vitops init --site [--name] [--url]` scaffolds a starter `site.json` (the umbrella `Config`,
|
|
133
|
+
embedding a starter design system at `designSystem.themes.default`) instead of a bare
|
|
134
|
+
`design-system.json` — exported as `defaultSiteConfig()` from `@getvitops/generator`, alongside a
|
|
135
|
+
new `CONFIG_SCHEMA_LOCAL_PATH` and an optional `$schema` field on `ConfigSchema` so a scaffolded
|
|
136
|
+
config gets editor autocomplete the same way `design-system.json` already does.
|
|
137
|
+
|
|
138
|
+
`vitops setup <repo|hosting|domain|search|ads|all>` (new `@getvitops/utils/hosting` subpath: a
|
|
139
|
+
pure planner + `wrangler`/`gh`-shelling executors, following the same idempotent
|
|
140
|
+
plan/observe/apply split as `onboarding`/`ads`) automates GitHub repo + branch policy and
|
|
141
|
+
Cloudflare dev D1/R2 + repo secrets, and plans/checks the workers.dev-vs-custom-domain
|
|
142
|
+
consistency without ever applying the cutover itself — see AGENTS.md's new "Client onboarding"
|
|
143
|
+
section for the full design rationale. `search`/`ads` forward to the existing `search setup`/
|
|
144
|
+
`ads setup` subcommands rather than reimplementing them.
|
|
145
|
+
|
|
146
|
+
No consumer-facing change in `@getvitops/vite` or `@getvitops/astro` in this release — bumped
|
|
147
|
+
only because they are in the `fixed` release group with `generator`/`utils`/`cli`.
|
|
148
|
+
`@getvitops/core` does have a real fix in this release; see the separate `banner-hidden-fix`
|
|
149
|
+
changeset.
|
|
150
|
+
|
|
151
|
+
- **Fixed — five patterns rendered as `<ul>`/`<ol>` kept the UA list styles (all formats).**
|
|
152
|
+
|
|
153
|
+
`.gallery`, `.tag-list`, `.cluster`, `.masonry` and `.comment-thread` never neutralised the
|
|
154
|
+
browser's default list padding/margin/markers, so `<Gallery />`'s generated `<ul role="list">`
|
|
155
|
+
(and any hand-authored `<ul>` carrying one of the other four classes) rendered with a
|
|
156
|
+
40px-ish inline padding and a bullet on every item. Each now resets `margin`/`padding`/
|
|
157
|
+
`list-style` behind a `:is(ul, ol)` gate — conditional so the class stays usable on a `div` —
|
|
158
|
+
and neutralises `.rhythm :where(ul, ol) > li + li`'s sibling margin the same way `.subgrid` and
|
|
159
|
+
`.grid-auto` already do, so an item inside a prose container doesn't pick up a stray top
|
|
160
|
+
margin that offsets the first grid cell alone. No markup change required.
|
|
161
|
+
|
|
3
162
|
## 8.0.1
|
|
4
163
|
|
|
5
164
|
## 8.0.0
|
|
@@ -11,7 +170,6 @@
|
|
|
11
170
|
|
|
12
171
|
**Breaking (`css`/`bricks` and `tailwind` formats — `.lightbox-dialog`'s markup
|
|
13
172
|
contract changed):**
|
|
14
|
-
|
|
15
173
|
- `<Gallery />` now emits ONE `<dialog class="lightbox-dialog">` per gallery,
|
|
16
174
|
wrapping a `<Carousel />`, instead of one dialog per image. A `.gallery__trigger`
|
|
17
175
|
is now a plain `<a href="#slide-id">`, not a `command="show-modal"` button.
|
|
@@ -52,7 +210,6 @@
|
|
|
52
210
|
### Minor Changes
|
|
53
211
|
|
|
54
212
|
- Add Google Business Profile reviews: fetch, cache, render.
|
|
55
|
-
|
|
56
213
|
- **`vitops reviews`** (new CLI command) fetches a client's GBP reviews (legacy v4 API — the only
|
|
57
214
|
one that has them) and writes them to `.vitops/reviews.json`, meant to be committed so a build
|
|
58
215
|
never needs a live Google credential. `--dry` prints the plan; `--check` diffs against the cache
|
|
@@ -87,7 +244,6 @@
|
|
|
87
244
|
`--site` for now.)
|
|
88
245
|
|
|
89
246
|
**Fixed:**
|
|
90
|
-
|
|
91
247
|
- `@getvitops/core`'s 12 runtime deps (`lit`, both `@oddbird` polyfills, both
|
|
92
248
|
`@webcomponents` packages, `colorjs.io`, etc.) were listed as `dependencies` despite
|
|
93
249
|
being bundled into `dist/elements.js`/`dist/polyfills.js`/`dist/deferred.js` — a
|
|
@@ -822,20 +978,28 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
822
978
|
{
|
|
823
979
|
"designSystem": {
|
|
824
980
|
"themes": {
|
|
825
|
-
"default": {
|
|
981
|
+
"default": {
|
|
982
|
+
/* … */
|
|
983
|
+
},
|
|
826
984
|
},
|
|
827
985
|
},
|
|
828
986
|
"organization": {
|
|
829
987
|
"name": "Acme",
|
|
830
988
|
"contact": "hq",
|
|
831
989
|
"locations": {
|
|
832
|
-
"hq": {
|
|
990
|
+
"hq": {
|
|
991
|
+
/* … */
|
|
992
|
+
},
|
|
833
993
|
},
|
|
834
994
|
},
|
|
835
995
|
"site": {
|
|
836
996
|
"defaultLocale": "en",
|
|
837
|
-
"domains": {
|
|
838
|
-
|
|
997
|
+
"domains": {
|
|
998
|
+
/* … */
|
|
999
|
+
},
|
|
1000
|
+
"analytics": {
|
|
1001
|
+
/* … */
|
|
1002
|
+
},
|
|
839
1003
|
},
|
|
840
1004
|
}
|
|
841
1005
|
```
|
|
@@ -1073,8 +1237,8 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
|
|
|
1073
1237
|
|
|
1074
1238
|
```js
|
|
1075
1239
|
vitops({
|
|
1076
|
-
analytics: { googleAnalytics:
|
|
1077
|
-
consent: { policyUrl:
|
|
1240
|
+
analytics: { googleAnalytics: 'G-XXXXXXXXXX', plausible: 'acme.com' },
|
|
1241
|
+
consent: { policyUrl: '/legal/cookies' },
|
|
1078
1242
|
});
|
|
1079
1243
|
```
|
|
1080
1244
|
|
|
@@ -1698,7 +1862,7 @@ inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting
|
|
|
1698
1862
|
```js
|
|
1699
1863
|
// astro.config.mjs
|
|
1700
1864
|
getvitops({
|
|
1701
|
-
css: { input:
|
|
1865
|
+
css: { input: 'design-system.json', format: 'css', out: 'src/styles' },
|
|
1702
1866
|
editor: true,
|
|
1703
1867
|
});
|
|
1704
1868
|
```
|
package/css/layout-utilities.css
CHANGED
|
@@ -270,6 +270,19 @@
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/* Opt out of `.rhythm`'s ambient `li + li` sibling margin (layout.css) for a list
|
|
274
|
+
whose item spacing is already the layout's job — a custom `gap`-based grid or
|
|
275
|
+
flex wrap the framework has no pattern for. Wins by LAYER (this file is
|
|
276
|
+
`vitops.utilities`, later than `.rhythm`'s `vitops.components`), the same
|
|
277
|
+
guarantee `bg-danger-muted` uses to beat `.card` — no specificity trick
|
|
278
|
+
needed, unlike every pattern-local `:is(ul, ol) > li + li` neutraliser above
|
|
279
|
+
and in patterns/*.css. The five framework list patterns (gallery, tag-list,
|
|
280
|
+
cluster, masonry, comment-thread) already do this automatically and don't
|
|
281
|
+
need this class; reach for it on markup the framework doesn't own. */
|
|
282
|
+
.no-rhythm > li + li {
|
|
283
|
+
margin-top: 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
273
286
|
/* ── Flex direction (display + direction; tiered classes set both) ── */
|
|
274
287
|
.flex {
|
|
275
288
|
display: flex;
|
package/css/layout.css
CHANGED
|
@@ -133,6 +133,37 @@
|
|
|
133
133
|
.rhythm > :where(ul, ol) + p {
|
|
134
134
|
margin-top: calc(var(--rhythm-base) * var(--rhythm-list-p));
|
|
135
135
|
}
|
|
136
|
+
/* The only DESCENDANT rule in this block (every other rule above is `>`-scoped to
|
|
137
|
+
direct children) — it reaches into a list at any depth, including one nested
|
|
138
|
+
inside a pattern several levels down. That is deliberate: it is the correct
|
|
139
|
+
default for prose, and prose lists can be arbitrarily nested.
|
|
140
|
+
|
|
141
|
+
It is also 0-1-2, not 0-1-0 like the rest of this file's `:where()`-guarded
|
|
142
|
+
rules — `li + li` sits OUTSIDE the `:where()`, so it carries real weight. A
|
|
143
|
+
pattern whose item spacing is already its own job (a `gap`-based grid or flex
|
|
144
|
+
wrap: `.gallery`, `.tag-list`, `.cluster`, `.masonry`, `.comment-thread`,
|
|
145
|
+
`.subgrid`, `.grid-auto`) neutralises this LOCALLY, in its own partial, with
|
|
146
|
+
`&:is(ul, ol) > li + li { margin-block-start: 0; }` — `:is(ul, ol)` pushes it
|
|
147
|
+
to 0-1-3, safely past this rule's 0-1-2 regardless of source order. Consumer
|
|
148
|
+
markup gets the same escape hatch as a public utility: `.no-rhythm`
|
|
149
|
+
(layout-utilities.css), which wins by LAYER instead, since it lives in
|
|
150
|
+
`vitops.utilities`.
|
|
151
|
+
|
|
152
|
+
Two alternatives were considered and rejected, worth recording so neither is
|
|
153
|
+
re-proposed:
|
|
154
|
+
- Centralising the exclusion HERE, as `:where(ul, ol):not(:where(.gallery,
|
|
155
|
+
.subgrid, …))`. Rejected: it re-opens exactly the registry the heading rules
|
|
156
|
+
above were rewritten to delete (see the comment there) — an enumeration that
|
|
157
|
+
is always one pattern short, unlike a closed set like "what a heading is".
|
|
158
|
+
It also fails SILENTLY (a missing name renders a stray margin, nothing
|
|
159
|
+
errors) where the local form fails a build (`bundle-layers.test.ts`'s
|
|
160
|
+
per-pattern drift guard), and this file's `.rhythm` block is hand-mirrored
|
|
161
|
+
into a tailwind template literal (`generate.ts`) that has already drifted
|
|
162
|
+
once — a second selector list to keep in step, not a cleanup.
|
|
163
|
+
- Zeroing `--rhythm-li-li` locally instead of overriding `margin-top`. It
|
|
164
|
+
inherits (declared on `:root`), so it would also zero the rhythm of any
|
|
165
|
+
PROSE list nested inside the opted-out pattern — losing the exact behaviour
|
|
166
|
+
the descendant combinator above exists to provide. */
|
|
136
167
|
.rhythm :where(ul, ol) > li + li {
|
|
137
168
|
margin-top: calc(var(--rhythm-base) * var(--rhythm-li-li));
|
|
138
169
|
}
|
package/css/patterns/banner.css
CHANGED
|
@@ -62,3 +62,10 @@
|
|
|
62
62
|
flex-shrink: 0;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
/* `.banner`'s own `display: flex` and the UA `[hidden] { display: none }` are
|
|
67
|
+
both 0-1-0 — a tie the author sheet wins on source order, so a hidden
|
|
68
|
+
banner would render anyway. Name `[hidden]` explicitly so it always wins. */
|
|
69
|
+
.banner[hidden] {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
package/css/patterns/cluster.css
CHANGED
|
@@ -13,6 +13,25 @@
|
|
|
13
13
|
gap: var(--cluster-gap, 0.5rem);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
17
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
18
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
19
|
+
so only the author can restore it. Conditional so `.cluster` on a `div` is
|
|
20
|
+
untouched; scoped to `.cluster` alone — the `cluster-*` attribute selectors above
|
|
21
|
+
are alignment modifiers, not the list itself. */
|
|
22
|
+
.cluster:is(ul, ol) {
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
list-style: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Item spacing is the wrap's job, not the flow's — neutralise `.rhythm`'s
|
|
29
|
+
`li + li` margin (see layout.css for why this is local rather than
|
|
30
|
+
centralised). */
|
|
31
|
+
.cluster:is(ul, ol) > li + li {
|
|
32
|
+
margin-block-start: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
.cluster-start-start {
|
|
17
36
|
place-content: start start;
|
|
18
37
|
}
|
package/css/patterns/comment.css
CHANGED
|
@@ -85,4 +85,22 @@
|
|
|
85
85
|
display: flex;
|
|
86
86
|
flex-direction: column;
|
|
87
87
|
gap: 1.5rem;
|
|
88
|
+
|
|
89
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
90
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
91
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
92
|
+
so only the author can restore it. Conditional so `.comment-thread` on a `div`
|
|
93
|
+
is untouched. */
|
|
94
|
+
&:is(ul, ol) {
|
|
95
|
+
margin: 0;
|
|
96
|
+
padding: 0;
|
|
97
|
+
list-style: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Item spacing is the flex `gap` above's job, not the flow's — neutralise
|
|
101
|
+
`.rhythm`'s `li + li` margin (see layout.css for why this is local rather
|
|
102
|
+
than centralised). */
|
|
103
|
+
&:is(ul, ol) > li + li {
|
|
104
|
+
margin-block-start: 0;
|
|
105
|
+
}
|
|
88
106
|
}
|
package/css/patterns/gallery.css
CHANGED
|
@@ -27,6 +27,22 @@ wc-gallery {
|
|
|
27
27
|
display: grid;
|
|
28
28
|
grid-template-columns: repeat(auto-fill, minmax(var(--gallery-thumb-size, 150px), 1fr));
|
|
29
29
|
gap: var(--gallery-gap, 0.5rem);
|
|
30
|
+
|
|
31
|
+
/* Gallery.astro emits this as `<ul role="list">`, so the UA's list padding, block
|
|
32
|
+
margin and markers all apply. The pattern owns no outer spacing — the context it
|
|
33
|
+
sits in does. Conditional so `.gallery` on a `div` is untouched. */
|
|
34
|
+
&:is(ul, ol) {
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: 0;
|
|
37
|
+
list-style: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Item spacing is the grid's job, not the flow's — neutralise `.rhythm`'s
|
|
41
|
+
`li + li` margin (see layout.css for why this is local rather than
|
|
42
|
+
centralised). */
|
|
43
|
+
&:is(ul, ol) > li + li {
|
|
44
|
+
margin-block-start: 0;
|
|
45
|
+
}
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
.gallery__item {
|
package/css/patterns/list.css
CHANGED
|
@@ -244,6 +244,23 @@
|
|
|
244
244
|
align-items: center;
|
|
245
245
|
font-size: var(--_fs);
|
|
246
246
|
|
|
247
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
248
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
249
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
250
|
+
so only the author can restore it. Conditional so `.tag-list` on a `div` is
|
|
251
|
+
untouched — `--_p` already overrides the UA padding either way. */
|
|
252
|
+
&:is(ul, ol) {
|
|
253
|
+
margin: 0;
|
|
254
|
+
list-style: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Item spacing is the wrap's job, not the flow's — neutralise `.rhythm`'s
|
|
258
|
+
`li + li` margin (see layout.css for why this is local rather than
|
|
259
|
+
centralised). */
|
|
260
|
+
&:is(ul, ol) > li + li {
|
|
261
|
+
margin-block-start: 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
247
264
|
.tag-list__input {
|
|
248
265
|
flex: 1;
|
|
249
266
|
min-inline-size: 6rem;
|
package/css/patterns/marquee.css
CHANGED
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
* `100% + gap`, because the seam between copies is one more gap that has to be
|
|
18
18
|
* consumed for the loop to be seamless — translate by a bare `-100%` and the
|
|
19
19
|
* items visibly bunch up once per revolution.
|
|
20
|
+
*
|
|
21
|
+
* `Marquee.astro` authors THREE `.marquee__content` copies (the slotted one
|
|
22
|
+
* plus two `aria-hidden`), not two — a very wide track under the 100% floor
|
|
23
|
+
* needs more than one duplicate to stay covered. `<wc-marquee>` treats the
|
|
24
|
+
* first as its source and discards the rest, replacing them with however many
|
|
25
|
+
* its own measurement calls for.
|
|
20
26
|
*/
|
|
21
27
|
|
|
22
28
|
.marquee {
|
package/css/patterns/masonry.css
CHANGED
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
.masonry {
|
|
8
8
|
columns: var(--masonry-columns, 3);
|
|
9
9
|
|
|
10
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
11
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
12
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
13
|
+
so only the author can restore it. Conditional so `.masonry` on a `div` is
|
|
14
|
+
untouched. */
|
|
15
|
+
&:is(ul, ol) {
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
list-style: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Item spacing is the columns/masonry layout's job, not the flow's —
|
|
22
|
+
neutralise `.rhythm`'s `li + li` margin (see layout.css for why this is
|
|
23
|
+
local rather than centralised). */
|
|
24
|
+
&:is(ul, ol) > li + li {
|
|
25
|
+
margin-block-start: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
> * {
|
|
11
29
|
break-inside: avoid;
|
|
12
30
|
}
|
package/dist/consent.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=[`necessary`,`analytics`,`marketing`,`preferences`],t=`vitops:consent`,n=`vitops:consent-open`,r=`vitops_consent`,i=[`analytics`,`marketing`,`preferences`];function a(e){return{analytics:e,marketing:e,preferences:e}}function o(){return{ts:null,choices:a(null)}}function s(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 c(e){if(!e)return o();let t;try{t=JSON.parse(decodeURIComponent(e))}catch{return o()}if(typeof t!=`object`||!t)return o();let n=t;if(n.v!==2||typeof n.c!=`object`||n.c===null)return o();let r=n.c,s=a(null);for(let e of i){let t=r[e];typeof t==`boolean`&&(s[e]=t)}return{ts:typeof n.ts==`number`?n.ts:null,choices:s}}function l(e){let t={};for(let n of i){let r=e.choices[n];typeof r==`boolean`&&(t[n]=r)}return encodeURIComponent(JSON.stringify({v:2,ts:e.ts??0,c:t}))}function u(e,t){return t===`necessary
|
|
1
|
+
const e=[`necessary`,`analytics`,`marketing`,`preferences`],t=`vitops:consent`,n=`vitops:consent-open`,r=`vitops_consent`,i=[`analytics`,`marketing`,`preferences`];function a(e){return{analytics:e,marketing:e,preferences:e}}function o(){return{ts:null,choices:a(null)}}function s(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 c(e){if(!e)return o();let t;try{t=JSON.parse(decodeURIComponent(e))}catch{return o()}if(typeof t!=`object`||!t)return o();let n=t;if(n.v!==2||typeof n.c!=`object`||n.c===null)return o();let r=n.c,s=a(null);for(let e of i){let t=r[e];typeof t==`boolean`&&(s[e]=t)}return{ts:typeof n.ts==`number`?n.ts:null,choices:s}}function l(e){let t={};for(let n of i){let r=e.choices[n];typeof r==`boolean`&&(t[n]=r)}return encodeURIComponent(JSON.stringify({v:2,ts:e.ts??0,c:t}))}function u(e,t){return t===`necessary`||e.choices[t]===!0}function d(e,t){return t===`necessary`||e.choices[t]!==null}function f(e,t){for(let n of t)if(!d(e,n))return!0;return!1}function p(e,t){return{ts:t,choices:a(e)}}function m(e,t,n){let r={...e.choices};for(let e of i){let n=t[e];typeof n==`boolean`&&(r[e]=n)}return{ts:n,choices:r}}function h(e,t){return i.filter(n=>u(e,n)&&!u(t,n))}function g(e){return`; path=/; max-age=15552000; SameSite=Lax${e?`; Secure`:``}`}const _=location.protocol===`https:`;let v=c(s(document.cookie,r));const y=/* @__PURE__ */ new Set,b=/* @__PURE__ */ new WeakSet,x=/* @__PURE__ */ new Set,S=/* @__PURE__ */ new Set,C=/* @__PURE__ */ new Map,w=/* @__PURE__ */ new Set;function T(e){w.has(e)||(w.add(e),queueMicrotask(()=>{document.querySelector(`wc-consent`)||console.warn(`[vitops] something requested the "${e}" consent category, but this page has no <wc-consent> element to ask with — so the request can never be granted and whatever waited on it will not run. Render <CookieConsent /> on every page that gates anything.`)}))}function E(e){S.has(e)||(S.add(e),T(e),B())}function D(){for(let[e,t]of C)if(d(v,e)){C.delete(e);for(let n of t)n(u(v,e))}}const O={idle:[],async:[],interaction:[]},k={idle:!1,async:!0,interaction:!1};function A(e){if(k[e])return;k[e]=!0;let t=O[e];O[e]=[];for(let e of t)e()}function j(e,t){k[e]?t():O[e].push(t)}function M(e){document.readyState===`complete`?e():addEventListener(`load`,e,{once:!0})}M(()=>{let e=globalThis.requestIdleCallback;e?e(()=>A(`idle`),{timeout:3e3}):setTimeout(()=>A(`idle`),1)});{let e=[`pointerdown`,`keydown`,`scroll`,`touchstart`],t=()=>{for(let n of e)removeEventListener(n,t);A(`interaction`)};for(let n of e)addEventListener(n,t,{once:!0,passive:!0});M(()=>setTimeout(()=>A(`interaction`),8e3))}const N=/* @__PURE__ */ new Set([`type`,`src`,`data-vitops-tag`,`data-src`,`data-strategy`,`data-consent`,`data-consent-src`,`data-consent-cookies`]);function P(t){let n=t.getAttribute(`data-consent`);return e.includes(n??``)?n:`analytics`}function F(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))N.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 I(e){if(!b.has(e)){if(b.add(e),x.add(P(e)),e instanceof HTMLScriptElement)F(e);else{let t=e.getAttribute(`data-consent-src`);t&&e.setAttribute(`src`,t)}e.setAttribute(`data-vitops-activated`,``)}}function L(){let e=document.querySelectorAll(`script[data-vitops-tag]:not([data-vitops-activated]),[data-consent-src]:not([data-vitops-activated])`);for(let t of e){let e=t.getAttribute(`data-strategy`);j(e===`async`||e===`interaction`?e:`idle`,()=>{let e=P(t);u(v,e)?I(t):E(e)})}}function R(e){let t=/* @__PURE__ */ 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 z(e,t){let n=h(v,e),i=v;v=e,t?document.cookie=`${r}=${l(e)}${g(_)}`:document.cookie=`${r}=; path=/; max-age=0`,B(),L(),n.length&&(R(n),n.some(e=>x.has(e)&&u(i,e))&&V.reloadOnRevoke&&location.reload())}function B(){D();for(let e of y)e(v);document.dispatchEvent(new CustomEvent(t,{detail:v}))}const V={categories:e,reloadOnRevoke:!0,get:()=>v,granted:e=>u(v,e),needed:()=>f(v,S),demanded:()=>e.filter(e=>S.has(e)),require(e){return E(e),u(v,e)},request(e){return E(e),d(v,e)?Promise.resolve(u(v,e)):new Promise(t=>{let n=C.get(e);n?n.push(t):C.set(e,[t])})},set:e=>z(m(v,e,Date.now()),!0),acceptAll:()=>z(p(!0,Date.now()),!0),rejectAll:()=>z(p(!1,Date.now()),!0),reset:()=>z(o(),!1),open:()=>document.dispatchEvent(new CustomEvent(n)),subscribe(e){return y.add(e),e(v),()=>void y.delete(e)}},H=window.vitopsConsent?.q;if(window.vitopsConsent=V,H)for(let[e,t]of H)t?V.request(e).then(t):V.require(e);document.addEventListener(`click`,e=>{e.target?.closest?.(`[data-consent-open]`)&&(e.preventDefault(),V.open())}),document.addEventListener(`astro:page-load`,L),L(),B();var U=class extends HTMLElement{#e;#t=`prompt`;#n=!1;get#r(){return window.vitopsConsent}get#i(){return this.querySelector(`[popover]`)}connectedCallback(){let e=this.#r;if(!e)return;let t=this.#i;t&&typeof t.showPopover!=`function`&&t.removeAttribute(`popover`),this.getAttribute(`reload-on-revoke`)===`false`&&(e.reloadOnRevoke=!1),this.addEventListener(`click`,this.#p),this.addEventListener(`submit`,this.#o),document.addEventListener(n,this.#a),this.#e=e.subscribe(()=>{this.#n&&this.#t===`settings`?this.#f():e.needed()?this.#u(`prompt`):this.#d()})}disconnectedCallback(){this.removeEventListener(`click`,this.#p),this.removeEventListener(`submit`,this.#o),document.removeEventListener(n,this.#a),this.#e?.()}#a=()=>this.#u(`settings`);#o=e=>e.preventDefault();#s(e){return this.querySelector(`[data-consent-row="${e}"]`)??this.#c(e)?.closest(`label`)??null}#c(e){return this.querySelector(`input[name="${e}"]`)}#l(e){let t=this.#r;if(!t)return[];if(e===`settings`){let e=i.filter(e=>this.#s(e));return e.length?e:[...i]}let n=t.get();return i.filter(e=>t.demanded().includes(e)&&!d(n,e))}#u(e){if(!this.#l(e).length)return;this.#t=e,this.#f(),this.hidden=!1,this.#n=!0;let t=this.#i;t?.isConnected&&!t.matches(`:popover-open`)&&t.showPopover()}#d(){this.#n=!1,this.#i?.hidePopover?.(),this.hidden=!0}#f(){let e=this.#r;if(!e)return;let t=this.#l(this.#t);for(let n of i){let r=this.#s(n);r&&(r.hidden=!t.includes(n));let i=this.#c(n);i&&(i.checked=e.granted(n))}this.#i?.classList.toggle(`consent--single`,t.length<2)}#p=e=>{let t=this.#r,n=e.target?.closest?.(`[data-consent-accept],[data-consent-reject],[data-consent-save]`);if(!t||!n)return;e.preventDefault();let r=this.#l(this.#t);n.hasAttribute(`data-consent-accept`)?t.set(this.#m(r,()=>!0)):n.hasAttribute(`data-consent-reject`)?t.set(this.#m(r,()=>!1)):t.set(this.#m(r,e=>this.#c(e)?.checked===!0)),this.#d()};#m(e,t){let n={};for(let r of e)n[r]=t(r);return n}};customElements.get(`wc-consent`)||customElements.define(`wc-consent`,U);export{e as CATEGORIES,t as CONSENT_EVENT,n as CONSENT_OPEN_EVENT,r as COOKIE_NAME,V 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=>{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()})})});
|
|
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=(/* @__PURE__ */ new Date()).getFullYear();document.querySelectorAll(`[data-current-year]`).forEach(e=>{e.textContent=t.toString()})})});
|
package/dist/editor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=`vitops:ds-overrides`,t
|
|
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);
|
|
1
|
+
const e=`vitops:ds-overrides`,t=(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}`},n=(e,t)=>`--color-${e}-${t}`;var r=class extends HTMLElement{constructor(...e){super(...e),this.store={light:/* @__PURE__ */ new Map,dark:/* @__PURE__ */ new Map},this.roles=/* @__PURE__ */ new Map,this.roleVars=/* @__PURE__ */ new Set,this.syncers=/* @__PURE__ */ new Set,this.scheme=`light`,this.canSave=!1}static{this.observedAttributes=[`manifest`,`save-endpoint`]}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)}get saveUrl(){return this.getAttribute(`save-endpoint`)??`/__vitops/design-system`}async probeSave(){try{let e=await fetch(this.saveUrl);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,n,r){let i=e.colors.roles.find(e=>e.name===n)?.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([t(n,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:r}=this.section(`Palette`,!0);r.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 r of e.colors.steps){let a=e.colors.palette[t]?.[r];a!=null&&i.body.append(this.colorInput(n(t,r),a))}r.append(i.details)}return r.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,t){let{patch:n}=this.buildPatch(e);if(!Object.keys(n).length)return void this.flash(t,`Nothing to save`);try{let e=await fetch(this.saveUrl,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({patch:n})});if(!e.ok){let n=await e.json().catch(()=>({}));console.error(`[wc-theme-editor] save rejected`,n.errors??e.statusText),this.flash(t,`Rejected ✗`);return}this.reset(),this.flash(t,`Saved ✓`)}catch(e){console.error(`[wc-theme-editor] save failed`,e),this.flash(t,`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`,r);
|