@takazudo/zdtp 0.4.4 → 0.4.5
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 +22 -0
- package/README.md +113 -12
- package/dist/apply/apply-token-overrides.d.ts +139 -32
- package/dist/astro/host-adapter.js +39 -39
- package/dist/astro/index.js +1 -1
- package/dist/{autoload-state-CmhI7q9j.js → autoload-state-DF7TsTgY.js} +1 -1
- package/dist/bin/server.js +116 -112
- package/dist/config/panel-config.d.ts +36 -1
- package/dist/export-modal.d.ts +5 -3
- package/dist/import-modal.d.ts +9 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2239 -2219
- package/dist/load-routing-LJ72261l.js +421 -0
- package/dist/{panel-config-CXTCcYQs.js → panel-config-CipeKMTz.js} +286 -234
- package/dist/server/create-apply-handler.d.ts +6 -0
- package/dist/server/index.js +1 -1
- package/dist/state/tweak-state.d.ts +137 -14
- package/dist/testing.js +5 -5
- package/dist/tokens/tier-model.d.ts +12 -0
- package/dist/tweak-state-xt-tSQ_t.js +1958 -0
- package/dist/utils/design-token-serde.d.ts +20 -12
- package/package.json +1 -1
- package/dist/load-routing-DtNTKoLW.js +0 -353
- package/dist/tweak-state-BeXkzoj8.js +0 -1858
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.5
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Per-scheme/per-mode keyed color persistence — envelope v4 ([#509](https://github.com/Takazudo/zudo-design-token-panel/issues/509)) (653c4ab)
|
|
8
|
+
- Distinguish declared-outside-scanned-block from genuinely-absent unknowns ([#508](https://github.com/Takazudo/zudo-design-token-panel/issues/508)) (441fa93)
|
|
9
|
+
- Scan top-level `@theme` blocks in `applyTokenOverrides` ([#507](https://github.com/Takazudo/zudo-design-token-panel/issues/507)) (d6c6692)
|
|
10
|
+
- Add `colorExtras.semanticDefaults` config-time override map ([#499](https://github.com/Takazudo/zudo-design-token-panel/issues/499)) (543136e)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Harden v4 persistence load edge cases ([#509](https://github.com/Takazudo/zudo-design-token-panel/issues/509) audit, epic [#502](https://github.com/Takazudo/zudo-design-token-panel/issues/502) review) (2b07460)
|
|
15
|
+
- Scope color-scheme clearing to panel-written values (closes [#506](https://github.com/Takazudo/zudo-design-token-panel/issues/506)) (f434c8c)
|
|
16
|
+
- Validate persisted semantic index mappings against live cluster paletteSize ([#503](https://github.com/Takazudo/zudo-design-token-panel/issues/503)) (915ffbf)
|
|
17
|
+
|
|
18
|
+
### Other Changes
|
|
19
|
+
|
|
20
|
+
- docs: sync README + doc-site with `@theme` apply, v4 persistence, schemaId ([#511](https://github.com/Takazudo/zudo-design-token-panel/issues/511)) (3c3fd57)
|
|
21
|
+
- test: cross-cutting interaction coverage for [#510](https://github.com/Takazudo/zudo-design-token-panel/issues/510) confirm pass (9a5d7fe)
|
|
22
|
+
- docs: truth-up schemaId docs, tighten Import modal copy, export `SCHEMA_V1`/`SCHEMA_V2`/`SCHEMA_V3` ([#505](https://github.com/Takazudo/zudo-design-token-panel/issues/505)) (8088281)
|
|
23
|
+
- docs: replace zmodular references with Takazudo Modular markdown links (afcfea2)
|
|
24
|
+
|
|
3
25
|
## 0.4.4
|
|
4
26
|
|
|
5
27
|
### Features
|
package/README.md
CHANGED
|
@@ -161,6 +161,8 @@ export const panelConfig: PanelConfig = {
|
|
|
161
161
|
|
|
162
162
|
For the full token-overrides payload schema and the `PanelConfig` shape, see §5 and §6.
|
|
163
163
|
|
|
164
|
+
**Which CSS blocks the rewriter scans.** Each routed file's rewrite only reaches two locations: the FIRST top-level `:root { ... }` block and the FIRST top-level `@theme { ... }` block (bare `@theme`, or with one modifier such as `@theme inline` — the shape Tailwind v4 prescribes when theme values reference other variables). Later blocks of either kind, and anything nested under `@media` / `@layer` / `@supports`, are not scanned. A cssVar declared in BOTH blocks is rewritten only in `:root` (`:root`-first-match-wins). A file with neither block returns a 409 — see §11 for the Tailwind `@theme` worked example, and [Apply pipeline reference](/docs/reference/apply-pipeline) for the full response-field contract, including the `unknownOutsideBlockCssVars` diagnostic for a cssVar that's declared somewhere in the file but outside both scanned blocks.
|
|
165
|
+
|
|
164
166
|
### 3.3 Security model
|
|
165
167
|
|
|
166
168
|
The bin is **dev-only** and is built with three independent guards:
|
|
@@ -273,7 +275,7 @@ export const myPanelConfig: PanelConfig = {
|
|
|
273
275
|
storagePrefix: 'myapp-design-token-panel',
|
|
274
276
|
consoleNamespace: 'myapp',
|
|
275
277
|
modalClassPrefix: 'myapp-design-token-panel-modal',
|
|
276
|
-
schemaId: 'myapp-design-tokens/v1',
|
|
278
|
+
schemaId: 'myapp-design-tokens/v1', // display-only label — see §5.3; import/export always use the canonical SCHEMA_V1/V2/V3
|
|
277
279
|
exportFilenameBase: 'myapp-design-tokens',
|
|
278
280
|
tabs: [spacingTab /*, fontTab, sizeTab, colorTab, ... */],
|
|
279
281
|
};
|
|
@@ -477,7 +479,7 @@ export interface PanelInstanceHandle {
|
|
|
477
479
|
| `storagePrefix` | `string` | Base for every derived `localStorage` key. Also the instance id. See §9. |
|
|
478
480
|
| `consoleNamespace` | `string` | Global object the package installs `showDesignPanel` / `hideDesignPanel` / `toggleDesignPanel` on (e.g. `consoleNamespace: 'myapp'` → `window.myapp.showDesignPanel`). |
|
|
479
481
|
| `modalClassPrefix` | `string` | BEM root class for every modal the panel owns (export, import, apply). Emits `${prefix}__overlay`, `${prefix}__panel`, etc. |
|
|
480
|
-
| `schemaId` | `string` |
|
|
482
|
+
| `schemaId` | `string` | **Display-only** label surfaced in the Import modal's copy. `serialize()` always emits the canonical `SCHEMA_V2`/`SCHEMA_V3` and `deserialize()` always validates against the canonical `SCHEMA_V1`/`V2`/`V3` — neither reads this field. See §14's migration recipe. |
|
|
481
483
|
| `exportFilenameBase` | `string` | Default download filename base — exports save as `${exportFilenameBase}.json`. |
|
|
482
484
|
| `toggleEvent` | `string` (optional) | Window-event name that toggles THIS instance. Defaults to `toggle-${storagePrefix}` for non-default instances; the default instance keeps `toggle-design-token-panel`. |
|
|
483
485
|
| `tabs` | `readonly TabConfig[]` | **Required.** Tab strip data — each entry is a tab with one or more `TierConfig` objects. The color tab (id `'color'`) additionally requires `colorExtras`. See §6. |
|
|
@@ -533,7 +535,7 @@ const handle = configurePanel({
|
|
|
533
535
|
The Astro entry point (`<DesignTokenPanelHost>`) handles mounting for you. Internally:
|
|
534
536
|
|
|
535
537
|
- The console API (`showDesignPanel` etc.) is **always installed eagerly**, even when the panel module has not loaded — calling them is what triggers the lazy import for cold-start users.
|
|
536
|
-
- The panel module is **dynamically imported on first need**: when the user calls a console helper, OR when first-paint detects any of four gate signals in `localStorage` — `${storagePrefix}:visible` set to `1`, persisted overrides (`${storagePrefix}-state-v3`), the owner-autoload flag (`${storagePrefix}:autoload` set to `1`), or the element-path inspector enabled.
|
|
538
|
+
- The panel module is **dynamically imported on first need**: when the user calls a console helper, OR when first-paint detects any of four gate signals in `localStorage` — `${storagePrefix}:visible` set to `1`, persisted overrides (checked as `${storagePrefix}-state-v4`, falling back to `-state-v3` / `-state-v2` for pre-migration sessions — see §9), the owner-autoload flag (`${storagePrefix}:autoload` set to `1`), or the element-path inspector enabled.
|
|
537
539
|
- This gating keeps the panel out of the initial JS bundle for first-time visitors while still re-applying overrides on hard reload for users who have tweaked things. **General visitors** (none of the four signals set) pay zero bundle cost.
|
|
538
540
|
|
|
539
541
|
For a Vite-only / non-Astro host, mount it yourself by importing the adapter module after `configurePanel(...)`. See §11.5.
|
|
@@ -693,6 +695,13 @@ export interface ColorClusterExtras {
|
|
|
693
695
|
colorScheme: string;
|
|
694
696
|
colorMode: false | { defaultMode: 'light' | 'dark'; lightScheme: string; darkScheme: string };
|
|
695
697
|
};
|
|
698
|
+
/**
|
|
699
|
+
* Optional config-time override map for a semantic tier's derived defaults,
|
|
700
|
+
* keyed by semantic item id. This is the ONLY way to ship a `{ literal }` /
|
|
701
|
+
* `{ literal: { light, dark } }` / `{ ref }` default — `TierItem.default`
|
|
702
|
+
* itself stays a plain string, consumed generically by every tab kind.
|
|
703
|
+
*/
|
|
704
|
+
semanticDefaults?: Record<string, SemanticValue>;
|
|
696
705
|
}
|
|
697
706
|
|
|
698
707
|
export interface ColorScheme {
|
|
@@ -712,6 +721,30 @@ export interface ColorScheme {
|
|
|
712
721
|
> re-exported from the package root:
|
|
713
722
|
> `import type { ColorClusterConfig } from '@takazudo/zdtp'`.
|
|
714
723
|
|
|
724
|
+
**`colorExtras.semanticDefaults`.** Without this field, a semantic tier's default `SemanticValue` is DERIVED from each `TierItem.default` string (palette-index lookup, or a literal fallback). `semanticDefaults` lets a host override that derivation for specific semantic item ids and ship a default the plain-string `default` field can't express — most usefully a per-mode literal that resolves via CSS `light-dark()`:
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
const colorTab: TabConfig = {
|
|
728
|
+
id: 'color',
|
|
729
|
+
label: 'Color',
|
|
730
|
+
tiers: [paletteTier, semanticTier],
|
|
731
|
+
colorExtras: {
|
|
732
|
+
id: 'myapp',
|
|
733
|
+
baseRoles: { background: '--myapp-bg', foreground: '--myapp-fg' },
|
|
734
|
+
baseDefaults: {},
|
|
735
|
+
defaultShikiTheme: 'dracula',
|
|
736
|
+
colorSchemes: { /* ... */ },
|
|
737
|
+
panelSettings: { colorScheme: 'Default', colorMode: false },
|
|
738
|
+
semanticDefaults: {
|
|
739
|
+
// Keyed by the semantic TierItem's `id`, not its cssVar.
|
|
740
|
+
danger: { literal: { light: '#b91c1c', dark: '#f87171' } },
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
A key present in `semanticDefaults` wins verbatim over the derived value; keys not listed fall back to the normal derivation from `TierItem.default`.
|
|
747
|
+
|
|
715
748
|
### 7.2 JSON-serializable constraint (important)
|
|
716
749
|
|
|
717
750
|
**Every field on the color `TabConfig` (including `colorExtras` and every
|
|
@@ -888,9 +921,10 @@ Behaviour notes:
|
|
|
888
921
|
|
|
889
922
|
| Logical key | Derivation | Purpose |
|
|
890
923
|
| ----------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
891
|
-
| `state-
|
|
892
|
-
| `state-
|
|
893
|
-
| `state-
|
|
924
|
+
| `state-v4` | `${storagePrefix}-state-v4` | Current unified envelope. Same top-level slices as v3, EXCEPT `color` (and optional `secondary`) is an identity-keyed map — one `ColorTweakState` slot per active-scheme identity — instead of a single flat object. See "Per-scheme color persistence" below. |
|
|
925
|
+
| `state-v3` | `${storagePrefix}-state-v3` | Legacy pre-v4 format (flat, single-slot `color`). Migrated into `state-v4` on first load; the v3 key itself is left in place (not deleted) so a downgrade can still read it. |
|
|
926
|
+
| `state-v2` | `${storagePrefix}-state-v2` | Legacy pre-v3 format. Migrated into `state-v3` (and from there into `state-v4`) on first load, then deleted. |
|
|
927
|
+
| `state-v1` | `${storagePrefix}-state` | Legacy pre-v2 flat-state format (Color-only). Migrated into `state-v3` (and from there into `state-v4`) on first load, then deleted. |
|
|
894
928
|
| `open` | `${storagePrefix}-open` | Mirror of the panel's `open` boolean (synchronous mount-time read — preserves user intent across reloads, fixes #1549). |
|
|
895
929
|
| `position` | `${storagePrefix}-position` | Drag position `{ top, right }` so the panel reappears where the user left it. |
|
|
896
930
|
| `visible` | `${storagePrefix}:visible` | Adapter-level visibility-intent flag, owned by the lazy-load gate. |
|
|
@@ -899,6 +933,7 @@ Behaviour notes:
|
|
|
899
933
|
For example, with `storagePrefix: 'myapp-design-token-panel'`:
|
|
900
934
|
|
|
901
935
|
```
|
|
936
|
+
myapp-design-token-panel-state-v4
|
|
902
937
|
myapp-design-token-panel-state-v3
|
|
903
938
|
myapp-design-token-panel-state-v2
|
|
904
939
|
myapp-design-token-panel-state
|
|
@@ -912,18 +947,40 @@ myapp-design-token-panel:autoload
|
|
|
912
947
|
|
|
913
948
|
The `visible` and `autoload` keys use a literal `:` separator, not `-`. Every other derived key uses `-`. The colon form is intentional for these adapter-level flags — see [`PORTABLE-CONTRACT.md`](./PORTABLE-CONTRACT.md) §2 for the historical reason. Don't try to "normalize" them; the unit tests assert this specific shape.
|
|
914
949
|
|
|
915
|
-
###
|
|
950
|
+
### Per-scheme color persistence (v4 envelope)
|
|
916
951
|
|
|
917
|
-
|
|
952
|
+
Color/secondary tweaks are persisted **per (scheme, mode) identity**, inside the single `state-v4` storage key — there is no per-scheme key fan-out. The identity is exactly the active scheme name the panel already resolves for seeding (`getActiveSchemeName`): a cluster without `colorMode` resolves one constant identity (one slot, effectively the old global behavior); a cluster with light/dark `colorMode` resolves a distinct identity per side, so a light tweak and a dark tweak occupy independent slots in the same envelope:
|
|
918
953
|
|
|
919
|
-
|
|
954
|
+
```jsonc
|
|
955
|
+
{
|
|
956
|
+
"color": {
|
|
957
|
+
"Default Light": { "palette": [...], "semanticMappings": {...}, /* ... */ },
|
|
958
|
+
"Default Dark": { "palette": [...], "semanticMappings": {...}, /* ... */ }
|
|
959
|
+
},
|
|
960
|
+
"secondary": { "Default Light": { /* ... */ } }, // optional, same identity keying
|
|
961
|
+
"spacing": { /* ... */ },
|
|
962
|
+
"typography": { /* ... */ },
|
|
963
|
+
"size": { /* ... */ }
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
The `spacing` / `typography` / `size` (and generic `tabs`) slices stay **global and unkeyed**, exactly as in v1–v3 — a scheme/mode toggle never touches them.
|
|
920
968
|
|
|
921
|
-
|
|
969
|
+
When the host dispatches a `color-scheme-changed` event (e.g. a light/dark toggle), the panel re-seeds its live color state for the newly active identity:
|
|
970
|
+
|
|
971
|
+
- If that identity already has a persisted slot, the slot's stored `color` (and `secondary`) state is loaded and re-applied — a per-scheme tweak now **survives** a round-trip through another scheme and back.
|
|
972
|
+
- If the identity has no slot yet (never tweaked under this scheme before), the panel cold-seeds from that scheme's defaults instead — the pre-existing "adopt the new scheme's palette" behavior for a scheme you haven't touched.
|
|
973
|
+
|
|
974
|
+
Editing color under scheme A only ever overwrites scheme A's slot (`writeMergedV4` merge-saves: it reads the existing envelope, replaces just the active identity's slot, and preserves every other identity's slot untouched) — it never mutates scheme B's stored state. A full panel reset (Reset / Apply) still wipes every slice via `clearAppliedStyles`.
|
|
975
|
+
|
|
976
|
+
**Migration.** On first load with no `state-v4` key, the legacy v1/v2/v3 chain runs unchanged (v3 wins over v2 over v1, exactly as before) and the resulting single flat `TweakState` is filed into `state-v4` under whichever identity is active at that moment — every subsequent load reads `state-v4` first. The legacy v3/v2/v1 keys are left in place by the v4 migration step (only the v1→v2→v3 chain deletes as it goes), so a host that needs to downgrade can still read them.
|
|
977
|
+
|
|
978
|
+
**Host-owned `color-scheme` is never touched.** A host that manages its own `<html style="color-scheme">` (e.g. a site-level light/dark toggle) has that inline style tracked separately from the panel's own writes; the panel's mount/clear paths only ever remove a `color-scheme` value that the panel itself applied, never a host-owned one (#501).
|
|
922
979
|
|
|
923
980
|
Two consequences worth knowing as a host integrator:
|
|
924
981
|
|
|
925
|
-
- The event handler does not rewrite
|
|
926
|
-
- Do **not** manually delete the tweak keys on a scheme toggle. The panel already
|
|
982
|
+
- The event handler does not rewrite `localStorage` directly beyond what's described above — it reads/re-seeds the live (in-memory) state and clears the inline overrides for the previous identity. The next in-panel edit (or a full reload via `loadPersistedState`) is what persists the current identity's state.
|
|
983
|
+
- Do **not** manually delete the tweak keys on a scheme toggle. The panel already handles per-identity persistence on the event, and a manual envelope delete would also discard the scheme-independent `spacing` / `typography` / `size` overrides.
|
|
927
984
|
|
|
928
985
|
---
|
|
929
986
|
|
|
@@ -1032,6 +1089,39 @@ Because the fallback ladder is host-CSS-var-driven, a brand-new consumer can mou
|
|
|
1032
1089
|
|
|
1033
1090
|
The CSS variables the panel **writes to** (the `cssVar` field on each `TokenDef`, the cluster's `paletteCssVarTemplate`, base-role names, and semantic CSS names) are entirely consumer-controlled. The package never reads those consumer CSS variables; it only writes through `setProperty` on `:root`.
|
|
1034
1091
|
|
|
1092
|
+
### 11.2 Apply-to-disk for a Tailwind v4 `@theme` block
|
|
1093
|
+
|
|
1094
|
+
The section above is about the panel's OWN chrome — it never needed Tailwind. This section is about the opposite direction: a Tailwind v4 **consumer** whose design tokens live inside `@theme { ... }` (so Tailwind can generate utility classes from them) rather than `:root`.
|
|
1095
|
+
|
|
1096
|
+
The apply-to-disk rewriter (§3.2) scans the first top-level `:root` block AND the first top-level `@theme` block, so a Tailwind v4 token file like this applies out of the box (issue #496's repro):
|
|
1097
|
+
|
|
1098
|
+
```css
|
|
1099
|
+
:root {
|
|
1100
|
+
--palette-cool-700: oklch(0.21 0.03 264);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
@theme {
|
|
1104
|
+
--spacing-md: 0.75rem;
|
|
1105
|
+
--color-ink: light-dark(var(--palette-cool-700), var(--palette-cool-50));
|
|
1106
|
+
}
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
POSTing overrides for `--palette-cool-700` (in `:root`), `--spacing-md`, or `--color-ink` (both in `@theme`) all land in `changed` — no indirection required. A file that is 100% `@theme` (no `:root` block at all) also applies cleanly; only a file with **neither** block 409s.
|
|
1110
|
+
|
|
1111
|
+
**Legacy workaround (no longer required).** Before the rewriter learned to scan `@theme`, the only way to make a Tailwind-`@theme` token editable was to declare the editable value in `:root` and have `@theme` alias it via `var(...)`:
|
|
1112
|
+
|
|
1113
|
+
```css
|
|
1114
|
+
:root {
|
|
1115
|
+
--spacing-md-editable: 0.75rem; /* apply pipeline writes here */
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
@theme {
|
|
1119
|
+
--spacing-md: var(--spacing-md-editable); /* Tailwind reads the alias */
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
This indirection is no longer necessary — declare the token directly inside `@theme` as shown above. The alias pattern still works (it's just two ordinary top-level blocks), so existing consumers are not required to migrate.
|
|
1124
|
+
|
|
1035
1125
|
---
|
|
1036
1126
|
|
|
1037
1127
|
## 12. Bundler notes
|
|
@@ -1112,6 +1202,17 @@ This recipe walks through wiring the panel into a project that does not currentl
|
|
|
1112
1202
|
|
|
1113
1203
|
Pick identifiers for `storagePrefix`, `consoleNamespace`, `modalClassPrefix`, `schemaId`, `exportFilenameBase`, and your palette CSS-var family (`paletteCssVarTemplate`). Pull these into a host-side config file (e.g. `src/lib/panel-config.ts`). If you are migrating from an internal snapshot fork and want to preserve users' saved state across the migration, keep the legacy values verbatim; otherwise pick fresh, neutral identifiers (e.g. `myapp-design-token-panel`).
|
|
1114
1204
|
|
|
1205
|
+
`schemaId` is display-only (see §5.3) — it never gates import/export, so picking any string here is safe. If you want the Import modal's copy to name the ACTUAL schema your export/import round-trips through, import `SCHEMA_V1` / `SCHEMA_V2` / `SCHEMA_V3` from the package root and set `schemaId` to one of them instead of an arbitrary string:
|
|
1206
|
+
|
|
1207
|
+
```ts
|
|
1208
|
+
import { SCHEMA_V3 } from '@takazudo/zdtp';
|
|
1209
|
+
|
|
1210
|
+
export const myPanelConfig: PanelConfig = {
|
|
1211
|
+
// ...
|
|
1212
|
+
schemaId: SCHEMA_V3, // truthful label — matches what serialize()/deserialize() actually validate against
|
|
1213
|
+
};
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1115
1216
|
3. **Author your token manifest in the host project.**
|
|
1116
1217
|
|
|
1117
1218
|
The package itself ships zero baked-in manifest data. Define `SPACING_TOKENS`, `FONT_TOKENS`, `SIZE_TOKENS`, `COLOR_TOKENS` (or whatever names you prefer) in your project and wire them up as `tokens.spacing`, `tokens.typography`, `tokens.size`, `tokens.color`. The host is the source of truth.
|
|
@@ -2,79 +2,186 @@
|
|
|
2
2
|
* Pure, IO-free CSS custom-property value replacement.
|
|
3
3
|
*
|
|
4
4
|
* `applyTokenOverrides(source, overrides)` rewrites the values of one or more
|
|
5
|
-
* CSS custom properties inside the FIRST top-level `:root { ... }` block
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* CSS custom properties inside the FIRST top-level `:root { ... }` block AND
|
|
6
|
+
* the FIRST top-level `@theme { ... }` block (Tailwind v4 design-token scope)
|
|
7
|
+
* of a CSS file, preserving surrounding whitespace and trailing inline
|
|
8
|
+
* comments. The module is the regex foundation that the dev-API endpoint
|
|
8
9
|
* wraps; it performs no IO, no DOM access, and is safe to import in any
|
|
9
10
|
* environment.
|
|
10
11
|
*
|
|
11
12
|
* Behavior summary
|
|
12
13
|
* ----------------
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
14
|
+
* - Two top-level blocks are scanned: the FIRST top-level `:root { ... }` block
|
|
15
|
+
* and the FIRST top-level `@theme { ... }` block (first-of-each-kind only).
|
|
16
|
+
* Later blocks of either kind, and tokens inside `@media`, `@layer`,
|
|
17
|
+
* `@supports`, or nested blocks, are IGNORED.
|
|
18
|
+
* - `:root`-first-match-wins: for each override var, `:root` is tried first and
|
|
19
|
+
* `@theme` is the fallback. A var declared in BOTH blocks is rewritten only
|
|
20
|
+
* in `:root`.
|
|
21
|
+
* - `@theme` accepts an optional modifier — `@theme`, `@theme inline`,
|
|
22
|
+
* `@theme static`, `@theme reference`, or a combination — because Tailwind v4
|
|
23
|
+
* prescribes `@theme inline` precisely when theme values reference other
|
|
24
|
+
* variables (the mainstream case). `:root` matching stays strict: grouped
|
|
25
|
+
* selectors such as `:root, html { ... }` are NOT treated as a `:root` block.
|
|
15
26
|
* - Per-cssVar regex: `(--name:)\s*([\s\S]+?);` with the name regex-escaped.
|
|
16
27
|
* The non-greedy value capture stops at the first `;`, which preserves any
|
|
17
28
|
* trailing `/* ... */` inline comment on the same logical line.
|
|
18
29
|
* - A whitespace-trimmed comparison between the old value and the override
|
|
19
30
|
* decides whether the cssVar lands in `changed` or `unchanged`.
|
|
20
31
|
* - Idempotent: applying the same overrides twice in a row reports every key
|
|
21
|
-
* as `unchanged` on the second call and produces byte-identical output.
|
|
32
|
+
* as `unchanged` on the second call and produces byte-identical output. The
|
|
33
|
+
* two non-overlapping regions are spliced back in DESCENDING start order so
|
|
34
|
+
* the earlier region's offsets stay valid through the later region's splice.
|
|
35
|
+
* - `unknownOutsideBlock` (#508): a whole-file scan (ignoring the "first
|
|
36
|
+
* block of each kind" and "no nesting" restrictions above) reclassifies any
|
|
37
|
+
* `unknown` key that IS declared somewhere in the file, so callers can tell
|
|
38
|
+
* "declared, but outside a scanned block" apart from "genuinely absent".
|
|
22
39
|
*
|
|
23
40
|
* Known limitations (if a richer transform is ever needed, swap this module
|
|
24
41
|
* for a real CSS parser such as postcss):
|
|
25
42
|
* - Only `:root` as a bare selector is recognized. Grouped selectors such as
|
|
26
43
|
* `:root, html { ... }` are NOT treated as a `:root` block.
|
|
27
|
-
* -
|
|
28
|
-
*
|
|
44
|
+
* - Only the FIRST top-level block of each kind is scanned; a second top-level
|
|
45
|
+
* `:root` or `@theme` block is ignored.
|
|
46
|
+
* - Nested `:root` / `@theme` under `@media` / `@layer` / `@supports` is
|
|
47
|
+
* intentionally skipped; see the test matrix in the companion `__tests__`
|
|
48
|
+
* file.
|
|
29
49
|
* - Values containing a literal `;` inside a string or comment would confuse
|
|
30
50
|
* the non-greedy regex. Not supported.
|
|
31
|
-
* - If the same cssVar is declared multiple times
|
|
51
|
+
* - If the same cssVar is declared multiple times inside a single scanned
|
|
32
52
|
* block, only the FIRST occurrence is rewritten.
|
|
33
53
|
*/
|
|
34
54
|
export interface ApplyResult {
|
|
35
55
|
/** The new CSS file contents (byte-identical to `source` when nothing
|
|
36
|
-
* changed, including the "no
|
|
56
|
+
* changed, including the "no scannable block" case). */
|
|
37
57
|
updated: string;
|
|
38
58
|
/** cssVar names actually rewritten (trimmed new value differs from old). */
|
|
39
59
|
changed: string[];
|
|
40
|
-
/** cssVar names present in
|
|
41
|
-
* the override. */
|
|
60
|
+
/** cssVar names present in a scanned block whose trimmed value already
|
|
61
|
+
* matched the override. */
|
|
42
62
|
unchanged: string[];
|
|
43
|
-
/** cssVar names in `overrides` that were not found in the first
|
|
44
|
-
* `:root` block
|
|
45
|
-
*
|
|
63
|
+
/** cssVar names in `overrides` that were not found in EITHER the first
|
|
64
|
+
* top-level `:root` block or the first top-level `@theme` block (the name
|
|
65
|
+
* is absent, or neither block exists). */
|
|
46
66
|
unknown: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Subset of `unknown` (#508): cssVar names that ARE declared somewhere in
|
|
69
|
+
* `source` — found by a whole-file scan, not restricted to the two scanned
|
|
70
|
+
* blocks — but outside both of them, so nothing was rewritten. Typical
|
|
71
|
+
* causes: the declaration sits in a nested block (`@media`/`@layer`/
|
|
72
|
+
* `@supports`), a grouped selector (`:root, html { ... }`), or a SECOND
|
|
73
|
+
* top-level `:root`/`@theme` block (the scanner only reads the first of
|
|
74
|
+
* each kind). Lets callers distinguish "declared, but Apply doesn't rewrite
|
|
75
|
+
* this location" from "genuinely absent / typo'd name" — the remaining
|
|
76
|
+
* `unknown` entries not in this list.
|
|
77
|
+
*/
|
|
78
|
+
unknownOutsideBlock: string[];
|
|
47
79
|
}
|
|
48
80
|
/**
|
|
49
|
-
* Thrown by {@link applyTokenOverridesOrThrow} when the source has
|
|
50
|
-
* top-level `:root { ... }` block
|
|
51
|
-
* diagnostic message without having to probe the
|
|
81
|
+
* Thrown by {@link applyTokenOverridesOrThrow} when the source has neither a
|
|
82
|
+
* top-level `:root { ... }` block nor a top-level `@theme { ... }` block. Lets
|
|
83
|
+
* the dev-API handler surface a diagnostic message without having to probe the
|
|
84
|
+
* result shape.
|
|
52
85
|
*/
|
|
53
|
-
export declare class
|
|
86
|
+
export declare class NoTokenBlockError extends Error {
|
|
54
87
|
constructor(message?: string);
|
|
55
88
|
}
|
|
56
89
|
/**
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
90
|
+
* @deprecated Retained as an alias for {@link NoTokenBlockError} so existing
|
|
91
|
+
* `instanceof` / import sites keep working. The scan now covers `@theme`
|
|
92
|
+
* blocks too, so "NoRootBlock" is no longer literally accurate.
|
|
93
|
+
*/
|
|
94
|
+
export declare const NoRootBlockError: typeof NoTokenBlockError;
|
|
95
|
+
export interface BlockBounds {
|
|
96
|
+
/** Index of the first character INSIDE the block (just after the opening
|
|
97
|
+
* `{`). */
|
|
98
|
+
contentStart: number;
|
|
99
|
+
/** Index of the matching `}` (exclusive end of block content). */
|
|
100
|
+
contentEnd: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Bounds of the first top-level `:root { ... }` block, or `null`. Grouped
|
|
104
|
+
* selectors (`:root, html { ... }`) are intentionally NOT matched.
|
|
105
|
+
*/
|
|
106
|
+
export declare function findFirstTopLevelRootBlock(source: string): BlockBounds | null;
|
|
107
|
+
/**
|
|
108
|
+
* Bounds of the first top-level `@theme { ... }` block, or `null`. Tolerates an
|
|
109
|
+
* optional modifier (`@theme inline`, `@theme static`, ...) between `@theme`
|
|
110
|
+
* and the opening `{`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function findFirstTopLevelThemeBlock(source: string): BlockBounds | null;
|
|
113
|
+
export type SingleReplaceStatus = 'changed' | 'unchanged' | 'unknown';
|
|
114
|
+
export interface SingleReplaceResult {
|
|
115
|
+
content: string;
|
|
116
|
+
status: SingleReplaceStatus;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Replace every CSS block comment (`/* ... */`) with same-length whitespace
|
|
120
|
+
* so the per-declaration regex below cannot match a fake declaration that
|
|
121
|
+
* lives INSIDE a comment. Same-length is critical: the masked string is used
|
|
122
|
+
* only as a search space, but the resulting `match.index` is then sliced
|
|
123
|
+
* straight back into the original (un-masked) `content`, so masked and
|
|
124
|
+
* original must agree byte-for-byte on offsets.
|
|
125
|
+
*
|
|
126
|
+
* Pre-fix behaviour: a `:root` containing
|
|
127
|
+
*
|
|
128
|
+
* /* --zd-foo: red; */
|
|
129
|
+
* --zd-foo: blue;
|
|
59
130
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
131
|
+
* would have its commented-out value rewritten by the regex (it matches the
|
|
132
|
+
* FIRST occurrence), leaving the live declaration untouched and silently
|
|
133
|
+
* corrupting the user-authored comment.
|
|
134
|
+
*/
|
|
135
|
+
export declare function maskComments(input: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Replace the value of a single custom property inside `content`. The name
|
|
138
|
+
* may be supplied with or without the leading `--`. Returns the updated
|
|
139
|
+
* content along with a status flag used to populate `changed` / `unchanged`
|
|
140
|
+
* / `unknown` in the top-level {@link ApplyResult}.
|
|
141
|
+
*
|
|
142
|
+
* The match is run against a comment-masked copy of `content` so a
|
|
143
|
+
* commented-out declaration cannot shadow a live one (B4 fix). Edits are
|
|
144
|
+
* still spliced back into the original `content` so trailing inline comments
|
|
145
|
+
* survive byte-for-byte.
|
|
146
|
+
*/
|
|
147
|
+
export declare function replaceOne(content: string, cssVarName: string, newValue: string): SingleReplaceResult;
|
|
148
|
+
/**
|
|
149
|
+
* Rewrite `overrides` into the first top-level `:root { ... }` block AND the
|
|
150
|
+
* first top-level `@theme { ... }` block of `source`, returning an
|
|
151
|
+
* {@link ApplyResult} describing the outcome.
|
|
152
|
+
*
|
|
153
|
+
* Per override var, `:root` is tried first and `@theme` is the fallback, so a
|
|
154
|
+
* var present in both blocks is rewritten only in `:root` (`:root`-first-match-
|
|
155
|
+
* wins). The two non-overlapping block regions are spliced back into `source`
|
|
156
|
+
* in DESCENDING start order so the earlier region's offsets stay valid.
|
|
157
|
+
*
|
|
158
|
+
* If `source` has neither block, the function returns `source` unchanged with
|
|
159
|
+
* every key routed to `unknown` — the companion {@link applyTokenOverridesOrThrow}
|
|
160
|
+
* raises {@link NoTokenBlockError} in that case so callers that need a hard
|
|
161
|
+
* failure can distinguish it from a file where merely none of the overrides
|
|
162
|
+
* matched.
|
|
65
163
|
*/
|
|
66
164
|
export declare function applyTokenOverrides(source: string, overrides: Record<string, string>): ApplyResult;
|
|
67
165
|
/**
|
|
68
166
|
* Throwing variant. Same shape as {@link applyTokenOverrides} except it
|
|
69
|
-
* raises {@link
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* problem rather than
|
|
167
|
+
* raises {@link NoTokenBlockError} when the source has neither a top-level
|
|
168
|
+
* `:root { ... }` block nor a top-level `@theme { ... }` block, instead of
|
|
169
|
+
* returning every override under `unknown`. Use this from server-side handlers
|
|
170
|
+
* where "no writable token block" is a fatal configuration problem rather than
|
|
171
|
+
* an expected state.
|
|
73
172
|
*/
|
|
74
173
|
export declare function applyTokenOverridesOrThrow(source: string, overrides: Record<string, string>): ApplyResult;
|
|
75
174
|
/**
|
|
76
175
|
* Low-level predicate exposed primarily for callers that want to report a
|
|
77
176
|
* precise diagnostic without running a full rewrite. Returns `true` iff the
|
|
78
|
-
* source contains at least one top-level
|
|
177
|
+
* source contains at least one scannable top-level token block — a `:root`
|
|
178
|
+
* block OR a `@theme` block. Consistent with the {@link applyTokenOverridesOrThrow}
|
|
179
|
+
* throw condition (which fires only when this returns `false`).
|
|
180
|
+
*/
|
|
181
|
+
export declare function hasApplicableTokenBlock(source: string): boolean;
|
|
182
|
+
/**
|
|
183
|
+
* @deprecated Renamed to {@link hasApplicableTokenBlock} now that the scan
|
|
184
|
+
* covers `@theme` blocks too. Retained as an alias so existing call sites keep
|
|
185
|
+
* working; it returns `true` for a `@theme`-only file (no `:root`).
|
|
79
186
|
*/
|
|
80
|
-
export declare
|
|
187
|
+
export declare const hasTopLevelRootBlock: typeof hasApplicableTokenBlock;
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import { g as d, Z as
|
|
3
|
-
import { s as
|
|
4
|
-
const
|
|
5
|
-
function
|
|
1
|
+
import { c as w, g as u, a as g, b as y, d as P, e as b, f as _ } from "../panel-config-CipeKMTz.js";
|
|
2
|
+
import { g as d, Z as A } from "../tweak-state-xt-tSQ_t.js";
|
|
3
|
+
import { s as k, l as I, a as c, c as E, b as C } from "../autoload-state-DF7TsTgY.js";
|
|
4
|
+
const i = "tokenpanel-config";
|
|
5
|
+
function S() {
|
|
6
6
|
if (typeof document > "u")
|
|
7
7
|
throw new Error(
|
|
8
8
|
"[design-token-panel] host-adapter loaded without a document; expected to run in a browser context."
|
|
9
9
|
);
|
|
10
|
-
const n = document.getElementById(
|
|
10
|
+
const n = document.getElementById(i);
|
|
11
11
|
if (!n)
|
|
12
12
|
throw new Error(
|
|
13
|
-
`[design-token-panel] Inline config script #${
|
|
13
|
+
`[design-token-panel] Inline config script #${i} not found. Ensure <DesignTokenPanelHost config={...} /> is rendered on this page before the host script runs.`
|
|
14
14
|
);
|
|
15
|
-
const
|
|
15
|
+
const a = n.textContent ?? "";
|
|
16
16
|
let e;
|
|
17
17
|
try {
|
|
18
|
-
e = JSON.parse(
|
|
19
|
-
} catch (
|
|
18
|
+
e = JSON.parse(a);
|
|
19
|
+
} catch (o) {
|
|
20
20
|
throw new Error(
|
|
21
|
-
`[design-token-panel] Failed to parse inline config from #${
|
|
21
|
+
`[design-token-panel] Failed to parse inline config from #${i}: ${o.message}`
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
|
-
return
|
|
24
|
+
return _(e), e;
|
|
25
25
|
}
|
|
26
|
-
function
|
|
26
|
+
function T(n) {
|
|
27
27
|
return n.__zudoDesignTokenPanelAdapter || (n.__zudoDesignTokenPanelAdapter = {}), n.__zudoDesignTokenPanelAdapter;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const e =
|
|
31
|
-
let
|
|
32
|
-
return
|
|
29
|
+
function V(n, a) {
|
|
30
|
+
const e = T(n);
|
|
31
|
+
let o = e[a];
|
|
32
|
+
return o || (o = { bound: !1, modulePromise: null }, e[a] = o), o;
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function v(n) {
|
|
35
35
|
try {
|
|
36
36
|
return window.localStorage.getItem(n) === "1";
|
|
37
37
|
} catch {
|
|
38
38
|
return !1;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function D(n, a, e) {
|
|
42
42
|
try {
|
|
43
|
-
const
|
|
44
|
-
return e.getItem(
|
|
43
|
+
const o = window.localStorage;
|
|
44
|
+
return o.getItem(e) !== null || o.getItem(a) !== null || o.getItem(n) !== null;
|
|
45
45
|
} catch {
|
|
46
46
|
return !1;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
async function
|
|
50
|
-
return n.modulePromise === null && (n.modulePromise = import("@takazudo/zdtp").then((
|
|
49
|
+
async function l(n) {
|
|
50
|
+
return n.modulePromise === null && (n.modulePromise = import("@takazudo/zdtp").then((a) => {
|
|
51
51
|
try {
|
|
52
|
-
const e = u(),
|
|
53
|
-
e !==
|
|
52
|
+
const e = u(), o = a.__panelConfigForTest();
|
|
53
|
+
e !== o && console.warn(
|
|
54
54
|
"[design-token-panel] Singleton-sharing check failed: the host adapter and the panel module observed different PanelConfig singletons. This indicates the package's `config/panel-config` module is no longer code-split into a single shared chunk. The panel may behave correctly today, but storage keys / namespaces / branding could diverge between the two surfaces in future bundles."
|
|
55
55
|
);
|
|
56
56
|
} catch (e) {
|
|
@@ -58,17 +58,17 @@ async function i(n) {
|
|
|
58
58
|
"[design-token-panel] Singleton-sharing check could not run (likely an older dist without the __panelConfigForTest accessor): " + e.message
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
|
-
return
|
|
61
|
+
return a;
|
|
62
62
|
})), n.modulePromise;
|
|
63
63
|
}
|
|
64
|
-
function
|
|
65
|
-
const s = n[
|
|
64
|
+
function K(n, a, e, o, t) {
|
|
65
|
+
const s = n[a] ?? {};
|
|
66
66
|
s.showDesignPanel = async () => {
|
|
67
|
-
c(t, !0), await
|
|
67
|
+
c(t, !0), await l(e), o.open();
|
|
68
68
|
}, s.hideDesignPanel = async () => {
|
|
69
|
-
await
|
|
69
|
+
await l(e), o.close();
|
|
70
70
|
}, s.toggleDesignPanel = async () => {
|
|
71
|
-
await
|
|
71
|
+
await l(e), o.toggle();
|
|
72
72
|
let r = !1;
|
|
73
73
|
try {
|
|
74
74
|
r = window.localStorage.getItem(d(t)) === "1";
|
|
@@ -76,13 +76,13 @@ function D(n, o, e, a, t) {
|
|
|
76
76
|
}
|
|
77
77
|
r && c(t, !0);
|
|
78
78
|
}, s.enableAutoload = async () => {
|
|
79
|
-
(await
|
|
79
|
+
(await l(e)).enableAutoload(t);
|
|
80
80
|
}, s.disableAutoload = async () => {
|
|
81
81
|
if (e.modulePromise !== null) {
|
|
82
|
-
(await
|
|
82
|
+
(await l(e)).disableAutoload(t);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
E(t), C(!1, t);
|
|
86
86
|
try {
|
|
87
87
|
window.localStorage.setItem(g(t), "0");
|
|
88
88
|
} catch {
|
|
@@ -91,12 +91,12 @@ function D(n, o, e, a, t) {
|
|
|
91
91
|
window.localStorage.removeItem(d(t));
|
|
92
92
|
} catch {
|
|
93
93
|
}
|
|
94
|
-
}, n[
|
|
94
|
+
}, n[a] = s;
|
|
95
95
|
}
|
|
96
96
|
(function() {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
97
|
+
const a = S(), e = a.legacyIdRenameMap ? a : { ...a, legacyIdRenameMap: { ...A } }, o = w(e), t = u(), s = window, r = V(s, t.storagePrefix);
|
|
98
|
+
if (K(s, t.consoleNamespace, r, o, t), r.bound) return;
|
|
99
99
|
r.bound = !0;
|
|
100
|
-
const f = g(t), p =
|
|
101
|
-
(
|
|
100
|
+
const f = g(t), m = y(t), p = P(t), h = b(t);
|
|
101
|
+
(v(f) || D(m, p, h) || k(t) || I(t)) && l(r);
|
|
102
102
|
})();
|
package/dist/astro/index.js
CHANGED