diff --git a/.changeset/portable-chart-barrels.md b/.changeset/portable-chart-barrels.md deleted file mode 100644 index 5e328d4d..00000000 --- a/.changeset/portable-chart-barrels.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@tanstack/charts': minor ---- - -Add environment-safe `/portable` and `/types` entry points while preserving -the existing browser-oriented root exports. diff --git a/.changeset/universal-chart-barrels.md b/.changeset/universal-chart-barrels.md new file mode 100644 index 00000000..5224fb06 --- /dev/null +++ b/.changeset/universal-chart-barrels.md @@ -0,0 +1,7 @@ +--- +'@tanstack/charts': minor +--- + +Add environment-safe `/universal` and `/types` entry points, including generic +tooltip-extension token contracts for non-DOM hosts, while preserving the +existing browser-oriented root exports. diff --git a/API-FRICTION.md b/API-FRICTION.md index 40b0e569..59eb6fae 100644 --- a/API-FRICTION.md +++ b/API-FRICTION.md @@ -3690,22 +3690,26 @@ Each entry records: 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. + not select the universal 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. Definition inputs retain DOM-free extension - token contracts while typed tooltip and portal lifecycles remain in the DOM - module. Do not conditionally change the root until a native host can test one - coherent platform contract. + `@tanstack/charts/universal` for common authoring/runtime values plus + `@tanstack/charts/types` for universal contracts. The name describes the + supported cross-runtime surface while the browser-first root remains the + normal web entry. DOM surface, renderer, host, and render-context types now + live behind an internal module while retaining their existing root + re-exports. Definition inputs retain DOM-free extension token contracts while + the generic tooltip and portal token interfaces are exported for host-adapter + authors. Typed DOM tooltip and portal lifecycles remain in the DOM module. 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, including tooltip definition inputs, with Web Worker rather - than DOM globals. Type regressions reject swapping tooltip and portal tokens. + declarations, including tooltip definition inputs and direct generic-token + imports, with Web Worker rather than DOM globals. Type regressions reject + swapping tooltip and portal tokens. The packed bundle proof excludes the root, adapters, Canvas, DOM host/text, browser export, reconciliation, renderer, and SVG surface modules. That full - portable barrel measures 53.95 kB minified and 16.60 kB gzip; granular + universal barrel measures 53.95 kB minified and 16.60 kB gzip; granular subpaths remain the bundle-sensitive option. ### F-155 — Optional tooltip code burdened every chart consumer diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cb92106..19692c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,11 @@ require the chart-definition migration above. ### Added +- Added environment-safe `@tanstack/charts/universal` and + `@tanstack/charts/types` entry points for cross-runtime chart definitions, + scene compilation, and extension contracts. The existing `@tanstack/charts` + root remains the browser-oriented entry point. + - Added the optional compact scale package: ```sh diff --git a/benchmarks/comparison/bundle-baseline.json b/benchmarks/comparison/bundle-baseline.json index e475dc7e..a0238116 100644 --- a/benchmarks/comparison/bundle-baseline.json +++ b/benchmarks/comparison/bundle-baseline.json @@ -1,6 +1,6 @@ { "schemaVersion": 3, - "generatedAt": "2026-07-31T19:37:47.858Z", + "generatedAt": "2026-07-31T20:36:32.985Z", "packageVersions": { "tanstack": "0.0.2", "chartjs": "4.5.1", @@ -11,7 +11,7 @@ "sources": { "tanstack": { "kind": "workspace", - "revision": "b1bba0e6e22fab1099f27afab165d07d283244e9" + "revision": "2be6729b5e73afe553cb17b1c054f04dda00a614" }, "chartjs": { "kind": "package", @@ -45,10 +45,10 @@ "bundles": { "tanstack-line-basic": { "minifiedBytes": 63594, - "gzipBytes": 24325, - "brotliBytes": 21616, - "incrementalGzipBytes": 24325, - "incrementalBrotliBytes": 21616 + "gzipBytes": 24326, + "brotliBytes": 21671, + "incrementalGzipBytes": 24326, + "incrementalBrotliBytes": 21671 }, "tanstack-line-interactive": { "minifiedBytes": 68776, @@ -80,17 +80,17 @@ }, "tanstack-bar-advanced": { "minifiedBytes": 71759, - "gzipBytes": 27019, - "brotliBytes": 23947, - "incrementalGzipBytes": 27019, - "incrementalBrotliBytes": 23947 + "gzipBytes": 27018, + "brotliBytes": 23957, + "incrementalGzipBytes": 27018, + "incrementalBrotliBytes": 23957 }, "tanstack-area-basic": { "minifiedBytes": 63472, - "gzipBytes": 24298, - "brotliBytes": 21643, - "incrementalGzipBytes": 24298, - "incrementalBrotliBytes": 21643 + "gzipBytes": 24300, + "brotliBytes": 21642, + "incrementalGzipBytes": 24300, + "incrementalBrotliBytes": 21642 }, "tanstack-area-interactive": { "minifiedBytes": 68658, diff --git a/docs/comparison.md b/docs/comparison.md index f7ac50dd..8606490d 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 `b1bba0e` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `4a8a256` | | [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 `b1bba0e`. +at page render time. The measured TanStack workspace revision is `4a8a256`. ## Capability matrix diff --git a/docs/installation.md b/docs/installation.md index 1f5f6ed5..70981673 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -150,7 +150,7 @@ import { mountChart } from '@tanstack/charts/dom' import { renderChartSvg } from '@tanstack/charts/svg' ``` -Use the portable barrel when definitions and scene compilation must not make +Use the universal barrel when definitions and scene compilation must not make the browser host reachable: ```ts @@ -158,7 +158,7 @@ import { createChartRuntime, defineChart, lineY, -} from '@tanstack/charts/portable' +} from '@tanstack/charts/universal' import type { ChartDefinition } from '@tanstack/charts/types' ``` diff --git a/docs/reference/index.md b/docs/reference/index.md index bd00a6fb..c96178ed 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -57,7 +57,7 @@ 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, browser host, and their public types. The portable entry +scene, SVG renderer, browser host, and their public types. The universal entry excludes browser hosts and adapters. Granular subpaths keep optional capabilities and individual marks independently tree-shakeable. @@ -87,7 +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/universal` | 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` | @@ -99,7 +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/types` | Universal 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 8332e68a..f74ac1fb 100644 --- a/docs/reference/types.md +++ b/docs/reference/types.md @@ -214,12 +214,14 @@ See [DOM host](./dom-host.md) and | `ChartSpatialIndexFactory` | Builds an index from current scene points | | `ChartExtensionInput` | Generic bare-token or `{ use, ...options }` extension input | | `ChartTooltipInput` | Tooltip extension token or configured extension options | +| `ChartTooltipExtensionToken` | Environment-neutral contract implemented by host tooltip extensions | | `ChartTooltipExtension` | Tooltip lifecycle implementation | | `ChartTooltipExtensionContext` | Container, dismissal, and adapter-body bridge given to a tooltip | | `ChartTooltipExtensionInstance` | Tooltip update, paint, hide, containment, and destroy lifecycle | | `ChartTooltipPaintContext` | Focused points, scene, surface, pointer, and pinned state | | `ChartTooltipOptions` | Native tooltip content, ordering, anchoring, and pinning | | `ChartTooltipPortalInput` | Portal extension token or configured transport options | +| `ChartTooltipPortalExtensionToken` | Environment-neutral contract implemented by host portal extensions | | `ChartTooltipPortalExtension` | Tooltip transport lifecycle implementation | | `ChartTooltipPortalExtensionContext` | Container, tooltip element, and reposition callback given to a portal | | `ChartTooltipPortalExtensionInstance` | Portal update, position, hide, and destroy lifecycle | diff --git a/packages/charts-core/docs/comparison.md b/packages/charts-core/docs/comparison.md index f7ac50dd..8606490d 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 `b1bba0e` | +| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `4a8a256` | | [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 `b1bba0e`. +at page render time. The measured TanStack workspace revision is `4a8a256`. ## Capability matrix diff --git a/packages/charts-core/docs/installation.md b/packages/charts-core/docs/installation.md index 1f5f6ed5..70981673 100644 --- a/packages/charts-core/docs/installation.md +++ b/packages/charts-core/docs/installation.md @@ -150,7 +150,7 @@ import { mountChart } from '@tanstack/charts/dom' import { renderChartSvg } from '@tanstack/charts/svg' ``` -Use the portable barrel when definitions and scene compilation must not make +Use the universal barrel when definitions and scene compilation must not make the browser host reachable: ```ts @@ -158,7 +158,7 @@ import { createChartRuntime, defineChart, lineY, -} from '@tanstack/charts/portable' +} from '@tanstack/charts/universal' import type { ChartDefinition } from '@tanstack/charts/types' ``` diff --git a/packages/charts-core/docs/reference/index.md b/packages/charts-core/docs/reference/index.md index bd00a6fb..c96178ed 100644 --- a/packages/charts-core/docs/reference/index.md +++ b/packages/charts-core/docs/reference/index.md @@ -57,7 +57,7 @@ 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, browser host, and their public types. The portable entry +scene, SVG renderer, browser host, and their public types. The universal entry excludes browser hosts and adapters. Granular subpaths keep optional capabilities and individual marks independently tree-shakeable. @@ -87,7 +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/universal` | 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` | @@ -99,7 +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/types` | Universal 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 8332e68a..f74ac1fb 100644 --- a/packages/charts-core/docs/reference/types.md +++ b/packages/charts-core/docs/reference/types.md @@ -214,12 +214,14 @@ See [DOM host](./dom-host.md) and | `ChartSpatialIndexFactory` | Builds an index from current scene points | | `ChartExtensionInput` | Generic bare-token or `{ use, ...options }` extension input | | `ChartTooltipInput` | Tooltip extension token or configured extension options | +| `ChartTooltipExtensionToken` | Environment-neutral contract implemented by host tooltip extensions | | `ChartTooltipExtension` | Tooltip lifecycle implementation | | `ChartTooltipExtensionContext` | Container, dismissal, and adapter-body bridge given to a tooltip | | `ChartTooltipExtensionInstance` | Tooltip update, paint, hide, containment, and destroy lifecycle | | `ChartTooltipPaintContext` | Focused points, scene, surface, pointer, and pinned state | | `ChartTooltipOptions` | Native tooltip content, ordering, anchoring, and pinning | | `ChartTooltipPortalInput` | Portal extension token or configured transport options | +| `ChartTooltipPortalExtensionToken` | Environment-neutral contract implemented by host portal extensions | | `ChartTooltipPortalExtension` | Tooltip transport lifecycle implementation | | `ChartTooltipPortalExtensionContext` | Container, tooltip element, and reposition callback given to a portal | | `ChartTooltipPortalExtensionInstance` | Portal update, position, hide, and destroy lifecycle | diff --git a/packages/charts-core/package.json b/packages/charts-core/package.json index 79ece266..1de50425 100644 --- a/packages/charts-core/package.json +++ b/packages/charts-core/package.json @@ -54,7 +54,7 @@ "./link": "./src/link.ts", "./mark/scale-values": "./src/mark-with-scale-values.ts", "./polar": "./src/polar.ts", - "./portable": "./src/portable.ts", + "./universal": "./src/universal.ts", "./reconcile": "./src/reconcile.ts", "./rect": "./src/rect.ts", "./renderer": "./src/renderer.ts", @@ -68,7 +68,7 @@ "./tick": "./src/tick.ts", "./tooltip": "./src/tooltip.ts", "./tooltip/portal": "./src/tooltip-portal.ts", - "./types": "./src/portable-types.ts", + "./types": "./src/universal-types.ts", "./vector": "./src/vector.ts" }, "publishConfig": { @@ -171,9 +171,9 @@ "types": "./dist/polar.d.ts", "import": "./dist/polar.js" }, - "./portable": { - "types": "./dist/portable.d.ts", - "import": "./dist/portable.js" + "./universal": { + "types": "./dist/universal.d.ts", + "import": "./dist/universal.js" }, "./reconcile": { "types": "./dist/reconcile.d.ts", @@ -228,8 +228,8 @@ "import": "./dist/tooltip-portal.js" }, "./types": { - "types": "./dist/portable-types.d.ts", - "import": "./dist/portable-types.js" + "types": "./dist/universal-types.d.ts", + "import": "./dist/universal-types.js" }, "./vector": { "types": "./dist/vector.d.ts", diff --git a/packages/charts-core/src/exports.test.ts b/packages/charts-core/src/exports.test.ts index 853b73f1..45eb2239 100644 --- a/packages/charts-core/src/exports.test.ts +++ b/packages/charts-core/src/exports.test.ts @@ -24,10 +24,10 @@ describe('public package exports', () => { ).toBe(true) }) - it('keeps the portable barrel aligned with root authoring exports', async () => { - const [root, portable] = await Promise.all([ + it('keeps the universal barrel aligned with root authoring exports', async () => { + const [root, universal] = await Promise.all([ import('@tanstack/charts'), - import('@tanstack/charts/portable'), + import('@tanstack/charts/universal'), ]) const browserOnlyRootValues = new Set([ 'createChartAdapter', @@ -36,7 +36,7 @@ describe('public package exports', () => { 'resolveChartAdapterLayout', ]) - expect(Object.keys(portable).sort()).toEqual( + expect(Object.keys(universal).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 564bbaa9..9cb47338 100644 --- a/packages/charts-core/src/index.ts +++ b/packages/charts-core/src/index.ts @@ -144,12 +144,14 @@ export type { ChartTooltipChannelItem, ChartTooltipDatumItem, ChartTooltipDerivedItem, + ChartTooltipExtensionToken, ChartTooltipInput, ChartTooltipItem, ChartTooltipItemBase, ChartTooltipOptions, ChartTooltipPlacement, ChartTooltipPortalInput, + ChartTooltipPortalExtensionToken, ChartTooltipPortalOptions, ChartTooltipPosition, ChartTooltipRow, @@ -172,7 +174,7 @@ export type { SceneRule, SceneStyle, StaticChartDefinition, -} from './portable-types' +} from './universal-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/universal-types.ts similarity index 97% rename from packages/charts-core/src/portable-types.ts rename to packages/charts-core/src/universal-types.ts index 6b47c814..09b54676 100644 --- a/packages/charts-core/src/portable-types.ts +++ b/packages/charts-core/src/universal-types.ts @@ -90,12 +90,14 @@ export type { ChartTooltipChannelItem, ChartTooltipDatumItem, ChartTooltipDerivedItem, + ChartTooltipExtensionToken, ChartTooltipInput, ChartTooltipItem, ChartTooltipItemBase, ChartTooltipOptions, ChartTooltipPlacement, ChartTooltipPortalInput, + ChartTooltipPortalExtensionToken, ChartTooltipPortalOptions, ChartTooltipPosition, ChartTooltipRow, diff --git a/packages/charts-core/src/portable.ts b/packages/charts-core/src/universal.ts similarity index 95% rename from packages/charts-core/src/portable.ts rename to packages/charts-core/src/universal.ts index 471acf65..44e16a56 100644 --- a/packages/charts-core/src/portable.ts +++ b/packages/charts-core/src/universal.ts @@ -25,4 +25,4 @@ export { ruleX, ruleY } from './rule' export { text } from './text' export { tickX, tickY } from './tick' export { vector } from './vector' -export type * from './portable-types' +export type * from './universal-types' diff --git a/scripts/check-packed-consumers.mjs b/scripts/check-packed-consumers.mjs index 51c18362..e5bef6e3 100644 --- a/scripts/check-packed-consumers.mjs +++ b/scripts/check-packed-consumers.mjs @@ -1111,17 +1111,19 @@ async function verifyDeclarations() { await assertPackedDeclarationSources(program, 'DOM declaration contract') - const portableSource = ` + const universalSource = ` import { createChartRuntime, defineChart, lineY, type ChartPoint, type ChartTooltipOptions, - } from '@tanstack/charts/portable' + } from '@tanstack/charts/universal' import type { ChartDefinition, ChartScene, + ChartTooltipExtensionToken, + ChartTooltipPortalExtensionToken, } from '@tanstack/charts/types' import { scaleLinear } from 'd3-scale' @@ -1149,28 +1151,36 @@ async function verifyDeclarations() { const tooltip: ChartTooltipOptions = { format: (nextPoint) => nextPoint.datum.id, } - void [point, tooltip] + const tooltipToken: ChartTooltipExtensionToken = { + id: 'host-tooltip', + create: () => undefined, + } + const portalToken: ChartTooltipPortalExtensionToken = { + id: 'host-tooltip-portal', + create: () => undefined, + } + void [point, tooltip, tooltipToken, portalToken] ` - const portableContractPath = resolve( + const universalContractPath = resolve( fixtureDirectory, - 'portable-type-contract.ts', + 'universal-type-contract.ts', ) - await writeFile(portableContractPath, portableSource) - const portableProgram = ts.createProgram([portableContractPath], { + await writeFile(universalContractPath, universalSource) + const universalProgram = ts.createProgram([universalContractPath], { ...options, lib: ['lib.es2022.d.ts', 'lib.webworker.d.ts'], }) - const portableDiagnostics = ts.getPreEmitDiagnostics(portableProgram) - if (portableDiagnostics.length) { + const universalDiagnostics = ts.getPreEmitDiagnostics(universalProgram) + if (universalDiagnostics.length) { throw new Error( - `Packed portable declaration contract failed:\n${formatDiagnostics( - portableDiagnostics, + `Packed universal declaration contract failed:\n${formatDiagnostics( + universalDiagnostics, )}`, ) } await assertPackedDeclarationSources( - portableProgram, - 'portable declaration contract', + universalProgram, + 'universal declaration contract', ) } @@ -1263,14 +1273,14 @@ async function verifyProductionBundles() { } const entries = [ { - label: 'Portable', - filename: 'portable.ts', + label: 'Universal', + filename: 'universal.ts', external: [], - rendererBoundary: 'portable', + rendererBoundary: 'universal', platform: 'neutral', conditions: ['import', 'default'], source: ` - export * from '@tanstack/charts/portable' + export * from '@tanstack/charts/universal' `, }, { @@ -1602,11 +1612,11 @@ function assertRendererBoundary(label, inputs, boundary, modules) { const svg = matchingModules(paths, modules.svg) const browserHost = matchingModules(paths, modules.browserHost) - if (boundary === 'portable') { + if (boundary === 'universal') { assert.deepEqual( browserHost, [], - `${label} portable bundle included browser host modules`, + `${label} universal bundle included browser host modules`, ) return }