@takazudo/zdtp 0.4.4 → 0.4.6

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.
@@ -22,6 +22,12 @@ export interface PerFileResult {
22
22
  changed: string[];
23
23
  unchanged: string[];
24
24
  unknown: string[];
25
+ /**
26
+ * Subset of `unknown` (#508): declared somewhere in the file but outside
27
+ * the scanned `:root`/`@theme` blocks — see `ApplyResult.unknownOutsideBlock`
28
+ * in `apply-token-overrides.ts` for the full classification rules.
29
+ */
30
+ unknownOutsideBlock: string[];
25
31
  }
26
32
  /**
27
33
  * Create a framework-agnostic Fetch API handler for the design-token apply
@@ -1,4 +1,4 @@
1
- import { C as s, c as i, i as l, a as r, l as t, s as o, v as d } from "../load-routing-DtNTKoLW.js";
1
+ import { C as s, c as i, i as l, a as r, l as t, s as o, v as d } from "../load-routing-LJ72261l.js";
2
2
  export {
3
3
  s as CSS_VAR_NAME_RE,
4
4
  i as createApplyHandler,
@@ -80,6 +80,12 @@ export type { TabOverrides } from '../apply/tier-resolver';
80
80
  export declare function getStorageKeyV1(cfg?: PanelConfig): string;
81
81
  export declare function getStorageKeyV2(cfg?: PanelConfig): string;
82
82
  export declare function getStorageKeyV3(cfg?: PanelConfig): string;
83
+ /**
84
+ * v4 unified envelope key — the per-scheme-keyed color format (#500/#509).
85
+ * See `storageKey_stateV4` for the shape rationale. Read first by
86
+ * `loadPersistedState`; v1/v2/v3 migrate into it on first load.
87
+ */
88
+ export declare function getStorageKeyV4(cfg?: PanelConfig): string;
83
89
  export declare function getOpenKey(cfg?: PanelConfig): string;
84
90
  export declare function getPositionKey(cfg?: PanelConfig): string;
85
91
  export declare function getSizeKey(cfg?: PanelConfig): string;
@@ -258,6 +264,32 @@ export interface TweakState {
258
264
  /** Generic tab overrides keyed by tab id. Added in v3 envelope. */
259
265
  tabs?: Record<string, TabOverrides>;
260
266
  }
267
+ /**
268
+ * On-disk v4 persist envelope (#500/#509). Same top-level slices as the runtime
269
+ * `TweakState`, EXCEPT the `color` and `secondary` slices are identity-keyed
270
+ * sub-objects: one `ColorTweakState` slot per active-color IDENTITY (see
271
+ * `getActiveColorIdentity`). This is what lets a per-scheme color tweak survive
272
+ * a scheme toggle — editing under scheme A never touches scheme B's slot.
273
+ *
274
+ * The non-color slices (`spacing` / `typography` / `size` / `tabs`) stay global
275
+ * and unkeyed, exactly as in v1–v3: a light/dark toggle does not change them.
276
+ *
277
+ * `secondary` slots are keyed by the SAME primary identity — the secondary
278
+ * cluster has no scheme of its own but reseeds on the same `color-scheme-changed`
279
+ * event, so it follows the primary's identity.
280
+ *
281
+ * `loadPersistedState` selects the active identity's slots into a runtime
282
+ * `TweakState`; `savePersistedState` merge-saves the active slots while
283
+ * preserving every inactive identity's slot.
284
+ */
285
+ export interface PersistedEnvelopeV4 {
286
+ color: Record<string, ColorTweakState>;
287
+ secondary?: Record<string, ColorTweakState>;
288
+ spacing: TokenOverrides;
289
+ typography: TokenOverrides;
290
+ size: TokenOverrides;
291
+ tabs?: Record<string, TabOverrides>;
292
+ }
261
293
  /** Produce an empty overrides map — `TweakState` default for new tabs. */
262
294
  export declare function emptyOverrides(): TokenOverrides;
263
295
  /**
@@ -360,6 +392,24 @@ export declare function getActiveSchemeName(cluster?: ColorClusterDataConfig, cf
360
392
  * is the correct behavior for non-sink instances but wrong for sinks.
361
393
  */
362
394
  export declare function initColorFromScheme(cluster?: ColorClusterDataConfig, cfg?: PanelConfig): ColorTweakState;
395
+ /**
396
+ * Derive the active-color IDENTITY — the key under which the v4 envelope files
397
+ * this instance's `color` / `secondary` slots (#500/#509).
398
+ *
399
+ * The identity is EXACTLY the active scheme name `initColorFromScheme` resolves
400
+ * for the primary cluster via `getActiveSchemeName` (the active scheme, with the
401
+ * light/dark mode already folded into the resolved name where the cluster
402
+ * `panelSettings.colorMode` distinguishes modes). This is deliberate: the same
403
+ * inputs whose change triggers a reseed today produce the identity, so the
404
+ * invariant holds — *the same identity that causes a reseed selects its own
405
+ * persisted slot*. A colorMode-less cluster resolves a constant identity (one
406
+ * slot); a light/dark cluster resolves distinct identities per side.
407
+ *
408
+ * The secondary cluster has no scheme concept of its own but reseeds on the same
409
+ * `color-scheme-changed` event, so its slots are keyed by this SAME primary
410
+ * identity — callers pass the PRIMARY cluster here for both slices.
411
+ */
412
+ export declare function getActiveColorIdentity(cluster?: ColorClusterDataConfig, cfg?: PanelConfig): string;
363
413
  /**
364
414
  * Palette entry sanitiser. Raw `oklch(...)` values are preserved verbatim so
365
415
  * wide-gamut chroma survives (and they never touch the canvas → no '#000000'
@@ -438,8 +488,17 @@ export declare function resolveSemanticPreviewColor(mapping: SemanticValue, stat
438
488
  * for an unresolvable ref rather than throwing, and this function skips a
439
489
  * `null` (leaving the token at its stylesheet default) rather than painting
440
490
  * a fallback color.
491
+ *
492
+ * `ownerPrefix` (#501) is the instance `storagePrefix` this apply belongs to.
493
+ * When supplied AND this cluster emits a per-mode `light-dark()` literal (so
494
+ * `color-scheme: light dark` is written to the apply target — either
495
+ * `document.documentElement` or the sink's root), the instance is recorded as
496
+ * the owner of that inline `color-scheme` so the clear paths can later remove
497
+ * it without ever wiping a host-owned `<html style="color-scheme">`. Omitting
498
+ * it (direct callers with no instance context, e.g. some tests) skips the
499
+ * bookkeeping only — the actual CSS write is unchanged.
441
500
  */
442
- export declare function applyColorState(state: ColorTweakState, cluster?: ColorClusterDataConfig, sink?: ApplySink, currentTab?: TabConfig, tabs?: readonly TabConfig[]): void;
501
+ export declare function applyColorState(state: ColorTweakState, cluster?: ColorClusterDataConfig, sink?: ApplySink, currentTab?: TabConfig, tabs?: readonly TabConfig[], ownerPrefix?: string): void;
443
502
  /**
444
503
  * Apply a `TokenOverrides` map for a given manifest — writes inline
445
504
  * `--css-var: value` on `:root` for every overridden token, and removes the
@@ -450,6 +509,31 @@ export declare function applyColorState(state: ColorTweakState, cluster?: ColorC
450
509
  * in the UI and are never written to storage.
451
510
  */
452
511
  export declare function applyTokenOverrides(tokens: readonly TokenDef[], overrides: TokenOverrides, sink?: ApplySink): void;
512
+ /**
513
+ * Apply ONLY the color slices — primary color cluster + optional secondary
514
+ * cluster + the aggregate `color-scheme` decision — leaving spacing / font /
515
+ * size / generic-tab vars untouched.
516
+ *
517
+ * Extracted from `applyFullState` (#500/#509) so the scheme-change handler can
518
+ * re-paint the new identity's persisted color overrides after
519
+ * `clearAppliedColorStyles` WITHOUT re-writing (or clearing) the scheme-
520
+ * independent non-color vars that must survive a light/dark toggle (#347).
521
+ *
522
+ * `color-scheme` is managed as an AGGREGATE across the primary + secondary
523
+ * clusters, not per-cluster (#482 D3): `applyColorState` only ever SETS
524
+ * `color-scheme: light dark` when its OWN cluster needs it, it never clears
525
+ * it, so demoting the last per-mode-literal row (in either cluster) would
526
+ * otherwise leave the previous apply's value stale. A naive per-cluster
527
+ * clear inside `applyColorState` would let a per-mode-free secondary undo
528
+ * what the primary just required (or vice versa), so the clear-when-neither-
529
+ * needs-it decision is made here, once, after both clusters have applied.
530
+ *
531
+ * #501 — the `document.documentElement` clear is scoped to a color-scheme THIS
532
+ * instance actually wrote (`clearOwnedColorSchemeFromDocument`) so a host that
533
+ * owns `<html style="color-scheme">` is never clobbered; the sink path stays
534
+ * unconditional (sink targets are panel-owned) but drops the ownership claim.
535
+ */
536
+ export declare function applyColorSlices(color: ColorTweakState, secondary: ColorTweakState | undefined, cfg?: PanelConfig): void;
453
537
  /**
454
538
  * Apply the full unified `TweakState` — primary color cluster + token
455
539
  * overrides + optional secondary cluster.
@@ -462,16 +546,26 @@ export declare function applyTokenOverrides(tokens: readonly TokenDef[], overrid
462
546
  * CSS-var writes for this instance. Omitting `cfg` uses the default active
463
547
  * config (single-panel path, unchanged behavior).
464
548
  *
465
- * `color-scheme` is managed as an AGGREGATE across the primary + secondary
466
- * clusters, not per-cluster (#482 D3): `applyColorState` only ever SETS
467
- * `color-scheme: light dark` when its OWN cluster needs it, it never clears
468
- * it, so demoting the last per-mode-literal row (in either cluster) would
469
- * otherwise leave the previous apply's value stale. A naive per-cluster
470
- * clear inside `applyColorState` would let a per-mode-free secondary undo
471
- * what the primary just required (or vice versa), so the clear-when-neither-
472
- * needs-it decision is made here, once, after both clusters have applied.
549
+ * The color slices + aggregate `color-scheme` decision are delegated to
550
+ * `applyColorSlices`; this function adds the non-color (spacing / font / size /
551
+ * generic-tab) writes.
473
552
  */
474
553
  export declare function applyFullState(state: TweakState, cfg?: PanelConfig): void;
554
+ /**
555
+ * Apply ONLY the non-color slices (spacing / typography / size / generic-tab
556
+ * overrides), leaving the color clusters and aggregate `color-scheme` decision
557
+ * untouched. Complement of `applyColorSlices`; together they compose
558
+ * `applyFullState`.
559
+ *
560
+ * Extracted (#509 audit) so the persisted-overrides reapply paths
561
+ * (`reapplyPersistedOverrides`, the panel first-open effect) can, when a v4
562
+ * envelope exists but the ACTIVE (scheme, mode) identity has no color slot,
563
+ * still apply the scheme-INDEPENDENT non-color tweaks while leaving the active
564
+ * scheme's own stylesheet colors to show through — instead of painting the
565
+ * synthesized config-default colors inline. This mirrors the scheme-change
566
+ * handler's missing-slot behavior (gated on `hasActiveColorSlot`).
567
+ */
568
+ export declare function applyNonColorSlices(state: TweakState, cfg?: PanelConfig): void;
475
569
  /**
476
570
  * Strip the inline CSS variables written by the color cluster(s) only —
477
571
  * palette slots, base roles, and semantic vars. Spacing / typography / size
@@ -495,6 +589,11 @@ export declare function applyFullState(state: TweakState, cfg?: PanelConfig): vo
495
589
  * historical `(clusters, sink)` call shape (e.g. the scheme-change path before
496
590
  * #357, and existing tests) is unchanged. Omitting all three preserves the
497
591
  * single-panel default-instance behaviour.
592
+ *
593
+ * The color-scheme removal on the `document.documentElement` path is gated on
594
+ * this instance owning the inline value (#501) — see
595
+ * `clearOwnedColorSchemeFromDocument`. The owner is `cfg.storagePrefix` when a
596
+ * `cfg` is given, else the default instance's prefix.
498
597
  */
499
598
  export declare function clearAppliedColorStyles(clusters?: readonly ColorClusterDataConfig[], sink?: ApplySink, cfg?: PanelConfig): void;
500
599
  /**
@@ -560,17 +659,41 @@ export interface StorageLike {
560
659
  * labels), so this migration only applies to the typography slice.
561
660
  */
562
661
  export declare const ZDTP_LEGACY_TYPOGRAPHY_RENAME_MAP: Readonly<Record<string, string | null>>;
662
+ /**
663
+ * True when the v4 envelope holds a color slot for the CURRENTLY active
664
+ * identity. The scheme-change handler uses this to decide whether to hydrate +
665
+ * apply persisted overrides for the new scheme (slot present) or reseed from
666
+ * config defaults WITHOUT applying (slot absent — the pre-#500 behavior that
667
+ * lets the new scheme's stylesheet show through).
668
+ */
669
+ export declare function hasActiveColorSlot(cfg?: PanelConfig, cluster?: ColorClusterDataConfig, storage?: StorageLike): boolean;
670
+ /**
671
+ * Load the active `TweakState`, preferring the v4 per-scheme envelope
672
+ * (#500/#509).
673
+ *
674
+ * Order:
675
+ * 1. v4 present → select the active identity's color/secondary slots.
676
+ * 2. else run the legacy v1/v2/v3 chain (unchanged) → migrate that state into
677
+ * a v4 envelope filed under the identity active at load, WITHOUT deleting
678
+ * the legacy key (v4 wins on every future load via step 1). This is why the
679
+ * pre-existing v1/v2/v3 migration tests keep passing: the legacy chain still
680
+ * writes/keeps the v3 key exactly as before; v4 is layered on top.
681
+ * 3. else null (caller cold-seeds).
682
+ *
683
+ * A present-but-malformed v4 blob falls through to steps 2/3, which overwrite it.
684
+ */
563
685
  export declare function loadPersistedState(storage?: StorageLike, colorDefaults?: ColorTweakState, cluster?: ColorClusterDataConfig, cfg?: PanelConfig): TweakState | null;
564
686
  /**
565
- * Persist the full `TweakState` to v3.
687
+ * Merge-save the full `TweakState` into the v4 per-scheme envelope (#500/#509).
566
688
  *
567
- * `cfg` scopes the storage key to a specific panel instance (multi-instance,
568
- * #357). Omitting it resolves the default instance via `getPanelConfig()`,
569
- * preserving the single-panel path.
689
+ * The active identity's color/secondary slots are overwritten; every inactive
690
+ * identity's slot is preserved (read → merge → write). Global non-color slices
691
+ * are replaced wholesale. `cfg` scopes the storage key to a specific panel
692
+ * instance (multi-instance, #357).
570
693
  */
571
694
  export declare function savePersistedState(state: TweakState, storage?: StorageLike, cfg?: PanelConfig): void;
572
695
  /**
573
- * Remove v3 (and lingering v2/v1) keys.
696
+ * Remove the v4 key (and lingering v3/v2/v1 keys).
574
697
  *
575
698
  * `cfg` scopes the cleared keys to a specific panel instance (multi-instance,
576
699
  * #357). Omitting it resolves the default instance via `getPanelConfig()`,
@@ -0,0 +1,25 @@
1
+ import type { TabConfig } from '../tokens/tier-model';
2
+ interface NotesTabProps {
3
+ tab: TabConfig;
4
+ }
5
+ /**
6
+ * Notes tab — host-configurable "token notes" landing view (#515).
7
+ *
8
+ * Renders `tab.notesExtras.title` as a section heading
9
+ * (`div[role="heading"][aria-level=3]`, NEVER an hN tag — see
10
+ * packages/zdtp/CLAUDE.md "Panel DOM hygiene") and `tab.notesExtras.html` as
11
+ * sanitized HTML via `dangerouslySetInnerHTML` — the package's first and only
12
+ * use of it. `sanitizeHtml` (utils/sanitize-html.ts) is the security
13
+ * boundary, an allowlist parser, not this component and not the CSS below.
14
+ *
15
+ * The rendered `.tokenpanel-notes-body` subtree is HOST-AUTHORED PROSE
16
+ * CONTENT, not panel chrome: the DOM-hygiene ban on h1-h6/p/ul/a/table/etc.
17
+ * applies to the panel's OWN JSX, and does not apply inside sanitized note
18
+ * content — that's exactly the markup a "notes" feature needs to render.
19
+ *
20
+ * `assertValidPanelConfig` requires `notesExtras` (non-empty title + html)
21
+ * on every tab with `id: 'notes'`, so this component can assume it's
22
+ * present without a runtime fallback UI.
23
+ */
24
+ export default function NotesTab({ tab }: NotesTabProps): import("preact").JSX.Element | null;
25
+ export {};
package/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as r, c as i, f as _, h as n, i as g, b as R, a as p } from "./panel-config-CXTCcYQs.js";
2
- import { a as l, b as y, l as O } from "./tweak-state-BeXkzoj8.js";
1
+ import { _ as r, c as _, h as i, j as n, k as g, b as R, a as p } from "./panel-config-Bw7D-25C.js";
2
+ import { a as y, b as O, l as P } from "./tweak-state-CD1y56zR.js";
3
3
  import { F as K, G as E, a as S, S as u } from "./manifest-DCReQE0k.js";
4
4
  async function s(a, e) {
5
5
  await a.fill(e), await a.dispatchEvent("input");
@@ -10,12 +10,12 @@ export {
10
10
  S as GROUP_TITLES,
11
11
  u as SIZE_GROUP_ORDER,
12
12
  r as __resetPanelConfigForTests,
13
- i as configurePanel,
14
- l as getStorageKeyV1,
15
- y as getStorageKeyV2,
16
- O as loadPersistedState,
13
+ _ as configurePanel,
14
+ y as getStorageKeyV1,
15
+ O as getStorageKeyV2,
16
+ P as loadPersistedState,
17
17
  s as setPanelInputValue,
18
- _ as storageKey_open,
18
+ i as storageKey_open,
19
19
  n as storageKey_position,
20
20
  g as storageKey_stateV1,
21
21
  R as storageKey_stateV2,
@@ -60,6 +60,14 @@ export interface TokenDef {
60
60
  step: number;
61
61
  /** Unit suffix (e.g. `rem`, `px`). Read-only rows may use an empty string. */
62
62
  unit: string;
63
+ /**
64
+ * Optional list of unit suffixes this row may cycle through by clicking
65
+ * the unit label (#519). 2+ entries opts in; omitted/single-entry keeps
66
+ * today's static, non-interactive unit suffix. Mirrors `TierValueKind`'s
67
+ * `length.units` — the default-slider mapping in a host's
68
+ * `toTierItem()`-style helper should pass it straight through.
69
+ */
70
+ units?: readonly string[];
63
71
  /** Read-only tokens are displayed but not editable (e.g. `clamp()` expressions). */
64
72
  readonly?: true;
65
73
  /** Which control renders this token. Defaults to `"slider"` when absent. */
@@ -4,6 +4,16 @@ export type TierValueKind = {
4
4
  kind: 'length';
5
5
  step: number;
6
6
  unit: string;
7
+ /**
8
+ * Optional list of unit suffixes this row may cycle through by
9
+ * clicking the unit label (#519). 2+ entries opts the row into an
10
+ * interactive, click/Enter/Space-cycling unit suffix (wrapping through
11
+ * the list); omitted or a single entry keeps today's static,
12
+ * non-interactive unit span, pixel-identical. This kind is reused for
13
+ * ms durations and unitless values too — only declare `units` when
14
+ * cycling between those suffixes is valid CSS for the token.
15
+ */
16
+ units?: readonly string[];
7
17
  } | {
8
18
  kind: 'number';
9
19
  step: number;
@@ -132,10 +142,36 @@ export interface ColorClusterExtras {
132
142
  defaultShikiTheme: string;
133
143
  colorSchemes: Record<string, ColorScheme>;
134
144
  panelSettings: ClusterPanelSettings;
145
+ /**
146
+ * Config-time override map for a semantic tier's derived defaults (#499).
147
+ * Keyed by semantic item id; when a key is present here,
148
+ * `resolveColorClusterFromTab` uses its `SemanticValue` verbatim instead of
149
+ * running `deriveSemanticValue` on the item's plain-string `default` — this
150
+ * is the only way a host can ship a `{ literal: { light, dark } }` (or
151
+ * `{ ref }`) config-time default, since `TierItem.default` itself stays a
152
+ * plain string (Option B from #499, chosen over widening `TierItem.default`
153
+ * to `SemanticValue` — that field is consumed generically as a plain string
154
+ * by every tab kind, not just Color).
155
+ */
156
+ semanticDefaults?: Record<string, SemanticValue>;
157
+ }
158
+ /**
159
+ * Host-configurable "token notes" content — ONLY valid on the reserved
160
+ * `id: 'notes'` pseudo-tab (#515). That tab renders `title` as a section
161
+ * heading and `html` (sanitized by `utils/sanitize-html.ts`) as its body,
162
+ * instead of a tier-driven token editor. `assertValidPanelConfig` enforces
163
+ * the full contract: required + non-empty on `id: 'notes'`, forbidden on
164
+ * every other tab id, and `id: 'notes'` tabs must ship `tiers: []` and no
165
+ * `colorExtras`.
166
+ */
167
+ export interface NotesExtras {
168
+ title: string;
169
+ html: string;
135
170
  }
136
171
  export interface TabConfig {
137
172
  id: string;
138
173
  label: string;
139
174
  tiers: readonly TierConfig[];
140
175
  colorExtras?: ColorClusterExtras;
176
+ notesExtras?: NotesExtras;
141
177
  }