cortena-ui 1.3.0 → 1.3.1

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/dist/index.d.ts CHANGED
@@ -1108,7 +1108,10 @@ interface ExportSheet {
1108
1108
  records: ExportCell[][];
1109
1109
  }
1110
1110
  export declare function toSheet<Row extends RowData>(table: DataTableTable<Row>, rows?: DataTableRow<Row>[]): ExportSheet;
1111
- /** RFC 4180 text: comma separated, CRLF lines, quotes doubled. */
1111
+ /**
1112
+ * RFC 4180 text: comma separated, CRLF lines, quotes doubled. A cell that a
1113
+ * spreadsheet would read as a formula is prefixed with `'` so it stays text.
1114
+ */
1112
1115
  export declare function toCsv<Row extends RowData>(table: DataTableTable<Row>, rows?: DataTableRow<Row>[]): string;
1113
1116
  export declare function downloadCsv<Row extends RowData>(table: DataTableTable<Row>, fileName?: string, rows?: DataTableRow<Row>[]): void;
1114
1117
  /**
@@ -1351,7 +1354,7 @@ declare function AvatarFallback({ className, ...props }: Avatar$1.Fallback.Props
1351
1354
  //#endregion
1352
1355
  //#region src/components/badge.d.ts
1353
1356
  declare const badgeVariants: (props?: ({
1354
- variant?: "default" | "success" | "warn" | "destructive" | "danger" | "info" | "warning" | "neutral" | "secondary" | "outline" | null | undefined;
1357
+ variant?: "default" | "success" | "warn" | "destructive" | "danger" | "info" | "secondary" | "outline" | "warning" | "neutral" | null | undefined;
1355
1358
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
1356
1359
  interface BadgeProps extends React$1.ComponentProps<"span">, VariantProps<typeof badgeVariants> {}
1357
1360
  declare function Badge({ className, variant, ...props }: BadgeProps): React$1.JSX.Element;
@@ -1454,7 +1457,7 @@ declare function BreadcrumbEllipsis({ className, children, ...props }: React$1.C
1454
1457
  * rendered element, which is Base UI's equivalent.
1455
1458
  */
1456
1459
  declare const buttonVariants: (props?: ({
1457
- variant?: "link" | "destructive" | "secondary" | "outline" | "primary" | "ghost" | "soft" | null | undefined;
1460
+ variant?: "destructive" | "primary" | "secondary" | "outline" | "ghost" | "soft" | "link" | null | undefined;
1458
1461
  size?: "sm" | "md" | "lg" | "icon" | "icon-sm" | null | undefined;
1459
1462
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
1460
1463
  interface ButtonProps extends React$1.ComponentProps<typeof Button$1>, VariantProps<typeof buttonVariants> {}
package/dist/index.js CHANGED
@@ -950,14 +950,28 @@ function useNivoTheme() {
950
950
  }
951
951
  //#endregion
952
952
  //#region src/components/chart/nivo-charts.tsx
953
- const lazy = (load) => React.lazy(async () => ({ default: await load() }));
954
- const HeatMapSvg = lazy(async () => (await import("@nivo/heatmap")).ResponsiveHeatMap);
955
- const HeatMapCanvas = lazy(async () => (await import("@nivo/heatmap")).ResponsiveHeatMapCanvas);
956
- const CalendarSvg = lazy(async () => (await import("@nivo/calendar")).ResponsiveCalendar);
957
- const CalendarCanvas = lazy(async () => (await import("@nivo/calendar")).ResponsiveCalendarCanvas);
958
- const TreeMapSvg = lazy(async () => (await import("@nivo/treemap")).ResponsiveTreeMap);
959
- const TreeMapCanvas = lazy(async () => (await import("@nivo/treemap")).ResponsiveTreeMapCanvas);
960
- const SunburstSvg = lazy(async () => (await import("@nivo/sunburst")).ResponsiveSunburst);
953
+ const loaders = {
954
+ "heatmap-svg": async () => (await import("@nivo/heatmap")).ResponsiveHeatMap,
955
+ "heatmap-canvas": async () => (await import("@nivo/heatmap")).ResponsiveHeatMapCanvas,
956
+ "calendar-svg": async () => (await import("@nivo/calendar")).ResponsiveCalendar,
957
+ "calendar-canvas": async () => (await import("@nivo/calendar")).ResponsiveCalendarCanvas,
958
+ "treemap-svg": async () => (await import("@nivo/treemap")).ResponsiveTreeMap,
959
+ "treemap-canvas": async () => (await import("@nivo/treemap")).ResponsiveTreeMapCanvas,
960
+ "sunburst-svg": async () => (await import("@nivo/sunburst")).ResponsiveSunburst
961
+ };
962
+ let chunks = /* @__PURE__ */ new Map();
963
+ function chunk(name) {
964
+ let component = chunks.get(name);
965
+ if (!component) {
966
+ component = React.lazy(async () => ({ default: await loaders[name]() }));
967
+ chunks.set(name, component);
968
+ }
969
+ return component;
970
+ }
971
+ /** Forget every loaded-or-failed Nivo chunk, so the next render loads it again. */
972
+ function resetNivoChunks() {
973
+ chunks = /* @__PURE__ */ new Map();
974
+ }
961
975
  /**
962
976
  * Resolved series colours for Nivo: config colours are `var()` strings, which
963
977
  * Nivo cannot use, so a configured colour is resolved through the container's
@@ -1007,7 +1021,7 @@ function NivoChart({ type, data, config, renderer, scale, legend, valueFormatter
1007
1021
  const labelOn = (d) => onFill(d.color);
1008
1022
  switch (type) {
1009
1023
  case "heatmap": {
1010
- const Component = canvas ? HeatMapCanvas : HeatMapSvg;
1024
+ const Component = chunk(canvas ? "heatmap-canvas" : "heatmap-svg");
1011
1025
  const rows = isMatrix(data) ? matrixToSeries(data) : data;
1012
1026
  const cells = rows.reduce((n, r) => n + r.data.length, 0);
1013
1027
  const colorConfig = scale === "diverging" ? {
@@ -1056,7 +1070,7 @@ function NivoChart({ type, data, config, renderer, scale, legend, valueFormatter
1056
1070
  });
1057
1071
  }
1058
1072
  case "calendar": {
1059
- const Component = canvas ? CalendarCanvas : CalendarSvg;
1073
+ const Component = chunk(canvas ? "calendar-canvas" : "calendar-svg");
1060
1074
  const days = data;
1061
1075
  const range = calendarRange(days, from, to);
1062
1076
  return /* @__PURE__ */ jsx(Component, {
@@ -1090,7 +1104,7 @@ function NivoChart({ type, data, config, renderer, scale, legend, valueFormatter
1090
1104
  });
1091
1105
  }
1092
1106
  case "treemap": {
1093
- const Component = canvas ? TreeMapCanvas : TreeMapSvg;
1107
+ const Component = chunk(canvas ? "treemap-canvas" : "treemap-svg");
1094
1108
  const tree = data;
1095
1109
  return /* @__PURE__ */ jsx(Component, {
1096
1110
  data: tree,
@@ -1117,8 +1131,9 @@ function NivoChart({ type, data, config, renderer, scale, legend, valueFormatter
1117
1131
  });
1118
1132
  }
1119
1133
  case "sunburst": {
1134
+ const Component = chunk("sunburst-svg");
1120
1135
  const tree = data;
1121
- return /* @__PURE__ */ jsx(SunburstSvg, {
1136
+ return /* @__PURE__ */ jsx(Component, {
1122
1137
  data: tree,
1123
1138
  id: treeIdentity(tree),
1124
1139
  value: "value",
@@ -1404,13 +1419,33 @@ function seriesKeys(type, data, indexBy, keys) {
1404
1419
  case "calendar": return [];
1405
1420
  }
1406
1421
  }
1422
+ /**
1423
+ * Catches a Nivo chunk that failed to load. Recovering takes two things,
1424
+ * because `React.lazy` memoises a rejection for the life of the page: fresh
1425
+ * lazy components ({@link resetNivoChunks}) and a boundary that leaves its
1426
+ * failed state. `resetKey` does the second on its own when the chart changes,
1427
+ * and the fallback gets a `retry` that does both.
1428
+ */
1407
1429
  var ChunkBoundary = class extends React.Component {
1408
- state = { failed: false };
1430
+ state = {
1431
+ failed: false,
1432
+ key: this.props.resetKey
1433
+ };
1409
1434
  static getDerivedStateFromError() {
1410
1435
  return { failed: true };
1411
1436
  }
1437
+ static getDerivedStateFromProps(props, state) {
1438
+ return props.resetKey === state.key ? null : {
1439
+ failed: false,
1440
+ key: props.resetKey
1441
+ };
1442
+ }
1443
+ retry = () => {
1444
+ resetNivoChunks();
1445
+ this.setState({ failed: false });
1446
+ };
1412
1447
  render() {
1413
- return this.state.failed ? this.props.fallback : this.props.children;
1448
+ return this.state.failed ? this.props.fallback(this.retry) : this.props.children;
1414
1449
  }
1415
1450
  };
1416
1451
  /**
@@ -1450,7 +1485,18 @@ function Chart({ type, data, config, renderer = "svg", indexBy, keys, stacked, s
1450
1485
  else if (errorText) body = /* @__PURE__ */ jsx(ChartError, { children: errorText });
1451
1486
  else if (empty) body = /* @__PURE__ */ jsx(ChartEmpty, { children: emptyMessage });
1452
1487
  else if (engine === "nivo") body = /* @__PURE__ */ jsx(ChunkBoundary, {
1453
- fallback: /* @__PURE__ */ jsx(ChartError, { children: "Chart could not be loaded" }),
1488
+ resetKey: `${type}|${renderer}`,
1489
+ fallback: (retry) => /* @__PURE__ */ jsxs(ChartError, { children: [
1490
+ "Chart could not be loaded.",
1491
+ " ",
1492
+ /* @__PURE__ */ jsx("button", {
1493
+ type: "button",
1494
+ "data-slot": "chart-retry",
1495
+ onClick: retry,
1496
+ className: "underline underline-offset-2",
1497
+ children: "Retry"
1498
+ })
1499
+ ] }),
1454
1500
  children: /* @__PURE__ */ jsx(React.Suspense, {
1455
1501
  fallback: /* @__PURE__ */ jsx(ChartLoading, {}),
1456
1502
  children: /* @__PURE__ */ jsx(NivoChart, {
@@ -1904,12 +1950,25 @@ function toSheet(table, rows = exportRows(table)) {
1904
1950
  }))
1905
1951
  };
1906
1952
  }
1953
+ /**
1954
+ * Text a spreadsheet would evaluate rather than display. Excel, Sheets and
1955
+ * LibreOffice all treat a leading `=`, `+`, `-` or `@` as the start of a
1956
+ * formula, and a leading tab or CR as whitespace they strip before looking
1957
+ * again — so `\t=cmd|'/c calc'!A1` is a formula too. Only strings are checked:
1958
+ * a number, boolean or Date cell cannot carry one, and prefixing `-5` would
1959
+ * turn a number column into text.
1960
+ */
1961
+ const FORMULA_START = /^[=+\-@\t\r]/;
1907
1962
  function csvField(value) {
1908
1963
  if (value == null) return "";
1909
- const text = value instanceof Date ? value.toISOString() : String(value);
1964
+ let text = value instanceof Date ? value.toISOString() : String(value);
1965
+ if (typeof value === "string" && FORMULA_START.test(text)) text = `'${text}`;
1910
1966
  return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, "\"\"")}"` : text;
1911
1967
  }
1912
- /** RFC 4180 text: comma separated, CRLF lines, quotes doubled. */
1968
+ /**
1969
+ * RFC 4180 text: comma separated, CRLF lines, quotes doubled. A cell that a
1970
+ * spreadsheet would read as a formula is prefixed with `'` so it stays text.
1971
+ */
1913
1972
  function toCsv(table, rows) {
1914
1973
  const { headers, records } = toSheet(table, rows);
1915
1974
  return [headers, ...records].map((line) => line.map(csvField).join(",")).join("\r\n");
@@ -2596,14 +2655,20 @@ function useLocalStrategy(source, query, mode) {
2596
2655
  const targetPage = infinite ? loadedForScope.length : query.page;
2597
2656
  const needs = infinite ? loadedForScope.length < wantedPages : !(state.scopeKey === scopeKey && state.page === query.page && state.pages.length > 0);
2598
2657
  const requestKey = `${scopeKey}|${targetPage}|${tick}`;
2658
+ const fetchRef = React.useRef(fetch);
2659
+ React.useEffect(() => {
2660
+ fetchRef.current = fetch;
2661
+ });
2662
+ const hasFetch = Boolean(fetch);
2599
2663
  React.useEffect(() => {
2600
- if (!fetch || !needs) return;
2664
+ const run = fetchRef.current;
2665
+ if (!run || !needs) return;
2601
2666
  const controller = new AbortController();
2602
2667
  setState((s) => ({
2603
2668
  ...s,
2604
2669
  isFetching: true
2605
2670
  }));
2606
- fetch({
2671
+ run({
2607
2672
  ...scope,
2608
2673
  page: targetPage
2609
2674
  }, controller.signal).then((result) => {
@@ -2625,7 +2690,7 @@ function useLocalStrategy(source, query, mode) {
2625
2690
  });
2626
2691
  return () => controller.abort();
2627
2692
  }, [
2628
- fetch,
2693
+ hasFetch,
2629
2694
  requestKey,
2630
2695
  needs
2631
2696
  ]);
@@ -5127,14 +5192,21 @@ const buttonSize = z.enum([
5127
5192
  "lg"
5128
5193
  ]).describe("Control size.");
5129
5194
  /**
5195
+ * Root-relative means one slash and then a path. `//evil.example/x` is
5196
+ * protocol-relative — the browser resolves it against the page's scheme and
5197
+ * goes off-origin — and `/\evil.example` is the same trick, because browsers
5198
+ * treat a backslash after the leading slash as a second slash.
5199
+ */
5200
+ const rootRelative = (value) => value.startsWith("/") && value[1] !== "/" && value[1] !== "\\";
5201
+ /**
5130
5202
  * A URL an agent may point at.
5131
5203
  *
5132
5204
  * Restricted by scheme rather than sanitised after the fact: `javascript:` and
5133
5205
  * friends never reach the DOM because they never pass validation, so the
5134
5206
  * component gets an error card instead.
5135
5207
  */
5136
- const httpUrl = z.string().refine((value) => /^https?:\/\//i.test(value) || value.startsWith("/") || value.startsWith("#"), "Must be an http(s), root-relative or fragment URL.").describe("Destination URL (http, https, root-relative or fragment).");
5137
- const imageUrl = z.string().refine((value) => /^https?:\/\//i.test(value) || /^data:image\//i.test(value) || value.startsWith("/"), "Must be an http(s), root-relative or data:image URL.").describe("Image URL (http, https, root-relative or data:image).");
5208
+ const httpUrl = z.string().refine((value) => /^https?:\/\//i.test(value) || rootRelative(value) || value.startsWith("#"), "Must be an http(s), root-relative or fragment URL.").describe("Destination URL (http, https, root-relative or fragment).");
5209
+ const imageUrl = z.string().refine((value) => /^https?:\/\//i.test(value) || /^data:image\//i.test(value) || rootRelative(value), "Must be an http(s), root-relative or data:image URL.").describe("Image URL (http, https, root-relative or data:image).");
5138
5210
  /** Every chart type the Chart composite draws, Recharts and Nivo alike. */
5139
5211
  const chartType = z.enum([
5140
5212
  "bar",