@comitor/ui 3.11.0 → 3.11.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.
@@ -4,6 +4,6 @@ import '@tanstack/react-table';
4
4
 
5
5
  interface DataGridCellWrapperProps<TData> extends DataGridCellProps<TData>, React.ComponentProps<"div"> {
6
6
  }
7
- declare function DataGridCellWrapper<TData>({ cell: _cell, tableMeta, rowIndex, columnId, isEditing, isFocused, isSelected, isSearchMatch, isActiveSearchMatch, readOnly, rowHeight, className, onClick: onClickProp, onKeyDown: onKeyDownProp, ref, ...props }: DataGridCellWrapperProps<TData>): React.JSX.Element;
7
+ declare function DataGridCellWrapper<TData>({ cell, tableMeta, rowIndex, columnId, isEditing, isFocused, isSelected, isSearchMatch, isActiveSearchMatch, readOnly, rowHeight, className, onClick: onClickProp, onKeyDown: onKeyDownProp, ref, ...props }: DataGridCellWrapperProps<TData>): React.JSX.Element;
8
8
 
9
9
  export { DataGridCellWrapper };
@@ -3,15 +3,17 @@ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { useComposedRefs } from "../lib/compose-refs.js";
6
+ import { useDataGridMessages } from "./data-grid-locale.js";
6
7
  import { useDataGridPresence } from "./data-grid-presence.js";
7
- import { getCellKey } from "./utils.js";
8
+ import { messagesEn } from "./messages-en.js";
9
+ import { getCellKey, getColumnLabel, isEmptyCellValue } from "./utils.js";
8
10
  function DataGridCellWrapper({
9
11
  /*
10
- * ⚠ Khác bản gốc (3.1.0): tách `cell` ra dù không dùng. Mọi kiểu ô truyền nguyên `cell` (object của
11
- * TanStack) xuống wrapper, và bản gốc để nó rơi vào `...props` rồi rải lên thẻ `<div>` — DOM của
12
- * mỗi ô mang thuộc tính rác `cell="[object Object]"`.
12
+ * ⚠ Khác bản gốc (3.1.0): tách `cell` ra. Mọi kiểu ô truyền nguyên `cell` (object của TanStack)
13
+ * xuống wrapper, và bản gốc để nó rơi vào `...props` rồi rải lên thẻ `<div>` — DOM của mỗi ô mang
14
+ * thuộc tính rác `cell="[object Object]"`. Từ 3.11.1 nó còn cấp tên truy cập cho ô rỗng (bên dưới).
13
15
  */
14
- cell: _cell,
16
+ cell,
15
17
  tableMeta,
16
18
  rowIndex,
17
19
  columnId,
@@ -28,7 +30,10 @@ function DataGridCellWrapper({
28
30
  ref,
29
31
  ...props
30
32
  }) {
33
+ const msg = useDataGridMessages();
31
34
  const cellMapRef = tableMeta?.cellMapRef;
35
+ const emptyMessage = msg.cell.empty ?? messagesEn.cell.empty;
36
+ const emptyLabel = isEmptyCellValue(cell.getValue()) ? emptyMessage(getColumnLabel(cell.column)) : void 0;
32
37
  const cellPresence = useDataGridPresence(getCellKey(rowIndex, columnId));
33
38
  const onCellChange = React.useCallback(
34
39
  (node) => {
@@ -130,6 +135,7 @@ function DataGridCellWrapper({
130
135
  "data-focused": isFocused ? "" : void 0,
131
136
  "data-selected": isSelected ? "" : void 0,
132
137
  tabIndex: isFocused && !isEditing ? 0 : -1,
138
+ "aria-label": emptyLabel,
133
139
  ...props,
134
140
  ref: composedRef,
135
141
  className: cn(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-grid/data-grid-cell-wrapper.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { useComposedRefs } from \"../lib/compose-refs.js\";\nimport { useDataGridPresence } from \"./data-grid-presence.js\";\nimport type { DataGridCellProps } from \"./types.js\";\nimport { getCellKey } from \"./utils.js\";\n\ninterface DataGridCellWrapperProps<TData> extends DataGridCellProps<TData>, React.ComponentProps<\"div\"> {}\n\nexport function DataGridCellWrapper<TData>({\n /*\n * ⚠ Khác bản gốc (3.1.0): tách `cell` ra dù không dùng. Mọi kiểu ô truyền nguyên `cell` (object của\n * TanStack) xuống wrapper, và bản gốc để nó rơi vào `...props` rồi rải lên thẻ `<div>` — DOM của\n * mỗi ô mang thuộc tính rác `cell=\"[object Object]\"`.\n */\n cell: _cell,\n tableMeta,\n rowIndex,\n columnId,\n isEditing,\n isFocused,\n isSelected,\n isSearchMatch,\n isActiveSearchMatch,\n readOnly,\n rowHeight,\n className,\n onClick: onClickProp,\n onKeyDown: onKeyDownProp,\n ref,\n ...props\n}: DataGridCellWrapperProps<TData>) {\n const cellMapRef = tableMeta?.cellMapRef;\n const cellPresence = useDataGridPresence(getCellKey(rowIndex, columnId));\n\n const onCellChange = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (!cellMapRef) return;\n\n const cellKey = getCellKey(rowIndex, columnId);\n\n if (node) {\n cellMapRef.current.set(cellKey, node);\n } else {\n cellMapRef.current.delete(cellKey);\n }\n },\n [rowIndex, columnId, cellMapRef]\n );\n\n const composedRef = useComposedRefs(ref, onCellChange);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (!isEditing) {\n event.preventDefault();\n onClickProp?.(event);\n if (isFocused && !readOnly) {\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n } else {\n tableMeta?.onCellClick?.(rowIndex, columnId, event);\n }\n }\n },\n [tableMeta, rowIndex, columnId, isEditing, isFocused, readOnly, onClickProp]\n );\n\n const onContextMenu = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n tableMeta?.onCellContextMenu?.(rowIndex, columnId, event);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onDoubleClick = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n event.preventDefault();\n tableMeta?.onCellDoubleClick?.(rowIndex, columnId);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDownProp?.(event);\n\n if (event.defaultPrevented) return;\n\n if (\n event.key === \"ArrowUp\" ||\n event.key === \"ArrowDown\" ||\n event.key === \"ArrowLeft\" ||\n event.key === \"ArrowRight\" ||\n event.key === \"Home\" ||\n event.key === \"End\" ||\n event.key === \"PageUp\" ||\n event.key === \"PageDown\" ||\n event.key === \"Tab\"\n ) {\n return;\n }\n\n if (isFocused && !isEditing && !readOnly) {\n if (event.key === \"F2\" || event.key === \"Enter\") {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n return;\n }\n\n if (event.key === \" \") {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n return;\n }\n\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n }\n }\n },\n [onKeyDownProp, isFocused, isEditing, readOnly, tableMeta, rowIndex, columnId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n tableMeta?.onCellMouseDown?.(rowIndex, columnId, event);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onMouseEnter = React.useCallback(() => {\n if (!isEditing) {\n tableMeta?.onCellMouseEnter?.(rowIndex, columnId);\n }\n }, [tableMeta, rowIndex, columnId, isEditing]);\n\n const onMouseUp = React.useCallback(() => {\n if (!isEditing) {\n tableMeta?.onCellMouseUp?.();\n }\n }, [tableMeta, isEditing]);\n\n return (\n <div\n role=\"button\"\n data-slot=\"grid-cell-wrapper\"\n data-editing={isEditing ? \"\" : undefined}\n data-focused={isFocused ? \"\" : undefined}\n data-selected={isSelected ? \"\" : undefined}\n tabIndex={isFocused && !isEditing ? 0 : -1}\n {...props}\n ref={composedRef}\n className={cn(\n /*\n * ─ BA TÍN HIỆU MÀU CỦA MỘT Ô, VÀ VÌ SAO CHÚNG KHÔNG PHẢI TINT CỦA `--primary`/`--warning` ─\n *\n * Bản gốc tô `bg-primary/10` cho vùng chọn và `bg-warning/20`÷`/50` cho kết quả tìm kiếm.\n * Cả hai đều là bậc TÔ NỀN pha loãng, và `pnpm lint:a11y` bác đúng chỗ: 1,05:1 (light) ·\n * 1,21:1 (dark) cho vùng chọn — dưới xa ngưỡng 3:1 của WCAG 1.4.11 cho thông tin thị giác.\n * Một dấu hiệu không nhìn thấy là một dấu hiệu GIẢ.\n *\n * Thay bằng đúng hai bậc mà hợp đồng màu cấp cho việc này:\n * • vùng chọn → `--accent`, bậc tint TRẠNG THÁI (Tabs/Switch/Progress nói \"đang bật\"\n * bằng chính nó). Là token BỀ MẶT nên nó được phép nhạt: nghĩa vụ 3:1\n * không rơi lên tint, mà rơi lên NÉT đi kèm.\n * • tìm kiếm → viền `--warning-ink` (6,51:1 light · 11,60:1 dark trên nền ô) — ĐÂY mới\n * là thứ mang thông tin; ô đang đứng thì thêm nền `--warning` đặc + chữ\n * `--warning-foreground` (12,26:1) cho khác hẳn các ô còn lại.\n *\n * `border-transparent` ở bậc nghỉ để bật/tắt viền KHÔNG xô layout 1px (cùng cách\n * `TableRow` giữ bề rộng viền không đổi giữa hai trạng thái).\n */\n // has-[[role=checkbox]] = ô kiểu checkbox (Radix render <button role=\"checkbox\">)\n \"size-full border border-transparent px-2 py-1.5 text-start text-sm outline-none focus-visible:outline-none has-[[role=checkbox]]:pt-2.5\",\n {\n \"ring-1 ring-inset\": isFocused || !!cellPresence,\n \"ring-ring\": isFocused && !cellPresence,\n \"border-warning-ink\": isSearchMatch || isActiveSearchMatch,\n \"bg-warning text-warning-foreground\": isActiveSearchMatch,\n \"bg-accent\": isSelected && !isEditing && !isActiveSearchMatch,\n \"cursor-default\": !isEditing,\n \"[&_[data-slot=grid-cell-content]]:line-clamp-1\": !isEditing && rowHeight === \"short\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-2\": !isEditing && rowHeight === \"medium\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-3\": !isEditing && rowHeight === \"tall\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-4\": !isEditing && rowHeight === \"extra-tall\"\n },\n className\n )}\n style={cellPresence ? ({ \"--tw-ring-color\": cellPresence.color } as React.CSSProperties) : undefined}\n onClick={onClick}\n onContextMenu={onContextMenu}\n onDoubleClick={onDoubleClick}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseUp={onMouseUp}\n onKeyDown={onKeyDown}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-grid/data-grid-cell-wrapper.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { useComposedRefs } from \"../lib/compose-refs.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport { useDataGridPresence } from \"./data-grid-presence.js\";\nimport { messagesEn } from \"./messages-en.js\";\nimport type { DataGridCellProps } from \"./types.js\";\nimport { getCellKey, getColumnLabel, isEmptyCellValue } from \"./utils.js\";\n\ninterface DataGridCellWrapperProps<TData> extends DataGridCellProps<TData>, React.ComponentProps<\"div\"> {}\n\nexport function DataGridCellWrapper<TData>({\n /*\n * ⚠ Khác bản gốc (3.1.0): tách `cell` ra. Mọi kiểu ô truyền nguyên `cell` (object của TanStack)\n * xuống wrapper, và bản gốc để nó rơi vào `...props` rồi rải lên thẻ `<div>` — DOM của mỗi ô mang\n * thuộc tính rác `cell=\"[object Object]\"`. Từ 3.11.1 nó còn cấp tên truy cập cho ô rỗng (bên dưới).\n */\n cell,\n tableMeta,\n rowIndex,\n columnId,\n isEditing,\n isFocused,\n isSelected,\n isSearchMatch,\n isActiveSearchMatch,\n readOnly,\n rowHeight,\n className,\n onClick: onClickProp,\n onKeyDown: onKeyDownProp,\n ref,\n ...props\n}: DataGridCellWrapperProps<TData>) {\n const msg = useDataGridMessages();\n const cellMapRef = tableMeta?.cellMapRef;\n /*\n * ⚠ Khác bản gốc (3.11.1, inv-07): wrapper là `role=\"button\"`, và tên của nó CHỈ đến từ chữ bên\n * trong — ô rỗng (select chưa chọn, ngày chưa có, chữ trống…) là một NÚT KHÔNG TÊN. Chỉ gắn\n * `aria-label` khi ô rỗng: ô có giá trị thì để chữ bên trong làm tên, khỏi đè lên nội dung mà\n * `renderValue` của app vẽ ra. `aria-label` app truyền thẳng (qua `...props`) vẫn thắng.\n * Đường lui về bảng tiếng Anh: `cell.empty` là khoá MỚI, và bảng chuỗi app soạn tay trước 3.11.1 (truyền\n * qua `locale`) không có nó — thiếu thì mọi ô rỗng ném \"not a function\" lúc render.\n */\n const emptyMessage = msg.cell.empty ?? messagesEn.cell.empty;\n const emptyLabel = isEmptyCellValue(cell.getValue()) ? emptyMessage(getColumnLabel(cell.column)) : undefined;\n const cellPresence = useDataGridPresence(getCellKey(rowIndex, columnId));\n\n const onCellChange = React.useCallback(\n (node: HTMLDivElement | null) => {\n if (!cellMapRef) return;\n\n const cellKey = getCellKey(rowIndex, columnId);\n\n if (node) {\n cellMapRef.current.set(cellKey, node);\n } else {\n cellMapRef.current.delete(cellKey);\n }\n },\n [rowIndex, columnId, cellMapRef]\n );\n\n const composedRef = useComposedRefs(ref, onCellChange);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (!isEditing) {\n event.preventDefault();\n onClickProp?.(event);\n if (isFocused && !readOnly) {\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n } else {\n tableMeta?.onCellClick?.(rowIndex, columnId, event);\n }\n }\n },\n [tableMeta, rowIndex, columnId, isEditing, isFocused, readOnly, onClickProp]\n );\n\n const onContextMenu = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n tableMeta?.onCellContextMenu?.(rowIndex, columnId, event);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onDoubleClick = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n event.preventDefault();\n tableMeta?.onCellDoubleClick?.(rowIndex, columnId);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n onKeyDownProp?.(event);\n\n if (event.defaultPrevented) return;\n\n if (\n event.key === \"ArrowUp\" ||\n event.key === \"ArrowDown\" ||\n event.key === \"ArrowLeft\" ||\n event.key === \"ArrowRight\" ||\n event.key === \"Home\" ||\n event.key === \"End\" ||\n event.key === \"PageUp\" ||\n event.key === \"PageDown\" ||\n event.key === \"Tab\"\n ) {\n return;\n }\n\n if (isFocused && !isEditing && !readOnly) {\n if (event.key === \"F2\" || event.key === \"Enter\") {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n return;\n }\n\n if (event.key === \" \") {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n return;\n }\n\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {\n event.preventDefault();\n event.stopPropagation();\n tableMeta?.onCellEditingStart?.(rowIndex, columnId);\n }\n }\n },\n [onKeyDownProp, isFocused, isEditing, readOnly, tableMeta, rowIndex, columnId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent) => {\n if (!isEditing) {\n tableMeta?.onCellMouseDown?.(rowIndex, columnId, event);\n }\n },\n [tableMeta, rowIndex, columnId, isEditing]\n );\n\n const onMouseEnter = React.useCallback(() => {\n if (!isEditing) {\n tableMeta?.onCellMouseEnter?.(rowIndex, columnId);\n }\n }, [tableMeta, rowIndex, columnId, isEditing]);\n\n const onMouseUp = React.useCallback(() => {\n if (!isEditing) {\n tableMeta?.onCellMouseUp?.();\n }\n }, [tableMeta, isEditing]);\n\n return (\n <div\n role=\"button\"\n data-slot=\"grid-cell-wrapper\"\n data-editing={isEditing ? \"\" : undefined}\n data-focused={isFocused ? \"\" : undefined}\n data-selected={isSelected ? \"\" : undefined}\n tabIndex={isFocused && !isEditing ? 0 : -1}\n aria-label={emptyLabel}\n {...props}\n ref={composedRef}\n className={cn(\n /*\n * ─ BA TÍN HIỆU MÀU CỦA MỘT Ô, VÀ VÌ SAO CHÚNG KHÔNG PHẢI TINT CỦA `--primary`/`--warning` ─\n *\n * Bản gốc tô `bg-primary/10` cho vùng chọn và `bg-warning/20`÷`/50` cho kết quả tìm kiếm.\n * Cả hai đều là bậc TÔ NỀN pha loãng, và `pnpm lint:a11y` bác đúng chỗ: 1,05:1 (light) ·\n * 1,21:1 (dark) cho vùng chọn — dưới xa ngưỡng 3:1 của WCAG 1.4.11 cho thông tin thị giác.\n * Một dấu hiệu không nhìn thấy là một dấu hiệu GIẢ.\n *\n * Thay bằng đúng hai bậc mà hợp đồng màu cấp cho việc này:\n * • vùng chọn → `--accent`, bậc tint TRẠNG THÁI (Tabs/Switch/Progress nói \"đang bật\"\n * bằng chính nó). Là token BỀ MẶT nên nó được phép nhạt: nghĩa vụ 3:1\n * không rơi lên tint, mà rơi lên NÉT đi kèm.\n * • tìm kiếm → viền `--warning-ink` (6,51:1 light · 11,60:1 dark trên nền ô) — ĐÂY mới\n * là thứ mang thông tin; ô đang đứng thì thêm nền `--warning` đặc + chữ\n * `--warning-foreground` (12,26:1) cho khác hẳn các ô còn lại.\n *\n * `border-transparent` ở bậc nghỉ để bật/tắt viền KHÔNG xô layout 1px (cùng cách\n * `TableRow` giữ bề rộng viền không đổi giữa hai trạng thái).\n */\n // has-[[role=checkbox]] = ô kiểu checkbox (Radix render <button role=\"checkbox\">)\n \"size-full border border-transparent px-2 py-1.5 text-start text-sm outline-none focus-visible:outline-none has-[[role=checkbox]]:pt-2.5\",\n {\n \"ring-1 ring-inset\": isFocused || !!cellPresence,\n \"ring-ring\": isFocused && !cellPresence,\n \"border-warning-ink\": isSearchMatch || isActiveSearchMatch,\n \"bg-warning text-warning-foreground\": isActiveSearchMatch,\n \"bg-accent\": isSelected && !isEditing && !isActiveSearchMatch,\n \"cursor-default\": !isEditing,\n \"[&_[data-slot=grid-cell-content]]:line-clamp-1\": !isEditing && rowHeight === \"short\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-2\": !isEditing && rowHeight === \"medium\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-3\": !isEditing && rowHeight === \"tall\",\n \"[&_[data-slot=grid-cell-content]]:line-clamp-4\": !isEditing && rowHeight === \"extra-tall\"\n },\n className\n )}\n style={cellPresence ? ({ \"--tw-ring-color\": cellPresence.color } as React.CSSProperties) : undefined}\n onClick={onClick}\n onContextMenu={onContextMenu}\n onDoubleClick={onDoubleClick}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseUp={onMouseUp}\n onKeyDown={onKeyDown}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -13,7 +13,7 @@ import {
13
13
  import { Tooltip, TooltipContent, TooltipTrigger } from "../components/ui/tooltip.js";
14
14
  import { cn } from "../lib/cn.js";
15
15
  import { useDataGridMessages } from "./data-grid-locale.js";
16
- import { getColumnVariant } from "./utils.js";
16
+ import { getColumnLabel, getColumnVariant } from "./utils.js";
17
17
  function DataGridColumnHeader({
18
18
  header,
19
19
  table,
@@ -23,7 +23,7 @@ function DataGridColumnHeader({
23
23
  }) {
24
24
  const msg = useDataGridMessages();
25
25
  const column = header.column;
26
- const label = column.columnDef.meta?.label ? column.columnDef.meta.label : typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
26
+ const label = getColumnLabel(column);
27
27
  const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn;
28
28
  const cellVariant = column.columnDef.meta?.cell;
29
29
  const columnVariant = getColumnVariant(cellVariant?.variant, msg);
@@ -87,12 +87,12 @@ function DataGridColumnHeader({
87
87
  children: [
88
88
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: [
89
89
  columnVariant && /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 100, children: [
90
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(columnVariant.icon, { className: "size-3.5 shrink-0 text-muted-foreground" }) }),
90
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(columnVariant.icon, { "aria-hidden": "true", className: "size-3.5 shrink-0 text-muted-foreground" }) }),
91
91
  /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: columnVariant.label })
92
92
  ] }),
93
93
  /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
94
94
  ] }),
95
- /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 shrink-0 text-muted-foreground" })
95
+ /* @__PURE__ */ jsx(ChevronDownIcon, { "aria-hidden": "true", className: "size-3.5 shrink-0 text-muted-foreground" })
96
96
  ]
97
97
  }
98
98
  ),
@@ -105,7 +105,7 @@ function DataGridColumnHeader({
105
105
  checked: column.getIsSorted() === "asc",
106
106
  onSelect: () => onSortingChange("asc"),
107
107
  children: [
108
- /* @__PURE__ */ jsx(ChevronUpIcon, {}),
108
+ /* @__PURE__ */ jsx(ChevronUpIcon, { "aria-hidden": "true" }),
109
109
  msg.column.sortAsc
110
110
  ]
111
111
  }
@@ -117,30 +117,30 @@ function DataGridColumnHeader({
117
117
  checked: column.getIsSorted() === "desc",
118
118
  onSelect: () => onSortingChange("desc"),
119
119
  children: [
120
- /* @__PURE__ */ jsx(ChevronDownIcon, {}),
120
+ /* @__PURE__ */ jsx(ChevronDownIcon, { "aria-hidden": "true" }),
121
121
  msg.column.sortDesc
122
122
  ]
123
123
  }
124
124
  ),
125
125
  column.getIsSorted() && /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: onSortRemove, children: [
126
- /* @__PURE__ */ jsx(XIcon, {}),
126
+ /* @__PURE__ */ jsx(XIcon, { "aria-hidden": "true" }),
127
127
  msg.column.removeSort
128
128
  ] })
129
129
  ] }),
130
130
  column.getCanPin() && /* @__PURE__ */ jsxs(Fragment, { children: [
131
131
  column.getCanSort() && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
132
132
  isPinnedLeft ? /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onUnpin, children: [
133
- /* @__PURE__ */ jsx(PinOffIcon, {}),
133
+ /* @__PURE__ */ jsx(PinOffIcon, { "aria-hidden": "true" }),
134
134
  msg.column.unpinLeft
135
135
  ] }) : /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onLeftPin, children: [
136
- /* @__PURE__ */ jsx(PinIcon, {}),
136
+ /* @__PURE__ */ jsx(PinIcon, { "aria-hidden": "true" }),
137
137
  msg.column.pinLeft
138
138
  ] }),
139
139
  isPinnedRight ? /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onUnpin, children: [
140
- /* @__PURE__ */ jsx(PinOffIcon, {}),
140
+ /* @__PURE__ */ jsx(PinOffIcon, { "aria-hidden": "true" }),
141
141
  msg.column.unpinRight
142
142
  ] }) : /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "[&_svg]:text-muted-foreground", onSelect: onRightPin, children: [
143
- /* @__PURE__ */ jsx(PinIcon, {}),
143
+ /* @__PURE__ */ jsx(PinIcon, { "aria-hidden": "true" }),
144
144
  msg.column.pinRight
145
145
  ] })
146
146
  ] }),
@@ -152,7 +152,7 @@ function DataGridColumnHeader({
152
152
  className: "[&_svg]:text-muted-foreground",
153
153
  onSelect: () => column.toggleVisibility(false),
154
154
  children: [
155
- /* @__PURE__ */ jsx(EyeOffIcon, {}),
155
+ /* @__PURE__ */ jsx(EyeOffIcon, { "aria-hidden": "true" }),
156
156
  msg.column.hide
157
157
  ]
158
158
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-grid/data-grid-column-header.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ColumnSort, Header, SortDirection, SortingState, Table } from \"@tanstack/react-table\";\nimport { ChevronDownIcon, ChevronUpIcon, EyeOffIcon, PinIcon, PinOffIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../components/ui/dropdown-menu.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport { getColumnVariant } from \"./utils.js\";\n\ninterface DataGridColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {\n header: Header<TData, TValue>;\n table: Table<TData>;\n}\n\nexport function DataGridColumnHeader<TData, TValue>({\n header,\n table,\n className,\n onPointerDown,\n ...props\n}: DataGridColumnHeaderProps<TData, TValue>) {\n const msg = useDataGridMessages();\n\n const column = header.column;\n const label = column.columnDef.meta?.label\n ? column.columnDef.meta.label\n : typeof column.columnDef.header === \"string\"\n ? column.columnDef.header\n : column.id;\n\n const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn;\n\n const cellVariant = column.columnDef.meta?.cell;\n const columnVariant = getColumnVariant(cellVariant?.variant, msg);\n\n const pinnedPosition = column.getIsPinned();\n const isPinnedLeft = pinnedPosition === \"left\";\n const isPinnedRight = pinnedPosition === \"right\";\n\n const onSortingChange = React.useCallback(\n (direction: SortDirection) => {\n table.setSorting((prev: SortingState) => {\n const existingSortIndex = prev.findIndex((sort) => sort.id === column.id);\n const newSort: ColumnSort = {\n id: column.id,\n desc: direction === \"desc\"\n };\n\n if (existingSortIndex >= 0) {\n const updated = [...prev];\n updated[existingSortIndex] = newSort;\n return updated;\n } else {\n return [...prev, newSort];\n }\n });\n },\n [column.id, table]\n );\n\n const onSortRemove = React.useCallback(() => {\n table.setSorting((prev: SortingState) => prev.filter((sort) => sort.id !== column.id));\n }, [column.id, table]);\n\n const onLeftPin = React.useCallback(() => {\n column.pin(\"left\");\n }, [column]);\n\n const onRightPin = React.useCallback(() => {\n column.pin(\"right\");\n }, [column]);\n\n const onUnpin = React.useCallback(() => {\n column.pin(false);\n }, [column]);\n\n const onTriggerPointerDown = React.useCallback(\n (event: React.PointerEvent<HTMLButtonElement>) => {\n onPointerDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.button !== 0) {\n return;\n }\n table.options.meta?.onColumnClick?.(column.id);\n },\n [table.options.meta, column.id, onPointerDown]\n );\n\n return (\n <>\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger\n className={cn(\n \"flex size-full items-center justify-between gap-2 p-2 text-sm hover:bg-accent/40 data-[state=open]:bg-accent/40 [&_svg]:size-4\",\n isAnyColumnResizing && \"pointer-events-none\",\n className\n )}\n onPointerDown={onTriggerPointerDown}\n {...props}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n {columnVariant && (\n <Tooltip delayDuration={100}>\n <TooltipTrigger asChild>\n <columnVariant.icon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </TooltipTrigger>\n {/* `TooltipContent` của gói TỰ portal ra ngoài — cần thiết vì hộp lưới\n có overflow-auto, không portal thì tooltip bị cắt. */}\n <TooltipContent side=\"top\">{columnVariant.label}</TooltipContent>\n </Tooltip>\n )}\n <span className=\"truncate\">{label}</span>\n </div>\n <ChevronDownIcon className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" sideOffset={0} className=\"w-60\">\n {column.getCanSort() && (\n <>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"asc\"}\n onSelect={() => onSortingChange(\"asc\")}\n >\n <ChevronUpIcon />\n {msg.column.sortAsc}\n </DropdownMenuCheckboxItem>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"desc\"}\n onSelect={() => onSortingChange(\"desc\")}\n >\n <ChevronDownIcon />\n {msg.column.sortDesc}\n </DropdownMenuCheckboxItem>\n {column.getIsSorted() && (\n <DropdownMenuItem onSelect={onSortRemove}>\n <XIcon />\n {msg.column.removeSort}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanPin() && (\n <>\n {column.getCanSort() && <DropdownMenuSeparator />}\n\n {isPinnedLeft ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon />\n {msg.column.unpinLeft}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onLeftPin}>\n <PinIcon />\n {msg.column.pinLeft}\n </DropdownMenuItem>\n )}\n {isPinnedRight ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon />\n {msg.column.unpinRight}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onRightPin}>\n <PinIcon />\n {msg.column.pinRight}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"[&_svg]:text-muted-foreground\"\n onSelect={() => column.toggleVisibility(false)}\n >\n <EyeOffIcon />\n {msg.column.hide}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n {header.column.getCanResize() && <DataGridColumnResizer header={header} table={table} label={label} />}\n </>\n );\n}\n\nconst DataGridColumnResizer = React.memo(DataGridColumnResizerImpl, (prev, next) => {\n const prevColumn = prev.header.column;\n const nextColumn = next.header.column;\n\n if (prevColumn.getIsResizing() !== nextColumn.getIsResizing() || prevColumn.getSize() !== nextColumn.getSize()) {\n return false;\n }\n\n if (prev.label !== next.label) return false;\n\n return true;\n}) as typeof DataGridColumnResizerImpl;\n\ninterface DataGridColumnResizerProps<TData, TValue> extends DataGridColumnHeaderProps<TData, TValue> {\n label: string;\n}\n\nfunction DataGridColumnResizerImpl<TData, TValue>({ header, table, label }: DataGridColumnResizerProps<TData, TValue>) {\n const msg = useDataGridMessages();\n const defaultColumnDef = table._getDefaultColumnDef();\n\n const onDoubleClick = React.useCallback(() => {\n header.column.resetSize();\n }, [header.column]);\n\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={msg.column.resize(label)}\n aria-valuenow={header.column.getSize()}\n aria-valuemin={defaultColumnDef.minSize}\n aria-valuemax={defaultColumnDef.maxSize}\n tabIndex={0}\n className={cn(\n // Tay kéo chỉ hiện khi rê/focus/đang kéo, nên nó CHỈ mang đúng một màu — `--primary-ink`\n // (bậc NÉT). `--primary` trần là bậc tô NỀN: gold #E8B824 chỉ 1,73:1 với nền trang ở\n // light, tức một tay kéo nhìn thấy được ở dark mà gần như vô hình ở light.\n \"absolute -end-px top-0 z-50 h-full w-0.5 cursor-ew-resize touch-none select-none bg-primary-ink transition-opacity after:absolute after:inset-y-0 after:start-1/2 after:h-full after:w-[18px] after:-translate-x-1/2 after:content-[''] focus:outline-none\",\n header.column.getIsResizing() ? \"opacity-100\" : \"opacity-0 hover:opacity-100 focus:opacity-100\"\n )}\n onDoubleClick={onDoubleClick}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-grid/data-grid-column-header.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ColumnSort, Header, SortDirection, SortingState, Table } from \"@tanstack/react-table\";\nimport { ChevronDownIcon, ChevronUpIcon, EyeOffIcon, PinIcon, PinOffIcon, XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger\n} from \"../components/ui/dropdown-menu.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { useDataGridMessages } from \"./data-grid-locale.js\";\nimport { getColumnLabel, getColumnVariant } from \"./utils.js\";\n\ninterface DataGridColumnHeaderProps<TData, TValue> extends React.ComponentProps<typeof DropdownMenuTrigger> {\n header: Header<TData, TValue>;\n table: Table<TData>;\n}\n\nexport function DataGridColumnHeader<TData, TValue>({\n header,\n table,\n className,\n onPointerDown,\n ...props\n}: DataGridColumnHeaderProps<TData, TValue>) {\n const msg = useDataGridMessages();\n\n const column = header.column;\n const label = getColumnLabel(column);\n\n const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn;\n\n const cellVariant = column.columnDef.meta?.cell;\n const columnVariant = getColumnVariant(cellVariant?.variant, msg);\n\n const pinnedPosition = column.getIsPinned();\n const isPinnedLeft = pinnedPosition === \"left\";\n const isPinnedRight = pinnedPosition === \"right\";\n\n const onSortingChange = React.useCallback(\n (direction: SortDirection) => {\n table.setSorting((prev: SortingState) => {\n const existingSortIndex = prev.findIndex((sort) => sort.id === column.id);\n const newSort: ColumnSort = {\n id: column.id,\n desc: direction === \"desc\"\n };\n\n if (existingSortIndex >= 0) {\n const updated = [...prev];\n updated[existingSortIndex] = newSort;\n return updated;\n } else {\n return [...prev, newSort];\n }\n });\n },\n [column.id, table]\n );\n\n const onSortRemove = React.useCallback(() => {\n table.setSorting((prev: SortingState) => prev.filter((sort) => sort.id !== column.id));\n }, [column.id, table]);\n\n const onLeftPin = React.useCallback(() => {\n column.pin(\"left\");\n }, [column]);\n\n const onRightPin = React.useCallback(() => {\n column.pin(\"right\");\n }, [column]);\n\n const onUnpin = React.useCallback(() => {\n column.pin(false);\n }, [column]);\n\n const onTriggerPointerDown = React.useCallback(\n (event: React.PointerEvent<HTMLButtonElement>) => {\n onPointerDown?.(event);\n if (event.defaultPrevented) return;\n\n if (event.button !== 0) {\n return;\n }\n table.options.meta?.onColumnClick?.(column.id);\n },\n [table.options.meta, column.id, onPointerDown]\n );\n\n return (\n <>\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger\n className={cn(\n \"flex size-full items-center justify-between gap-2 p-2 text-sm hover:bg-accent/40 data-[state=open]:bg-accent/40 [&_svg]:size-4\",\n isAnyColumnResizing && \"pointer-events-none\",\n className\n )}\n onPointerDown={onTriggerPointerDown}\n {...props}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-1.5\">\n {columnVariant && (\n <Tooltip delayDuration={100}>\n {/*\n * `aria-hidden` TƯỜNG MINH trên mọi icon trong nút này (và trong menu bên dưới).\n * lucide ≥1.x tự gắn nó — nhưng chỉ khi icon KHÔNG mang prop `aria-*` nào, mà\n * `TooltipTrigger asChild` rải `aria-describedby` lên icon lúc tooltip mở; và\n * peer cho phép lucide `0.x`, bản chưa tự gắn. Thiếu nó thì trình đọc màn hình đọc\n * icon như một phần tên của nút tiêu đề cột (inv-07). Tên kiểu cột chỉ là gợi ý\n * cho chuột — tên của nút là tên cột.\n */}\n <TooltipTrigger asChild>\n <columnVariant.icon aria-hidden=\"true\" className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </TooltipTrigger>\n {/* `TooltipContent` của gói TỰ portal ra ngoài — cần thiết vì hộp lưới\n có overflow-auto, không portal thì tooltip bị cắt. */}\n <TooltipContent side=\"top\">{columnVariant.label}</TooltipContent>\n </Tooltip>\n )}\n <span className=\"truncate\">{label}</span>\n </div>\n <ChevronDownIcon aria-hidden=\"true\" className=\"size-3.5 shrink-0 text-muted-foreground\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" sideOffset={0} className=\"w-60\">\n {column.getCanSort() && (\n <>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"asc\"}\n onSelect={() => onSortingChange(\"asc\")}\n >\n <ChevronUpIcon aria-hidden=\"true\" />\n {msg.column.sortAsc}\n </DropdownMenuCheckboxItem>\n <DropdownMenuCheckboxItem\n className=\"gap-2 [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\"\n checked={column.getIsSorted() === \"desc\"}\n onSelect={() => onSortingChange(\"desc\")}\n >\n <ChevronDownIcon aria-hidden=\"true\" />\n {msg.column.sortDesc}\n </DropdownMenuCheckboxItem>\n {column.getIsSorted() && (\n <DropdownMenuItem onSelect={onSortRemove}>\n <XIcon aria-hidden=\"true\" />\n {msg.column.removeSort}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanPin() && (\n <>\n {column.getCanSort() && <DropdownMenuSeparator />}\n\n {isPinnedLeft ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon aria-hidden=\"true\" />\n {msg.column.unpinLeft}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onLeftPin}>\n <PinIcon aria-hidden=\"true\" />\n {msg.column.pinLeft}\n </DropdownMenuItem>\n )}\n {isPinnedRight ? (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onUnpin}>\n <PinOffIcon aria-hidden=\"true\" />\n {msg.column.unpinRight}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem className=\"[&_svg]:text-muted-foreground\" onSelect={onRightPin}>\n <PinIcon aria-hidden=\"true\" />\n {msg.column.pinRight}\n </DropdownMenuItem>\n )}\n </>\n )}\n {column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"[&_svg]:text-muted-foreground\"\n onSelect={() => column.toggleVisibility(false)}\n >\n <EyeOffIcon aria-hidden=\"true\" />\n {msg.column.hide}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n {header.column.getCanResize() && <DataGridColumnResizer header={header} table={table} label={label} />}\n </>\n );\n}\n\nconst DataGridColumnResizer = React.memo(DataGridColumnResizerImpl, (prev, next) => {\n const prevColumn = prev.header.column;\n const nextColumn = next.header.column;\n\n if (prevColumn.getIsResizing() !== nextColumn.getIsResizing() || prevColumn.getSize() !== nextColumn.getSize()) {\n return false;\n }\n\n if (prev.label !== next.label) return false;\n\n return true;\n}) as typeof DataGridColumnResizerImpl;\n\ninterface DataGridColumnResizerProps<TData, TValue> extends DataGridColumnHeaderProps<TData, TValue> {\n label: string;\n}\n\nfunction DataGridColumnResizerImpl<TData, TValue>({ header, table, label }: DataGridColumnResizerProps<TData, TValue>) {\n const msg = useDataGridMessages();\n const defaultColumnDef = table._getDefaultColumnDef();\n\n const onDoubleClick = React.useCallback(() => {\n header.column.resetSize();\n }, [header.column]);\n\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={msg.column.resize(label)}\n aria-valuenow={header.column.getSize()}\n aria-valuemin={defaultColumnDef.minSize}\n aria-valuemax={defaultColumnDef.maxSize}\n tabIndex={0}\n className={cn(\n // Tay kéo chỉ hiện khi rê/focus/đang kéo, nên nó CHỈ mang đúng một màu — `--primary-ink`\n // (bậc NÉT). `--primary` trần là bậc tô NỀN: gold #E8B824 chỉ 1,73:1 với nền trang ở\n // light, tức một tay kéo nhìn thấy được ở dark mà gần như vô hình ở light.\n \"absolute -end-px top-0 z-50 h-full w-0.5 cursor-ew-resize touch-none select-none bg-primary-ink transition-opacity after:absolute after:inset-y-0 after:start-1/2 after:h-full after:w-[18px] after:-translate-x-1/2 after:content-[''] focus:outline-none\",\n header.column.getIsResizing() ? \"opacity-100\" : \"opacity-0 hover:opacity-100 focus:opacity-100\"\n )}\n onDoubleClick={onDoubleClick}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -164,6 +164,8 @@ declare const messagesEn: {
164
164
  clearAll: string;
165
165
  invalidUrlTitle: string;
166
166
  invalidUrlDescription: string;
167
+ /** Tên truy cập của ô RỖNG — ô không có chữ thì nút `role="button"` của nó không có tên nào */
168
+ empty: (columnLabel: string) => string;
167
169
  };
168
170
  file: {
169
171
  uploadLabel: string;
@@ -144,7 +144,9 @@ const messagesEn = {
144
144
  optionEmpty: "No options found.",
145
145
  clearAll: "Clear all",
146
146
  invalidUrlTitle: "Invalid URL",
147
- invalidUrlDescription: "URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)"
147
+ invalidUrlDescription: "URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)",
148
+ /** Tên truy cập của ô RỖNG — ô không có chữ thì nút `role="button"` của nó không có tên nào */
149
+ empty: (columnLabel) => `${columnLabel}: empty`
148
150
  },
149
151
  file: {
150
152
  uploadLabel: "Upload",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-grid/messages-en.ts"],"sourcesContent":["/**\n * Chuỗi hiển thị tiếng ANH của DataGrid — và là BẢN GỐC định nghĩa hình dạng của\n * {@link DataGridMessages}. Mọi bảng ngôn ngữ khác phải khớp đúng bộ khoá này; thiếu một khoá là\n * typecheck đỏ ngay, không phải một chuỗi rỗng lặng lẽ xuất hiện trên giao diện.\n *\n * ⚠ **KHÔNG `as const`.** Có `as const` thì mỗi chuỗi thành một literal type riêng\n * (`\"Add row\"` chứ không phải `string`), và bảng tiếng Việt vĩnh viễn không bao giờ gán được vào\n * `DataGridMessages`. Kiểu rộng mới là thứ dùng được ở đây.\n *\n * ─ VÌ SAO TIẾNG ANH LÀ BẢN GỐC ─\n * `DataGrid` mặc định tiếng Anh từ `2.0.0` — trước cả gói (cả gói theo từ `3.0.0`). Phần lớn\n * chuỗi ở đây là chuỗi gốc của tablecn — repo mà lưới được port từ đó — nên giữ tiếng Anh làm bản\n * gốc thì mỗi lần đối chiếu ngược với thượng nguồn là so trực tiếp, không phải dịch hai lượt.\n * Lưới giữ bảng RIÊNG thay vì một namespace trong `src/i18n/`, nhưng ngôn ngữ thì đi theo\n * `ComitorLocaleProvider` như mọi component khác (xem `data-grid-locale.tsx`).\n *\n * Quy ước: chuỗi cố định để nguyên; chuỗi có tham số viết thành HÀM (đừng nối chuỗi ở chỗ gọi —\n * trật tự từ của tiếng Việt và tiếng Anh khác nhau). KHÔNG đặt JSX ở đây.\n */\nexport const messagesEn = {\n grid: {\n /** Nhãn mặc định của vùng `role=\"grid\"` (ghi đè bằng prop `aria-label`) */\n label: \"Data grid\",\n addRow: \"Add row\"\n },\n\n search: {\n placeholder: \"Find in table...\",\n label: \"Find in table\",\n prevMatch: \"Previous match\",\n nextMatch: \"Next match\",\n close: \"Close search\",\n matchPosition: (index: number, total: number) => `${index} / ${total}`,\n noResults: \"No results\",\n typeToSearch: \"Type to search\"\n },\n\n column: {\n sortAsc: \"Sort asc\",\n sortDesc: \"Sort desc\",\n removeSort: \"Remove sort\",\n pinLeft: \"Pin to left\",\n unpinLeft: \"Unpin from left\",\n pinRight: \"Pin to right\",\n unpinRight: \"Unpin from right\",\n hide: \"Hide column\",\n resize: (label: string) => `Resize ${label} column`,\n /** Tên kiểu dữ liệu của cột, hiện trong tooltip ở tiêu đề cột */\n variant: {\n shortText: \"Short text\",\n longText: \"Long text\",\n number: \"Number\",\n url: \"URL\",\n checkbox: \"Checkbox\",\n select: \"Select\",\n multiSelect: \"Multi-select\",\n date: \"Date\",\n file: \"File\"\n }\n },\n\n select: {\n all: \"Select all\",\n row: \"Select row\",\n rowNumber: (rowNumber: number) => `Select row ${rowNumber}`\n },\n\n contextMenu: {\n copy: \"Copy\",\n cut: \"Cut\",\n clear: \"Clear\",\n deleteRows: \"Delete rows\"\n },\n\n pasteDialog: {\n title: \"Do you want to add more rows?\",\n description: (rowsNeeded: number) =>\n `We need ${rowsNeeded} additional ${rowsNeeded === 1 ? \"row\" : \"rows\"} to paste everything from your clipboard.`,\n expandTitle: \"Create new rows\",\n expandDescription: (rowsNeeded: number) =>\n `Add ${rowsNeeded} new ${rowsNeeded === 1 ? \"row\" : \"rows\"} to the table and paste all data`,\n keepTitle: \"Keep current rows\",\n keepDescription: \"Paste only what fits in the existing rows\",\n cancel: \"Cancel\",\n confirm: \"Continue\"\n },\n\n rowHeight: {\n label: \"Row height\",\n short: \"Short\",\n medium: \"Medium\",\n tall: \"Tall\",\n extraTall: \"Extra tall\"\n },\n\n view: {\n trigger: \"View\",\n toggleColumns: \"Toggle columns\",\n searchPlaceholder: \"Search columns...\",\n empty: \"No columns found.\"\n },\n\n sort: {\n trigger: \"Sort\",\n title: \"Sort by\",\n emptyTitle: \"No sorting applied\",\n description: \"Modify sorting to organize your rows.\",\n emptyDescription: \"Add sorting to organize your rows.\",\n add: \"Add sort\",\n reset: \"Reset sorting\",\n remove: \"Remove sort\",\n drag: \"Drag to reorder\",\n fieldPlaceholder: \"Search fields...\",\n fieldEmpty: \"No fields found.\",\n asc: \"Asc\",\n desc: \"Desc\"\n },\n\n filter: {\n trigger: \"Filter\",\n title: \"Filter by\",\n emptyTitle: \"No filters applied\",\n description: \"Modify filters to narrow down your data.\",\n emptyDescription: \"Add filters to narrow down your data.\",\n add: \"Add filter\",\n reset: \"Reset filters\",\n remove: \"Remove filter\",\n drag: \"Drag to reorder\",\n where: \"Where\",\n and: \"And\",\n fieldPlaceholder: \"Search fields...\",\n fieldEmpty: \"No fields found.\",\n valuePlaceholder: \"Value\",\n rangeStart: \"From\",\n rangeEnd: \"To\",\n pickDate: \"Pick a date\",\n pickRange: \"Pick a range\",\n optionPlaceholder: \"Search options...\",\n optionEmpty: \"No options found.\",\n selectedCount: (count: number) => `${count} selected`,\n emptyValue: (label: string) => `${label} filter is empty`,\n /** Nhãn toán tử lọc — xem `filters.ts` để biết toán tử nào dùng cho kiểu ô nào */\n operators: {\n contains: \"Contains\",\n notContains: \"Does not contain\",\n equals: \"Is\",\n notEquals: \"Is not\",\n startsWith: \"Starts with\",\n endsWith: \"Ends with\",\n isEmpty: \"Is empty\",\n isNotEmpty: \"Is not empty\",\n lessThan: \"Is less than\",\n lessThanOrEqual: \"Is less than or equal to\",\n greaterThan: \"Is greater than\",\n greaterThanOrEqual: \"Is greater than or equal to\",\n isBetween: \"Is between\",\n dateEquals: \"Is\",\n dateNotEquals: \"Is not\",\n before: \"Is before\",\n after: \"Is after\",\n onOrBefore: \"Is on or before\",\n onOrAfter: \"Is on or after\",\n is: \"Is\",\n isNot: \"Is not\",\n isAnyOf: \"Has any of\",\n isNoneOf: \"Has none of\",\n isTrue: \"Is checked\",\n isFalse: \"Is not checked\"\n }\n },\n\n cell: {\n longTextPlaceholder: \"Enter text...\",\n optionSearchPlaceholder: \"Search...\",\n optionEmpty: \"No options found.\",\n clearAll: \"Clear all\",\n invalidUrlTitle: \"Invalid URL\",\n invalidUrlDescription: \"URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)\"\n },\n\n file: {\n uploadLabel: \"Upload\",\n dropHere: \"Drop files here\",\n dragHere: \"Drag files here\",\n browse: \"or click to browse\",\n limits: (maxSize?: string, maxFiles?: number) => {\n if (maxSize && maxFiles) return `Max size: ${maxSize} • Max ${maxFiles} files`;\n if (maxSize) return `Max size: ${maxSize}`;\n if (maxFiles) return `Max ${maxFiles} files`;\n return \"Select files to upload\";\n },\n count: (count: number) => `${count} ${count === 1 ? \"file\" : \"files\"}`,\n clearAll: \"Clear all\",\n remove: \"Remove file\",\n uploading: \"Uploading...\",\n deleting: \"Deleting...\",\n tooMany: (maxFiles: number) => `Only ${maxFiles} ${maxFiles === 1 ? \"file\" : \"files\"} allowed`,\n tooLarge: (maxSize: string) => `File size exceeds ${maxSize}`,\n wrongType: \"File type not accepted\",\n rejectedOne: (name: string) => `\"${name}\" has been rejected`,\n rejectedMany: (name: string, others: number) => `\"${name}\" and ${others} more rejected`,\n uploadFailed: (count: number) => `Failed to upload ${count} ${count === 1 ? \"file\" : \"files\"}`,\n deleteFailed: (name: string) => `Failed to delete ${name}`,\n deleteFailedMany: \"Failed to delete files\"\n },\n\n toast: {\n cellsCopied: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} copied`,\n copyFailed: \"Failed to copy to clipboard\",\n cellsCut: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} cut`,\n cutFailed: \"Failed to cut to clipboard\",\n cellsPasted: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} pasted`,\n cellsPastedWithSkipped: (count: number, skipped: number) =>\n `${count} ${count === 1 ? \"cell\" : \"cells\"} pasted, ${skipped} skipped`,\n pasteSkipped: (skipped: number) =>\n `${skipped} ${skipped === 1 ? \"cell\" : \"cells\"} skipped pasting for invalid data`,\n pasteFailed: \"Failed to paste. Please try again.\",\n cellsCleared: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} cleared`,\n rowsDeleted: (count: number) => `${count} ${count === 1 ? \"row\" : \"rows\"} deleted`,\n nothingToUndo: \"Nothing left to undo\",\n nothingToRedo: \"Nothing left to redo\",\n undone: (count: number) => `${count} ${count === 1 ? \"action\" : \"actions\"} undone`,\n redone: (count: number) => `${count} ${count === 1 ? \"action\" : \"actions\"} redone`\n },\n\n /** Giá trị văn bản khi dán boolean vào ô văn bản */\n booleanText: {\n checked: \"Yes\",\n unchecked: \"No\"\n },\n\n /**\n * Lời đọc cho trình đọc màn hình khi kéo-thả để đổi thứ tự điều kiện trong menu Lọc / Sắp xếp\n * (`sortable.tsx`, qua `accessibility` của dnd-kit). `item` là id của mục đang kéo; `position`\n * đếm từ 1.\n */\n sortable: {\n /** Hướng dẫn gắn vào mục kéo được; `orientation` quyết định cặp phím mũi tên được nhắc tới. */\n instructions: (orientation: \"vertical\" | \"horizontal\" | \"mixed\") => {\n const keys =\n orientation === \"vertical\" ? \"up and down\" : orientation === \"horizontal\" ? \"left and right\" : \"arrow\";\n return `To pick up a sortable item, press space or enter. While dragging, use the ${keys} keys to move the item. Press space or enter again to drop the item in its new position, or press escape to cancel.`;\n },\n dragStart: (item: string, position: number, total: number) =>\n `Picked up sortable item \"${item}\". Current position is ${position} of ${total}. Use arrow keys to move, space to drop.`,\n dragOver: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Sortable item \"${item}\" moved ${direction} to position ${position} of ${total}.`,\n dragMove: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Sortable item \"${item}\" is moving ${direction} to position ${position} of ${total}.`,\n /** Mục đang kéo đã ra ngoài vùng thả (dùng cho cả `onDragOver` lẫn `onDragMove`). */\n outside: \"Sortable item is no longer over a droppable area. Press escape to cancel.\",\n dragEnd: (item: string, position: number, total: number) =>\n `Sortable item \"${item}\" dropped at position ${position} of ${total}.`,\n dragEndNoChange: (item: string) => `Sortable item \"${item}\" dropped. No changes were made.`,\n dragCancel: (item: string, position: number, total: number) =>\n `Sorting cancelled. Sortable item \"${item}\" returned to position ${position} of ${total}.`\n },\n\n shortcuts: {\n trigger: \"Keyboard shortcuts\",\n title: \"Keyboard shortcuts\",\n description: \"Every shortcut for working through the grid without the mouse.\",\n searchPlaceholder: \"Search shortcuts...\",\n emptyTitle: \"No shortcuts found\",\n emptyDescription: \"Try a different search term.\",\n close: \"Close\",\n groups: {\n navigation: \"Navigation\",\n selection: \"Selection\",\n editing: \"Editing\",\n search: \"Search\",\n filtering: \"Filtering\",\n sorting: \"Sorting\",\n general: \"General\"\n },\n keys: {\n click: \"Click\",\n doubleClick: \"Double click\"\n },\n navigation: {\n move: \"Navigate between cells\",\n nextCell: \"Move to next cell\",\n prevCell: \"Move to previous cell\",\n firstColumn: \"Move to first column\",\n lastColumn: \"Move to last column\",\n firstRowSameColumn: \"Move to first row (same column)\",\n lastRowSameColumn: \"Move to last row (same column)\",\n firstColumnSameRow: \"Move to first column (same row)\",\n lastColumnSameRow: \"Move to last column (same row)\",\n firstCell: \"Move to first cell\",\n lastCell: \"Move to last cell\",\n pageUp: \"Move up one page\",\n pageDown: \"Move down one page\",\n scrollUp: \"Scroll up one page\",\n scrollDown: \"Scroll down one page\",\n scrollLeft: \"Scroll left one page of columns\",\n scrollRight: \"Scroll right one page of columns\"\n },\n selection: {\n extend: \"Extend selection\",\n toTop: \"Select to top of table\",\n toBottom: \"Select to bottom of table\",\n toFirstColumn: \"Select to first column\",\n toLastColumn: \"Select to last column\",\n all: \"Select all cells\",\n toggle: \"Toggle cell selection\",\n range: \"Select range\",\n clear: \"Clear selection\"\n },\n editing: {\n start: \"Start editing cell\",\n insertRow: \"Insert row below\",\n copy: \"Copy selected cells\",\n cut: \"Cut selected cells\",\n paste: \"Paste cells\",\n clear: \"Clear selected cells\",\n deleteRows: \"Delete selected rows\",\n undo: \"Undo last action\",\n redo: \"Redo last action\"\n },\n search: {\n open: \"Open search\",\n next: \"Next match\",\n prev: \"Previous match\",\n close: \"Close search\"\n },\n filtering: {\n toggle: \"Toggle the filter menu\",\n remove: \"Remove filter (when focused)\"\n },\n sorting: {\n toggle: \"Toggle the sort menu\",\n remove: \"Remove sort (when focused)\"\n },\n general: {\n shortcuts: \"Show keyboard shortcuts\"\n }\n }\n};\n\n/**\n * Hình dạng của một bảng ngôn ngữ. Suy ra từ bảng tiếng Anh nên không có chỗ nào để hai thứ\n * lệch nhau: thêm một khoá vào `messagesEn` là mọi bảng khác đỏ cho tới khi dịch xong.\n */\nexport type DataGridMessages = typeof messagesEn;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-grid/messages-en.ts"],"sourcesContent":["/**\n * Chuỗi hiển thị tiếng ANH của DataGrid — và là BẢN GỐC định nghĩa hình dạng của\n * {@link DataGridMessages}. Mọi bảng ngôn ngữ khác phải khớp đúng bộ khoá này; thiếu một khoá là\n * typecheck đỏ ngay, không phải một chuỗi rỗng lặng lẽ xuất hiện trên giao diện.\n *\n * ⚠ **KHÔNG `as const`.** Có `as const` thì mỗi chuỗi thành một literal type riêng\n * (`\"Add row\"` chứ không phải `string`), và bảng tiếng Việt vĩnh viễn không bao giờ gán được vào\n * `DataGridMessages`. Kiểu rộng mới là thứ dùng được ở đây.\n *\n * ─ VÌ SAO TIẾNG ANH LÀ BẢN GỐC ─\n * `DataGrid` mặc định tiếng Anh từ `2.0.0` — trước cả gói (cả gói theo từ `3.0.0`). Phần lớn\n * chuỗi ở đây là chuỗi gốc của tablecn — repo mà lưới được port từ đó — nên giữ tiếng Anh làm bản\n * gốc thì mỗi lần đối chiếu ngược với thượng nguồn là so trực tiếp, không phải dịch hai lượt.\n * Lưới giữ bảng RIÊNG thay vì một namespace trong `src/i18n/`, nhưng ngôn ngữ thì đi theo\n * `ComitorLocaleProvider` như mọi component khác (xem `data-grid-locale.tsx`).\n *\n * Quy ước: chuỗi cố định để nguyên; chuỗi có tham số viết thành HÀM (đừng nối chuỗi ở chỗ gọi —\n * trật tự từ của tiếng Việt và tiếng Anh khác nhau). KHÔNG đặt JSX ở đây.\n */\nexport const messagesEn = {\n grid: {\n /** Nhãn mặc định của vùng `role=\"grid\"` (ghi đè bằng prop `aria-label`) */\n label: \"Data grid\",\n addRow: \"Add row\"\n },\n\n search: {\n placeholder: \"Find in table...\",\n label: \"Find in table\",\n prevMatch: \"Previous match\",\n nextMatch: \"Next match\",\n close: \"Close search\",\n matchPosition: (index: number, total: number) => `${index} / ${total}`,\n noResults: \"No results\",\n typeToSearch: \"Type to search\"\n },\n\n column: {\n sortAsc: \"Sort asc\",\n sortDesc: \"Sort desc\",\n removeSort: \"Remove sort\",\n pinLeft: \"Pin to left\",\n unpinLeft: \"Unpin from left\",\n pinRight: \"Pin to right\",\n unpinRight: \"Unpin from right\",\n hide: \"Hide column\",\n resize: (label: string) => `Resize ${label} column`,\n /** Tên kiểu dữ liệu của cột, hiện trong tooltip ở tiêu đề cột */\n variant: {\n shortText: \"Short text\",\n longText: \"Long text\",\n number: \"Number\",\n url: \"URL\",\n checkbox: \"Checkbox\",\n select: \"Select\",\n multiSelect: \"Multi-select\",\n date: \"Date\",\n file: \"File\"\n }\n },\n\n select: {\n all: \"Select all\",\n row: \"Select row\",\n rowNumber: (rowNumber: number) => `Select row ${rowNumber}`\n },\n\n contextMenu: {\n copy: \"Copy\",\n cut: \"Cut\",\n clear: \"Clear\",\n deleteRows: \"Delete rows\"\n },\n\n pasteDialog: {\n title: \"Do you want to add more rows?\",\n description: (rowsNeeded: number) =>\n `We need ${rowsNeeded} additional ${rowsNeeded === 1 ? \"row\" : \"rows\"} to paste everything from your clipboard.`,\n expandTitle: \"Create new rows\",\n expandDescription: (rowsNeeded: number) =>\n `Add ${rowsNeeded} new ${rowsNeeded === 1 ? \"row\" : \"rows\"} to the table and paste all data`,\n keepTitle: \"Keep current rows\",\n keepDescription: \"Paste only what fits in the existing rows\",\n cancel: \"Cancel\",\n confirm: \"Continue\"\n },\n\n rowHeight: {\n label: \"Row height\",\n short: \"Short\",\n medium: \"Medium\",\n tall: \"Tall\",\n extraTall: \"Extra tall\"\n },\n\n view: {\n trigger: \"View\",\n toggleColumns: \"Toggle columns\",\n searchPlaceholder: \"Search columns...\",\n empty: \"No columns found.\"\n },\n\n sort: {\n trigger: \"Sort\",\n title: \"Sort by\",\n emptyTitle: \"No sorting applied\",\n description: \"Modify sorting to organize your rows.\",\n emptyDescription: \"Add sorting to organize your rows.\",\n add: \"Add sort\",\n reset: \"Reset sorting\",\n remove: \"Remove sort\",\n drag: \"Drag to reorder\",\n fieldPlaceholder: \"Search fields...\",\n fieldEmpty: \"No fields found.\",\n asc: \"Asc\",\n desc: \"Desc\"\n },\n\n filter: {\n trigger: \"Filter\",\n title: \"Filter by\",\n emptyTitle: \"No filters applied\",\n description: \"Modify filters to narrow down your data.\",\n emptyDescription: \"Add filters to narrow down your data.\",\n add: \"Add filter\",\n reset: \"Reset filters\",\n remove: \"Remove filter\",\n drag: \"Drag to reorder\",\n where: \"Where\",\n and: \"And\",\n fieldPlaceholder: \"Search fields...\",\n fieldEmpty: \"No fields found.\",\n valuePlaceholder: \"Value\",\n rangeStart: \"From\",\n rangeEnd: \"To\",\n pickDate: \"Pick a date\",\n pickRange: \"Pick a range\",\n optionPlaceholder: \"Search options...\",\n optionEmpty: \"No options found.\",\n selectedCount: (count: number) => `${count} selected`,\n emptyValue: (label: string) => `${label} filter is empty`,\n /** Nhãn toán tử lọc — xem `filters.ts` để biết toán tử nào dùng cho kiểu ô nào */\n operators: {\n contains: \"Contains\",\n notContains: \"Does not contain\",\n equals: \"Is\",\n notEquals: \"Is not\",\n startsWith: \"Starts with\",\n endsWith: \"Ends with\",\n isEmpty: \"Is empty\",\n isNotEmpty: \"Is not empty\",\n lessThan: \"Is less than\",\n lessThanOrEqual: \"Is less than or equal to\",\n greaterThan: \"Is greater than\",\n greaterThanOrEqual: \"Is greater than or equal to\",\n isBetween: \"Is between\",\n dateEquals: \"Is\",\n dateNotEquals: \"Is not\",\n before: \"Is before\",\n after: \"Is after\",\n onOrBefore: \"Is on or before\",\n onOrAfter: \"Is on or after\",\n is: \"Is\",\n isNot: \"Is not\",\n isAnyOf: \"Has any of\",\n isNoneOf: \"Has none of\",\n isTrue: \"Is checked\",\n isFalse: \"Is not checked\"\n }\n },\n\n cell: {\n longTextPlaceholder: \"Enter text...\",\n optionSearchPlaceholder: \"Search...\",\n optionEmpty: \"No options found.\",\n clearAll: \"Clear all\",\n invalidUrlTitle: \"Invalid URL\",\n invalidUrlDescription: \"URL contains a dangerous protocol (javascript:, data:, vbscript:, or file:)\",\n /** Tên truy cập của ô RỖNG — ô không có chữ thì nút `role=\"button\"` của nó không có tên nào */\n empty: (columnLabel: string) => `${columnLabel}: empty`\n },\n\n file: {\n uploadLabel: \"Upload\",\n dropHere: \"Drop files here\",\n dragHere: \"Drag files here\",\n browse: \"or click to browse\",\n limits: (maxSize?: string, maxFiles?: number) => {\n if (maxSize && maxFiles) return `Max size: ${maxSize} • Max ${maxFiles} files`;\n if (maxSize) return `Max size: ${maxSize}`;\n if (maxFiles) return `Max ${maxFiles} files`;\n return \"Select files to upload\";\n },\n count: (count: number) => `${count} ${count === 1 ? \"file\" : \"files\"}`,\n clearAll: \"Clear all\",\n remove: \"Remove file\",\n uploading: \"Uploading...\",\n deleting: \"Deleting...\",\n tooMany: (maxFiles: number) => `Only ${maxFiles} ${maxFiles === 1 ? \"file\" : \"files\"} allowed`,\n tooLarge: (maxSize: string) => `File size exceeds ${maxSize}`,\n wrongType: \"File type not accepted\",\n rejectedOne: (name: string) => `\"${name}\" has been rejected`,\n rejectedMany: (name: string, others: number) => `\"${name}\" and ${others} more rejected`,\n uploadFailed: (count: number) => `Failed to upload ${count} ${count === 1 ? \"file\" : \"files\"}`,\n deleteFailed: (name: string) => `Failed to delete ${name}`,\n deleteFailedMany: \"Failed to delete files\"\n },\n\n toast: {\n cellsCopied: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} copied`,\n copyFailed: \"Failed to copy to clipboard\",\n cellsCut: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} cut`,\n cutFailed: \"Failed to cut to clipboard\",\n cellsPasted: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} pasted`,\n cellsPastedWithSkipped: (count: number, skipped: number) =>\n `${count} ${count === 1 ? \"cell\" : \"cells\"} pasted, ${skipped} skipped`,\n pasteSkipped: (skipped: number) =>\n `${skipped} ${skipped === 1 ? \"cell\" : \"cells\"} skipped pasting for invalid data`,\n pasteFailed: \"Failed to paste. Please try again.\",\n cellsCleared: (count: number) => `${count} ${count === 1 ? \"cell\" : \"cells\"} cleared`,\n rowsDeleted: (count: number) => `${count} ${count === 1 ? \"row\" : \"rows\"} deleted`,\n nothingToUndo: \"Nothing left to undo\",\n nothingToRedo: \"Nothing left to redo\",\n undone: (count: number) => `${count} ${count === 1 ? \"action\" : \"actions\"} undone`,\n redone: (count: number) => `${count} ${count === 1 ? \"action\" : \"actions\"} redone`\n },\n\n /** Giá trị văn bản khi dán boolean vào ô văn bản */\n booleanText: {\n checked: \"Yes\",\n unchecked: \"No\"\n },\n\n /**\n * Lời đọc cho trình đọc màn hình khi kéo-thả để đổi thứ tự điều kiện trong menu Lọc / Sắp xếp\n * (`sortable.tsx`, qua `accessibility` của dnd-kit). `item` là id của mục đang kéo; `position`\n * đếm từ 1.\n */\n sortable: {\n /** Hướng dẫn gắn vào mục kéo được; `orientation` quyết định cặp phím mũi tên được nhắc tới. */\n instructions: (orientation: \"vertical\" | \"horizontal\" | \"mixed\") => {\n const keys =\n orientation === \"vertical\" ? \"up and down\" : orientation === \"horizontal\" ? \"left and right\" : \"arrow\";\n return `To pick up a sortable item, press space or enter. While dragging, use the ${keys} keys to move the item. Press space or enter again to drop the item in its new position, or press escape to cancel.`;\n },\n dragStart: (item: string, position: number, total: number) =>\n `Picked up sortable item \"${item}\". Current position is ${position} of ${total}. Use arrow keys to move, space to drop.`,\n dragOver: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Sortable item \"${item}\" moved ${direction} to position ${position} of ${total}.`,\n dragMove: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Sortable item \"${item}\" is moving ${direction} to position ${position} of ${total}.`,\n /** Mục đang kéo đã ra ngoài vùng thả (dùng cho cả `onDragOver` lẫn `onDragMove`). */\n outside: \"Sortable item is no longer over a droppable area. Press escape to cancel.\",\n dragEnd: (item: string, position: number, total: number) =>\n `Sortable item \"${item}\" dropped at position ${position} of ${total}.`,\n dragEndNoChange: (item: string) => `Sortable item \"${item}\" dropped. No changes were made.`,\n dragCancel: (item: string, position: number, total: number) =>\n `Sorting cancelled. Sortable item \"${item}\" returned to position ${position} of ${total}.`\n },\n\n shortcuts: {\n trigger: \"Keyboard shortcuts\",\n title: \"Keyboard shortcuts\",\n description: \"Every shortcut for working through the grid without the mouse.\",\n searchPlaceholder: \"Search shortcuts...\",\n emptyTitle: \"No shortcuts found\",\n emptyDescription: \"Try a different search term.\",\n close: \"Close\",\n groups: {\n navigation: \"Navigation\",\n selection: \"Selection\",\n editing: \"Editing\",\n search: \"Search\",\n filtering: \"Filtering\",\n sorting: \"Sorting\",\n general: \"General\"\n },\n keys: {\n click: \"Click\",\n doubleClick: \"Double click\"\n },\n navigation: {\n move: \"Navigate between cells\",\n nextCell: \"Move to next cell\",\n prevCell: \"Move to previous cell\",\n firstColumn: \"Move to first column\",\n lastColumn: \"Move to last column\",\n firstRowSameColumn: \"Move to first row (same column)\",\n lastRowSameColumn: \"Move to last row (same column)\",\n firstColumnSameRow: \"Move to first column (same row)\",\n lastColumnSameRow: \"Move to last column (same row)\",\n firstCell: \"Move to first cell\",\n lastCell: \"Move to last cell\",\n pageUp: \"Move up one page\",\n pageDown: \"Move down one page\",\n scrollUp: \"Scroll up one page\",\n scrollDown: \"Scroll down one page\",\n scrollLeft: \"Scroll left one page of columns\",\n scrollRight: \"Scroll right one page of columns\"\n },\n selection: {\n extend: \"Extend selection\",\n toTop: \"Select to top of table\",\n toBottom: \"Select to bottom of table\",\n toFirstColumn: \"Select to first column\",\n toLastColumn: \"Select to last column\",\n all: \"Select all cells\",\n toggle: \"Toggle cell selection\",\n range: \"Select range\",\n clear: \"Clear selection\"\n },\n editing: {\n start: \"Start editing cell\",\n insertRow: \"Insert row below\",\n copy: \"Copy selected cells\",\n cut: \"Cut selected cells\",\n paste: \"Paste cells\",\n clear: \"Clear selected cells\",\n deleteRows: \"Delete selected rows\",\n undo: \"Undo last action\",\n redo: \"Redo last action\"\n },\n search: {\n open: \"Open search\",\n next: \"Next match\",\n prev: \"Previous match\",\n close: \"Close search\"\n },\n filtering: {\n toggle: \"Toggle the filter menu\",\n remove: \"Remove filter (when focused)\"\n },\n sorting: {\n toggle: \"Toggle the sort menu\",\n remove: \"Remove sort (when focused)\"\n },\n general: {\n shortcuts: \"Show keyboard shortcuts\"\n }\n }\n};\n\n/**\n * Hình dạng của một bảng ngôn ngữ. Suy ra từ bảng tiếng Anh nên không có chỗ nào để hai thứ\n * lệch nhau: thêm một khoá vào `messagesEn` là mọi bảng khác đỏ cho tới khi dịch xong.\n */\nexport type DataGridMessages = typeof messagesEn;\n"],"mappings":"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","names":[]}
@@ -144,7 +144,8 @@ const messagesVi = {
144
144
  optionEmpty: "Kh\xF4ng c\xF3 l\u1EF1a ch\u1ECDn ph\xF9 h\u1EE3p.",
145
145
  clearAll: "B\u1ECF ch\u1ECDn h\u1EBFt",
146
146
  invalidUrlTitle: "Li\xEAn k\u1EBFt kh\xF4ng h\u1EE3p l\u1EC7",
147
- invalidUrlDescription: "Li\xEAn k\u1EBFt ch\u1EE9a giao th\u1EE9c nguy hi\u1EC3m (javascript:, data:, vbscript: ho\u1EB7c file:)"
147
+ invalidUrlDescription: "Li\xEAn k\u1EBFt ch\u1EE9a giao th\u1EE9c nguy hi\u1EC3m (javascript:, data:, vbscript: ho\u1EB7c file:)",
148
+ empty: (columnLabel) => `${columnLabel}: tr\u1ED1ng`
148
149
  },
149
150
  file: {
150
151
  uploadLabel: "T\u1EA3i t\u1EC7p l\xEAn",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-grid/messages-vi.ts"],"sourcesContent":["import type { DataGridMessages } from \"./messages-en.js\";\n\n/**\n * Bảng tiếng VIỆT của DataGrid. Hình dạng do `messages-en.ts` quy định — chú thích `: DataGridMessages`\n * ở đây là thứ bắt lỗi: thêm khoá mới ở bản tiếng Anh mà quên dịch thì typecheck đỏ ngay, thay vì\n * một chuỗi `undefined` lọt ra giao diện lúc chạy.\n *\n * ⚠ Bản này KHÔNG còn là mặc định từ `2.0.0`. Từ `3.0.0` lưới tự chọn nó khi app đặt\n * `<ComitorLocaleProvider locale=\"vi\">` ở root; muốn riêng lưới tiếng Việt thì truyền `locale=\"vi\"`\n * (hoặc bọc `DataGridLocaleProvider locale=\"vi\"`).\n */\nexport const messagesVi: DataGridMessages = {\n grid: {\n /** Nhãn mặc định của vùng `role=\"grid\"` (ghi đè bằng prop `aria-label`) */\n label: \"Bảng dữ liệu\",\n addRow: \"Thêm dòng\"\n },\n\n search: {\n placeholder: \"Tìm trong bảng...\",\n label: \"Tìm trong bảng\",\n prevMatch: \"Kết quả trước\",\n nextMatch: \"Kết quả sau\",\n close: \"Đóng tìm kiếm\",\n matchPosition: (index: number, total: number) => `${index} / ${total}`,\n noResults: \"Không có kết quả\",\n typeToSearch: \"Gõ để tìm\"\n },\n\n column: {\n sortAsc: \"Sắp xếp tăng dần\",\n sortDesc: \"Sắp xếp giảm dần\",\n removeSort: \"Bỏ sắp xếp\",\n pinLeft: \"Ghim sang trái\",\n unpinLeft: \"Bỏ ghim trái\",\n pinRight: \"Ghim sang phải\",\n unpinRight: \"Bỏ ghim phải\",\n hide: \"Ẩn cột\",\n resize: (label: string) => `Đổi độ rộng cột ${label}`,\n /** Tên kiểu dữ liệu của cột, hiện trong tooltip ở tiêu đề cột */\n variant: {\n shortText: \"Văn bản ngắn\",\n longText: \"Văn bản dài\",\n number: \"Số\",\n url: \"Liên kết\",\n checkbox: \"Ô đánh dấu\",\n select: \"Chọn một\",\n multiSelect: \"Chọn nhiều\",\n date: \"Ngày\",\n file: \"Tệp\"\n }\n },\n\n select: {\n all: \"Chọn tất cả\",\n row: \"Chọn dòng\",\n rowNumber: (rowNumber: number) => `Chọn dòng ${rowNumber}`\n },\n\n contextMenu: {\n copy: \"Sao chép\",\n cut: \"Cắt\",\n clear: \"Xoá nội dung\",\n deleteRows: \"Xoá dòng\"\n },\n\n pasteDialog: {\n title: \"Thêm dòng để dán hết dữ liệu?\",\n description: (rowsNeeded: number) => `Cần thêm ${rowsNeeded} dòng nữa để dán hết nội dung đang có trong clipboard.`,\n expandTitle: \"Tạo dòng mới\",\n expandDescription: (rowsNeeded: number) => `Thêm ${rowsNeeded} dòng vào bảng rồi dán toàn bộ dữ liệu`,\n keepTitle: \"Giữ nguyên số dòng\",\n keepDescription: \"Chỉ dán phần vừa với các dòng hiện có\",\n cancel: \"Huỷ\",\n confirm: \"Tiếp tục\"\n },\n\n rowHeight: {\n label: \"Chiều cao dòng\",\n short: \"Thấp\",\n medium: \"Vừa\",\n tall: \"Cao\",\n extraTall: \"Rất cao\"\n },\n\n view: {\n trigger: \"Hiển thị\",\n toggleColumns: \"Ẩn/hiện cột\",\n searchPlaceholder: \"Tìm cột...\",\n empty: \"Không có cột phù hợp.\"\n },\n\n sort: {\n trigger: \"Sắp xếp\",\n title: \"Sắp xếp theo\",\n emptyTitle: \"Chưa sắp xếp\",\n description: \"Đổi tiêu chí sắp xếp cho danh sách.\",\n emptyDescription: \"Thêm tiêu chí để sắp xếp danh sách.\",\n add: \"Thêm sắp xếp\",\n reset: \"Bỏ sắp xếp\",\n remove: \"Xoá tiêu chí\",\n drag: \"Kéo để đổi thứ tự\",\n fieldPlaceholder: \"Tìm trường...\",\n fieldEmpty: \"Không có trường phù hợp.\",\n asc: \"Tăng dần\",\n desc: \"Giảm dần\"\n },\n\n filter: {\n trigger: \"Lọc\",\n title: \"Lọc theo\",\n emptyTitle: \"Chưa có bộ lọc\",\n description: \"Đổi điều kiện lọc để thu hẹp dữ liệu.\",\n emptyDescription: \"Thêm điều kiện lọc để thu hẹp dữ liệu.\",\n add: \"Thêm bộ lọc\",\n reset: \"Xoá bộ lọc\",\n remove: \"Xoá điều kiện\",\n drag: \"Kéo để đổi thứ tự\",\n where: \"Khi\",\n and: \"Và\",\n fieldPlaceholder: \"Tìm trường...\",\n fieldEmpty: \"Không có trường phù hợp.\",\n valuePlaceholder: \"Giá trị\",\n rangeStart: \"Từ\",\n rangeEnd: \"Đến\",\n pickDate: \"Chọn ngày\",\n pickRange: \"Chọn khoảng ngày\",\n optionPlaceholder: \"Tìm lựa chọn...\",\n optionEmpty: \"Không có lựa chọn phù hợp.\",\n selectedCount: (count: number) => `Đã chọn ${count}`,\n emptyValue: (label: string) => `Bộ lọc ${label} chưa cần giá trị`,\n /** Nhãn toán tử lọc — xem `filters.ts` để biết toán tử nào dùng cho kiểu ô nào */\n operators: {\n contains: \"Chứa\",\n notContains: \"Không chứa\",\n equals: \"Bằng\",\n notEquals: \"Khác\",\n startsWith: \"Bắt đầu bằng\",\n endsWith: \"Kết thúc bằng\",\n isEmpty: \"Đang trống\",\n isNotEmpty: \"Không trống\",\n lessThan: \"Nhỏ hơn\",\n lessThanOrEqual: \"Nhỏ hơn hoặc bằng\",\n greaterThan: \"Lớn hơn\",\n greaterThanOrEqual: \"Lớn hơn hoặc bằng\",\n isBetween: \"Trong khoảng\",\n dateEquals: \"Đúng ngày\",\n dateNotEquals: \"Khác ngày\",\n before: \"Trước ngày\",\n after: \"Sau ngày\",\n onOrBefore: \"Vào hoặc trước ngày\",\n onOrAfter: \"Vào hoặc sau ngày\",\n is: \"Là\",\n isNot: \"Không là\",\n isAnyOf: \"Là một trong\",\n isNoneOf: \"Không là bất kỳ\",\n isTrue: \"Có đánh dấu\",\n isFalse: \"Không đánh dấu\"\n }\n },\n\n cell: {\n longTextPlaceholder: \"Nhập nội dung...\",\n optionSearchPlaceholder: \"Tìm...\",\n optionEmpty: \"Không có lựa chọn phù hợp.\",\n clearAll: \"Bỏ chọn hết\",\n invalidUrlTitle: \"Liên kết không hợp lệ\",\n invalidUrlDescription: \"Liên kết chứa giao thức nguy hiểm (javascript:, data:, vbscript: hoặc file:)\"\n },\n\n file: {\n uploadLabel: \"Tải tệp lên\",\n dropHere: \"Thả tệp vào đây\",\n dragHere: \"Kéo tệp vào đây\",\n browse: \"hoặc bấm để chọn tệp\",\n limits: (maxSize?: string, maxFiles?: number) => {\n if (maxSize && maxFiles) return `Tối đa ${maxSize} • Tối đa ${maxFiles} tệp`;\n if (maxSize) return `Tối đa ${maxSize}`;\n if (maxFiles) return `Tối đa ${maxFiles} tệp`;\n return \"Chọn tệp để tải lên\";\n },\n count: (count: number) => `${count} tệp`,\n clearAll: \"Xoá hết\",\n remove: \"Xoá tệp\",\n uploading: \"Đang tải lên...\",\n deleting: \"Đang xoá...\",\n tooMany: (maxFiles: number) => `Chỉ cho phép tối đa ${maxFiles} tệp`,\n tooLarge: (maxSize: string) => `Tệp vượt quá ${maxSize}`,\n wrongType: \"Không chấp nhận định dạng tệp này\",\n rejectedOne: (name: string) => `Đã bỏ qua \"${name}\"`,\n rejectedMany: (name: string, others: number) => `Đã bỏ qua \"${name}\" và ${others} tệp khác`,\n uploadFailed: (count: number) => `Không tải lên được ${count} tệp`,\n deleteFailed: (name: string) => `Không xoá được ${name}`,\n deleteFailedMany: \"Không xoá được tệp\"\n },\n\n toast: {\n cellsCopied: (count: number) => `Đã sao chép ${count} ô`,\n copyFailed: \"Không sao chép được vào clipboard\",\n cellsCut: (count: number) => `Đã cắt ${count} ô`,\n cutFailed: \"Không cắt được vào clipboard\",\n cellsPasted: (count: number) => `Đã dán ${count} ô`,\n cellsPastedWithSkipped: (count: number, skipped: number) => `Đã dán ${count} ô, bỏ qua ${skipped} ô`,\n pasteSkipped: (skipped: number) => `Bỏ qua ${skipped} ô do dữ liệu không hợp lệ`,\n pasteFailed: \"Không dán được. Vui lòng thử lại.\",\n cellsCleared: (count: number) => `Đã xoá nội dung ${count} ô`,\n rowsDeleted: (count: number) => `Đã xoá ${count} dòng`,\n nothingToUndo: \"Không còn thao tác để hoàn tác\",\n nothingToRedo: \"Không còn thao tác để làm lại\",\n undone: (count: number) => `Đã hoàn tác ${count} thao tác`,\n redone: (count: number) => `Đã làm lại ${count} thao tác`\n },\n\n /** Giá trị văn bản khi dán boolean vào ô văn bản */\n booleanText: {\n checked: \"Có\",\n unchecked: \"Không\"\n },\n\n /** Lời đọc cho trình đọc màn hình khi kéo-thả đổi thứ tự trong menu Lọc / Sắp xếp */\n sortable: {\n instructions: (orientation: \"vertical\" | \"horizontal\" | \"mixed\") => {\n const keys = orientation === \"vertical\" ? \"lên/xuống\" : orientation === \"horizontal\" ? \"trái/phải\" : \"\";\n return `Nhấn phím cách hoặc Enter để nhấc mục lên. Khi đang giữ, dùng phím mũi tên${keys ? ` ${keys}` : \"\"} để di chuyển. Nhấn phím cách hoặc Enter lần nữa để thả vào vị trí mới, hoặc Esc để huỷ.`;\n },\n dragStart: (item: string, position: number, total: number) =>\n `Đã nhấc mục \"${item}\". Vị trí hiện tại ${position} / ${total}. Dùng phím mũi tên để di chuyển, phím cách để thả.`,\n dragOver: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Mục \"${item}\" đã chuyển ${direction === \"down\" ? \"xuống\" : \"lên\"} vị trí ${position} / ${total}.`,\n dragMove: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Mục \"${item}\" đang di chuyển ${direction === \"down\" ? \"xuống\" : \"lên\"} vị trí ${position} / ${total}.`,\n outside: \"Mục đang ở ngoài vùng thả. Nhấn Esc để huỷ.\",\n dragEnd: (item: string, position: number, total: number) => `Đã thả mục \"${item}\" ở vị trí ${position} / ${total}.`,\n dragEndNoChange: (item: string) => `Đã thả mục \"${item}\". Không có thay đổi nào.`,\n dragCancel: (item: string, position: number, total: number) =>\n `Đã huỷ sắp xếp. Mục \"${item}\" trở về vị trí ${position} / ${total}.`\n },\n\n shortcuts: {\n trigger: \"Phím tắt\",\n title: \"Phím tắt\",\n description: \"Danh sách phím tắt để thao tác nhanh với bảng dữ liệu.\",\n searchPlaceholder: \"Tìm phím tắt...\",\n emptyTitle: \"Không tìm thấy phím tắt\",\n emptyDescription: \"Thử tìm với từ khoá khác.\",\n close: \"Đóng\",\n groups: {\n navigation: \"Di chuyển\",\n selection: \"Chọn ô\",\n editing: \"Sửa dữ liệu\",\n search: \"Tìm kiếm\",\n filtering: \"Lọc\",\n sorting: \"Sắp xếp\",\n general: \"Chung\"\n },\n keys: {\n click: \"Nhấp chuột\",\n doubleClick: \"Nhấp đúp\"\n },\n navigation: {\n move: \"Di chuyển giữa các ô\",\n nextCell: \"Sang ô kế tiếp\",\n prevCell: \"Về ô trước đó\",\n firstColumn: \"Về cột đầu\",\n lastColumn: \"Tới cột cuối\",\n firstRowSameColumn: \"Về dòng đầu (cùng cột)\",\n lastRowSameColumn: \"Tới dòng cuối (cùng cột)\",\n firstColumnSameRow: \"Về cột đầu (cùng dòng)\",\n lastColumnSameRow: \"Tới cột cuối (cùng dòng)\",\n firstCell: \"Về ô đầu bảng\",\n lastCell: \"Tới ô cuối bảng\",\n pageUp: \"Lên một trang\",\n pageDown: \"Xuống một trang\",\n scrollUp: \"Cuộn lên một trang\",\n scrollDown: \"Cuộn xuống một trang\",\n scrollLeft: \"Cuộn sang trái một trang cột\",\n scrollRight: \"Cuộn sang phải một trang cột\"\n },\n selection: {\n extend: \"Mở rộng vùng chọn\",\n toTop: \"Chọn tới đầu bảng\",\n toBottom: \"Chọn tới cuối bảng\",\n toFirstColumn: \"Chọn tới cột đầu\",\n toLastColumn: \"Chọn tới cột cuối\",\n all: \"Chọn toàn bộ ô\",\n toggle: \"Thêm/bớt một ô khỏi vùng chọn\",\n range: \"Chọn cả vùng\",\n clear: \"Bỏ chọn\"\n },\n editing: {\n start: \"Bắt đầu sửa ô\",\n insertRow: \"Chèn dòng bên dưới\",\n copy: \"Sao chép ô đang chọn\",\n cut: \"Cắt ô đang chọn\",\n paste: \"Dán vào ô\",\n clear: \"Xoá nội dung ô đang chọn\",\n deleteRows: \"Xoá dòng đang chọn\",\n undo: \"Hoàn tác\",\n redo: \"Làm lại\"\n },\n search: {\n open: \"Mở ô tìm kiếm\",\n next: \"Kết quả kế tiếp\",\n prev: \"Kết quả trước đó\",\n close: \"Đóng tìm kiếm\"\n },\n filtering: {\n toggle: \"Mở/đóng menu lọc\",\n remove: \"Xoá điều kiện lọc (khi đang chọn)\"\n },\n sorting: {\n toggle: \"Mở/đóng menu sắp xếp\",\n remove: \"Xoá tiêu chí sắp xếp (khi đang chọn)\"\n },\n general: {\n shortcuts: \"Xem danh sách phím tắt\"\n }\n }\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-grid/messages-vi.ts"],"sourcesContent":["import type { DataGridMessages } from \"./messages-en.js\";\n\n/**\n * Bảng tiếng VIỆT của DataGrid. Hình dạng do `messages-en.ts` quy định — chú thích `: DataGridMessages`\n * ở đây là thứ bắt lỗi: thêm khoá mới ở bản tiếng Anh mà quên dịch thì typecheck đỏ ngay, thay vì\n * một chuỗi `undefined` lọt ra giao diện lúc chạy.\n *\n * ⚠ Bản này KHÔNG còn là mặc định từ `2.0.0`. Từ `3.0.0` lưới tự chọn nó khi app đặt\n * `<ComitorLocaleProvider locale=\"vi\">` ở root; muốn riêng lưới tiếng Việt thì truyền `locale=\"vi\"`\n * (hoặc bọc `DataGridLocaleProvider locale=\"vi\"`).\n */\nexport const messagesVi: DataGridMessages = {\n grid: {\n /** Nhãn mặc định của vùng `role=\"grid\"` (ghi đè bằng prop `aria-label`) */\n label: \"Bảng dữ liệu\",\n addRow: \"Thêm dòng\"\n },\n\n search: {\n placeholder: \"Tìm trong bảng...\",\n label: \"Tìm trong bảng\",\n prevMatch: \"Kết quả trước\",\n nextMatch: \"Kết quả sau\",\n close: \"Đóng tìm kiếm\",\n matchPosition: (index: number, total: number) => `${index} / ${total}`,\n noResults: \"Không có kết quả\",\n typeToSearch: \"Gõ để tìm\"\n },\n\n column: {\n sortAsc: \"Sắp xếp tăng dần\",\n sortDesc: \"Sắp xếp giảm dần\",\n removeSort: \"Bỏ sắp xếp\",\n pinLeft: \"Ghim sang trái\",\n unpinLeft: \"Bỏ ghim trái\",\n pinRight: \"Ghim sang phải\",\n unpinRight: \"Bỏ ghim phải\",\n hide: \"Ẩn cột\",\n resize: (label: string) => `Đổi độ rộng cột ${label}`,\n /** Tên kiểu dữ liệu của cột, hiện trong tooltip ở tiêu đề cột */\n variant: {\n shortText: \"Văn bản ngắn\",\n longText: \"Văn bản dài\",\n number: \"Số\",\n url: \"Liên kết\",\n checkbox: \"Ô đánh dấu\",\n select: \"Chọn một\",\n multiSelect: \"Chọn nhiều\",\n date: \"Ngày\",\n file: \"Tệp\"\n }\n },\n\n select: {\n all: \"Chọn tất cả\",\n row: \"Chọn dòng\",\n rowNumber: (rowNumber: number) => `Chọn dòng ${rowNumber}`\n },\n\n contextMenu: {\n copy: \"Sao chép\",\n cut: \"Cắt\",\n clear: \"Xoá nội dung\",\n deleteRows: \"Xoá dòng\"\n },\n\n pasteDialog: {\n title: \"Thêm dòng để dán hết dữ liệu?\",\n description: (rowsNeeded: number) => `Cần thêm ${rowsNeeded} dòng nữa để dán hết nội dung đang có trong clipboard.`,\n expandTitle: \"Tạo dòng mới\",\n expandDescription: (rowsNeeded: number) => `Thêm ${rowsNeeded} dòng vào bảng rồi dán toàn bộ dữ liệu`,\n keepTitle: \"Giữ nguyên số dòng\",\n keepDescription: \"Chỉ dán phần vừa với các dòng hiện có\",\n cancel: \"Huỷ\",\n confirm: \"Tiếp tục\"\n },\n\n rowHeight: {\n label: \"Chiều cao dòng\",\n short: \"Thấp\",\n medium: \"Vừa\",\n tall: \"Cao\",\n extraTall: \"Rất cao\"\n },\n\n view: {\n trigger: \"Hiển thị\",\n toggleColumns: \"Ẩn/hiện cột\",\n searchPlaceholder: \"Tìm cột...\",\n empty: \"Không có cột phù hợp.\"\n },\n\n sort: {\n trigger: \"Sắp xếp\",\n title: \"Sắp xếp theo\",\n emptyTitle: \"Chưa sắp xếp\",\n description: \"Đổi tiêu chí sắp xếp cho danh sách.\",\n emptyDescription: \"Thêm tiêu chí để sắp xếp danh sách.\",\n add: \"Thêm sắp xếp\",\n reset: \"Bỏ sắp xếp\",\n remove: \"Xoá tiêu chí\",\n drag: \"Kéo để đổi thứ tự\",\n fieldPlaceholder: \"Tìm trường...\",\n fieldEmpty: \"Không có trường phù hợp.\",\n asc: \"Tăng dần\",\n desc: \"Giảm dần\"\n },\n\n filter: {\n trigger: \"Lọc\",\n title: \"Lọc theo\",\n emptyTitle: \"Chưa có bộ lọc\",\n description: \"Đổi điều kiện lọc để thu hẹp dữ liệu.\",\n emptyDescription: \"Thêm điều kiện lọc để thu hẹp dữ liệu.\",\n add: \"Thêm bộ lọc\",\n reset: \"Xoá bộ lọc\",\n remove: \"Xoá điều kiện\",\n drag: \"Kéo để đổi thứ tự\",\n where: \"Khi\",\n and: \"Và\",\n fieldPlaceholder: \"Tìm trường...\",\n fieldEmpty: \"Không có trường phù hợp.\",\n valuePlaceholder: \"Giá trị\",\n rangeStart: \"Từ\",\n rangeEnd: \"Đến\",\n pickDate: \"Chọn ngày\",\n pickRange: \"Chọn khoảng ngày\",\n optionPlaceholder: \"Tìm lựa chọn...\",\n optionEmpty: \"Không có lựa chọn phù hợp.\",\n selectedCount: (count: number) => `Đã chọn ${count}`,\n emptyValue: (label: string) => `Bộ lọc ${label} chưa cần giá trị`,\n /** Nhãn toán tử lọc — xem `filters.ts` để biết toán tử nào dùng cho kiểu ô nào */\n operators: {\n contains: \"Chứa\",\n notContains: \"Không chứa\",\n equals: \"Bằng\",\n notEquals: \"Khác\",\n startsWith: \"Bắt đầu bằng\",\n endsWith: \"Kết thúc bằng\",\n isEmpty: \"Đang trống\",\n isNotEmpty: \"Không trống\",\n lessThan: \"Nhỏ hơn\",\n lessThanOrEqual: \"Nhỏ hơn hoặc bằng\",\n greaterThan: \"Lớn hơn\",\n greaterThanOrEqual: \"Lớn hơn hoặc bằng\",\n isBetween: \"Trong khoảng\",\n dateEquals: \"Đúng ngày\",\n dateNotEquals: \"Khác ngày\",\n before: \"Trước ngày\",\n after: \"Sau ngày\",\n onOrBefore: \"Vào hoặc trước ngày\",\n onOrAfter: \"Vào hoặc sau ngày\",\n is: \"Là\",\n isNot: \"Không là\",\n isAnyOf: \"Là một trong\",\n isNoneOf: \"Không là bất kỳ\",\n isTrue: \"Có đánh dấu\",\n isFalse: \"Không đánh dấu\"\n }\n },\n\n cell: {\n longTextPlaceholder: \"Nhập nội dung...\",\n optionSearchPlaceholder: \"Tìm...\",\n optionEmpty: \"Không có lựa chọn phù hợp.\",\n clearAll: \"Bỏ chọn hết\",\n invalidUrlTitle: \"Liên kết không hợp lệ\",\n invalidUrlDescription: \"Liên kết chứa giao thức nguy hiểm (javascript:, data:, vbscript: hoặc file:)\",\n empty: (columnLabel: string) => `${columnLabel}: trống`\n },\n\n file: {\n uploadLabel: \"Tải tệp lên\",\n dropHere: \"Thả tệp vào đây\",\n dragHere: \"Kéo tệp vào đây\",\n browse: \"hoặc bấm để chọn tệp\",\n limits: (maxSize?: string, maxFiles?: number) => {\n if (maxSize && maxFiles) return `Tối đa ${maxSize} • Tối đa ${maxFiles} tệp`;\n if (maxSize) return `Tối đa ${maxSize}`;\n if (maxFiles) return `Tối đa ${maxFiles} tệp`;\n return \"Chọn tệp để tải lên\";\n },\n count: (count: number) => `${count} tệp`,\n clearAll: \"Xoá hết\",\n remove: \"Xoá tệp\",\n uploading: \"Đang tải lên...\",\n deleting: \"Đang xoá...\",\n tooMany: (maxFiles: number) => `Chỉ cho phép tối đa ${maxFiles} tệp`,\n tooLarge: (maxSize: string) => `Tệp vượt quá ${maxSize}`,\n wrongType: \"Không chấp nhận định dạng tệp này\",\n rejectedOne: (name: string) => `Đã bỏ qua \"${name}\"`,\n rejectedMany: (name: string, others: number) => `Đã bỏ qua \"${name}\" và ${others} tệp khác`,\n uploadFailed: (count: number) => `Không tải lên được ${count} tệp`,\n deleteFailed: (name: string) => `Không xoá được ${name}`,\n deleteFailedMany: \"Không xoá được tệp\"\n },\n\n toast: {\n cellsCopied: (count: number) => `Đã sao chép ${count} ô`,\n copyFailed: \"Không sao chép được vào clipboard\",\n cellsCut: (count: number) => `Đã cắt ${count} ô`,\n cutFailed: \"Không cắt được vào clipboard\",\n cellsPasted: (count: number) => `Đã dán ${count} ô`,\n cellsPastedWithSkipped: (count: number, skipped: number) => `Đã dán ${count} ô, bỏ qua ${skipped} ô`,\n pasteSkipped: (skipped: number) => `Bỏ qua ${skipped} ô do dữ liệu không hợp lệ`,\n pasteFailed: \"Không dán được. Vui lòng thử lại.\",\n cellsCleared: (count: number) => `Đã xoá nội dung ${count} ô`,\n rowsDeleted: (count: number) => `Đã xoá ${count} dòng`,\n nothingToUndo: \"Không còn thao tác để hoàn tác\",\n nothingToRedo: \"Không còn thao tác để làm lại\",\n undone: (count: number) => `Đã hoàn tác ${count} thao tác`,\n redone: (count: number) => `Đã làm lại ${count} thao tác`\n },\n\n /** Giá trị văn bản khi dán boolean vào ô văn bản */\n booleanText: {\n checked: \"Có\",\n unchecked: \"Không\"\n },\n\n /** Lời đọc cho trình đọc màn hình khi kéo-thả đổi thứ tự trong menu Lọc / Sắp xếp */\n sortable: {\n instructions: (orientation: \"vertical\" | \"horizontal\" | \"mixed\") => {\n const keys = orientation === \"vertical\" ? \"lên/xuống\" : orientation === \"horizontal\" ? \"trái/phải\" : \"\";\n return `Nhấn phím cách hoặc Enter để nhấc mục lên. Khi đang giữ, dùng phím mũi tên${keys ? ` ${keys}` : \"\"} để di chuyển. Nhấn phím cách hoặc Enter lần nữa để thả vào vị trí mới, hoặc Esc để huỷ.`;\n },\n dragStart: (item: string, position: number, total: number) =>\n `Đã nhấc mục \"${item}\". Vị trí hiện tại ${position} / ${total}. Dùng phím mũi tên để di chuyển, phím cách để thả.`,\n dragOver: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Mục \"${item}\" đã chuyển ${direction === \"down\" ? \"xuống\" : \"lên\"} vị trí ${position} / ${total}.`,\n dragMove: (item: string, direction: \"up\" | \"down\", position: number, total: number) =>\n `Mục \"${item}\" đang di chuyển ${direction === \"down\" ? \"xuống\" : \"lên\"} vị trí ${position} / ${total}.`,\n outside: \"Mục đang ở ngoài vùng thả. Nhấn Esc để huỷ.\",\n dragEnd: (item: string, position: number, total: number) => `Đã thả mục \"${item}\" ở vị trí ${position} / ${total}.`,\n dragEndNoChange: (item: string) => `Đã thả mục \"${item}\". Không có thay đổi nào.`,\n dragCancel: (item: string, position: number, total: number) =>\n `Đã huỷ sắp xếp. Mục \"${item}\" trở về vị trí ${position} / ${total}.`\n },\n\n shortcuts: {\n trigger: \"Phím tắt\",\n title: \"Phím tắt\",\n description: \"Danh sách phím tắt để thao tác nhanh với bảng dữ liệu.\",\n searchPlaceholder: \"Tìm phím tắt...\",\n emptyTitle: \"Không tìm thấy phím tắt\",\n emptyDescription: \"Thử tìm với từ khoá khác.\",\n close: \"Đóng\",\n groups: {\n navigation: \"Di chuyển\",\n selection: \"Chọn ô\",\n editing: \"Sửa dữ liệu\",\n search: \"Tìm kiếm\",\n filtering: \"Lọc\",\n sorting: \"Sắp xếp\",\n general: \"Chung\"\n },\n keys: {\n click: \"Nhấp chuột\",\n doubleClick: \"Nhấp đúp\"\n },\n navigation: {\n move: \"Di chuyển giữa các ô\",\n nextCell: \"Sang ô kế tiếp\",\n prevCell: \"Về ô trước đó\",\n firstColumn: \"Về cột đầu\",\n lastColumn: \"Tới cột cuối\",\n firstRowSameColumn: \"Về dòng đầu (cùng cột)\",\n lastRowSameColumn: \"Tới dòng cuối (cùng cột)\",\n firstColumnSameRow: \"Về cột đầu (cùng dòng)\",\n lastColumnSameRow: \"Tới cột cuối (cùng dòng)\",\n firstCell: \"Về ô đầu bảng\",\n lastCell: \"Tới ô cuối bảng\",\n pageUp: \"Lên một trang\",\n pageDown: \"Xuống một trang\",\n scrollUp: \"Cuộn lên một trang\",\n scrollDown: \"Cuộn xuống một trang\",\n scrollLeft: \"Cuộn sang trái một trang cột\",\n scrollRight: \"Cuộn sang phải một trang cột\"\n },\n selection: {\n extend: \"Mở rộng vùng chọn\",\n toTop: \"Chọn tới đầu bảng\",\n toBottom: \"Chọn tới cuối bảng\",\n toFirstColumn: \"Chọn tới cột đầu\",\n toLastColumn: \"Chọn tới cột cuối\",\n all: \"Chọn toàn bộ ô\",\n toggle: \"Thêm/bớt một ô khỏi vùng chọn\",\n range: \"Chọn cả vùng\",\n clear: \"Bỏ chọn\"\n },\n editing: {\n start: \"Bắt đầu sửa ô\",\n insertRow: \"Chèn dòng bên dưới\",\n copy: \"Sao chép ô đang chọn\",\n cut: \"Cắt ô đang chọn\",\n paste: \"Dán vào ô\",\n clear: \"Xoá nội dung ô đang chọn\",\n deleteRows: \"Xoá dòng đang chọn\",\n undo: \"Hoàn tác\",\n redo: \"Làm lại\"\n },\n search: {\n open: \"Mở ô tìm kiếm\",\n next: \"Kết quả kế tiếp\",\n prev: \"Kết quả trước đó\",\n close: \"Đóng tìm kiếm\"\n },\n filtering: {\n toggle: \"Mở/đóng menu lọc\",\n remove: \"Xoá điều kiện lọc (khi đang chọn)\"\n },\n sorting: {\n toggle: \"Mở/đóng menu sắp xếp\",\n remove: \"Xoá tiêu chí sắp xếp (khi đang chọn)\"\n },\n general: {\n shortcuts: \"Xem danh sách phím tắt\"\n }\n }\n};\n"],"mappings":"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","names":[]}
@@ -164,6 +164,7 @@ declare const DATA_GRID_MESSAGES: {
164
164
  clearAll: string;
165
165
  invalidUrlTitle: string;
166
166
  invalidUrlDescription: string;
167
+ empty: (columnLabel: string) => string;
167
168
  };
168
169
  file: {
169
170
  uploadLabel: string;
@@ -440,6 +441,7 @@ declare const DATA_GRID_MESSAGES: {
440
441
  clearAll: string;
441
442
  invalidUrlTitle: string;
442
443
  invalidUrlDescription: string;
444
+ empty: (columnLabel: string) => string;
443
445
  };
444
446
  file: {
445
447
  uploadLabel: string;
@@ -266,6 +266,7 @@ declare function useDataGrid<TData>({ data, columns, rowHeight: rowHeightProp, o
266
266
  clearAll: string;
267
267
  invalidUrlTitle: string;
268
268
  invalidUrlDescription: string;
269
+ empty: (columnLabel: string) => string;
269
270
  };
270
271
  file: {
271
272
  uploadLabel: string;
@@ -52,6 +52,18 @@ declare function parseTsv(text: string, fallbackColumnCount: number): string[][]
52
52
  * vào thanh cũng bị lưới kéo ngược về ô.
53
53
  */
54
54
  declare function getIsInPopover(element: unknown): boolean;
55
+ /**
56
+ * Tên hiển thị của một cột: `meta.label` → `header` nếu là chuỗi → `id`. MỘT bản cho cả tiêu đề cột
57
+ * lẫn tên truy cập của ô rỗng (`DataGridCellWrapper`), để hai chỗ không gọi cùng một cột bằng hai tên.
58
+ * Không xuất qua barrel — nội bộ của lưới.
59
+ */
60
+ declare function getColumnLabel<TData, TValue>(column: Column<TData, TValue>): string;
61
+ /**
62
+ * Ô "rỗng" theo nghĩa TRỢ NĂNG: không có gì để trình đọc màn hình đọc ra — `null`/`undefined`, chuỗi
63
+ * chỉ có khoảng trắng, mảng rỗng (multi-select, file). `false` (checkbox) và `0` (number) KHÔNG rỗng:
64
+ * ô vẫn vẽ ra một thứ có tên (checkbox có `aria-label`, số `0` là chữ).
65
+ */
66
+ declare function isEmptyCellValue(value: unknown): boolean;
55
67
  /**
56
68
  * Icon + tên kiểu dữ liệu của một cột (hiện trong tooltip ở tiêu đề cột).
57
69
  *
@@ -105,4 +117,4 @@ declare function readClipboardText(): Promise<string | null>;
105
117
  declare function formatFileSize(bytes: number): string;
106
118
  declare function getFileIcon(type: string): React.ComponentType<React.SVGProps<SVGSVGElement>>;
107
119
 
108
- export { flexRender, formatDate, formatDateForDisplay, formatDateToString, formatFileSize, generateFileId, getCellKey, getColumnBorderVisibility, getColumnPinningStyle, getColumnVariant, getEmptyCellValue, getFileIcon, getIsFileCellData, getIsInPopover, getLineCount, getRowHeightValue, getScrollDirection, getUrlHref, matchSelectOption, parseCellKey, parseDateInput, parseLocalDate, parseTsv, readClipboardText, scrollCellIntoView, writeClipboardText };
120
+ export { flexRender, formatDate, formatDateForDisplay, formatDateToString, formatFileSize, generateFileId, getCellKey, getColumnBorderVisibility, getColumnLabel, getColumnPinningStyle, getColumnVariant, getEmptyCellValue, getFileIcon, getIsFileCellData, getIsInPopover, getLineCount, getRowHeightValue, getScrollDirection, getUrlHref, isEmptyCellValue, matchSelectOption, parseCellKey, parseDateInput, parseLocalDate, parseTsv, readClipboardText, scrollCellIntoView, writeClipboardText };
@@ -241,6 +241,17 @@ function getIsInPopover(element) {
241
241
  // bấm Huỷ trong hộp xác nhận xoá các dòng đang tick, dù hộp mở ra chính vì các dòng đó.
242
242
  element.closest("[role='alertdialog']") !== null || element.closest("[role='menu']") !== null || element.closest("[role='listbox']") !== null;
243
243
  }
244
+ function getColumnLabel(column) {
245
+ const { meta, header } = column.columnDef;
246
+ if (meta?.label) return meta.label;
247
+ return typeof header === "string" ? header : column.id;
248
+ }
249
+ function isEmptyCellValue(value) {
250
+ if (value === null || value === void 0) return true;
251
+ if (typeof value === "string") return value.trim() === "";
252
+ if (Array.isArray(value)) return value.length === 0;
253
+ return false;
254
+ }
244
255
  function getColumnVariant(variant, messages) {
245
256
  const msg = messages ?? getDataGridMessages();
246
257
  switch (variant) {
@@ -398,6 +409,7 @@ export {
398
409
  generateFileId,
399
410
  getCellKey,
400
411
  getColumnBorderVisibility,
412
+ getColumnLabel,
401
413
  getColumnPinningStyle,
402
414
  getColumnVariant,
403
415
  getEmptyCellValue,
@@ -408,6 +420,7 @@ export {
408
420
  getRowHeightValue,
409
421
  getScrollDirection,
410
422
  getUrlHref,
423
+ isEmptyCellValue,
411
424
  matchSelectOption,
412
425
  parseCellKey,
413
426
  parseDateInput,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-grid/utils.ts"],"sourcesContent":["import type { Column, Table } from \"@tanstack/react-table\";\nimport { type Locale as DateFnsLocale, format, isValid, parse } from \"date-fns\";\nimport {\n BaselineIcon,\n CalendarIcon,\n CheckSquareIcon,\n File,\n FileArchive,\n FileAudio,\n FileIcon,\n FileImage,\n FileSpreadsheet,\n FileText,\n FileVideo,\n HashIcon,\n LinkIcon,\n ListChecksIcon,\n ListIcon,\n Presentation,\n WrapTextIcon\n} from \"lucide-react\";\nimport type * as React from \"react\";\nimport { type DataGridMessages, getDataGridMessages } from \"./messages.js\";\nimport type { CellOpts, CellPosition, Direction, FileCellData, RowHeightValue } from \"./types.js\";\n\nexport function flexRender<TProps extends object>(\n Comp: ((props: TProps) => React.ReactNode) | string | undefined,\n props: TProps\n): React.ReactNode {\n if (typeof Comp === \"string\") {\n return Comp;\n }\n return Comp?.(props);\n}\n\nexport function getIsFileCellData(item: unknown): item is FileCellData {\n return !!item && typeof item === \"object\" && \"id\" in item && \"name\" in item && \"size\" in item && \"type\" in item;\n}\n\nexport function matchSelectOption(value: string, options: { value: string; label: string }[]): string | undefined {\n return options.find(\n (o) =>\n o.value === value ||\n o.value.toLowerCase() === value.toLowerCase() ||\n o.label.toLowerCase() === value.toLowerCase()\n )?.value;\n}\n\nexport function getCellKey(rowIndex: number, columnId: string) {\n return `${rowIndex}:${columnId}`;\n}\n\nexport function parseCellKey(cellKey: string): Required<CellPosition> {\n const parts = cellKey.split(\":\");\n const rowIndexStr = parts[0];\n const columnId = parts[1];\n if (rowIndexStr && columnId) {\n const rowIndex = parseInt(rowIndexStr, 10);\n if (!Number.isNaN(rowIndex)) {\n return { rowIndex, columnId };\n }\n }\n return { rowIndex: 0, columnId: \"\" };\n}\n\nexport function getRowHeightValue(rowHeight: RowHeightValue): number {\n const rowHeightMap: Record<RowHeightValue, number> = {\n short: 36,\n medium: 56,\n tall: 76,\n \"extra-tall\": 96\n };\n\n return rowHeightMap[rowHeight];\n}\n\nexport function getLineCount(rowHeight: RowHeightValue): number {\n const lineCountMap: Record<RowHeightValue, number> = {\n short: 1,\n medium: 2,\n tall: 3,\n \"extra-tall\": 4\n };\n\n return lineCountMap[rowHeight];\n}\n\nexport function getColumnBorderVisibility<TData>(params: {\n column: Column<TData>;\n nextColumn?: Column<TData>;\n isLastColumn: boolean;\n}): {\n showEndBorder: boolean;\n showStartBorder: boolean;\n} {\n const { column, nextColumn, isLastColumn } = params;\n\n const isPinned = column.getIsPinned();\n const isFirstRightPinnedColumn = isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n const isLastRightPinnedColumn = isPinned === \"right\" && column.getIsLastColumn(\"right\");\n\n const nextIsPinned = nextColumn?.getIsPinned();\n const isBeforeRightPinned = nextIsPinned === \"right\" && nextColumn?.getIsFirstColumn(\"right\");\n\n const showEndBorder = !isBeforeRightPinned && (isLastColumn || !isLastRightPinnedColumn);\n\n const showStartBorder = isFirstRightPinnedColumn;\n\n return {\n showEndBorder,\n showStartBorder\n };\n}\n\nexport function getColumnPinningStyle<TData>(params: {\n column: Column<TData>;\n withBorder?: boolean;\n dir?: Direction;\n}): React.CSSProperties {\n const { column, dir = \"ltr\", withBorder = false } = params;\n\n const isPinned = column.getIsPinned();\n const isLastLeftPinnedColumn = isPinned === \"left\" && column.getIsLastColumn(\"left\");\n const isFirstRightPinnedColumn = isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n\n const isRtl = dir === \"rtl\";\n\n const leftPosition = isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined;\n const rightPosition = isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined;\n\n /*\n * ⚠ Token của gói này là MÀU ĐẦY ĐỦ (`--card: #ffffff`), KHÔNG phải bộ 3 kênh HSL — nên viết\n * `var(--card)` trần, đừng bọc `hsl()`. Bản gốc port từ một hệ token kiểu `\"150 20% 98%\"` nên\n * ở đó `hsl(var(--card))` mới đúng; bê nguyên sang đây thì `hsl(#ffffff)` là giá trị KHÔNG\n * HỢP LỆ, và hệ quả là cột ghim trong suốt — nội dung cuộn phía dưới lộ xuyên qua. Không có\n * lỗi nào được ném ra: CSS bỏ qua khai báo hỏng rồi rơi về `background: transparent`.\n *\n * Nền lấy theo `--card` vì hộp lưới dùng `bg-card` như mọi bảng khác của gói.\n *\n * ⚠ Khác bản gốc (3.3.2): KHÔNG có `opacity`. Bản gốc đặt `opacity: 0.97` cho ô ghim — nền đã\n * đục nhưng cả Ô mờ 3%, nên khi cuộn ngang, chữ của cột đang trôi phía dưới lộ thành vệt mờ sau\n * cột ghim. Cùng một kết cục với lỗi `hsl()` ngay trên (cột ghim để lộ nội dung cuộn qua), chỉ\n * nhạt hơn — và cũng không cổng nào thấy, vì `opacity` nằm ở `style` inline. `test:grid-render`\n * nay canh cả chuyện này. Ranh giới vùng ghim ⇄ vùng cuộn đã có viền cuối cột lo.\n */\n return {\n boxShadow: withBorder\n ? isLastLeftPinnedColumn\n ? isRtl\n ? \"4px 0 4px -4px var(--border) inset\"\n : \"-4px 0 4px -4px var(--border) inset\"\n : isFirstRightPinnedColumn\n ? isRtl\n ? \"-4px 0 4px -4px var(--border) inset\"\n : \"4px 0 4px -4px var(--border) inset\"\n : undefined\n : undefined,\n left: isRtl ? rightPosition : leftPosition,\n right: isRtl ? leftPosition : rightPosition,\n position: isPinned ? \"sticky\" : \"relative\",\n background: \"var(--card)\",\n width: column.getSize(),\n zIndex: isPinned ? 1 : undefined\n };\n}\n\nexport function getScrollDirection(direction: string): \"left\" | \"right\" | \"home\" | \"end\" | undefined {\n if (direction === \"left\" || direction === \"right\" || direction === \"home\" || direction === \"end\") {\n return direction as \"left\" | \"right\" | \"home\" | \"end\";\n }\n if (direction === \"pageleft\") return \"left\";\n if (direction === \"pageright\") return \"right\";\n return undefined;\n}\n\nexport function scrollCellIntoView<TData>(params: {\n container: HTMLDivElement;\n targetCell: HTMLDivElement;\n tableRef: React.RefObject<Table<TData> | null>;\n viewportOffset: number;\n direction?: \"left\" | \"right\" | \"home\" | \"end\";\n isRtl: boolean;\n}): void {\n const { container, targetCell, tableRef, direction, viewportOffset, isRtl } = params;\n\n const containerRect = container.getBoundingClientRect();\n const cellRect = targetCell.getBoundingClientRect();\n\n const hasNegativeScroll = container.scrollLeft < 0;\n const isActuallyRtl = isRtl || hasNegativeScroll;\n\n const currentTable = tableRef.current;\n const leftPinnedColumns = currentTable?.getLeftVisibleLeafColumns() ?? [];\n const rightPinnedColumns = currentTable?.getRightVisibleLeafColumns() ?? [];\n\n const leftPinnedWidth = leftPinnedColumns.reduce((sum, c) => sum + c.getSize(), 0);\n const rightPinnedWidth = rightPinnedColumns.reduce((sum, c) => sum + c.getSize(), 0);\n\n const viewportLeft = isActuallyRtl\n ? containerRect.left + rightPinnedWidth + viewportOffset\n : containerRect.left + leftPinnedWidth + viewportOffset;\n const viewportRight = isActuallyRtl\n ? containerRect.right - leftPinnedWidth - viewportOffset\n : containerRect.right - rightPinnedWidth - viewportOffset;\n\n const isFullyVisible = cellRect.left >= viewportLeft && cellRect.right <= viewportRight;\n\n if (isFullyVisible) return;\n\n const isClippedLeft = cellRect.left < viewportLeft;\n const isClippedRight = cellRect.right > viewportRight;\n\n let scrollDelta = 0;\n\n if (!direction) {\n if (isClippedRight) {\n scrollDelta = cellRect.right - viewportRight;\n } else if (isClippedLeft) {\n scrollDelta = -(viewportLeft - cellRect.left);\n }\n } else {\n const shouldScrollRight = isActuallyRtl\n ? direction === \"right\" || direction === \"home\"\n : direction === \"right\" || direction === \"end\";\n\n if (shouldScrollRight) {\n scrollDelta = cellRect.right - viewportRight;\n } else {\n scrollDelta = -(viewportLeft - cellRect.left);\n }\n }\n\n container.scrollLeft += scrollDelta;\n}\n\nfunction countTabs(s: string): number {\n let n = 0;\n for (let i = 0; i < s.length; i++) if (s[i] === \"\\t\") n++;\n return n;\n}\n\nexport function parseTsv(text: string, fallbackColumnCount: number): string[][] {\n if (text.startsWith('\"') || text.includes('\\t\"')) {\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentField = \"\";\n let inQuotes = false;\n let i = 0;\n\n while (i < text.length) {\n const char = text[i];\n const nextChar = text[i + 1];\n\n if (inQuotes) {\n if (char === '\"' && nextChar === '\"') {\n currentField += '\"';\n i += 2;\n } else if (char === '\"') {\n inQuotes = false;\n i++;\n } else {\n currentField += char;\n i++;\n }\n } else {\n if (char === '\"' && currentField === \"\") {\n inQuotes = true;\n i++;\n } else if (char === \"\\t\") {\n currentRow.push(currentField);\n currentField = \"\";\n i++;\n } else if (char === \"\\n\") {\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n currentRow = [];\n currentField = \"\";\n i++;\n } else if (char === \"\\r\" && nextChar === \"\\n\") {\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n currentRow = [];\n currentField = \"\";\n i += 2;\n } else {\n currentField += char;\n i++;\n }\n }\n }\n\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n\n return rows;\n }\n\n const lines = text.split(\"\\n\").map((l) => l.replace(/\\r$/, \"\"));\n let maxTabCount = 0;\n for (const line of lines) {\n const n = countTabs(line);\n if (n > maxTabCount) maxTabCount = n;\n }\n const columnCount = maxTabCount > 0 ? maxTabCount + 1 : fallbackColumnCount;\n if (columnCount <= 0) return [];\n\n const expectedTabCount = columnCount - 1;\n const rows: string[][] = [];\n let buf = \"\";\n let bufTabCount = 0;\n\n for (const line of lines) {\n const tc = countTabs(line);\n\n if (tc === expectedTabCount) {\n if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n buf = \"\";\n bufTabCount = 0;\n rows.push(line.split(\"\\t\"));\n } else {\n buf = buf ? `${buf}\\n${line}` : line;\n bufTabCount += tc;\n if (bufTabCount === expectedTabCount) {\n rows.push(buf.split(\"\\t\"));\n buf = \"\";\n bufTabCount = 0;\n }\n }\n }\n\n if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n\n return rows.length > 0 ? rows : lines.filter((l) => l.length > 0).map((l) => l.split(\"\\t\"));\n}\n\n/**\n * Phần tử có đang nằm trong một lớp nổi không? Dùng để lưới KHÔNG coi thao tác\n * trong popover/menu/hộp thoại là \"bấm ra ngoài\" (sẽ mất focus ô đang sửa).\n *\n * Primitive của package không gắn `data-slot`, nên bắt theo wrapper của Radix Popper\n * (bao Popover/DropdownMenu/Select/Tooltip) và theo role — kể cả Select \"item-aligned\".\n *\n * ⚠ Thêm ở `3.1.0`: `ActionBar` của gói (`data-slot=\"action-bar\"`). Thanh thao tác hàng loạt là bạn\n * đi kèm của lưới — nó mở khi có dòng được tick — nhưng nó là `role=\"toolbar\"` nằm NGOÀI lưới,\n * nên trước đây một cú bấm vào nó bị tính là \"bấm ra ngoài\": `mousedown` xoá lựa chọn dòng, thanh\n * đóng lại TRƯỚC khi `click` tới nơi, và hành động không bao giờ chạy. Đo trên comitor-ds: tick hai\n * dòng, bấm \"Mark as done\" → thanh biến mất, không dòng nào đổi. Cùng lý do, focus bàn phím đi\n * vào thanh cũng bị lưới kéo ngược về ô.\n */\nexport function getIsInPopover(element: unknown): boolean {\n if (!(element instanceof Element)) return false;\n\n return (\n element.closest(\"[data-grid-cell-editor]\") !== null ||\n element.closest(\"[data-grid-popover]\") !== null ||\n element.closest(\"[data-radix-popper-content-wrapper]\") !== null ||\n element.closest(\"[data-slot='action-bar']\") !== null ||\n element.closest(\"[role='dialog']\") !== null ||\n // `alertdialog` (3.1.0): `ConfirmDialog` của gói là `role=\"alertdialog\"` — thiếu dòng này thì\n // bấm Huỷ trong hộp xác nhận xoá các dòng đang tick, dù hộp mở ra chính vì các dòng đó.\n element.closest(\"[role='alertdialog']\") !== null ||\n element.closest(\"[role='menu']\") !== null ||\n element.closest(\"[role='listbox']\") !== null\n );\n}\n\n/**\n * Icon + tên kiểu dữ liệu của một cột (hiện trong tooltip ở tiêu đề cột).\n *\n * `messages` bỏ trống = bảng mặc định (tiếng Anh), giữ đúng chữ ký của `1.11.0`. Component nào\n * vẽ nhãn này cho người dùng thì PHẢI truyền bảng đang có hiệu lực vào — quên là tooltip nói\n * tiếng Anh giữa một lưới tiếng Việt.\n */\nexport function getColumnVariant(\n variant?: CellOpts[\"variant\"],\n messages?: DataGridMessages\n): {\n icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;\n label: string;\n} | null {\n const msg = messages ?? getDataGridMessages();\n switch (variant) {\n case \"short-text\":\n return { label: msg.column.variant.shortText, icon: BaselineIcon };\n case \"long-text\":\n return { label: msg.column.variant.longText, icon: WrapTextIcon };\n case \"number\":\n return { label: msg.column.variant.number, icon: HashIcon };\n case \"url\":\n return { label: msg.column.variant.url, icon: LinkIcon };\n case \"checkbox\":\n return { label: msg.column.variant.checkbox, icon: CheckSquareIcon };\n case \"select\":\n return { label: msg.column.variant.select, icon: ListIcon };\n case \"multi-select\":\n return { label: msg.column.variant.multiSelect, icon: ListChecksIcon };\n case \"date\":\n return { label: msg.column.variant.date, icon: CalendarIcon };\n case \"file\":\n return { label: msg.column.variant.file, icon: FileIcon };\n default:\n return null;\n }\n}\n\nexport function getEmptyCellValue(variant: CellOpts[\"variant\"] | undefined): unknown {\n if (variant === \"multi-select\" || variant === \"file\") return [];\n if (variant === \"number\" || variant === \"date\" || variant === \"select\") return null;\n if (variant === \"checkbox\") return false;\n return \"\";\n}\n\nexport function getUrlHref(urlString: string): string {\n if (!urlString || urlString.trim() === \"\") return \"\";\n\n const trimmed = urlString.trim();\n\n // Reject dangerous protocols (extra safety, though our http:// prefix would neutralize them)\n if (/^(javascript|data|vbscript|file):/i.test(trimmed)) {\n return \"\";\n }\n\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed;\n }\n\n return `http://${trimmed}`;\n}\n\nexport function parseLocalDate(dateStr: unknown): Date | null {\n if (!dateStr) return null;\n if (dateStr instanceof Date) return dateStr;\n if (typeof dateStr !== \"string\") return null;\n const [year, month, day] = dateStr.split(\"-\").map(Number);\n if (!year || !month || !day) return null;\n const date = new Date(year, month - 1, day);\n // Verify date wasn't auto-corrected (e.g. Feb 30 -> Mar 1)\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return null;\n }\n return date;\n}\n\nexport function formatDateToString(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Mẫu định dạng ngày của lưới: ngày NGẮN theo locale date-fns — `en-US` ra `12/25/2024`,\n * `vi` / `en-GB` ra `25/12/2024`. Một mẫu cho cả hiển thị lẫn phép đọc lại ({@link parseDateInput}),\n * để thứ người dùng THẤY cũng là thứ dán vào được.\n */\nconst DATE_DISPLAY_FORMAT = \"P\";\n\n/**\n * Hiển thị ngày dạng ngắn theo `locale` (dùng cho ô ngày, bộ lọc ngày).\n *\n * Hàm THUẦN nên không đọc được ngôn ngữ của lưới: component truyền locale đang có hiệu lực vào\n * (`useDataGridDateLocale()`). Bỏ trống `locale` = locale mặc định của date-fns (`enUS`, trừ khi\n * app đã đổi bằng `setDefaultOptions`).\n *\n * ⚠ Trước `3.0.0` hàm này luôn ra `dd/MM/yyyy` bất kể ngôn ngữ. Cần lại đúng chuỗi cũ thì truyền\n * `vi` (`import { vi } from \"date-fns/locale\"`).\n */\nexport function formatDate(date: Date | string | number | null | undefined, locale?: DateFnsLocale): string {\n if (!date) return \"\";\n const value = date instanceof Date ? date : new Date(date);\n if (Number.isNaN(value.getTime())) return \"\";\n return format(value, DATE_DISPLAY_FORMAT, { locale });\n}\n\n/** Như {@link formatDate}, cho giá trị thô của ô ngày (`yyyy-MM-dd` hoặc `Date`). */\nexport function formatDateForDisplay(dateStr: unknown, locale?: DateFnsLocale): string {\n if (!dateStr) return \"\";\n const date = parseLocalDate(dateStr);\n if (!date) return typeof dateStr === \"string\" ? dateStr : \"\";\n return formatDate(date, locale);\n}\n\n/**\n * Đọc chuỗi ngày DÁN vào ô ngày. Thử dạng ngắn của `locale` trước — đúng dạng lưới đang hiển\n * thị, nên `25/12/2024` dán vào lưới tiếng Việt là 25 tháng 12, không phải lỗi — rồi mới tới\n * `new Date()` (ISO `yyyy-MM-dd`, và những gì JS tự hiểu). `null` khi không đọc được.\n *\n * Năm phải đủ 4 chữ số: mẫu `\"P\"` đọc `25/12/24` thành năm 24 sau Công nguyên, một ngày \"hợp lệ\"\n * mà không ai muốn — chuỗi như thế rơi xuống `new Date()` như trước.\n */\nexport function parseDateInput(text: string, locale?: DateFnsLocale): Date | null {\n const trimmed = text.trim();\n if (!trimmed) return null;\n const localized = parse(trimmed, DATE_DISPLAY_FORMAT, new Date(), { locale });\n if (isValid(localized) && localized.getFullYear() >= 1000) return localized;\n const date = new Date(trimmed);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/**\n * Id cho tệp đính kèm. `crypto.randomUUID` chỉ có ở secure context (https/localhost) —\n * app nội bộ chạy http trong mạng bệnh viện sẽ không có, nên phải có đường lui.\n */\nexport function generateFileId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `file-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Chép text vào clipboard. `navigator.clipboard` cũng chỉ có ở secure context,\n * nên rơi về `document.execCommand(\"copy\")` (đã deprecated nhưng vẫn chạy mọi trình duyệt).\n * Ném lỗi khi cả hai đường đều không dùng được để phía gọi báo toast.\n */\nexport async function writeClipboardText(text: string): Promise<void> {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n try {\n await navigator.clipboard.writeText(text);\n return;\n } catch {\n // Bị chặn quyền → thử đường lui bên dưới\n }\n }\n\n if (!copyWithExecCommand(text)) {\n throw new Error(\"Clipboard không khả dụng\");\n }\n}\n\nfunction copyWithExecCommand(text: string): boolean {\n if (typeof document === \"undefined\") return false;\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.top = \"0\";\n textarea.style.insetInlineStart = \"-9999px\";\n document.body.appendChild(textarea);\n\n const previouslyFocused = document.activeElement;\n textarea.select();\n\n let copied = false;\n try {\n copied = document.execCommand(\"copy\");\n } catch {\n copied = false;\n }\n\n document.body.removeChild(textarea);\n if (previouslyFocused instanceof HTMLElement) {\n previouslyFocused.focus();\n }\n\n return copied;\n}\n\n/** Đọc clipboard khi có quyền; trả `null` nếu trình duyệt/ngữ cảnh không cho đọc. */\nexport async function readClipboardText(): Promise<string | null> {\n if (typeof navigator === \"undefined\" || !navigator.clipboard?.readText) return null;\n try {\n return await navigator.clipboard.readText();\n } catch {\n return null;\n }\n}\n\nexport function formatFileSize(bytes: number): string {\n if (bytes <= 0 || !Number.isFinite(bytes)) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.min(sizes.length - 1, Math.floor(Math.log(bytes) / Math.log(k)));\n return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;\n}\n\nexport function getFileIcon(type: string): React.ComponentType<React.SVGProps<SVGSVGElement>> {\n if (type.startsWith(\"image/\")) return FileImage;\n if (type.startsWith(\"video/\")) return FileVideo;\n if (type.startsWith(\"audio/\")) return FileAudio;\n if (type.includes(\"pdf\")) return FileText;\n if (type.includes(\"zip\") || type.includes(\"rar\")) return FileArchive;\n if (type.includes(\"word\") || type.includes(\"document\") || type.includes(\"doc\")) return FileText;\n if (type.includes(\"sheet\") || type.includes(\"excel\") || type.includes(\"xls\")) return FileSpreadsheet;\n if (type.includes(\"presentation\") || type.includes(\"powerpoint\") || type.includes(\"ppt\")) return Presentation;\n return File;\n}\n"],"mappings":"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","names":["rows"]}
1
+ {"version":3,"sources":["../../src/data-grid/utils.ts"],"sourcesContent":["import type { Column, Table } from \"@tanstack/react-table\";\nimport { type Locale as DateFnsLocale, format, isValid, parse } from \"date-fns\";\nimport {\n BaselineIcon,\n CalendarIcon,\n CheckSquareIcon,\n File,\n FileArchive,\n FileAudio,\n FileIcon,\n FileImage,\n FileSpreadsheet,\n FileText,\n FileVideo,\n HashIcon,\n LinkIcon,\n ListChecksIcon,\n ListIcon,\n Presentation,\n WrapTextIcon\n} from \"lucide-react\";\nimport type * as React from \"react\";\nimport { type DataGridMessages, getDataGridMessages } from \"./messages.js\";\nimport type { CellOpts, CellPosition, Direction, FileCellData, RowHeightValue } from \"./types.js\";\n\nexport function flexRender<TProps extends object>(\n Comp: ((props: TProps) => React.ReactNode) | string | undefined,\n props: TProps\n): React.ReactNode {\n if (typeof Comp === \"string\") {\n return Comp;\n }\n return Comp?.(props);\n}\n\nexport function getIsFileCellData(item: unknown): item is FileCellData {\n return !!item && typeof item === \"object\" && \"id\" in item && \"name\" in item && \"size\" in item && \"type\" in item;\n}\n\nexport function matchSelectOption(value: string, options: { value: string; label: string }[]): string | undefined {\n return options.find(\n (o) =>\n o.value === value ||\n o.value.toLowerCase() === value.toLowerCase() ||\n o.label.toLowerCase() === value.toLowerCase()\n )?.value;\n}\n\nexport function getCellKey(rowIndex: number, columnId: string) {\n return `${rowIndex}:${columnId}`;\n}\n\nexport function parseCellKey(cellKey: string): Required<CellPosition> {\n const parts = cellKey.split(\":\");\n const rowIndexStr = parts[0];\n const columnId = parts[1];\n if (rowIndexStr && columnId) {\n const rowIndex = parseInt(rowIndexStr, 10);\n if (!Number.isNaN(rowIndex)) {\n return { rowIndex, columnId };\n }\n }\n return { rowIndex: 0, columnId: \"\" };\n}\n\nexport function getRowHeightValue(rowHeight: RowHeightValue): number {\n const rowHeightMap: Record<RowHeightValue, number> = {\n short: 36,\n medium: 56,\n tall: 76,\n \"extra-tall\": 96\n };\n\n return rowHeightMap[rowHeight];\n}\n\nexport function getLineCount(rowHeight: RowHeightValue): number {\n const lineCountMap: Record<RowHeightValue, number> = {\n short: 1,\n medium: 2,\n tall: 3,\n \"extra-tall\": 4\n };\n\n return lineCountMap[rowHeight];\n}\n\nexport function getColumnBorderVisibility<TData>(params: {\n column: Column<TData>;\n nextColumn?: Column<TData>;\n isLastColumn: boolean;\n}): {\n showEndBorder: boolean;\n showStartBorder: boolean;\n} {\n const { column, nextColumn, isLastColumn } = params;\n\n const isPinned = column.getIsPinned();\n const isFirstRightPinnedColumn = isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n const isLastRightPinnedColumn = isPinned === \"right\" && column.getIsLastColumn(\"right\");\n\n const nextIsPinned = nextColumn?.getIsPinned();\n const isBeforeRightPinned = nextIsPinned === \"right\" && nextColumn?.getIsFirstColumn(\"right\");\n\n const showEndBorder = !isBeforeRightPinned && (isLastColumn || !isLastRightPinnedColumn);\n\n const showStartBorder = isFirstRightPinnedColumn;\n\n return {\n showEndBorder,\n showStartBorder\n };\n}\n\nexport function getColumnPinningStyle<TData>(params: {\n column: Column<TData>;\n withBorder?: boolean;\n dir?: Direction;\n}): React.CSSProperties {\n const { column, dir = \"ltr\", withBorder = false } = params;\n\n const isPinned = column.getIsPinned();\n const isLastLeftPinnedColumn = isPinned === \"left\" && column.getIsLastColumn(\"left\");\n const isFirstRightPinnedColumn = isPinned === \"right\" && column.getIsFirstColumn(\"right\");\n\n const isRtl = dir === \"rtl\";\n\n const leftPosition = isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined;\n const rightPosition = isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined;\n\n /*\n * ⚠ Token của gói này là MÀU ĐẦY ĐỦ (`--card: #ffffff`), KHÔNG phải bộ 3 kênh HSL — nên viết\n * `var(--card)` trần, đừng bọc `hsl()`. Bản gốc port từ một hệ token kiểu `\"150 20% 98%\"` nên\n * ở đó `hsl(var(--card))` mới đúng; bê nguyên sang đây thì `hsl(#ffffff)` là giá trị KHÔNG\n * HỢP LỆ, và hệ quả là cột ghim trong suốt — nội dung cuộn phía dưới lộ xuyên qua. Không có\n * lỗi nào được ném ra: CSS bỏ qua khai báo hỏng rồi rơi về `background: transparent`.\n *\n * Nền lấy theo `--card` vì hộp lưới dùng `bg-card` như mọi bảng khác của gói.\n *\n * ⚠ Khác bản gốc (3.3.2): KHÔNG có `opacity`. Bản gốc đặt `opacity: 0.97` cho ô ghim — nền đã\n * đục nhưng cả Ô mờ 3%, nên khi cuộn ngang, chữ của cột đang trôi phía dưới lộ thành vệt mờ sau\n * cột ghim. Cùng một kết cục với lỗi `hsl()` ngay trên (cột ghim để lộ nội dung cuộn qua), chỉ\n * nhạt hơn — và cũng không cổng nào thấy, vì `opacity` nằm ở `style` inline. `test:grid-render`\n * nay canh cả chuyện này. Ranh giới vùng ghim ⇄ vùng cuộn đã có viền cuối cột lo.\n */\n return {\n boxShadow: withBorder\n ? isLastLeftPinnedColumn\n ? isRtl\n ? \"4px 0 4px -4px var(--border) inset\"\n : \"-4px 0 4px -4px var(--border) inset\"\n : isFirstRightPinnedColumn\n ? isRtl\n ? \"-4px 0 4px -4px var(--border) inset\"\n : \"4px 0 4px -4px var(--border) inset\"\n : undefined\n : undefined,\n left: isRtl ? rightPosition : leftPosition,\n right: isRtl ? leftPosition : rightPosition,\n position: isPinned ? \"sticky\" : \"relative\",\n background: \"var(--card)\",\n width: column.getSize(),\n zIndex: isPinned ? 1 : undefined\n };\n}\n\nexport function getScrollDirection(direction: string): \"left\" | \"right\" | \"home\" | \"end\" | undefined {\n if (direction === \"left\" || direction === \"right\" || direction === \"home\" || direction === \"end\") {\n return direction as \"left\" | \"right\" | \"home\" | \"end\";\n }\n if (direction === \"pageleft\") return \"left\";\n if (direction === \"pageright\") return \"right\";\n return undefined;\n}\n\nexport function scrollCellIntoView<TData>(params: {\n container: HTMLDivElement;\n targetCell: HTMLDivElement;\n tableRef: React.RefObject<Table<TData> | null>;\n viewportOffset: number;\n direction?: \"left\" | \"right\" | \"home\" | \"end\";\n isRtl: boolean;\n}): void {\n const { container, targetCell, tableRef, direction, viewportOffset, isRtl } = params;\n\n const containerRect = container.getBoundingClientRect();\n const cellRect = targetCell.getBoundingClientRect();\n\n const hasNegativeScroll = container.scrollLeft < 0;\n const isActuallyRtl = isRtl || hasNegativeScroll;\n\n const currentTable = tableRef.current;\n const leftPinnedColumns = currentTable?.getLeftVisibleLeafColumns() ?? [];\n const rightPinnedColumns = currentTable?.getRightVisibleLeafColumns() ?? [];\n\n const leftPinnedWidth = leftPinnedColumns.reduce((sum, c) => sum + c.getSize(), 0);\n const rightPinnedWidth = rightPinnedColumns.reduce((sum, c) => sum + c.getSize(), 0);\n\n const viewportLeft = isActuallyRtl\n ? containerRect.left + rightPinnedWidth + viewportOffset\n : containerRect.left + leftPinnedWidth + viewportOffset;\n const viewportRight = isActuallyRtl\n ? containerRect.right - leftPinnedWidth - viewportOffset\n : containerRect.right - rightPinnedWidth - viewportOffset;\n\n const isFullyVisible = cellRect.left >= viewportLeft && cellRect.right <= viewportRight;\n\n if (isFullyVisible) return;\n\n const isClippedLeft = cellRect.left < viewportLeft;\n const isClippedRight = cellRect.right > viewportRight;\n\n let scrollDelta = 0;\n\n if (!direction) {\n if (isClippedRight) {\n scrollDelta = cellRect.right - viewportRight;\n } else if (isClippedLeft) {\n scrollDelta = -(viewportLeft - cellRect.left);\n }\n } else {\n const shouldScrollRight = isActuallyRtl\n ? direction === \"right\" || direction === \"home\"\n : direction === \"right\" || direction === \"end\";\n\n if (shouldScrollRight) {\n scrollDelta = cellRect.right - viewportRight;\n } else {\n scrollDelta = -(viewportLeft - cellRect.left);\n }\n }\n\n container.scrollLeft += scrollDelta;\n}\n\nfunction countTabs(s: string): number {\n let n = 0;\n for (let i = 0; i < s.length; i++) if (s[i] === \"\\t\") n++;\n return n;\n}\n\nexport function parseTsv(text: string, fallbackColumnCount: number): string[][] {\n if (text.startsWith('\"') || text.includes('\\t\"')) {\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentField = \"\";\n let inQuotes = false;\n let i = 0;\n\n while (i < text.length) {\n const char = text[i];\n const nextChar = text[i + 1];\n\n if (inQuotes) {\n if (char === '\"' && nextChar === '\"') {\n currentField += '\"';\n i += 2;\n } else if (char === '\"') {\n inQuotes = false;\n i++;\n } else {\n currentField += char;\n i++;\n }\n } else {\n if (char === '\"' && currentField === \"\") {\n inQuotes = true;\n i++;\n } else if (char === \"\\t\") {\n currentRow.push(currentField);\n currentField = \"\";\n i++;\n } else if (char === \"\\n\") {\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n currentRow = [];\n currentField = \"\";\n i++;\n } else if (char === \"\\r\" && nextChar === \"\\n\") {\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n currentRow = [];\n currentField = \"\";\n i += 2;\n } else {\n currentField += char;\n i++;\n }\n }\n }\n\n currentRow.push(currentField);\n if (currentRow.length > 1 || currentRow.some((f) => f.length > 0)) {\n rows.push(currentRow);\n }\n\n return rows;\n }\n\n const lines = text.split(\"\\n\").map((l) => l.replace(/\\r$/, \"\"));\n let maxTabCount = 0;\n for (const line of lines) {\n const n = countTabs(line);\n if (n > maxTabCount) maxTabCount = n;\n }\n const columnCount = maxTabCount > 0 ? maxTabCount + 1 : fallbackColumnCount;\n if (columnCount <= 0) return [];\n\n const expectedTabCount = columnCount - 1;\n const rows: string[][] = [];\n let buf = \"\";\n let bufTabCount = 0;\n\n for (const line of lines) {\n const tc = countTabs(line);\n\n if (tc === expectedTabCount) {\n if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n buf = \"\";\n bufTabCount = 0;\n rows.push(line.split(\"\\t\"));\n } else {\n buf = buf ? `${buf}\\n${line}` : line;\n bufTabCount += tc;\n if (bufTabCount === expectedTabCount) {\n rows.push(buf.split(\"\\t\"));\n buf = \"\";\n bufTabCount = 0;\n }\n }\n }\n\n if (buf && bufTabCount === expectedTabCount) rows.push(buf.split(\"\\t\"));\n\n return rows.length > 0 ? rows : lines.filter((l) => l.length > 0).map((l) => l.split(\"\\t\"));\n}\n\n/**\n * Phần tử có đang nằm trong một lớp nổi không? Dùng để lưới KHÔNG coi thao tác\n * trong popover/menu/hộp thoại là \"bấm ra ngoài\" (sẽ mất focus ô đang sửa).\n *\n * Primitive của package không gắn `data-slot`, nên bắt theo wrapper của Radix Popper\n * (bao Popover/DropdownMenu/Select/Tooltip) và theo role — kể cả Select \"item-aligned\".\n *\n * ⚠ Thêm ở `3.1.0`: `ActionBar` của gói (`data-slot=\"action-bar\"`). Thanh thao tác hàng loạt là bạn\n * đi kèm của lưới — nó mở khi có dòng được tick — nhưng nó là `role=\"toolbar\"` nằm NGOÀI lưới,\n * nên trước đây một cú bấm vào nó bị tính là \"bấm ra ngoài\": `mousedown` xoá lựa chọn dòng, thanh\n * đóng lại TRƯỚC khi `click` tới nơi, và hành động không bao giờ chạy. Đo trên comitor-ds: tick hai\n * dòng, bấm \"Mark as done\" → thanh biến mất, không dòng nào đổi. Cùng lý do, focus bàn phím đi\n * vào thanh cũng bị lưới kéo ngược về ô.\n */\nexport function getIsInPopover(element: unknown): boolean {\n if (!(element instanceof Element)) return false;\n\n return (\n element.closest(\"[data-grid-cell-editor]\") !== null ||\n element.closest(\"[data-grid-popover]\") !== null ||\n element.closest(\"[data-radix-popper-content-wrapper]\") !== null ||\n element.closest(\"[data-slot='action-bar']\") !== null ||\n element.closest(\"[role='dialog']\") !== null ||\n // `alertdialog` (3.1.0): `ConfirmDialog` của gói là `role=\"alertdialog\"` — thiếu dòng này thì\n // bấm Huỷ trong hộp xác nhận xoá các dòng đang tick, dù hộp mở ra chính vì các dòng đó.\n element.closest(\"[role='alertdialog']\") !== null ||\n element.closest(\"[role='menu']\") !== null ||\n element.closest(\"[role='listbox']\") !== null\n );\n}\n\n/**\n * Tên hiển thị của một cột: `meta.label` → `header` nếu là chuỗi → `id`. MỘT bản cho cả tiêu đề cột\n * lẫn tên truy cập của ô rỗng (`DataGridCellWrapper`), để hai chỗ không gọi cùng một cột bằng hai tên.\n * Không xuất qua barrel — nội bộ của lưới.\n */\nexport function getColumnLabel<TData, TValue>(column: Column<TData, TValue>): string {\n const { meta, header } = column.columnDef;\n if (meta?.label) return meta.label;\n return typeof header === \"string\" ? header : column.id;\n}\n\n/**\n * Ô \"rỗng\" theo nghĩa TRỢ NĂNG: không có gì để trình đọc màn hình đọc ra — `null`/`undefined`, chuỗi\n * chỉ có khoảng trắng, mảng rỗng (multi-select, file). `false` (checkbox) và `0` (number) KHÔNG rỗng:\n * ô vẫn vẽ ra một thứ có tên (checkbox có `aria-label`, số `0` là chữ).\n */\nexport function isEmptyCellValue(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * Icon + tên kiểu dữ liệu của một cột (hiện trong tooltip ở tiêu đề cột).\n *\n * `messages` bỏ trống = bảng mặc định (tiếng Anh), giữ đúng chữ ký của `1.11.0`. Component nào\n * vẽ nhãn này cho người dùng thì PHẢI truyền bảng đang có hiệu lực vào — quên là tooltip nói\n * tiếng Anh giữa một lưới tiếng Việt.\n */\nexport function getColumnVariant(\n variant?: CellOpts[\"variant\"],\n messages?: DataGridMessages\n): {\n icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;\n label: string;\n} | null {\n const msg = messages ?? getDataGridMessages();\n switch (variant) {\n case \"short-text\":\n return { label: msg.column.variant.shortText, icon: BaselineIcon };\n case \"long-text\":\n return { label: msg.column.variant.longText, icon: WrapTextIcon };\n case \"number\":\n return { label: msg.column.variant.number, icon: HashIcon };\n case \"url\":\n return { label: msg.column.variant.url, icon: LinkIcon };\n case \"checkbox\":\n return { label: msg.column.variant.checkbox, icon: CheckSquareIcon };\n case \"select\":\n return { label: msg.column.variant.select, icon: ListIcon };\n case \"multi-select\":\n return { label: msg.column.variant.multiSelect, icon: ListChecksIcon };\n case \"date\":\n return { label: msg.column.variant.date, icon: CalendarIcon };\n case \"file\":\n return { label: msg.column.variant.file, icon: FileIcon };\n default:\n return null;\n }\n}\n\nexport function getEmptyCellValue(variant: CellOpts[\"variant\"] | undefined): unknown {\n if (variant === \"multi-select\" || variant === \"file\") return [];\n if (variant === \"number\" || variant === \"date\" || variant === \"select\") return null;\n if (variant === \"checkbox\") return false;\n return \"\";\n}\n\nexport function getUrlHref(urlString: string): string {\n if (!urlString || urlString.trim() === \"\") return \"\";\n\n const trimmed = urlString.trim();\n\n // Reject dangerous protocols (extra safety, though our http:// prefix would neutralize them)\n if (/^(javascript|data|vbscript|file):/i.test(trimmed)) {\n return \"\";\n }\n\n if (trimmed.startsWith(\"http://\") || trimmed.startsWith(\"https://\")) {\n return trimmed;\n }\n\n return `http://${trimmed}`;\n}\n\nexport function parseLocalDate(dateStr: unknown): Date | null {\n if (!dateStr) return null;\n if (dateStr instanceof Date) return dateStr;\n if (typeof dateStr !== \"string\") return null;\n const [year, month, day] = dateStr.split(\"-\").map(Number);\n if (!year || !month || !day) return null;\n const date = new Date(year, month - 1, day);\n // Verify date wasn't auto-corrected (e.g. Feb 30 -> Mar 1)\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return null;\n }\n return date;\n}\n\nexport function formatDateToString(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Mẫu định dạng ngày của lưới: ngày NGẮN theo locale date-fns — `en-US` ra `12/25/2024`,\n * `vi` / `en-GB` ra `25/12/2024`. Một mẫu cho cả hiển thị lẫn phép đọc lại ({@link parseDateInput}),\n * để thứ người dùng THẤY cũng là thứ dán vào được.\n */\nconst DATE_DISPLAY_FORMAT = \"P\";\n\n/**\n * Hiển thị ngày dạng ngắn theo `locale` (dùng cho ô ngày, bộ lọc ngày).\n *\n * Hàm THUẦN nên không đọc được ngôn ngữ của lưới: component truyền locale đang có hiệu lực vào\n * (`useDataGridDateLocale()`). Bỏ trống `locale` = locale mặc định của date-fns (`enUS`, trừ khi\n * app đã đổi bằng `setDefaultOptions`).\n *\n * ⚠ Trước `3.0.0` hàm này luôn ra `dd/MM/yyyy` bất kể ngôn ngữ. Cần lại đúng chuỗi cũ thì truyền\n * `vi` (`import { vi } from \"date-fns/locale\"`).\n */\nexport function formatDate(date: Date | string | number | null | undefined, locale?: DateFnsLocale): string {\n if (!date) return \"\";\n const value = date instanceof Date ? date : new Date(date);\n if (Number.isNaN(value.getTime())) return \"\";\n return format(value, DATE_DISPLAY_FORMAT, { locale });\n}\n\n/** Như {@link formatDate}, cho giá trị thô của ô ngày (`yyyy-MM-dd` hoặc `Date`). */\nexport function formatDateForDisplay(dateStr: unknown, locale?: DateFnsLocale): string {\n if (!dateStr) return \"\";\n const date = parseLocalDate(dateStr);\n if (!date) return typeof dateStr === \"string\" ? dateStr : \"\";\n return formatDate(date, locale);\n}\n\n/**\n * Đọc chuỗi ngày DÁN vào ô ngày. Thử dạng ngắn của `locale` trước — đúng dạng lưới đang hiển\n * thị, nên `25/12/2024` dán vào lưới tiếng Việt là 25 tháng 12, không phải lỗi — rồi mới tới\n * `new Date()` (ISO `yyyy-MM-dd`, và những gì JS tự hiểu). `null` khi không đọc được.\n *\n * Năm phải đủ 4 chữ số: mẫu `\"P\"` đọc `25/12/24` thành năm 24 sau Công nguyên, một ngày \"hợp lệ\"\n * mà không ai muốn — chuỗi như thế rơi xuống `new Date()` như trước.\n */\nexport function parseDateInput(text: string, locale?: DateFnsLocale): Date | null {\n const trimmed = text.trim();\n if (!trimmed) return null;\n const localized = parse(trimmed, DATE_DISPLAY_FORMAT, new Date(), { locale });\n if (isValid(localized) && localized.getFullYear() >= 1000) return localized;\n const date = new Date(trimmed);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/**\n * Id cho tệp đính kèm. `crypto.randomUUID` chỉ có ở secure context (https/localhost) —\n * app nội bộ chạy http trong mạng bệnh viện sẽ không có, nên phải có đường lui.\n */\nexport function generateFileId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `file-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Chép text vào clipboard. `navigator.clipboard` cũng chỉ có ở secure context,\n * nên rơi về `document.execCommand(\"copy\")` (đã deprecated nhưng vẫn chạy mọi trình duyệt).\n * Ném lỗi khi cả hai đường đều không dùng được để phía gọi báo toast.\n */\nexport async function writeClipboardText(text: string): Promise<void> {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n try {\n await navigator.clipboard.writeText(text);\n return;\n } catch {\n // Bị chặn quyền → thử đường lui bên dưới\n }\n }\n\n if (!copyWithExecCommand(text)) {\n throw new Error(\"Clipboard không khả dụng\");\n }\n}\n\nfunction copyWithExecCommand(text: string): boolean {\n if (typeof document === \"undefined\") return false;\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.top = \"0\";\n textarea.style.insetInlineStart = \"-9999px\";\n document.body.appendChild(textarea);\n\n const previouslyFocused = document.activeElement;\n textarea.select();\n\n let copied = false;\n try {\n copied = document.execCommand(\"copy\");\n } catch {\n copied = false;\n }\n\n document.body.removeChild(textarea);\n if (previouslyFocused instanceof HTMLElement) {\n previouslyFocused.focus();\n }\n\n return copied;\n}\n\n/** Đọc clipboard khi có quyền; trả `null` nếu trình duyệt/ngữ cảnh không cho đọc. */\nexport async function readClipboardText(): Promise<string | null> {\n if (typeof navigator === \"undefined\" || !navigator.clipboard?.readText) return null;\n try {\n return await navigator.clipboard.readText();\n } catch {\n return null;\n }\n}\n\nexport function formatFileSize(bytes: number): string {\n if (bytes <= 0 || !Number.isFinite(bytes)) return \"0 B\";\n const k = 1024;\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\n const i = Math.min(sizes.length - 1, Math.floor(Math.log(bytes) / Math.log(k)));\n return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;\n}\n\nexport function getFileIcon(type: string): React.ComponentType<React.SVGProps<SVGSVGElement>> {\n if (type.startsWith(\"image/\")) return FileImage;\n if (type.startsWith(\"video/\")) return FileVideo;\n if (type.startsWith(\"audio/\")) return FileAudio;\n if (type.includes(\"pdf\")) return FileText;\n if (type.includes(\"zip\") || type.includes(\"rar\")) return FileArchive;\n if (type.includes(\"word\") || type.includes(\"document\") || type.includes(\"doc\")) return FileText;\n if (type.includes(\"sheet\") || type.includes(\"excel\") || type.includes(\"xls\")) return FileSpreadsheet;\n if (type.includes(\"presentation\") || type.includes(\"powerpoint\") || type.includes(\"ppt\")) return Presentation;\n return File;\n}\n"],"mappings":"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","names":["rows"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comitor/ui",
3
- "version": "3.11.0",
3
+ "version": "3.11.1",
4
4
  "description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
5
5
  "license": "MIT",
6
6
  "type": "module",