cortena-ui 1.10.0 → 1.12.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +135 -2
  3. package/dist/components/badge.d.ts +1 -1
  4. package/dist/components/button.d.ts +1 -1
  5. package/dist/components/chart/nivo-theme.js +23 -5
  6. package/dist/components/chart/nivo-theme.js.map +1 -1
  7. package/dist/components/chart/recharts-charts.js +9 -14
  8. package/dist/components/chart/recharts-charts.js.map +1 -1
  9. package/dist/components/chart/tokens.js +21 -1
  10. package/dist/components/chart/tokens.js.map +1 -1
  11. package/dist/components/markdown.js +32 -38
  12. package/dist/components/markdown.js.map +1 -1
  13. package/dist/components/rich-text-editor/extensions.d.ts +43 -0
  14. package/dist/components/rich-text-editor/extensions.js +278 -0
  15. package/dist/components/rich-text-editor/extensions.js.map +1 -0
  16. package/dist/components/rich-text-editor/index.d.ts +6 -0
  17. package/dist/components/rich-text-editor/markdown-bridge.d.ts +81 -0
  18. package/dist/components/rich-text-editor/markdown-bridge.js +618 -0
  19. package/dist/components/rich-text-editor/markdown-bridge.js.map +1 -0
  20. package/dist/components/rich-text-editor/rich-text-editor.d.ts +109 -0
  21. package/dist/components/rich-text-editor/rich-text-editor.js +357 -0
  22. package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -0
  23. package/dist/components/rich-text-editor/toolbar.d.ts +27 -0
  24. package/dist/components/rich-text-editor/toolbar.js +222 -0
  25. package/dist/components/rich-text-editor/toolbar.js.map +1 -0
  26. package/dist/core.d.ts +2 -1
  27. package/dist/index.d.ts +7 -1
  28. package/dist/index.js +5 -1
  29. package/dist/lib/prose.js +85 -0
  30. package/dist/lib/prose.js.map +1 -0
  31. package/dist/rich-text-editor.d.ts +7 -0
  32. package/dist/rich-text-editor.js +6 -0
  33. package/package.json +22 -8
  34. package/src/components/chart/nivo-theme.ts +23 -4
  35. package/src/components/chart/recharts-charts.tsx +13 -10
  36. package/src/components/chart/tokens.ts +19 -0
  37. package/src/components/markdown.tsx +29 -172
  38. package/src/components/rich-text-editor/extensions.ts +328 -0
  39. package/src/components/rich-text-editor/index.ts +19 -0
  40. package/src/components/rich-text-editor/markdown-bridge.ts +791 -0
  41. package/src/components/rich-text-editor/rich-text-editor.tsx +607 -0
  42. package/src/components/rich-text-editor/toolbar.tsx +281 -0
  43. package/src/entries/core.ts +1 -0
  44. package/src/entries/rich-text-editor.ts +53 -0
  45. package/src/index.ts +2 -0
  46. package/src/lib/prose.ts +149 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,71 @@
3
3
  Notable changes per release. Versions before 1.6.0 are recorded in the git log
4
4
  and in `../../CONSUMING.md`; this file starts where the changelog does.
5
5
 
6
+ ## 1.12.0
7
+
8
+ ### Changed
9
+
10
+ - **`RenderProp` is exported** from `cortena-ui/core` and the root barrel, so a
11
+ consumer can name the type its wrappers around `Card`, `Breadcrumb` and
12
+ `ButtonLink` already use (DESIGN-51).
13
+ - **`cortena-design` peer range is bounded** at `>=3.4.0 <4`; a future 4.x no
14
+ longer resolves silently (DESIGN-51).
15
+ - **Chart SSR fallbacks match the tokens.** The no-DOM fallbacks for
16
+ `--ds-radius-md` and `--ds-radius-sm` were 6 and 4; the tokens are 12 and 8,
17
+ so a server-rendered Nivo tooltip drew at half radius until hydration. The
18
+ plot margin now reads `--ds-space-2` through `plotMargin()` instead of a raw
19
+ 8. Pixel-identical in the browser; asserted by test (DESIGN-51).
20
+
21
+ ### Removed
22
+
23
+ - `@nivo/bar`, `@nivo/core`, `@nivo/line`, `@nivo/pie`, `@nivo/scatterplot`
24
+ as dependencies — nothing imported them; those chart types render on the
25
+ Recharts side. `@nivo/core` stays as a transitive of the four Nivo charts
26
+ that remain (DESIGN-51).
27
+
28
+ ## 1.11.0
29
+
30
+ ### Added
31
+
32
+ - **`RichTextEditor`** (DESIGN-54). Rich text editing, which nothing in the
33
+ fleet had. KeyStone needs it for vision documents, PRDs and requirement
34
+ documents; Tasks needs it to be able to replace a `<textarea>` it chose on
35
+ purpose. TipTap on ProseMirror, React bindings, value is Markdown (GFM) in
36
+ and out. Exported from the barrel and from its own subpath entry,
37
+ `cortena-ui/rich-text-editor` — TipTap and ProseMirror are 619 kB and MCP app
38
+ HTML is capped at 512 kB, so `test/bundle-probe.test.mjs` now asserts that no
39
+ other entry carries a byte of the editor engine (`button` from the barrel is
40
+ still 37.7 kB and pulls none of it).
41
+ - **The round trip is the feature.** An untouched document serialises
42
+ byte-identically — same line endings, same trailing whitespace — and
43
+ editing one paragraph changes one paragraph. Tasks uses a plain textarea
44
+ because a rich editor "would reformat text nobody touched and land it as a
45
+ spurious revision"; that constraint is a requirement of this component.
46
+ `markdown-bridge.ts` is mdast-based and source-preserving rather than
47
+ `tiptap-markdown`, which serialises through Turndown and renormalises
48
+ everything it touches. Asserted over an 18-file corpus in
49
+ `test/fixtures/rich-text/`.
50
+ - **Supported subset**: paragraphs, h2–h4, bold, italic, strikethrough,
51
+ inline code, links, ordered/bullet/task lists, blockquote, fenced code, GFM
52
+ tables, horizontal rule, images by URL. Anything outside it — front matter,
53
+ HTML blocks, footnotes, display maths, h1, h5, h6 — is preserved as a raw
54
+ Markdown block, never dropped.
55
+ - **Slots, not features**: `mentionProvider`, `selectionActions`,
56
+ `renderChip`. A mention is stored as `[@Priya](mention:user-42)`, so it is
57
+ still Markdown. Editor JSON and HTML never leave the component.
58
+ - **Controlled `value` with an explicit `version`**: a new revision arriving
59
+ while the author is focused does not clobber them; `onExternalChange` fires
60
+ with an `apply` for a conflict banner.
61
+ - Toolbar on the existing `Toolbar` primitive, keyboard shortcuts, and the
62
+ Markdown input rules (`## `, `- `, `1. `, `> `, backticks).
63
+
64
+ ### Changed
65
+
66
+ - The prose class map moved to `src/lib/prose.ts` and is now shared by
67
+ `Markdown` and `RichTextEditor`. No visual change — the strings are the same
68
+ ones — but the read view and the edit view can no longer drift apart, which
69
+ is what keeps a document from shifting when it enters edit mode.
70
+
6
71
  ## 1.10.0
7
72
 
8
73
  1.9.0 was never released: it was published by mistake and has been deprecated.
package/README.md CHANGED
@@ -36,6 +36,7 @@ consumer's bundler getting tree-shaking right:
36
36
  | `cortena-ui/a2ui` | the A2UI catalogue and renderer | most of the package, by design |
37
37
  | `cortena-ui/sortable-list` | `SortableList`, `SortableHandle`, `arrayMove` | dnd-kit |
38
38
  | `cortena-ui/agent-chat` | `AgentChatPopup`, `AgentChat`, `createAguiAgentChatClient` | the A2UI catalogue, plus `@ag-ui/client` (rxjs, zod 3, uuid, protobuf) |
39
+ | `cortena-ui/rich-text-editor` | `RichTextEditor`, `parseMarkdown`, `serializeMarkdown` | TipTap on ProseMirror, 619 kB — the heaviest entry in the package |
39
40
 
40
41
  Each component has exactly one home, so the barrel re-exports every entry
41
42
  without an ambiguous name. **Adding a component means adding it to its area
@@ -121,8 +122,9 @@ regression; `test/chart-bundle.test.tsx` says which line caused it.
121
122
  `text-[color:var(--ds-foreground)]` and `text-[length:var(--ds-text-caption)]`.
122
123
  Backgrounds, borders and radii are unambiguous and need no hint.
123
124
  - **Tested in a real browser.** Tests assert computed pixels against the
124
- token the browser resolved, in light and dark Chromium instances. A dangling
125
- `var()` paints transparent, which the tests catch and jsdom cannot.
125
+ token the browser resolved. A dangling `var()` paints transparent, which the
126
+ tests catch and jsdom cannot. Most tests pin a theme and run once; see
127
+ "Which tests run in both themes".
126
128
  - **Every component appears in the guide.** `guide/sections.tsx` is the visual
127
129
  check in light, dark and system-dark; add an entry with every variant and
128
130
  size when adding a component.
@@ -431,6 +433,119 @@ names the session (the key is still written, so one window stays on one
431
433
  session), `listSessions: false` for a host with its own session list, and
432
434
  `mintSessionKey` for a host that binds sessions some other way.
433
435
 
436
+ ## RichTextEditor
437
+
438
+ WYSIWYG editing whose value is Markdown (GFM), in and out. KeyStone needs it for
439
+ vision documents, PRDs and requirement documents; nothing else in the fleet has
440
+ rich text at all.
441
+
442
+ ```tsx
443
+ import { RichTextEditor } from "cortena-ui/rich-text-editor";
444
+
445
+ <RichTextEditor
446
+ value={markdown}
447
+ version={revision}
448
+ aria-label="Vision document"
449
+ onChange={setMarkdown}
450
+ onExternalChange={({ apply }) => setConflict(() => apply)}
451
+ mentionProvider={({ trigger, query }) => search(trigger, query)}
452
+ selectionActions={({ text, editor }) => <AskAgent text={text} />}
453
+ />
454
+ ```
455
+
456
+ Editor JSON and HTML never leave the component. There is no `getHTML` escape
457
+ hatch on purpose: the moment a consumer stores ProseMirror JSON, the document
458
+ stops being a file that anything else can read.
459
+
460
+ ### The round trip is the feature
461
+
462
+ Tasks deliberately uses a plain `<textarea>` for its section editor, because a
463
+ rich editor "would reformat text nobody touched and land it as a spurious
464
+ revision". That constraint is a requirement of this component, not a nice-to-have:
465
+
466
+ - **An untouched document serialises byte-identically.** Same bytes, including
467
+ line endings, trailing whitespace and a byte order mark.
468
+ - **Editing one paragraph changes one paragraph.** Every other block is written
469
+ back from the source it was parsed from.
470
+
471
+ `test/rich-text-markdown.test.tsx` asserts both over an 18-file corpus in
472
+ `test/fixtures/rich-text/`, covering nested lists, fences containing fences,
473
+ tables with escaped pipes, links with titles, CRLF and trailing whitespace.
474
+
475
+ ### Why the bridge is written rather than adopted
476
+
477
+ `tiptap-markdown` is the obvious candidate and it fails the one requirement
478
+ above. It serialises by rendering the editor to HTML and running Turndown over
479
+ it, so the output is whatever Turndown's rules produce: bullets become `*`,
480
+ emphasis becomes `_`, tables are re-padded, and a document nobody edited comes
481
+ back different from how it went in. A component that cannot replace the Tasks
482
+ textarea has not delivered anything.
483
+
484
+ A plain mdast serializer does not fix it either. Any AST round trip normalises —
485
+ `mdast-util-to-markdown` has one way to write a bullet — and the moment the
486
+ author's file disagrees, every untouched paragraph in it is rewritten.
487
+
488
+ So `markdown-bridge.ts` is mdast-based **and** source-preserving. Parsing keeps
489
+ the exact source slice of every top-level block; a block whose ProseMirror JSON
490
+ is byte-for-byte what parsing produced is written back from that slice, and only
491
+ a block the user actually changed goes through `mdast-util-to-markdown`. The
492
+ dependencies are `mdast-util-from-markdown`, `mdast-util-to-markdown`,
493
+ `mdast-util-gfm` and `micromark-extension-frontmatter` (all MIT), which is the
494
+ same layer react-markdown already uses — `src/lib/prose.ts` is imported by both
495
+ views and reaches neither react-markdown nor ProseMirror.
496
+
497
+ ### The supported subset, and what happens outside it
498
+
499
+ Paragraphs; headings h2–h4 (h1 is reserved for the record's own title); bold,
500
+ italic, strikethrough, inline code; links; ordered, bullet and task lists;
501
+ blockquote; fenced code; GFM tables; horizontal rule; images by URL.
502
+
503
+ Anything else — front matter, HTML blocks, footnotes, display maths, h1, h5, h6 —
504
+ becomes a **raw block**: a code-like node holding the original Markdown as text,
505
+ editable as raw Markdown and serialised with no escaping. It is never dropped.
506
+ The rule applies at the block boundary, so a paragraph containing a footnote
507
+ reference goes raw whole rather than losing the reference from an otherwise
508
+ editable paragraph.
509
+
510
+ Not in scope: maths rendering, collaborative cursors, comments UI, image upload,
511
+ syntax highlighting in fences.
512
+
513
+ ### Slots, not features
514
+
515
+ `@` and `#` mean nothing to this component. `mentionProvider` is asked for items
516
+ and a chip is inserted; whether that resolves to a person, a task or a page is
517
+ the consumer's business. A mention is stored as `[@Priya](mention:user-42)`, so
518
+ the document is still ordinary Markdown and the editor reads the chip back on
519
+ the next open.
520
+
521
+ What survives in the **read** view is the label, not the link: `Markdown` passes
522
+ URLs through react-markdown's `urlTransform`, which drops any protocol it does
523
+ not know, so `[@Priya](mention:user-42)` renders as the text `@Priya` with no
524
+ href. A host that wants mentions to be clickable in the read view passes its own
525
+ `urlTransform` to `Markdown` and resolves `mention:` itself.
526
+
527
+ `selectionActions` renders whatever the host wants over the current selection,
528
+ and `renderChip` replaces how a mention looks in the menu.
529
+
530
+ ### The controlled value and `version`
531
+
532
+ `value` is the document and `version` is the revision it belongs to. A new
533
+ revision arriving while the author is focused does **not** replace what they are
534
+ writing: `onExternalChange` fires with `{ value, version, apply }` so the
535
+ consumer can show a conflict banner and adopt it on the author's say-so. When
536
+ the author is not in the editor it is adopted silently. `version` defaults to
537
+ `value`, so a consumer that simply echoes `onChange` back still behaves — an
538
+ echo of our own output is never treated as a conflict.
539
+
540
+ ### Styling
541
+
542
+ Prose comes from `src/lib/prose.ts`, the same class strings the `Markdown`
543
+ renderer paints the read view with, handed to TipTap as each node's
544
+ `HTMLAttributes.class`. That is the only reason a document does not shift when
545
+ it enters edit mode, and `test/rich-text-editor.test.tsx` compares the computed
546
+ font size, family, weight and line height of both components on the same source.
547
+ Changing a prose style means changing it in `prose.ts`, for both views at once.
548
+
434
549
  ## Column widths
435
550
 
436
551
  `DataTable` lays the grid out `table-fixed`: every column is drawn at its
@@ -582,6 +697,22 @@ action is shaped like A2UI's client-to-server `action`, and the catalogue is a
582
697
  schema-per-component registry. Swapping the engine later is mechanical. Nothing
583
698
  from `@a2ui/*` is installed, so `vitest.config.ts` has no entry for it.
584
699
 
700
+ ## Which tests run in both themes
701
+
702
+ `vitest.config.ts` has two projects. **`light`** runs every file once in a
703
+ Chromium whose OS colour scheme is light; **`dark`** runs a second time, in a
704
+ dark Chromium, only the files whose name ends `.theme.test.tsx`. Tag a test with
705
+ that suffix when its result depends on the browser's own colour scheme — that
706
+ is, when it asserts a theme-dependent value while no `data-theme` attribute is
707
+ set, the `@media (prefers-color-scheme: dark)` branch of `tokens.css`. Nothing
708
+ else needs the tag: an explicit `data-theme` always beats the media query, so a
709
+ test that calls `setTheme("light")` or `setTheme("dark")` computes the same
710
+ bytes in either browser, and the `for (const theme of ["light", "dark"])` loops
711
+ already paint both branches and assert that they differ, inside one run. If one
712
+ test in a file needs the tag, move that test to a sibling `*.theme.test.tsx`
713
+ rather than paying for the whole file twice — six such files exist today, and
714
+ `pnpm test:dark` runs exactly them.
715
+
585
716
  ## Commands
586
717
 
587
718
  ```bash
@@ -589,6 +720,7 @@ pnpm check # types
589
720
  pnpm lint:design # no literals where a token exists; no dangling var()
590
721
  pnpm build # dist/ via tsdown (ESM + d.ts); also runs on prepack
591
722
  pnpm test # changed-only: the suites of this package your diff affects
723
+ pnpm test:dark # the dark project alone: *.theme.test.tsx in dark Chromium
592
724
  pnpm test:bundle # bundle budgets alone (builds first, then real Vite apps)
593
725
  pnpm bundle:probe # the size table, printed, without asserting anything
594
726
  pnpm guide # three-theme guide on a local Vite server
@@ -611,6 +743,7 @@ src/styles/index.css what consumers import after the tokens: animation
611
743
  utilities and the Base UI data-attribute variants
612
744
  scripts/bundle-probe.mjs builds a consumer app per entry and weighs it
613
745
  test/ browser tests; setup.css is the consumer recipe verbatim
746
+ *.theme.test.tsx also run in a dark Chromium
614
747
  *.test.mjs are the Node-side bundle budgets
615
748
  guide/ Vite app: ?theme=light|dark|system frames, or all three
616
749
  ```
@@ -3,7 +3,7 @@ import { VariantProps } from "class-variance-authority";
3
3
  import * as React from "react";
4
4
  //#region src/components/badge.d.ts
5
5
  declare const badgeVariants: (props?: ({
6
- variant?: "default" | "success" | "warn" | "destructive" | "danger" | "info" | "outline" | "warning" | "neutral" | "secondary" | null | undefined;
6
+ variant?: "default" | "outline" | "success" | "warn" | "destructive" | "danger" | "info" | "warning" | "secondary" | "neutral" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  export interface BadgeProps extends React.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
9
9
  declare function Badge({ className, variant, ...props }: BadgeProps): React.JSX.Element;
@@ -14,7 +14,7 @@ import { Button } from "@base-ui/react/button";
14
14
  * rendered element, which is Base UI's equivalent.
15
15
  */
16
16
  declare const buttonVariants: (props?: ({
17
- variant?: "link" | "destructive" | "outline" | "secondary" | "primary" | "ghost" | "soft" | null | undefined;
17
+ variant?: "link" | "outline" | "destructive" | "primary" | "secondary" | "ghost" | "soft" | null | undefined;
18
18
  size?: "sm" | "md" | "lg" | "icon" | "icon-sm" | null | undefined;
19
19
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
20
20
  export interface ButtonProps extends React.ComponentProps<typeof Button>, VariantProps<typeof buttonVariants> {}
@@ -5,6 +5,24 @@ import { useChart } from "./container.js";
5
5
  import { useMemo } from "react";
6
6
  //#region src/components/chart/nivo-theme.ts
7
7
  /**
8
+ * What these four sizes are when there is no DOM to read them from.
9
+ *
10
+ * `nivoTheme` runs on the server too, where `getComputedStyle` does not exist
11
+ * and every `tokenPx` call comes back undefined. Each number below is the
12
+ * pixel value its token emits at the default scale, so the server markup and
13
+ * the first client paint agree. `chart.test.tsx` asserts them against the
14
+ * live tokens, which is what stops them drifting the way the two radii had:
15
+ * `--ds-radius-md` is 12px and fell back to 6, `--ds-radius-sm` is 8px and
16
+ * fell back to 4, so every server-rendered tooltip drew half-round corners
17
+ * until hydration.
18
+ */
19
+ const NIVO_SIZE_FALLBACK = {
20
+ captionSm: 11,
21
+ caption: 12,
22
+ radiusMd: 12,
23
+ radiusSm: 8
24
+ };
25
+ /**
8
26
  * Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).
9
27
  * Every colour is read from a `--ds-*` custom property and converted to hex,
10
28
  * because Nivo's canvas renderer and its colour derivation cannot read
@@ -15,10 +33,10 @@ import { useMemo } from "react";
15
33
  */
16
34
  function nivoTheme(el) {
17
35
  const fontFamily = readToken("--ds-font-body", el) || "var(--ds-font-body)";
18
- const captionSm = tokenPx("--ds-text-caption-sm", el) ?? 11;
19
- const caption = tokenPx("--ds-text-caption", el) ?? 12;
20
- const radiusMd = tokenPx("--ds-radius-md", el) ?? 6;
21
- const radiusSm = tokenPx("--ds-radius-sm", el) ?? 4;
36
+ const captionSm = tokenPx("--ds-text-caption-sm", el) ?? NIVO_SIZE_FALLBACK.captionSm;
37
+ const caption = tokenPx("--ds-text-caption", el) ?? NIVO_SIZE_FALLBACK.caption;
38
+ const radiusMd = tokenPx("--ds-radius-md", el) ?? NIVO_SIZE_FALLBACK.radiusMd;
39
+ const radiusSm = tokenPx("--ds-radius-sm", el) ?? NIVO_SIZE_FALLBACK.radiusSm;
22
40
  const foreground = resolveColor("--ds-foreground", el);
23
41
  const muted = resolveColor("--ds-muted-foreground", el);
24
42
  const border = resolveColor("--ds-border", el);
@@ -156,6 +174,6 @@ function useNivoTheme() {
156
174
  return useMemo(() => nivoTheme(el), [el, version]);
157
175
  }
158
176
  //#endregion
159
- export { nivoTheme, useNivoTheme };
177
+ export { NIVO_SIZE_FALLBACK, nivoTheme, useNivoTheme };
160
178
 
161
179
  //# sourceMappingURL=nivo-theme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nivo-theme.js","names":[],"sources":["../../../src/components/chart/nivo-theme.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useChart } from \"./container\";\nimport { readToken, resolveColor, tokenPx, useThemeVersion } from \"./tokens\";\n\n/**\n * The Nivo theme object, typed structurally so no `@nivo/*` package is a\n * static import of this module (the Nivo packages are lazy-loaded). It is a\n * subset of `PartialTheme` from `@nivo/theming` and assignable to every\n * chart's `theme` prop.\n */\nexport interface NivoTheme {\n background: string;\n text: { fontFamily: string; fontSize: number; fill: string };\n axis: {\n domain: { line: { stroke: string; strokeWidth: number } };\n ticks: {\n line: { stroke: string; strokeWidth: number };\n text: { fill: string; fontSize: number };\n };\n legend: { text: { fill: string; fontSize: number; fontWeight: number } };\n };\n grid: { line: { stroke: string; strokeWidth: number } };\n crosshair: { line: { stroke: string; strokeWidth: number; strokeOpacity: number; strokeDasharray: string } };\n legends: {\n text: { fill: string; fontSize: number };\n title: { text: { fill: string; fontSize: number } };\n ticks: { line: { stroke: string; strokeWidth: number }; text: { fill: string; fontSize: number } };\n };\n labels: { text: { fill: string; fontSize: number; fontWeight: number } };\n dots: { text: { fill: string; fontSize: number } };\n markers: { lineColor: string; lineStrokeWidth: number; text: { fill: string; fontSize: number } };\n annotations: {\n text: { fill: string; fontSize: number; outlineWidth: number; outlineColor: string };\n link: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n outline: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n symbol: { fill: string; outlineWidth: number; outlineColor: string };\n };\n tooltip: {\n container: {\n background: string;\n color: string;\n fontSize: number;\n fontFamily: string;\n borderRadius: number;\n boxShadow: string;\n border: string;\n padding: string;\n };\n chip: { borderRadius: number };\n };\n}\n\n/**\n * Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).\n * Every colour is read from a `--ds-*` custom property and converted to hex,\n * because Nivo's canvas renderer and its colour derivation cannot read\n * `var()`. Sizes come from the type scale with `--ds-text-caption-sm` (11px)\n * as the floor for ticks and labels.\n *\n * Moved here from cortenaweb's webchat renderer; nothing is hard-coded.\n */\nexport function nivoTheme(el?: Element | null): NivoTheme {\n const fontFamily = readToken(\"--ds-font-body\", el) || \"var(--ds-font-body)\";\n const captionSm = tokenPx(\"--ds-text-caption-sm\", el) ?? 11;\n const caption = tokenPx(\"--ds-text-caption\", el) ?? 12;\n const radiusMd = tokenPx(\"--ds-radius-md\", el) ?? 6;\n const radiusSm = tokenPx(\"--ds-radius-sm\", el) ?? 4;\n const foreground = resolveColor(\"--ds-foreground\", el);\n const muted = resolveColor(\"--ds-muted-foreground\", el);\n const border = resolveColor(\"--ds-border\", el);\n const borderSubtle = resolveColor(\"--ds-border-subtle\", el);\n const borderStrong = resolveColor(\"--ds-border-strong\", el);\n const popover = resolveColor(\"--ds-popover\", el);\n const popoverForeground = resolveColor(\"--ds-popover-foreground\", el);\n const card = resolveColor(\"--ds-card\", el);\n const shadow = readToken(\"--ds-shadow-md\", el) || \"var(--ds-shadow-md)\";\n const space2 = readToken(\"--ds-space-2\", el) || \"var(--ds-space-2)\";\n const space3 = readToken(\"--ds-space-3\", el) || \"var(--ds-space-3)\";\n\n return {\n background: \"transparent\",\n text: { fontFamily, fontSize: captionSm, fill: foreground },\n axis: {\n domain: { line: { stroke: border, strokeWidth: 1 } },\n ticks: {\n line: { stroke: border, strokeWidth: 1 },\n text: { fill: muted, fontSize: captionSm },\n },\n legend: { text: { fill: foreground, fontSize: caption, fontWeight: 500 } },\n },\n grid: { line: { stroke: borderSubtle, strokeWidth: 1 } },\n crosshair: { line: { stroke: borderStrong, strokeWidth: 1, strokeOpacity: 1, strokeDasharray: \"4 4\" } },\n legends: {\n text: { fill: muted, fontSize: captionSm },\n title: { text: { fill: foreground, fontSize: caption } },\n ticks: { line: { stroke: border, strokeWidth: 1 }, text: { fill: muted, fontSize: captionSm } },\n },\n labels: { text: { fill: foreground, fontSize: captionSm, fontWeight: 500 } },\n dots: { text: { fill: foreground, fontSize: captionSm } },\n markers: { lineColor: borderStrong, lineStrokeWidth: 1, text: { fill: muted, fontSize: captionSm } },\n annotations: {\n text: { fill: foreground, fontSize: captionSm, outlineWidth: 2, outlineColor: card },\n link: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n outline: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n symbol: { fill: foreground, outlineWidth: 2, outlineColor: card },\n },\n tooltip: {\n container: {\n background: popover,\n color: popoverForeground,\n fontSize: caption,\n fontFamily,\n borderRadius: radiusMd,\n boxShadow: shadow,\n border: `1px solid ${borderSubtle}`,\n padding: `${space2} ${space3}`,\n },\n chip: { borderRadius: radiusSm },\n },\n };\n}\n\n/**\n * The Nivo theme for the current theme, re-resolved when `data-theme` or the\n * OS scheme flips. Inside a ChartContainer the tokens are read on its element,\n * so a nested theme scope is honoured.\n */\nexport function useNivoTheme(): NivoTheme {\n const version = useThemeVersion();\n const chart = useChart();\n const el = chart?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- version is the cache key for the DOM read\n return useMemo(() => nivoTheme(el), [el, version]);\n}\n"],"mappings":";;;;;;;;;;;;;;;AA+DA,SAAgB,UAAU,IAAgC;CACxD,MAAM,aAAa,UAAU,kBAAkB,EAAE,KAAK;CACtD,MAAM,YAAY,QAAQ,wBAAwB,EAAE,KAAK;CACzD,MAAM,UAAU,QAAQ,qBAAqB,EAAE,KAAK;CACpD,MAAM,WAAW,QAAQ,kBAAkB,EAAE,KAAK;CAClD,MAAM,WAAW,QAAQ,kBAAkB,EAAE,KAAK;CAClD,MAAM,aAAa,aAAa,mBAAmB,EAAE;CACrD,MAAM,QAAQ,aAAa,yBAAyB,EAAE;CACtD,MAAM,SAAS,aAAa,eAAe,EAAE;CAC7C,MAAM,eAAe,aAAa,sBAAsB,EAAE;CAC1D,MAAM,eAAe,aAAa,sBAAsB,EAAE;CAC1D,MAAM,UAAU,aAAa,gBAAgB,EAAE;CAC/C,MAAM,oBAAoB,aAAa,2BAA2B,EAAE;CACpE,MAAM,OAAO,aAAa,aAAa,EAAE;CACzC,MAAM,SAAS,UAAU,kBAAkB,EAAE,KAAK;CAClD,MAAM,SAAS,UAAU,gBAAgB,EAAE,KAAK;CAChD,MAAM,SAAS,UAAU,gBAAgB,EAAE,KAAK;CAEhD,OAAO;EACL,YAAY;EACZ,MAAM;GAAE;GAAY,UAAU;GAAW,MAAM;EAAW;EAC1D,MAAM;GACJ,QAAQ,EAAE,MAAM;IAAE,QAAQ;IAAQ,aAAa;GAAE,EAAE;GACnD,OAAO;IACL,MAAM;KAAE,QAAQ;KAAQ,aAAa;IAAE;IACvC,MAAM;KAAE,MAAM;KAAO,UAAU;IAAU;GAC3C;GACA,QAAQ,EAAE,MAAM;IAAE,MAAM;IAAY,UAAU;IAAS,YAAY;GAAI,EAAE;EAC3E;EACA,MAAM,EAAE,MAAM;GAAE,QAAQ;GAAc,aAAa;EAAE,EAAE;EACvD,WAAW,EAAE,MAAM;GAAE,QAAQ;GAAc,aAAa;GAAG,eAAe;GAAG,iBAAiB;EAAM,EAAE;EACtG,SAAS;GACP,MAAM;IAAE,MAAM;IAAO,UAAU;GAAU;GACzC,OAAO,EAAE,MAAM;IAAE,MAAM;IAAY,UAAU;GAAQ,EAAE;GACvD,OAAO;IAAE,MAAM;KAAE,QAAQ;KAAQ,aAAa;IAAE;IAAG,MAAM;KAAE,MAAM;KAAO,UAAU;IAAU;GAAE;EAChG;EACA,QAAQ,EAAE,MAAM;GAAE,MAAM;GAAY,UAAU;GAAW,YAAY;EAAI,EAAE;EAC3E,MAAM,EAAE,MAAM;GAAE,MAAM;GAAY,UAAU;EAAU,EAAE;EACxD,SAAS;GAAE,WAAW;GAAc,iBAAiB;GAAG,MAAM;IAAE,MAAM;IAAO,UAAU;GAAU;EAAE;EACnG,aAAa;GACX,MAAM;IAAE,MAAM;IAAY,UAAU;IAAW,cAAc;IAAG,cAAc;GAAK;GACnF,MAAM;IAAE,QAAQ;IAAc,aAAa;IAAG,cAAc;IAAG,cAAc;GAAK;GAClF,SAAS;IAAE,QAAQ;IAAc,aAAa;IAAG,cAAc;IAAG,cAAc;GAAK;GACrF,QAAQ;IAAE,MAAM;IAAY,cAAc;IAAG,cAAc;GAAK;EAClE;EACA,SAAS;GACP,WAAW;IACT,YAAY;IACZ,OAAO;IACP,UAAU;IACV;IACA,cAAc;IACd,WAAW;IACX,QAAQ,aAAa;IACrB,SAAS,GAAG,OAAO,GAAG;GACxB;GACA,MAAM,EAAE,cAAc,SAAS;EACjC;CACF;AACF;;;;;;AAOA,SAAgB,eAA0B;CACxC,MAAM,UAAU,gBAAgB;CAEhC,MAAM,KADQ,SACC,CAAC,EAAE,WAAW;CAE7B,OAAO,cAAc,UAAU,EAAE,GAAG,CAAC,IAAI,OAAO,CAAC;AACnD"}
1
+ {"version":3,"file":"nivo-theme.js","names":[],"sources":["../../../src/components/chart/nivo-theme.ts"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useChart } from \"./container\";\nimport { readToken, resolveColor, tokenPx, useThemeVersion } from \"./tokens\";\n\n/**\n * The Nivo theme object, typed structurally so no `@nivo/*` package is a\n * static import of this module (the Nivo packages are lazy-loaded). It is a\n * subset of `PartialTheme` from `@nivo/theming` and assignable to every\n * chart's `theme` prop.\n */\nexport interface NivoTheme {\n background: string;\n text: { fontFamily: string; fontSize: number; fill: string };\n axis: {\n domain: { line: { stroke: string; strokeWidth: number } };\n ticks: {\n line: { stroke: string; strokeWidth: number };\n text: { fill: string; fontSize: number };\n };\n legend: { text: { fill: string; fontSize: number; fontWeight: number } };\n };\n grid: { line: { stroke: string; strokeWidth: number } };\n crosshair: { line: { stroke: string; strokeWidth: number; strokeOpacity: number; strokeDasharray: string } };\n legends: {\n text: { fill: string; fontSize: number };\n title: { text: { fill: string; fontSize: number } };\n ticks: { line: { stroke: string; strokeWidth: number }; text: { fill: string; fontSize: number } };\n };\n labels: { text: { fill: string; fontSize: number; fontWeight: number } };\n dots: { text: { fill: string; fontSize: number } };\n markers: { lineColor: string; lineStrokeWidth: number; text: { fill: string; fontSize: number } };\n annotations: {\n text: { fill: string; fontSize: number; outlineWidth: number; outlineColor: string };\n link: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n outline: { stroke: string; strokeWidth: number; outlineWidth: number; outlineColor: string };\n symbol: { fill: string; outlineWidth: number; outlineColor: string };\n };\n tooltip: {\n container: {\n background: string;\n color: string;\n fontSize: number;\n fontFamily: string;\n borderRadius: number;\n boxShadow: string;\n border: string;\n padding: string;\n };\n chip: { borderRadius: number };\n };\n}\n\n/**\n * What these four sizes are when there is no DOM to read them from.\n *\n * `nivoTheme` runs on the server too, where `getComputedStyle` does not exist\n * and every `tokenPx` call comes back undefined. Each number below is the\n * pixel value its token emits at the default scale, so the server markup and\n * the first client paint agree. `chart.test.tsx` asserts them against the\n * live tokens, which is what stops them drifting the way the two radii had:\n * `--ds-radius-md` is 12px and fell back to 6, `--ds-radius-sm` is 8px and\n * fell back to 4, so every server-rendered tooltip drew half-round corners\n * until hydration.\n */\nexport const NIVO_SIZE_FALLBACK = {\n captionSm: 11,\n caption: 12,\n radiusMd: 12,\n radiusSm: 8,\n} as const;\n\n/**\n * Build the Nivo theme from the tokens, resolved on `el` (default `<html>`).\n * Every colour is read from a `--ds-*` custom property and converted to hex,\n * because Nivo's canvas renderer and its colour derivation cannot read\n * `var()`. Sizes come from the type scale with `--ds-text-caption-sm` (11px)\n * as the floor for ticks and labels.\n *\n * Moved here from cortenaweb's webchat renderer; nothing is hard-coded.\n */\nexport function nivoTheme(el?: Element | null): NivoTheme {\n const fontFamily = readToken(\"--ds-font-body\", el) || \"var(--ds-font-body)\";\n const captionSm = tokenPx(\"--ds-text-caption-sm\", el) ?? NIVO_SIZE_FALLBACK.captionSm;\n const caption = tokenPx(\"--ds-text-caption\", el) ?? NIVO_SIZE_FALLBACK.caption;\n const radiusMd = tokenPx(\"--ds-radius-md\", el) ?? NIVO_SIZE_FALLBACK.radiusMd;\n const radiusSm = tokenPx(\"--ds-radius-sm\", el) ?? NIVO_SIZE_FALLBACK.radiusSm;\n const foreground = resolveColor(\"--ds-foreground\", el);\n const muted = resolveColor(\"--ds-muted-foreground\", el);\n const border = resolveColor(\"--ds-border\", el);\n const borderSubtle = resolveColor(\"--ds-border-subtle\", el);\n const borderStrong = resolveColor(\"--ds-border-strong\", el);\n const popover = resolveColor(\"--ds-popover\", el);\n const popoverForeground = resolveColor(\"--ds-popover-foreground\", el);\n const card = resolveColor(\"--ds-card\", el);\n const shadow = readToken(\"--ds-shadow-md\", el) || \"var(--ds-shadow-md)\";\n const space2 = readToken(\"--ds-space-2\", el) || \"var(--ds-space-2)\";\n const space3 = readToken(\"--ds-space-3\", el) || \"var(--ds-space-3)\";\n\n return {\n background: \"transparent\",\n text: { fontFamily, fontSize: captionSm, fill: foreground },\n axis: {\n domain: { line: { stroke: border, strokeWidth: 1 } },\n ticks: {\n line: { stroke: border, strokeWidth: 1 },\n text: { fill: muted, fontSize: captionSm },\n },\n legend: { text: { fill: foreground, fontSize: caption, fontWeight: 500 } },\n },\n grid: { line: { stroke: borderSubtle, strokeWidth: 1 } },\n crosshair: { line: { stroke: borderStrong, strokeWidth: 1, strokeOpacity: 1, strokeDasharray: \"4 4\" } },\n legends: {\n text: { fill: muted, fontSize: captionSm },\n title: { text: { fill: foreground, fontSize: caption } },\n ticks: { line: { stroke: border, strokeWidth: 1 }, text: { fill: muted, fontSize: captionSm } },\n },\n labels: { text: { fill: foreground, fontSize: captionSm, fontWeight: 500 } },\n dots: { text: { fill: foreground, fontSize: captionSm } },\n markers: { lineColor: borderStrong, lineStrokeWidth: 1, text: { fill: muted, fontSize: captionSm } },\n annotations: {\n text: { fill: foreground, fontSize: captionSm, outlineWidth: 2, outlineColor: card },\n link: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n outline: { stroke: borderStrong, strokeWidth: 1, outlineWidth: 2, outlineColor: card },\n symbol: { fill: foreground, outlineWidth: 2, outlineColor: card },\n },\n tooltip: {\n container: {\n background: popover,\n color: popoverForeground,\n fontSize: caption,\n fontFamily,\n borderRadius: radiusMd,\n boxShadow: shadow,\n border: `1px solid ${borderSubtle}`,\n padding: `${space2} ${space3}`,\n },\n chip: { borderRadius: radiusSm },\n },\n };\n}\n\n/**\n * The Nivo theme for the current theme, re-resolved when `data-theme` or the\n * OS scheme flips. Inside a ChartContainer the tokens are read on its element,\n * so a nested theme scope is honoured.\n */\nexport function useNivoTheme(): NivoTheme {\n const version = useThemeVersion();\n const chart = useChart();\n const el = chart?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- version is the cache key for the DOM read\n return useMemo(() => nivoTheme(el), [el, version]);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  "use client";
3
- import { contrastLabel, useThemeVersion } from "./tokens.js";
3
+ import { contrastLabel, plotMargin, useThemeVersion } from "./tokens.js";
4
4
  import { seriesColor, useChart } from "./container.js";
5
5
  import { toRows, toSlices } from "./data.js";
6
6
  import { ChartLegend, ChartTooltip } from "./recharts-parts.js";
@@ -8,12 +8,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import * as React from "react";
9
9
  import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Funnel, FunnelChart, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Scatter, ScatterChart, XAxis, YAxis } from "recharts";
10
10
  //#region src/components/chart/recharts-charts.tsx
11
- const MARGIN = {
12
- top: 8,
13
- right: 8,
14
- bottom: 0,
15
- left: 0
16
- };
17
11
  /**
18
12
  * Where an arc label sits, as a fraction of the way from the inner radius to
19
13
  * the outer one. Just inside the middle of the band: the arc is widest towards
@@ -69,6 +63,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
69
63
  const themeVersion = useThemeVersion();
70
64
  const element = useChart()?.element ?? null;
71
65
  const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);
66
+ const margin = React.useMemo(() => plotMargin(element), [element, themeVersion]);
72
67
  const tip = tooltip ? /* @__PURE__ */ jsx(ChartTooltip, { formatter: valueFormatter ? (value) => valueFormatter(Number(value)) : void 0 }) : null;
73
68
  const leg = legend ? /* @__PURE__ */ jsx(ChartLegend, {}) : null;
74
69
  switch (type) {
@@ -76,7 +71,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
76
71
  const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
77
72
  return /* @__PURE__ */ jsxs(BarChart, {
78
73
  data: rows,
79
- margin: MARGIN,
74
+ margin,
80
75
  children: [
81
76
  grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
82
77
  /* @__PURE__ */ jsx(XAxis, {
@@ -112,7 +107,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
112
107
  const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
113
108
  return /* @__PURE__ */ jsxs(LineChart, {
114
109
  data: rows,
115
- margin: MARGIN,
110
+ margin,
116
111
  children: [
117
112
  grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
118
113
  /* @__PURE__ */ jsx(XAxis, {
@@ -144,7 +139,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
144
139
  const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
145
140
  return /* @__PURE__ */ jsxs(AreaChart, {
146
141
  data: rows,
147
- margin: MARGIN,
142
+ margin,
148
143
  children: [
149
144
  grid ? /* @__PURE__ */ jsx(CartesianGrid, { vertical: false }) : null,
150
145
  /* @__PURE__ */ jsx(XAxis, {
@@ -178,7 +173,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
178
173
  const slices = toSlices(data);
179
174
  const fillOf = (i) => seriesColor(config, slices[i]?.id ?? String(i), i);
180
175
  return /* @__PURE__ */ jsxs(PieChart, {
181
- margin: MARGIN,
176
+ margin,
182
177
  children: [
183
178
  tip,
184
179
  leg,
@@ -200,7 +195,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
200
195
  }
201
196
  case "scatter":
202
197
  case "scatterplot": return /* @__PURE__ */ jsxs(ScatterChart, {
203
- margin: MARGIN,
198
+ margin,
204
199
  children: [
205
200
  grid ? /* @__PURE__ */ jsx(CartesianGrid, {}) : null,
206
201
  /* @__PURE__ */ jsx(XAxis, {
@@ -232,7 +227,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
232
227
  const { rows, indexBy: index, keys: series } = toRows(data, indexBy, keys);
233
228
  return /* @__PURE__ */ jsxs(RadarChart, {
234
229
  data: rows,
235
- margin: MARGIN,
230
+ margin,
236
231
  children: [
237
232
  grid ? /* @__PURE__ */ jsx(PolarGrid, {}) : null,
238
233
  /* @__PURE__ */ jsx(PolarAngleAxis, { dataKey: index }),
@@ -256,7 +251,7 @@ function RechartsChart({ type, data, config, indexBy, keys, stacked, legend, gri
256
251
  case "funnel": {
257
252
  const slices = toSlices(data);
258
253
  return /* @__PURE__ */ jsxs(FunnelChart, {
259
- margin: MARGIN,
254
+ margin,
260
255
  children: [
261
256
  tip,
262
257
  leg,
@@ -1 +1 @@
1
- {"version":3,"file":"recharts-charts.js","names":["series"],"sources":["../../../src/components/chart/recharts-charts.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Funnel,\n FunnelChart,\n Line,\n LineChart,\n Pie,\n PieChart,\n PolarAngleAxis,\n PolarGrid,\n PolarRadiusAxis,\n Radar,\n RadarChart,\n Scatter,\n ScatterChart,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { seriesColor, useChart } from \"./container\";\nimport { ChartLegend, ChartTooltip } from \"./recharts-parts\";\nimport { toRows, toSlices } from \"./data\";\nimport { contrastLabel, useThemeVersion } from \"./tokens\";\nimport type { ChartConfig, ChartLabels, ChartRow, ChartSeries, ChartSlice, ChartType } from \"./types\";\n\nexport type RechartsType = Extract<\n ChartType,\n \"bar\" | \"line\" | \"area\" | \"pie\" | \"donut\" | \"scatter\" | \"scatterplot\" | \"radar\" | \"funnel\"\n>;\n\nexport interface RechartsRenderProps {\n type: RechartsType;\n data: unknown;\n config: ChartConfig;\n indexBy?: string;\n keys?: string[];\n stacked?: boolean;\n legend: boolean;\n grid: boolean;\n tooltip: boolean;\n /** Per-arc labels for pie and donut. Ignored by every other type. */\n labels: ChartLabels;\n valueFormatter?: (value: number) => string;\n /**\n * Entry animation. Off under `testMode`: Recharts draws pie labels only\n * once the animation has finished, so a test that asserts on them would\n * otherwise be racing the clock.\n */\n animate?: boolean;\n}\n\nconst MARGIN = { top: 8, right: 8, bottom: 0, left: 0 };\n\n/**\n * Where an arc label sits, as a fraction of the way from the inner radius to\n * the outer one. Just inside the middle of the band: the arc is widest towards\n * its outer edge, so a label pushed slightly out has more room than a centred\n * one, without ever touching the rim.\n */\nconst ARC_LABEL_RADIUS = 0.58;\n\n/**\n * Arcs smaller than this share of the whole are left unlabelled.\n *\n * Below roughly a twentieth the arc is narrower than the text it would carry,\n * so the label either overflows into its neighbours or has to be dragged\n * outside on a leader line. Leader lines on a small chart are worse than no\n * label — they collide with each other and with the legend, and the reader\n * still has to trace them. The tooltip covers the slices that fall out.\n */\nconst ARC_LABEL_MIN_FRACTION = 0.05;\n\n/**\n * What Recharts hands a `<Pie label={…}>` renderer. Every geometry field is\n * optional on its side — the same callback types the label of a chart that has\n * not measured itself yet — so each is checked before it is used rather than\n * asserted away.\n */\ninterface ArcLabelProps {\n cx?: number;\n cy?: number;\n midAngle?: number;\n innerRadius?: number;\n outerRadius?: number;\n percent?: number;\n value?: number;\n index?: number;\n}\n\n/**\n * A per-arc label, drawn inside the arc it belongs to.\n *\n * Inside rather than outside so there is nothing to collide: each label is\n * bounded by its own sector, and a sector too thin to hold one simply does not\n * get one. The text colour comes from `contrastLabel`, the same measured\n * ink-or-white choice the Nivo charts make, so a label stays legible on a pale\n * series colour and on a dark one, in either theme.\n */\nfunction arcLabel(\n labels: Exclude<ChartLabels, \"none\">,\n colourOn: (fill: string) => string,\n fillOf: (index: number) => string,\n valueFormatter?: (value: number) => string,\n) {\n return function ArcLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, index }: ArcLabelProps) {\n if (cx == null || cy == null || midAngle == null || innerRadius == null || outerRadius == null) return null;\n if (!(percent != null && percent >= ARC_LABEL_MIN_FRACTION)) return null;\n const radius = innerRadius + (outerRadius - innerRadius) * ARC_LABEL_RADIUS;\n // Recharts measures angles anticlockwise from the positive x-axis, SVG's y\n // grows downward, so the sine is negated to land on the drawn arc.\n const radians = (-midAngle * Math.PI) / 180;\n const text =\n labels === \"percent\"\n ? `${Math.round(percent * 100)}%`\n : (valueFormatter?.(Number(value)) ?? String(value ?? \"\"));\n return (\n <text\n data-slot=\"chart-arc-label\"\n x={cx + radius * Math.cos(radians)}\n y={cy + radius * Math.sin(radians)}\n fill={colourOn(fillOf(index ?? 0))}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"pointer-events-none text-[length:var(--ds-text-caption)] font-medium tabular-nums\"\n >\n {text}\n </text>\n );\n };\n}\n\n/**\n * The Recharts side of `Chart`. Returns a chart element for the\n * ResponsiveContainer inside ChartContainer; colours come from `seriesColor`\n * (config colour or `var(--ds-viz-N)` by series index) and every other visual\n * value from the container's token styling.\n */\nexport function RechartsChart({\n type,\n data,\n config,\n indexBy,\n keys,\n stacked,\n legend,\n grid,\n tooltip,\n labels,\n valueFormatter,\n animate = true,\n}: RechartsRenderProps): React.ReactElement {\n // Arc labels sit on top of a series colour, so their ink depends on which\n // colour and on the theme. `useThemeVersion` is the cache key for that DOM\n // read; `element` scopes it to a nested theme rather than the document.\n const themeVersion = useThemeVersion();\n const element = useChart()?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);\n\n const tip = tooltip ? (\n <ChartTooltip\n formatter={valueFormatter ? (value: unknown) => valueFormatter(Number(value)) : undefined}\n />\n ) : null;\n const leg = legend ? <ChartLegend /> : null;\n\n switch (type) {\n case \"bar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <BarChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Bar\n key={key}\n dataKey={key}\n name={key}\n fill={seriesColor(config, key, i)}\n stackId={stacked ? \"stack\" : undefined}\n radius={stacked ? 0 : [2, 2, 0, 0]}\n maxBarSize={48}\n />\n ))}\n </BarChart>\n );\n }\n\n case \"line\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <LineChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Line\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4 }}\n />\n ))}\n </LineChart>\n );\n }\n\n case \"area\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <AreaChart data={rows} margin={MARGIN}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Area\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n />\n ))}\n </AreaChart>\n );\n }\n\n case \"pie\":\n case \"donut\": {\n const slices = toSlices(data as ChartSlice[]);\n const fillOf = (i: number) => seriesColor(config, slices[i]?.id ?? String(i), i);\n return (\n <PieChart margin={MARGIN}>\n {tip}\n {leg}\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={type === \"donut\" ? \"55%\" : 0}\n outerRadius=\"80%\"\n paddingAngle={type === \"donut\" ? 2 : 0}\n strokeWidth={1}\n isAnimationActive={animate}\n label={labels === \"none\" ? undefined : arcLabel(labels, colourOn, fillOf, valueFormatter)}\n // The labels are inside their arcs, so there is nothing to lead to.\n labelLine={false}\n >\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={fillOf(i)} />\n ))}\n </Pie>\n </PieChart>\n );\n }\n\n case \"scatter\":\n case \"scatterplot\": {\n const series = data as ChartSeries[];\n return (\n <ScatterChart margin={MARGIN}>\n {grid ? <CartesianGrid /> : null}\n <XAxis dataKey=\"x\" type=\"number\" name=\"x\" tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis dataKey=\"y\" type=\"number\" name=\"y\" tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((s, i) => (\n <Scatter key={String(s.id)} name={String(s.id)} data={s.data} fill={seriesColor(config, String(s.id), i)} />\n ))}\n </ScatterChart>\n );\n }\n\n case \"radar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <RadarChart data={rows} margin={MARGIN}>\n {grid ? <PolarGrid /> : null}\n <PolarAngleAxis dataKey={index} />\n <PolarRadiusAxis tick={false} axisLine={false} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Radar\n key={key}\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n />\n ))}\n </RadarChart>\n );\n }\n\n case \"funnel\": {\n const slices = toSlices(data as ChartSlice[]);\n return (\n <FunnelChart margin={MARGIN}>\n {tip}\n {leg}\n <Funnel data={slices} dataKey=\"value\" nameKey=\"name\">\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={seriesColor(config, slice.id, i)} />\n ))}\n </Funnel>\n </FunnelChart>\n );\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"recharts-charts.js","names":["series"],"sources":["../../../src/components/chart/recharts-charts.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n Area,\n AreaChart,\n Bar,\n BarChart,\n CartesianGrid,\n Cell,\n Funnel,\n FunnelChart,\n Line,\n LineChart,\n Pie,\n PieChart,\n PolarAngleAxis,\n PolarGrid,\n PolarRadiusAxis,\n Radar,\n RadarChart,\n Scatter,\n ScatterChart,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { seriesColor, useChart } from \"./container\";\nimport { ChartLegend, ChartTooltip } from \"./recharts-parts\";\nimport { toRows, toSlices } from \"./data\";\nimport { contrastLabel, plotMargin, useThemeVersion } from \"./tokens\";\nimport type { ChartConfig, ChartLabels, ChartRow, ChartSeries, ChartSlice, ChartType } from \"./types\";\n\nexport type RechartsType = Extract<\n ChartType,\n \"bar\" | \"line\" | \"area\" | \"pie\" | \"donut\" | \"scatter\" | \"scatterplot\" | \"radar\" | \"funnel\"\n>;\n\nexport interface RechartsRenderProps {\n type: RechartsType;\n data: unknown;\n config: ChartConfig;\n indexBy?: string;\n keys?: string[];\n stacked?: boolean;\n legend: boolean;\n grid: boolean;\n tooltip: boolean;\n /** Per-arc labels for pie and donut. Ignored by every other type. */\n labels: ChartLabels;\n valueFormatter?: (value: number) => string;\n /**\n * Entry animation. Off under `testMode`: Recharts draws pie labels only\n * once the animation has finished, so a test that asserts on them would\n * otherwise be racing the clock.\n */\n animate?: boolean;\n}\n\n/**\n * Where an arc label sits, as a fraction of the way from the inner radius to\n * the outer one. Just inside the middle of the band: the arc is widest towards\n * its outer edge, so a label pushed slightly out has more room than a centred\n * one, without ever touching the rim.\n */\nconst ARC_LABEL_RADIUS = 0.58;\n\n/**\n * Arcs smaller than this share of the whole are left unlabelled.\n *\n * Below roughly a twentieth the arc is narrower than the text it would carry,\n * so the label either overflows into its neighbours or has to be dragged\n * outside on a leader line. Leader lines on a small chart are worse than no\n * label — they collide with each other and with the legend, and the reader\n * still has to trace them. The tooltip covers the slices that fall out.\n */\nconst ARC_LABEL_MIN_FRACTION = 0.05;\n\n/**\n * What Recharts hands a `<Pie label={…}>` renderer. Every geometry field is\n * optional on its side — the same callback types the label of a chart that has\n * not measured itself yet — so each is checked before it is used rather than\n * asserted away.\n */\ninterface ArcLabelProps {\n cx?: number;\n cy?: number;\n midAngle?: number;\n innerRadius?: number;\n outerRadius?: number;\n percent?: number;\n value?: number;\n index?: number;\n}\n\n/**\n * A per-arc label, drawn inside the arc it belongs to.\n *\n * Inside rather than outside so there is nothing to collide: each label is\n * bounded by its own sector, and a sector too thin to hold one simply does not\n * get one. The text colour comes from `contrastLabel`, the same measured\n * ink-or-white choice the Nivo charts make, so a label stays legible on a pale\n * series colour and on a dark one, in either theme.\n */\nfunction arcLabel(\n labels: Exclude<ChartLabels, \"none\">,\n colourOn: (fill: string) => string,\n fillOf: (index: number) => string,\n valueFormatter?: (value: number) => string,\n) {\n return function ArcLabel({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, index }: ArcLabelProps) {\n if (cx == null || cy == null || midAngle == null || innerRadius == null || outerRadius == null) return null;\n if (!(percent != null && percent >= ARC_LABEL_MIN_FRACTION)) return null;\n const radius = innerRadius + (outerRadius - innerRadius) * ARC_LABEL_RADIUS;\n // Recharts measures angles anticlockwise from the positive x-axis, SVG's y\n // grows downward, so the sine is negated to land on the drawn arc.\n const radians = (-midAngle * Math.PI) / 180;\n const text =\n labels === \"percent\"\n ? `${Math.round(percent * 100)}%`\n : (valueFormatter?.(Number(value)) ?? String(value ?? \"\"));\n return (\n <text\n data-slot=\"chart-arc-label\"\n x={cx + radius * Math.cos(radians)}\n y={cy + radius * Math.sin(radians)}\n fill={colourOn(fillOf(index ?? 0))}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className=\"pointer-events-none text-[length:var(--ds-text-caption)] font-medium tabular-nums\"\n >\n {text}\n </text>\n );\n };\n}\n\n/**\n * The Recharts side of `Chart`. Returns a chart element for the\n * ResponsiveContainer inside ChartContainer; colours come from `seriesColor`\n * (config colour or `var(--ds-viz-N)` by series index) and every other visual\n * value from the container's token styling.\n */\nexport function RechartsChart({\n type,\n data,\n config,\n indexBy,\n keys,\n stacked,\n legend,\n grid,\n tooltip,\n labels,\n valueFormatter,\n animate = true,\n}: RechartsRenderProps): React.ReactElement {\n // Arc labels sit on top of a series colour, so their ink depends on which\n // colour and on the theme. `useThemeVersion` is the cache key for that DOM\n // read; `element` scopes it to a nested theme rather than the document.\n const themeVersion = useThemeVersion();\n const element = useChart()?.element ?? null;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const colourOn = React.useMemo(() => contrastLabel(element), [element, themeVersion]);\n // The plot margin is `--ds-space-2`, read from the tokens like every colour\n // here rather than frozen as a number, and on the same cache key so a theme\n // or scale change re-reads it.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- themeVersion is the cache key for the DOM read\n const margin = React.useMemo(() => plotMargin(element), [element, themeVersion]);\n\n const tip = tooltip ? (\n <ChartTooltip\n formatter={valueFormatter ? (value: unknown) => valueFormatter(Number(value)) : undefined}\n />\n ) : null;\n const leg = legend ? <ChartLegend /> : null;\n\n switch (type) {\n case \"bar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <BarChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Bar\n key={key}\n dataKey={key}\n name={key}\n fill={seriesColor(config, key, i)}\n stackId={stacked ? \"stack\" : undefined}\n radius={stacked ? 0 : [2, 2, 0, 0]}\n maxBarSize={48}\n />\n ))}\n </BarChart>\n );\n }\n\n case \"line\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <LineChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Line\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 4 }}\n />\n ))}\n </LineChart>\n );\n }\n\n case \"area\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <AreaChart data={rows} margin={margin}>\n {grid ? <CartesianGrid vertical={false} /> : null}\n <XAxis dataKey={index} tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Area\n key={key}\n type=\"monotone\"\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n stackId={stacked ? \"stack\" : undefined}\n />\n ))}\n </AreaChart>\n );\n }\n\n case \"pie\":\n case \"donut\": {\n const slices = toSlices(data as ChartSlice[]);\n const fillOf = (i: number) => seriesColor(config, slices[i]?.id ?? String(i), i);\n return (\n <PieChart margin={margin}>\n {tip}\n {leg}\n <Pie\n data={slices}\n dataKey=\"value\"\n nameKey=\"name\"\n innerRadius={type === \"donut\" ? \"55%\" : 0}\n outerRadius=\"80%\"\n paddingAngle={type === \"donut\" ? 2 : 0}\n strokeWidth={1}\n isAnimationActive={animate}\n label={labels === \"none\" ? undefined : arcLabel(labels, colourOn, fillOf, valueFormatter)}\n // The labels are inside their arcs, so there is nothing to lead to.\n labelLine={false}\n >\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={fillOf(i)} />\n ))}\n </Pie>\n </PieChart>\n );\n }\n\n case \"scatter\":\n case \"scatterplot\": {\n const series = data as ChartSeries[];\n return (\n <ScatterChart margin={margin}>\n {grid ? <CartesianGrid /> : null}\n <XAxis dataKey=\"x\" type=\"number\" name=\"x\" tickLine={false} axisLine={false} tickMargin={8} />\n <YAxis dataKey=\"y\" type=\"number\" name=\"y\" tickLine={false} axisLine={false} tickFormatter={valueFormatter} />\n {tip}\n {leg}\n {series.map((s, i) => (\n <Scatter key={String(s.id)} name={String(s.id)} data={s.data} fill={seriesColor(config, String(s.id), i)} />\n ))}\n </ScatterChart>\n );\n }\n\n case \"radar\": {\n const { rows, indexBy: index, keys: series } = toRows(data as ChartRow[] | ChartSeries[], indexBy, keys);\n return (\n <RadarChart data={rows} margin={margin}>\n {grid ? <PolarGrid /> : null}\n <PolarAngleAxis dataKey={index} />\n <PolarRadiusAxis tick={false} axisLine={false} />\n {tip}\n {leg}\n {series.map((key, i) => (\n <Radar\n key={key}\n dataKey={key}\n name={key}\n stroke={seriesColor(config, key, i)}\n fill={seriesColor(config, key, i)}\n fillOpacity={0.2}\n strokeWidth={2}\n />\n ))}\n </RadarChart>\n );\n }\n\n case \"funnel\": {\n const slices = toSlices(data as ChartSlice[]);\n return (\n <FunnelChart margin={margin}>\n {tip}\n {leg}\n <Funnel data={slices} dataKey=\"value\" nameKey=\"name\">\n {slices.map((slice, i) => (\n <Cell key={slice.id} fill={seriesColor(config, slice.id, i)} />\n ))}\n </Funnel>\n </FunnelChart>\n );\n }\n }\n}\n"],"mappings":"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"}
@@ -40,6 +40,26 @@ function tokenPx(name, el) {
40
40
  if (raw.endsWith("rem")) return n * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16);
41
41
  return n;
42
42
  }
43
+ /**
44
+ * The margin a Recharts plot is drawn with, in pixels, resolved on `el`
45
+ * (default `<html>`).
46
+ *
47
+ * Recharts takes a margin as numbers rather than CSS, so the token has to be
48
+ * read and handed over already in pixels. It is `--ds-space-2` at the top and
49
+ * on the right, which is what keeps the topmost y tick label and the last x
50
+ * tick label inside the box; the bottom and the left stay zero because the
51
+ * axes draw their own gutter there. Without a DOM — SSR — the fallback is the
52
+ * 8px that token emits at the default scale, asserted in `chart.test.tsx`.
53
+ */
54
+ function plotMargin(el) {
55
+ const space2 = tokenPx("--ds-space-2", el) ?? 8;
56
+ return {
57
+ top: space2,
58
+ right: space2,
59
+ bottom: 0,
60
+ left: 0
61
+ };
62
+ }
43
63
  let probe;
44
64
  function hex2(n) {
45
65
  return n.toString(16).padStart(2, "0");
@@ -187,6 +207,6 @@ function useThemeVersion() {
187
207
  return version;
188
208
  }
189
209
  //#endregion
190
- export { VIZ_SERIES_COUNT, chartScales, contrastLabel, isDarkSurface, piecewiseHex, readToken, resolveColor, resolveVizSeries, toHex, tokenPx, useThemeVersion, vizSeriesVars, vizVar };
210
+ export { VIZ_SERIES_COUNT, chartScales, contrastLabel, isDarkSurface, piecewiseHex, plotMargin, readToken, resolveColor, resolveVizSeries, toHex, tokenPx, useThemeVersion, vizSeriesVars, vizVar };
191
211
 
192
212
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","names":[],"sources":["../../../src/components/chart/tokens.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { vizSeries } from \"cortena-design/tokens\";\n\n/**\n * Token plumbing for the chart engines.\n *\n * Recharts draws SVG, so it can take `var(--ds-viz-1)` verbatim and the\n * browser resolves it. Nivo cannot: its canvas renderer hands colours to a\n * 2D context, and its SVG renderer runs colours through d3-color for label and\n * border derivation, and neither understands `var()`. So every Nivo value is\n * read from the `--ds-*` custom property at runtime and handed over as a\n * concrete hex. Nothing in this file is a literal; on the server (no DOM) the\n * `var()` form is returned so SSR markup still resolves in the browser.\n */\n\n/** Number of categorical series colours the design system defines (`--ds-viz-1..n`). */\nexport const VIZ_SERIES_COUNT = vizSeries.length;\n\n/** `var(--ds-viz-N)` for a zero-based series index, wrapping past the palette. */\nexport function vizVar(index: number): string {\n const n = VIZ_SERIES_COUNT;\n return `var(--ds-viz-${(((index % n) + n) % n) + 1})`;\n}\n\n/** The categorical palette as `var()` references, in series order. */\nexport const vizSeriesVars: readonly string[] = /* @__PURE__ */ Array.from({ length: VIZ_SERIES_COUNT }, (_, i) =>\n vizVar(i),\n);\n\nfunction hasDom(): boolean {\n return typeof document !== \"undefined\" && typeof getComputedStyle === \"function\";\n}\n\n/** Read a custom property as the browser computes it on `el` (default: `<html>`). */\nexport function readToken(name: string, el?: Element | null): string {\n if (!hasDom()) return \"\";\n return getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();\n}\n\n/** A `--ds-text-*` size (rem or px) as a pixel number, for engines that want numbers. */\nexport function tokenPx(name: string, el?: Element | null): number | undefined {\n const raw = readToken(name, el);\n if (!raw) return undefined;\n const n = Number.parseFloat(raw);\n if (Number.isNaN(n)) return undefined;\n if (raw.endsWith(\"rem\")) {\n const root = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return n * root;\n }\n return n;\n}\n\nlet probe: CanvasRenderingContext2D | null | undefined;\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, \"0\");\n}\n\n/**\n * Convert any CSS colour the browser understands (oklch, color-mix, rgba…) to\n * `#rrggbb`, by painting it into an offscreen 2D context. Alpha is composited\n * against transparent, so translucent tokens come back opaque; chart fills\n * never use those.\n */\nexport function toHex(css: string): string | undefined {\n if (!hasDom() || !css) return undefined;\n if (probe === undefined) {\n probe = document.createElement(\"canvas\").getContext(\"2d\", { willReadFrequently: true });\n }\n if (!probe) return undefined;\n probe.clearRect(0, 0, 1, 1);\n probe.fillStyle = css;\n probe.fillRect(0, 0, 1, 1);\n const d = probe.getImageData(0, 0, 1, 1).data;\n return `#${hex2(d[0] ?? 0)}${hex2(d[1] ?? 0)}${hex2(d[2] ?? 0)}`;\n}\n\n/**\n * A token as a concrete colour. Returns hex in the browser; on the server the\n * `var()` form, which is fine for SVG markup and irrelevant for canvas.\n */\nexport function resolveColor(token: string, el?: Element | null): string {\n return toHex(readToken(token, el)) ?? `var(${token})`;\n}\n\n/** The categorical palette resolved to concrete colours, in series order. */\nexport function resolveVizSeries(el?: Element | null): string[] {\n return vizSeriesVars.map((_, i) => resolveColor(`--ds-viz-${i + 1}`, el));\n}\n\n/** Relative luminance of a `#rrggbb`, for choosing a scale direction. */\nfunction luminance(hex: string): number {\n const c = (i: number) => {\n const v = Number.parseInt(hex.slice(i, i + 2), 16) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * c(1) + 0.7152 * c(3) + 0.0722 * c(5);\n}\n\n/**\n * A label colour that reads on a given fill: the design system's ink on light\n * fills, its white on dark ones. Nivo cells and arcs paint their labels with\n * this so a value on a deep sequential cell stays legible.\n */\nexport function contrastLabel(el?: Element | null): (fill: string) => string {\n const ink = resolveColor(\"--ds-base-text-ink\", el);\n const white = resolveColor(\"--ds-base-white\", el);\n return (fill: string) => {\n const hex = fill.startsWith(\"#\") && fill.length === 7 ? fill : toHex(fill);\n return hex && luminance(hex) < 0.3 ? white : ink;\n };\n}\n\n/** True when the resolved `--ds-background` is dark, whatever set the theme (attribute or OS). */\nexport function isDarkSurface(el?: Element | null): boolean {\n const bg = toHex(readToken(\"--ds-background\", el));\n return bg ? luminance(bg) < 0.4 : false;\n}\n\n/** Linear interpolation through a list of hex stops; `t` in [0, 1]. */\nexport function piecewiseHex(stops: string[]): (t: number) => string {\n const rgb = stops.map((h) => [1, 3, 5].map((i) => Number.parseInt(h.slice(i, i + 2), 16)));\n return (t: number) => {\n const clamped = Math.min(1, Math.max(0, Number.isNaN(t) ? 0 : t));\n const pos = clamped * (rgb.length - 1);\n const i = Math.max(0, Math.min(rgb.length - 2, Math.floor(pos)));\n const f = pos - i;\n const a = rgb[i] ?? [0, 0, 0];\n const b = rgb[i + 1] ?? a;\n const mix = (k: number) => Math.round((a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * f);\n return `#${hex2(mix(0))}${hex2(mix(1))}${hex2(mix(2))}`;\n };\n}\n\nexport interface ChartScales {\n /** Low → high, as concrete hex stops. */\n sequential: string[];\n /** Negative → neutral → positive, as concrete hex stops. */\n diverging: string[];\n /** Interpolators over the stops, `t` in [0, 1], for Nivo's `interpolator` configs. */\n sequentialInterpolator: (t: number) => string;\n divergingInterpolator: (t: number) => string;\n /** `n` evenly spaced sequential swatches, for quantised fills like the calendar. */\n sequentialSteps: (n: number) => string[];\n /** Fill for cells with no value. */\n empty: string;\n /** The chart's surface (`--ds-background`), for the gaps between cells and arcs. */\n surface: string;\n}\n\n/**\n * Sequential and diverging scales from the tokens.\n *\n * The token set has only the eight categorical `--ds-viz-*` colours and no\n * sequential ramp, so the sequential scale is derived from the\n * `--ds-primary-100..900` ladder: light surfaces run 100 → 700 (pale to deep),\n * dark surfaces run 900 → 400 (deep to bright), so low values sit near the\n * background in both themes. The diverging scale runs `--ds-viz-5` (rose)\n * through the theme's neutral (`--ds-neutral-200` light, `--ds-neutral-700`\n * dark) to `--ds-viz-1` (blue).\n */\nexport function chartScales(el?: Element | null): ChartScales {\n const dark = isDarkSurface(el);\n const seqTokens = dark\n ? [\"--ds-primary-900\", \"--ds-primary-800\", \"--ds-primary-600\", \"--ds-primary-400\"]\n : [\"--ds-primary-100\", \"--ds-primary-300\", \"--ds-primary-500\", \"--ds-primary-700\"];\n const sequential = seqTokens.map((t) => resolveColor(t, el));\n const diverging = [\"--ds-viz-5\", dark ? \"--ds-neutral-700\" : \"--ds-neutral-200\", \"--ds-viz-1\"].map(\n (t) => resolveColor(t, el),\n );\n const sequentialInterpolator = piecewiseHex(sequential);\n const divergingInterpolator = piecewiseHex(diverging);\n return {\n sequential,\n diverging,\n sequentialInterpolator,\n divergingInterpolator,\n sequentialSteps: (n) =>\n Array.from({ length: n }, (_, i) => sequentialInterpolator(n === 1 ? 1 : i / (n - 1))),\n empty: resolveColor(\"--ds-muted\", el),\n surface: resolveColor(\"--ds-background\", el),\n };\n}\n\n/**\n * A counter that bumps whenever the theme may have changed: the `data-theme`\n * or `class` attribute on `<html>`, or the OS colour scheme. Resolved token\n * values are memoised on it so Nivo re-reads them on a flip.\n */\nexport function useThemeVersion(): number {\n const [version, setVersion] = useState(0);\n useEffect(() => {\n if (!hasDom()) return;\n const bump = () => setVersion((v) => v + 1);\n const observer = new MutationObserver(bump);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\", \"style\"],\n });\n const media = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n media?.addEventListener?.(\"change\", bump);\n return () => {\n observer.disconnect();\n media?.removeEventListener?.(\"change\", bump);\n };\n }, []);\n return version;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tokens.js","names":[],"sources":["../../../src/components/chart/tokens.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { vizSeries } from \"cortena-design/tokens\";\n\n/**\n * Token plumbing for the chart engines.\n *\n * Recharts draws SVG, so it can take `var(--ds-viz-1)` verbatim and the\n * browser resolves it. Nivo cannot: its canvas renderer hands colours to a\n * 2D context, and its SVG renderer runs colours through d3-color for label and\n * border derivation, and neither understands `var()`. So every Nivo value is\n * read from the `--ds-*` custom property at runtime and handed over as a\n * concrete hex. Nothing in this file is a literal; on the server (no DOM) the\n * `var()` form is returned so SSR markup still resolves in the browser.\n */\n\n/** Number of categorical series colours the design system defines (`--ds-viz-1..n`). */\nexport const VIZ_SERIES_COUNT = vizSeries.length;\n\n/** `var(--ds-viz-N)` for a zero-based series index, wrapping past the palette. */\nexport function vizVar(index: number): string {\n const n = VIZ_SERIES_COUNT;\n return `var(--ds-viz-${(((index % n) + n) % n) + 1})`;\n}\n\n/** The categorical palette as `var()` references, in series order. */\nexport const vizSeriesVars: readonly string[] = /* @__PURE__ */ Array.from({ length: VIZ_SERIES_COUNT }, (_, i) =>\n vizVar(i),\n);\n\nfunction hasDom(): boolean {\n return typeof document !== \"undefined\" && typeof getComputedStyle === \"function\";\n}\n\n/** Read a custom property as the browser computes it on `el` (default: `<html>`). */\nexport function readToken(name: string, el?: Element | null): string {\n if (!hasDom()) return \"\";\n return getComputedStyle(el ?? document.documentElement).getPropertyValue(name).trim();\n}\n\n/** A `--ds-text-*` size (rem or px) as a pixel number, for engines that want numbers. */\nexport function tokenPx(name: string, el?: Element | null): number | undefined {\n const raw = readToken(name, el);\n if (!raw) return undefined;\n const n = Number.parseFloat(raw);\n if (Number.isNaN(n)) return undefined;\n if (raw.endsWith(\"rem\")) {\n const root = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return n * root;\n }\n return n;\n}\n\n/** The plot margin in pixels when there is no DOM: what `--ds-space-2` emits. */\nexport const PLOT_MARGIN_FALLBACK = 8;\n\n/**\n * The margin a Recharts plot is drawn with, in pixels, resolved on `el`\n * (default `<html>`).\n *\n * Recharts takes a margin as numbers rather than CSS, so the token has to be\n * read and handed over already in pixels. It is `--ds-space-2` at the top and\n * on the right, which is what keeps the topmost y tick label and the last x\n * tick label inside the box; the bottom and the left stay zero because the\n * axes draw their own gutter there. Without a DOM — SSR — the fallback is the\n * 8px that token emits at the default scale, asserted in `chart.test.tsx`.\n */\nexport function plotMargin(el?: Element | null): { top: number; right: number; bottom: number; left: number } {\n const space2 = tokenPx(\"--ds-space-2\", el) ?? PLOT_MARGIN_FALLBACK;\n return { top: space2, right: space2, bottom: 0, left: 0 };\n}\n\nlet probe: CanvasRenderingContext2D | null | undefined;\n\nfunction hex2(n: number): string {\n return n.toString(16).padStart(2, \"0\");\n}\n\n/**\n * Convert any CSS colour the browser understands (oklch, color-mix, rgba…) to\n * `#rrggbb`, by painting it into an offscreen 2D context. Alpha is composited\n * against transparent, so translucent tokens come back opaque; chart fills\n * never use those.\n */\nexport function toHex(css: string): string | undefined {\n if (!hasDom() || !css) return undefined;\n if (probe === undefined) {\n probe = document.createElement(\"canvas\").getContext(\"2d\", { willReadFrequently: true });\n }\n if (!probe) return undefined;\n probe.clearRect(0, 0, 1, 1);\n probe.fillStyle = css;\n probe.fillRect(0, 0, 1, 1);\n const d = probe.getImageData(0, 0, 1, 1).data;\n return `#${hex2(d[0] ?? 0)}${hex2(d[1] ?? 0)}${hex2(d[2] ?? 0)}`;\n}\n\n/**\n * A token as a concrete colour. Returns hex in the browser; on the server the\n * `var()` form, which is fine for SVG markup and irrelevant for canvas.\n */\nexport function resolveColor(token: string, el?: Element | null): string {\n return toHex(readToken(token, el)) ?? `var(${token})`;\n}\n\n/** The categorical palette resolved to concrete colours, in series order. */\nexport function resolveVizSeries(el?: Element | null): string[] {\n return vizSeriesVars.map((_, i) => resolveColor(`--ds-viz-${i + 1}`, el));\n}\n\n/** Relative luminance of a `#rrggbb`, for choosing a scale direction. */\nfunction luminance(hex: string): number {\n const c = (i: number) => {\n const v = Number.parseInt(hex.slice(i, i + 2), 16) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * c(1) + 0.7152 * c(3) + 0.0722 * c(5);\n}\n\n/**\n * A label colour that reads on a given fill: the design system's ink on light\n * fills, its white on dark ones. Nivo cells and arcs paint their labels with\n * this so a value on a deep sequential cell stays legible.\n */\nexport function contrastLabel(el?: Element | null): (fill: string) => string {\n const ink = resolveColor(\"--ds-base-text-ink\", el);\n const white = resolveColor(\"--ds-base-white\", el);\n return (fill: string) => {\n const hex = fill.startsWith(\"#\") && fill.length === 7 ? fill : toHex(fill);\n return hex && luminance(hex) < 0.3 ? white : ink;\n };\n}\n\n/** True when the resolved `--ds-background` is dark, whatever set the theme (attribute or OS). */\nexport function isDarkSurface(el?: Element | null): boolean {\n const bg = toHex(readToken(\"--ds-background\", el));\n return bg ? luminance(bg) < 0.4 : false;\n}\n\n/** Linear interpolation through a list of hex stops; `t` in [0, 1]. */\nexport function piecewiseHex(stops: string[]): (t: number) => string {\n const rgb = stops.map((h) => [1, 3, 5].map((i) => Number.parseInt(h.slice(i, i + 2), 16)));\n return (t: number) => {\n const clamped = Math.min(1, Math.max(0, Number.isNaN(t) ? 0 : t));\n const pos = clamped * (rgb.length - 1);\n const i = Math.max(0, Math.min(rgb.length - 2, Math.floor(pos)));\n const f = pos - i;\n const a = rgb[i] ?? [0, 0, 0];\n const b = rgb[i + 1] ?? a;\n const mix = (k: number) => Math.round((a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * f);\n return `#${hex2(mix(0))}${hex2(mix(1))}${hex2(mix(2))}`;\n };\n}\n\nexport interface ChartScales {\n /** Low → high, as concrete hex stops. */\n sequential: string[];\n /** Negative → neutral → positive, as concrete hex stops. */\n diverging: string[];\n /** Interpolators over the stops, `t` in [0, 1], for Nivo's `interpolator` configs. */\n sequentialInterpolator: (t: number) => string;\n divergingInterpolator: (t: number) => string;\n /** `n` evenly spaced sequential swatches, for quantised fills like the calendar. */\n sequentialSteps: (n: number) => string[];\n /** Fill for cells with no value. */\n empty: string;\n /** The chart's surface (`--ds-background`), for the gaps between cells and arcs. */\n surface: string;\n}\n\n/**\n * Sequential and diverging scales from the tokens.\n *\n * The token set has only the eight categorical `--ds-viz-*` colours and no\n * sequential ramp, so the sequential scale is derived from the\n * `--ds-primary-100..900` ladder: light surfaces run 100 → 700 (pale to deep),\n * dark surfaces run 900 → 400 (deep to bright), so low values sit near the\n * background in both themes. The diverging scale runs `--ds-viz-5` (rose)\n * through the theme's neutral (`--ds-neutral-200` light, `--ds-neutral-700`\n * dark) to `--ds-viz-1` (blue).\n */\nexport function chartScales(el?: Element | null): ChartScales {\n const dark = isDarkSurface(el);\n const seqTokens = dark\n ? [\"--ds-primary-900\", \"--ds-primary-800\", \"--ds-primary-600\", \"--ds-primary-400\"]\n : [\"--ds-primary-100\", \"--ds-primary-300\", \"--ds-primary-500\", \"--ds-primary-700\"];\n const sequential = seqTokens.map((t) => resolveColor(t, el));\n const diverging = [\"--ds-viz-5\", dark ? \"--ds-neutral-700\" : \"--ds-neutral-200\", \"--ds-viz-1\"].map(\n (t) => resolveColor(t, el),\n );\n const sequentialInterpolator = piecewiseHex(sequential);\n const divergingInterpolator = piecewiseHex(diverging);\n return {\n sequential,\n diverging,\n sequentialInterpolator,\n divergingInterpolator,\n sequentialSteps: (n) =>\n Array.from({ length: n }, (_, i) => sequentialInterpolator(n === 1 ? 1 : i / (n - 1))),\n empty: resolveColor(\"--ds-muted\", el),\n surface: resolveColor(\"--ds-background\", el),\n };\n}\n\n/**\n * A counter that bumps whenever the theme may have changed: the `data-theme`\n * or `class` attribute on `<html>`, or the OS colour scheme. Resolved token\n * values are memoised on it so Nivo re-reads them on a flip.\n */\nexport function useThemeVersion(): number {\n const [version, setVersion] = useState(0);\n useEffect(() => {\n if (!hasDom()) return;\n const bump = () => setVersion((v) => v + 1);\n const observer = new MutationObserver(bump);\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\", \"class\", \"style\"],\n });\n const media = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n media?.addEventListener?.(\"change\", bump);\n return () => {\n observer.disconnect();\n media?.removeEventListener?.(\"change\", bump);\n };\n }, []);\n return version;\n}\n"],"mappings":"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"}