@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.
@@ -86,7 +86,12 @@ export interface PanelConfig {
86
86
  consoleNamespace: string;
87
87
  /** BEM-style prefix used by every modal in the panel (export / import / apply). */
88
88
  modalClassPrefix: string;
89
- /** `$schema` value emitted into export JSON and required on import. */
89
+ /**
90
+ * Schema label surfaced in the Import modal's hint/placeholder/error text
91
+ * and the Export modal's copy. NOT enforced: `serialize()`/`deserialize()`
92
+ * always use the fixed `SCHEMA_V1`/`SCHEMA_V2`/`SCHEMA_V3` constants
93
+ * regardless of this value (#498).
94
+ */
90
95
  schemaId: string;
91
96
  /** Default filename base — exports save as `${exportFilenameBase}.json`. */
92
97
  exportFilenameBase: string;
@@ -364,6 +369,24 @@ export declare function getPanelConfigByPrefix(prefix: string): PanelConfig | nu
364
369
  * instance, parked presets, and parked pre-configure hooks.
365
370
  */
366
371
  export declare function __resetPanelConfigForTests(): void;
372
+ /**
373
+ * #501 — record that the panel instance keyed by `prefix` wrote `color-scheme`
374
+ * to its apply target. Called from the apply path (`applyColorState`) wherever
375
+ * a per-mode literal forces `color-scheme: light dark`.
376
+ */
377
+ export declare function markColorSchemeWritten(prefix: string): void;
378
+ /**
379
+ * #501 — drop the color-scheme ownership claim for `prefix`. Called from the
380
+ * clear paths after a panel-written `color-scheme` is removed (or found to have
381
+ * been overwritten by the host — the stale-ownership case).
382
+ */
383
+ export declare function clearColorSchemeOwnership(prefix: string): void;
384
+ /**
385
+ * #501 — true when the panel instance keyed by `prefix` is the recorded writer
386
+ * of the current inline `color-scheme`. Gates the clear paths so the panel
387
+ * never removes a `color-scheme` it did not write.
388
+ */
389
+ export declare function ownsColorScheme(prefix: string): boolean;
367
390
  export { resolvePrimaryColorCluster, resolveSecondaryColorClusterFromTabs, } from './cluster-config';
368
391
  import type { ColorClusterDataConfig } from './cluster-config';
369
392
  /**
@@ -391,6 +414,18 @@ export declare function storageKey_stateV2(cfg: PanelConfig): string;
391
414
  * this key, then deletes the v2 key.
392
415
  */
393
416
  export declare function storageKey_stateV3(cfg: PanelConfig): string;
417
+ /**
418
+ * Storage key for the v4 unified envelope. Upgrades the "global tweak model" to
419
+ * per-scheme color persistence (#500/#509): the `color` and `secondary` slices
420
+ * become identity-keyed sub-objects (one slot per resolved scheme/mode), so a
421
+ * per-scheme color tweak survives a scheme toggle round-trip instead of being
422
+ * clobbered by the next unrelated edit. Non-color slices stay global. Kept a
423
+ * SINGLE storage key (not one key per scheme) because the Astro bootstrap must
424
+ * existence-check persistence before cluster config loads — a per-scheme key
425
+ * would hit a chicken-and-egg there. v1/v2/v3 migrate into this key on first
426
+ * load; v4 takes precedence on every subsequent load.
427
+ */
428
+ export declare function storageKey_stateV4(cfg: PanelConfig): string;
394
429
  /** Legacy v1 key (Color-only flat state). Migrated into v2 on first load, then deleted. */
395
430
  export declare function storageKey_stateV1(cfg: PanelConfig): string;
396
431
  /** Mirror of the panel's `open` boolean (synchronous mount-time read). */
@@ -455,6 +490,38 @@ export declare function openStateChangedEventName(cfg: PanelConfig): string;
455
490
  export declare function modalClass(cfg: PanelConfig, suffix: string): string;
456
491
  /** Default download filename for export. */
457
492
  export declare function exportFilename(cfg: PanelConfig): string;
493
+ /** Shape of the fixed-name global alias installed at `window.zdtp`. */
494
+ export interface ZdtpGlobalApi {
495
+ show: () => void | Promise<void>;
496
+ hide: () => void | Promise<void>;
497
+ toggle: () => void | Promise<void>;
498
+ }
499
+ /**
500
+ * Install `window.zdtp = { show, hide, toggle }`, the fixed-name console
501
+ * sugar for the common single-panel case (issue #523).
502
+ *
503
+ * Guard rules:
504
+ * - A pre-existing `window.zdtp` WITHOUT this package's marker is assumed to
505
+ * be host-defined — never overwritten. Logs a `console.warn` so the host
506
+ * can see why `zdtp.show()` did not appear.
507
+ * - A pre-existing `window.zdtp` WITH the marker means this package already
508
+ * installed the alias (from the other call site, or an earlier run of this
509
+ * same one). First install wins — the call is a silent no-op. In the Astro
510
+ * flow this manifests as "the adapter wins": its bootstrap script always
511
+ * runs (and installs the alias) before the panel module's lazy dynamic
512
+ * import can resolve and reach the package-root install site.
513
+ *
514
+ * `show` / `hide` / `toggle` are caller-supplied closures, so which instance
515
+ * they target is up to the caller — the package-root install site (below,
516
+ * `index.tsx`) wraps `showDesignTokenPanel()` etc., which re-resolve
517
+ * `getPanelConfig()` (the CURRENT default instance) on every call; the Astro
518
+ * host-adapter install site instead binds to the specific `PanelInstanceHandle`
519
+ * captured at its own install time, which does NOT track a later change of
520
+ * default (see PORTABLE-CONTRACT.md §6.5 for the full nuance). Either way,
521
+ * for a multi-instance page use `configurePanel(cfg).open()` etc. on the
522
+ * specific instance's own handle instead of relying on this alias.
523
+ */
524
+ export declare function installZdtpGlobalAlias(api: ZdtpGlobalApi): void;
458
525
  /**
459
526
  * Runtime validation at the host-adapter trust boundary. The Astro inline
460
527
  * `<script type="application/json">` payload is untrusted-by-the-types:
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ActionsMenuPopover — narrow-panel replacement for the header action links
3
+ * (Export / Load from JSON… / Apply / Reset), collapsed behind the kebab
4
+ * trigger below the panel's <480px container-query breakpoint (#518).
5
+ *
6
+ * A PLAIN popover of ordinary action RoleButtons, NOT an ARIA menu:
7
+ * role="menu"/menuitem demands arrow-key navigation and focus management,
8
+ * and RoleButton hardcodes role="button" — so this follows the existing
9
+ * gear-settings popover's dialog + outside-click/Escape wiring instead
10
+ * (usePopoverClose, shared with highlight/highlight-settings-popover.tsx).
11
+ */
12
+ import type { JSX } from 'preact';
13
+ export interface ActionsMenuAction {
14
+ label: string;
15
+ onSelect: () => void;
16
+ }
17
+ interface ActionsMenuPopoverProps {
18
+ /** The kebab trigger element — excluded from the outside-click close so its
19
+ * own click-to-toggle isn't raced by the popover's pointerdown listener
20
+ * (mirrors the gear button / HighlightSettingsPopover pairing). */
21
+ anchorRef: React.RefObject<HTMLElement | null>;
22
+ actions: readonly ActionsMenuAction[];
23
+ onClose: () => void;
24
+ }
25
+ export declare function ActionsMenuPopover({ anchorRef, actions, onClose, }: ActionsMenuPopoverProps): JSX.Element;
26
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * HelpIcon — small round "?" badge that explains a nearby control via the
3
+ * shared `.tokenpanel-tooltip--help` variant (issue #520).
4
+ *
5
+ * Chrome-button policy (CLAUDE.md): implemented as
6
+ * `<div role="button" tabIndex={0}>` with explicit Enter/Space handling —
7
+ * never a native `<button>`.
8
+ *
9
+ * Behavior:
10
+ * - Hover / focus shows the help tooltip transiently, same as any other
11
+ * `useTooltip` trigger.
12
+ * - Click (or Enter/Space) PINS the tooltip open — touch devices have no
13
+ * hover, so a tap must be able to reveal and hold the tip. A second
14
+ * activation, or pressing Escape anywhere on the page, unpins it.
15
+ *
16
+ * Callers place this as a SIBLING of the control it explains (e.g. after a
17
+ * `<select>`, or after a `<label>` wrapping a checkbox) — never nested
18
+ * inside a `<label>`, where a click would also toggle the label's own
19
+ * control.
20
+ *
21
+ * Known trade-off: `tooltip.tsx` renders a single shared tooltip DOM node,
22
+ * so pinning icon A then hovering/pinning icon B silently replaces A's
23
+ * visible content with B's — A's `pinned`/`is-pinned` state persists until
24
+ * A is next clicked or Escape is pressed, even though its tooltip is no
25
+ * longer the one on screen. Acceptable for this panel's single-tooltip
26
+ * architecture; not worth a cross-instance pin registry for a "?" hint.
27
+ * The same applies to a panel/window resize: `tooltip.tsx` hides the shared
28
+ * tooltip on resize (its ResizeObserver + window-resize handlers), but this
29
+ * component's local `pinned` state only clears on click/Escape — so after a
30
+ * resize the icon may briefly keep `is-pinned`/`aria-pressed` with no tooltip
31
+ * visible until the next activation. Same accepted single-tooltip trade-off.
32
+ */
33
+ import type { JSX } from 'preact';
34
+ export declare const LITERAL_HELP_TEXT = "Literal sets a fixed color just for this token \u2014 it won't update if you edit a palette/ramp swatch later. Pick a ramp option to keep it linked live.";
35
+ export declare const PER_MODE_HELP_TEXT = "Per-mode gives this token two colors \u2014 one for light mode, one for dark. The browser picks the right one automatically via CSS light-dark().";
36
+ export interface HelpIconProps {
37
+ /** The help text shown in the tooltip (wrapped, multi-line). */
38
+ text: string;
39
+ /** Accessible name for the icon itself (e.g. "Literal help", "Surface per-mode help"). */
40
+ ariaLabel: string;
41
+ }
42
+ export declare function HelpIcon({ text, ariaLabel }: HelpIconProps): JSX.Element;
43
+ export default HelpIcon;
@@ -16,6 +16,13 @@
16
16
  * <div {...tooltipProps}>...</div>
17
17
  */
18
18
  import type { ComponentChildren, JSX } from 'preact';
19
+ /**
20
+ * Opt-in tooltip presentation variant. `'help'` applies the wrapped,
21
+ * bounded-width `.tokenpanel-tooltip--help` class (see panel.css) instead of
22
+ * the default single-line `nowrap` tooltip — used by `controls/help-icon.tsx`
23
+ * for the multi-sentence Literal / Per-mode explainer tips.
24
+ */
25
+ export type TooltipVariant = 'help';
19
26
  interface TooltipProviderProps {
20
27
  children: ComponentChildren;
21
28
  }
@@ -27,17 +34,28 @@ interface TooltipProviderProps {
27
34
  * tooltip globally.
28
35
  */
29
36
  export declare function TooltipProvider({ children }: TooltipProviderProps): JSX.Element;
37
+ export interface UseTooltipOptions {
38
+ /** Opt into a presentation variant — see `TooltipVariant` above. Omit for
39
+ * the default single-line `nowrap` tooltip. */
40
+ variant?: TooltipVariant;
41
+ }
30
42
  /**
31
43
  * Returns event handler props to spread onto the tooltip trigger element.
32
44
  *
33
45
  * @param text - The full text to show in the tooltip.
46
+ * @param opts - Optional presentation variant (see `UseTooltipOptions`).
34
47
  * @returns An object of `onMouseEnter`, `onMouseLeave`, `onFocusIn`, `onFocusOut`
35
- * handlers to spread onto the trigger element.
48
+ * handlers to spread onto the trigger element, plus imperative
49
+ * `show`/`hide` escape hatches for callers that need to
50
+ * show/keep-open the tooltip outside of a raw hover/focus event
51
+ * (e.g. `controls/help-icon.tsx`'s click-to-pin behavior).
36
52
  */
37
- export declare function useTooltip(text: string): {
53
+ export declare function useTooltip(text: string, opts?: UseTooltipOptions): {
38
54
  onMouseEnter: JSX.MouseEventHandler<HTMLElement>;
39
55
  onMouseLeave: JSX.MouseEventHandler<HTMLElement>;
40
56
  onFocusIn: JSX.FocusEventHandler<HTMLElement>;
41
57
  onFocusOut: JSX.FocusEventHandler<HTMLElement>;
58
+ show: (el: HTMLElement) => void;
59
+ hide: (el: HTMLElement) => void;
42
60
  };
43
61
  export {};
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Export modal — renders the current `TweakState` as a design-tokens JSON
3
- * document (default schema: `zudo-design-tokens/v1`; configurable via
4
- * `panelConfig.schemaId`) with a diff-only toggle and a copy-to-clipboard
5
- * button.
3
+ * document (`serialize()` emits `SCHEMA_V2` or opportunistically `SCHEMA_V3`
4
+ * — see `./utils/design-token-serde`; `panelConfig.schemaId` is not read
5
+ * here or anywhere else in this package — it is a UI-label-only field on
6
+ * `PanelConfig` that hosts may read via `getDesignTokenSchema()`, #498) with
7
+ * a diff-only toggle and a copy-to-clipboard button.
6
8
  *
7
9
  * Ported verbatim from zudo-doc's
8
10
  * `src/components/design-token-tweak/export-modal.tsx`. Intentional deltas:
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Import modal — accepts a pasted design-tokens JSON document (default schema:
3
- * `zudo-design-tokens/v1`; configurable via `panelConfig.schemaId`) and lifts
4
- * it into a `TweakState` via `deserialize()`. Validation and unknown-token
5
- * reporting are delegated to serde; this component only renders status
6
- * feedback and routes the parsed state up to the shell.
2
+ * Import modal — accepts a pasted design-tokens JSON document and lifts it
3
+ * into a `TweakState` via `deserialize()`. `deserialize()` validates the
4
+ * `$schema` key against the fixed `SCHEMA_V1`/`SCHEMA_V2`/`SCHEMA_V3`
5
+ * constants exported by the serde — `panelConfig.schemaId` plays no part in
6
+ * that validation; it is a UI-label-only field (#498). This component does
7
+ * not read `schemaId` at all; the hint/placeholder/error copy below names
8
+ * the actual accepted constants directly.
7
9
  *
8
10
  * Ported verbatim from zudo-doc's
9
11
  * `src/components/design-token-tweak/import-modal.tsx`. Intentional deltas:
10
- * - `deserialize` / `DesignTokenSchemaError` / `getDesignTokenSchema` come
11
- * from this package's serde (`./utils/design-token-serde`); the schema
12
- * id is read at render time from `panelConfig.schemaId` instead of
13
- * being a module-level constant.
12
+ * - `deserialize` / `DesignTokenSchemaError` come from this package's serde
13
+ * (`./utils/design-token-serde`).
14
14
  * - State types import from this package's state envelope.
15
15
  * - Modal class names are derived from `panelConfig.modalClassPrefix` via
16
16
  * `modalClass(...)` so a single config swap re-themes every dialog.
package/dist/index.d.ts CHANGED
@@ -56,10 +56,11 @@ export type { ColorClusterConfig } from './state/tweak-state';
56
56
  export { ZDTP_LEGACY_TYPOGRAPHY_RENAME_MAP } from './state/tweak-state';
57
57
  export type { ColorScheme, ColorRef } from './config/color-schemes';
58
58
  export type { TokenManifest, TokenDef } from './tokens/manifest';
59
- export type { TierValueKind, PillSpec, TierItem, TierConfig, TabConfig, ColorClusterExtras, } from './tokens/tier-model';
59
+ export type { TierValueKind, PillSpec, TierItem, TierConfig, TabConfig, ColorClusterExtras, NotesExtras, } from './tokens/tier-model';
60
60
  export { isLengthKind, isNumberKind, isSelectKind, isTextKind, isColorKind, isCursorKind, isContentKind, isMaskImageKind, } from './tokens/tier-model';
61
61
  export type { TweakState } from './state/tweak-state';
62
62
  export { emptyOverrides } from './state/tweak-state';
63
+ export { SCHEMA_V1, SCHEMA_V2, SCHEMA_V3, } from './utils/design-token-serde';
63
64
  export { getClusterDefaultMode, resolvePerModeLiteral, resolveSemanticPreviewColor, } from './state/tweak-state';
64
65
  export declare function showDesignTokenPanel(): void;
65
66
  export declare function hideDesignTokenPanel(): void;