@getvitops/core 0.7.0 → 0.9.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 +206 -0
- package/css/animation.css +12 -2
- package/css/global.css +10 -0
- package/css/index.css +1 -0
- package/css/patterns/details.css +27 -8
- package/css/patterns/drawer.css +15 -0
- package/css/patterns/forms.css +31 -0
- package/css/patterns/grouped.css +1 -1
- package/css/patterns/popover.css +18 -1
- package/css/patterns/subgrid.css +62 -16
- package/css/patterns/theme-editor.css +122 -0
- package/css/utilities.css +5 -3
- package/dist/editor.js +2 -0
- package/dist/elements.js +2 -2
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,211 @@
|
|
|
1
1
|
# @getvitops/core
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **The theme editor no longer dims the page it's editing.**
|
|
8
|
+
|
|
9
|
+
The panel used `.drawer`'s scrim and `popover="auto"`, which fought the whole purpose of a live
|
|
10
|
+
editor: the page you were tuning sat behind a 4px blur, and the first click on it dismissed the
|
|
11
|
+
panel. It is now modeless — no scrim, and clicking through to the page keeps it open. Escape still
|
|
12
|
+
closes it (wired explicitly, since `popover="manual"` doesn't provide it), as does the × button.
|
|
13
|
+
|
|
14
|
+
**New: `.drawer--modeless`.** The same treatment is available to any drawer — a side panel you work
|
|
15
|
+
_alongside_ rather than through (an inspector, a live editor, a filter rail). Pair it with
|
|
16
|
+
`popover="manual"`:
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<div class="drawer drawer--right drawer--modeless" popover="manual">…</div>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An `auto` popover light-dismisses on the first outside click, which is exactly the interaction a
|
|
23
|
+
modeless panel exists to permit — so `manual` is part of the pattern, not an afterthought. It also
|
|
24
|
+
drops Escape, so give the panel a visible close control.
|
|
25
|
+
|
|
26
|
+
**Added:** a drift guard tying `<wc-theme-editor>`'s dark-override selector to the generator's
|
|
27
|
+
`DARK_SEL`. The two must match or the editor's dark-mode edits land on a rule the page never
|
|
28
|
+
matches — a failure that is invisible in light mode, so nothing would have caught it.
|
|
29
|
+
|
|
30
|
+
- **Fixed: `Subgrid` and `Cards` rendered as unstyled lists, in every format.**
|
|
31
|
+
|
|
32
|
+
`Subgrid.astro` drew its geometry with Tailwind utilities — `grid-rows-subgrid` and
|
|
33
|
+
`row-span-(--row-span)`, the latter Tailwind v4's arbitrary-CSS-variable syntax. No framework CSS
|
|
34
|
+
layer defines those, so under `css.format: 'css'` or `'bricks'` the component had no layout at all.
|
|
35
|
+
|
|
36
|
+
It failed under `'tailwind'` too, which is the part worth internalising: **Tailwind v4 is JIT and
|
|
37
|
+
does not scan `node_modules`**, so a class that only a shipped component references is never
|
|
38
|
+
generated. A consumer had to add the package to Tailwind's `@source` by hand to get a subgrid;
|
|
39
|
+
without that, cards silently laid out at `grid-row: auto` — visually plausible, quietly wrong. The
|
|
40
|
+
same trap applied to `grid` in `Subgrid` and `sr-only` / `not-sr-only` in `Popover` / `Drawer`:
|
|
41
|
+
those are in the generator's `TW_CLASH` list, so the framework strips its own rules for them from
|
|
42
|
+
the tailwind bundle and defers to Tailwind — meaning they resolved only when the consumer's own
|
|
43
|
+
templates happened to use the same class.
|
|
44
|
+
|
|
45
|
+
The components now emit framework classes only, and the `subgrid` pattern owns the layout:
|
|
46
|
+
- `Subgrid` renders `<ul class="subgrid"><li>…` and ships no `<style>` block of its own.
|
|
47
|
+
- `Popover` / `Drawer` use a component-scoped `visually-hidden` instead of `sr-only`. The
|
|
48
|
+
`not-sr-only` on their icons was a no-op and is gone.
|
|
49
|
+
- The `subgrid` pattern absorbed the wrapped-row margin the component used to carry, so
|
|
50
|
+
hand-written `.subgrid` markup gets it too, and resets list markers on `ul`/`ol`.
|
|
51
|
+
|
|
52
|
+
**Also fixed: `Cards` discarded the `card` class and every class you put on a child.** It wrote
|
|
53
|
+
`card` onto the slotted element and then serialised that element's _inner_ HTML, dropping the
|
|
54
|
+
attribute it had just set. Child `class` and `style` now reach the rendered `<li>`.
|
|
55
|
+
|
|
56
|
+
**Breaking — the subgrid custom properties are renamed.** One `--subgrid-*` vocabulary now covers
|
|
57
|
+
both the pattern and the component; the old names are removed, not aliased:
|
|
58
|
+
|
|
59
|
+
| removed | use |
|
|
60
|
+
| ------------------------------- | -------------------- |
|
|
61
|
+
| `--items-per-row`, `--cols` | `--subgrid-cols` |
|
|
62
|
+
| `--rows-per-item`, `--row-span` | `--subgrid-row-span` |
|
|
63
|
+
| `--row-margin` | `--subgrid-row-gap` |
|
|
64
|
+
|
|
65
|
+
`--subgrid-gap` (the grid gap) is unchanged, as are the `.subgrid-cols-*` / `.subgrid-rows-*` /
|
|
66
|
+
`.subgrid-responsive` modifiers. Anything still setting an old name falls back to the pattern
|
|
67
|
+
defaults — 3 columns, span 2 — so grep for them when you upgrade.
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- **Fixed: `vitops init` and `vp create` scaffolded configs referencing tokens that no longer
|
|
72
|
+
exist.** `defaultConfig()` and the EmDash template still pointed at `--color-surface-xl` and
|
|
73
|
+
`--color-surface-xxl`, aliases from the named-step colour scale removed in 0.6. A scaffolded
|
|
74
|
+
project therefore got a `card` with no background and an invalid default border colour, with
|
|
75
|
+
nothing reporting it. Both now use `--surface-bg` / `--surface-bg-muted`. The EmDash template
|
|
76
|
+
also dropped its `patterns.radii.card` key, which collided with the `card` pattern on
|
|
77
|
+
`--br-card` — the collision `validate()` has been warning about (the `panel` group already
|
|
78
|
+
supplies the same radius, so nothing changes visually).
|
|
79
|
+
|
|
80
|
+
**Fixed: `.text-reveal` rendered invisible text.** Its gradient consumed
|
|
81
|
+
`--text-reveal-color-from`/`-to` with no defaults, so unless a consumer set both, the `var()`
|
|
82
|
+
substitution failed, `background` became invalid at computed-value time, and the accompanying
|
|
83
|
+
`color: transparent` left the text with no way to render. Both tones now default to the
|
|
84
|
+
functional text tokens.
|
|
85
|
+
|
|
86
|
+
**Fixed: `.bordered` fell back to `currentColor`** via a reference to `--color-surface-xl`,
|
|
87
|
+
which the generator never emitted. It now resolves `--surface-border`.
|
|
88
|
+
|
|
89
|
+
**New: `validate()` warns when a required role is missing.** `colors.roles` is an open map —
|
|
90
|
+
any name works and generates a full token set — but the shipped component CSS references
|
|
91
|
+
`brand-primary`, `danger`, `neutral`, `surface`, `ui-primary` and `warning` with no fallback,
|
|
92
|
+
so omitting one leaves those components uncoloured. This is a warning, not an error; the
|
|
93
|
+
required list is re-derived from the CSS partials by a test so it cannot drift.
|
|
94
|
+
|
|
95
|
+
`vitops docs` and `vitops agents` now surface config warnings (on stderr, so piping `docs` is
|
|
96
|
+
unaffected). They previously discarded them, unlike `generate` and `validate`.
|
|
97
|
+
|
|
98
|
+
Docs corrections, all in the generated bundle:
|
|
99
|
+
- **Raw scale classes are frozen and do not remap in dark mode** — stated explicitly for the
|
|
100
|
+
first time, with a migration table to the role equivalents. The dark-mode guarantee only ever
|
|
101
|
+
applied to functional role tokens, and nothing said so.
|
|
102
|
+
- **Roles are extensible over a required core** — the schema description and class reference
|
|
103
|
+
read as a closed enumeration, which is why a consumer forked their own colour layer rather
|
|
104
|
+
than adding a role.
|
|
105
|
+
- **The `md:` / `@md:` / `md-` distinction** in the tailwind format: `@md:` uses the framework's
|
|
106
|
+
breakpoints, `md:` works but uses Tailwind's (which differ — `sm:` is 40rem, `@sm:` is 30rem),
|
|
107
|
+
and `md-` is silently inert. Plus a note that registering `--container-*` also re-points
|
|
108
|
+
Tailwind's `max-w-*` scale.
|
|
109
|
+
- The css and bricks bundles now carry a `/*!` banner pointing at `npx vitops docs classes`
|
|
110
|
+
(the previous plain comment was stripped by the minifier, so it never reached the file).
|
|
111
|
+
|
|
112
|
+
Contrast checking now covers **every** background plane a role emits (`bg`, `bg-muted`, and
|
|
113
|
+
`bg-bold`), not just `bg` — body text on a `card` was previously unguaranteed.
|
|
114
|
+
|
|
115
|
+
- **Fixed: `<details>` disclosures never opened.**
|
|
116
|
+
|
|
117
|
+
Every `<details>` on a page using this framework was inert in Chrome 149 — clicking a summary
|
|
118
|
+
toggled the `open` attribute and nothing else happened. This affected the bare element, not just
|
|
119
|
+
`.details`, so it hit the docs table-of-contents, the mobile nav, and the theme editor's sections
|
|
120
|
+
alike.
|
|
121
|
+
|
|
122
|
+
The cause was the open/close animation on `::details-content`. It used the canonical recipe
|
|
123
|
+
(`block-size: 0 → auto` plus `content-visibility` under `allow-discrete`, backed by
|
|
124
|
+
`interpolate-size: allow-keywords`), and that recipe deadlocks: `content-visibility` stays `hidden`,
|
|
125
|
+
so the content has no layout, so `auto` resolves to `0`, so the transition has nothing to animate
|
|
126
|
+
and the state never advances. The failure is total rather than cosmetic — collapsed content simply
|
|
127
|
+
could not be reached.
|
|
128
|
+
|
|
129
|
+
Bisected against a bare `<details>` with only the framework stylesheet. `transition: none` is the
|
|
130
|
+
only thing that restores it; transitioning `block-size` alone, `content-visibility` alone,
|
|
131
|
+
`calc-size(auto, size)`, and the `grid-template-rows: 0fr → 1fr` technique all fail identically.
|
|
132
|
+
|
|
133
|
+
**So the transition is gone and disclosures now open instantly.** Correctness over motion. If you
|
|
134
|
+
reinstate it, verify a _first click actually expands_ in a real browser — the syntax is valid and
|
|
135
|
+
`@supports` reports it as supported, so a feature query proves nothing here; only the interaction
|
|
136
|
+
does.
|
|
137
|
+
|
|
138
|
+
**Also fixed: `.details--marker-start` right-aligned its label.** It mirrored the trailing marker's
|
|
139
|
+
`margin-inline-start: auto` as `margin-inline-end: auto`, which pushed everything after the icon to
|
|
140
|
+
the opposite edge instead of just placing the icon first.
|
|
141
|
+
|
|
142
|
+
**Also:** `<wc-theme-editor>`'s section headers now carry a disclosure marker. The `.details`
|
|
143
|
+
pattern removes the native one and expects a `.details-icon`; without it a panel of ~40 nested
|
|
144
|
+
sections gave no indication anything was collapsible.
|
|
145
|
+
- @getvitops/utils@0.9.0
|
|
146
|
+
|
|
147
|
+
## 0.8.0
|
|
148
|
+
|
|
149
|
+
### Minor Changes
|
|
150
|
+
|
|
151
|
+
- **New: `<wc-theme-editor>`, a live theme editor you can ship with your site.**
|
|
152
|
+
|
|
153
|
+
Tune the whole design system in the browser — palette, semantic roles, type roles, spacing, layout,
|
|
154
|
+
pattern geometry, radii, shadows — with no rebuild. Edits are layered as `:root` custom-property
|
|
155
|
+
overrides, persist to localStorage, and export as CSS or as a `design-system.json` patch. It follows
|
|
156
|
+
your site's colour scheme and drives the same `data-theme` + storage key as `<color-scheme-toggle>`,
|
|
157
|
+
so the two no longer fight.
|
|
158
|
+
|
|
159
|
+
It ships as a **separate, opt-in bundle** (`@getvitops/core/editor`, ~13 kB, no Lit) and is never
|
|
160
|
+
registered in `elements.js`: a page that doesn't ask for it pays nothing.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
// astro.config.mjs
|
|
164
|
+
getvitops({
|
|
165
|
+
css: { input: 'design-system.json', format: 'css', out: 'src/styles' },
|
|
166
|
+
editor: true,
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<!-- anywhere in your layout -->
|
|
172
|
+
<wc-theme-editor></wc-theme-editor>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`editor: true` copies `editor.js` into `public/vitops/`, loads it from `<Head />`, and mirrors
|
|
176
|
+
`design-manifest.json` to `/vitops/design-manifest.json` (override with the `manifest` attribute).
|
|
177
|
+
Outside Astro, load the bundle yourself and point `manifest` at the file.
|
|
178
|
+
|
|
179
|
+
**Save straight back to your config, in dev.** `@getvitops/vite` now serves a dev-only
|
|
180
|
+
`/__vitops/design-system` endpoint: the editor POSTs its patch, the plugin deep-merges it into your
|
|
181
|
+
`design-system.json`, validates the _merged_ result, writes it back preserving the file's formatting,
|
|
182
|
+
then regenerates and reloads. The endpoint exists only under `vite dev` — on a static deploy the
|
|
183
|
+
editor detects its absence and hides the button, while everything else still works.
|
|
184
|
+
|
|
185
|
+
**Added:** `colors.roleTokens` in `design-manifest.json` — the functional token set (bg / border /
|
|
186
|
+
solid / on-solid / text / emphasis stops) precomputed per hue, in `default` and `surface` variants for
|
|
187
|
+
both appearances. Re-pointing a role at another hue needs these: `solid` is chosen by scanning the hue
|
|
188
|
+
and `on-solid` is a computed contrast value, so neither is derivable by a browser client.
|
|
189
|
+
|
|
190
|
+
**Removed:** the dead `interaction` block from `design-manifest.json` — a hardcoded
|
|
191
|
+
`{duration, easing}` literal with no schema key, no reverse-index entry, and no corresponding CSS
|
|
192
|
+
variable. Nothing could have consumed it meaningfully. The real animation knobs are
|
|
193
|
+
`--animation-duration-*` / `--custom-ease-*`.
|
|
194
|
+
|
|
195
|
+
**Fixed:** a `[popover]` reset in `popover.css` was stripping the background, padding and border from
|
|
196
|
+
any pattern used as a popover. It is imported after `drawer.css`, so at equal specificity it beat
|
|
197
|
+
`.drawer` — and `dialog` (0-0-1) too — leaving a `.drawer[popover]` transparent and unpadded despite
|
|
198
|
+
drawer.css documenting popover as supported. The reset now sits in `:where()` at zero specificity, so
|
|
199
|
+
it still overrides the UA stylesheet but any pattern class takes its surface back. Open/closed
|
|
200
|
+
behaviour is unchanged.
|
|
201
|
+
|
|
202
|
+
**Added:** `input[type="range"]` and `input[type="color"]` now have baseline styling in `forms.css`
|
|
203
|
+
(accent colour from `--ui-primary-solid`; the colour swatch picks up the control border and radius).
|
|
204
|
+
|
|
205
|
+
### Patch Changes
|
|
206
|
+
|
|
207
|
+
- @getvitops/utils@0.8.0
|
|
208
|
+
|
|
3
209
|
## 0.7.0
|
|
4
210
|
|
|
5
211
|
### Patch Changes
|
package/css/animation.css
CHANGED
|
@@ -303,6 +303,16 @@
|
|
|
303
303
|
failure leaves readable text rather than blank space. Scroll-timeline is also
|
|
304
304
|
guarded — without it the text simply shows the final (to) color, still legible. */
|
|
305
305
|
.text-reveal {
|
|
306
|
+
/* Both tones MUST default, and NOT to currentColor. They are consumed by the
|
|
307
|
+
gradient below, where a var() substitution failure makes `background`
|
|
308
|
+
invalid at computed-value time — which, with the `color: transparent` in
|
|
309
|
+
that same block, renders the text completely invisible rather than
|
|
310
|
+
degrading. currentColor is unusable there for the same reason: inside the
|
|
311
|
+
@supports block it resolves to that transparent value. So default to the
|
|
312
|
+
functional text tokens (they flip with the appearance) with a literal
|
|
313
|
+
backstop, since this is precisely the declaration that must never fail. */
|
|
314
|
+
--_reveal-from: var(--text-reveal-color-from, var(--neutral-text-x-muted, #9099a8));
|
|
315
|
+
--_reveal-to: var(--text-reveal-color-to, var(--neutral-text, #05070d));
|
|
306
316
|
/* Fallback baseline: ordinary visible text in the start color. */
|
|
307
317
|
color: var(--text-reveal-color-from, currentColor);
|
|
308
318
|
}
|
|
@@ -313,8 +323,8 @@
|
|
|
313
323
|
--text-reveal-background,
|
|
314
324
|
linear-gradient(
|
|
315
325
|
var(--text-reveal-slant, 90deg),
|
|
316
|
-
var(--
|
|
317
|
-
var(--
|
|
326
|
+
var(--_reveal-to) 50%,
|
|
327
|
+
var(--_reveal-from) 50%
|
|
318
328
|
)
|
|
319
329
|
);
|
|
320
330
|
background-size: 200% 100%;
|
package/css/global.css
CHANGED
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
font-size: 100% !important;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/* Drop the UA's 8px body margin. The framework owns page gutters via `.centered`
|
|
12
|
+
(--gutter), so the UA default just offsets every full-bleed surface — sticky
|
|
13
|
+
headers and `bg-*` bands end up inset by 8px with a sliver of canvas showing
|
|
14
|
+
around them. This is the only UA reset the framework makes: it deliberately
|
|
15
|
+
does NOT ship a general reset (no global box-sizing change), because that
|
|
16
|
+
would silently reflow existing consumer layouts. */
|
|
17
|
+
body {
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
@media (prefers-reduced-motion: no-preference) {
|
|
12
22
|
html {
|
|
13
23
|
scrollbar-gutter: stable;
|
package/css/index.css
CHANGED
package/css/patterns/details.css
CHANGED
|
@@ -37,15 +37,31 @@ details,
|
|
|
37
37
|
rotate: var(--_details-icon-rotate, 90deg);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Collapse without a transition — deliberately.
|
|
42
|
+
*
|
|
43
|
+
* This carried the canonical open/close animation (`block-size: 0 → auto` with
|
|
44
|
+
* `content-visibility` under `allow-discrete`, backed by the
|
|
45
|
+
* `interpolate-size: allow-keywords` in utilities.css). In Chrome 149 that
|
|
46
|
+
* deadlocks: `content-visibility` stays `hidden`, so the content has no layout,
|
|
47
|
+
* so `auto` resolves to `0`, so the transition has nothing to run — and the
|
|
48
|
+
* disclosure never opens *at all*. Not "opens without animating": every
|
|
49
|
+
* `<details>` on the page becomes inert, which is a total content-loss bug.
|
|
50
|
+
*
|
|
51
|
+
* Verified against a bare `<details>` with only this stylesheet, and bisected:
|
|
52
|
+
* `transition: none` is the only thing that restores it. Transitioning only
|
|
53
|
+
* `block-size`, only `content-visibility`, `calc-size(auto, size)`, and the
|
|
54
|
+
* `grid-template-rows: 0fr → 1fr` technique all fail the same way.
|
|
55
|
+
*
|
|
56
|
+
* So: correctness over motion. Re-add the transition only with a live
|
|
57
|
+
* cross-browser check that a first click actually expands — a passing
|
|
58
|
+
* `@supports` query is not evidence, since the syntax is valid and supported;
|
|
59
|
+
* it's the interaction that breaks.
|
|
60
|
+
*/
|
|
40
61
|
&::details-content {
|
|
41
62
|
display: block;
|
|
42
63
|
overflow: hidden;
|
|
43
64
|
block-size: 0;
|
|
44
|
-
opacity: 0;
|
|
45
|
-
transition:
|
|
46
|
-
block-size 0.3s ease,
|
|
47
|
-
opacity 0.3s ease,
|
|
48
|
-
content-visibility 0.3s allow-discrete;
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
> summary + * {
|
|
@@ -62,13 +78,16 @@ details,
|
|
|
62
78
|
|
|
63
79
|
&[open]::details-content {
|
|
64
80
|
block-size: auto;
|
|
65
|
-
opacity: 1;
|
|
66
81
|
}
|
|
67
82
|
|
|
83
|
+
/* Leading marker. The default (trailing) icon uses `margin-inline-start: auto`
|
|
84
|
+
to push itself to the end; the mirrored `margin-inline-end: auto` here did
|
|
85
|
+
the same thing to everything *after* it, so the label got shoved to the
|
|
86
|
+
opposite edge and the variant read as right-aligned text. A leading marker
|
|
87
|
+
just wants both auto margins gone — `gap` on the summary does the spacing. */
|
|
68
88
|
&.details--marker-start > summary > .details-icon {
|
|
69
89
|
order: -1;
|
|
70
|
-
margin-inline
|
|
71
|
-
margin-inline-end: auto;
|
|
90
|
+
margin-inline: 0;
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
/* Plus/minus variant — toggle two sibling icon elements */
|
package/css/patterns/drawer.css
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
backdrop-filter: blur(4px);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Modeless — a panel you work *alongside* rather than through. Drops the
|
|
29
|
+
* scrim so the page behind stays legible, which is the whole point for a
|
|
30
|
+
* live editor or an inspector: you're watching what your edits do.
|
|
31
|
+
*
|
|
32
|
+
* Pair with `popover="manual"`. An `auto` popover light-dismisses on the
|
|
33
|
+
* first click outside — i.e. exactly the interaction a modeless panel exists
|
|
34
|
+
* to allow — and `manual` also drops Esc, so give it an explicit close
|
|
35
|
+
* control (and wire Esc yourself if the panel warrants it).
|
|
36
|
+
*/
|
|
37
|
+
&.drawer--modeless::backdrop {
|
|
38
|
+
background: none;
|
|
39
|
+
backdrop-filter: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
&,
|
|
28
43
|
&.drawer--left {
|
|
29
44
|
inset-block: 0;
|
package/css/patterns/forms.css
CHANGED
|
@@ -90,6 +90,37 @@ fieldset.fieldset,
|
|
|
90
90
|
background: var(--surface-bg);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/* ── Range + colour controls ───────────────────────────────────────────────── */
|
|
94
|
+
/* `accent-color` is the whole job for range: it themes the track fill and thumb
|
|
95
|
+
* natively, in both appearances, with no pseudo-element rewriting. The colour
|
|
96
|
+
* swatch only needs its chrome removed so the well fills the control box. */
|
|
97
|
+
input[type='range'] {
|
|
98
|
+
accent-color: var(--range-accent, var(--ui-primary-solid));
|
|
99
|
+
inline-size: 100%;
|
|
100
|
+
min-inline-size: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
input[type='color'] {
|
|
104
|
+
--_br: var(--br-input, var(--br-control, var(--br-default)));
|
|
105
|
+
|
|
106
|
+
inline-size: var(--color-swatch-size, 2rem);
|
|
107
|
+
block-size: var(--color-swatch-size, 2rem);
|
|
108
|
+
padding: 0;
|
|
109
|
+
border: var(--b-input, var(--b-control, var(--b-default)));
|
|
110
|
+
border-radius: var(--_br);
|
|
111
|
+
background: none;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
|
|
114
|
+
&::-webkit-color-swatch-wrapper {
|
|
115
|
+
padding: 2px;
|
|
116
|
+
}
|
|
117
|
+
&::-webkit-color-swatch,
|
|
118
|
+
&::-moz-color-swatch {
|
|
119
|
+
border: none;
|
|
120
|
+
border-radius: calc(var(--_br) - 2px);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
/* ── Input group — input flanked by icons/addons ───────────────────────────── */
|
|
94
125
|
.input-group {
|
|
95
126
|
--_br: var(--br-input-group, var(--br-control, var(--br-default)));
|
package/css/patterns/grouped.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
--_bs: var(--bs, solid);
|
|
10
10
|
border-width: var(--_bw);
|
|
11
11
|
border-style: var(--_bs);
|
|
12
|
-
border-color: var(--bc, var(--
|
|
12
|
+
border-color: var(--bc, var(--surface-border, currentColor));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/* ── Group — collapse border-radius (+ shared borders) across direct children ─
|
package/css/patterns/popover.css
CHANGED
|
@@ -4,14 +4,31 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/* ── Popover — native Popover API reset + anchored position variants ───────── */
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The UA gives `[popover]` a padding, border and canvas background; a bare
|
|
10
|
+
* popover wants none of that. But this partial is imported *after* drawer.css /
|
|
11
|
+
* dialog.css, so at plain `[popover]` specificity (0-1-0) the reset also beat
|
|
12
|
+
* `.drawer` (0-1-0, earlier) and `dialog` (0-0-1) — silently stripping the
|
|
13
|
+
* surface off any pattern used as a popover, which is exactly what drawer.css
|
|
14
|
+
* documents itself as supporting.
|
|
15
|
+
*
|
|
16
|
+
* So the reset sits in `:where()` at zero specificity: it still overrides the UA
|
|
17
|
+
* stylesheet, and *any* explicit class takes it back. Same mechanism the `btn`
|
|
18
|
+
* pattern uses with `:where(button, .btn)`. Keep the visual reset here and the
|
|
19
|
+
* open/closed behaviour below — that must stay at real specificity so a
|
|
20
|
+
* component's own rules can't accidentally reveal a closed popover.
|
|
21
|
+
*/
|
|
22
|
+
:where([popover]) {
|
|
8
23
|
padding: 0;
|
|
9
24
|
border: none;
|
|
10
25
|
background: transparent;
|
|
11
26
|
color: inherit;
|
|
12
27
|
overflow: visible;
|
|
13
28
|
inset: auto;
|
|
29
|
+
}
|
|
14
30
|
|
|
31
|
+
[popover] {
|
|
15
32
|
&:popover-open {
|
|
16
33
|
opacity: 1;
|
|
17
34
|
}
|
package/css/patterns/subgrid.css
CHANGED
|
@@ -3,51 +3,97 @@
|
|
|
3
3
|
* Consumes the generated token cascade (src/css/generated/tokens.css).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/* ── Subgrid — content-aligned cards/tables via CSS subgrid
|
|
6
|
+
/* ── Subgrid — content-aligned cards/tables via CSS subgrid ────────────────────
|
|
7
|
+
*
|
|
8
|
+
* Each item spans `--subgrid-row-span` row tracks of the parent and re-declares
|
|
9
|
+
* them with `grid-template-rows: subgrid`, so the tranches inside every item —
|
|
10
|
+
* head, body, footer — land on the same row lines no matter how much content
|
|
11
|
+
* each one holds.
|
|
12
|
+
*
|
|
13
|
+
* <ul class="subgrid" style="--subgrid-cols: 3; --subgrid-row-span: 3">
|
|
14
|
+
*
|
|
15
|
+
* Public custom properties:
|
|
16
|
+
* --subgrid-cols columns (default 3)
|
|
17
|
+
* --subgrid-row-span row tracks each item spans, i.e. tranches per item (2)
|
|
18
|
+
* --subgrid-gap the grid gap — between columns, and between tranches (1.5rem)
|
|
19
|
+
* --subgrid-row-gap EXTRA space between wrapped rows of items (0); see below
|
|
20
|
+
*/
|
|
7
21
|
.subgrid {
|
|
8
|
-
--
|
|
9
|
-
--
|
|
10
|
-
--
|
|
22
|
+
--_cols: var(--subgrid-cols, 3);
|
|
23
|
+
--_row-span: var(--subgrid-row-span, 2);
|
|
24
|
+
--_gap: var(--subgrid-gap, 1.5rem);
|
|
25
|
+
--_row-gap: var(--subgrid-row-gap, 0px);
|
|
11
26
|
|
|
12
27
|
display: grid;
|
|
13
|
-
grid-template-columns: repeat(var(--
|
|
14
|
-
gap: var(--
|
|
28
|
+
grid-template-columns: repeat(var(--_cols), 1fr);
|
|
29
|
+
gap: var(--_gap);
|
|
15
30
|
|
|
16
31
|
> * {
|
|
17
|
-
grid-row: span var(--
|
|
32
|
+
grid-row: span var(--_row-span);
|
|
18
33
|
display: grid;
|
|
19
34
|
grid-template-rows: subgrid;
|
|
20
35
|
}
|
|
21
36
|
|
|
37
|
+
/* Spacing between items is the grid's job, not the flow's — neutralise the
|
|
38
|
+
`.rhythm :where(ul, ol) > li + li` margin, which reaches into any list
|
|
39
|
+
inside a prose container and outweighs `> *` on specificity. */
|
|
40
|
+
&:is(ul, ol) > li + li {
|
|
41
|
+
margin-block-start: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* The grid's own row gap is the gap between an item's *tranches*, so it can't
|
|
45
|
+
also be the gap between *rows of items* — items past the first row take an
|
|
46
|
+
extra margin instead. Computed in CSS rather than from a server-rendered
|
|
47
|
+
index because --_cols can change in a media query. Guarded the same way as
|
|
48
|
+
.stagger (animation.css): sibling-index() lacks only Firefox as of 2026,
|
|
49
|
+
and where it's missing the margin stays 0 and rows fall back to the grid
|
|
50
|
+
gap, rather than producing an invalid margin. The second selector repeats
|
|
51
|
+
the rule at the specificity rhythm forces above. */
|
|
52
|
+
@supports (--x: sibling-index()) {
|
|
53
|
+
> *,
|
|
54
|
+
&:is(ul, ol) > li + li {
|
|
55
|
+
--_row-index: round(down, calc((sibling-index() - 1) / var(--_cols)), 1);
|
|
56
|
+
margin-block-start: calc(clamp(0, var(--_row-index), 1) * var(--_row-gap));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Markerless when it's a list — a subgrid of cards is a list of things, and
|
|
61
|
+
the markers would sit in the gutter. */
|
|
62
|
+
&:is(ul, ol) {
|
|
63
|
+
list-style: none;
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
22
68
|
&.subgrid-cols-1 {
|
|
23
|
-
--
|
|
69
|
+
--_cols: 1;
|
|
24
70
|
}
|
|
25
71
|
&.subgrid-cols-2 {
|
|
26
|
-
--
|
|
72
|
+
--_cols: 2;
|
|
27
73
|
}
|
|
28
74
|
&.subgrid-cols-3 {
|
|
29
|
-
--
|
|
75
|
+
--_cols: 3;
|
|
30
76
|
}
|
|
31
77
|
&.subgrid-cols-4 {
|
|
32
|
-
--
|
|
78
|
+
--_cols: 4;
|
|
33
79
|
}
|
|
34
80
|
|
|
35
81
|
&.subgrid-rows-2 {
|
|
36
|
-
--
|
|
82
|
+
--_row-span: 2;
|
|
37
83
|
}
|
|
38
84
|
&.subgrid-rows-3 {
|
|
39
|
-
--
|
|
85
|
+
--_row-span: 3;
|
|
40
86
|
}
|
|
41
87
|
&.subgrid-rows-4 {
|
|
42
|
-
--
|
|
88
|
+
--_row-span: 4;
|
|
43
89
|
}
|
|
44
90
|
|
|
45
91
|
&.subgrid-responsive {
|
|
46
92
|
@media (width < 60rem) {
|
|
47
|
-
--
|
|
93
|
+
--_cols: min(var(--subgrid-cols, 3), 2);
|
|
48
94
|
}
|
|
49
95
|
@media (width < 40rem) {
|
|
50
|
-
--
|
|
96
|
+
--_cols: 1;
|
|
51
97
|
}
|
|
52
98
|
}
|
|
53
99
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme editor — chrome for `<wc-theme-editor>` (@getvitops/core/editor).
|
|
3
|
+
*
|
|
4
|
+
* Deliberately thin: the panel is a `.drawer.drawer--right` popover, its sections
|
|
5
|
+
* are `.details`, its controls come from `forms.css` and its buttons from the
|
|
6
|
+
* generated `btn`/`cta` patterns. What's left here is only what those don't cover
|
|
7
|
+
* — the launcher's fixed position, the label/control row, and the density the
|
|
8
|
+
* panel needs to fit a whole design system in a sidebar.
|
|
9
|
+
*
|
|
10
|
+
* This partial is imported by the bundle like any other, so a consumer who never
|
|
11
|
+
* loads the editor bundle still pays for it (~1KB) but sees nothing: every rule is
|
|
12
|
+
* scoped under `wc-theme-editor`, which only exists once the element is defined.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
wc-theme-editor {
|
|
16
|
+
/* Density: the panel packs ~200 controls, so it runs a step down from body. */
|
|
17
|
+
--_fs: var(--text-xs, 0.78rem);
|
|
18
|
+
|
|
19
|
+
.ed-launch {
|
|
20
|
+
position: fixed;
|
|
21
|
+
inset-block-end: 1rem;
|
|
22
|
+
inset-inline-end: 1rem;
|
|
23
|
+
z-index: var(--z-tier-top, 100);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.ed-panel {
|
|
27
|
+
--drawer-max-inline-size: min(28rem, 100vw);
|
|
28
|
+
|
|
29
|
+
overflow-y: auto;
|
|
30
|
+
padding: 1rem;
|
|
31
|
+
font-size: var(--_fs);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Sections: the drawer supplies the surface, so these only need separation. */
|
|
35
|
+
.ed-section {
|
|
36
|
+
border-block-start: var(--b-default);
|
|
37
|
+
padding-block-start: 0.5rem;
|
|
38
|
+
}
|
|
39
|
+
.ed-section > summary {
|
|
40
|
+
padding-block: 0.35rem;
|
|
41
|
+
gap: 0.4rem;
|
|
42
|
+
}
|
|
43
|
+
/* The pattern's icon slot is sized for body text; at panel density it needs to
|
|
44
|
+
be small enough not to outweigh the label it sits next to. */
|
|
45
|
+
.details-icon {
|
|
46
|
+
font-size: 0.75em;
|
|
47
|
+
line-height: 1;
|
|
48
|
+
opacity: 0.7;
|
|
49
|
+
}
|
|
50
|
+
.ed-sub {
|
|
51
|
+
border: var(--b-default);
|
|
52
|
+
border-radius: var(--br-default);
|
|
53
|
+
padding: 0.4rem 0.6rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* A control row: fixed-width var name, control takes the rest. */
|
|
57
|
+
.ed-row {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 0.5rem;
|
|
61
|
+
}
|
|
62
|
+
.ed-label {
|
|
63
|
+
flex: 0 0 8.5rem;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
font-family: var(--font-mono, monospace);
|
|
66
|
+
font-size: 0.9em;
|
|
67
|
+
color: var(--surface-text-x-muted);
|
|
68
|
+
text-overflow: ellipsis;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
}
|
|
71
|
+
.ed-input {
|
|
72
|
+
flex: 1;
|
|
73
|
+
min-inline-size: 0;
|
|
74
|
+
padding: 0.3em 0.5em;
|
|
75
|
+
border: var(--b-default);
|
|
76
|
+
border-radius: var(--br-default);
|
|
77
|
+
background: var(--surface-bg);
|
|
78
|
+
color: inherit;
|
|
79
|
+
font: inherit;
|
|
80
|
+
}
|
|
81
|
+
.ed-mono {
|
|
82
|
+
font-family: var(--font-mono, monospace);
|
|
83
|
+
}
|
|
84
|
+
.ed-pair {
|
|
85
|
+
flex: 1;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 0.4rem;
|
|
89
|
+
min-inline-size: 0;
|
|
90
|
+
}
|
|
91
|
+
.ed-color {
|
|
92
|
+
flex: 0 0 auto;
|
|
93
|
+
--color-swatch-size: 1.8rem;
|
|
94
|
+
}
|
|
95
|
+
.ed-out {
|
|
96
|
+
flex: 0 0 4.5rem;
|
|
97
|
+
font-family: var(--font-mono, monospace);
|
|
98
|
+
font-size: 0.9em;
|
|
99
|
+
text-align: end;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Toolbar: a segmented light/dark pair plus actions. */
|
|
103
|
+
.ed-toolbar {
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
gap: 0.4rem;
|
|
106
|
+
position: sticky;
|
|
107
|
+
inset-block-start: 0;
|
|
108
|
+
z-index: 1;
|
|
109
|
+
padding-block: 0.5rem;
|
|
110
|
+
background: var(--surface-bg);
|
|
111
|
+
}
|
|
112
|
+
.ed-segwrap {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
}
|
|
115
|
+
.ed-seg.is-active {
|
|
116
|
+
background: var(--ui-primary-solid);
|
|
117
|
+
color: var(--ui-primary-on-solid);
|
|
118
|
+
}
|
|
119
|
+
.ed-danger {
|
|
120
|
+
color: var(--danger-text);
|
|
121
|
+
}
|
|
122
|
+
}
|
package/css/utilities.css
CHANGED
|
@@ -311,13 +311,15 @@
|
|
|
311
311
|
Animate the UA-generated content box directly; interpolate-size (Layer 2)
|
|
312
312
|
lets block-size reach `auto`. Guarded — engines without ::details-content just
|
|
313
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. */
|
|
314
319
|
@supports selector(::details-content) {
|
|
315
320
|
details::details-content {
|
|
316
321
|
overflow: clip;
|
|
317
322
|
block-size: 0;
|
|
318
|
-
transition:
|
|
319
|
-
block-size var(--reveal-duration) var(--interact-easing, ease),
|
|
320
|
-
content-visibility var(--reveal-duration) allow-discrete;
|
|
321
323
|
}
|
|
322
324
|
details[open]::details-content {
|
|
323
325
|
block-size: auto;
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`vitops:ds-overrides`,t=`/__vitops/design-system`,n=(e,t)=>t.startsWith(`stop-`)?`--color-${e}-${t.slice(5)}`:`--${e}-${t}`,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=t===`surface`?`surface`:`default`,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[e,n]of this.roles)this.deepSet(t,`colors.roles.${e}`,n);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);
|
package/dist/elements.js
CHANGED
|
@@ -543,7 +543,7 @@ let Tt=!1;const Et=new Y.subtle.Watcher(async()=>{Tt||(Tt=!0,queueMicrotask(()=>
|
|
|
543
543
|
>
|
|
544
544
|
<span class=${e} part="icon" aria-hidden="true"></span>
|
|
545
545
|
</button>
|
|
546
|
-
`}};customElements.get(`copy-button`)||customElements.define(`copy-button`,Nt);var Pt=class extends ${static{this.properties={autoplay:{type:Number,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=!1;#r=!1;#i=null;#a=!1;#o=null;#s=!1;init(){this.autoplay=0}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`region`),this.hasAttribute(`aria-roledescription`)||this.setAttribute(`aria-roledescription`,`carousel`),this.#e=[...this.children].filter(e=>!e.hasAttribute(`slot`)),!(this.#e.length<2)&&(this.#n=this.classList.contains(`carousel--auto-pages`),this.#n||(this.#c(),this.#d(),this.addEventListener(`scrollsnapchange`,this.#p)),!this.autoplay&&this.hasAttribute(`autoplay`)&&(this.autoplay=Number(this.getAttribute(`autoplay`))||0),this.#s=matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.autoplay>0&&(this.#y(),this.#s?(this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#b()):this.#m(),this.addEventListener(`pointerenter`,this.#g),this.addEventListener(`pointerleave`,this.#_),this.addEventListener(`focusin`,this.#g),this.addEventListener(`focusout`,this.#_)))}disconnectedCallback(){super.disconnectedCallback(),this.#n||(this.removeEventListener(`scrollsnapchange`,this.#p),this.#u()),this.removeEventListener(`pointerenter`,this.#g),this.removeEventListener(`pointerleave`,this.#_),this.removeEventListener(`focusin`,this.#g),this.removeEventListener(`focusout`,this.#_),this.#h(),this.#o&&=(this.#o.removeEventListener(`click`,this.#v),this.#o.hasAttribute(`slot`)||this.#o.remove(),null),this.removeAttribute(`aria-live`)}#c(){let e=[],t=[];for(let n=0;n<this.#e.length;n++){let r=this.#e[n];e.push(this.#l(r,n)),t.push(this.#l(r,n))}for(let t=e.length-1;t>=0;t--)this.prepend(e[t]);for(let e of t)this.append(e);this.#t=[...e,...t]}#l(e,t){let n=e.cloneNode(!0);n.setAttribute(`data-carousel-clone`,String(t)),n.setAttribute(`aria-hidden`,`true`);for(let e of[n,...n.querySelectorAll(`[tabindex], a, button, input, select, textarea`)])e.tabIndex=-1;return n}#u(){for(let e of this.#t)e.remove();this.#t=[]}#d(){requestAnimationFrame(()=>{let e=this.#e[0];e&&(this.style.scrollBehavior=`auto`,this.scrollLeft=e.offsetLeft-this.offsetLeft-(parseFloat(getComputedStyle(this).scrollPaddingLeft)||0)+(parseFloat(getComputedStyle(this).paddingLeft)||0),requestAnimationFrame(()=>{this.style.scrollBehavior=``}))})}#f(){this.scrollBy({left:this.clientWidth,behavior:`smooth`})}#p=e=>{if(this.#r){this.#r=!1;return}let t=e.snapTargetInline;if(!t||!t.hasAttribute(`data-carousel-clone`))return;let n=Number(t.getAttribute(`data-carousel-clone`)),r=this.#e[n];if(!r)return;let i=r.offsetLeft-t.offsetLeft;this.#r=!0,this.style.scrollBehavior=`auto`,this.scrollLeft+=i,requestAnimationFrame(()=>{this.style.scrollBehavior=``})};#m(){this.#i||this.autoplay<=0||(this.#a=!1,this.#i=setInterval(()=>this.#f(),this.autoplay),this.setAttribute(`aria-live`,`off`),this.#b())}#h(){this.#i&&=(clearInterval(this.#i),null)}#g=()=>{this.#h(),this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#b()};#_=()=>{this.#s||this.#m()};#v=()=>{this.#a?(this.#s=!1,this.#m()):this.#g()};#y(){let e=this.querySelector(`[slot="autoplay"]`);if(e)this.#o=e;else{let e=document.createElement(`button`);e.type=`button`,this.append(e),this.#o=e}this.#o.setAttribute(`part`,`autoplay`),this.#o.addEventListener(`click`,this.#v),this.#b()}#b(){this.#o&&(this.#o.setAttribute(`aria-label`,this.#a?`Play`:`Pause`),this.#o.setAttribute(`aria-pressed`,String(!this.#a)),this.#o.hasAttribute(`slot`)||(this.#o.textContent=this.#a?`▶`:`⏸`))}};customElements.get(`wc-carousel`)||customElements.define(`wc-carousel`,Pt);var Ft=class extends ${static{this.properties={breakpoint:{type:String,reflect:!0},projection:{type:Boolean,reflect:!0},singular:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=[];#r=null;#i=null;#a=null;#o=[];#s=[];#c=0;#l=!1;#u=null;#d=0;init(){this.breakpoint=`40rem`,this.projection=!1,this.singular=!1}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),this.#f(),this.#e.length!==0&&(this.#m(),this.projection&&(this.#p(),this.#h(),this.#g(),this.singular&&(this.classList.add(`entries--singular`),this.#v(0))),this.#d=this.#b(),this.#x())}disconnectedCallback(){super.disconnectedCallback(),this.#u?.disconnect(),this.#u=null,this.#a?.remove(),this.#a=null,this.#o=[],this.#i?this.#i.remove():this.#r?.remove(),this.#r=null,this.#i=null,this.#s=[],this.classList.remove(`entries--wide`,`entries--projection`,`entries--singular`)}#f(){this.#e=[];let e=new Set,t=new Map,n=null;for(let r of this.children){let i=r.tagName.toLowerCase();if(/^h[1-6]$/.test(i))n=r;else if(i===`dl`&&n){let i=new Map,a=r.querySelectorAll(`dt`),o=r.querySelectorAll(`dd`);for(let n=0;n<a.length;n++){let r=a[n],s=r.textContent?.trim()??``,c=o[n]?.innerHTML??``;if(s){i.set(s,c),e.add(s);let n=r.getAttribute(`data-label`);n&&!t.has(s)&&t.set(s,n)}}this.#e.push({title:n.innerHTML,heading:n,dl:r,properties:i}),n=null}}this.#t=[...e],this.#n=this.#t.map(e=>t.get(e)??e)}#p(){this.classList.add(`entries--projection`)}#m(){let e=document.createElement(`table`);e.classList.add(`entries__table`),e.setAttribute(`aria-hidden`,`true`);let t=document.createElement(`thead`),n=document.createElement(`tr`),r=document.createElement(`th`);r.textContent=``,n.appendChild(r),this.#s=[];for(let e of this.#t){let t=document.createElement(`th`);t.textContent=e,n.appendChild(t),this.#s.push(t)}t.appendChild(n),e.appendChild(t);let i=document.createElement(`tbody`);for(let e of this.#e){let t=document.createElement(`tr`),n=document.createElement(`td`);n.innerHTML=e.title,n.style.fontWeight=`600`,t.appendChild(n);for(let n of this.#t){let r=document.createElement(`td`);r.innerHTML=e.properties.get(n)??``,t.appendChild(r)}i.appendChild(t)}e.appendChild(i),this.appendChild(e),this.#r=e}#h(){if(!this.#r)return;let e=document.createElement(`div`);e.classList.add(`entries__scroll`),this.#r.replaceWith(e),e.appendChild(this.#r),this.#i=e}#g(){let e=document.createElement(`nav`);e.classList.add(`entries__nav`),e.setAttribute(`aria-label`,`Column selector`),e.setAttribute(`aria-hidden`,`true`),this.#o=this.#t.map((t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.classList.add(`entries__nav-btn`),r.textContent=this.#n[n]??t,r.setAttribute(`aria-pressed`,String(n===0)),r.addEventListener(`click`,()=>this.#_(n)),e.appendChild(r),r}),this.#i.before(e),this.#a=e}#_(e){this.#c=e;for(let t=0;t<this.#o.length;t++)this.#o[t]?.setAttribute(`aria-pressed`,String(t===e));if(this.singular)this.#v(e);else{let t=this.#s[e];t&&this.#i&&t.scrollIntoView({behavior:`smooth`,block:`nearest`,inline:`start`})}this.dispatchEvent(new CustomEvent(`entries-column-change`,{detail:{column:e,header:this.#t[e]},bubbles:!0,composed:!0}))}#v(e){if(!this.#r)return;let t=e+1;for(let e of this.#r.rows)for(let n=1;n<e.cells.length;n++){let r=e.cells[n];r&&(r.hidden=n!==t)}}#y(){if(this.#r)for(let e of this.#r.rows)for(let t=1;t<e.cells.length;t++){let n=e.cells[t];n&&(n.hidden=!1)}}#b(){let e=this.breakpoint||`40rem`,t=document.createElement(`div`);t.style.width=e,t.style.position=`absolute`,t.style.visibility=`hidden`,this.appendChild(t);let n=t.offsetWidth;return t.remove(),n}#x(){this.#u=new ResizeObserver(this.#S),this.#u.observe(this)}#S=e=>{let t=e[0];if(!t)return;let n=t.contentBoxSize?.[0]?.inlineSize??t.contentRect.width;this.#l=n>=this.#d,this.#l?(this.classList.add(`entries--wide`),this.singular&&this.projection&&this.#y()):(this.classList.remove(`entries--wide`),this.singular&&this.projection&&this.#v(this.#c))}};customElements.get(`wc-entries`)||customElements.define(`wc-entries`,Ft);var It=class extends ${static{this.styles=[$.styles,o`
|
|
546
|
+
`}};customElements.get(`copy-button`)||customElements.define(`copy-button`,Nt);var Pt=class extends ${static{this.properties={autoplay:{type:Number,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=!1;#r=!1;#i=null;#a=!1;#o=null;#s=!1;init(){this.autoplay=0}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),this.hasAttribute(`role`)||this.setAttribute(`role`,`region`),this.hasAttribute(`aria-roledescription`)||this.setAttribute(`aria-roledescription`,`carousel`),this.#e=[...this.children].filter(e=>!e.hasAttribute(`slot`)),!(this.#e.length<2)&&(this.#n=this.classList.contains(`carousel--auto-pages`),this.#n||(this.#c(),this.#d(),this.addEventListener(`scrollsnapchange`,this.#p)),!this.autoplay&&this.hasAttribute(`autoplay`)&&(this.autoplay=Number(this.getAttribute(`autoplay`))||0),this.#s=matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.autoplay>0&&(this.#y(),this.#s?(this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#b()):this.#m(),this.addEventListener(`pointerenter`,this.#g),this.addEventListener(`pointerleave`,this.#_),this.addEventListener(`focusin`,this.#g),this.addEventListener(`focusout`,this.#_)))}disconnectedCallback(){super.disconnectedCallback(),this.#n||(this.removeEventListener(`scrollsnapchange`,this.#p),this.#u()),this.removeEventListener(`pointerenter`,this.#g),this.removeEventListener(`pointerleave`,this.#_),this.removeEventListener(`focusin`,this.#g),this.removeEventListener(`focusout`,this.#_),this.#h(),this.#o&&=(this.#o.removeEventListener(`click`,this.#v),this.#o.hasAttribute(`slot`)||this.#o.remove(),null),this.removeAttribute(`aria-live`)}#c(){let e=[],t=[];for(let n=0;n<this.#e.length;n++){let r=this.#e[n];e.push(this.#l(r,n)),t.push(this.#l(r,n))}for(let t=e.length-1;t>=0;t--)this.prepend(e[t]);for(let e of t)this.append(e);this.#t=[...e,...t]}#l(e,t){let n=e.cloneNode(!0);n.setAttribute(`data-carousel-clone`,String(t)),n.setAttribute(`aria-hidden`,`true`);for(let e of[n,...n.querySelectorAll(`[tabindex], a, button, input, select, textarea`)])e.tabIndex=-1;return n}#u(){for(let e of this.#t)e.remove();this.#t=[]}#d(){requestAnimationFrame(()=>{let e=this.#e[0];e&&(this.style.scrollBehavior=`auto`,this.scrollLeft=e.offsetLeft-this.offsetLeft-(parseFloat(getComputedStyle(this).scrollPaddingLeft)||0)+(parseFloat(getComputedStyle(this).paddingLeft)||0),requestAnimationFrame(()=>{this.style.scrollBehavior=``}))})}#f(){this.scrollBy({left:this.clientWidth,behavior:`smooth`})}#p=e=>{if(this.#r){this.#r=!1;return}let t=e.snapTargetInline;if(!t||!t.hasAttribute(`data-carousel-clone`))return;let n=Number(t.getAttribute(`data-carousel-clone`)),r=this.#e[n];if(!r)return;let i=r.offsetLeft-t.offsetLeft;this.#r=!0,this.style.scrollBehavior=`auto`,this.scrollLeft+=i,requestAnimationFrame(()=>{this.style.scrollBehavior=``})};#m(){this.#i||this.autoplay<=0||(this.#a=!1,this.#i=setInterval(()=>this.#f(),this.autoplay),this.setAttribute(`aria-live`,`off`),this.#b())}#h(){this.#i&&=(clearInterval(this.#i),null)}#g=()=>{this.#h(),this.#a=!0,this.setAttribute(`aria-live`,`polite`),this.#b()};#_=()=>{this.#s||this.#m()};#v=()=>{this.#a?(this.#s=!1,this.#m()):this.#g()};#y(){let e=this.querySelector(`[slot="autoplay"]`);if(e)this.#o=e;else{let e=document.createElement(`button`);e.type=`button`,this.append(e),this.#o=e}this.#o.setAttribute(`part`,`autoplay`),this.#o.addEventListener(`click`,this.#v),this.#b()}#b(){this.#o&&(this.#o.setAttribute(`aria-label`,this.#a?`Play`:`Pause`),this.#o.setAttribute(`aria-pressed`,String(!this.#a)),this.#o.hasAttribute(`slot`)||(this.#o.textContent=this.#a?`▶`:`⏸`))}};customElements.get(`wc-carousel`)||customElements.define(`wc-carousel`,Pt);var Ft=class extends ${static{this.properties={breakpoint:{type:String,reflect:!0},projection:{type:Boolean,reflect:!0},singular:{type:Boolean,reflect:!0}}}createRenderRoot(){return this}#e=[];#t=[];#n=[];#r=null;#i=null;#a=null;#o=[];#s=[];#c=0;#l=!1;#u=null;#d=0;init(){this.breakpoint=`40rem`,this.projection=!1,this.singular=!1}constructor(){super(),this.init()}connectedCallback(){super.connectedCallback(),this.#f(),this.#e.length!==0&&(this.#m(),this.projection&&(this.#p(),this.#h(),this.#g(),this.singular&&(this.classList.add(`entries--singular`),this.#v(0))),this.#d=this.#b(),this.#x())}disconnectedCallback(){super.disconnectedCallback(),this.#u?.disconnect(),this.#u=null,this.#a?.remove(),this.#a=null,this.#o=[],this.#i?this.#i.remove():this.#r?.remove(),this.#r=null,this.#i=null,this.#s=[],this.classList.remove(`entries--wide`,`entries--projection`,`entries--singular`)}#f(){this.#e=[];let e=new Set,t=new Map,n=null;for(let r of this.children){let i=r.tagName.toLowerCase();if(/^h[1-6]$/.test(i))n=r;else if(i===`dl`&&n){let i=new Map,a=r.querySelectorAll(`dt`),o=r.querySelectorAll(`dd`);for(let n=0;n<a.length;n++){let r=a[n],s=r.textContent?.trim()??``,c=o[n]?.innerHTML??``;if(s){i.set(s,c),e.add(s);let n=r.getAttribute(`data-label`);n&&!t.has(s)&&t.set(s,n)}}this.#e.push({title:n.innerHTML,heading:n,dl:r,properties:i}),n=null}}this.#t=[...e],this.#n=this.#t.map(e=>t.get(e)??e)}#p(){this.classList.add(`entries--projection`)}#m(){let e=document.createElement(`table`);e.classList.add(`entries__table`),e.setAttribute(`aria-hidden`,`true`);let t=document.createElement(`thead`),n=document.createElement(`tr`),r=document.createElement(`th`);r.textContent=``,n.appendChild(r),this.#s=[];for(let e of this.#t){let t=document.createElement(`th`);t.textContent=e,n.appendChild(t),this.#s.push(t)}t.appendChild(n),e.appendChild(t);let i=document.createElement(`tbody`);for(let e of this.#e){let t=document.createElement(`tr`),n=document.createElement(`td`);n.innerHTML=e.title,n.style.fontWeight=`600`,t.appendChild(n);for(let n of this.#t){let r=document.createElement(`td`);r.innerHTML=e.properties.get(n)??``,t.appendChild(r)}i.appendChild(t)}e.appendChild(i),this.appendChild(e),this.#r=e}#h(){if(!this.#r)return;let e=document.createElement(`div`);e.classList.add(`entries__scroll`),this.#r.replaceWith(e),e.appendChild(this.#r),this.#i=e}#g(){let e=document.createElement(`nav`);e.classList.add(`entries__nav`),e.setAttribute(`aria-label`,`Column selector`),e.setAttribute(`aria-hidden`,`true`),this.#o=this.#t.map((t,n)=>{let r=document.createElement(`button`);return r.type=`button`,r.classList.add(`entries__nav-btn`),r.textContent=this.#n[n]??t,r.setAttribute(`aria-pressed`,String(n===0)),r.addEventListener(`click`,()=>this.#_(n)),e.appendChild(r),r}),this.#i.before(e),this.#a=e}#_(e){this.#c=e;for(let t=0;t<this.#o.length;t++)this.#o[t]?.setAttribute(`aria-pressed`,String(t===e));if(this.singular)this.#v(e);else{let t=this.#s[e];t&&this.#i&&t.scrollIntoView({behavior:`smooth`,block:`nearest`,inline:`start`})}this.dispatchEvent(new CustomEvent(`entries-column-change`,{detail:{column:e,header:this.#t[e]},bubbles:!0,composed:!0}))}#v(e){if(!this.#r)return;let t=e+1;for(let e of this.#r.rows)for(let n=1;n<e.cells.length;n++){let r=e.cells[n];r&&(r.hidden=n!==t)}}#y(){if(this.#r)for(let e of this.#r.rows)for(let t=1;t<e.cells.length;t++){let n=e.cells[t];n&&(n.hidden=!1)}}#b(){let e=this.breakpoint||`40rem`,t=document.createElement(`div`);t.style.width=e,t.style.position=`absolute`,t.style.visibility=`hidden`,this.appendChild(t);let n=t.offsetWidth;return t.remove(),n}#x(){this.#u=new ResizeObserver(this.#S),this.#u.observe(this)}#S=e=>{let t=e[0];if(!t)return;let n=t.contentBoxSize?.[0]?.inlineSize??t.contentRect.width;this.#l=n>=this.#d,this.#l?(this.classList.add(`entries--wide`),this.singular&&this.projection&&this.#y()):(this.classList.remove(`entries--wide`),this.singular&&this.projection&&this.#v(this.#c))}};customElements.get(`wc-entries`)||customElements.define(`wc-entries`,Ft);var It=class e extends ${static{this.styles=[$.styles,o`
|
|
547
547
|
:host {
|
|
548
548
|
display: none;
|
|
549
549
|
}
|
|
@@ -641,7 +641,7 @@ let Tt=!1;const Et=new Y.subtle.Watcher(async()=>{Tt||(Tt=!0,queueMicrotask(()=>
|
|
|
641
641
|
transition: none;
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
|
-
`]}static{this.properties={scheme:{type:String,reflect:!0}}}constructor(){super(),this._mediaQuery=null,this._boundMediaHandler=null,this.init()}init(){this.scheme
|
|
644
|
+
`]}static{this.properties={scheme:{type:String,reflect:!0}}}constructor(){super(),this._mediaQuery=null,this._boundMediaHandler=null,this.init()}init(){this.scheme=e.readStored()??`system`}static{this.STORAGE_KEY=`vitops-color-scheme`}static readStored(){try{let t=localStorage.getItem(e.STORAGE_KEY);return t===`light`||t===`dark`||t===`system`?t:null}catch{return null}}_persist(){try{this.scheme===`system`?localStorage.removeItem(e.STORAGE_KEY):localStorage.setItem(e.STORAGE_KEY,this.scheme)}catch{}}connectedCallback(){super.connectedCallback(),this._mediaQuery=window.matchMedia(`(prefers-color-scheme: dark)`),this._boundMediaHandler=this._handleMediaChange.bind(this),this._mediaQuery.addEventListener(`change`,this._boundMediaHandler),this._applyColorScheme()}disconnectedCallback(){super.disconnectedCallback(),this._mediaQuery&&this._boundMediaHandler&&this._mediaQuery.removeEventListener(`change`,this._boundMediaHandler)}_handleMediaChange(e){this.scheme===`system`&&this.requestUpdate()}_applyColorScheme(){let e=document.documentElement;this.scheme===`system`?(e.style.removeProperty(`color-scheme`),delete e.dataset.theme):(e.style.colorScheme=`${this.scheme} only`,e.dataset.theme=this.scheme)}_handleSegmentClick(e){this.scheme!==e&&(this.scheme=e,this._applyColorScheme(),this._persist(),this.dispatchEvent(new CustomEvent(`scheme-change`,{detail:{scheme:this.scheme},bubbles:!0,composed:!0})))}updated(e){e.has(`scheme`)&&this._applyColorScheme()}render(){return Q`
|
|
645
645
|
<div class="toggle__group" role="group" aria-label="Color scheme" part="group">
|
|
646
646
|
<button
|
|
647
647
|
class="toggle__segment"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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
|
+
"./editor": "./dist/editor.js",
|
|
14
15
|
"./polyfills": "./dist/polyfills.js",
|
|
15
16
|
"./deferred": "./dist/deferred.js",
|
|
16
17
|
"./css/*": "./css/*",
|
|
@@ -30,6 +31,6 @@
|
|
|
30
31
|
"lit": "^3.3.3",
|
|
31
32
|
"scroll-timeline-polyfill": "^1.1.0",
|
|
32
33
|
"temporal-polyfill": "^1.0.1",
|
|
33
|
-
"@getvitops/utils": "0.
|
|
34
|
+
"@getvitops/utils": "0.9.0"
|
|
34
35
|
}
|
|
35
36
|
}
|