lynote-ui 0.0.9 → 0.0.11

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.
@@ -0,0 +1,1077 @@
1
+ import { t as cn } from "./utils-Bg4z4cXr.mjs";
2
+ import { Button } from "./button/index.mjs";
3
+ import { Input } from "./input/index.mjs";
4
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "./dropdown-menu/index.mjs";
5
+ import { NativeSelect, NativeSelectOption } from "./native-select/index.mjs";
6
+ import { Skeleton } from "./skeleton/index.mjs";
7
+ import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, GripVerticalIcon, SearchIcon, Settings2Icon, XIcon } from "lucide-react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { Fragment as Fragment$1, createContext, forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
10
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
11
+ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
12
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
13
+ import { CSS } from "@dnd-kit/utilities";
14
+ import { flexRender, getCoreRowModel, getExpandedRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
15
+ import { useVirtualizer } from "@tanstack/react-virtual";
16
+ //#region src/table/components/base-table.tsx
17
+ function Table({ className, ...props }) {
18
+ return /* @__PURE__ */ jsx("div", {
19
+ "data-slot": "table-container",
20
+ className: "relative w-full overflow-x-auto",
21
+ children: /* @__PURE__ */ jsx("table", {
22
+ "data-slot": "table",
23
+ className: cn("w-full caption-bottom text-sm", className),
24
+ ...props
25
+ })
26
+ });
27
+ }
28
+ function TableHeader({ className, ...props }) {
29
+ return /* @__PURE__ */ jsx("thead", {
30
+ "data-slot": "table-header",
31
+ className: cn("[&_tr]:border-b", className),
32
+ ...props
33
+ });
34
+ }
35
+ function TableBody({ className, ...props }) {
36
+ return /* @__PURE__ */ jsx("tbody", {
37
+ "data-slot": "table-body",
38
+ className: cn("[&_tr:last-child]:border-0", className),
39
+ ...props
40
+ });
41
+ }
42
+ function TableFooter({ className, ...props }) {
43
+ return /* @__PURE__ */ jsx("tfoot", {
44
+ "data-slot": "table-footer",
45
+ className: cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className),
46
+ ...props
47
+ });
48
+ }
49
+ function TableRow({ className, ...props }) {
50
+ return /* @__PURE__ */ jsx("tr", {
51
+ "data-slot": "table-row",
52
+ className: cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className),
53
+ ...props
54
+ });
55
+ }
56
+ function TableHead({ className, ...props }) {
57
+ return /* @__PURE__ */ jsx("th", {
58
+ "data-slot": "table-head",
59
+ className: cn("text-foreground h-10 whitespace-nowrap px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0", className),
60
+ ...props
61
+ });
62
+ }
63
+ function TableCell({ className, ...props }) {
64
+ return /* @__PURE__ */ jsx("td", {
65
+ "data-slot": "table-cell",
66
+ className: cn("whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0", className),
67
+ ...props
68
+ });
69
+ }
70
+ function TableCaption({ className, ...props }) {
71
+ return /* @__PURE__ */ jsx("caption", {
72
+ "data-slot": "table-caption",
73
+ className: cn("text-muted-foreground mt-4 text-sm", className),
74
+ ...props
75
+ });
76
+ }
77
+ //#endregion
78
+ //#region src/table/hooks/use-data-table.ts
79
+ var DEFAULT_FEATURES = {
80
+ sorting: true,
81
+ multiSort: false,
82
+ filtering: true,
83
+ globalFilter: false,
84
+ pagination: true,
85
+ rowSelection: false,
86
+ columnVisibility: true,
87
+ columnOrdering: false,
88
+ columnPinning: false,
89
+ columnResizing: false,
90
+ rowDnd: false,
91
+ expanding: false
92
+ };
93
+ function applyUpdater(updater, prev) {
94
+ return typeof updater === "function" ? updater(prev) : updater;
95
+ }
96
+ /**
97
+ * 选取受控值,若未传则回退到非受控
98
+ *
99
+ * setter 同时支持值和函数式 updater,以避免一帧内多次更新拿到旧 snapshot
100
+ */
101
+ function useControlled(controlled, initial, onChange) {
102
+ const isControlled = controlled !== void 0;
103
+ const [internal, setInternal] = useState(initial);
104
+ const value = isControlled ? controlled : internal;
105
+ const setValue = (updater) => {
106
+ if (isControlled) {
107
+ const next = applyUpdater(updater, controlled);
108
+ onChange?.(next);
109
+ return;
110
+ }
111
+ setInternal((prev) => {
112
+ const next = applyUpdater(updater, prev);
113
+ onChange?.(next);
114
+ return next;
115
+ });
116
+ };
117
+ return [value, setValue];
118
+ }
119
+ /**
120
+ * 构造 TanStack 表格实例,并按需开启各项功能
121
+ */
122
+ function useDataTable({ data, columns, features, state, initialState, onStateChange, tableOptions }) {
123
+ const mergedFeatures = {
124
+ ...DEFAULT_FEATURES,
125
+ ...features
126
+ };
127
+ const [sorting, setSorting] = useControlled(state?.sorting, initialState?.sorting ?? [], onStateChange?.onSortingChange);
128
+ const [columnFilters, setColumnFilters] = useControlled(state?.columnFilters, initialState?.columnFilters ?? [], onStateChange?.onColumnFiltersChange);
129
+ const [columnVisibility, setColumnVisibility] = useControlled(state?.columnVisibility, initialState?.columnVisibility ?? {}, onStateChange?.onColumnVisibilityChange);
130
+ const [columnOrder, setColumnOrder] = useControlled(state?.columnOrder, initialState?.columnOrder ?? [], onStateChange?.onColumnOrderChange);
131
+ const [columnPinning, setColumnPinning] = useControlled(state?.columnPinning, initialState?.columnPinning ?? {}, onStateChange?.onColumnPinningChange);
132
+ const [rowSelection, setRowSelection] = useControlled(state?.rowSelection, initialState?.rowSelection ?? {}, onStateChange?.onRowSelectionChange);
133
+ const [pagination, setPagination] = useControlled(state?.pagination, initialState?.pagination ?? {
134
+ pageIndex: 0,
135
+ pageSize: 10
136
+ }, onStateChange?.onPaginationChange);
137
+ const [globalFilter, setGlobalFilter] = useControlled(state?.globalFilter, initialState?.globalFilter ?? "", onStateChange?.onGlobalFilterChange);
138
+ const [expanded, setExpanded] = useControlled(state?.expanded, initialState?.expanded ?? {}, onStateChange?.onExpandedChange);
139
+ return {
140
+ table: useReactTable({
141
+ data,
142
+ columns,
143
+ state: {
144
+ sorting,
145
+ columnFilters,
146
+ columnVisibility,
147
+ columnOrder,
148
+ columnPinning,
149
+ rowSelection,
150
+ pagination,
151
+ globalFilter,
152
+ expanded
153
+ },
154
+ enableSorting: mergedFeatures.sorting,
155
+ enableMultiSort: mergedFeatures.multiSort,
156
+ enableFilters: mergedFeatures.filtering,
157
+ enableGlobalFilter: mergedFeatures.globalFilter,
158
+ enableRowSelection: mergedFeatures.rowSelection,
159
+ enableHiding: mergedFeatures.columnVisibility,
160
+ enableColumnPinning: mergedFeatures.columnPinning,
161
+ enableColumnResizing: mergedFeatures.columnResizing,
162
+ columnResizeMode: "onChange",
163
+ onSortingChange: setSorting,
164
+ onColumnFiltersChange: setColumnFilters,
165
+ onColumnVisibilityChange: setColumnVisibility,
166
+ onColumnOrderChange: setColumnOrder,
167
+ onColumnPinningChange: setColumnPinning,
168
+ onRowSelectionChange: setRowSelection,
169
+ onPaginationChange: setPagination,
170
+ onGlobalFilterChange: setGlobalFilter,
171
+ onExpandedChange: setExpanded,
172
+ getCoreRowModel: getCoreRowModel(),
173
+ getSortedRowModel: mergedFeatures.sorting ? getSortedRowModel() : void 0,
174
+ getFilteredRowModel: mergedFeatures.filtering || mergedFeatures.globalFilter ? getFilteredRowModel() : void 0,
175
+ getPaginationRowModel: mergedFeatures.pagination && !tableOptions?.manualPagination ? getPaginationRowModel() : void 0,
176
+ getExpandedRowModel: mergedFeatures.expanding ? getExpandedRowModel() : void 0,
177
+ ...tableOptions
178
+ }),
179
+ features: mergedFeatures
180
+ };
181
+ }
182
+ /**
183
+ * 监听容器横向滚动,返回左右阴影显示状态
184
+ *
185
+ * 用于固定列阴影提示
186
+ */
187
+ function useScrollShadow(wrapperRef, containerSelector = "[data-slot=\"table-container\"]") {
188
+ const [scrollShadow, setScrollShadow] = useState({
189
+ left: false,
190
+ right: false
191
+ });
192
+ useEffect(() => {
193
+ const scrollContainer = wrapperRef.current?.querySelector(containerSelector);
194
+ if (!scrollContainer) return;
195
+ const update = () => {
196
+ const scrollLeft = scrollContainer.scrollLeft;
197
+ const maxScrollLeft = Math.max(0, scrollContainer.scrollWidth - scrollContainer.clientWidth);
198
+ const next = {
199
+ left: scrollLeft > 1,
200
+ right: maxScrollLeft - scrollLeft > 1
201
+ };
202
+ setScrollShadow((current) => current.left === next.left && current.right === next.right ? current : next);
203
+ };
204
+ update();
205
+ scrollContainer.addEventListener("scroll", update, { passive: true });
206
+ const resizeObserver = new ResizeObserver(update);
207
+ resizeObserver.observe(scrollContainer);
208
+ const tableElement = scrollContainer.querySelector("table");
209
+ if (tableElement) resizeObserver.observe(tableElement);
210
+ return () => {
211
+ scrollContainer.removeEventListener("scroll", update);
212
+ resizeObserver.disconnect();
213
+ };
214
+ }, [wrapperRef, containerSelector]);
215
+ return scrollShadow;
216
+ }
217
+ //#endregion
218
+ //#region src/table/utils.ts
219
+ /**
220
+ * 展开面板 row 的 DOM id 协议
221
+ *
222
+ * `DataTable` 渲染面板 row 时把这个 id 设到 `<tr id>` 上,
223
+ * `RowExpandToggle` 把它写进按钮的 `aria-controls`,二者必须保持一致
224
+ */
225
+ var panelIdFor = (rowId) => `data-table-row-${rowId}-panel`;
226
+ /**
227
+ * 计算固定列(sticky)需要的样式
228
+ */
229
+ function getCommonPinningStyles(column) {
230
+ const isPinned = column.getIsPinned();
231
+ return {
232
+ left: isPinned === "left" ? `${column.getStart("left")}px` : void 0,
233
+ opacity: isPinned ? .98 : 1,
234
+ position: isPinned ? "sticky" : "relative",
235
+ right: isPinned === "right" ? `${column.getAfter("right")}px` : void 0,
236
+ width: `${column.getSize()}px`,
237
+ zIndex: isPinned ? 1 : 0
238
+ };
239
+ }
240
+ /**
241
+ * 计算固定列阴影 className
242
+ */
243
+ function getColumnPinningClassName(column, scrollShadow) {
244
+ const isPinned = column.getIsPinned();
245
+ const isLastLeftPinnedColumn = isPinned === "left" && column.getIsLastColumn("left");
246
+ const isFirstRightPinnedColumn = isPinned === "right" && column.getIsFirstColumn("right");
247
+ let styles = "bg-background after:transition-all";
248
+ if (isLastLeftPinnedColumn) styles = cn(styles, "after:absolute after:bottom-0 after:top-0 after:right-0 after:w-[30px] after:translate-x-full after:content-[''] after:shadow-[inset_15px_0_8px_-8px_#0000000d] dark:after:shadow-[inset_15px_0_8px_-8px_#ffffff0d]");
249
+ if (isLastLeftPinnedColumn && !scrollShadow.left) styles = cn(styles, "after:shadow-[inset_15px_0_8px_-8px_#00000000]");
250
+ if (isFirstRightPinnedColumn) styles = cn(styles, "after:absolute after:bottom-0 after:top-0 after:left-0 after:w-[30px] after:-translate-x-full after:content-[''] after:shadow-[inset_-15px_0_8px_-8px_#0000000d] dark:after:shadow-[inset_-15px_0_8px_-8px_#ffffff0d]");
251
+ if (isFirstRightPinnedColumn && !scrollShadow.right) styles = cn(styles, "after:shadow-[inset_-15px_0_8px_-8px_#00000000]");
252
+ return styles;
253
+ }
254
+ function ensureRow(map, rowIndex) {
255
+ let entry = map.get(rowIndex);
256
+ if (!entry) {
257
+ entry = /* @__PURE__ */ new Map();
258
+ map.set(rowIndex, entry);
259
+ }
260
+ return entry;
261
+ }
262
+ function setSpan(map, rowIndex, columnId, next) {
263
+ const row = ensureRow(map, rowIndex);
264
+ const previous = row.get(columnId) ?? {
265
+ rowSpan: 1,
266
+ colSpan: 1
267
+ };
268
+ row.set(columnId, {
269
+ ...previous,
270
+ ...next
271
+ });
272
+ }
273
+ /**
274
+ * 根据每列 `meta.rowSpan` / `meta.colSpan` 计算每行每列的合并跨度
275
+ *
276
+ * `0` 表示该单元格不应渲染(被合并掉了)
277
+ */
278
+ function computeCellSpans(rows, columns) {
279
+ const map = /* @__PURE__ */ new Map();
280
+ if (rows.length === 0) return map;
281
+ columns.forEach((column) => {
282
+ const meta = column.columnDef.meta;
283
+ const rowSpanCfg = meta?.rowSpan;
284
+ if (!rowSpanCfg) return;
285
+ if (rowSpanCfg === true || rowSpanCfg === "auto") {
286
+ const parentKey = meta?.rowSpanParent;
287
+ let index = 0;
288
+ while (index < rows.length) {
289
+ const value = rows[index].getValue(column.id);
290
+ const parentValue = parentKey ? rows[index].getValue(parentKey) : void 0;
291
+ let span = 1;
292
+ while (index + span < rows.length) {
293
+ const nextValue = rows[index + span].getValue(column.id);
294
+ const nextParent = parentKey ? rows[index + span].getValue(parentKey) : void 0;
295
+ if (Object.is(nextValue, value) && (!parentKey || Object.is(nextParent, parentValue))) span += 1;
296
+ else break;
297
+ }
298
+ if (span > 1) {
299
+ setSpan(map, index, column.id, { rowSpan: span });
300
+ for (let offset = 1; offset < span; offset += 1) setSpan(map, index + offset, column.id, {
301
+ rowSpan: 0,
302
+ colSpan: 0
303
+ });
304
+ }
305
+ index += span;
306
+ }
307
+ } else if (typeof rowSpanCfg === "function") rows.forEach((row, rowIndex) => {
308
+ const span = rowSpanCfg({
309
+ row,
310
+ rows,
311
+ rowIndex,
312
+ value: row.getValue(column.id)
313
+ });
314
+ const existing = map.get(rowIndex)?.get(column.id);
315
+ if (existing && existing.rowSpan === 0) return;
316
+ if (span === 0) setSpan(map, rowIndex, column.id, {
317
+ rowSpan: 0,
318
+ colSpan: 0
319
+ });
320
+ else if (span > 1) {
321
+ setSpan(map, rowIndex, column.id, { rowSpan: span });
322
+ for (let offset = 1; offset < span; offset += 1) setSpan(map, rowIndex + offset, column.id, {
323
+ rowSpan: 0,
324
+ colSpan: 0
325
+ });
326
+ }
327
+ });
328
+ });
329
+ rows.forEach((row, rowIndex) => {
330
+ const cells = row.getVisibleCells();
331
+ let skipUntil = -1;
332
+ cells.forEach((cell, cellIndex) => {
333
+ const columnId = cell.column.id;
334
+ const rowMap = ensureRow(map, rowIndex);
335
+ const existing = rowMap.get(columnId);
336
+ if (existing && existing.rowSpan === 0) return;
337
+ if (cellIndex <= skipUntil) {
338
+ rowMap.set(columnId, {
339
+ rowSpan: existing?.rowSpan ?? 1,
340
+ colSpan: 0
341
+ });
342
+ return;
343
+ }
344
+ const colSpanCfg = cell.column.columnDef.meta?.colSpan;
345
+ if (typeof colSpanCfg === "function") {
346
+ const colSpan = colSpanCfg({
347
+ row,
348
+ rows,
349
+ rowIndex,
350
+ column: cell.column
351
+ });
352
+ if (colSpan === 0) rowMap.set(columnId, {
353
+ rowSpan: existing?.rowSpan ?? 1,
354
+ colSpan: 0
355
+ });
356
+ else if (colSpan > 1) {
357
+ rowMap.set(columnId, {
358
+ rowSpan: existing?.rowSpan ?? 1,
359
+ colSpan
360
+ });
361
+ skipUntil = cellIndex + colSpan - 1;
362
+ }
363
+ }
364
+ });
365
+ });
366
+ return map;
367
+ }
368
+ //#endregion
369
+ //#region src/table/components/data-table-column-header.tsx
370
+ /**
371
+ * 表头单元格,封装排序、列宽拖拽、固定列阴影
372
+ */
373
+ function DataTableColumnHeader({ header, resizable, scrollShadow }) {
374
+ const canSort = header.column.getCanSort();
375
+ const isSorted = header.column.getIsSorted();
376
+ const canResize = resizable && header.column.getCanResize();
377
+ return /* @__PURE__ */ jsx(TableHead, {
378
+ "data-slot": "data-table-column-header",
379
+ colSpan: header.colSpan,
380
+ style: getCommonPinningStyles(header.column),
381
+ className: getColumnPinningClassName(header.column, scrollShadow),
382
+ children: header.isPlaceholder ? null : /* @__PURE__ */ jsxs("div", {
383
+ className: "flex items-center gap-1",
384
+ children: [/* @__PURE__ */ jsxs("div", {
385
+ className: cn("flex flex-1 items-center gap-1", canSort && "cursor-pointer select-none"),
386
+ onClick: canSort ? header.column.getToggleSortingHandler() : void 0,
387
+ children: [flexRender(header.column.columnDef.header, header.getContext()), canSort && /* @__PURE__ */ jsxs("span", {
388
+ className: "flex flex-col items-center",
389
+ children: [/* @__PURE__ */ jsx(ChevronUpIcon, { className: cn("text-muted-foreground -mb-1 size-[1em] transition", isSorted === "asc" && "text-foreground") }), /* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("text-muted-foreground -mt-1 size-[1em] transition", isSorted === "desc" && "text-foreground") })]
390
+ })]
391
+ }), canResize && /* @__PURE__ */ jsx("span", {
392
+ onDoubleClick: header.column.resetSize,
393
+ onMouseDown: header.getResizeHandler(),
394
+ onTouchStart: header.getResizeHandler(),
395
+ className: cn("ml-auto h-[stretch] w-px cursor-col-resize touch-none select-none", "hover:bg-primary/40 data-[resizing=true]:bg-primary"),
396
+ "data-resizing": header.column.getIsResizing() || void 0
397
+ })]
398
+ })
399
+ });
400
+ }
401
+ //#endregion
402
+ //#region src/table/components/data-table-pagination.tsx
403
+ var DEFAULT_PAGE_SIZE_OPTIONS = [
404
+ 10,
405
+ 20,
406
+ 30,
407
+ 50,
408
+ 100
409
+ ];
410
+ /**
411
+ * 表格分页栏:总行数 / 每页数量 / 页码跳转
412
+ */
413
+ function DataTablePagination({ table, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, showSelectionCount = true }) {
414
+ const pagination = table.getState().pagination;
415
+ const selectedCount = Object.keys(table.getState().rowSelection).length;
416
+ const totalRows = table.getRowCount();
417
+ const pageCount = table.getPageCount();
418
+ return /* @__PURE__ */ jsxs("div", {
419
+ className: "text-muted-foreground flex flex-wrap items-center justify-between gap-2 py-2 text-sm",
420
+ children: [
421
+ /* @__PURE__ */ jsx("div", {
422
+ className: "flex-1",
423
+ children: showSelectionCount && selectedCount > 0 ? `已选择 ${selectedCount} / ${totalRows} 条` : `共 ${totalRows} 条`
424
+ }),
425
+ /* @__PURE__ */ jsxs("div", {
426
+ className: "flex items-center gap-2",
427
+ children: [/* @__PURE__ */ jsx("span", { children: "每页" }), /* @__PURE__ */ jsx(NativeSelect, {
428
+ size: "sm",
429
+ value: pagination.pageSize,
430
+ onChange: (event) => table.setPageSize(Number(event.target.value)),
431
+ children: pageSizeOptions.map((size) => /* @__PURE__ */ jsx(NativeSelectOption, {
432
+ value: size,
433
+ children: size
434
+ }, size))
435
+ })]
436
+ }),
437
+ /* @__PURE__ */ jsxs("div", {
438
+ className: "flex items-center gap-1",
439
+ children: [
440
+ /* @__PURE__ */ jsxs("span", {
441
+ className: "px-2",
442
+ children: [
443
+ "第 ",
444
+ pagination.pageIndex + 1,
445
+ " / ",
446
+ Math.max(pageCount, 1),
447
+ " 页"
448
+ ]
449
+ }),
450
+ /* @__PURE__ */ jsx(Button, {
451
+ variant: "outline",
452
+ size: "icon-sm",
453
+ "aria-label": "第一页",
454
+ onClick: () => table.setPageIndex(0),
455
+ disabled: !table.getCanPreviousPage(),
456
+ children: /* @__PURE__ */ jsx(ChevronsLeftIcon, {})
457
+ }),
458
+ /* @__PURE__ */ jsx(Button, {
459
+ variant: "outline",
460
+ size: "icon-sm",
461
+ "aria-label": "上一页",
462
+ onClick: () => table.previousPage(),
463
+ disabled: !table.getCanPreviousPage(),
464
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
465
+ }),
466
+ /* @__PURE__ */ jsx(Button, {
467
+ variant: "outline",
468
+ size: "icon-sm",
469
+ "aria-label": "下一页",
470
+ onClick: () => table.nextPage(),
471
+ disabled: !table.getCanNextPage(),
472
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
473
+ }),
474
+ /* @__PURE__ */ jsx(Button, {
475
+ variant: "outline",
476
+ size: "icon-sm",
477
+ "aria-label": "最后一页",
478
+ onClick: () => table.setPageIndex(pageCount - 1),
479
+ disabled: !table.getCanNextPage(),
480
+ children: /* @__PURE__ */ jsx(ChevronsRightIcon, {})
481
+ })
482
+ ]
483
+ })
484
+ ]
485
+ });
486
+ }
487
+ //#endregion
488
+ //#region src/table/components/data-table-virtual-body.tsx
489
+ var DEFAULT_ROW_HEIGHT = 36;
490
+ var DEFAULT_MAX_HEIGHT = 480;
491
+ var DEFAULT_OVERSCAN = 10;
492
+ /**
493
+ * 行虚拟滚动表格:使用 grid 布局 + `useVirtualizer` 仅渲染可视区域
494
+ *
495
+ * 因表格采用 grid 布局,固定列 / 多级表头暂不支持
496
+ */
497
+ function DataTableVirtualBody({ table, loading, empty = "暂无数据", options, bordered = false, className }) {
498
+ const estimateRowHeight = options?.estimateRowHeight ?? DEFAULT_ROW_HEIGHT;
499
+ const maxHeight = options?.maxHeight ?? DEFAULT_MAX_HEIGHT;
500
+ const overscan = options?.overscan ?? DEFAULT_OVERSCAN;
501
+ const containerRef = useRef(null);
502
+ const rows = table.getRowModel().rows;
503
+ const totalWidth = table.getTotalSize();
504
+ const virtualizer = useVirtualizer({
505
+ count: rows.length,
506
+ getScrollElement: () => containerRef.current,
507
+ estimateSize: () => estimateRowHeight,
508
+ overscan
509
+ });
510
+ const virtualItems = virtualizer.getVirtualItems();
511
+ const showEmpty = !loading && rows.length === 0;
512
+ const skeletonRowCount = Math.max(1, Math.floor((typeof maxHeight === "number" ? maxHeight : 480) / estimateRowHeight));
513
+ return /* @__PURE__ */ jsx("div", {
514
+ ref: containerRef,
515
+ "data-slot": "table-container",
516
+ className: cn("bg-background relative overflow-auto rounded-md", bordered && "border", className),
517
+ style: { height: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight },
518
+ children: /* @__PURE__ */ jsxs("table", {
519
+ "data-slot": "table",
520
+ className: "grid text-sm",
521
+ style: {
522
+ width: `${totalWidth}px`,
523
+ minWidth: "100%"
524
+ },
525
+ children: [/* @__PURE__ */ jsx("thead", {
526
+ "data-slot": "table-header",
527
+ className: "bg-background sticky top-0 z-10 grid border-b",
528
+ children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx("tr", {
529
+ className: "flex w-full",
530
+ children: headerGroup.headers.map((header, headerIndex) => {
531
+ const canSort = header.column.getCanSort();
532
+ const isSorted = header.column.getIsSorted();
533
+ const isLast = headerIndex === headerGroup.headers.length - 1;
534
+ return /* @__PURE__ */ jsxs("th", {
535
+ "data-slot": "table-head",
536
+ className: cn("text-foreground flex h-10 shrink-0 items-center gap-1 px-2 text-left font-medium", canSort && "cursor-pointer select-none", bordered && !isLast && "border-r"),
537
+ style: { width: `${header.getSize()}px` },
538
+ onClick: canSort ? header.column.getToggleSortingHandler() : void 0,
539
+ children: [header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()), canSort && /* @__PURE__ */ jsxs("span", {
540
+ className: "ml-1 flex flex-col items-center",
541
+ children: [/* @__PURE__ */ jsx(ChevronUpIcon, { className: cn("text-muted-foreground -mb-1 size-3 transition", isSorted === "asc" && "text-foreground") }), /* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("text-muted-foreground -mt-1 size-3 transition", isSorted === "desc" && "text-foreground") })]
542
+ })]
543
+ }, header.id);
544
+ })
545
+ }, headerGroup.id))
546
+ }), /* @__PURE__ */ jsx("tbody", {
547
+ "data-slot": "table-body",
548
+ className: "relative grid",
549
+ style: { height: loading ? `${skeletonRowCount * estimateRowHeight}px` : `${virtualizer.getTotalSize()}px` },
550
+ children: loading ? Array.from({ length: skeletonRowCount }).map((_, rowIndex) => {
551
+ const leafColumns = table.getVisibleLeafColumns();
552
+ return /* @__PURE__ */ jsx("tr", {
553
+ className: "flex w-full border-b",
554
+ style: { height: `${estimateRowHeight}px` },
555
+ children: leafColumns.map((column, columnIndex) => {
556
+ const isLast = columnIndex === leafColumns.length - 1;
557
+ return /* @__PURE__ */ jsx("td", {
558
+ className: cn("flex items-center px-2", bordered && !isLast && "border-r"),
559
+ style: { width: `${column.getSize()}px` },
560
+ children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })
561
+ }, column.id);
562
+ })
563
+ }, `loading-${rowIndex}`);
564
+ }) : showEmpty ? /* @__PURE__ */ jsx("tr", {
565
+ className: "flex w-full",
566
+ children: /* @__PURE__ */ jsx("td", {
567
+ colSpan: table.getVisibleLeafColumns().length,
568
+ className: "text-muted-foreground flex h-24 w-full items-center justify-center",
569
+ children: empty
570
+ })
571
+ }) : virtualItems.map((virtualRow) => {
572
+ const row = rows[virtualRow.index];
573
+ const cells = row.getVisibleCells();
574
+ return /* @__PURE__ */ jsx("tr", {
575
+ "data-index": virtualRow.index,
576
+ "data-state": row.getIsSelected() ? "selected" : void 0,
577
+ ref: (node) => virtualizer.measureElement(node),
578
+ className: "hover:bg-muted/50 data-[state=selected]:bg-muted absolute flex w-full border-b",
579
+ style: { transform: `translateY(${virtualRow.start}px)` },
580
+ children: cells.map((cell, cellIndex) => {
581
+ const isLast = cellIndex === cells.length - 1;
582
+ return /* @__PURE__ */ jsx("td", {
583
+ "data-slot": "table-cell",
584
+ className: cn("text-foreground flex items-center px-2", bordered && !isLast && "border-r"),
585
+ style: {
586
+ width: `${cell.column.getSize()}px`,
587
+ height: `${estimateRowHeight}px`
588
+ },
589
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
590
+ }, cell.id);
591
+ })
592
+ }, row.id);
593
+ })
594
+ })]
595
+ })
596
+ });
597
+ }
598
+ //#endregion
599
+ //#region src/table/components/row-drag-handle.tsx
600
+ var RowDragHandleContext = createContext(null);
601
+ /** 在 cell 内任意位置放此组件即可成为行拖拽手柄 */
602
+ function RowDragHandle({ className, ...props }) {
603
+ const handle = useContext(RowDragHandleContext);
604
+ if (!handle) return null;
605
+ return /* @__PURE__ */ jsx("button", {
606
+ type: "button",
607
+ ref: handle.setActivatorNodeRef,
608
+ "aria-label": "拖动排序",
609
+ className: cn("text-muted-foreground hover:text-foreground inline-flex size-6 cursor-grab items-center justify-center rounded-md focus-visible:outline-none active:cursor-grabbing", className),
610
+ ...handle.attributes,
611
+ ...handle.listeners,
612
+ ...props,
613
+ children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-4" })
614
+ });
615
+ }
616
+ //#endregion
617
+ //#region src/table/components/data-table.tsx
618
+ var ROW_DND_HANDLE_COLUMN_ID = "__row_dnd_handle__";
619
+ var DEFAULT_SKELETON_ROWS = 8;
620
+ /** ReactNode 但语义上等同于"不渲染"的值(null / undefined / boolean) */
621
+ function isRenderablePanel(value) {
622
+ return value !== null && value !== void 0 && typeof value !== "boolean";
623
+ }
624
+ /**
625
+ * 渲染一行 cells —— 处理列合并 / 跨列 / 固定列样式
626
+ *
627
+ * 抽成共享函数,sortable 与非 sortable 两条路径都用同一份单元格生成逻辑
628
+ */
629
+ function renderRowCells(row, rowIndex, cellSpans, scrollShadow) {
630
+ return row.getVisibleCells().map((cell) => {
631
+ const span = cellSpans.get(rowIndex)?.get(cell.column.id);
632
+ const rowSpan = span?.rowSpan ?? 1;
633
+ const colSpan = span?.colSpan ?? 1;
634
+ if (rowSpan === 0 || colSpan === 0) return null;
635
+ return /* @__PURE__ */ jsx(TableCell, {
636
+ rowSpan: rowSpan > 1 ? rowSpan : void 0,
637
+ colSpan: colSpan > 1 ? colSpan : void 0,
638
+ style: getCommonPinningStyles(cell.column),
639
+ className: getColumnPinningClassName(cell.column, scrollShadow),
640
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
641
+ }, cell.id);
642
+ });
643
+ }
644
+ /** 渲染展开面板 row —— 跨满全列的单格 TableRow */
645
+ function renderExpandedPanel({ rowId, panel, visibleColumnCount, isSelected, style, isDragging }) {
646
+ return /* @__PURE__ */ jsx(TableRow, {
647
+ id: panelIdFor(rowId),
648
+ "data-slot": "data-table-expanded-row",
649
+ "data-state": isSelected ? "selected" : void 0,
650
+ style,
651
+ className: cn("bg-muted/30 hover:bg-muted/30", isDragging && "shadow-md"),
652
+ children: /* @__PURE__ */ jsx(TableCell, {
653
+ colSpan: visibleColumnCount,
654
+ className: "p-4",
655
+ children: panel
656
+ })
657
+ });
658
+ }
659
+ function buildRowDndColumn() {
660
+ return {
661
+ id: ROW_DND_HANDLE_COLUMN_ID,
662
+ size: 36,
663
+ enableSorting: false,
664
+ enableHiding: false,
665
+ enableResizing: false,
666
+ enableColumnFilter: false,
667
+ enablePinning: false,
668
+ header: "",
669
+ cell: () => /* @__PURE__ */ jsx(RowDragHandle, {})
670
+ };
671
+ }
672
+ /** 单行 sortable 包装,把 dnd-kit 属性透传到 TableRow + Context */
673
+ function SortableTableRow({ row, rowIndex, cellSpans, scrollShadow, onRowClick, expandedPanel, visibleColumnCount }) {
674
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id: row.id });
675
+ const style = {
676
+ transform: CSS.Transform.toString(transform),
677
+ transition,
678
+ position: isDragging ? "relative" : void 0,
679
+ zIndex: isDragging ? 1 : void 0
680
+ };
681
+ const isSelected = row.getIsSelected();
682
+ const showPanel = expandedPanel !== void 0;
683
+ return /* @__PURE__ */ jsxs(RowDragHandleContext.Provider, {
684
+ value: {
685
+ attributes,
686
+ listeners,
687
+ setActivatorNodeRef,
688
+ isDragging
689
+ },
690
+ children: [/* @__PURE__ */ jsx(TableRow, {
691
+ ref: setNodeRef,
692
+ style,
693
+ "data-state": isSelected ? "selected" : void 0,
694
+ className: cn(isDragging && "bg-muted/70 shadow-md", onRowClick && "cursor-pointer"),
695
+ onClick: onRowClick ? () => onRowClick(row.original) : void 0,
696
+ children: renderRowCells(row, rowIndex, cellSpans, scrollShadow)
697
+ }), showPanel ? renderExpandedPanel({
698
+ rowId: row.id,
699
+ panel: expandedPanel,
700
+ visibleColumnCount,
701
+ isSelected,
702
+ style,
703
+ isDragging
704
+ }) : null]
705
+ });
706
+ }
707
+ /**
708
+ * 全功能数据表格
709
+ *
710
+ * 在 `Table` 基础组件之上,基于 `@tanstack/react-table`
711
+ * 提供排序、筛选、分页、行选择、列显隐 / 拖拽 / 固定 / 调宽、行虚拟滚动、行拖拽排序等能力
712
+ *
713
+ * 可通过 `ref` 拿到底层 TanStack `table` 实例 ([[DataTableHandle]]) 进行命令式操作
714
+ */
715
+ function DataTableInner({ columns, data, loading, state, initialState, onStateChange, features, renderToolbar, renderPagination, empty = "暂无数据", pageSizeOptions, className, tableClassName, onRowClick, onRowOrderChange, rowDndHandle = "auto", tableOptions, virtual = false, bordered = false, expandedRowRender }, ref) {
716
+ const virtualEnabled = Boolean(virtual);
717
+ const virtualOptions = virtual && typeof virtual === "object" ? virtual : void 0;
718
+ const rowDndEnabled = !virtualEnabled && Boolean(features?.rowDnd);
719
+ const autoHandle = rowDndEnabled && rowDndHandle === "auto";
720
+ const expandedRowRenderEffective = virtualEnabled ? void 0 : expandedRowRender;
721
+ const virtualConflicts = virtualEnabled && Boolean(features?.rowDnd || features?.expanding || expandedRowRender);
722
+ const rowDndMissingGetRowId = rowDndEnabled && !tableOptions?.getRowId;
723
+ useEffect(() => {
724
+ if (virtualConflicts) console.warn("[DataTable] virtual 模式与 rowDnd / expanding / expandedRowRender 不兼容,已自动忽略相关功能");
725
+ if (rowDndMissingGetRowId) console.warn("[DataTable] 启用 features.rowDnd 时,请通过 tableOptions.getRowId 提供稳定的行 id,否则拖拽顺序会错乱");
726
+ }, [virtualConflicts, rowDndMissingGetRowId]);
727
+ const effectiveFeatures = virtualEnabled ? {
728
+ ...features,
729
+ pagination: false,
730
+ rowDnd: false,
731
+ expanding: false
732
+ } : features;
733
+ const { table, features: mergedFeatures } = useDataTable({
734
+ data,
735
+ columns: useMemo(() => autoHandle && !columns.some((c) => c.id === ROW_DND_HANDLE_COLUMN_ID) ? [buildRowDndColumn(), ...columns] : columns, [columns, autoHandle]),
736
+ features: effectiveFeatures,
737
+ state,
738
+ initialState,
739
+ onStateChange,
740
+ tableOptions: useMemo(() => {
741
+ if (!expandedRowRenderEffective) return tableOptions;
742
+ if (tableOptions?.getRowCanExpand) return tableOptions;
743
+ return {
744
+ ...tableOptions,
745
+ getRowCanExpand: () => true
746
+ };
747
+ }, [tableOptions, expandedRowRenderEffective])
748
+ });
749
+ useImperativeHandle(ref, () => table, [table]);
750
+ const wrapperRef = useRef(null);
751
+ const scrollShadow = useScrollShadow(wrapperRef);
752
+ const visibleLeafColumns = table.getVisibleLeafColumns();
753
+ const visibleColumnCount = visibleLeafColumns.length;
754
+ const rows = table.getRowModel().rows;
755
+ const cellSpans = computeCellSpans(rows, visibleLeafColumns);
756
+ const rowIds = rows.map((row) => row.id);
757
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
758
+ const handleDragEnd = (event) => {
759
+ const { active, over } = event;
760
+ if (!over || active.id === over.id) return;
761
+ const oldIndex = rows.findIndex((row) => row.id === String(active.id));
762
+ const newIndex = rows.findIndex((row) => row.id === String(over.id));
763
+ if (oldIndex === -1 || newIndex === -1) return;
764
+ const nextData = arrayMove(data, oldIndex, newIndex);
765
+ onRowOrderChange?.(nextData, {
766
+ oldIndex,
767
+ newIndex,
768
+ activeId: String(active.id),
769
+ overId: String(over.id)
770
+ });
771
+ };
772
+ const toolbarNode = renderToolbar?.(table);
773
+ const skeletonRowCount = mergedFeatures.pagination ? table.getState().pagination.pageSize : DEFAULT_SKELETON_ROWS;
774
+ const bodyContent = loading ? Array.from({ length: skeletonRowCount }).map((_, rowIndex) => /* @__PURE__ */ jsx(TableRow, { children: table.getVisibleLeafColumns().map((column) => /* @__PURE__ */ jsx(TableCell, {
775
+ style: getCommonPinningStyles(column),
776
+ className: getColumnPinningClassName(column, scrollShadow),
777
+ children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" })
778
+ }, column.id)) }, `loading-${rowIndex}`)) : rows.length ? rows.map((row, rowIndex) => {
779
+ const rawPanel = expandedRowRenderEffective && row.getIsExpanded() ? expandedRowRenderEffective(row) : void 0;
780
+ const expandedPanel = isRenderablePanel(rawPanel) ? rawPanel : void 0;
781
+ const isSelected = row.getIsSelected();
782
+ if (rowDndEnabled) return /* @__PURE__ */ jsx(SortableTableRow, {
783
+ row,
784
+ rowIndex,
785
+ cellSpans,
786
+ scrollShadow,
787
+ onRowClick,
788
+ expandedPanel,
789
+ visibleColumnCount
790
+ }, row.id);
791
+ if (expandedPanel === void 0) return /* @__PURE__ */ jsx(TableRow, {
792
+ "data-state": isSelected ? "selected" : void 0,
793
+ className: onRowClick ? "cursor-pointer" : void 0,
794
+ onClick: onRowClick ? () => onRowClick(row.original) : void 0,
795
+ children: renderRowCells(row, rowIndex, cellSpans, scrollShadow)
796
+ }, row.id);
797
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TableRow, {
798
+ "data-state": isSelected ? "selected" : void 0,
799
+ className: onRowClick ? "cursor-pointer" : void 0,
800
+ onClick: onRowClick ? () => onRowClick(row.original) : void 0,
801
+ children: renderRowCells(row, rowIndex, cellSpans, scrollShadow)
802
+ }), renderExpandedPanel({
803
+ rowId: row.id,
804
+ panel: expandedPanel,
805
+ visibleColumnCount,
806
+ isSelected
807
+ })] }, row.id);
808
+ }) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
809
+ colSpan: visibleColumnCount,
810
+ className: "text-muted-foreground h-24 text-center",
811
+ children: empty
812
+ }) });
813
+ const standardBody = /* @__PURE__ */ jsx("div", {
814
+ ref: wrapperRef,
815
+ className: cn("rounded-md", bordered && "border [&_td]:border-r [&_th]:border-r [&_tr>*:last-child]:border-r-0", tableClassName),
816
+ children: /* @__PURE__ */ jsxs(Table, {
817
+ className: "min-w-max",
818
+ style: mergedFeatures.columnResizing ? { width: table.getTotalSize() } : void 0,
819
+ children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(DataTableColumnHeader, {
820
+ header,
821
+ resizable: mergedFeatures.columnResizing,
822
+ scrollShadow
823
+ }, header.id)) }, headerGroup.id)) }), /* @__PURE__ */ jsx(TableBody, { children: rowDndEnabled ? /* @__PURE__ */ jsx(SortableContext, {
824
+ items: rowIds,
825
+ strategy: verticalListSortingStrategy,
826
+ children: bodyContent
827
+ }) : bodyContent })]
828
+ })
829
+ });
830
+ const bodyNode = virtualEnabled ? /* @__PURE__ */ jsx(DataTableVirtualBody, {
831
+ table,
832
+ loading,
833
+ empty,
834
+ options: virtualOptions,
835
+ bordered,
836
+ className: tableClassName
837
+ }) : rowDndEnabled ? /* @__PURE__ */ jsx(DndContext, {
838
+ sensors,
839
+ collisionDetection: closestCenter,
840
+ modifiers: [restrictToVerticalAxis],
841
+ onDragEnd: handleDragEnd,
842
+ children: standardBody
843
+ }) : standardBody;
844
+ return /* @__PURE__ */ jsxs("div", {
845
+ "data-slot": "data-table",
846
+ className: cn("flex flex-col", className),
847
+ children: [
848
+ toolbarNode,
849
+ bodyNode,
850
+ mergedFeatures.pagination && (renderPagination !== void 0 ? renderPagination(table) : /* @__PURE__ */ jsx(DataTablePagination, {
851
+ table,
852
+ pageSizeOptions
853
+ }))
854
+ ]
855
+ });
856
+ }
857
+ /**
858
+ * forwardRef 包装,泛型用 cast 保留 —— 否则会被 forwardRef 的签名擦除成 unknown
859
+ */
860
+ var DataTable = forwardRef(DataTableInner);
861
+ //#endregion
862
+ //#region src/table/components/data-table-view-options.tsx
863
+ /**
864
+ * 列显隐 / 排序 / 固定 设置面板
865
+ *
866
+ * 通过 DropdownMenu 弹出列表,内置 DnD-Kit 排序与固定按钮
867
+ */
868
+ function DataTableViewOptions({ table, enableOrdering = false, enablePinning = false, trigger }) {
869
+ const draggableColumns = table.getAllLeafColumns().filter((column) => column.getCanHide());
870
+ const draggableColumnIds = draggableColumns.map((column) => column.id);
871
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
872
+ const handleDragEnd = ({ active, over }) => {
873
+ if (!over || active.id === over.id) return;
874
+ const currentOrder = table.getState().columnOrder;
875
+ const allColumnIds = table.getAllLeafColumns().map((column) => column.id);
876
+ const orderedColumnIds = currentOrder.length ? currentOrder.filter((columnId) => allColumnIds.includes(columnId)) : allColumnIds;
877
+ const nextOrder = [...orderedColumnIds, ...allColumnIds.filter((columnId) => !orderedColumnIds.includes(columnId))];
878
+ const oldIndex = nextOrder.indexOf(String(active.id));
879
+ const newIndex = nextOrder.indexOf(String(over.id));
880
+ if (oldIndex === -1 || newIndex === -1) return;
881
+ table.setColumnOrder(arrayMove(nextOrder, oldIndex, newIndex));
882
+ };
883
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, { render: trigger ?? /* @__PURE__ */ jsxs(Button, {
884
+ variant: "outline",
885
+ size: "sm",
886
+ children: [/* @__PURE__ */ jsx(Settings2Icon, {}), "视图"]
887
+ }) }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
888
+ align: "end",
889
+ className: "min-w-56",
890
+ children: [
891
+ /* @__PURE__ */ jsx(DropdownMenuGroup, { children: /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "显示的列" }) }),
892
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
893
+ enableOrdering ? /* @__PURE__ */ jsx(DndContext, {
894
+ sensors,
895
+ collisionDetection: closestCenter,
896
+ modifiers: [restrictToVerticalAxis],
897
+ onDragEnd: handleDragEnd,
898
+ children: /* @__PURE__ */ jsx(SortableContext, {
899
+ items: enableOrdering ? draggableColumnIds : [],
900
+ strategy: verticalListSortingStrategy,
901
+ children: draggableColumns.map((column) => /* @__PURE__ */ jsx(SortableColumnItem, {
902
+ column,
903
+ enablePinning
904
+ }, column.id))
905
+ })
906
+ }) : draggableColumns.map((column) => /* @__PURE__ */ jsx(ColumnVisibilityItem, {
907
+ column,
908
+ enablePinning
909
+ }, column.id))
910
+ ]
911
+ })] });
912
+ }
913
+ /**
914
+ * 列设置项内部统一渲染:复选 + 列名 + 可选固定操作
915
+ */
916
+ function ColumnItemBody({ column, enablePinning, dragHandle }) {
917
+ const pinned = column.getIsPinned();
918
+ const canPin = enablePinning && column.getCanPin();
919
+ const handlePinClick = (event, position) => {
920
+ event.preventDefault();
921
+ event.stopPropagation();
922
+ column.pin(position);
923
+ };
924
+ return /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
925
+ className: "capitalize",
926
+ checked: column.getIsVisible(),
927
+ onCheckedChange: (value) => column.toggleVisibility(!!value),
928
+ children: [
929
+ dragHandle,
930
+ /* @__PURE__ */ jsx("span", {
931
+ className: "min-w-20 flex-1",
932
+ children: column.id
933
+ }),
934
+ canPin && /* @__PURE__ */ jsxs("span", {
935
+ className: "ml-auto mr-5 flex items-center gap-1 normal-case",
936
+ onClick: (event) => event.stopPropagation(),
937
+ onPointerDown: (event) => event.stopPropagation(),
938
+ children: [
939
+ /* @__PURE__ */ jsx(Button, {
940
+ type: "button",
941
+ variant: pinned === "left" ? "default" : "ghost",
942
+ size: "xs",
943
+ onClick: (event) => handlePinClick(event, "left"),
944
+ children: "左"
945
+ }),
946
+ /* @__PURE__ */ jsx(Button, {
947
+ type: "button",
948
+ variant: pinned ? "ghost" : "default",
949
+ size: "xs",
950
+ onClick: (event) => handlePinClick(event, false),
951
+ children: "无"
952
+ }),
953
+ /* @__PURE__ */ jsx(Button, {
954
+ type: "button",
955
+ variant: pinned === "right" ? "default" : "ghost",
956
+ size: "xs",
957
+ onClick: (event) => handlePinClick(event, "right"),
958
+ children: "右"
959
+ })
960
+ ]
961
+ })
962
+ ]
963
+ });
964
+ }
965
+ function ColumnVisibilityItem({ column, enablePinning }) {
966
+ return /* @__PURE__ */ jsx(ColumnItemBody, {
967
+ column,
968
+ enablePinning
969
+ });
970
+ }
971
+ function SortableColumnItem({ column, enablePinning }) {
972
+ const { attributes, listeners, setActivatorNodeRef, setNodeRef, transform, transition, isDragging } = useSortable({ id: column.id });
973
+ return /* @__PURE__ */ jsx("div", {
974
+ ref: setNodeRef,
975
+ style: {
976
+ transform: CSS.Transform.toString(transform),
977
+ transition
978
+ },
979
+ className: cn(isDragging && "relative z-10 opacity-80"),
980
+ children: /* @__PURE__ */ jsx(ColumnItemBody, {
981
+ column,
982
+ enablePinning,
983
+ dragHandle: /* @__PURE__ */ jsx("span", {
984
+ ref: setActivatorNodeRef,
985
+ className: "cursor-grab active:cursor-grabbing",
986
+ "aria-label": `Reorder ${column.id} column`,
987
+ onClick: (event) => event.stopPropagation(),
988
+ ...attributes,
989
+ ...listeners,
990
+ children: /* @__PURE__ */ jsx(GripVerticalIcon, { className: "size-[1em]" })
991
+ })
992
+ })
993
+ });
994
+ }
995
+ //#endregion
996
+ //#region src/table/components/data-table-toolbar.tsx
997
+ /**
998
+ * 表格工具栏:搜索 + 视图配置 + 自定义扩展
999
+ */
1000
+ function DataTableToolbar({ table, searchColumnId, searchPlaceholder = "搜索...", showViewOptions = true, enableColumnOrdering, enableColumnPinning, extra, className }) {
1001
+ const searchColumn = searchColumnId ? table.getColumn(searchColumnId) : void 0;
1002
+ const useGlobalFilter = !searchColumn;
1003
+ const value = useGlobalFilter ? table.getState().globalFilter : searchColumn?.getFilterValue();
1004
+ const hasValue = Boolean(value);
1005
+ const handleChange = (next) => {
1006
+ if (useGlobalFilter) table.setGlobalFilter(next);
1007
+ else searchColumn?.setFilterValue(next || void 0);
1008
+ };
1009
+ return /* @__PURE__ */ jsxs("div", {
1010
+ "data-slot": "data-table-toolbar",
1011
+ className: cn("flex flex-wrap items-center gap-2 py-3", className),
1012
+ children: [/* @__PURE__ */ jsxs("div", {
1013
+ className: "relative max-w-sm flex-1",
1014
+ children: [
1015
+ /* @__PURE__ */ jsx(SearchIcon, { className: "text-muted-foreground pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2" }),
1016
+ /* @__PURE__ */ jsx(Input, {
1017
+ placeholder: searchPlaceholder,
1018
+ value: value ?? "",
1019
+ onChange: (event) => handleChange(event.target.value),
1020
+ className: "pl-8 pr-8"
1021
+ }),
1022
+ hasValue && /* @__PURE__ */ jsx(Button, {
1023
+ type: "button",
1024
+ variant: "ghost",
1025
+ size: "icon-xs",
1026
+ className: "absolute right-1 top-1/2 -translate-y-1/2",
1027
+ "aria-label": "清除搜索",
1028
+ onClick: () => handleChange(""),
1029
+ children: /* @__PURE__ */ jsx(XIcon, {})
1030
+ })
1031
+ ]
1032
+ }), /* @__PURE__ */ jsxs("div", {
1033
+ className: "ml-auto flex items-center gap-2",
1034
+ children: [extra, showViewOptions && /* @__PURE__ */ jsx(DataTableViewOptions, {
1035
+ table,
1036
+ enableOrdering: enableColumnOrdering,
1037
+ enablePinning: enableColumnPinning
1038
+ })]
1039
+ })]
1040
+ });
1041
+ }
1042
+ //#endregion
1043
+ //#region src/table/components/row-expand-toggle.tsx
1044
+ /**
1045
+ * 行展开切换按钮:基于 `row.getToggleExpandedHandler` + `row.depth` 自动缩进
1046
+ *
1047
+ * 配合 `features.expanding` 与 `tableOptions.getSubRows`(嵌套树),或 `expandedRowRender`(展开面板)使用
1048
+ */
1049
+ function RowExpandToggle({ row, indent = 16, placeholder = true, className, ...props }) {
1050
+ const paddingLeft = indent > 0 ? row.depth * indent : 0;
1051
+ const canExpand = row.getCanExpand();
1052
+ const isExpanded = row.getIsExpanded();
1053
+ if (!canExpand) {
1054
+ if (!placeholder) return null;
1055
+ return /* @__PURE__ */ jsx("span", {
1056
+ "aria-hidden": true,
1057
+ className: "inline-flex size-6 shrink-0",
1058
+ style: paddingLeft ? { marginLeft: paddingLeft } : void 0
1059
+ });
1060
+ }
1061
+ const hasPanelTarget = row.subRows.length === 0;
1062
+ return /* @__PURE__ */ jsx("button", {
1063
+ type: "button",
1064
+ "aria-label": isExpanded ? "收起" : "展开",
1065
+ "aria-expanded": isExpanded,
1066
+ "aria-controls": isExpanded && hasPanelTarget ? panelIdFor(row.id) : void 0,
1067
+ onClick: row.getToggleExpandedHandler(),
1068
+ className: cn("text-muted-foreground hover:text-foreground inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md transition-colors focus-visible:outline-none", className),
1069
+ style: paddingLeft ? { marginLeft: paddingLeft } : void 0,
1070
+ ...props,
1071
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cn("size-4 transition-transform", isExpanded ? "rotate-90" : "rotate-0") })
1072
+ });
1073
+ }
1074
+ //#endregion
1075
+ export { TableRow as C, TableHeader as S, TableBody as _, RowDragHandle as a, TableFooter as b, DataTablePagination as c, getColumnPinningClassName as d, getCommonPinningStyles as f, Table as g, useScrollShadow as h, DataTable as i, DataTableColumnHeader as l, useDataTable as m, DataTableToolbar as n, RowDragHandleContext as o, panelIdFor as p, DataTableViewOptions as r, DataTableVirtualBody as s, RowExpandToggle as t, computeCellSpans as u, TableCaption as v, TableHead as x, TableCell as y };
1076
+
1077
+ //# sourceMappingURL=table-BA2BijtZ.mjs.map