@elabs-ai/components-data 4.2.0 → 5.0.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.
- package/README.md +1 -1
- package/dist/chunk-3OVN7MMO.js +2 -0
- package/dist/chunk-3OVN7MMO.js.map +1 -0
- package/dist/chunk-3UFWYVDS.js +2 -0
- package/dist/chunk-3UFWYVDS.js.map +1 -0
- package/dist/chunk-47ZBQZYS.js +47 -0
- package/dist/chunk-47ZBQZYS.js.map +1 -0
- package/dist/chunk-5K2IFOV2.js +2 -0
- package/dist/chunk-5K2IFOV2.js.map +1 -0
- package/dist/chunk-677ORWI6.js +17 -0
- package/dist/chunk-677ORWI6.js.map +1 -0
- package/dist/chunk-7ZYUY3UL.js +2 -0
- package/dist/chunk-7ZYUY3UL.js.map +1 -0
- package/dist/chunk-BEYHV3ZX.js +37 -0
- package/dist/chunk-BEYHV3ZX.js.map +1 -0
- package/dist/chunk-DBPJ7LVM.js +57 -0
- package/dist/chunk-DBPJ7LVM.js.map +1 -0
- package/dist/chunk-E3V3SYY7.js +87 -0
- package/dist/chunk-E3V3SYY7.js.map +1 -0
- package/dist/chunk-GBO56756.js +81 -0
- package/dist/chunk-GBO56756.js.map +1 -0
- package/dist/chunk-HG2YMSRM.js +95 -0
- package/dist/chunk-HG2YMSRM.js.map +1 -0
- package/dist/chunk-K2GHSRCP.js +1823 -0
- package/dist/chunk-K2GHSRCP.js.map +1 -0
- package/dist/chunk-KCB33PD3.js +43 -0
- package/dist/chunk-KCB33PD3.js.map +1 -0
- package/dist/chunk-MV2YVXEZ.js +67 -0
- package/dist/chunk-MV2YVXEZ.js.map +1 -0
- package/dist/chunk-NKVJUQYW.js +82 -0
- package/dist/chunk-NKVJUQYW.js.map +1 -0
- package/dist/chunk-OASM53LL.js +29 -0
- package/dist/chunk-OASM53LL.js.map +1 -0
- package/dist/chunk-RZ5MYZOA.js +2 -0
- package/dist/chunk-RZ5MYZOA.js.map +1 -0
- package/dist/chunk-SK6347WB.js +100 -0
- package/dist/chunk-SK6347WB.js.map +1 -0
- package/dist/chunk-TBB33PHQ.js +58 -0
- package/dist/chunk-TBB33PHQ.js.map +1 -0
- package/dist/chunk-VCLA45IC.js +150 -0
- package/dist/chunk-VCLA45IC.js.map +1 -0
- package/dist/chunk-XN6O6Z2N.js +58 -0
- package/dist/chunk-XN6O6Z2N.js.map +1 -0
- package/dist/chunk-ZFEW36RG.js +77 -0
- package/dist/chunk-ZFEW36RG.js.map +1 -0
- package/dist/chunk-ZQXJ34VJ.js +34 -0
- package/dist/chunk-ZQXJ34VJ.js.map +1 -0
- package/dist/chunk-ZUCGV2XD.js +42 -0
- package/dist/chunk-ZUCGV2XD.js.map +1 -0
- package/dist/column-picker/column-picker.js +8 -0
- package/dist/column-picker/column-picker.js.map +1 -0
- package/dist/column-picker/index.js +9 -0
- package/dist/column-picker/index.js.map +1 -0
- package/dist/data-table/card-layout.js +11 -0
- package/dist/data-table/card-layout.js.map +1 -0
- package/dist/data-table/cell-scales.js +26 -0
- package/dist/data-table/cell-scales.js.map +1 -0
- package/dist/data-table/cells/bar-cell.js +14 -0
- package/dist/data-table/cells/bar-cell.js.map +1 -0
- package/dist/data-table/cells/category-legend.js +9 -0
- package/dist/data-table/cells/category-legend.js.map +1 -0
- package/dist/data-table/cells/columns-cell.js +13 -0
- package/dist/data-table/cells/columns-cell.js.map +1 -0
- package/dist/data-table/cells/heatmap-cell.js +13 -0
- package/dist/data-table/cells/heatmap-cell.js.map +1 -0
- package/dist/data-table/cells/markdown-cell.js +13 -0
- package/dist/data-table/cells/markdown-cell.js.map +1 -0
- package/dist/data-table/cells/sparkline-cell.js +13 -0
- package/dist/data-table/cells/sparkline-cell.js.map +1 -0
- package/dist/data-table/column-meta.js +12 -0
- package/dist/data-table/column-meta.js.map +1 -0
- package/dist/data-table/data-table.js +23 -0
- package/dist/data-table/data-table.js.map +1 -0
- package/dist/data-table/index.js +23 -0
- package/dist/data-table/index.js.map +1 -0
- package/dist/data-table/ranks-column.js +13 -0
- package/dist/data-table/ranks-column.js.map +1 -0
- package/dist/data-table/sticky-rows.js +10 -0
- package/dist/data-table/sticky-rows.js.map +1 -0
- package/dist/data-table/use-table-breakpoint.js +13 -0
- package/dist/data-table/use-table-breakpoint.js.map +1 -0
- package/dist/facet-filter/facet-filter.js +8 -0
- package/dist/facet-filter/facet-filter.js.map +1 -0
- package/dist/facet-filter/index.js +9 -0
- package/dist/facet-filter/index.js.map +1 -0
- package/dist/filter-bar/filter-bar.js +8 -0
- package/dist/filter-bar/filter-bar.js.map +1 -0
- package/dist/filter-bar/filter-chip.js +9 -0
- package/dist/filter-bar/filter-chip.js.map +1 -0
- package/dist/filter-bar/index.js +13 -0
- package/dist/filter-bar/index.js.map +1 -0
- package/dist/index.d.ts +547 -8
- package/dist/index.js +102 -1512
- package/dist/index.js.map +1 -1
- package/dist/search-input/index.js +9 -0
- package/dist/search-input/index.js.map +1 -0
- package/dist/search-input/search-input.js +9 -0
- package/dist/search-input/search-input.js.map +1 -0
- package/dist/to-csv.js +10 -0
- package/dist/to-csv.js.map +1 -0
- package/package.json +7 -7
- package/src/data-table/card-layout.test.tsx +26 -0
- package/src/data-table/card-layout.tsx +74 -0
- package/src/data-table/cell-scales.test.ts +118 -0
- package/src/data-table/cell-scales.ts +246 -0
- package/src/data-table/cells/bar-cell.test.tsx +158 -0
- package/src/data-table/cells/bar-cell.tsx +135 -0
- package/src/data-table/cells/category-legend.test.tsx +37 -0
- package/src/data-table/cells/category-legend.tsx +58 -0
- package/src/data-table/cells/columns-cell.test.tsx +27 -0
- package/src/data-table/cells/columns-cell.tsx +79 -0
- package/src/data-table/cells/heatmap-cell.test.tsx +31 -0
- package/src/data-table/cells/heatmap-cell.tsx +116 -0
- package/src/data-table/cells/markdown-cell.test.tsx +36 -0
- package/src/data-table/cells/markdown-cell.tsx +91 -0
- package/src/data-table/cells/sparkline-cell.test.tsx +43 -0
- package/src/data-table/cells/sparkline-cell.tsx +113 -0
- package/src/data-table/column-meta.test.ts +42 -0
- package/src/data-table/column-meta.ts +282 -0
- package/src/data-table/data-table.stories.tsx +733 -1
- package/src/data-table/data-table.test.tsx +486 -0
- package/src/data-table/data-table.tsx +965 -165
- package/src/data-table/ranks-column.test.tsx +65 -0
- package/src/data-table/ranks-column.tsx +91 -0
- package/src/data-table/sticky-rows.test.ts +16 -0
- package/src/data-table/sticky-rows.ts +77 -0
- package/src/data-table/use-table-breakpoint.test.ts +14 -0
- package/src/data-table/use-table-breakpoint.ts +67 -0
- package/src/filter-bar/filter-chip.stories.tsx +1 -0
- package/src/index.ts +64 -0
- package/src/templates-data-app.stories.tsx +7 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/data-table/cells/markdown-cell.tsx
|
|
4
|
+
import { Fragment, forwardRef } from "react";
|
|
5
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
var TOKEN = /\*\*([^*]+)\*\*|\*([^*]+)\*|_([^_]+)_|`([^`]+)`|!\[([^\]]*)\]\(([^)\s]+)\)|\[([^\]]+)\]\(([^)\s]+)\)|\^([^^]+)\^|<sup>(.*?)<\/sup>/g;
|
|
8
|
+
function isSafeCellUrl(url) {
|
|
9
|
+
return /^(https?:\/\/|mailto:|\/(?!\/)|#|\.{1,2}\/)/i.test(url.trim());
|
|
10
|
+
}
|
|
11
|
+
function parseCellMarkdown(text, images = false, keyPrefix = "md") {
|
|
12
|
+
const out = [];
|
|
13
|
+
let last = 0;
|
|
14
|
+
let n = 0;
|
|
15
|
+
for (const m of text.matchAll(TOKEN)) {
|
|
16
|
+
const at = m.index ?? 0;
|
|
17
|
+
if (at > last) out.push(text.slice(last, at));
|
|
18
|
+
const key = `${keyPrefix}-${n++}`;
|
|
19
|
+
const [, bold, em1, em2, code, alt, src, linkText, href, sup1, sup2] = m;
|
|
20
|
+
if (bold !== void 0)
|
|
21
|
+
out.push(/* @__PURE__ */ jsx("strong", { children: parseCellMarkdown(bold, images, key) }, key));
|
|
22
|
+
else if ((em1 ?? em2) !== void 0)
|
|
23
|
+
out.push(/* @__PURE__ */ jsx("em", { children: parseCellMarkdown(em1 ?? em2, images, key) }, key));
|
|
24
|
+
else if (code !== void 0)
|
|
25
|
+
out.push(
|
|
26
|
+
/* @__PURE__ */ jsx("code", { className: "rounded-sm bg-muted px-1 font-mono text-code", children: code }, key)
|
|
27
|
+
);
|
|
28
|
+
else if (src !== void 0)
|
|
29
|
+
out.push(
|
|
30
|
+
images && isSafeCellUrl(src) ? /* @__PURE__ */ jsx(
|
|
31
|
+
"img",
|
|
32
|
+
{
|
|
33
|
+
src,
|
|
34
|
+
alt: alt ?? "",
|
|
35
|
+
className: "inline-block h-4 w-auto align-text-bottom"
|
|
36
|
+
},
|
|
37
|
+
key
|
|
38
|
+
) : /* @__PURE__ */ jsx(Fragment, { children: alt }, key)
|
|
39
|
+
);
|
|
40
|
+
else if (href !== void 0 && linkText !== void 0)
|
|
41
|
+
out.push(
|
|
42
|
+
isSafeCellUrl(href) ? /* @__PURE__ */ jsx(
|
|
43
|
+
"a",
|
|
44
|
+
{
|
|
45
|
+
href,
|
|
46
|
+
className: "rounded-sm text-link underline underline-offset-2 hover:text-foreground focus-ring",
|
|
47
|
+
children: parseCellMarkdown(linkText, images, key)
|
|
48
|
+
},
|
|
49
|
+
key
|
|
50
|
+
) : /* @__PURE__ */ jsx(Fragment, { children: parseCellMarkdown(linkText, images, key) }, key)
|
|
51
|
+
);
|
|
52
|
+
else out.push(/* @__PURE__ */ jsx("sup", { children: sup1 ?? sup2 }, key));
|
|
53
|
+
last = at + m[0].length;
|
|
54
|
+
}
|
|
55
|
+
if (last < text.length) out.push(text.slice(last));
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
var MarkdownCell = forwardRef(function MarkdownCell2({ text, images = false, className, ...props }, ref) {
|
|
59
|
+
return /* @__PURE__ */ jsx("span", { ref, "data-slot": "markdown-cell", className: cn("break-words", className), ...props, children: parseCellMarkdown(text, images) });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
isSafeCellUrl,
|
|
64
|
+
parseCellMarkdown,
|
|
65
|
+
MarkdownCell
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=chunk-MV2YVXEZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table/cells/markdown-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport { Fragment, forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\n/**\n * Safe inline markdown for a table cell: `**bold**`, `*italic*` / `_italic_`,\n * `` `code` ``, `[link](url)`, `^sup^` / `<sup>sup</sup>`, and — only with\n * `images: true` — ``. Built as React elements, never HTML: there is\n * no `innerHTML` sink, and a link or image URL that is not `http(s):`,\n * `mailto:`, a path or a fragment renders as plain text.\n */\nconst TOKEN =\n /\\*\\*([^*]+)\\*\\*|\\*([^*]+)\\*|_([^_]+)_|`([^`]+)`|!\\[([^\\]]*)\\]\\(([^)\\s]+)\\)|\\[([^\\]]+)\\]\\(([^)\\s]+)\\)|\\^([^^]+)\\^|<sup>(.*?)<\\/sup>/g;\n\n/** `true` for a URL a cell may link to or load. */\nexport function isSafeCellUrl(url: string): boolean {\n return /^(https?:\\/\\/|mailto:|\\/(?!\\/)|#|\\.{1,2}\\/)/i.test(url.trim());\n}\n\n/** Parses `text` into React nodes. Exported for tests. */\nexport function parseCellMarkdown(text: string, images = false, keyPrefix = \"md\"): ReactNode[] {\n const out: ReactNode[] = [];\n let last = 0;\n let n = 0;\n for (const m of text.matchAll(TOKEN)) {\n const at = m.index ?? 0;\n if (at > last) out.push(text.slice(last, at));\n const key = `${keyPrefix}-${n++}`;\n const [, bold, em1, em2, code, alt, src, linkText, href, sup1, sup2] = m;\n if (bold !== undefined)\n out.push(<strong key={key}>{parseCellMarkdown(bold, images, key)}</strong>);\n else if ((em1 ?? em2) !== undefined)\n out.push(<em key={key}>{parseCellMarkdown((em1 ?? em2) as string, images, key)}</em>);\n else if (code !== undefined)\n out.push(\n <code key={key} className=\"rounded-sm bg-muted px-1 font-mono text-code\">\n {code}\n </code>,\n );\n else if (src !== undefined)\n out.push(\n images && isSafeCellUrl(src) ? (\n <img\n key={key}\n src={src}\n alt={alt ?? \"\"}\n className=\"inline-block h-4 w-auto align-text-bottom\"\n />\n ) : (\n <Fragment key={key}>{alt}</Fragment>\n ),\n );\n else if (href !== undefined && linkText !== undefined)\n out.push(\n isSafeCellUrl(href) ? (\n <a\n key={key}\n href={href}\n className=\"rounded-sm text-link underline underline-offset-2 hover:text-foreground focus-ring\"\n >\n {parseCellMarkdown(linkText, images, key)}\n </a>\n ) : (\n <Fragment key={key}>{parseCellMarkdown(linkText, images, key)}</Fragment>\n ),\n );\n else out.push(<sup key={key}>{(sup1 ?? sup2) as string}</sup>);\n last = at + m[0].length;\n }\n if (last < text.length) out.push(text.slice(last));\n return out;\n}\n\nexport interface MarkdownCellProps extends HTMLAttributes<HTMLSpanElement> {\n text: string;\n /** Render `` as an image. Default `false` (the alt text prints). */\n images?: boolean;\n}\n\n/** A cell rendered from safe inline markdown (see {@link parseCellMarkdown}). */\nexport const MarkdownCell = forwardRef<HTMLSpanElement, MarkdownCellProps>(function MarkdownCell(\n { text, images = false, className, ...props },\n ref,\n) {\n return (\n <span ref={ref} data-slot=\"markdown-cell\" className={cn(\"break-words\", className)} {...props}>\n {parseCellMarkdown(text, images)}\n </span>\n );\n});\n"],"mappings":";;;AAEA,SAAS,UAAU,kBAAuD;AAC1E,SAAS,UAAU;AA4BJ;AAnBf,IAAM,QACJ;AAGK,SAAS,cAAc,KAAsB;AAClD,SAAO,+CAA+C,KAAK,IAAI,KAAK,CAAC;AACvE;AAGO,SAAS,kBAAkB,MAAc,SAAS,OAAO,YAAY,MAAmB;AAC7F,QAAM,MAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,MAAI,IAAI;AACR,aAAW,KAAK,KAAK,SAAS,KAAK,GAAG;AACpC,UAAM,KAAK,EAAE,SAAS;AACtB,QAAI,KAAK,KAAM,KAAI,KAAK,KAAK,MAAM,MAAM,EAAE,CAAC;AAC5C,UAAM,MAAM,GAAG,SAAS,IAAI,GAAG;AAC/B,UAAM,CAAC,EAAE,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,UAAU,MAAM,MAAM,IAAI,IAAI;AACvE,QAAI,SAAS;AACX,UAAI,KAAK,oBAAC,YAAkB,4BAAkB,MAAM,QAAQ,GAAG,KAAzC,GAA2C,CAAS;AAAA,cAClE,OAAO,SAAS;AACxB,UAAI,KAAK,oBAAC,QAAc,4BAAmB,OAAO,KAAgB,QAAQ,GAAG,KAA3D,GAA6D,CAAK;AAAA,aAC7E,SAAS;AAChB,UAAI;AAAA,QACF,oBAAC,UAAe,WAAU,gDACvB,kBADQ,GAEX;AAAA,MACF;AAAA,aACO,QAAQ;AACf,UAAI;AAAA,QACF,UAAU,cAAc,GAAG,IACzB;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,KAAK,OAAO;AAAA,YACZ,WAAU;AAAA;AAAA,UAHL;AAAA,QAIP,IAEA,oBAAC,YAAoB,iBAAN,GAAU;AAAA,MAE7B;AAAA,aACO,SAAS,UAAa,aAAa;AAC1C,UAAI;AAAA,QACF,cAAc,IAAI,IAChB;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,WAAU;AAAA,YAET,4BAAkB,UAAU,QAAQ,GAAG;AAAA;AAAA,UAJnC;AAAA,QAKP,IAEA,oBAAC,YAAoB,4BAAkB,UAAU,QAAQ,GAAG,KAA7C,GAA+C;AAAA,MAElE;AAAA,QACG,KAAI,KAAK,oBAAC,SAAgB,kBAAQ,QAAf,GAA+B,CAAM;AAC7D,WAAO,KAAK,EAAE,CAAC,EAAE;AAAA,EACnB;AACA,MAAI,OAAO,KAAK,OAAQ,KAAI,KAAK,KAAK,MAAM,IAAI,CAAC;AACjD,SAAO;AACT;AASO,IAAM,eAAe,WAA+C,SAASA,cAClF,EAAE,MAAM,SAAS,OAAO,WAAW,GAAG,MAAM,GAC5C,KACA;AACA,SACE,oBAAC,UAAK,KAAU,aAAU,iBAAgB,WAAW,GAAG,eAAe,SAAS,GAAI,GAAG,OACpF,4BAAkB,MAAM,MAAM,GACjC;AAEJ,CAAC;","names":["MarkdownCell"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/data-table/cells/heatmap-cell.tsx
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
function heatmapCellStyle(color) {
|
|
8
|
+
return color ? { backgroundColor: color } : void 0;
|
|
9
|
+
}
|
|
10
|
+
var HeatmapCell = forwardRef(function HeatmapCell2({ label, hideValue = false, className, ...props }, ref) {
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
"span",
|
|
13
|
+
{
|
|
14
|
+
ref,
|
|
15
|
+
"data-slot": "heatmap-cell",
|
|
16
|
+
className: cn(
|
|
17
|
+
hideValue ? "sr-only" : "inline-block rounded-sm bg-card/85 px-1 text-foreground tabular-nums",
|
|
18
|
+
className
|
|
19
|
+
),
|
|
20
|
+
...props,
|
|
21
|
+
children: label
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
var HeatmapLegend = forwardRef(function HeatmapLegend2({ scale, title, formatValue, className, ...props }, ref) {
|
|
26
|
+
const first = scale.steps[0];
|
|
27
|
+
const last = scale.steps[scale.steps.length - 1];
|
|
28
|
+
if (!first || !last) return null;
|
|
29
|
+
const continuous = scale.type === "continuous";
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
"div",
|
|
32
|
+
{
|
|
33
|
+
ref,
|
|
34
|
+
role: "group",
|
|
35
|
+
"aria-label": title,
|
|
36
|
+
"data-slot": "heatmap-legend",
|
|
37
|
+
"data-scale-type": scale.type,
|
|
38
|
+
className: cn("flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-meta", className),
|
|
39
|
+
...props,
|
|
40
|
+
children: [
|
|
41
|
+
title && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-muted-foreground", children: title }),
|
|
42
|
+
continuous ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5 tabular-nums", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { children: formatValue(first.from) }),
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"span",
|
|
46
|
+
{
|
|
47
|
+
"aria-hidden": "true",
|
|
48
|
+
"data-slot": "heatmap-legend-ramp",
|
|
49
|
+
className: "block h-2.5 w-24 rounded-sm",
|
|
50
|
+
style: {
|
|
51
|
+
backgroundImage: `linear-gradient(to right, ${scale.steps.map((s) => s.color).join(", ")})`
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
/* @__PURE__ */ jsx("span", { children: formatValue(last.to) })
|
|
56
|
+
] }) : /* @__PURE__ */ jsx("ul", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 tabular-nums", children: scale.steps.map((step) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
"span",
|
|
59
|
+
{
|
|
60
|
+
"aria-hidden": "true",
|
|
61
|
+
"data-slot": "heatmap-legend-swatch",
|
|
62
|
+
className: "block size-2.5 rounded-full",
|
|
63
|
+
style: { backgroundColor: step.color }
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
67
|
+
formatValue(step.from),
|
|
68
|
+
"\u2013",
|
|
69
|
+
formatValue(step.to)
|
|
70
|
+
] })
|
|
71
|
+
] }, `${step.from}-${step.to}`)) })
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
heatmapCellStyle,
|
|
79
|
+
HeatmapCell,
|
|
80
|
+
HeatmapLegend
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=chunk-NKVJUQYW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table/cells/heatmap-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type CSSProperties, type HTMLAttributes } from \"react\";\nimport type { ColorScale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\n/**\n * The `<td>` style that paints a heatmap cell: the value's ramp colour (a\n * `var(--chart-seq-*)` / `var(--chart-div-*)` reference from `colorScaleFor`),\n * so a theme flip re-colours it with no re-render. `null` (no data) paints\n * nothing.\n */\nexport function heatmapCellStyle(color: string | null): CSSProperties | undefined {\n return color ? { backgroundColor: color } : undefined;\n}\n\nexport interface HeatmapCellProps extends HTMLAttributes<HTMLSpanElement> {\n /** The printed (formatted) value. Always in the accessible tree. */\n label: string;\n /** Hide the value visually; it stays for screen readers, sorting and copy. */\n hideValue?: boolean;\n}\n\n/**\n * A heatmap cell's content. The `<td>` carries the colour\n * ({@link heatmapCellStyle}); a visible value sits on a card plate, so its ink\n * reads on every ramp step in every theme without resolving the colour.\n */\nexport const HeatmapCell = forwardRef<HTMLSpanElement, HeatmapCellProps>(function HeatmapCell(\n { label, hideValue = false, className, ...props },\n ref,\n) {\n return (\n <span\n ref={ref}\n data-slot=\"heatmap-cell\"\n className={cn(\n hideValue\n ? \"sr-only\"\n : \"inline-block rounded-sm bg-card/85 px-1 text-foreground tabular-nums\",\n className,\n )}\n {...props}\n >\n {label}\n </span>\n );\n});\n\nexport interface HeatmapLegendProps extends HTMLAttributes<HTMLDivElement> {\n scale: ColorScale;\n /** What the colours measure (the column's header). */\n title?: string;\n formatValue: (value: number) => string;\n}\n\n/**\n * A heatmap column's colour key: one swatch per class (stepped) or a ramp\n * strip (continuous), with the class bounds printed in the `text-meta` role.\n */\nexport const HeatmapLegend = forwardRef<HTMLDivElement, HeatmapLegendProps>(function HeatmapLegend(\n { scale, title, formatValue, className, ...props },\n ref,\n) {\n const first = scale.steps[0];\n const last = scale.steps[scale.steps.length - 1];\n if (!first || !last) return null;\n const continuous = scale.type === \"continuous\";\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={title}\n data-slot=\"heatmap-legend\"\n data-scale-type={scale.type}\n className={cn(\"flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-meta\", className)}\n {...props}\n >\n {title && (\n <span aria-hidden=\"true\" className=\"text-muted-foreground\">\n {title}\n </span>\n )}\n {continuous ? (\n <span className=\"flex min-w-0 items-center gap-1.5 tabular-nums\">\n <span>{formatValue(first.from)}</span>\n <span\n aria-hidden=\"true\"\n data-slot=\"heatmap-legend-ramp\"\n className=\"block h-2.5 w-24 rounded-sm\"\n style={{\n backgroundImage: `linear-gradient(to right, ${scale.steps.map((s) => s.color).join(\", \")})`,\n }}\n />\n <span>{formatValue(last.to)}</span>\n </span>\n ) : (\n <ul className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 tabular-nums\">\n {scale.steps.map((step) => (\n <li key={`${step.from}-${step.to}`} className=\"flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n data-slot=\"heatmap-legend-swatch\"\n className=\"block size-2.5 rounded-full\"\n style={{ backgroundColor: step.color }}\n />\n <span>\n {formatValue(step.from)}–{formatValue(step.to)}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n});\n"],"mappings":";;;AAEA,SAAS,kBAA2D;AAEpE,SAAS,UAAU;AA6Bf,cAmDI,YAnDJ;AArBG,SAAS,iBAAiB,OAAiD;AAChF,SAAO,QAAQ,EAAE,iBAAiB,MAAM,IAAI;AAC9C;AAcO,IAAM,cAAc,WAA8C,SAASA,aAChF,EAAE,OAAO,YAAY,OAAO,WAAW,GAAG,MAAM,GAChD,KACA;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,WAAW;AAAA,QACT,YACI,YACA;AAAA,QACJ;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ,CAAC;AAaM,IAAM,gBAAgB,WAA+C,SAASC,eACnF,EAAE,OAAO,OAAO,aAAa,WAAW,GAAG,MAAM,GACjD,KACA;AACA,QAAM,QAAQ,MAAM,MAAM,CAAC;AAC3B,QAAM,OAAO,MAAM,MAAM,MAAM,MAAM,SAAS,CAAC;AAC/C,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO;AAC5B,QAAM,aAAa,MAAM,SAAS;AAClC,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,mBAAiB,MAAM;AAAA,MACvB,WAAW,GAAG,iEAAiE,SAAS;AAAA,MACvF,GAAG;AAAA,MAEH;AAAA,iBACC,oBAAC,UAAK,eAAY,QAAO,WAAU,yBAChC,iBACH;AAAA,QAED,aACC,qBAAC,UAAK,WAAU,kDACd;AAAA,8BAAC,UAAM,sBAAY,MAAM,IAAI,GAAE;AAAA,UAC/B;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,aAAU;AAAA,cACV,WAAU;AAAA,cACV,OAAO;AAAA,gBACL,iBAAiB,6BAA6B,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,cAC1F;AAAA;AAAA,UACF;AAAA,UACA,oBAAC,UAAM,sBAAY,KAAK,EAAE,GAAE;AAAA,WAC9B,IAEA,oBAAC,QAAG,WAAU,oEACX,gBAAM,MAAM,IAAI,CAAC,SAChB,qBAAC,QAAmC,WAAU,2BAC5C;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,eAAY;AAAA,cACZ,aAAU;AAAA,cACV,WAAU;AAAA,cACV,OAAO,EAAE,iBAAiB,KAAK,MAAM;AAAA;AAAA,UACvC;AAAA,UACA,qBAAC,UACE;AAAA,wBAAY,KAAK,IAAI;AAAA,YAAE;AAAA,YAAE,YAAY,KAAK,EAAE;AAAA,aAC/C;AAAA,aATO,GAAG,KAAK,IAAI,IAAI,KAAK,EAAE,EAUhC,CACD,GACH;AAAA;AAAA;AAAA,EAEJ;AAEJ,CAAC;","names":["HeatmapCell","HeatmapLegend"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/filter-bar/filter-chip.tsx
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import {
|
|
6
|
+
FilterChip as BaseFilterChip,
|
|
7
|
+
useLocale
|
|
8
|
+
} from "@elabs-ai/components-ui";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
var FilterChip = forwardRef(function FilterChip2({ label, count, countLabel, ...props }, ref) {
|
|
11
|
+
const { formatNumber } = useLocale();
|
|
12
|
+
const countText = count === void 0 ? void 0 : countLabel ? `${countLabel} ${formatNumber(count)}` : formatNumber(count);
|
|
13
|
+
const { trailing: _ignoredTrailing, ...restProps } = props;
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
BaseFilterChip,
|
|
16
|
+
{
|
|
17
|
+
ref,
|
|
18
|
+
"data-slot": "filter-chip",
|
|
19
|
+
label,
|
|
20
|
+
...restProps,
|
|
21
|
+
trailing: countText
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
FilterChip
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=chunk-OASM53LL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/filter-bar/filter-chip.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * filter-chip.tsx — `@elabs-ai/components-data`'s removable filter chip, with an optional\n * secondary count (\"excluded 1,204\") for `ProcessFilterBar` (RM-056, #221).\n *\n * Deliberately a thin COMPOSING wrapper around `@elabs-ai/components-ui`'s `FilterChip`\n * (`view-toolbar.tsx`, #331) rather than a second implementation — the dedupe\n * audit found the real, accessible, whole-chip-as-button `FilterChip` already\n * lives there (WCAG 2.5.8 target size, WCAG 2.5.3 \"Remove filter: <label>\"\n * accessible name). Building a second one in `packages/data` would duplicate\n * that work; this wrapper reuses it and passes `count`/`countLabel` through\n * the base component's `trailing` slot (#284) — a second, non-shrinking text\n * element, distinct from the truncatable `label` — so the count reaches the\n * chip's ACCESSIBLE NAME (screen readers hear \"Remove filter: Status: Failed\n * · excluded 1,204\") AND survives truncation in the visible chip, instead of\n * being folded into the one string CSS `truncate` can clip from the tail.\n */\nimport { forwardRef } from \"react\";\nimport {\n FilterChip as BaseFilterChip,\n type FilterChipProps as BaseFilterChipProps,\n useLocale,\n} from \"@elabs-ai/components-ui\";\n\n// `trailing` is omitted alongside `label`: this wrapper derives its OWN\n// `trailing` from `count`/`countLabel`. The `Omit` blocks `trailing` written\n// as an object LITERAL, but TypeScript's excess-property check does not\n// apply to a spread of an already-declared variable — `const extra = {\n// trailing: \"x\" }; <FilterChip {...extra} />` still type-checks, and the\n// value would land in `props` regardless of JSX attribute order (PR #408\n// review round 2). So the `Omit` is necessary but not sufficient: below,\n// `trailing` is also stripped from `props` at RUNTIME before it reaches the\n// base component, so a caller-supplied `trailing` — literal or\n// spread-smuggled — can never win at render, the same advertised-but-inert\n// failure mode #382/#284-round-1 already closed elsewhere in the repo\n// (`ContextRail`'s `children` omission).\nexport interface FilterChipProps extends Omit<BaseFilterChipProps, \"label\" | \"trailing\"> {\n /**\n * Label-in-value text — `\"Status: Failed\"`, never `\"Status = failed\"` and\n * never a bare `\"Failed\"`. Same contract as the base `FilterChip`.\n */\n label: string;\n /**\n * How many records this active filter excluded (or matched) — rendered as a\n * secondary, locale-formatted segment alongside `label`. Omit for a bare\n * chip with no count.\n */\n count?: number;\n /**\n * The word placed before the formatted count, e.g. `\"excluded\"` →\n * `\"excluded 1,204\"`. Omitted by default: a bare `count` renders as just the\n * formatted number.\n */\n countLabel?: string;\n}\n\n/**\n * A removable active-filter chip with an optional secondary count.\n *\n * `onRemove` stays REQUIRED (inherited from the base `FilterChip`, diverging\n * from this item's spec draft) — the whole chip IS the remove control, so a\n * chip with no removal affordance is a plain `Badge`, not this component.\n */\nexport const FilterChip = forwardRef<HTMLButtonElement, FilterChipProps>(function FilterChip(\n { label, count, countLabel, ...props },\n ref,\n) {\n const { formatNumber } = useLocale();\n const countText =\n count === undefined\n ? undefined\n : countLabel\n ? `${countLabel} ${formatNumber(count)}`\n : formatNumber(count);\n\n // Runtime guard (belt and braces alongside the `Omit` above): a caller can\n // still smuggle `trailing` into `props` through a spread of an\n // already-declared variable, which the type system cannot catch. Strip it\n // here so the derived count wins regardless of prop order.\n const { trailing: _ignoredTrailing, ...restProps } = props as Omit<BaseFilterChipProps, \"label\">;\n\n return (\n <BaseFilterChip\n ref={ref}\n data-slot=\"filter-chip\"\n label={label}\n {...restProps}\n trailing={countText}\n />\n );\n});\n"],"mappings":";;;AAkBA,SAAS,kBAAkB;AAC3B;AAAA,EACE,cAAc;AAAA,EAEd;AAAA,OACK;AA4DH;AAnBG,IAAM,aAAa,WAA+C,SAASA,YAChF,EAAE,OAAO,OAAO,YAAY,GAAG,MAAM,GACrC,KACA;AACA,QAAM,EAAE,aAAa,IAAI,UAAU;AACnC,QAAM,YACJ,UAAU,SACN,SACA,aACE,GAAG,UAAU,IAAI,aAAa,KAAK,CAAC,KACpC,aAAa,KAAK;AAM1B,QAAM,EAAE,UAAU,kBAAkB,GAAG,UAAU,IAAI;AAErD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV;AAAA,MACC,GAAG;AAAA,MACJ,UAAU;AAAA;AAAA,EACZ;AAEJ,CAAC;","names":["FilterChip"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
barGeometry
|
|
4
|
+
} from "./chunk-VCLA45IC.js";
|
|
5
|
+
|
|
6
|
+
// src/data-table/cells/bar-cell.tsx
|
|
7
|
+
import { forwardRef } from "react";
|
|
8
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var BAR_CELL_POSITIVE_COLOR = "var(--chart-div-pos-2)";
|
|
11
|
+
var BAR_CELL_NEGATIVE_COLOR = "var(--chart-div-neg-2)";
|
|
12
|
+
var BarCell = forwardRef(function BarCell2({
|
|
13
|
+
value,
|
|
14
|
+
label,
|
|
15
|
+
domain,
|
|
16
|
+
variant = "regular",
|
|
17
|
+
track = false,
|
|
18
|
+
fillColor,
|
|
19
|
+
negativeColor = true,
|
|
20
|
+
labelWidth,
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}, ref) {
|
|
24
|
+
const geometry = barGeometry(value ?? Number.NaN, domain);
|
|
25
|
+
const fill = geometry.negative && negativeColor ? BAR_CELL_NEGATIVE_COLOR : fillColor ?? BAR_CELL_POSITIVE_COLOR;
|
|
26
|
+
const straddlesZero = domain[0] < 0 && domain[1] > 0;
|
|
27
|
+
const slim = variant === "slim";
|
|
28
|
+
return /* @__PURE__ */ jsxs(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
ref,
|
|
32
|
+
"data-slot": "bar-cell",
|
|
33
|
+
"data-variant": variant,
|
|
34
|
+
"data-negative": geometry.negative || void 0,
|
|
35
|
+
className: cn(
|
|
36
|
+
"flex min-w-0",
|
|
37
|
+
slim ? "flex-col items-stretch gap-1" : "items-center gap-2",
|
|
38
|
+
className
|
|
39
|
+
),
|
|
40
|
+
...props,
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
"span",
|
|
44
|
+
{
|
|
45
|
+
"data-slot": "bar-cell-value",
|
|
46
|
+
className: cn("shrink-0 tabular-nums", !slim && "whitespace-nowrap text-end"),
|
|
47
|
+
style: !slim && labelWidth ? { width: `${labelWidth}ch` } : void 0,
|
|
48
|
+
children: label
|
|
49
|
+
}
|
|
50
|
+
),
|
|
51
|
+
/* @__PURE__ */ jsxs(
|
|
52
|
+
"span",
|
|
53
|
+
{
|
|
54
|
+
"aria-hidden": "true",
|
|
55
|
+
"data-slot": "bar-cell-track",
|
|
56
|
+
className: cn(
|
|
57
|
+
// The track grows along the PARENT's main axis, which the variant
|
|
58
|
+
// flips: a regular bar sits beside its value (row -> `flex-1` takes
|
|
59
|
+
// the spare width), a slim bar sits under it (column -> `flex-1`
|
|
60
|
+
// would resolve the HEIGHT to flex-basis 0 and erase the mark, so it
|
|
61
|
+
// spans the width and keeps its own height instead).
|
|
62
|
+
"relative block min-w-8 rounded-sm",
|
|
63
|
+
slim ? "h-1 w-full shrink-0" : "h-3 flex-1",
|
|
64
|
+
track && "bg-muted"
|
|
65
|
+
),
|
|
66
|
+
children: [
|
|
67
|
+
straddlesZero && /* @__PURE__ */ jsx(
|
|
68
|
+
"span",
|
|
69
|
+
{
|
|
70
|
+
"data-slot": "bar-cell-zero",
|
|
71
|
+
className: "absolute -inset-y-0.5 w-px bg-border-strong",
|
|
72
|
+
style: { insetInlineStart: `${geometry.zero}%` }
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
/* @__PURE__ */ jsx(
|
|
76
|
+
"span",
|
|
77
|
+
{
|
|
78
|
+
"data-slot": "bar-cell-bar",
|
|
79
|
+
className: "absolute inset-y-0 rounded-sm",
|
|
80
|
+
style: {
|
|
81
|
+
insetInlineStart: `${geometry.start}%`,
|
|
82
|
+
width: `${geometry.size}%`,
|
|
83
|
+
backgroundColor: fill
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
BAR_CELL_POSITIVE_COLOR,
|
|
97
|
+
BAR_CELL_NEGATIVE_COLOR,
|
|
98
|
+
BarCell
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-SK6347WB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table/cells/bar-cell.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { barGeometry } from \"../cell-scales\";\n\n/**\n * A positive bar's fill: the diverging ramp's positive end — the brand-hue arm\n * (117.6) that MIRRORS `BAR_CELL_NEGATIVE_COLOR`, so the two arms of a signed\n * column are drawn at the same weight instead of one shouting over the other.\n *\n * NOT `--chart-1` (the first series colour) any more. `--chart-1` is a\n * CATEGORICAL token — it answers \"which series\", it carries no contrast\n * guarantee, and on `light` it is a signed-off 1.4.11 exemption (see\n * `CHART_1411_EXEMPT` in packages/tokens/src/charts-contrast.test.ts): it\n * measures 1.42:1 on `--card` and 1.30:1 on the bar's own `bg-muted` track, so\n * the bar read as a wash exactly where its LENGTH is the whole message. The\n * diverging ramp is the one gated ≥3:1 in every theme, including its mid step —\n * measured 8.99:1 on `--card` in light and dark alike.\n */\nexport const BAR_CELL_POSITIVE_COLOR = \"var(--chart-div-pos-2)\";\n/** A negative bar's fill: the diverging ramp's negative end. */\nexport const BAR_CELL_NEGATIVE_COLOR = \"var(--chart-div-neg-2)\";\n\nexport interface BarCellProps extends HTMLAttributes<HTMLDivElement> {\n /** The number the bar draws; `null` draws no bar. */\n value: number | null;\n /** The printed (formatted) value. Always in the accessible tree. */\n label: string;\n /** `[lo, hi]` of the track; always contains zero (see `barDomain`). */\n domain: readonly [number, number];\n /** `\"regular\"` (default): bar beside the value; `\"slim\"`: a thin bar under it. */\n variant?: \"regular\" | \"slim\";\n /** Paint the grey remainder of the track. */\n track?: boolean;\n /** A category colour (`var(--chart-N)`) that replaces the positive fill. */\n fillColor?: string | null;\n /** Paint a negative value in the negative token. Default `true`. */\n negativeColor?: boolean;\n /**\n * Room reserved for the printed value, in `ch` (`labelBoxCh` over the whole\n * column). Every bar in a column then shares one track length, so bar lengths\n * compare down the column and a diverging column's zero rule keeps one x.\n * Unset (or `\"slim\"`, where the bar sits under the value) leaves the value\n * box to size itself.\n */\n labelWidth?: number;\n}\n\n/**\n * An in-cell bar (CSS only). The bar grows from zero — toward inline-end for a\n * positive value, toward inline-start for a negative one — inside a track that\n * spans the column's shared domain. The value stays printed beside it.\n */\nexport const BarCell = forwardRef<HTMLDivElement, BarCellProps>(function BarCell(\n {\n value,\n label,\n domain,\n variant = \"regular\",\n track = false,\n fillColor,\n negativeColor = true,\n labelWidth,\n className,\n ...props\n },\n ref,\n) {\n const geometry = barGeometry(value ?? Number.NaN, domain);\n const fill =\n geometry.negative && negativeColor\n ? BAR_CELL_NEGATIVE_COLOR\n : (fillColor ?? BAR_CELL_POSITIVE_COLOR);\n const straddlesZero = domain[0] < 0 && domain[1] > 0;\n const slim = variant === \"slim\";\n return (\n <div\n ref={ref}\n data-slot=\"bar-cell\"\n data-variant={variant}\n data-negative={geometry.negative || undefined}\n className={cn(\n \"flex min-w-0\",\n slim ? \"flex-col items-stretch gap-1\" : \"items-center gap-2\",\n className,\n )}\n {...props}\n >\n {/*\n The value box is a column-wide reservation (`labelWidth`), never this\n row's own text width: `shrink-0` plus an explicit `width` keeps every\n track in the column the same length, whatever the number reads.\n */}\n <span\n data-slot=\"bar-cell-value\"\n className={cn(\"shrink-0 tabular-nums\", !slim && \"whitespace-nowrap text-end\")}\n style={!slim && labelWidth ? { width: `${labelWidth}ch` } : undefined}\n >\n {label}\n </span>\n <span\n aria-hidden=\"true\"\n data-slot=\"bar-cell-track\"\n className={cn(\n // The track grows along the PARENT's main axis, which the variant\n // flips: a regular bar sits beside its value (row -> `flex-1` takes\n // the spare width), a slim bar sits under it (column -> `flex-1`\n // would resolve the HEIGHT to flex-basis 0 and erase the mark, so it\n // spans the width and keeps its own height instead).\n \"relative block min-w-8 rounded-sm\",\n slim ? \"h-1 w-full shrink-0\" : \"h-3 flex-1\",\n track && \"bg-muted\",\n )}\n >\n {straddlesZero && (\n <span\n data-slot=\"bar-cell-zero\"\n className=\"absolute -inset-y-0.5 w-px bg-border-strong\"\n style={{ insetInlineStart: `${geometry.zero}%` }}\n />\n )}\n <span\n data-slot=\"bar-cell-bar\"\n className=\"absolute inset-y-0 rounded-sm\"\n style={{\n insetInlineStart: `${geometry.start}%`,\n width: `${geometry.size}%`,\n backgroundColor: fill,\n }}\n />\n </span>\n </div>\n );\n});\n"],"mappings":";;;;;;AAEA,SAAS,kBAAuC;AAChD,SAAS,UAAU;AA2Fb,cAOA,YAPA;AA1EC,IAAM,0BAA0B;AAEhC,IAAM,0BAA0B;AAgChC,IAAM,UAAU,WAAyC,SAASA,SACvE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GACA,KACA;AACA,QAAM,WAAW,YAAY,SAAS,OAAO,KAAK,MAAM;AACxD,QAAM,OACJ,SAAS,YAAY,gBACjB,0BACC,aAAa;AACpB,QAAM,gBAAgB,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;AACnD,QAAM,OAAO,YAAY;AACzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,iBAAe,SAAS,YAAY;AAAA,MACpC,WAAW;AAAA,QACT;AAAA,QACA,OAAO,iCAAiC;AAAA,QACxC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAOJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,yBAAyB,CAAC,QAAQ,4BAA4B;AAAA,YAC5E,OAAO,CAAC,QAAQ,aAAa,EAAE,OAAO,GAAG,UAAU,KAAK,IAAI;AAAA,YAE3D;AAAA;AAAA,QACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,eAAY;AAAA,YACZ,aAAU;AAAA,YACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAMT;AAAA,cACA,OAAO,wBAAwB;AAAA,cAC/B,SAAS;AAAA,YACX;AAAA,YAEC;AAAA,+BACC;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAU;AAAA,kBACV,OAAO,EAAE,kBAAkB,GAAG,SAAS,IAAI,IAAI;AAAA;AAAA,cACjD;AAAA,cAEF;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAU;AAAA,kBACV,OAAO;AAAA,oBACL,kBAAkB,GAAG,SAAS,KAAK;AAAA,oBACnC,OAAO,GAAG,SAAS,IAAI;AAAA,oBACvB,iBAAiB;AAAA,kBACnB;AAAA;AAAA,cACF;AAAA;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ,CAAC;","names":["BarCell"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/data-table/column-meta.ts
|
|
4
|
+
var COMPACT_THRESHOLD = 1e3;
|
|
5
|
+
function formatCellValue(value, format, formatNumber) {
|
|
6
|
+
if (value === null || value === void 0) return "";
|
|
7
|
+
if (typeof value !== "number") return String(value);
|
|
8
|
+
if (!Number.isFinite(value)) return "";
|
|
9
|
+
if (!format) return formatNumber(value);
|
|
10
|
+
const style = format.style ?? "number";
|
|
11
|
+
const options = {};
|
|
12
|
+
if (style === "currency") {
|
|
13
|
+
options.style = "currency";
|
|
14
|
+
options.currency = format.currency ?? "USD";
|
|
15
|
+
} else if (style === "percent") {
|
|
16
|
+
options.style = "percent";
|
|
17
|
+
}
|
|
18
|
+
const abbreviate = style !== "percent" && (format.abbreviate === true || format.abbreviate !== false && Math.abs(value) >= COMPACT_THRESHOLD);
|
|
19
|
+
if (abbreviate) {
|
|
20
|
+
options.notation = "compact";
|
|
21
|
+
options.compactDisplay = "short";
|
|
22
|
+
}
|
|
23
|
+
const exact = style === "number" && format.abbreviate === false;
|
|
24
|
+
const digits = format.decimals ?? (exact ? void 0 : 1);
|
|
25
|
+
if (digits !== void 0) {
|
|
26
|
+
options.maximumFractionDigits = digits;
|
|
27
|
+
if (format.optionalDecimals === false) options.minimumFractionDigits = digits;
|
|
28
|
+
}
|
|
29
|
+
if (format.grouping === false) options.useGrouping = false;
|
|
30
|
+
const parens = format.sign === "parens";
|
|
31
|
+
if (format.sign === "always") options.signDisplay = "always";
|
|
32
|
+
if (parens) options.signDisplay = "never";
|
|
33
|
+
const body = formatNumber(value, options);
|
|
34
|
+
const signed = parens && value < 0 ? `(${body})` : body;
|
|
35
|
+
return `${format.prefix ?? ""}${signed}${format.suffix ?? ""}`;
|
|
36
|
+
}
|
|
37
|
+
function resolveShowAt(showAt, breakpoint) {
|
|
38
|
+
if (showAt === void 0) return true;
|
|
39
|
+
if (typeof showAt === "boolean") return showAt;
|
|
40
|
+
return breakpoint === "narrow" ? showAt.narrow ?? showAt.base : showAt.base;
|
|
41
|
+
}
|
|
42
|
+
function columnSizeStyle(meta) {
|
|
43
|
+
if (!meta || meta.width === void 0 && meta.minWidth === void 0 && !meta.style) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
...meta.width !== void 0 ? { width: `${Math.max(0, meta.width)}%` } : null,
|
|
48
|
+
...meta.minWidth !== void 0 ? { minWidth: Math.max(0, meta.minWidth) } : null,
|
|
49
|
+
...meta.style
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
formatCellValue,
|
|
55
|
+
resolveShowAt,
|
|
56
|
+
columnSizeStyle
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-TBB33PHQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table/column-meta.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { RowData } from \"@tanstack/react-table\";\nimport type { ColorScaleDomain } from \"@elabs-ai/components-ui\";\nimport type { DataTableBreakpoint } from \"./use-table-breakpoint\";\n\n/**\n * column-meta.ts — the typed `columnDef.meta` contract `DataTable` reads.\n *\n * `numeric` / `align` (#69) style a column; everything else is the editorial\n * presentation layer (RM-123): an in-cell visual, a number format, categorical\n * colouring, per-breakpoint visibility and column sizing. Every key is optional\n * and a column without them renders exactly as before.\n *\n * `data` never imports `@elabs-ai/components-charts` (one-way dependency\n * graph), so the number-format shape below is a STRUCTURAL twin of charts'\n * `ChartValueFormatSpec` (RM-109): a spec written for a chart type-checks here\n * unchanged, and formats the same way.\n */\n\n/**\n * A number format — the same object shape as charts' `ChartValueFormatSpec`.\n *\n * - `style`: `\"number\"` (default), `\"currency\"`, or `\"percent\"` (fraction in,\n * `%` out).\n * - `decimals`: maximum fraction digits. `optionalDecimals: false` makes it\n * the minimum too.\n * - `abbreviate`: `true` always compact (`1.5K`), `false` never, `\"auto\"`\n * (default) from 1,000 up. Percent is never compacted.\n * - `sign`: `\"auto\"` (minus only), `\"always\"` (`+`/`-`), `\"parens\"` (`(12)`).\n * - `prefix` / `suffix`: literal text around the number.\n * - `grouping: false` drops the thousands separator.\n * - `currency`: ISO 4217 code for `style: \"currency\"` (default `\"USD\"`).\n */\nexport interface DataTableValueFormatSpec {\n style?: \"number\" | \"currency\" | \"percent\";\n decimals?: number;\n optionalDecimals?: boolean;\n abbreviate?: boolean | \"auto\";\n sign?: \"auto\" | \"always\" | \"parens\";\n prefix?: string;\n suffix?: string;\n grouping?: boolean;\n currency?: string;\n}\n\n/** An in-cell bar: the value as a horizontal bar from zero. */\nexport interface DataTableBarVisual {\n kind: \"bar\";\n /** `\"regular\"` (default) sits beside the value; `\"slim\"` is a thin rule under it. */\n style?: \"regular\" | \"slim\";\n /** Paint the grey remainder of the range behind the bar. */\n track?: boolean;\n /**\n * The range a full-width bar spans. `\"column\"` (default): this column's own\n * min / max; `\"table\"`: shared by every bar column with `range: \"table\"`;\n * `[min, max]`: fixed. Zero is always inside the range.\n */\n range?: readonly [min: number, max: number] | \"column\" | \"table\";\n /** Row key whose category picks the bar colour (`--chart-1…12`). */\n colorBy?: string;\n /**\n * The `colorBy` key's title, or `false` to draw no key. Default `true`: a\n * category carried by hue alone is unreadable without one (WCAG 1.4.1), so\n * the key is opt-OUT — turn it off only when the table prints the category\n * in a column of its own.\n */\n legend?: string | boolean;\n /** Paint negative values in the negative token. Default `true`. */\n negative?: boolean;\n}\n\n/** An in-cell sparkline across several numeric row keys (one point per key). */\nexport interface DataTableSparklineVisual {\n kind: \"sparkline\";\n /** Row keys, in x order. */\n keys: readonly string[];\n /** Fill the area under the line. */\n fill?: boolean;\n /** `\"ends\"` prints the first and last values beside the line. Default `\"none\"`. */\n labels?: \"ends\" | \"none\";\n /** Drawing height in px. Default 24. */\n height?: number;\n /** `\"cell\"` (default): this row's own min / max; `\"column\"`: shared by every row. */\n range?: \"cell\" | \"column\";\n}\n\n/** In-cell mini columns across several numeric row keys. */\nexport interface DataTableColumnsVisual {\n kind: \"columns\";\n keys: readonly string[];\n /** Drawing height in px. Default 24. */\n height?: number;\n /** `\"cell\"` (default): this row's own max; `\"column\"`: shared by every row. */\n range?: \"cell\" | \"column\";\n}\n\n/**\n * A heatmap colour scale. Resolved by `colorScaleFor` (`@elabs-ai/components-ui`),\n * so the colours are `--chart-seq-*` / `--chart-div-*` ramp tokens.\n */\nexport interface DataTableHeatmapScale {\n /** `\"stepped\"` cuts countable classes; `\"continuous\"` snaps to the nearest ramp step. */\n type: \"stepped\" | \"continuous\";\n /** How many classes (stepped) or Jenks classes (continuous `jenks`). Default 5. */\n steps?: number;\n /**\n * `\"linear\"` (default) equal widths; `\"quantile\"` equal counts; `\"jenks\"`\n * natural breaks; `\"custom\"` the `breaks` you pass (always stepped).\n */\n method?: \"linear\" | \"quantile\" | \"jenks\" | \"custom\";\n /** `method: \"custom\"` only: the inner thresholds, ascending. */\n breaks?: readonly number[];\n /** `[min, max]`, or `[min, center, max]` to pin the ramp's middle colour. */\n domain?: ColorScaleDomain;\n /** Default `\"diverging\"` when `domain` has a centre, else `\"sequential\"`. */\n palette?: \"sequential\" | \"diverging\";\n}\n\n/** The cell's background is the value's ramp colour. */\nexport interface DataTableHeatmapVisual {\n kind: \"heatmap\";\n scale: DataTableHeatmapScale;\n /** Hide the printed value visually; screen readers, sorting and copy still read it. */\n hideValue?: boolean;\n /**\n * Print the scale's colour key above the table. `true` titles it with the\n * column header; a string is the title (use it when one heatmap spans several\n * columns — the key is printed once per shared scale).\n */\n legend?: boolean | string;\n}\n\nexport type DataTableCellVisual =\n | DataTableBarVisual\n | DataTableSparklineVisual\n | DataTableColumnsVisual\n | DataTableHeatmapVisual;\n\n/**\n * Categorical conditional formatting: the category in row key `key` picks a\n * `--chart-1…12` colour, painted as a background wash or as text ink, on this\n * column's cell or on the whole row.\n */\nexport interface DataTableColorBy {\n key: string;\n target: \"background\" | \"text\";\n /** Default `\"cell\"`. */\n scope?: \"cell\" | \"row\";\n /**\n * The colour key's title, or `false` to draw none. Default `true` — see\n * `DataTableBarVisual.legend`.\n */\n legend?: string | boolean;\n}\n\n/**\n * Show a column at a table breakpoint. `true` / `false` for every width, or\n * `{ base, narrow }`: `base` is the wide value, `narrow` applies below 450 px.\n */\nexport type DataTableShowAt = boolean | { base: boolean; narrow?: boolean };\n\n/** `markdown: true` or options. Images are off unless `images: true`. */\nexport interface DataTableMarkdownOptions {\n images?: boolean;\n}\n\n/**\n * `DataTable`'s `columnDef.meta` contract, read by the header, body, card and\n * skeleton cell renderers. Set `numeric: true` on a column to get\n * `tabular-nums` + end-alignment on both the `<th>` and every `<td>`\n * (including the loading skeleton) for free.\n */\nexport interface DataTableColumnMeta {\n /** Numeric column: tabular figures + end alignment on header and cells. */\n numeric?: boolean;\n /**\n * Explicit alignment override for when `numeric` isn't the right cue (or\n * to align a non-numeric column). Independent of `numeric` — `numeric`\n * alone still drives `tabular-nums` even when `align` overrides the\n * alignment away from `\"end\"`.\n */\n align?: \"start\" | \"center\" | \"end\";\n /**\n * An in-cell visual (bar, sparkline, mini columns, heatmap). The cell keeps\n * its value as text — visually hidden only with `hideValue` — so sorting,\n * copy and screen readers still read the number. Replaces the column's\n * `cell` renderer.\n */\n visual?: DataTableCellVisual;\n /** Number format for the printed value (the charts `valueFormat` object shape). */\n format?: DataTableValueFormatSpec;\n /** Categorical colouring driven by another row key. */\n colorBy?: DataTableColorBy;\n /** Per-breakpoint visibility; render-only, never written to `columnVisibility`. */\n showAt?: DataTableShowAt;\n /** Column width as a percentage of the table width. */\n width?: number;\n /** Minimum column width in px. */\n minWidth?: number;\n /** Extra inline style on this column's header and body cells. */\n style?: CSSProperties;\n /** Render a string value as safe inline markdown (bold, italic, links, `sup`, `code`). */\n markdown?: boolean | DataTableMarkdownOptions;\n}\n\ndeclare module \"@tanstack/react-table\" {\n // `TData`/`TValue` must stay in the signature to match the interface being\n // augmented, even though `DataTableColumnMeta` (deliberately) doesn't use\n // them; the empty extends-body is how TanStack's own module-augmentation\n // pattern for `ColumnMeta` is documented.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type\n interface ColumnMeta<TData extends RowData, TValue> extends DataTableColumnMeta {}\n}\n\n/** Charts' compaction threshold (`COMPACT_THRESHOLD`), mirrored. */\nconst COMPACT_THRESHOLD = 1000;\n\n/**\n * The printed text for a cell value. Numbers go through `format` (or the\n * locale's plain number format); anything else is stringified; nullish is `\"\"`.\n */\nexport function formatCellValue(\n value: unknown,\n format: DataTableValueFormatSpec | undefined,\n formatNumber: (n: number, opts?: Intl.NumberFormatOptions) => string,\n): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value !== \"number\") return String(value);\n if (!Number.isFinite(value)) return \"\";\n if (!format) return formatNumber(value);\n const style = format.style ?? \"number\";\n const options: Intl.NumberFormatOptions = {};\n if (style === \"currency\") {\n options.style = \"currency\";\n options.currency = format.currency ?? \"USD\";\n } else if (style === \"percent\") {\n options.style = \"percent\";\n }\n const abbreviate =\n style !== \"percent\" &&\n (format.abbreviate === true ||\n (format.abbreviate !== false && Math.abs(value) >= COMPACT_THRESHOLD));\n if (abbreviate) {\n options.notation = \"compact\";\n options.compactDisplay = \"short\";\n }\n const exact = style === \"number\" && format.abbreviate === false;\n const digits = format.decimals ?? (exact ? undefined : 1);\n if (digits !== undefined) {\n options.maximumFractionDigits = digits;\n if (format.optionalDecimals === false) options.minimumFractionDigits = digits;\n }\n if (format.grouping === false) options.useGrouping = false;\n const parens = format.sign === \"parens\";\n if (format.sign === \"always\") options.signDisplay = \"always\";\n if (parens) options.signDisplay = \"never\";\n const body = formatNumber(value, options);\n const signed = parens && value < 0 ? `(${body})` : body;\n return `${format.prefix ?? \"\"}${signed}${format.suffix ?? \"\"}`;\n}\n\n/** Whether a column is shown at `breakpoint` (no `showAt` → always). */\nexport function resolveShowAt(\n showAt: DataTableShowAt | undefined,\n breakpoint: DataTableBreakpoint,\n): boolean {\n if (showAt === undefined) return true;\n if (typeof showAt === \"boolean\") return showAt;\n return breakpoint === \"narrow\" ? (showAt.narrow ?? showAt.base) : showAt.base;\n}\n\n/** The inline style for `meta.width` (%), `meta.minWidth` (px) and `meta.style`. */\nexport function columnSizeStyle(meta: DataTableColumnMeta | undefined): CSSProperties | undefined {\n if (!meta || (meta.width === undefined && meta.minWidth === undefined && !meta.style)) {\n return undefined;\n }\n return {\n ...(meta.width !== undefined ? { width: `${Math.max(0, meta.width)}%` } : null),\n ...(meta.minWidth !== undefined ? { minWidth: Math.max(0, meta.minWidth) } : null),\n ...meta.style,\n };\n}\n"],"mappings":";;;AAuNA,IAAM,oBAAoB;AAMnB,SAAS,gBACd,OACA,QACA,cACQ;AACR,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,CAAC,OAAQ,QAAO,aAAa,KAAK;AACtC,QAAM,QAAQ,OAAO,SAAS;AAC9B,QAAM,UAAoC,CAAC;AAC3C,MAAI,UAAU,YAAY;AACxB,YAAQ,QAAQ;AAChB,YAAQ,WAAW,OAAO,YAAY;AAAA,EACxC,WAAW,UAAU,WAAW;AAC9B,YAAQ,QAAQ;AAAA,EAClB;AACA,QAAM,aACJ,UAAU,cACT,OAAO,eAAe,QACpB,OAAO,eAAe,SAAS,KAAK,IAAI,KAAK,KAAK;AACvD,MAAI,YAAY;AACd,YAAQ,WAAW;AACnB,YAAQ,iBAAiB;AAAA,EAC3B;AACA,QAAM,QAAQ,UAAU,YAAY,OAAO,eAAe;AAC1D,QAAM,SAAS,OAAO,aAAa,QAAQ,SAAY;AACvD,MAAI,WAAW,QAAW;AACxB,YAAQ,wBAAwB;AAChC,QAAI,OAAO,qBAAqB,MAAO,SAAQ,wBAAwB;AAAA,EACzE;AACA,MAAI,OAAO,aAAa,MAAO,SAAQ,cAAc;AACrD,QAAM,SAAS,OAAO,SAAS;AAC/B,MAAI,OAAO,SAAS,SAAU,SAAQ,cAAc;AACpD,MAAI,OAAQ,SAAQ,cAAc;AAClC,QAAM,OAAO,aAAa,OAAO,OAAO;AACxC,QAAM,SAAS,UAAU,QAAQ,IAAI,IAAI,IAAI,MAAM;AACnD,SAAO,GAAG,OAAO,UAAU,EAAE,GAAG,MAAM,GAAG,OAAO,UAAU,EAAE;AAC9D;AAGO,SAAS,cACd,QACA,YACS;AACT,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI,OAAO,WAAW,UAAW,QAAO;AACxC,SAAO,eAAe,WAAY,OAAO,UAAU,OAAO,OAAQ,OAAO;AAC3E;AAGO,SAAS,gBAAgB,MAAkE;AAChG,MAAI,CAAC,QAAS,KAAK,UAAU,UAAa,KAAK,aAAa,UAAa,CAAC,KAAK,OAAQ;AACrF,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC,IAAI,IAAI;AAAA,IAC1E,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,IAAI,GAAG,KAAK,QAAQ,EAAE,IAAI;AAAA,IAC7E,GAAG,KAAK;AAAA,EACV;AACF;","names":[]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/data-table/cell-scales.ts
|
|
4
|
+
import { colorScaleFor } from "@elabs-ai/components-ui";
|
|
5
|
+
function finiteNumbers(values) {
|
|
6
|
+
return values.filter((v) => typeof v === "number" && Number.isFinite(v));
|
|
7
|
+
}
|
|
8
|
+
function extentOf(values) {
|
|
9
|
+
const nums = finiteNumbers(values);
|
|
10
|
+
if (nums.length === 0) return null;
|
|
11
|
+
return { min: Math.min(...nums), max: Math.max(...nums) };
|
|
12
|
+
}
|
|
13
|
+
function unionExtent(a, b) {
|
|
14
|
+
if (!a) return b;
|
|
15
|
+
if (!b) return a;
|
|
16
|
+
return { min: Math.min(a.min, b.min), max: Math.max(a.max, b.max) };
|
|
17
|
+
}
|
|
18
|
+
function barDomain(range, column, table) {
|
|
19
|
+
const source = Array.isArray(range) && range.length === 2 ? { min: Math.min(range[0], range[1]), max: Math.max(range[0], range[1]) } : range === "table" ? table : column;
|
|
20
|
+
return [Math.min(0, source?.min ?? 0), Math.max(0, source?.max ?? 0)];
|
|
21
|
+
}
|
|
22
|
+
function barGeometry(value, domain) {
|
|
23
|
+
const [lo, hi] = domain;
|
|
24
|
+
const span = hi - lo;
|
|
25
|
+
if (!Number.isFinite(value) || span <= 0) return { start: 0, size: 0, zero: 0, negative: false };
|
|
26
|
+
const pct = (v) => Math.min(100, Math.max(0, (v - lo) / span * 100));
|
|
27
|
+
const zero = pct(0);
|
|
28
|
+
const end = pct(value);
|
|
29
|
+
return {
|
|
30
|
+
start: Math.min(zero, end),
|
|
31
|
+
size: Math.max(0, Math.abs(end - zero)),
|
|
32
|
+
zero,
|
|
33
|
+
negative: value < 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function labelBoxCh(labels) {
|
|
37
|
+
let widest = 0;
|
|
38
|
+
for (const label of labels) widest = Math.max(widest, [...label].length);
|
|
39
|
+
return widest;
|
|
40
|
+
}
|
|
41
|
+
function seriesEnds(values) {
|
|
42
|
+
const present = values.filter((v) => v !== null);
|
|
43
|
+
const first = present[0];
|
|
44
|
+
const last = present[present.length - 1];
|
|
45
|
+
return first === void 0 || last === void 0 ? null : [first, last];
|
|
46
|
+
}
|
|
47
|
+
function seriesValues(original, keys) {
|
|
48
|
+
const record = original ?? {};
|
|
49
|
+
return keys.map((key) => {
|
|
50
|
+
const v = record[key];
|
|
51
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function heatmapColorScaleSpec(scale) {
|
|
55
|
+
const base = {
|
|
56
|
+
...scale.domain ? { domain: scale.domain } : null,
|
|
57
|
+
...scale.palette ? { palette: scale.palette } : null
|
|
58
|
+
};
|
|
59
|
+
const method = scale.method ?? "linear";
|
|
60
|
+
if (scale.type === "stepped" || method === "custom") {
|
|
61
|
+
return {
|
|
62
|
+
...base,
|
|
63
|
+
type: "stepped",
|
|
64
|
+
method: method === "linear" ? "equidistant" : method,
|
|
65
|
+
...scale.steps !== void 0 ? { steps: scale.steps } : null,
|
|
66
|
+
...scale.breaks ? { breaks: scale.breaks } : null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (method === "jenks") {
|
|
70
|
+
return { ...base, type: "continuous", method: "natural", steps: scale.steps ?? 5 };
|
|
71
|
+
}
|
|
72
|
+
if (method === "quantile") {
|
|
73
|
+
const steps = scale.steps ?? 5;
|
|
74
|
+
const quantile = steps <= 2 ? "median" : steps <= 4 ? "quartiles" : steps <= 5 ? "quintiles" : "deciles";
|
|
75
|
+
return { ...base, type: "continuous", method: quantile };
|
|
76
|
+
}
|
|
77
|
+
return { ...base, type: "continuous", method: "linear" };
|
|
78
|
+
}
|
|
79
|
+
function categoryValues(rows, key) {
|
|
80
|
+
return rows.map((row) => {
|
|
81
|
+
const v = (row.original ?? {})[key];
|
|
82
|
+
return typeof v === "string" || typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function categoryScale(rows, key) {
|
|
86
|
+
return colorScaleFor(categoryValues(rows, key), { type: "stepped", palette: "categorical" });
|
|
87
|
+
}
|
|
88
|
+
function computeColumnScales(columns, rows) {
|
|
89
|
+
const scales = /* @__PURE__ */ new Map();
|
|
90
|
+
let tableBarExtent = null;
|
|
91
|
+
const heatmapValues = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const { id, meta } of columns) {
|
|
93
|
+
if (!meta?.visual && !meta?.colorBy) continue;
|
|
94
|
+
const values = rows.map((row) => row.getValue(id));
|
|
95
|
+
const scale = { extent: extentOf(values) };
|
|
96
|
+
const visual = meta.visual;
|
|
97
|
+
if (visual?.kind === "bar" && visual.range === "table") {
|
|
98
|
+
tableBarExtent = unionExtent(tableBarExtent, scale.extent);
|
|
99
|
+
}
|
|
100
|
+
if (visual?.kind === "bar" && visual.colorBy) {
|
|
101
|
+
scale.barCategory = categoryScale(rows, visual.colorBy);
|
|
102
|
+
}
|
|
103
|
+
if (visual?.kind === "sparkline" || visual?.kind === "columns") {
|
|
104
|
+
scale.seriesExtent = extentOf(rows.flatMap((row) => seriesValues(row.original, visual.keys)));
|
|
105
|
+
}
|
|
106
|
+
if (visual?.kind === "heatmap") {
|
|
107
|
+
const group = JSON.stringify(visual.scale);
|
|
108
|
+
scale.heatmapGroup = group;
|
|
109
|
+
const pooled = heatmapValues.get(group) ?? [];
|
|
110
|
+
pooled.push(...finiteNumbers(values));
|
|
111
|
+
heatmapValues.set(group, pooled);
|
|
112
|
+
}
|
|
113
|
+
if (meta.colorBy) scale.category = categoryScale(rows, meta.colorBy.key);
|
|
114
|
+
scales.set(id, scale);
|
|
115
|
+
}
|
|
116
|
+
const heatmapScales = /* @__PURE__ */ new Map();
|
|
117
|
+
for (const { id, meta } of columns) {
|
|
118
|
+
const scale = scales.get(id);
|
|
119
|
+
if (!scale) continue;
|
|
120
|
+
if (meta?.visual?.kind === "bar") {
|
|
121
|
+
scale.barDomain = barDomain(meta.visual.range, scale.extent, tableBarExtent);
|
|
122
|
+
}
|
|
123
|
+
if (meta?.visual?.kind === "heatmap" && scale.heatmapGroup !== void 0) {
|
|
124
|
+
let shared = heatmapScales.get(scale.heatmapGroup);
|
|
125
|
+
if (!shared) {
|
|
126
|
+
shared = colorScaleFor(
|
|
127
|
+
heatmapValues.get(scale.heatmapGroup) ?? [],
|
|
128
|
+
heatmapColorScaleSpec(meta.visual.scale)
|
|
129
|
+
);
|
|
130
|
+
heatmapScales.set(scale.heatmapGroup, shared);
|
|
131
|
+
}
|
|
132
|
+
scale.heatmap = shared;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return scales;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
finiteNumbers,
|
|
140
|
+
extentOf,
|
|
141
|
+
unionExtent,
|
|
142
|
+
barDomain,
|
|
143
|
+
barGeometry,
|
|
144
|
+
labelBoxCh,
|
|
145
|
+
seriesEnds,
|
|
146
|
+
seriesValues,
|
|
147
|
+
heatmapColorScaleSpec,
|
|
148
|
+
computeColumnScales
|
|
149
|
+
};
|
|
150
|
+
//# sourceMappingURL=chunk-VCLA45IC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table/cell-scales.ts"],"sourcesContent":["/**\n * cell-scales.ts — the shared scales behind `DataTable`'s in-cell visuals\n * (RM-123). Pure: no React, no DOM.\n *\n * Every visual cell reads ONE scale per column, computed once over the full\n * data (never the current page, filter or sort), so a bar, a sparkline or a\n * heatmap colour means the same thing on every page and after every sort.\n * Colour decisions (heatmap ramps, categories) come from `colorScaleFor`\n * (`@elabs-ai/components-ui`) — the one value → colour-token scale `charts`,\n * `data` and `maps` share — so colours are `var(--chart-…)` references.\n */\nimport { colorScaleFor, type ColorScale, type ColorScaleSpec } from \"@elabs-ai/components-ui\";\nimport type { DataTableBarVisual, DataTableColumnMeta, DataTableHeatmapScale } from \"./column-meta\";\n\n/** `[min, max]` of the finite numbers in a set. */\nexport interface NumericExtent {\n min: number;\n max: number;\n}\n\n/** The finite numbers in `values`, ignoring everything else. */\nexport function finiteNumbers(values: readonly unknown[]): number[] {\n return values.filter((v): v is number => typeof v === \"number\" && Number.isFinite(v));\n}\n\n/** The extent of the finite numbers in `values`; `null` when there are none. */\nexport function extentOf(values: readonly unknown[]): NumericExtent | null {\n const nums = finiteNumbers(values);\n if (nums.length === 0) return null;\n return { min: Math.min(...nums), max: Math.max(...nums) };\n}\n\n/** The union of two extents (either may be `null`). */\nexport function unionExtent(\n a: NumericExtent | null,\n b: NumericExtent | null,\n): NumericExtent | null {\n if (!a) return b;\n if (!b) return a;\n return { min: Math.min(a.min, b.min), max: Math.max(a.max, b.max) };\n}\n\n/** A bar domain always contains zero, so a bar is always measured from zero. */\nexport function barDomain(\n range: DataTableBarVisual[\"range\"],\n column: NumericExtent | null,\n table: NumericExtent | null,\n): readonly [number, number] {\n const source =\n Array.isArray(range) && range.length === 2\n ? { min: Math.min(range[0], range[1]), max: Math.max(range[0], range[1]) }\n : range === \"table\"\n ? table\n : column;\n return [Math.min(0, source?.min ?? 0), Math.max(0, source?.max ?? 0)];\n}\n\n/**\n * Where a bar sits inside its track, as percentages of the track width:\n * `start` is the inline-start edge, `size` the length. Negative values grow\n * toward inline-start from zero. Both are clamped to 0…100.\n */\nexport function barGeometry(\n value: number,\n domain: readonly [number, number],\n): { start: number; size: number; zero: number; negative: boolean } {\n const [lo, hi] = domain;\n const span = hi - lo;\n if (!Number.isFinite(value) || span <= 0) return { start: 0, size: 0, zero: 0, negative: false };\n const pct = (v: number) => Math.min(100, Math.max(0, ((v - lo) / span) * 100));\n const zero = pct(0);\n const end = pct(value);\n return {\n start: Math.min(zero, end),\n size: Math.max(0, Math.abs(end - zero)),\n zero,\n negative: value < 0,\n };\n}\n\n/**\n * How much room a column reserves for a printed label, in `ch`: the longest\n * label's length in code points.\n *\n * A visual cell draws in what its printed value leaves over, so the text box\n * must be a COLUMN-wide constant. Sized per row instead, a row with a shorter\n * number gets a longer track and therefore draws a longer bar for a smaller\n * value, and a diverging column's zero rule lands on a different pixel in every\n * row. `tabular-nums` makes every digit exactly `1ch` and the separators\n * narrower, so a reservation of the longest label's character count holds it.\n */\nexport function labelBoxCh(labels: readonly string[]): number {\n let widest = 0;\n for (const label of labels) widest = Math.max(widest, [...label].length);\n return widest;\n}\n\n/** The first and last finite values of a series; `null` when it has none. */\nexport function seriesEnds(\n values: readonly (number | null)[],\n): readonly [first: number, last: number] | null {\n const present = values.filter((v): v is number => v !== null);\n const first = present[0];\n const last = present[present.length - 1];\n return first === undefined || last === undefined ? null : [first, last];\n}\n\n/** A row's numbers for `keys`, `null` where a key holds no finite number. */\nexport function seriesValues(original: unknown, keys: readonly string[]): (number | null)[] {\n const record = (original ?? {}) as Record<string, unknown>;\n return keys.map((key) => {\n const v = record[key];\n return typeof v === \"number\" && Number.isFinite(v) ? v : null;\n });\n}\n\n/** The `ColorScaleSpec` a heatmap scale asks `colorScaleFor` for. */\nexport function heatmapColorScaleSpec(scale: DataTableHeatmapScale): ColorScaleSpec {\n const base = {\n ...(scale.domain ? { domain: scale.domain } : null),\n ...(scale.palette ? { palette: scale.palette } : null),\n };\n const method = scale.method ?? \"linear\";\n if (scale.type === \"stepped\" || method === \"custom\") {\n return {\n ...base,\n type: \"stepped\",\n method: method === \"linear\" ? \"equidistant\" : method,\n ...(scale.steps !== undefined ? { steps: scale.steps } : null),\n ...(scale.breaks ? { breaks: scale.breaks } : null),\n };\n }\n if (method === \"jenks\") {\n return { ...base, type: \"continuous\", method: \"natural\", steps: scale.steps ?? 5 };\n }\n if (method === \"quantile\") {\n const steps = scale.steps ?? 5;\n const quantile =\n steps <= 2 ? \"median\" : steps <= 4 ? \"quartiles\" : steps <= 5 ? \"quintiles\" : \"deciles\";\n return { ...base, type: \"continuous\", method: quantile };\n }\n return { ...base, type: \"continuous\", method: \"linear\" };\n}\n\n/** Everything a column's visual cells share. Only the parts the column uses are set. */\nexport interface DataTableColumnScale {\n /** Extent of the column's own values. */\n extent: NumericExtent | null;\n /** Bar visual: the resolved `[lo, hi]` (always contains zero). */\n barDomain?: readonly [number, number];\n /** Bar `colorBy`: the category → colour scale. */\n barCategory?: ColorScale;\n /** Sparkline / columns: extent of every row's series values. */\n seriesExtent?: NumericExtent | null;\n /**\n * Heatmap: the value → ramp-colour scale. Heatmap columns with an identical\n * `scale` spec SHARE one scale over all their values (one heatmap across\n * several columns); `heatmapGroup` names it.\n */\n heatmap?: ColorScale;\n heatmapGroup?: string;\n /** `meta.colorBy`: the category → colour scale. */\n category?: ColorScale;\n}\n\n/** A minimal row: what `computeColumnScales` needs from a TanStack row. */\nexport interface ScaleRow {\n original: unknown;\n getValue: (columnId: string) => unknown;\n}\n\n/** A minimal column: its id and meta. */\nexport interface ScaleColumn {\n id: string;\n meta: DataTableColumnMeta | undefined;\n}\n\nfunction categoryValues(rows: readonly ScaleRow[], key: string): (string | number | null)[] {\n return rows.map((row) => {\n const v = ((row.original ?? {}) as Record<string, unknown>)[key];\n return typeof v === \"string\" || (typeof v === \"number\" && Number.isFinite(v)) ? v : null;\n });\n}\n\nfunction categoryScale(rows: readonly ScaleRow[], key: string): ColorScale {\n return colorScaleFor(categoryValues(rows, key), { type: \"stepped\", palette: \"categorical\" });\n}\n\n/**\n * One scale per column that needs one (a `visual` or a `colorBy`), over ALL\n * `rows`. Columns with neither are absent from the map.\n */\nexport function computeColumnScales(\n columns: readonly ScaleColumn[],\n rows: readonly ScaleRow[],\n): Map<string, DataTableColumnScale> {\n const scales = new Map<string, DataTableColumnScale>();\n let tableBarExtent: NumericExtent | null = null;\n const heatmapValues = new Map<string, number[]>();\n for (const { id, meta } of columns) {\n if (!meta?.visual && !meta?.colorBy) continue;\n const values = rows.map((row) => row.getValue(id));\n const scale: DataTableColumnScale = { extent: extentOf(values) };\n const visual = meta.visual;\n if (visual?.kind === \"bar\" && visual.range === \"table\") {\n tableBarExtent = unionExtent(tableBarExtent, scale.extent);\n }\n if (visual?.kind === \"bar\" && visual.colorBy) {\n scale.barCategory = categoryScale(rows, visual.colorBy);\n }\n if (visual?.kind === \"sparkline\" || visual?.kind === \"columns\") {\n scale.seriesExtent = extentOf(rows.flatMap((row) => seriesValues(row.original, visual.keys)));\n }\n if (visual?.kind === \"heatmap\") {\n const group = JSON.stringify(visual.scale);\n scale.heatmapGroup = group;\n const pooled = heatmapValues.get(group) ?? [];\n pooled.push(...finiteNumbers(values));\n heatmapValues.set(group, pooled);\n }\n if (meta.colorBy) scale.category = categoryScale(rows, meta.colorBy.key);\n scales.set(id, scale);\n }\n // Bar domains and heatmap scales resolve last: `range: \"table\"` needs every\n // bar column's extent, a heatmap group every member column's values.\n const heatmapScales = new Map<string, ColorScale>();\n for (const { id, meta } of columns) {\n const scale = scales.get(id);\n if (!scale) continue;\n if (meta?.visual?.kind === \"bar\") {\n scale.barDomain = barDomain(meta.visual.range, scale.extent, tableBarExtent);\n }\n if (meta?.visual?.kind === \"heatmap\" && scale.heatmapGroup !== undefined) {\n let shared = heatmapScales.get(scale.heatmapGroup);\n if (!shared) {\n shared = colorScaleFor(\n heatmapValues.get(scale.heatmapGroup) ?? [],\n heatmapColorScaleSpec(meta.visual.scale),\n );\n heatmapScales.set(scale.heatmapGroup, shared);\n }\n scale.heatmap = shared;\n }\n }\n return scales;\n}\n"],"mappings":"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","names":[]}
|