@marimo-team/islands 0.22.5-dev9 → 0.22.6-dev1
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/{ConnectedDataExplorerComponent-mLj6D01z.js → ConnectedDataExplorerComponent-D08JKcQg.js} +1 -1
- package/dist/{chat-ui-X5KPeHrU.js → chat-ui-BXYRQ5MH.js} +3 -3
- package/dist/main.js +212 -131
- package/dist/{mermaid-B93TKi2g.js → mermaid-BZ2YHhbi.js} +1 -1
- package/dist/{process-output-C0tmJosY.js → process-output-D_uZ0o1x.js} +2097 -2090
- package/dist/style.css +1 -1
- package/dist/{toDate-D1_ZulwM.js → toDate-D0QaHNwR.js} +8 -7
- package/dist/{useAsyncData-C9ez7Ilo.js → useAsyncData-BG3ULuDU.js} +1 -1
- package/dist/{useDeepCompareMemoize-BvvMxigY.js → useDeepCompareMemoize-CkSq3l3_.js} +1 -1
- package/dist/{vega-component-Bzzut3-P.js → vega-component-z4WGXPkf.js} +3 -3
- package/package.json +2 -2
- package/src/components/data-table/__tests__/columns.test.tsx +92 -13
- package/src/components/data-table/column-header.tsx +81 -56
- package/src/components/data-table/columns.tsx +25 -32
- package/src/components/data-table/data-table.tsx +8 -1
- package/src/components/data-table/renderers.tsx +19 -6
- package/src/components/data-table/types.ts +4 -0
- package/src/components/editor/Output.tsx +1 -1
- package/src/components/editor/__tests__/Output.test.tsx +36 -1
- package/src/core/cells/__tests__/cells.test.ts +41 -0
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +38 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/cells/collapseConsoleOutputs.tsx +3 -0
- package/src/core/cells/document-changes.ts +12 -0
- package/src/core/runtime/__tests__/runtime.test.ts +138 -2
- package/src/core/runtime/runtime.ts +25 -5
- package/src/core/saving/file-state.ts +16 -0
- package/src/hooks/useAsyncData.ts +1 -1
- package/src/mount.tsx +17 -1
- package/src/plugins/impl/DataTablePlugin.tsx +1 -1
- package/src/plugins/impl/plotly/__tests__/selection.test.ts +22 -0
- package/src/plugins/impl/plotly/selection.ts +1 -0
|
@@ -498,7 +498,8 @@ var RuntimeManager = class {
|
|
|
498
498
|
return g.pathname = `${g.pathname.replace(/\/$/, "")}/${e.replace(/^\//, "")}`, g.hash = "", g;
|
|
499
499
|
}
|
|
500
500
|
formatWsURL(e, m) {
|
|
501
|
-
|
|
501
|
+
let h = this.formatHttpURL(e, m, false);
|
|
502
|
+
return !this.isSameOrigin && this.config.authToken && h.searchParams.set(KnownQueryParams.accessToken, this.config.authToken), asWsUrl(h.toString());
|
|
502
503
|
}
|
|
503
504
|
getWsURL(e) {
|
|
504
505
|
let m = new URL(this.config.url), h = new URLSearchParams(m.search);
|
|
@@ -517,8 +518,8 @@ var RuntimeManager = class {
|
|
|
517
518
|
}
|
|
518
519
|
getLSPURL(e) {
|
|
519
520
|
if (e === "copilot") {
|
|
520
|
-
let m = this.formatWsURL(`/lsp/${e}`);
|
|
521
|
-
return m.search = "", m;
|
|
521
|
+
let m = this.formatWsURL(`/lsp/${e}`), h = m.searchParams.get(KnownQueryParams.accessToken);
|
|
522
|
+
return m.search = "", h && m.searchParams.set(KnownQueryParams.accessToken, h), m;
|
|
522
523
|
}
|
|
523
524
|
return this.formatWsURL(`/lsp/${e}`);
|
|
524
525
|
}
|
|
@@ -534,13 +535,13 @@ var RuntimeManager = class {
|
|
|
534
535
|
let e = await fetch(this.healthURL().toString());
|
|
535
536
|
if (e.redirected) {
|
|
536
537
|
Logger.debug(`Runtime redirected to ${e.url}`);
|
|
537
|
-
let m2 = e.url
|
|
538
|
-
this.config.url = m2;
|
|
538
|
+
let m2 = new URL(e.url);
|
|
539
|
+
m2.pathname = m2.pathname.replace(/\/health$/, ""), this.config.url = m2.toString();
|
|
539
540
|
}
|
|
540
541
|
let m = e.ok;
|
|
541
542
|
return m && this.setDOMBaseUri(this.config.url), m;
|
|
542
|
-
} catch {
|
|
543
|
-
return false;
|
|
543
|
+
} catch (e) {
|
|
544
|
+
return Logger.error(`Failed to check health: ${e instanceof Error ? e.message : "Unknown error"}`, { cause: e }), false;
|
|
544
545
|
}
|
|
545
546
|
}
|
|
546
547
|
setDOMBaseUri(e) {
|
|
@@ -80,7 +80,7 @@ function useAsyncData(e, s) {
|
|
|
80
80
|
}, c[13] = f, c[14] = v, c[15] = y, c[16] = b) : b = c[16], b;
|
|
81
81
|
}
|
|
82
82
|
function _temp(e) {
|
|
83
|
-
return e.status === "success" ? Result.loading(e.data) : Result.pending();
|
|
83
|
+
return e.status === "success" || e.status === "loading" ? Result.loading(e.data) : Result.pending();
|
|
84
84
|
}
|
|
85
85
|
export {
|
|
86
86
|
useAsyncData as t
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-Bs6Z0kvn.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
|
|
4
|
-
import { t as toDate } from "./toDate-
|
|
4
|
+
import { t as toDate } from "./toDate-D0QaHNwR.js";
|
|
5
5
|
import { r as cva, y as cn } from "./button-DNlNlZY_.js";
|
|
6
6
|
import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
|
|
7
7
|
import { C as dequal } from "./useTheme-CxjbgkRc.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-Bs6Z0kvn.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-B_OLMU9S.js";
|
|
4
|
-
import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-
|
|
4
|
+
import { c as asRemoteURL, g as CircleQuestionMark } from "./toDate-D0QaHNwR.js";
|
|
5
5
|
import { c as Objects, g as Logger, h as Events, y as cn } from "./button-DNlNlZY_.js";
|
|
6
6
|
import "./react-dom-BSUuJjCR.js";
|
|
7
7
|
import { t as require_jsx_runtime } from "./jsx-runtime-9hcJiI23.js";
|
|
@@ -11,10 +11,10 @@ import { t as Tooltip } from "./tooltip-B5EnNyok.js";
|
|
|
11
11
|
import { i as debounce_default } from "./constants-CvyfaCvs.js";
|
|
12
12
|
import { n as useTheme, w as useEvent_default } from "./useTheme-CxjbgkRc.js";
|
|
13
13
|
import { s as uniq } from "./arrays-beUWo8RF.js";
|
|
14
|
-
import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-
|
|
14
|
+
import { a as AlertTitle, n as arrow, o as isValid, r as Alert, t as useDeepCompareMemoize } from "./useDeepCompareMemoize-CkSq3l3_.js";
|
|
15
15
|
import { n as formats } from "./vega-loader.browser-DqEcFOPD.js";
|
|
16
16
|
import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Bd1kgLn7.js";
|
|
17
|
-
import { t as useAsyncData } from "./useAsyncData-
|
|
17
|
+
import { t as useAsyncData } from "./useAsyncData-BG3ULuDU.js";
|
|
18
18
|
import { t as j } from "./react-vega-CzRAIHrv.js";
|
|
19
19
|
import "./defaultLocale-qS7DaAmi.js";
|
|
20
20
|
import "./defaultLocale-Bxoo2-30.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.6-dev1",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"marked": "^15.0.12",
|
|
121
121
|
"mermaid": "^11.12.3",
|
|
122
122
|
"partysocket": "1.1.10",
|
|
123
|
-
"path-to-regexp": "^8.
|
|
123
|
+
"path-to-regexp": "^8.4.0",
|
|
124
124
|
"plotly.js": "^3.3.1",
|
|
125
125
|
"pyodide": "0.27.7",
|
|
126
126
|
"react-arborist": "^3.4.3",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Column } from "@tanstack/react-table";
|
|
4
|
-
import { render } from "@testing-library/react";
|
|
4
|
+
import { fireEvent, render } from "@testing-library/react";
|
|
5
5
|
import { I18nProvider } from "react-aria";
|
|
6
|
-
import { describe, expect, it, test } from "vitest";
|
|
6
|
+
import { describe, expect, it, test, vi } from "vitest";
|
|
7
7
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
8
|
import { parseContent } from "@/utils/url-parser";
|
|
9
9
|
import {
|
|
@@ -312,7 +312,7 @@ describe("generateColumns", () => {
|
|
|
312
312
|
expect(cell?.props.className).toContain("center");
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
it("should
|
|
315
|
+
it("should always left-align column headers regardless of text justification", () => {
|
|
316
316
|
const columns = generateColumns({
|
|
317
317
|
rowHeaders: [],
|
|
318
318
|
selection: null,
|
|
@@ -330,33 +330,112 @@ describe("generateColumns", () => {
|
|
|
330
330
|
columnDef: { meta: col.meta },
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
-
//
|
|
333
|
+
// Even with right justification, header is left-aligned with sort + menu buttons
|
|
334
334
|
const { container: rightContainer } = render(
|
|
335
335
|
<TooltipProvider>
|
|
336
336
|
{/* oxlint-disable-next-line typescript/no-explicit-any */}
|
|
337
337
|
{(columns[0].header as any)({ column: mockColumn(columns[0]) })}
|
|
338
338
|
</TooltipProvider>,
|
|
339
339
|
);
|
|
340
|
+
expect(
|
|
341
|
+
rightContainer.querySelector("[data-testid='data-table-sort-button']"),
|
|
342
|
+
).toBeTruthy();
|
|
343
|
+
expect(
|
|
344
|
+
rightContainer.querySelector(
|
|
345
|
+
"[data-testid='data-table-column-menu-button']",
|
|
346
|
+
),
|
|
347
|
+
).toBeTruthy();
|
|
348
|
+
// No flex-row-reverse or items-end on header
|
|
340
349
|
const rightWrapper = rightContainer.firstElementChild;
|
|
341
|
-
expect(rightWrapper?.className).toContain("items-end");
|
|
342
|
-
|
|
343
|
-
"[data-testid='data-table-sort-button']",
|
|
344
|
-
);
|
|
345
|
-
expect(rightHeader?.className).toContain("flex-row-reverse");
|
|
350
|
+
expect(rightWrapper?.className).not.toContain("items-end");
|
|
351
|
+
expect(rightWrapper?.className).not.toContain("flex-row-reverse");
|
|
346
352
|
|
|
347
|
-
//
|
|
353
|
+
// Same for center-justified column
|
|
348
354
|
const { container: centerContainer } = render(
|
|
349
355
|
<TooltipProvider>
|
|
350
356
|
{/* oxlint-disable-next-line typescript/no-explicit-any */}
|
|
351
357
|
{(columns[1].header as any)({ column: mockColumn(columns[1]) })}
|
|
352
358
|
</TooltipProvider>,
|
|
353
359
|
);
|
|
360
|
+
expect(
|
|
361
|
+
centerContainer.querySelector("[data-testid='data-table-sort-button']"),
|
|
362
|
+
).toBeTruthy();
|
|
363
|
+
expect(
|
|
364
|
+
centerContainer.querySelector(
|
|
365
|
+
"[data-testid='data-table-column-menu-button']",
|
|
366
|
+
),
|
|
367
|
+
).toBeTruthy();
|
|
354
368
|
const centerWrapper = centerContainer.firstElementChild;
|
|
355
|
-
expect(centerWrapper?.className).toContain("items-center");
|
|
356
|
-
|
|
369
|
+
expect(centerWrapper?.className).not.toContain("items-center");
|
|
370
|
+
expect(centerWrapper?.className).not.toContain("flex-row-reverse");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should cycle sort button through asc, desc, and clear on clicks", () => {
|
|
374
|
+
const columns = generateColumns({
|
|
375
|
+
rowHeaders: [],
|
|
376
|
+
selection: null,
|
|
377
|
+
fieldTypes,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const toggleSorting = vi.fn();
|
|
381
|
+
const clearSorting = vi.fn();
|
|
382
|
+
let sortDirection: false | "asc" | "desc" = false;
|
|
383
|
+
|
|
384
|
+
const mockColumn = (col: (typeof columns)[number]) => ({
|
|
385
|
+
id: col.id,
|
|
386
|
+
getCanSort: () => true,
|
|
387
|
+
getCanFilter: () => false,
|
|
388
|
+
getIsSorted: () => sortDirection,
|
|
389
|
+
getSortIndex: () => -1,
|
|
390
|
+
getFilterValue: () => undefined,
|
|
391
|
+
toggleSorting,
|
|
392
|
+
clearSorting,
|
|
393
|
+
columnDef: { meta: col.meta },
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const mock = mockColumn(columns[0]);
|
|
397
|
+
|
|
398
|
+
const { container, rerender } = render(
|
|
399
|
+
<TooltipProvider>
|
|
400
|
+
{/* oxlint-disable-next-line typescript/no-explicit-any */}
|
|
401
|
+
{(columns[0].header as any)({ column: mock })}
|
|
402
|
+
</TooltipProvider>,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const sortButton = container.querySelector(
|
|
357
406
|
"[data-testid='data-table-sort-button']",
|
|
358
407
|
);
|
|
359
|
-
expect(
|
|
408
|
+
expect(sortButton).toBeTruthy();
|
|
409
|
+
|
|
410
|
+
// first click unsorted > asc
|
|
411
|
+
fireEvent.click(sortButton!);
|
|
412
|
+
expect(toggleSorting).toHaveBeenCalledWith(false, true);
|
|
413
|
+
|
|
414
|
+
// Simulate asc state and re-render
|
|
415
|
+
sortDirection = "asc";
|
|
416
|
+
rerender(
|
|
417
|
+
<TooltipProvider>
|
|
418
|
+
{/* oxlint-disable-next-line typescript/no-explicit-any */}
|
|
419
|
+
{(columns[0].header as any)({ column: mock })}
|
|
420
|
+
</TooltipProvider>,
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// second click asc >dsc
|
|
424
|
+
fireEvent.click(sortButton!);
|
|
425
|
+
expect(toggleSorting).toHaveBeenCalledWith(true, true);
|
|
426
|
+
|
|
427
|
+
// Simulate desc state and re-render
|
|
428
|
+
sortDirection = "desc";
|
|
429
|
+
rerender(
|
|
430
|
+
<TooltipProvider>
|
|
431
|
+
{/* oxlint-disable-next-line typescript/no-explicit-any */}
|
|
432
|
+
{(columns[0].header as any)({ column: mock })}
|
|
433
|
+
</TooltipProvider>,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// third click back to unsorted
|
|
437
|
+
fireEvent.click(sortButton!);
|
|
438
|
+
expect(clearSorting).toHaveBeenCalled();
|
|
360
439
|
});
|
|
361
440
|
|
|
362
441
|
it("should not include index column if it exists", () => {
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
"use no memo";
|
|
3
3
|
|
|
4
4
|
import type { Column, Table } from "@tanstack/react-table";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
EllipsisIcon,
|
|
7
|
+
FilterIcon,
|
|
8
|
+
MinusIcon,
|
|
9
|
+
TextIcon,
|
|
10
|
+
XIcon,
|
|
11
|
+
} from "lucide-react";
|
|
6
12
|
import { useMemo, useRef, useState } from "react";
|
|
7
13
|
import { useLocale } from "react-aria";
|
|
8
14
|
import {
|
|
@@ -69,7 +75,7 @@ interface DataTableColumnHeaderProps<
|
|
|
69
75
|
> extends React.HTMLAttributes<HTMLDivElement> {
|
|
70
76
|
column: Column<TData, TValue>;
|
|
71
77
|
header: React.ReactNode;
|
|
72
|
-
|
|
78
|
+
subheader?: React.ReactNode;
|
|
73
79
|
calculateTopKRows?: CalculateTopKRows;
|
|
74
80
|
table?: Table<TData>;
|
|
75
81
|
}
|
|
@@ -77,7 +83,7 @@ interface DataTableColumnHeaderProps<
|
|
|
77
83
|
export const DataTableColumnHeader = <TData, TValue>({
|
|
78
84
|
column,
|
|
79
85
|
header,
|
|
80
|
-
|
|
86
|
+
subheader,
|
|
81
87
|
className,
|
|
82
88
|
calculateTopKRows,
|
|
83
89
|
table,
|
|
@@ -92,49 +98,51 @@ export const DataTableColumnHeader = <TData, TValue>({
|
|
|
92
98
|
|
|
93
99
|
// No sorting or filtering
|
|
94
100
|
if (!column.getCanSort() && !column.getCanFilter()) {
|
|
95
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
<div className={cn(className)}>
|
|
103
|
+
{header}
|
|
104
|
+
{subheader}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
const hasFilter = column.getFilterValue() !== undefined;
|
|
99
|
-
const hideIcon = !column.getIsSorted() && !hasFilter;
|
|
100
110
|
|
|
101
111
|
return (
|
|
102
112
|
<>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
>
|
|
121
|
-
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
</DropdownMenuContent>
|
|
137
|
-
</DropdownMenu>
|
|
113
|
+
<div
|
|
114
|
+
className={cn("group flex flex-col my-1 w-full select-none", className)}
|
|
115
|
+
>
|
|
116
|
+
<div className="flex items-center gap-1">
|
|
117
|
+
<span>{header}</span>
|
|
118
|
+
{column.getCanSort() && <SortButton column={column} />}
|
|
119
|
+
<DropdownMenu modal={false}>
|
|
120
|
+
<DropdownMenuTrigger asChild={true}>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
className="inline-flex items-center justify-center h-5 w-5 rounded hover:bg-(--slate-4) text-muted-foreground opacity-0 group-hover:opacity-100 focus:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100 data-[state=open]:text-accent-foreground"
|
|
124
|
+
aria-label="Column options"
|
|
125
|
+
data-testid="data-table-column-menu-button"
|
|
126
|
+
>
|
|
127
|
+
<EllipsisIcon className="h-3.5 w-3.5" />
|
|
128
|
+
</button>
|
|
129
|
+
</DropdownMenuTrigger>
|
|
130
|
+
<DropdownMenuContent align="start">
|
|
131
|
+
{renderDataType(column)}
|
|
132
|
+
{renderSorts(column, table)}
|
|
133
|
+
{renderCopyColumn(column)}
|
|
134
|
+
{renderColumnPinning(column)}
|
|
135
|
+
{renderColumnWrapping(column)}
|
|
136
|
+
{renderFormatOptions(column, locale)}
|
|
137
|
+
<DropdownMenuSeparator />
|
|
138
|
+
{renderMenuItemFilter(column)}
|
|
139
|
+
{renderFilterByValues(column, setIsFilterValueOpen)}
|
|
140
|
+
{hasFilter && <ClearFilterMenuItem column={column} />}
|
|
141
|
+
</DropdownMenuContent>
|
|
142
|
+
</DropdownMenu>
|
|
143
|
+
</div>
|
|
144
|
+
{subheader}
|
|
145
|
+
</div>
|
|
138
146
|
{isFilterValueOpen && (
|
|
139
147
|
<PopoverFilterByValues
|
|
140
148
|
setIsFilterValueOpen={setIsFilterValueOpen}
|
|
@@ -146,28 +154,45 @@ export const DataTableColumnHeader = <TData, TValue>({
|
|
|
146
154
|
);
|
|
147
155
|
};
|
|
148
156
|
|
|
149
|
-
|
|
157
|
+
const SortButton = <TData, TValue>({
|
|
150
158
|
column,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
className,
|
|
154
|
-
}: DataTableColumnHeaderProps<TData, TValue> & {
|
|
155
|
-
summary: React.ReactNode;
|
|
159
|
+
}: {
|
|
160
|
+
column: Column<TData, TValue>;
|
|
156
161
|
}) => {
|
|
162
|
+
const sortDirection = column.getIsSorted();
|
|
163
|
+
|
|
164
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
if (!sortDirection) {
|
|
167
|
+
column.toggleSorting(false, true); // asc
|
|
168
|
+
} else if (sortDirection === "asc") {
|
|
169
|
+
column.toggleSorting(true, true); // desc
|
|
170
|
+
} else {
|
|
171
|
+
column.clearSorting();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
157
175
|
return (
|
|
158
|
-
<
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={handleClick}
|
|
159
179
|
className={cn(
|
|
160
|
-
"flex
|
|
161
|
-
|
|
180
|
+
"inline-flex items-center justify-center h-5 w-5 rounded hover:bg-(--slate-4)",
|
|
181
|
+
sortDirection
|
|
182
|
+
? "text-accent-foreground"
|
|
183
|
+
: "text-muted-foreground opacity-0 group-hover:opacity-100 focus:opacity-100 group-focus-within:opacity-100",
|
|
162
184
|
)}
|
|
185
|
+
aria-label={
|
|
186
|
+
sortDirection === "asc"
|
|
187
|
+
? "Sorted ascending, click to sort descending"
|
|
188
|
+
: sortDirection === "desc"
|
|
189
|
+
? "Sorted descending, click to clear sort"
|
|
190
|
+
: "Sort column ascending"
|
|
191
|
+
}
|
|
192
|
+
data-testid="data-table-sort-button"
|
|
163
193
|
>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
header={header}
|
|
167
|
-
className={className}
|
|
168
|
-
/>
|
|
169
|
-
{summary}
|
|
170
|
-
</div>
|
|
194
|
+
{renderSortFilterIcon(column)}
|
|
195
|
+
</button>
|
|
171
196
|
);
|
|
172
197
|
};
|
|
173
198
|
|
|
@@ -206,41 +206,30 @@ export function generateColumns<T>({
|
|
|
206
206
|
</div>
|
|
207
207
|
) : null;
|
|
208
208
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const headerWithType = (
|
|
212
|
-
<div
|
|
209
|
+
const headerName = (
|
|
210
|
+
<span
|
|
213
211
|
className={cn(
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
justify === "right" && "items-end",
|
|
212
|
+
"font-bold",
|
|
213
|
+
headerTitle && "underline decoration-dotted",
|
|
217
214
|
)}
|
|
218
215
|
>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"font-bold",
|
|
222
|
-
headerTitle && "underline decoration-dotted",
|
|
223
|
-
)}
|
|
224
|
-
>
|
|
225
|
-
{key === "" ? " " : key}
|
|
226
|
-
</span>
|
|
227
|
-
{dtypeHeader}
|
|
228
|
-
</div>
|
|
216
|
+
{key === "" ? " " : key}
|
|
217
|
+
</span>
|
|
229
218
|
);
|
|
230
219
|
|
|
231
220
|
const headerWithTooltip = headerTitle ? (
|
|
232
221
|
<Tooltip content={headerTitle} delayDuration={300}>
|
|
233
|
-
{
|
|
222
|
+
{headerName}
|
|
234
223
|
</Tooltip>
|
|
235
224
|
) : (
|
|
236
|
-
|
|
225
|
+
headerName
|
|
237
226
|
);
|
|
238
227
|
|
|
239
228
|
const dataTableColumnHeader = (
|
|
240
229
|
<DataTableColumnHeader
|
|
241
230
|
header={headerWithTooltip}
|
|
231
|
+
subheader={dtypeHeader}
|
|
242
232
|
column={column}
|
|
243
|
-
justify={justify}
|
|
244
233
|
calculateTopKRows={calculateTopKRows}
|
|
245
234
|
table={table}
|
|
246
235
|
/>
|
|
@@ -255,8 +244,6 @@ export function generateColumns<T>({
|
|
|
255
244
|
<div
|
|
256
245
|
className={cn(
|
|
257
246
|
"flex flex-col h-full pt-0.5 pb-3 justify-between items-start",
|
|
258
|
-
justify === "center" && "items-center",
|
|
259
|
-
justify === "right" && "items-end",
|
|
260
247
|
)}
|
|
261
248
|
>
|
|
262
249
|
{dataTableColumnHeader}
|
|
@@ -283,13 +270,13 @@ export function generateColumns<T>({
|
|
|
283
270
|
|
|
284
271
|
const dataType = column.columnDef.meta?.dataType;
|
|
285
272
|
const isNumeric = dataType === "number" || dataType === "integer";
|
|
286
|
-
const cellStyles = getCellStyleClass(
|
|
273
|
+
const cellStyles = getCellStyleClass({
|
|
287
274
|
justify,
|
|
288
275
|
wrapped,
|
|
289
276
|
canSelectCell,
|
|
290
|
-
isCellSelected,
|
|
277
|
+
isSelected: isCellSelected,
|
|
291
278
|
isNumeric,
|
|
292
|
-
);
|
|
279
|
+
});
|
|
293
280
|
|
|
294
281
|
const renderedCell = renderCellValue({
|
|
295
282
|
column,
|
|
@@ -448,13 +435,19 @@ function getFilterTypeForFieldType(
|
|
|
448
435
|
}
|
|
449
436
|
}
|
|
450
437
|
|
|
451
|
-
function getCellStyleClass(
|
|
452
|
-
justify
|
|
453
|
-
wrapped
|
|
454
|
-
canSelectCell
|
|
455
|
-
isSelected
|
|
456
|
-
isNumeric
|
|
457
|
-
|
|
438
|
+
function getCellStyleClass({
|
|
439
|
+
justify = "left",
|
|
440
|
+
wrapped,
|
|
441
|
+
canSelectCell,
|
|
442
|
+
isSelected,
|
|
443
|
+
isNumeric = false,
|
|
444
|
+
}: {
|
|
445
|
+
justify: "left" | "center" | "right" | undefined;
|
|
446
|
+
wrapped: boolean | undefined;
|
|
447
|
+
canSelectCell: boolean;
|
|
448
|
+
isSelected: boolean;
|
|
449
|
+
isNumeric?: boolean;
|
|
450
|
+
}): string {
|
|
458
451
|
return cn(
|
|
459
452
|
canSelectCell && "cursor-pointer",
|
|
460
453
|
isSelected &&
|
|
@@ -47,6 +47,7 @@ import { DataTableBody, renderTableHeader } from "./renderers";
|
|
|
47
47
|
import { TableBottomBar } from "./TableBottomBar";
|
|
48
48
|
import { TableTopBar } from "./TableTopBar";
|
|
49
49
|
import {
|
|
50
|
+
AUTO_WIDTH_MAX_COLUMNS,
|
|
50
51
|
type DataTableSelection,
|
|
51
52
|
MIN_ROWS_TO_VIRTUALIZE,
|
|
52
53
|
type TooManyRows,
|
|
@@ -300,7 +301,13 @@ const DataTableInternal = <TData,>({
|
|
|
300
301
|
isAnyPanelOpen={isAnyPanelOpen}
|
|
301
302
|
downloadAs={downloadAs}
|
|
302
303
|
/>
|
|
303
|
-
<Table
|
|
304
|
+
<Table
|
|
305
|
+
className={cn(
|
|
306
|
+
"relative",
|
|
307
|
+
columns.length <= AUTO_WIDTH_MAX_COLUMNS ? "w-auto" : "w-full",
|
|
308
|
+
)}
|
|
309
|
+
ref={tableRef}
|
|
310
|
+
>
|
|
304
311
|
{showLoadingBar && (
|
|
305
312
|
<thead className="absolute top-0 left-0 h-[3px] w-1/2 bg-primary animate-slide" />
|
|
306
313
|
)}
|
|
@@ -27,7 +27,7 @@ import { DataTableContextMenu } from "./context-menu";
|
|
|
27
27
|
import { CellRangeSelectionIndicator } from "./range-focus/cell-selection-indicator";
|
|
28
28
|
import { useCellRangeSelection } from "./range-focus/use-cell-range-selection";
|
|
29
29
|
import { useScrollIntoViewOnFocus } from "./range-focus/use-scroll-into-view";
|
|
30
|
-
import { TABLE_ROW_HEIGHT_PX } from "./types";
|
|
30
|
+
import { AUTO_WIDTH_MAX_COLUMNS, TABLE_ROW_HEIGHT_PX } from "./types";
|
|
31
31
|
import { stringifyUnknownValue } from "./utils";
|
|
32
32
|
|
|
33
33
|
export function renderTableHeader<TData>(
|
|
@@ -46,7 +46,7 @@ export function renderTableHeader<TData>(
|
|
|
46
46
|
<TableHead
|
|
47
47
|
key={header.id}
|
|
48
48
|
className={cn(
|
|
49
|
-
"h-auto min-h-10 whitespace-pre align-top",
|
|
49
|
+
"h-auto min-h-10 whitespace-pre align-top border-r border-r-border/75",
|
|
50
50
|
className,
|
|
51
51
|
)}
|
|
52
52
|
style={style}
|
|
@@ -69,6 +69,13 @@ export function renderTableHeader<TData>(
|
|
|
69
69
|
{renderHeaderGroup(table.getLeftHeaderGroups())}
|
|
70
70
|
{renderHeaderGroup(table.getCenterHeaderGroups())}
|
|
71
71
|
{renderHeaderGroup(table.getRightHeaderGroups())}
|
|
72
|
+
{table.getAllColumns().length <= AUTO_WIDTH_MAX_COLUMNS && (
|
|
73
|
+
<th
|
|
74
|
+
className="w-full border-0"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
role="presentation"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
72
79
|
</TableRow>
|
|
73
80
|
</TableHeader>
|
|
74
81
|
);
|
|
@@ -163,7 +170,7 @@ export const DataTableBody = <TData,>({
|
|
|
163
170
|
{...getCellDomProps(cell.id)}
|
|
164
171
|
key={cell.id}
|
|
165
172
|
className={cn(
|
|
166
|
-
"whitespace-pre truncate max-w-[300px] outline-hidden",
|
|
173
|
+
"whitespace-pre truncate max-w-[300px] outline-hidden border-r border-r-border/75",
|
|
167
174
|
cell.column.getColumnWrapping &&
|
|
168
175
|
cell.column.getColumnWrapping?.() === "wrap" &&
|
|
169
176
|
COLUMN_WRAPPING_STYLES,
|
|
@@ -230,15 +237,21 @@ export const DataTableBody = <TData,>({
|
|
|
230
237
|
{renderCells(row.getLeftVisibleCells())}
|
|
231
238
|
{renderCells(row.getCenterVisibleCells())}
|
|
232
239
|
{renderCells(row.getRightVisibleCells())}
|
|
240
|
+
{columns.length <= AUTO_WIDTH_MAX_COLUMNS && (
|
|
241
|
+
<td className="border-0" aria-hidden="true" role="presentation" />
|
|
242
|
+
)}
|
|
233
243
|
</TableRow>
|
|
234
244
|
);
|
|
235
245
|
};
|
|
236
246
|
|
|
247
|
+
const hasFillerColumn = columns.length <= AUTO_WIDTH_MAX_COLUMNS;
|
|
248
|
+
const totalColSpan = columns.length + (hasFillerColumn ? 1 : 0);
|
|
249
|
+
|
|
237
250
|
const renderRows = () => {
|
|
238
251
|
if (rows.length === 0) {
|
|
239
252
|
return (
|
|
240
253
|
<TableRow>
|
|
241
|
-
<TableCell colSpan={
|
|
254
|
+
<TableCell colSpan={totalColSpan} className="h-24 text-center">
|
|
242
255
|
No results.
|
|
243
256
|
</TableCell>
|
|
244
257
|
</TableRow>
|
|
@@ -255,7 +268,7 @@ export const DataTableBody = <TData,>({
|
|
|
255
268
|
data-virtual-spacer=""
|
|
256
269
|
style={{ height: virtualItems[0].start }}
|
|
257
270
|
>
|
|
258
|
-
<td colSpan={
|
|
271
|
+
<td colSpan={totalColSpan} />
|
|
259
272
|
</tr>
|
|
260
273
|
)}
|
|
261
274
|
{virtualItems.map((vItem) => renderRow(rows[vItem.index]))}
|
|
@@ -266,7 +279,7 @@ export const DataTableBody = <TData,>({
|
|
|
266
279
|
height: totalSize - (virtualItems.at(-1)?.end ?? totalSize),
|
|
267
280
|
}}
|
|
268
281
|
>
|
|
269
|
-
<td colSpan={
|
|
282
|
+
<td colSpan={totalColSpan} />
|
|
270
283
|
</tr>
|
|
271
284
|
)}
|
|
272
285
|
</>
|
|
@@ -16,6 +16,10 @@ declare module "@tanstack/react-table" {
|
|
|
16
16
|
export const TABLE_ROW_HEIGHT_PX = 24;
|
|
17
17
|
export const TABLE_HEADER_HEIGHT_PX = 40;
|
|
18
18
|
|
|
19
|
+
// Below this column count, the table uses w-auto with a filler column
|
|
20
|
+
// to prevent columns from stretching unnecessarily
|
|
21
|
+
export const AUTO_WIDTH_MAX_COLUMNS = 4;
|
|
22
|
+
|
|
19
23
|
// Default number of visible rows when virtualizing without an explicit maxHeight.
|
|
20
24
|
export const DEFAULT_VIRTUAL_ROWS = 15;
|
|
21
25
|
|