@blueprint-chart/lib 0.1.15 → 0.1.16

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.
@@ -1,4 +1,4 @@
1
- var BlueprintChart=(function(l){"use strict";const p=`
1
+ var BlueprintChart=(function(l){"use strict";const v=`
2
2
  /* Blueprint Chart — Runtime Embed Styles
3
3
  CSS custom properties with baked-in defaults for standalone iframe usage. */
4
4
 
@@ -89,6 +89,7 @@ var BlueprintChart=(function(l){"use strict";const p=`
89
89
  font-weight: 600;
90
90
  display: inline-flex;
91
91
  align-items: center;
92
+ text-decoration: none;
92
93
  }
93
94
 
94
95
  .bc-frame-source-link {
@@ -182,5 +183,5 @@ body {
182
183
  margin: 0;
183
184
  overflow: hidden;
184
185
  }
185
- `;function s(){document.querySelectorAll('script[type="application/blueprint-chart"]').forEach(u)}function u(t){var a,c;const e=(a=t.textContent)==null?void 0:a.trim();if(!e)return;const r=document.createElement("iframe");r.className="blueprint-chart-iframe",r.style.cssText="border: none; width: 100%; display: block;",r.setAttribute("sandbox","allow-scripts"),r.setAttribute("title","Blueprint Chart"),(c=t.parentNode)==null||c.insertBefore(r,t),r.srcdoc=g(e);const o=n=>{var i;((i=n.data)==null?void 0:i.type)==="blueprint-chart-resize"&&n.source===r.contentWindow&&(r.style.height=`${n.data.height}px`)};window.addEventListener("message",o)}function g(t){const e=h(t);return["<!DOCTYPE html>","<html><head>",`<style>${p}</style>`,"</head><body>",`<div id="chart" class="blueprint-chart-container blueprint-chart-placeholder">${e}</div>`,"<script>","function notifySize() {"," var h = document.documentElement.scrollHeight;",' parent.postMessage({ type: "blueprint-chart-resize", height: h }, "*");',"}","notifySize();","new ResizeObserver(notifySize).observe(document.body);","<\/script>","</body></html>"].join(`
186
- `)}function h(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(t,e,r){let o=0;const a=document.createElement("nav");a.className="blueprint-chart-scenes";const c=document.createElement("button");c.className="blueprint-chart-scenes-prev",c.textContent="Previous";const n=document.createElement("button");n.className="blueprint-chart-scenes-next",n.textContent="Next";const i=document.createElement("span");i.className="blueprint-chart-scenes-counter",a.appendChild(c),a.appendChild(i),a.appendChild(n),t.appendChild(a);function m(){i.textContent=`${o+1} / ${e.length}`}function d(x){const b=(x%e.length+e.length)%e.length;o=b,m(),r(e[b],b)}return c.addEventListener("click",()=>d(o-1)),n.addEventListener("click",()=>d(o+1)),m(),{get currentScene(){return o},get totalScenes(){return e.length},next(){d(o+1)},previous(){d(o-1)},goTo:d,destroy(){a.remove()}}}const v=f;return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",s):s(),l.createSceneController=f,l.createStepController=v,l.initBlueprint=s,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}),l})({});
186
+ `,p=new Map;let f=null;function x(){f||(f=t=>{for(const[e,r]of p)if(t.source===e.contentWindow){r(t);return}},window.addEventListener("message",f))}function b(){x(),document.querySelectorAll('script[type="application/blueprint-chart"]').forEach(y)}function y(t){var c,n;const e=(c=t.textContent)==null?void 0:c.trim();if(!e)return;const r=document.createElement("iframe");r.className="blueprint-chart-iframe",r.style.cssText="border: none; width: 100%; display: block;",r.setAttribute("sandbox","allow-scripts"),r.setAttribute("title","Blueprint Chart"),(n=t.parentNode)==null||n.insertBefore(r,t),r.srcdoc=z(e);const o=a=>{var i;((i=a.data)==null?void 0:i.type)==="blueprint-chart-resize"&&(r.style.height=`${a.data.height}px`)};p.set(r,o)}function z(t){const e=C(t);return["<!DOCTYPE html>","<html><head>",`<style>${v}</style>`,"</head><body>",`<div id="chart" class="blueprint-chart-container blueprint-chart-placeholder">${e}</div>`,"<script>","function notifySize() {"," var h = document.documentElement.scrollHeight;",' parent.postMessage({ type: "blueprint-chart-resize", height: h }, "*");',"}","notifySize();","new ResizeObserver(notifySize).observe(document.body);","<\/script>","</body></html>"].join(`
187
+ `)}function C(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function u(t,e,r){let o=0,c=!1;const n=document.createElement("nav");n.className="blueprint-chart-scenes";const a=document.createElement("button");a.className="blueprint-chart-scenes-prev",a.textContent="Previous";const i=document.createElement("button");i.className="blueprint-chart-scenes-next",i.textContent="Next";const s=document.createElement("span");s.className="blueprint-chart-scenes-counter",n.appendChild(a),n.appendChild(s),n.appendChild(i),e.length>0&&t.appendChild(n);function g(){if(e.length===0){s.textContent="";return}s.textContent=`${o+1} / ${e.length}`}function d(h){if(c||e.length===0||!Number.isFinite(h))return;const m=(Math.floor(h)%e.length+e.length)%e.length;o=m,g(),r(e[m],m)}return a.addEventListener("click",()=>d(o-1)),i.addEventListener("click",()=>d(o+1)),g(),{get currentScene(){return o},get totalScenes(){return e.length},next(){d(o+1)},previous(){d(o-1)},goTo:d,destroy(){c||(c=!0,n.remove())}}}const k=u;return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",b):b(),l.createSceneController=u,l.createStepController=k,l.initBlueprint=b,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}),l})({});
@@ -1,3 +1,5 @@
1
1
  import { ChartNode } from '../dsl/types';
2
2
  import { ChartDefinition } from './types';
3
+ /** @internal — exposed for tests that need to reset the warn-once cache. */
4
+ export declare function __resetTransformWarnings(): void;
3
5
  export declare function astToDefinition(ast: ChartNode): ChartDefinition;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Cancel and remove any in-flight fade-out overlays inside `container`.
3
+ *
4
+ * Safe to call at any time — when no overlay is present this is a no-op.
5
+ * Use as an explicit cleanup hook (e.g. when tearing down a chart or before
6
+ * starting a brand-new fade) to prevent stacked overlays and zombie WAAPI
7
+ * animations from accumulating on rapid re-triggers.
8
+ */
9
+ export declare function cancelInflightFade(container: HTMLElement): void;
1
10
  export declare function snapshotIfTypeChanged(container: HTMLElement, newChartType: string, transition: boolean): HTMLElement | null;
2
11
  export declare function commitCrossTypeFade(container: HTMLElement, newChartType: string, overlay: HTMLElement | null): void;
3
12
  export declare function clearCrossTypeMarker(container: HTMLElement): void;
@@ -1,2 +1,4 @@
1
1
  import { ChartDefinition, ResolvedChartState } from './types';
2
+ /** @internal — exposed for tests that need to reset the warn-once cache. */
3
+ export declare function __resetTransformWarnings(): void;
2
4
  export declare function resolveScene(def: ChartDefinition, sceneIndex: number | undefined): ResolvedChartState;
@@ -1,6 +1,7 @@
1
1
  import { SortDirection, SortMode } from '../enums';
2
2
  import { ChartData, ChartTypeOptions, ColorizeConfig, HighlightConfig, AreaFillConfig, AnnotationConfig, SeriesOverride, FrameOptions } from '../charts/types';
3
3
  import { PropertyNode, SceneNode } from '../dsl/types';
4
+ import { TransitionMode } from '../transitions/types';
4
5
  export interface ChartDefinition {
5
6
  chartType: string;
6
7
  data: ChartData;
@@ -20,6 +21,12 @@ export interface ChartDefinition {
20
21
  export interface RenderOptions {
21
22
  sceneIndex?: number;
22
23
  transition?: boolean;
24
+ /**
25
+ * Transition mode. Defaults to `'transform'` when omitted. Only the
26
+ * `transform` mode runs in v1; other values warn-once at the orchestrator
27
+ * and fall back to snap. See `transitions/index.ts`.
28
+ */
29
+ transitionMode?: TransitionMode;
23
30
  thumbnail?: boolean;
24
31
  stripColors?: boolean;
25
32
  ignoreLayout?: boolean;
@@ -1 +1 @@
1
- export declare const CHART_CSS = "\n/* Blueprint Chart \u2014 Runtime Embed Styles\n CSS custom properties with baked-in defaults for standalone iframe usage. */\n\n.bc-frame {\n --bc-frame-font-family: system-ui, -apple-system, sans-serif;\n --bc-frame-padding: 0;\n --bc-text-color: #333;\n font-family: var(--bc-frame-font-family);\n}\n\n.bc-frame-header {\n padding: var(--bc-frame-padding) var(--bc-frame-padding) 0;\n background: var(--bc-frame-header-bg, transparent);\n border-bottom: var(--bc-frame-header-border-bottom, none);\n margin-bottom: var(--bc-frame-header-margin-bottom, 0);\n}\n\n.bc-frame-body {\n padding: 0 var(--bc-frame-padding);\n}\n\n.bc-frame-title {\n --bc-frame-title-color: var(--bc-text-color, #333);\n --bc-frame-title-font-size: 1.25rem;\n color: var(--bc-frame-title-color);\n font-size: var(--bc-frame-title-font-size);\n font-weight: bold;\n margin: 0;\n}\n\n.bc-frame-description {\n --bc-frame-description-color: var(--bc-text-color, #555);\n --bc-frame-description-font-size: 0.875rem;\n color: var(--bc-frame-description-color);\n font-size: var(--bc-frame-description-font-size);\n margin: 0.25rem 0 0;\n}\n\n.bc-frame-footer {\n margin-top: 0.5rem;\n gap: 0.25rem 1rem;\n padding: var(--bc-frame-footer-padding-top, 0) var(--bc-frame-padding) var(--bc-frame-padding);\n background: var(--bc-frame-footer-bg, transparent);\n border-top: var(--bc-frame-footer-border-top, none);\n}\n\n.bc-frame-footer-left {\n display: flex;\n align-items: baseline;\n flex-wrap: wrap;\n gap: 0.25rem 0.75rem;\n}\n\n.bc-frame-footer-left > :not(:first-child)::before {\n content: \"\\00B7\";\n margin-right: 0.5rem;\n color: var(--bc-text-color, #888);\n}\n\n.bc-frame-footer-right {\n display: flex;\n align-items: center;\n}\n\n.bc-frame-note {\n --bc-frame-note-color: var(--bc-text-color, #888);\n --bc-frame-note-font-size: 0.75rem;\n font-style: italic;\n color: var(--bc-frame-note-color);\n font-size: var(--bc-frame-note-font-size);\n margin: 0;\n padding: 0 var(--bc-frame-padding);\n}\n\n.bc-frame-byline,\n.bc-frame-source {\n --bc-frame-meta-color: var(--bc-text-color, #888);\n --bc-frame-meta-font-size: 0.75rem;\n color: var(--bc-frame-meta-color);\n font-size: var(--bc-frame-meta-font-size);\n}\n\n.bc-frame-credit {\n --bc-frame-credit-color: var(--bc-text-color, #666);\n --bc-frame-credit-font-size: 0.75rem;\n color: var(--bc-frame-credit-color);\n font-size: var(--bc-frame-credit-font-size);\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n}\n\n.bc-frame-source-link {\n color: inherit;\n text-decoration: underline;\n}\n\n.bc-axis .domain {\n stroke: var(--bc-axis-color, #333);\n}\n\n.bc-axis .tick text {\n fill: var(--bc-axis-color, #333);\n font-size: 10px;\n}\n\n.bc-axis .tick line {\n stroke: var(--bc-axis-color, #333);\n}\n\n.bc-grid-line {\n stroke: var(--bc-grid-color, #e0e0e0);\n stroke-width: 1;\n}\n\n.bc-zero-baseline {\n stroke: #666;\n stroke-width: 1;\n}\n\n.bc-bar {\n /* fill is set via D3 .attr() from data-bound colors \u2014 do not override */\n}\n\n.bc-line {\n fill: none;\n stroke-width: 2;\n}\n\n.bc-value-label {\n font-size: 11px;\n /* fill is set via D3 .attr() \u2014 do not override */\n}\n\n.bc-direct-label {\n font-size: 12px;\n font-weight: 600;\n}\n\n.bc-tooltip {\n position: absolute;\n pointer-events: none;\n background: var(--bc-tooltip-bg, #fff);\n color: var(--bc-tooltip-color, #212529);\n border: 1px solid var(--bc-tooltip-border-color, #dee2e6);\n border-radius: 4px;\n padding: 6px 10px;\n font-size: 13px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.12);\n z-index: 9999;\n display: none;\n}\n\n.bc-crosshair {\n stroke: #999;\n stroke-width: 1;\n pointer-events: none;\n}\n\n.bc-arc-label-line {\n fill: none;\n stroke: #999;\n stroke-width: 1;\n}\n\n.bc-theme-blueprint-framed {\n --bc-frame-header-border-bottom: 1px solid #e0e0e0;\n --bc-frame-header-margin-bottom: 0.5rem;\n --bc-frame-footer-bg: #f8f8f8;\n --bc-frame-footer-border-top: 1px solid #e0e0e0;\n --bc-frame-footer-padding-top: 0.625rem;\n}\n\n.blueprint-chart-error {\n color: red;\n padding: 1em;\n border: 1px solid red;\n}\n\nbody {\n margin: 0;\n overflow: hidden;\n}\n";
1
+ export declare const CHART_CSS = "\n/* Blueprint Chart \u2014 Runtime Embed Styles\n CSS custom properties with baked-in defaults for standalone iframe usage. */\n\n.bc-frame {\n --bc-frame-font-family: system-ui, -apple-system, sans-serif;\n --bc-frame-padding: 0;\n --bc-text-color: #333;\n font-family: var(--bc-frame-font-family);\n}\n\n.bc-frame-header {\n padding: var(--bc-frame-padding) var(--bc-frame-padding) 0;\n background: var(--bc-frame-header-bg, transparent);\n border-bottom: var(--bc-frame-header-border-bottom, none);\n margin-bottom: var(--bc-frame-header-margin-bottom, 0);\n}\n\n.bc-frame-body {\n padding: 0 var(--bc-frame-padding);\n}\n\n.bc-frame-title {\n --bc-frame-title-color: var(--bc-text-color, #333);\n --bc-frame-title-font-size: 1.25rem;\n color: var(--bc-frame-title-color);\n font-size: var(--bc-frame-title-font-size);\n font-weight: bold;\n margin: 0;\n}\n\n.bc-frame-description {\n --bc-frame-description-color: var(--bc-text-color, #555);\n --bc-frame-description-font-size: 0.875rem;\n color: var(--bc-frame-description-color);\n font-size: var(--bc-frame-description-font-size);\n margin: 0.25rem 0 0;\n}\n\n.bc-frame-footer {\n margin-top: 0.5rem;\n gap: 0.25rem 1rem;\n padding: var(--bc-frame-footer-padding-top, 0) var(--bc-frame-padding) var(--bc-frame-padding);\n background: var(--bc-frame-footer-bg, transparent);\n border-top: var(--bc-frame-footer-border-top, none);\n}\n\n.bc-frame-footer-left {\n display: flex;\n align-items: baseline;\n flex-wrap: wrap;\n gap: 0.25rem 0.75rem;\n}\n\n.bc-frame-footer-left > :not(:first-child)::before {\n content: \"\\00B7\";\n margin-right: 0.5rem;\n color: var(--bc-text-color, #888);\n}\n\n.bc-frame-footer-right {\n display: flex;\n align-items: center;\n}\n\n.bc-frame-note {\n --bc-frame-note-color: var(--bc-text-color, #888);\n --bc-frame-note-font-size: 0.75rem;\n font-style: italic;\n color: var(--bc-frame-note-color);\n font-size: var(--bc-frame-note-font-size);\n margin: 0;\n padding: 0 var(--bc-frame-padding);\n}\n\n.bc-frame-byline,\n.bc-frame-source {\n --bc-frame-meta-color: var(--bc-text-color, #888);\n --bc-frame-meta-font-size: 0.75rem;\n color: var(--bc-frame-meta-color);\n font-size: var(--bc-frame-meta-font-size);\n}\n\n.bc-frame-credit {\n --bc-frame-credit-color: var(--bc-text-color, #666);\n --bc-frame-credit-font-size: 0.75rem;\n color: var(--bc-frame-credit-color);\n font-size: var(--bc-frame-credit-font-size);\n font-weight: 600;\n display: inline-flex;\n align-items: center;\n text-decoration: none;\n}\n\n.bc-frame-source-link {\n color: inherit;\n text-decoration: underline;\n}\n\n.bc-axis .domain {\n stroke: var(--bc-axis-color, #333);\n}\n\n.bc-axis .tick text {\n fill: var(--bc-axis-color, #333);\n font-size: 10px;\n}\n\n.bc-axis .tick line {\n stroke: var(--bc-axis-color, #333);\n}\n\n.bc-grid-line {\n stroke: var(--bc-grid-color, #e0e0e0);\n stroke-width: 1;\n}\n\n.bc-zero-baseline {\n stroke: #666;\n stroke-width: 1;\n}\n\n.bc-bar {\n /* fill is set via D3 .attr() from data-bound colors \u2014 do not override */\n}\n\n.bc-line {\n fill: none;\n stroke-width: 2;\n}\n\n.bc-value-label {\n font-size: 11px;\n /* fill is set via D3 .attr() \u2014 do not override */\n}\n\n.bc-direct-label {\n font-size: 12px;\n font-weight: 600;\n}\n\n.bc-tooltip {\n position: absolute;\n pointer-events: none;\n background: var(--bc-tooltip-bg, #fff);\n color: var(--bc-tooltip-color, #212529);\n border: 1px solid var(--bc-tooltip-border-color, #dee2e6);\n border-radius: 4px;\n padding: 6px 10px;\n font-size: 13px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.12);\n z-index: 9999;\n display: none;\n}\n\n.bc-crosshair {\n stroke: #999;\n stroke-width: 1;\n pointer-events: none;\n}\n\n.bc-arc-label-line {\n fill: none;\n stroke: #999;\n stroke-width: 1;\n}\n\n.bc-theme-blueprint-framed {\n --bc-frame-header-border-bottom: 1px solid #e0e0e0;\n --bc-frame-header-margin-bottom: 0.5rem;\n --bc-frame-footer-bg: #f8f8f8;\n --bc-frame-footer-border-top: 1px solid #e0e0e0;\n --bc-frame-footer-padding-top: 0.625rem;\n}\n\n.blueprint-chart-error {\n color: red;\n padding: 1em;\n border: 1px solid red;\n}\n\nbody {\n margin: 0;\n overflow: hidden;\n}\n";
@@ -1 +1,2 @@
1
1
  export declare function initBlueprint(): void;
2
+ export declare function teardownBlueprint(): void;
@@ -12,7 +12,7 @@ export interface SceneController {
12
12
  goTo(index: number): void;
13
13
  destroy(): void;
14
14
  }
15
- /** @deprecated Use SceneController instead */
15
+ /** @deprecated Use StepController instead */
16
16
  export type StepController = SceneController;
17
17
  export declare function createSceneController(container: HTMLElement, scenes: SceneDefinition[], onSceneChange: (scene: SceneDefinition, index: number) => void): SceneController;
18
18
  /** @deprecated Use createSceneController instead */
@@ -0,0 +1,13 @@
1
+ import { SceneTransition } from './scene-transition';
2
+ import { FeatureJoinConfig } from './types';
3
+ /**
4
+ * Idempotent keyed data-join for a single visual feature.
5
+ *
6
+ * Behaviour by orchestrator state:
7
+ * - `idle` / `animating` — plain d3 data-join; attrs applied directly.
8
+ * A featureJoin during `animating` cannot piggyback on the in-flight
9
+ * transition (it has no place to register), so we snap. Mid-tween
10
+ * featureJoin calls should be rare in normal flow; tests cover them.
11
+ * - `committing` — buffer enter / update / exit for the next commit.
12
+ */
13
+ export declare function featureJoin<D>(orchestrator: SceneTransition, cfg: FeatureJoinConfig<D>): void;
@@ -0,0 +1,7 @@
1
+ export { SceneTransition, getSceneTransition } from './scene-transition';
2
+ export { BC_TRANSITION_NAME } from './types';
3
+ export type { CommitOptions } from './scene-transition';
4
+ export { featureJoin } from './feature-join';
5
+ export { roleScan, tagsCompatible, shouldEscalateToFade, ROLE_MATCH_THRESHOLD } from './role-matcher';
6
+ export { snapshotLiveAttrs } from './snapshot';
7
+ export type { TransitionMode, SceneTransitionState, FeatureRole, FeatureJoinConfig, AttrMap, AttrValue, } from './types';
@@ -0,0 +1,42 @@
1
+ import { FeatureRole } from './types';
2
+ /**
3
+ * Minimum fraction of new features that must match a prior role-tagged
4
+ * element for per-feature morph to be preferred over whole-chart crossfade.
5
+ *
6
+ * Below this threshold, the visual gap between "old chart" and "new chart"
7
+ * is large enough that morphing a small overlap feels jarring; a clean
8
+ * crossfade reads better. Stage 7 ships this constant as scaffolding; the
9
+ * orchestrator-level wiring lives downstream.
10
+ */
11
+ export declare const ROLE_MATCH_THRESHOLD = 0.5;
12
+ /**
13
+ * Return all live elements in `container` tagged with the given role,
14
+ * keyed by their `data-bc-key` attribute. Used by featureJoin to find
15
+ * cross-feature predecessors when the new commit's selector differs
16
+ * from the prior's.
17
+ *
18
+ * Elements missing a `data-bc-key` are skipped (they couldn't be matched
19
+ * against the new data anyway).
20
+ */
21
+ export declare function roleScan(container: HTMLElement, role: FeatureRole): Map<string, Element>;
22
+ /**
23
+ * Tag-name compatibility check for cross-type morph.
24
+ *
25
+ * Two elements can morph attribute-to-attribute only when they share a tag
26
+ * (rect → rect, path → path). When the new feature uses a different SVG
27
+ * element than the prior (rect ↔ circle, rect ↔ path), the orchestrator
28
+ * falls back to per-feature crossfade rather than attempting an invalid
29
+ * attribute tween.
30
+ */
31
+ export declare function tagsCompatible(prior: Element, next: Element): boolean;
32
+ /**
33
+ * Decide whether the orchestrator should escalate a commit from per-feature
34
+ * morph to whole-chart crossfade based on how many of the new features have
35
+ * a same-role predecessor.
36
+ *
37
+ * Returns `true` when there is something to match against (`total > 0`) but
38
+ * the match ratio falls below {@link ROLE_MATCH_THRESHOLD}. When `total` is
39
+ * 0 there is nothing to match — return `false` so the caller treats this as
40
+ * a normal first-render rather than a crossfade.
41
+ */
42
+ export declare function shouldEscalateToFade(matchedCount: number, totalCount: number): boolean;
@@ -0,0 +1,78 @@
1
+ import { SceneTransitionState, TransitionMode } from './types';
2
+ /** Get or create the SceneTransition bound to this container. */
3
+ export declare function getSceneTransition(container: HTMLElement): SceneTransition;
4
+ export interface CommitOptions {
5
+ /** Tween duration in ms. `0` snaps without animating (reduced-motion path). */
6
+ duration?: number;
7
+ /** Transition mode. Defaults to `'transform'`. Other values warn-once and snap. */
8
+ mode?: TransitionMode;
9
+ }
10
+ /**
11
+ * Per-container orchestrator that owns the scene transition lifecycle.
12
+ *
13
+ * Lifecycle:
14
+ * idle ──beginCommit()──▶ committing ──commit()──▶ animating ──end──▶ idle
15
+ *
16
+ * Re-entry into `beginCommit()` while in `committing` or `animating`
17
+ * interrupts the prior transition (invariant I1) before proceeding.
18
+ *
19
+ * NOTE: this task implements only the lifecycle / interrupt / registry.
20
+ * `featureJoin` and feature buffering arrive in the next task.
21
+ */
22
+ export declare class SceneTransition {
23
+ readonly container: HTMLElement;
24
+ private _state;
25
+ private _transition;
26
+ private _buffer;
27
+ /**
28
+ * Internal: register a flush callback to be run on the next commit.
29
+ * Used by `featureJoin` to defer DOM mutations until the orchestrator
30
+ * is animating. Callbacks run in registration order.
31
+ */
32
+ register(flush: () => void): void;
33
+ constructor(container: HTMLElement);
34
+ get state(): SceneTransitionState;
35
+ /** Internal: the active d3 transition handle, or null when not animating. */
36
+ get activeTransition(): any;
37
+ /**
38
+ * Enter the committing phase. If a prior transition is in flight, it
39
+ * is interrupted first (invariant I1).
40
+ */
41
+ beginCommit(): void;
42
+ /**
43
+ * Commit any buffered work and start animating.
44
+ *
45
+ * If `duration` is 0 (e.g. prefers-reduced-motion), the orchestrator
46
+ * goes straight to idle without creating a d3 transition. Tests that
47
+ * pass `duration: 0` rely on this snap path.
48
+ */
49
+ commit(opts?: CommitOptions): void;
50
+ /**
51
+ * Clamp a requested duration to 0 when the user prefers reduced motion.
52
+ * The lifecycle still runs (snap path), so the same code paths exercise
53
+ * both animated and reduced-motion behaviour — no aesthetic-only branch.
54
+ */
55
+ private effectiveDuration;
56
+ /**
57
+ * Cancel any in-flight tween. Always safe; idempotent on `idle`.
58
+ *
59
+ * NOTE: this interrupts BC_TRANSITION_NAME tweens on the container and
60
+ * its attached descendants. A descendant that was detached from the
61
+ * container (e.g. an exit node that already self-removed) cannot be
62
+ * reached and its tween may continue ticking until natural completion.
63
+ * Task 5 (featureJoin exit pathway) must therefore wait for the
64
+ * transition's `end` event before calling `.remove()`, not before.
65
+ */
66
+ interrupt(): void;
67
+ /**
68
+ * Convenience: run a synchronous callback inside `committing` and then
69
+ * commit. Catches exceptions from the callback so the lifecycle never
70
+ * gets stuck.
71
+ */
72
+ run(work: () => void, opts?: CommitOptions): void;
73
+ /**
74
+ * Tear down: interrupt and drop the WeakMap entry so a fresh lookup
75
+ * returns a new instance. Idempotent.
76
+ */
77
+ destroy(): void;
78
+ }
@@ -0,0 +1,16 @@
1
+ import { AttrMap } from './types';
2
+ /**
3
+ * Read the listed attributes off `el` as currently set in the DOM.
4
+ *
5
+ * Before reading, the orchestrator-named d3 transition on the element
6
+ * is interrupted (invariant I4). After interrupt, the attribute values
7
+ * reflect "where the pixels are right now" — exactly what we need as
8
+ * the starting point of a cancel-and-retween.
9
+ *
10
+ * The interrupt targets only the `BC_TRANSITION_NAME` transition so
11
+ * unrelated transitions on the element are unaffected.
12
+ *
13
+ * Attributes that are not present on the element are omitted from the
14
+ * result rather than represented as `null`.
15
+ */
16
+ export declare function snapshotLiveAttrs(el: Element, names: readonly string[]): AttrMap;
@@ -0,0 +1,44 @@
1
+ import { Selection } from 'd3';
2
+ /**
3
+ * Name used for every d3 transition created by the orchestrator.
4
+ * Snapshot + interrupt operations target this name so they don't
5
+ * collide with any unrelated transitions a renderer might have.
6
+ */
7
+ export declare const BC_TRANSITION_NAME = "bc-scene";
8
+ /** Transition mode chosen by the caller. Only `'transform'` runs in v1. */
9
+ export type TransitionMode = 'transform' | 'fade' | 'slide-x' | 'slide-y';
10
+ /** Lifecycle state of a SceneTransition instance. */
11
+ export type SceneTransitionState = 'idle' | 'committing' | 'animating';
12
+ /**
13
+ * Abstract feature role used for cross-type morph matching.
14
+ *
15
+ * Roles are container-independent: a tick on a vertical axis in one scene
16
+ * and a tick on a horizontal axis in the next can match on
17
+ * `'axis-tick.value'`. The role-matcher (future plan) acts on this tag;
18
+ * v1 stores it without acting on it for cross-type.
19
+ */
20
+ export type FeatureRole = 'mark-per-category' | 'mark-per-cell' | 'series-path' | 'axis-tick.value' | 'axis-tick.category' | 'value-label' | 'annotation.point' | 'annotation.range' | 'annotation.free';
21
+ /** Subset of SVG / HTML attribute values we tween. */
22
+ export type AttrValue = string | number;
23
+ /** Map of attribute name → value. */
24
+ export type AttrMap = Record<string, AttrValue>;
25
+ /**
26
+ * Description of a keyed visual feature for the orchestrator.
27
+ *
28
+ * The orchestrator reads `data`, matches existing DOM by `key`, and on
29
+ * commit either snaps every element to `attrs(d)` (idle path) or tweens
30
+ * each entry through enter/update/exit pathways (animating path).
31
+ */
32
+ export interface FeatureJoinConfig<D> {
33
+ role: FeatureRole;
34
+ parent: SVGElement;
35
+ selector: string;
36
+ data: D[];
37
+ key: (d: D) => string;
38
+ insert: (enterSel: Selection<any, D, any, any>) => Selection<any, D, any, any>;
39
+ attrs: (d: D) => AttrMap;
40
+ /** Attrs to apply to a brand-new element at the start of its enter tween. Defaults to `attrs(d)` (snap-in). */
41
+ enterFrom?: (d: D) => AttrMap;
42
+ /** Attrs to tween an exiting element toward before removal. Default: `{ opacity: 0 }`. */
43
+ exitTo?: (d: D) => AttrMap;
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueprint-chart/lib",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "DSL-driven charting library built on D3. Define charts as text, render them anywhere.",
5
5
  "keywords": [
6
6
  "chart",
@@ -35,6 +35,30 @@
35
35
  background: var(--bc-frame-bg);
36
36
  }
37
37
 
38
+ // During a cross-type scene transition, `snapshotForFadeOut()` in motion.ts
39
+ // renames `.bc-frame` -> `.bc-frame--fade-snapshot` on the overlay clone so
40
+ // selectors targeting `.bc-frame` don't double-match the live + fading
41
+ // charts. Without this alias, the snapshot loses the frame's background and
42
+ // font, so the fade-out has nothing visible to fade — producing a flash.
43
+ // `@extend` makes the snapshot inherit every rule rooted on `.bc-frame`,
44
+ // including any descendant selectors added in the future.
45
+ .bc-frame--fade-snapshot {
46
+ @extend .bc-frame;
47
+ }
48
+
49
+ // The fade overlay is a position:absolute; inset:0; display:flex column
50
+ // container created by snapshotForFadeOut() during cross-type scene
51
+ // transitions. Its cloned chart needs to fill it so the fade-out is
52
+ // visible at the same dimensions as the original chart.
53
+ [data-bc-fade-overlay] {
54
+ > .bc-frame,
55
+ > .bc-frame--fade-snapshot {
56
+ flex: 1;
57
+ min-height: 0;
58
+ width: 100%;
59
+ }
60
+ }
61
+
38
62
  // Constrained-height mode: when the frame's container has a definite height
39
63
  // (fixed or aspect-ratio).
40
64
  //
@@ -86,7 +110,7 @@
86
110
  // -- Header elements --------------------------------------------------------
87
111
 
88
112
  .bc-frame-header {
89
- padding: var(--bc-frame-padding) var(--bc-frame-padding) var(--bc-frame-header-padding-bottom, var(--bc-frame-padding));
113
+ padding: var(--bc-frame-padding) var(--bc-frame-padding) var(--bc-frame-header-padding-bottom, 0.5rem);
90
114
  background: var(--bc-frame-header-bg, transparent);
91
115
  border-bottom: var(--bc-frame-header-border-bottom, none);
92
116
  margin-bottom: var(--bc-frame-header-margin-bottom, 0);
@@ -197,6 +221,7 @@
197
221
  font-weight: var(--bc-frame-credit-font-weight);
198
222
  display: inline-flex;
199
223
  align-items: center;
224
+ text-decoration: none;
200
225
  }
201
226
 
202
227
  // ---------------------------------------------------------------------------
@@ -1,8 +0,0 @@
1
- export interface Token {
2
- type: TokenType;
3
- value: string;
4
- line: number;
5
- column: number;
6
- }
7
- export type TokenType = 'keyword' | 'string' | 'number' | 'percent' | 'lbrace' | 'rbrace' | 'equals' | 'tab' | 'identifier' | 'eof';
8
- export declare function tokenize(input: string): Token[];