diff --git a/.changeset/portable-chart-barrels.md b/.changeset/portable-chart-barrels.md new file mode 100644 index 0000000..5e328d4 --- /dev/null +++ b/.changeset/portable-chart-barrels.md @@ -0,0 +1,6 @@ +--- +'@tanstack/charts': minor +--- + +Add environment-safe `/portable` and `/types` entry points while preserving +the existing browser-oriented root exports. diff --git a/API-FRICTION.md b/API-FRICTION.md index fd9fd52..758713a 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -189,6 +189,7 @@ Each entry records: | F-151 | Artifact actions targeted deprecated Node 20 | Tooling | resolved | | F-152 | Version bumps invalidated workspace bundle evidence | Tooling/Release | resolved | | F-153 | Changesets left release-facing version claims behind | Tooling/Release | resolved | +| F-154 | Root barrels crossed the browser host boundary | API/Tooling | resolved | ## Findings @@ -3655,3 +3656,28 @@ Each entry records: - Verification: focused tests cover version-heading discovery, complete replacement, idempotency, and missing-version rejection. Generated package docs remain outputs of `pnpm docs:sync`, never hand-edited sources. + +### F-154 — Root barrels crossed the browser host boundary + +- Status: resolved +- Severity: high +- Owner: API/Tooling +- Observed in: isolating the React Native host proof from the core package +- Friction: shared chart definitions and scene compilation are + platform-neutral, but the root value barrel made bundlers traverse DOM hosts, + adapters, reconciliation, and SVG surfaces. Its type graph also declared + `Element`, `HTMLElement`, and `SVGSVGElement`, so a non-DOM consumer could + not select the portable contracts as one supported entry. +- Decision: preserve the existing browser-oriented root API and add + `@tanstack/charts/portable` for common authoring/runtime values plus + `@tanstack/charts/types` for portable contracts. DOM surface, renderer, host, + and render-context types now live behind an internal module while retaining + their existing root re-exports. Do not conditionally change the root until a + native host can test one coherent platform contract. +- Verification: root typechecking and 61 focused core tests pass. The packed + package gate resolves both new entries from `dist`, compiles their + declarations with Web Worker rather than DOM globals, and proves the + portable bundle excludes the root, adapters, Canvas, DOM host/text, browser + export, reconciliation, renderer, and SVG surface modules. That full + portable barrel measures 55.26 kB minified and 17.04 kB gzip; granular + subpaths remain the bundle-sensitive option. diff --git a/benchmarks/comparison/bundle-baseline.json b/benchmarks/comparison/bundle-baseline.json index d5ba33b..60be679 100644 --- a/benchmarks/comparison/bundle-baseline.json +++ b/benchmarks/comparison/bundle-baseline.json @@ -1,8 +1,8 @@ { "schemaVersion": 3, - "generatedAt": "2026-07-31T05:39:15.459Z", + "generatedAt": "2026-07-31T18:30:38.408Z", "packageVersions": { - "tanstack": "0.0.1", + "tanstack": "0.0.2", "chartjs": "4.5.1", "echarts": "6.1.0", "recharts": "3.10.1", @@ -11,7 +11,7 @@ "sources": { "tanstack": { "kind": "workspace", - "revision": "5c36a3866d71ad94cbcc934272e2cd7a868065f3" + "revision": "059faf0ed4d74f360529356c77e6e0fa46bacf65" }, "chartjs": { "kind": "package", diff --git a/docs/comparison.md b/docs/comparison.md index 81f1fb1..100eab7 100644 --- a/docs/comparison.md +++ b/docs/comparison.md @@ -12,14 +12,14 @@ evidence without turning untested behavior into a checkmark. | Library | Package | Measured source | | -------------------------------------------------------------------------------------- | -------------------- | ------------------- | -| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `5c36a38` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `059faf0` | | [Chart.js](https://www.chartjs.org/docs/latest/) | `chart.js` | npm `4.5.1` | | [Apache ECharts](https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg/) | `echarts` | npm `6.1.0` | | [Recharts](https://recharts.github.io/en-US/) | `recharts` | npm `3.10.1` | | [Observable Plot](https://observablehq.com/plot/features/plots) | `@observablehq/plot` | npm `0.6.17` | The competitor versions are exact package pins, not latest versions inferred -at page render time. The measured TanStack workspace revision is `5c36a38`. +at page render time. The measured TanStack workspace revision is `059faf0`. ## Capability matrix diff --git a/docs/installation.md b/docs/installation.md index bed3946..461edea 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -135,7 +135,20 @@ import { mountChart } from '@tanstack/charts/dom' import { renderChartSvg } from '@tanstack/charts/svg' ``` -Subpaths are not a second API. They expose the same functions without making unrelated marks, the DOM host, exporters, or framework code reachable from that entry. +Use the portable barrel when definitions and scene compilation must not make +the browser host reachable: + +```ts +import { + createChartRuntime, + defineChart, + lineY, +} from '@tanstack/charts/portable' +import type { ChartDefinition } from '@tanstack/charts/types' +``` + +The root remains the browser-oriented compatibility entry. Subpaths expose the +same contracts behind explicit capability boundaries. Optional capabilities have explicit entries: diff --git a/docs/reference/index.md b/docs/reference/index.md index ef59e59..bd00a6f 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -57,7 +57,8 @@ select the optional Canvas renderer; their `/core` entries require an explicit ## Import map The root `@tanstack/charts` entry point exports the common grammar, runtime, -scene, SVG renderer, and their public types. Granular subpaths keep optional +scene, SVG renderer, browser host, and their public types. The portable entry +excludes browser hosts and adapters. Granular subpaths keep optional capabilities and individual marks independently tree-shakeable. | Import | Public values | @@ -86,6 +87,7 @@ capabilities and individual marks independently tree-shakeable. | `@tanstack/charts/link` | `link` | | `@tanstack/charts/mark/scale-values` | `createMarkWithScaleValues` | | `@tanstack/charts/polar` | `polar`, radial arc/line/area/dot marks, and radial/angle guides | +| `@tanstack/charts/portable` | Common root authoring, runtime, scene, and static SVG values without browser hosts or adapters | | `@tanstack/charts/reconcile` | `reconcileChartSvg` | | `@tanstack/charts/rect` | `rect`, `cell` | | `@tanstack/charts/renderer` | `mountChartRenderer` | @@ -97,6 +99,7 @@ capabilities and individual marks independently tree-shakeable. | `@tanstack/charts/svg/resources` | `renderChartSvgWithResources` | | `@tanstack/charts/text` | `text` | | `@tanstack/charts/tick` | `tickX`, `tickY` | +| `@tanstack/charts/types` | Portable definition, mark, scene, runtime, focus, and tooltip-model types | | `@tanstack/charts/vector` | `vector` | Import from the narrowest stable entry point when bundle isolation matters. diff --git a/docs/reference/types.md b/docs/reference/types.md index ae2b33a..166efa9 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -8,6 +8,11 @@ flow through its definition into scales, axis formatters, host and adapter callbacks, focus callbacks, and selection callbacks. Normal application code should not cast chart definitions or supply adapter generics. +Browser applications can import types from the package root. Platform-neutral +libraries can import the same definition, mark, scene, runtime, focus, and +tooltip-model contracts from `@tanstack/charts/types`; DOM host and renderer +types remain available from the root. + ## Values and channels ```ts diff --git a/packages/charts-core/docs/comparison.md b/packages/charts-core/docs/comparison.md index 81f1fb1..100eab7 100644 --- a/packages/charts-core/docs/comparison.md +++ b/packages/charts-core/docs/comparison.md @@ -12,14 +12,14 @@ evidence without turning untested behavior into a checkmark. | Library | Package | Measured source | | -------------------------------------------------------------------------------------- | -------------------- | ------------------- | -| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `5c36a38` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `059faf0` | | [Chart.js](https://www.chartjs.org/docs/latest/) | `chart.js` | npm `4.5.1` | | [Apache ECharts](https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg/) | `echarts` | npm `6.1.0` | | [Recharts](https://recharts.github.io/en-US/) | `recharts` | npm `3.10.1` | | [Observable Plot](https://observablehq.com/plot/features/plots) | `@observablehq/plot` | npm `0.6.17` | The competitor versions are exact package pins, not latest versions inferred -at page render time. The measured TanStack workspace revision is `5c36a38`. +at page render time. The measured TanStack workspace revision is `059faf0`. ## Capability matrix diff --git a/packages/charts-core/docs/installation.md b/packages/charts-core/docs/installation.md index bed3946..461edea 100644 --- a/packages/charts-core/docs/installation.md +++ b/packages/charts-core/docs/installation.md @@ -135,7 +135,20 @@ import { mountChart } from '@tanstack/charts/dom' import { renderChartSvg } from '@tanstack/charts/svg' ``` -Subpaths are not a second API. They expose the same functions without making unrelated marks, the DOM host, exporters, or framework code reachable from that entry. +Use the portable barrel when definitions and scene compilation must not make +the browser host reachable: + +```ts +import { + createChartRuntime, + defineChart, + lineY, +} from '@tanstack/charts/portable' +import type { ChartDefinition } from '@tanstack/charts/types' +``` + +The root remains the browser-oriented compatibility entry. Subpaths expose the +same contracts behind explicit capability boundaries. Optional capabilities have explicit entries: diff --git a/packages/charts-core/docs/reference/index.md b/packages/charts-core/docs/reference/index.md index ef59e59..bd00a6f 100644 --- a/packages/charts-core/docs/reference/index.md +++ b/packages/charts-core/docs/reference/index.md @@ -57,7 +57,8 @@ select the optional Canvas renderer; their `/core` entries require an explicit ## Import map The root `@tanstack/charts` entry point exports the common grammar, runtime, -scene, SVG renderer, and their public types. Granular subpaths keep optional +scene, SVG renderer, browser host, and their public types. The portable entry +excludes browser hosts and adapters. Granular subpaths keep optional capabilities and individual marks independently tree-shakeable. | Import | Public values | @@ -86,6 +87,7 @@ capabilities and individual marks independently tree-shakeable. | `@tanstack/charts/link` | `link` | | `@tanstack/charts/mark/scale-values` | `createMarkWithScaleValues` | | `@tanstack/charts/polar` | `polar`, radial arc/line/area/dot marks, and radial/angle guides | +| `@tanstack/charts/portable` | Common root authoring, runtime, scene, and static SVG values without browser hosts or adapters | | `@tanstack/charts/reconcile` | `reconcileChartSvg` | | `@tanstack/charts/rect` | `rect`, `cell` | | `@tanstack/charts/renderer` | `mountChartRenderer` | @@ -97,6 +99,7 @@ capabilities and individual marks independently tree-shakeable. | `@tanstack/charts/svg/resources` | `renderChartSvgWithResources` | | `@tanstack/charts/text` | `text` | | `@tanstack/charts/tick` | `tickX`, `tickY` | +| `@tanstack/charts/types` | Portable definition, mark, scene, runtime, focus, and tooltip-model types | | `@tanstack/charts/vector` | `vector` | Import from the narrowest stable entry point when bundle isolation matters. diff --git a/packages/charts-core/docs/reference/types.md b/packages/charts-core/docs/reference/types.md index ae2b33a..166efa9 100644 --- a/packages/charts-core/docs/reference/types.md +++ b/packages/charts-core/docs/reference/types.md @@ -8,6 +8,11 @@ flow through its definition into scales, axis formatters, host and adapter callbacks, focus callbacks, and selection callbacks. Normal application code should not cast chart definitions or supply adapter generics. +Browser applications can import types from the package root. Platform-neutral +libraries can import the same definition, mark, scene, runtime, focus, and +tooltip-model contracts from `@tanstack/charts/types`; DOM host and renderer +types remain available from the root. + ## Values and channels ```ts diff --git a/packages/charts-core/package.json b/packages/charts-core/package.json index 4ce86ba..21f33e4 100644 --- a/packages/charts-core/package.json +++ b/packages/charts-core/package.json @@ -54,6 +54,7 @@ "./link": "./src/link.ts", "./mark/scale-values": "./src/mark-with-scale-values.ts", "./polar": "./src/polar.ts", + "./portable": "./src/portable.ts", "./reconcile": "./src/reconcile.ts", "./rect": "./src/rect.ts", "./renderer": "./src/renderer.ts", @@ -65,6 +66,7 @@ "./svg/resources": "./src/svg-resources.ts", "./text": "./src/text.ts", "./tick": "./src/tick.ts", + "./types": "./src/portable-types.ts", "./vector": "./src/vector.ts" }, "publishConfig": { @@ -167,6 +169,10 @@ "types": "./dist/polar.d.ts", "import": "./dist/polar.js" }, + "./portable": { + "types": "./dist/portable.d.ts", + "import": "./dist/portable.js" + }, "./reconcile": { "types": "./dist/reconcile.d.ts", "import": "./dist/reconcile.js" @@ -211,6 +217,10 @@ "types": "./dist/tick.d.ts", "import": "./dist/tick.js" }, + "./types": { + "types": "./dist/portable-types.d.ts", + "import": "./dist/portable-types.js" + }, "./vector": { "types": "./dist/vector.d.ts", "import": "./dist/vector.js" diff --git a/packages/charts-core/src/adapter-renderer.ts b/packages/charts-core/src/adapter-renderer.ts index e217e1e..060dbe0 100644 --- a/packages/charts-core/src/adapter-renderer.ts +++ b/packages/charts-core/src/adapter-renderer.ts @@ -2,12 +2,8 @@ import { mountChartRenderer } from './renderer' import { createChartRuntime } from './runtime' import { resolveChartAdapterLayout } from './adapter-shared' import type { ChartAdapter } from './adapter-shared' -import type { - ChartRendererHost, - ChartRendererHostOptions, - ChartRuntime, - ChartValue, -} from './types' +import type { ChartRendererHost, ChartRendererHostOptions } from './dom-types' +import type { ChartRuntime, ChartValue } from './types' export function createChartRendererAdapter< TDatum, diff --git a/packages/charts-core/src/adapter.ts b/packages/charts-core/src/adapter.ts index 6483e3a..1677b33 100644 --- a/packages/charts-core/src/adapter.ts +++ b/packages/charts-core/src/adapter.ts @@ -4,12 +4,8 @@ import { renderChartSvg } from './svg' import { createSvgChartRenderer } from './svg-surface' import { resolveChartAdapterLayout } from './adapter-shared' import type { ChartAdapter } from './adapter-shared' -import type { - ChartHost, - ChartHostOptions, - ChartRuntime, - ChartValue, -} from './types' +import type { ChartHost, ChartHostOptions } from './dom-types' +import type { ChartRuntime, ChartValue } from './types' export { resolveChartAdapterLayout, diff --git a/packages/charts-core/src/canvas.test.ts b/packages/charts-core/src/canvas.test.ts index 721694f..347b70e 100644 --- a/packages/charts-core/src/canvas.test.ts +++ b/packages/charts-core/src/canvas.test.ts @@ -7,7 +7,8 @@ import { } from './canvas' import { lineY } from './line' import { defineChart } from './scene' -import type { ChartScene, ChartSurfaceRenderOptions, SceneNode } from './types' +import type { ChartSurfaceRenderOptions } from './dom-types' +import type { ChartScene, SceneNode } from './types' interface FakeCanvasContext { operations: string[] diff --git a/packages/charts-core/src/canvas.ts b/packages/charts-core/src/canvas.ts index a386b1d..4b97008 100644 --- a/packages/charts-core/src/canvas.ts +++ b/packages/charts-core/src/canvas.ts @@ -1,16 +1,18 @@ import { mountChartRenderer } from './renderer' import { createChartRuntime } from './runtime' import type { - ChartAnimationOptions, - ChartBounds, - ChartPoint, ChartRenderer, ChartRendererHost, ChartRendererHostOptions, - ChartRuntime, - ChartScene, ChartSurface, ChartSurfaceRenderOptions, +} from './dom-types' +import type { + ChartAnimationOptions, + ChartBounds, + ChartPoint, + ChartRuntime, + ChartScene, ChartValue, SceneNode, SceneStyle, diff --git a/packages/charts-core/src/dom-types.ts b/packages/charts-core/src/dom-types.ts new file mode 100644 index 0000000..0980783 --- /dev/null +++ b/packages/charts-core/src/dom-types.ts @@ -0,0 +1,170 @@ +import type { + ChartAnimationOptions, + ChartDefinition, + ChartPoint, + ChartScene, + ChartSvgRenderer, + ChartTextMeasurer, + ChartTooltipBodyContext, + ChartValue, + RenderChartOptions, + RenderChartSvgOptions, +} from './types' + +export interface ChartSurfaceRenderOptions extends RenderChartOptions { + animation?: ChartAnimationOptions +} + +export interface ChartSurface< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + readonly renderer: ChartRenderer + readonly element: Element + render: ( + scene: ChartScene, + options: ChartSurfaceRenderOptions, + ) => void + clientToScene: ( + scene: ChartScene, + clientX: number, + clientY: number, + ) => { x: number; y: number } | null + paintFocus: ( + point: ChartPoint | null, + points: readonly ChartPoint[], + ) => void + destroy: () => void +} + +export interface ChartRenderer< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + readonly id: string + prerender: ( + scene: ChartScene, + options: RenderChartOptions, + ) => string + mount: ( + container: HTMLElement, + requestRender: (force?: boolean) => void, + ) => ChartSurface +} + +export interface ChartRendererRenderContext< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + container: HTMLElement + scene: ChartScene + surface: ChartSurface +} + +export interface ChartTooltipBodyTarget< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> extends ChartTooltipBodyContext { + element: HTMLElement +} + +export interface ChartRenderContext< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + container: HTMLElement + svg: SVGSVGElement + scene: ChartScene +} + +export interface ChartHostCommonOptions< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> extends RenderChartSvgOptions { + height?: number + aspectRatio?: number + width?: number + initialWidth?: number + onFocusChange?: (point: ChartPoint | null) => void + onFocusGroupChange?: ( + points: readonly ChartPoint[], + ) => void + onSelect?: (point: ChartPoint | null) => void + onRender?: (context: ChartRenderContext) => void + renderSvg?: ChartSvgRenderer< + NoInfer, + NoInfer, + NoInfer + > + measureText?: ChartTextMeasurer +} + +export interface ChartRendererHostCommonOptions< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> extends RenderChartOptions { + renderer: ChartRenderer, NoInfer, NoInfer> + height?: number + aspectRatio?: number + width?: number + initialWidth?: number + onFocusChange?: (point: ChartPoint | null) => void + onFocusGroupChange?: ( + points: readonly ChartPoint[], + ) => void + onSelect?: (point: ChartPoint | null) => void + onRender?: ( + context: ChartRendererRenderContext, + ) => void + onTooltipBodyChange?: ( + target: ChartTooltipBodyTarget< + NoInfer, + NoInfer, + NoInfer + > | null, + ) => void + measureText?: ChartTextMeasurer +} + +export type ChartRendererHostOptions< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> = ChartRendererHostCommonOptions & { + definition: ChartDefinition +} + +export type ChartHostOptions< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> = ChartHostCommonOptions & { + definition: ChartDefinition +} + +export interface ChartHost< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + update: (options: ChartHostOptions) => void + getScene: () => ChartScene + destroy: () => void +} + +export interface ChartRendererHost< + TDatum = unknown, + TXValue extends ChartValue = ChartValue, + TYValue extends ChartValue = ChartValue, +> { + update: (options: ChartRendererHostOptions) => void + getScene: () => ChartScene + destroy: () => void +} diff --git a/packages/charts-core/src/dom.ts b/packages/charts-core/src/dom.ts index 2c982f3..19d7a64 100644 --- a/packages/charts-core/src/dom.ts +++ b/packages/charts-core/src/dom.ts @@ -6,10 +6,8 @@ import type { ChartHost, ChartHostOptions, ChartRendererHostOptions, - ChartRuntime, - ChartSvgRenderer, - ChartValue, -} from './types' +} from './dom-types' +import type { ChartRuntime, ChartSvgRenderer, ChartValue } from './types' export function mountChart< TDatum, diff --git a/packages/charts-core/src/exports.test.ts b/packages/charts-core/src/exports.test.ts index 61c3408..2b7dabb 100644 --- a/packages/charts-core/src/exports.test.ts +++ b/packages/charts-core/src/exports.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from 'vitest' import packageJson from '../package.json' +const typeOnlySpecifiers = new Set(['@tanstack/charts/types']) + describe('public package exports', () => { it('resolves every manifest capability subpath', async () => { const specifiers = Object.keys(packageJson.exports).map((subpath) => @@ -13,6 +15,31 @@ describe('public package exports', () => { ) expect(modules).toHaveLength(specifiers.length) - expect(modules.every((module) => Object.keys(module).length > 0)).toBe(true) + expect( + modules.every( + (module, index) => + typeOnlySpecifiers.has(specifiers[index]!) || + Object.keys(module).length > 0, + ), + ).toBe(true) + }) + + it('keeps the portable barrel aligned with root authoring exports', async () => { + const [root, portable] = await Promise.all([ + import('@tanstack/charts'), + import('@tanstack/charts/portable'), + ]) + const browserOnlyRootValues = new Set([ + 'createChartAdapter', + 'createChartRendererAdapter', + 'mountChart', + 'resolveChartAdapterLayout', + ]) + + expect(Object.keys(portable).sort()).toEqual( + Object.keys(root) + .filter((name) => !browserOnlyRootValues.has(name)) + .sort(), + ) }) }) diff --git a/packages/charts-core/src/index.ts b/packages/charts-core/src/index.ts index 609d1e3..2144176 100644 --- a/packages/charts-core/src/index.ts +++ b/packages/charts-core/src/index.ts @@ -46,6 +46,20 @@ export { tickX, tickY } from './tick' export type { TickXOptions, TickYOptions } from './tick' export { vector } from './vector' export type { VectorAnchor, VectorOptions } from './vector' +export type { + ChartHost, + ChartHostCommonOptions, + ChartHostOptions, + ChartRenderContext, + ChartRenderer, + ChartRendererHost, + ChartRendererHostCommonOptions, + ChartRendererHostOptions, + ChartRendererRenderContext, + ChartSurface, + ChartSurfaceRenderOptions, + ChartTooltipBodyTarget, +} from './dom-types' export type { Channel, ChannelAccessor, @@ -79,9 +93,6 @@ export type { ChartFocusPreset, ChartFocusStrategy, ChartGradientStop, - ChartHost, - ChartHostCommonOptions, - ChartHostOptions, ChartKey, ChartLayoutOptions, ChartMargin, @@ -94,12 +105,6 @@ export type { ChartMarkY, ChartPoint, ChartRuntime, - ChartRenderContext, - ChartRenderer, - ChartRendererHost, - ChartRendererHostCommonOptions, - ChartRendererHostOptions, - ChartRendererRenderContext, ChartScene, ChartScale, ChartScaleFactory, @@ -117,15 +122,12 @@ export type { ChartSpecYValue, ChartSpec, ChartSvgRenderer, - ChartSurface, - ChartSurfaceRenderOptions, ChartTheme, ChartTextMeasurer, ChartTextMeasureOptions, ChartTextMetrics, ChartTick, ChartTooltipBodyContext, - ChartTooltipBodyTarget, ChartTooltipContent, ChartTooltipContentContext, ChartTooltipAnchor, @@ -158,7 +160,7 @@ export type { SceneRule, SceneStyle, StaticChartDefinition, -} from './types' +} from './portable-types' export { areaX } from './area-x' export type { AreaXCurve, AreaXOptions } from './area-x' export { d3AreaXCurve } from './d3-area-x' diff --git a/packages/charts-core/src/portable-types.ts b/packages/charts-core/src/portable-types.ts new file mode 100644 index 0000000..4830c1b --- /dev/null +++ b/packages/charts-core/src/portable-types.ts @@ -0,0 +1,117 @@ +export type { AreaYOptions } from './area' +export type { AreaXCurve, AreaXOptions } from './area-x' +export type { ArrowOptions } from './arrow' +export type { BarXOptions, BarYOptions } from './bar' +export type { DotOptions } from './dot' +export type { FacetAxes, FacetOptions } from './facet' +export type { FrameOptions } from './frame' +export type { HexagonOptions } from './hexagon' +export type { LineYOptions } from './line' +export type { LinkOptions } from './link' +export type { ColorGradientLegendOptions, ColorLegendOptions } from './legend' +export type { CellOptions, RectOptions } from './rect' +export type { RuleXOptions, RuleYOptions } from './rule' +export type { TextAnchor, TextOptions } from './text' +export type { TickXOptions, TickYOptions } from './tick' +export type { VectorAnchor, VectorOptions } from './vector' +export type { + Channel, + ChannelAccessor, + ChannelField, + ChannelOutput, + DynamicChartDefinition, + InitializedMark, + MarkInitializeContext, + MarkRenderContext, + MarkScene, + MaterializedChannel, + ChartAxisOptions, + ChartAxisGuideOptions, + ChartAxisValue, + ChartAnimationOptions, + ChartBounds, + ChartBuildContext, + ChartColorOptions, + ChartColorScaleFactory, + ConfiguredColorScaleLike, + ChartColorScale, + ChartColorScaleContext, + ResolvedColorScaleKind, + InferableColorScaleLike, + ChartColorLegend, + ChartColorLegendContext, + ChartCurve, + ChartDefinition, + ChartDefinitionOptions, + ChartFocusMode, + ChartFocusPreset, + ChartFocusStrategy, + ChartGradientStop, + ChartKey, + ChartLayoutOptions, + ChartMargin, + ChartNumericScale, + ChartNumericScaleOptions, + ChartLinearGradient, + ChartMark, + ChartMarkDatum, + ChartMarkX, + ChartMarkY, + ChartPoint, + ChartRuntime, + ChartScene, + ChartScale, + ChartScaleFactory, + ChartScaleInput, + ChartScaleResolveContext, + ChartScaleResolver, + ConfiguredScaleLike, + InferableScaleLike, + OptionChannelOutput, + ChartSize, + ChartSpatialIndex, + ChartSpatialIndexFactory, + ChartSpecDatum, + ChartSpecXValue, + ChartSpecYValue, + ChartSpec, + ChartSvgRenderer, + ChartTheme, + ChartTextMeasurer, + ChartTextMeasureOptions, + ChartTextMetrics, + ChartTick, + ChartTooltipBodyContext, + ChartTooltipContent, + ChartTooltipContentContext, + ChartTooltipAnchor, + ChartTooltipAnchorContext, + ChartTooltipChannelItem, + ChartTooltipDatumItem, + ChartTooltipDerivedItem, + ChartTooltipItem, + ChartTooltipItemBase, + ChartTooltipOptions, + ChartTooltipPlacement, + ChartTooltipPosition, + ChartTooltipRow, + ChartTooltipSort, + ChartValue, + DynamicChartConfig, + VisualChannel, + WidenChartValue, + RenderChartSvgOptions, + RenderChartOptions, + ResolvedScale, + ResolvedColorScale, + SceneDot, + SceneArea, + SceneGroup, + SceneLabel, + SceneNode, + ScenePolyline, + SceneRect, + SceneRule, + SceneStyle, + StaticChartDefinition, +} from './types' diff --git a/packages/charts-core/src/portable.ts b/packages/charts-core/src/portable.ts new file mode 100644 index 0000000..471acf6 --- /dev/null +++ b/packages/charts-core/src/portable.ts @@ -0,0 +1,28 @@ +export { areaY } from './area' +export { areaX } from './area-x' +export { arrow } from './arrow' +export { barX, barY } from './bar' +export { d3AreaXCurve } from './d3-area-x' +export { d3Curve } from './d3-shape' +export { dot } from './dot' +export { facet, facetChart } from './facet' +export { frame } from './frame' +export { hexagon } from './hexagon' +export { lineY } from './line' +export { link } from './link' +export { colorGradientLegend, colorLegend } from './legend' +export { createMark } from './mark' +export { cell, rect } from './rect' +export { createChartRuntime, isDynamicChartDefinition } from './runtime' +export { + createChartScene, + defaultChartTheme, + defineChart, + findNearestPoint, +} from './scene' +export { renderChartSvg } from './svg' +export { ruleX, ruleY } from './rule' +export { text } from './text' +export { tickX, tickY } from './tick' +export { vector } from './vector' +export type * from './portable-types' diff --git a/packages/charts-core/src/renderer.test.ts b/packages/charts-core/src/renderer.test.ts index 869fff8..e66af17 100644 --- a/packages/charts-core/src/renderer.test.ts +++ b/packages/charts-core/src/renderer.test.ts @@ -5,10 +5,10 @@ import { mountChartRenderer } from './renderer' import { defineChart } from './scene' import type { ChartRenderer, - ChartScene, ChartSurface, ChartSurfaceRenderOptions, -} from './types' +} from './dom-types' +import type { ChartScene } from './types' interface Datum { id: string diff --git a/packages/charts-core/src/renderer.ts b/packages/charts-core/src/renderer.ts index c17956b..b1ed502 100644 --- a/packages/charts-core/src/renderer.ts +++ b/packages/charts-core/src/renderer.ts @@ -2,18 +2,20 @@ import { createChartRuntime } from './runtime' import { createDomTextMeasurer } from './dom-text' import { findNearestPoint } from './scene' import { focusNearestX, focusNearestY, focusX, focusY } from './focus' +import type { + ChartRendererHost, + ChartRendererHostOptions, + ChartSurface, + ChartTooltipBodyTarget, +} from './dom-types' import type { ChartAnimationOptions, ChartFocusMode, ChartFocusStrategy, ChartPoint, - ChartRendererHost, - ChartRendererHostOptions, ChartRuntime, ChartScene, ChartSpatialIndex, - ChartSurface, - ChartTooltipBodyTarget, ChartTooltipContent, ChartTooltipContentContext, ChartTooltipChannelItem, diff --git a/packages/charts-core/src/svg-surface.ts b/packages/charts-core/src/svg-surface.ts index 2772754..fe2abdb 100644 --- a/packages/charts-core/src/svg-surface.ts +++ b/packages/charts-core/src/svg-surface.ts @@ -1,10 +1,9 @@ import { reconcileChartSvg } from './reconcile' import { renderChartSvg } from './svg' +import type { ChartRenderer, ChartSurface } from './dom-types' import type { ChartPoint, - ChartRenderer, ChartScene, - ChartSurface, ChartSvgRenderer, ChartValue, } from './types' diff --git a/packages/charts-core/src/types.ts b/packages/charts-core/src/types.ts index d922c36..8ded05e 100644 --- a/packages/charts-core/src/types.ts +++ b/packages/charts-core/src/types.ts @@ -717,59 +717,6 @@ export interface ChartAnimationOptions { resize?: boolean } -export interface ChartSurfaceRenderOptions extends RenderChartOptions { - animation?: ChartAnimationOptions -} - -export interface ChartSurface< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - readonly renderer: ChartRenderer - readonly element: Element - render: ( - scene: ChartScene, - options: ChartSurfaceRenderOptions, - ) => void - clientToScene: ( - scene: ChartScene, - clientX: number, - clientY: number, - ) => { x: number; y: number } | null - paintFocus: ( - point: ChartPoint | null, - points: readonly ChartPoint[], - ) => void - destroy: () => void -} - -export interface ChartRenderer< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - readonly id: string - prerender: ( - scene: ChartScene, - options: RenderChartOptions, - ) => string - mount: ( - container: HTMLElement, - requestRender: (force?: boolean) => void, - ) => ChartSurface -} - -export interface ChartRendererRenderContext< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - container: HTMLElement - scene: ChartScene - surface: ChartSurface -} - export interface ChartTooltipOptions< TDatum = unknown, TXValue extends ChartValue = ChartValue, @@ -929,14 +876,6 @@ export interface ChartTooltipBodyContext< dismiss: () => void } -export interface ChartTooltipBodyTarget< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> extends ChartTooltipBodyContext { - element: HTMLElement -} - export interface ChartFocusStrategy< TDatum = unknown, TXValue extends ChartValue = ChartValue, @@ -966,16 +905,6 @@ export type ChartFocusMode< TYValue extends ChartValue = ChartValue, > = ChartFocusPreset | ChartFocusStrategy -export interface ChartRenderContext< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - container: HTMLElement - svg: SVGSVGElement - scene: ChartScene -} - export interface ChartSpatialIndex< TDatum = unknown, TXValue extends ChartValue = ChartValue, @@ -996,93 +925,6 @@ export type ChartSpatialIndexFactory< points: readonly ChartPoint[], ) => ChartSpatialIndex -export interface ChartHostCommonOptions< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> extends RenderChartSvgOptions { - height?: number - aspectRatio?: number - width?: number - initialWidth?: number - onFocusChange?: (point: ChartPoint | null) => void - onFocusGroupChange?: ( - points: readonly ChartPoint[], - ) => void - onSelect?: (point: ChartPoint | null) => void - onRender?: (context: ChartRenderContext) => void - renderSvg?: ChartSvgRenderer< - NoInfer, - NoInfer, - NoInfer - > - measureText?: ChartTextMeasurer -} - -export interface ChartRendererHostCommonOptions< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> extends RenderChartOptions { - renderer: ChartRenderer, NoInfer, NoInfer> - height?: number - aspectRatio?: number - width?: number - initialWidth?: number - onFocusChange?: (point: ChartPoint | null) => void - onFocusGroupChange?: ( - points: readonly ChartPoint[], - ) => void - onSelect?: (point: ChartPoint | null) => void - onRender?: ( - context: ChartRendererRenderContext, - ) => void - onTooltipBodyChange?: ( - target: ChartTooltipBodyTarget< - NoInfer, - NoInfer, - NoInfer - > | null, - ) => void - measureText?: ChartTextMeasurer -} - -export type ChartRendererHostOptions< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> = ChartRendererHostCommonOptions & { - definition: ChartDefinition -} - -export type ChartHostOptions< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> = ChartHostCommonOptions & { - definition: ChartDefinition -} - -export interface ChartHost< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - update: (options: ChartHostOptions) => void - getScene: () => ChartScene - destroy: () => void -} - -export interface ChartRendererHost< - TDatum = unknown, - TXValue extends ChartValue = ChartValue, - TYValue extends ChartValue = ChartValue, -> { - update: (options: ChartRendererHostOptions) => void - getScene: () => ChartScene - destroy: () => void -} - export interface ChartRuntime< TDatum = unknown, TXValue extends ChartValue = ChartValue, diff --git a/scripts/check-packed-consumers.mjs b/scripts/check-packed-consumers.mjs index e0bd690..72ebf70 100644 --- a/scripts/check-packed-consumers.mjs +++ b/scripts/check-packed-consumers.mjs @@ -550,6 +550,7 @@ async function verifyEsmRuntime() { const canonicalRoot = pathToFileURL(${JSON.stringify(`${root}${sep}`)}).href const installedRoot = realpathSync('./node_modules') + const typeOnlySpecifiers = new Set(['@tanstack/charts/types']) for (const specifier of ${JSON.stringify(publishedSubpaths)}) { const resolved = import.meta.resolve(specifier) const resolvedPath = realpathSync(fileURLToPath(resolved)) @@ -557,7 +558,9 @@ async function verifyEsmRuntime() { assert.ok(resolvedPath.includes('/dist/'), resolvedPath) assert.equal(resolved.startsWith(canonicalRoot), false, resolved) const module = await import(specifier) - assert.ok(Object.keys(module).length > 0, specifier) + if (!typeOnlySpecifiers.has(specifier)) { + assert.ok(Object.keys(module).length > 0, specifier) + } } const rows = [ @@ -662,13 +665,24 @@ async function verifyDeclarations() { mountChart, renderChartSvg, type ChartFocusStrategy, + type ChartHost, + type ChartHostCommonOptions, + type ChartHostOptions, type ChartSpec, type ChartPoint, + type ChartRenderContext, type ChartRenderer, + type ChartRendererHost, + type ChartRendererHostCommonOptions, + type ChartRendererHostOptions, + type ChartRendererRenderContext, type ChartSpecDatum, type ChartSpecXValue, type ChartSpecYValue, + type ChartSurface, + type ChartSurfaceRenderOptions, type ChartSvgRenderer, + type ChartTooltipBodyTarget, } from '@tanstack/charts' import { canvasChartRenderer } from '@tanstack/charts/canvas' import { lineY } from '@tanstack/charts/line' @@ -1048,6 +1062,72 @@ async function verifyDeclarations() { ) } + await assertPackedDeclarationSources(program, 'DOM declaration contract') + + const portableSource = ` + import { + createChartRuntime, + defineChart, + lineY, + type ChartPoint, + type ChartTooltipOptions, + } from '@tanstack/charts/portable' + import type { + ChartDefinition, + ChartScene, + } from '@tanstack/charts/types' + import { scaleLinear } from 'd3-scale' + + interface Row { + id: string + x: number + y: number + } + + const rows: readonly Row[] = [ + { id: 'a', x: 0, y: 2 }, + { id: 'b', x: 1, y: 5 }, + ] + const definition: ChartDefinition = defineChart({ + marks: [lineY(rows, { x: 'x', y: 'y', key: 'id' })], + x: { scale: scaleLinear() }, + y: { scale: scaleLinear() }, + }) + const runtime = createChartRuntime() + const scene: ChartScene = runtime.render( + definition, + { width: 320, height: 180 }, + ) + const point: ChartPoint | undefined = scene.points[0] + const tooltip: ChartTooltipOptions = { + format: (nextPoint) => nextPoint.datum.id, + } + void [point, tooltip] + ` + const portableContractPath = resolve( + fixtureDirectory, + 'portable-type-contract.ts', + ) + await writeFile(portableContractPath, portableSource) + const portableProgram = ts.createProgram([portableContractPath], { + ...options, + lib: ['lib.es2022.d.ts', 'lib.webworker.d.ts'], + }) + const portableDiagnostics = ts.getPreEmitDiagnostics(portableProgram) + if (portableDiagnostics.length) { + throw new Error( + `Packed portable declaration contract failed:\n${formatDiagnostics( + portableDiagnostics, + )}`, + ) + } + await assertPackedDeclarationSources( + portableProgram, + 'portable declaration contract', + ) +} + +async function assertPackedDeclarationSources(program, label) { const canonicalSourceRoots = packages.map( (packageInfo) => `${resolve(packageInfo.sourceDirectory, 'src')}${sep}`, ) @@ -1059,7 +1139,7 @@ async function verifyDeclarations() { .filter((file) => file.fileName.includes(`${sep}@tanstack${sep}`)) assert.ok( resolvedPackageSources.length > 0, - 'TypeScript did not load packed declarations', + `TypeScript did not load packed declarations for ${label}`, ) for (const file of resolvedPackageSources) { const resolvedFile = await realpath(file.fileName) @@ -1068,15 +1148,15 @@ async function verifyDeclarations() { resolvedFile.startsWith(sourceRoot), ), false, - `TypeScript escaped to workspace source: ${resolvedFile}`, + `${label} escaped to workspace source: ${resolvedFile}`, ) assert.ok( resolvedFile.startsWith(fixtureNodeModules), - `TypeScript resolved outside the fixture: ${resolvedFile}`, + `${label} resolved outside the fixture: ${resolvedFile}`, ) assert.ok( resolvedFile.includes(`${sep}dist${sep}`), - `TypeScript did not resolve a declaration artifact: ${resolvedFile}`, + `${label} did not resolve a declaration artifact: ${resolvedFile}`, ) } } @@ -1099,8 +1179,32 @@ async function verifyProductionBundles() { '/@tanstack/react-charts/dist/Chart.js', '/@tanstack/octane-charts/dist/Chart.js', ], + browserHost: [ + '/@tanstack/charts/dist/index.js', + '/@tanstack/charts/dist/adapter.js', + '/@tanstack/charts/dist/adapter-renderer.js', + '/@tanstack/charts/dist/adapter-shared.js', + '/@tanstack/charts/dist/canvas.js', + '/@tanstack/charts/dist/dom.js', + '/@tanstack/charts/dist/dom-text.js', + '/@tanstack/charts/dist/export.js', + '/@tanstack/charts/dist/reconcile.js', + '/@tanstack/charts/dist/renderer.js', + '/@tanstack/charts/dist/svg-surface.js', + ], } const entries = [ + { + label: 'Portable', + filename: 'portable.ts', + external: [], + rendererBoundary: 'portable', + platform: 'neutral', + conditions: ['import', 'default'], + source: ` + export * from '@tanstack/charts/portable' + `, + }, { label: 'Core', filename: 'core.ts', @@ -1235,14 +1339,14 @@ async function verifyProductionBundles() { outfile, absWorkingDir: fixtureDirectory, bundle: true, - conditions: ['browser', 'import', 'default'], + conditions: entry.conditions ?? ['browser', 'import', 'default'], external: entry.external, format: 'esm', legalComments: 'none', logLevel: 'silent', metafile: true, minify: true, - platform: 'browser', + platform: entry.platform ?? 'browser', target: 'es2022', treeShaking: true, }) @@ -1291,6 +1395,16 @@ function assertRendererBoundary(label, inputs, boundary, modules) { const paths = inputs.map((input) => input.replaceAll('\\', '/')) const canvas = matchingModules(paths, modules.canvas) const svg = matchingModules(paths, modules.svg) + const browserHost = matchingModules(paths, modules.browserHost) + + if (boundary === 'portable') { + assert.deepEqual( + browserHost, + [], + `${label} portable bundle included browser host modules`, + ) + return + } if (boundary === 'neutral') { assert.deepEqual(canvas, [], `${label} neutral bundle included Canvas`)