@underverse-ui/underverse 1.0.70 → 1.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20112,10 +20112,65 @@ var TableCaption = React52.forwardRef(({ className, ...props }, ref) => /* @__PU
20112
20112
  TableCaption.displayName = "TableCaption";
20113
20113
 
20114
20114
  // src/components/DataTable/DataTable.tsx
20115
- import React61 from "react";
20115
+ import React62 from "react";
20116
20116
 
20117
20117
  // src/components/DataTable/components/DataTableBody.tsx
20118
+ import React53 from "react";
20118
20119
  import { jsx as jsx62, jsxs as jsxs52 } from "react/jsx-runtime";
20120
+ function DataTableOverflowText({
20121
+ text,
20122
+ align
20123
+ }) {
20124
+ const triggerId = React53.useId();
20125
+ const [isOverflowing, setIsOverflowing] = React53.useState(false);
20126
+ const alignClass = align === "right" ? "text-right" : align === "center" ? "text-center" : "text-left";
20127
+ const measureOverflow = React53.useCallback(() => {
20128
+ if (typeof document === "undefined") return;
20129
+ const element = document.querySelector(`[data-underverse-datatable-cell="${triggerId}"]`);
20130
+ if (!element) return;
20131
+ setIsOverflowing(
20132
+ element.scrollWidth - element.clientWidth > 1 || element.scrollHeight - element.clientHeight > 1
20133
+ );
20134
+ }, [triggerId]);
20135
+ React53.useLayoutEffect(() => {
20136
+ measureOverflow();
20137
+ }, [measureOverflow, text]);
20138
+ React53.useEffect(() => {
20139
+ if (typeof document === "undefined") return;
20140
+ const element = document.querySelector(`[data-underverse-datatable-cell="${triggerId}"]`);
20141
+ if (!element) return;
20142
+ if (typeof ResizeObserver === "undefined") return;
20143
+ const observer = new ResizeObserver(() => {
20144
+ measureOverflow();
20145
+ });
20146
+ observer.observe(element);
20147
+ return () => observer.disconnect();
20148
+ }, [measureOverflow, triggerId]);
20149
+ const trigger = /* @__PURE__ */ jsx62(
20150
+ "button",
20151
+ {
20152
+ type: "button",
20153
+ "data-underverse-datatable-cell": triggerId,
20154
+ onMouseEnter: measureOverflow,
20155
+ onFocus: measureOverflow,
20156
+ className: cn(
20157
+ "block w-full truncate bg-transparent p-0 font-inherit text-inherit select-text",
20158
+ "cursor-text",
20159
+ alignClass
20160
+ ),
20161
+ children: text
20162
+ }
20163
+ );
20164
+ return /* @__PURE__ */ jsx62(
20165
+ Tooltip,
20166
+ {
20167
+ disabled: !isOverflowing,
20168
+ placement: "top",
20169
+ content: /* @__PURE__ */ jsx62("div", { className: cn("max-w-[min(40rem,calc(100vw-2rem))] whitespace-pre-wrap break-all select-text", alignClass), children: text }),
20170
+ children: trigger
20171
+ }
20172
+ );
20173
+ }
20119
20174
  function DataTableBodyRows({
20120
20175
  leafColumns,
20121
20176
  displayedData,
@@ -20164,6 +20219,7 @@ function DataTableBodyRows({
20164
20219
  return /* @__PURE__ */ jsx62(
20165
20220
  TableCell,
20166
20221
  {
20222
+ "data-underverse-column-key": col.key,
20167
20223
  style: getStickyColumnStyle(col),
20168
20224
  className: cn(
20169
20225
  cellPadding,
@@ -20172,7 +20228,7 @@ function DataTableBodyRows({
20172
20228
  showBorderLeft && "border-l border-border/60",
20173
20229
  getStickyCellClass(col, isStripedRow)
20174
20230
  ),
20175
- children: col.render ? col.render(value, row, idx) : String(value ?? "")
20231
+ children: col.render ? col.render(value, row, idx) : /* @__PURE__ */ jsx62(DataTableOverflowText, { text: String(value ?? ""), align: col.align })
20176
20232
  },
20177
20233
  col.key
20178
20234
  );
@@ -20184,7 +20240,7 @@ function DataTableBodyRows({
20184
20240
  }
20185
20241
 
20186
20242
  // src/components/DataTable/components/DataTableHeader.tsx
20187
- import React53 from "react";
20243
+ import React54 from "react";
20188
20244
  import { Filter as FilterIcon } from "lucide-react";
20189
20245
  import { Fragment as Fragment22, jsx as jsx63, jsxs as jsxs53 } from "react/jsx-runtime";
20190
20246
  function DataTableHeader({
@@ -20200,11 +20256,13 @@ function DataTableHeader({
20200
20256
  setCurPage,
20201
20257
  setFilters,
20202
20258
  setSort,
20259
+ onAutoFitColumn,
20260
+ enableHeaderAutoFit,
20203
20261
  getStickyHeaderClass,
20204
20262
  getStickyHeaderCellStyle,
20205
20263
  t
20206
20264
  }) {
20207
- const renderFilterControl = React53.useCallback(
20265
+ const renderFilterControl = React54.useCallback(
20208
20266
  (col) => {
20209
20267
  if (!col.filter) return null;
20210
20268
  const key = col.key;
@@ -20257,7 +20315,7 @@ function DataTableHeader({
20257
20315
  },
20258
20316
  [filters, setCurPage, setFilters, size]
20259
20317
  );
20260
- const renderHeaderContent = React53.useCallback(
20318
+ const renderHeaderContent = React54.useCallback(
20261
20319
  (col, isLeaf) => {
20262
20320
  if (!isLeaf) {
20263
20321
  return /* @__PURE__ */ jsx63(
@@ -20397,22 +20455,55 @@ function DataTableHeader({
20397
20455
  const prevCol = prevCell?.column;
20398
20456
  const isAfterFixedLeft = prevCol?.fixed === "left";
20399
20457
  const showBorderLeft = columnDividers && cellIndex > 0 && !isAfterFixedLeft && !col.fixed;
20400
- return /* @__PURE__ */ jsx63(
20458
+ return /* @__PURE__ */ jsxs53(
20401
20459
  TableHead,
20402
20460
  {
20403
20461
  colSpan,
20404
20462
  rowSpan,
20463
+ "data-underverse-column-key": isLeaf ? col.key : void 0,
20405
20464
  style: {
20406
20465
  width: col.width,
20407
20466
  ...getStickyHeaderCellStyle(headerCell)
20408
20467
  },
20409
20468
  className: cn(
20469
+ "relative",
20410
20470
  (col.align === "right" || !col.align && headerAlign === "right") && "text-right",
20411
20471
  (col.align === "center" || !col.align && headerAlign === "center") && "text-center",
20412
20472
  showBorderLeft && "border-l border-border/60",
20413
20473
  getStickyHeaderClass(col)
20414
20474
  ),
20415
- children: renderHeaderContent(col, isLeaf)
20475
+ children: [
20476
+ renderHeaderContent(col, isLeaf),
20477
+ isLeaf && enableHeaderAutoFit && /* @__PURE__ */ jsx63(
20478
+ Tooltip,
20479
+ {
20480
+ placement: "top",
20481
+ content: /* @__PURE__ */ jsx63("span", { className: "text-xs font-medium", children: "Double click to auto-fit" }),
20482
+ children: /* @__PURE__ */ jsx63(
20483
+ "button",
20484
+ {
20485
+ type: "button",
20486
+ "aria-label": `Auto fit ${String(col.title)}`,
20487
+ onClick: (event) => {
20488
+ event.preventDefault();
20489
+ event.stopPropagation();
20490
+ },
20491
+ onDoubleClick: (event) => {
20492
+ event.preventDefault();
20493
+ event.stopPropagation();
20494
+ onAutoFitColumn?.(col.key);
20495
+ },
20496
+ className: cn(
20497
+ "absolute inset-y-0 right-0 z-10 w-3 -mr-1",
20498
+ "cursor-col-resize select-none bg-transparent",
20499
+ "after:absolute after:inset-y-2 after:right-[3px] after:w-px after:bg-border/0 after:transition-colors",
20500
+ "hover:after:bg-primary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary"
20501
+ )
20502
+ }
20503
+ )
20504
+ }
20505
+ )
20506
+ ]
20416
20507
  },
20417
20508
  col.key
20418
20509
  );
@@ -20420,7 +20511,7 @@ function DataTableHeader({
20420
20511
  }
20421
20512
 
20422
20513
  // src/components/DataTable/components/Pagination.tsx
20423
- import React54 from "react";
20514
+ import React55 from "react";
20424
20515
  import { jsx as jsx64, jsxs as jsxs54 } from "react/jsx-runtime";
20425
20516
  function DataTablePagination({
20426
20517
  totalItems,
@@ -20432,7 +20523,7 @@ function DataTablePagination({
20432
20523
  size
20433
20524
  }) {
20434
20525
  const totalPages = Math.ceil(totalItems / curPageSize);
20435
- const pages = React54.useMemo(() => {
20526
+ const pages = React55.useMemo(() => {
20436
20527
  const result = [];
20437
20528
  if (totalPages <= 5) {
20438
20529
  for (let i = 1; i <= totalPages; i++) result.push(i);
@@ -20514,7 +20605,7 @@ function DataTablePagination({
20514
20605
  }
20515
20606
 
20516
20607
  // src/components/DataTable/components/Toolbar.tsx
20517
- import React55 from "react";
20608
+ import React56 from "react";
20518
20609
 
20519
20610
  // src/components/DataTable/utils/headers.ts
20520
20611
  function isLeafColumn(col) {
@@ -20635,7 +20726,7 @@ function DataTableToolbar({
20635
20726
  const controlButtonClass = size === "sm" ? "h-7 px-2 text-xs" : size === "lg" ? "h-9 px-3 text-sm" : "h-8 px-2";
20636
20727
  const iconClass = size === "sm" ? "w-3.5 h-3.5 mr-1" : "w-4 h-4 mr-1";
20637
20728
  const captionClass = size === "sm" ? "text-xs" : size === "lg" ? "text-sm" : "text-sm";
20638
- const leafCols = React55.useMemo(() => getLeafColumns(columns), [columns]);
20729
+ const leafCols = React56.useMemo(() => getLeafColumns(columns), [columns]);
20639
20730
  return /* @__PURE__ */ jsxs55("div", { className: "flex items-center justify-between gap-4 mb-1", children: [
20640
20731
  /* @__PURE__ */ jsx65("div", { className: captionClass + " text-muted-foreground", children: caption }),
20641
20732
  /* @__PURE__ */ jsxs55("div", { className: "flex items-center gap-2", children: [
@@ -20703,10 +20794,10 @@ function DataTableToolbar({
20703
20794
  }
20704
20795
 
20705
20796
  // src/components/DataTable/hooks/useDebounced.ts
20706
- import React56 from "react";
20797
+ import React57 from "react";
20707
20798
  function useDebounced(value, delay = 300) {
20708
- const [debounced, setDebounced] = React56.useState(value);
20709
- React56.useEffect(() => {
20799
+ const [debounced, setDebounced] = React57.useState(value);
20800
+ React57.useEffect(() => {
20710
20801
  const id = setTimeout(() => setDebounced(value), delay);
20711
20802
  return () => clearTimeout(id);
20712
20803
  }, [value, delay]);
@@ -20714,7 +20805,7 @@ function useDebounced(value, delay = 300) {
20714
20805
  }
20715
20806
 
20716
20807
  // src/components/DataTable/hooks/useDataTableModel.ts
20717
- import React57 from "react";
20808
+ import React58 from "react";
20718
20809
 
20719
20810
  // src/components/DataTable/utils/columns.ts
20720
20811
  function getColumnWidth(col, fallback = 150) {
@@ -20742,22 +20833,22 @@ function useDataTableModel({
20742
20833
  isServerMode,
20743
20834
  total
20744
20835
  }) {
20745
- const visibleColsSet = React57.useMemo(() => new Set(visibleCols), [visibleCols]);
20746
- const allLeafColumns = React57.useMemo(() => getLeafColumns(columns), [columns]);
20747
- const columnMap = React57.useMemo(() => {
20836
+ const visibleColsSet = React58.useMemo(() => new Set(visibleCols), [visibleCols]);
20837
+ const allLeafColumns = React58.useMemo(() => getLeafColumns(columns), [columns]);
20838
+ const columnMap = React58.useMemo(() => {
20748
20839
  return new Map(allLeafColumns.map((column) => [column.key, column]));
20749
20840
  }, [allLeafColumns]);
20750
- const visibleColumns = React57.useMemo(() => {
20841
+ const visibleColumns = React58.useMemo(() => {
20751
20842
  return filterVisibleColumns(columns, visibleColsSet);
20752
20843
  }, [columns, visibleColsSet]);
20753
- const leafColumns = React57.useMemo(() => {
20844
+ const leafColumns = React58.useMemo(() => {
20754
20845
  return getLeafColumnsWithFixedInheritance(visibleColumns);
20755
20846
  }, [visibleColumns]);
20756
- const headerRows = React57.useMemo(() => buildHeaderRows(visibleColumns), [visibleColumns]);
20757
- const totalColumnsWidth = React57.useMemo(() => {
20847
+ const headerRows = React58.useMemo(() => buildHeaderRows(visibleColumns), [visibleColumns]);
20848
+ const totalColumnsWidth = React58.useMemo(() => {
20758
20849
  return leafColumns.reduce((sum, column) => sum + getColumnWidth(column), 0);
20759
20850
  }, [leafColumns]);
20760
- const processedData = React57.useMemo(() => {
20851
+ const processedData = React58.useMemo(() => {
20761
20852
  if (isServerMode) return data;
20762
20853
  let result = [...data];
20763
20854
  if (Object.keys(filters).length > 0) {
@@ -20789,7 +20880,7 @@ function useDataTableModel({
20789
20880
  return result;
20790
20881
  }, [columnMap, data, filters, isServerMode, sort]);
20791
20882
  const totalItems = isServerMode ? total : processedData.length;
20792
- const displayedData = React57.useMemo(() => {
20883
+ const displayedData = React58.useMemo(() => {
20793
20884
  if (isServerMode) return data;
20794
20885
  const start = (curPage - 1) * curPageSize;
20795
20886
  return processedData.slice(start, start + curPageSize);
@@ -20805,10 +20896,10 @@ function useDataTableModel({
20805
20896
  }
20806
20897
 
20807
20898
  // src/components/DataTable/hooks/useDataTableState.ts
20808
- import React59 from "react";
20899
+ import React60 from "react";
20809
20900
 
20810
20901
  // src/components/DataTable/hooks/usePageSizeStorage.ts
20811
- import React58 from "react";
20902
+ import React59 from "react";
20812
20903
  function readStoredPageSize(storageKey) {
20813
20904
  if (typeof window === "undefined" || !storageKey) return null;
20814
20905
  try {
@@ -20821,8 +20912,8 @@ function readStoredPageSize(storageKey) {
20821
20912
  }
20822
20913
  }
20823
20914
  function usePageSizeStorage({ pageSize, storageKey }) {
20824
- const storedPageSize = React58.useMemo(() => readStoredPageSize(storageKey), [storageKey]);
20825
- const [overrideState, setOverrideState] = React58.useState({
20915
+ const storedPageSize = React59.useMemo(() => readStoredPageSize(storageKey), [storageKey]);
20916
+ const [overrideState, setOverrideState] = React59.useState({
20826
20917
  storageKey,
20827
20918
  pageSize: null
20828
20919
  });
@@ -20830,7 +20921,7 @@ function usePageSizeStorage({ pageSize, storageKey }) {
20830
20921
  const persistedPageSize = storageKey ? overridePageSize ?? storedPageSize : null;
20831
20922
  const loadedFromStorage = persistedPageSize != null;
20832
20923
  const curPageSize = storageKey ? persistedPageSize ?? pageSize : overridePageSize ?? pageSize;
20833
- const setCurPageSize = React58.useCallback(
20924
+ const setCurPageSize = React59.useCallback(
20834
20925
  (nextPageSize) => {
20835
20926
  const baseValue = storageKey ? persistedPageSize ?? pageSize : overridePageSize ?? pageSize;
20836
20927
  const resolved = typeof nextPageSize === "function" ? nextPageSize(baseValue) : nextPageSize;
@@ -20859,17 +20950,17 @@ function useDataTableState({
20859
20950
  size,
20860
20951
  storageKey
20861
20952
  }) {
20862
- const allLeafColumns = React59.useMemo(() => getLeafColumns(columns), [columns]);
20863
- const defaultVisibleLeafKeys = React59.useMemo(() => allLeafColumns.filter((column) => column.visible !== false).map((column) => column.key), [allLeafColumns]);
20864
- const knownLeafKeysRef = React59.useRef(new Set(defaultVisibleLeafKeys));
20865
- const [headerAlign, setHeaderAlign] = React59.useState("left");
20866
- const [visibleCols, setVisibleCols] = React59.useState(defaultVisibleLeafKeys);
20867
- const [filters, setFilters] = React59.useState({});
20868
- const [sort, setSort] = React59.useState(null);
20869
- const [density, setDensity] = React59.useState(() => SIZE_TO_DENSITY[size]);
20870
- const [curPage, setCurPage] = React59.useState(page);
20953
+ const allLeafColumns = React60.useMemo(() => getLeafColumns(columns), [columns]);
20954
+ const defaultVisibleLeafKeys = React60.useMemo(() => allLeafColumns.filter((column) => column.visible !== false).map((column) => column.key), [allLeafColumns]);
20955
+ const knownLeafKeysRef = React60.useRef(new Set(defaultVisibleLeafKeys));
20956
+ const [headerAlign, setHeaderAlign] = React60.useState("left");
20957
+ const [visibleCols, setVisibleCols] = React60.useState(defaultVisibleLeafKeys);
20958
+ const [filters, setFilters] = React60.useState({});
20959
+ const [sort, setSort] = React60.useState(null);
20960
+ const [density, setDensity] = React60.useState(() => SIZE_TO_DENSITY[size]);
20961
+ const [curPage, setCurPage] = React60.useState(page);
20871
20962
  const { curPageSize, setCurPageSize } = usePageSizeStorage({ pageSize, storageKey });
20872
- React59.useEffect(() => {
20963
+ React60.useEffect(() => {
20873
20964
  const knownLeafKeys = knownLeafKeysRef.current;
20874
20965
  setVisibleCols((prev) => {
20875
20966
  const prevSet = new Set(prev);
@@ -20877,10 +20968,10 @@ function useDataTableState({
20877
20968
  });
20878
20969
  knownLeafKeysRef.current = new Set(allLeafColumns.map((column) => column.key));
20879
20970
  }, [allLeafColumns]);
20880
- React59.useEffect(() => {
20971
+ React60.useEffect(() => {
20881
20972
  setCurPage(page);
20882
20973
  }, [page]);
20883
- React59.useEffect(() => {
20974
+ React60.useEffect(() => {
20884
20975
  setDensity(SIZE_TO_DENSITY[size]);
20885
20976
  }, [size]);
20886
20977
  return {
@@ -20902,7 +20993,7 @@ function useDataTableState({
20902
20993
  }
20903
20994
 
20904
20995
  // src/components/DataTable/hooks/useStickyColumns.ts
20905
- import React60 from "react";
20996
+ import React61 from "react";
20906
20997
 
20907
20998
  // src/components/DataTable/utils/sticky.ts
20908
20999
  function buildStickyLayout(visibleColumns) {
@@ -20949,8 +21040,8 @@ function resolveGroupStickyPosition(column, positions) {
20949
21040
 
20950
21041
  // src/components/DataTable/hooks/useStickyColumns.ts
20951
21042
  function useStickyColumns(visibleColumns) {
20952
- const { positions, leftBoundaryKey, rightBoundaryKey } = React60.useMemo(() => buildStickyLayout(visibleColumns), [visibleColumns]);
20953
- const getStickyColumnStyle = React60.useCallback(
21043
+ const { positions, leftBoundaryKey, rightBoundaryKey } = React61.useMemo(() => buildStickyLayout(visibleColumns), [visibleColumns]);
21044
+ const getStickyColumnStyle = React61.useCallback(
20954
21045
  (col) => {
20955
21046
  const pos = resolveStickyPosition(col, positions);
20956
21047
  if (!pos) return {};
@@ -20961,7 +21052,7 @@ function useStickyColumns(visibleColumns) {
20961
21052
  },
20962
21053
  [positions]
20963
21054
  );
20964
- const getBoundaryShadowClass = React60.useCallback(
21055
+ const getBoundaryShadowClass = React61.useCallback(
20965
21056
  (col) => {
20966
21057
  if (col.fixed === "left" && col.key === leftBoundaryKey) {
20967
21058
  return "border-r border-border/80 shadow-[10px_0_16px_-10px_rgba(0,0,0,0.55)]";
@@ -20973,14 +21064,14 @@ function useStickyColumns(visibleColumns) {
20973
21064
  },
20974
21065
  [leftBoundaryKey, rightBoundaryKey]
20975
21066
  );
20976
- const getStickyHeaderClass = React60.useCallback(
21067
+ const getStickyHeaderClass = React61.useCallback(
20977
21068
  (col) => {
20978
21069
  if (!col.fixed) return "";
20979
21070
  return cn("sticky", col.fixed === "left" && "left-0", col.fixed === "right" && "right-0", getBoundaryShadowClass(col), "z-50 !bg-muted");
20980
21071
  },
20981
21072
  [getBoundaryShadowClass]
20982
21073
  );
20983
- const getStickyCellClass = React60.useCallback(
21074
+ const getStickyCellClass = React61.useCallback(
20984
21075
  (col, isStripedRow) => {
20985
21076
  if (!col.fixed) return "";
20986
21077
  return cn(
@@ -20993,7 +21084,7 @@ function useStickyColumns(visibleColumns) {
20993
21084
  },
20994
21085
  [getBoundaryShadowClass]
20995
21086
  );
20996
- const getStickyHeaderCellStyle = React60.useCallback(
21087
+ const getStickyHeaderCellStyle = React61.useCallback(
20997
21088
  (headerCell) => {
20998
21089
  const col = headerCell.column;
20999
21090
  if (headerCell.isLeaf) {
@@ -21092,6 +21183,66 @@ function validateColumns(columns) {
21092
21183
 
21093
21184
  // src/components/DataTable/DataTable.tsx
21094
21185
  import { jsx as jsx66, jsxs as jsxs56 } from "react/jsx-runtime";
21186
+ function applyColumnWidthOverrides(columns, widthOverrides) {
21187
+ return columns.map((column) => {
21188
+ if (column.children?.length) {
21189
+ return {
21190
+ ...column,
21191
+ children: applyColumnWidthOverrides(column.children, widthOverrides)
21192
+ };
21193
+ }
21194
+ const nextWidth = widthOverrides[column.key];
21195
+ if (nextWidth == null) {
21196
+ return column;
21197
+ }
21198
+ return {
21199
+ ...column,
21200
+ width: nextWidth
21201
+ };
21202
+ });
21203
+ }
21204
+ function measureNaturalContentWidth(node) {
21205
+ if (typeof document === "undefined") return 0;
21206
+ const measurementRoot = document.createElement("div");
21207
+ measurementRoot.style.position = "absolute";
21208
+ measurementRoot.style.left = "-99999px";
21209
+ measurementRoot.style.top = "0";
21210
+ measurementRoot.style.visibility = "hidden";
21211
+ measurementRoot.style.pointerEvents = "none";
21212
+ measurementRoot.style.whiteSpace = "nowrap";
21213
+ measurementRoot.style.width = "max-content";
21214
+ measurementRoot.style.maxWidth = "none";
21215
+ measurementRoot.style.minWidth = "0";
21216
+ measurementRoot.style.overflow = "visible";
21217
+ const clone = (node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node).cloneNode(true);
21218
+ if (!(clone instanceof HTMLElement)) {
21219
+ return 0;
21220
+ }
21221
+ const sourceStyle = window.getComputedStyle(node);
21222
+ const cloneStyle = window.getComputedStyle(clone);
21223
+ clone.style.width = "max-content";
21224
+ clone.style.maxWidth = "none";
21225
+ clone.style.minWidth = "0";
21226
+ clone.style.overflow = "visible";
21227
+ clone.style.textOverflow = "clip";
21228
+ clone.style.whiteSpace = cloneStyle.whiteSpace === "normal" ? "pre-wrap" : "nowrap";
21229
+ measurementRoot.appendChild(clone);
21230
+ document.body.appendChild(measurementRoot);
21231
+ const horizontalPadding = parseFloat(sourceStyle.paddingLeft || "0") + parseFloat(sourceStyle.paddingRight || "0");
21232
+ const horizontalBorder = parseFloat(sourceStyle.borderLeftWidth || "0") + parseFloat(sourceStyle.borderRightWidth || "0");
21233
+ const cloneRectWidth = clone.getBoundingClientRect().width;
21234
+ const cloneScrollWidth = clone.scrollWidth;
21235
+ const sourceScrollWidth = node.scrollWidth;
21236
+ const measuredContentWidth = Math.max(cloneRectWidth, cloneScrollWidth) || sourceScrollWidth;
21237
+ const measured = Math.ceil(measuredContentWidth + horizontalPadding + horizontalBorder);
21238
+ document.body.removeChild(measurementRoot);
21239
+ return measured;
21240
+ }
21241
+ function isNodeOverflowing(node) {
21242
+ const contentNode = node.firstElementChild instanceof HTMLElement ? node.firstElementChild : node;
21243
+ return node.scrollWidth > node.clientWidth + 1 || contentNode.scrollWidth > contentNode.clientWidth + 1;
21244
+ }
21245
+ var AUTO_FIT_BUFFER_PX = 8;
21095
21246
  function DataTable({
21096
21247
  columns,
21097
21248
  data,
@@ -21115,9 +21266,15 @@ function DataTable({
21115
21266
  stickyHeader = true,
21116
21267
  maxHeight = 500,
21117
21268
  useOverlayScrollbar = false,
21269
+ enableHeaderAutoFit = true,
21118
21270
  labels
21119
21271
  }) {
21120
21272
  const t = useSmartTranslations("Common");
21273
+ const [columnWidthOverrides, setColumnWidthOverrides] = React62.useState({});
21274
+ const columnsWithWidthOverrides = React62.useMemo(
21275
+ () => applyColumnWidthOverrides(columns, columnWidthOverrides),
21276
+ [columnWidthOverrides, columns]
21277
+ );
21121
21278
  const {
21122
21279
  headerAlign,
21123
21280
  setHeaderAlign,
@@ -21134,22 +21291,22 @@ function DataTable({
21134
21291
  curPageSize,
21135
21292
  setCurPageSize
21136
21293
  } = useDataTableState({
21137
- columns,
21294
+ columns: columnsWithWidthOverrides,
21138
21295
  page,
21139
21296
  pageSize,
21140
21297
  size,
21141
21298
  storageKey
21142
21299
  });
21143
- React61.useEffect(() => {
21300
+ React62.useEffect(() => {
21144
21301
  if (process.env.NODE_ENV === "development") {
21145
- const warnings = validateColumns(columns);
21302
+ const warnings = validateColumns(columnsWithWidthOverrides);
21146
21303
  warnings.forEach((w) => console.warn(`[DataTable] ${w}`));
21147
21304
  }
21148
- }, [columns]);
21305
+ }, [columnsWithWidthOverrides]);
21149
21306
  const debouncedFilters = useDebounced(filters, 350);
21150
21307
  const isServerMode = Boolean(onQueryChange);
21151
- const hasEmittedQuery = React61.useRef(false);
21152
- React61.useEffect(() => {
21308
+ const hasEmittedQuery = React62.useRef(false);
21309
+ React62.useEffect(() => {
21153
21310
  if (!onQueryChange) return;
21154
21311
  if (!hasEmittedQuery.current) {
21155
21312
  hasEmittedQuery.current = true;
@@ -21157,7 +21314,7 @@ function DataTable({
21157
21314
  }
21158
21315
  onQueryChange({ filters: debouncedFilters, sort, page: curPage, pageSize: curPageSize });
21159
21316
  }, [debouncedFilters, sort, curPage, curPageSize, onQueryChange]);
21160
- React61.useEffect(() => {
21317
+ React62.useEffect(() => {
21161
21318
  if (process.env.NODE_ENV !== "development" || rowKey) return;
21162
21319
  const hasQueryFeatures = columns.some((column) => column.sortable || column.filter) || Boolean(pageSizeOptions?.length) || isServerMode;
21163
21320
  if (!hasQueryFeatures) return;
@@ -21169,7 +21326,7 @@ function DataTable({
21169
21326
  const headerMinHeightClass = size === "sm" ? "min-h-9" : size === "lg" ? "min-h-11" : "min-h-10";
21170
21327
  const sortIconClass = size === "sm" ? "w-3.5 h-3.5" : size === "lg" ? "w-4 h-4" : "w-3.5 h-3.5";
21171
21328
  const { visibleColumns, leafColumns, headerRows, totalColumnsWidth, totalItems, displayedData } = useDataTableModel({
21172
- columns,
21329
+ columns: columnsWithWidthOverrides,
21173
21330
  data,
21174
21331
  visibleCols,
21175
21332
  filters,
@@ -21185,15 +21342,45 @@ function DataTable({
21185
21342
  if (typeof rowKey === "function") return String(rowKey(row));
21186
21343
  return String(row[rowKey]);
21187
21344
  };
21188
- const viewportRef = React61.useRef(null);
21345
+ const viewportRef = React62.useRef(null);
21346
+ const tableRef = React62.useRef(null);
21189
21347
  useOverlayScrollbarTarget(viewportRef, { enabled: useOverlayScrollbar });
21348
+ const autoFitColumn = React62.useCallback((columnKey) => {
21349
+ const tableElement = tableRef.current;
21350
+ if (!tableElement) return;
21351
+ const nodes = Array.from(
21352
+ tableElement.querySelectorAll(`[data-underverse-column-key="${columnKey}"]`)
21353
+ );
21354
+ if (nodes.length === 0) return;
21355
+ const hasOverflow = nodes.some((node) => isNodeOverflowing(node));
21356
+ if (!hasOverflow) return;
21357
+ const measuredWidth = nodes.reduce((maxWidth, node) => {
21358
+ const nextWidth2 = measureNaturalContentWidth(node);
21359
+ return Math.max(maxWidth, nextWidth2);
21360
+ }, 0);
21361
+ const currentRenderedWidth = nodes.reduce((maxWidth, node) => {
21362
+ const nextWidth2 = Math.max(
21363
+ Math.ceil(node.getBoundingClientRect().width || 0),
21364
+ node.offsetWidth || 0,
21365
+ node.clientWidth || 0
21366
+ );
21367
+ return Math.max(maxWidth, nextWidth2);
21368
+ }, 0);
21369
+ if (measuredWidth <= 0) return;
21370
+ if (currentRenderedWidth > 0 && measuredWidth <= currentRenderedWidth + AUTO_FIT_BUFFER_PX) return;
21371
+ const nextWidth = Math.max(80, measuredWidth + AUTO_FIT_BUFFER_PX);
21372
+ setColumnWidthOverrides((prev) => {
21373
+ if (prev[columnKey] === nextWidth) return prev;
21374
+ return { ...prev, [columnKey]: nextWidth };
21375
+ });
21376
+ }, []);
21190
21377
  return /* @__PURE__ */ jsxs56("div", { className: cn("space-y-2", className), children: [
21191
21378
  /* @__PURE__ */ jsx66(
21192
21379
  DataTableToolbar,
21193
21380
  {
21194
21381
  caption,
21195
21382
  toolbar,
21196
- columns,
21383
+ columns: columnsWithWidthOverrides,
21197
21384
  visibleCols,
21198
21385
  setVisibleCols,
21199
21386
  enableDensityToggle,
@@ -21223,6 +21410,7 @@ function DataTable({
21223
21410
  children: /* @__PURE__ */ jsxs56(
21224
21411
  Table,
21225
21412
  {
21413
+ ref: tableRef,
21226
21414
  disableContainer: true,
21227
21415
  className: cn(
21228
21416
  "table-fixed",
@@ -21245,6 +21433,8 @@ function DataTable({
21245
21433
  setCurPage,
21246
21434
  setFilters,
21247
21435
  setSort,
21436
+ onAutoFitColumn: autoFitColumn,
21437
+ enableHeaderAutoFit,
21248
21438
  getStickyHeaderClass,
21249
21439
  getStickyHeaderCellStyle,
21250
21440
  t
@@ -21291,10 +21481,10 @@ function DataTable({
21291
21481
  var DataTable_default = DataTable;
21292
21482
 
21293
21483
  // src/components/Form.tsx
21294
- import * as React62 from "react";
21484
+ import * as React63 from "react";
21295
21485
  import { Controller, FormProvider, useFormContext, useForm } from "react-hook-form";
21296
21486
  import { jsx as jsx67, jsxs as jsxs57 } from "react/jsx-runtime";
21297
- var FormConfigContext = React62.createContext({ size: "md" });
21487
+ var FormConfigContext = React63.createContext({ size: "md" });
21298
21488
  var FormWrapper = ({
21299
21489
  children,
21300
21490
  onSubmit,
@@ -21307,7 +21497,7 @@ var FormWrapper = ({
21307
21497
  const methods = useForm({
21308
21498
  defaultValues: initialValues
21309
21499
  });
21310
- React62.useEffect(() => {
21500
+ React63.useEffect(() => {
21311
21501
  if (initialValues) {
21312
21502
  methods.reset(initialValues);
21313
21503
  }
@@ -21316,15 +21506,15 @@ var FormWrapper = ({
21316
21506
  return /* @__PURE__ */ jsx67(FormProvider, { ...methods, children: /* @__PURE__ */ jsx67(FormConfigContext.Provider, { value: { size }, children: /* @__PURE__ */ jsx67("form", { onSubmit: methods.handleSubmit(onSubmit), className, ...formProps, children }) }) });
21317
21507
  };
21318
21508
  var Form = FormWrapper;
21319
- var FormFieldContext = React62.createContext({});
21509
+ var FormFieldContext = React63.createContext({});
21320
21510
  var FormField = ({
21321
21511
  ...props
21322
21512
  }) => {
21323
21513
  return /* @__PURE__ */ jsx67(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx67(Controller, { ...props }) });
21324
21514
  };
21325
21515
  var useFormField = () => {
21326
- const fieldContext = React62.useContext(FormFieldContext);
21327
- const itemContext = React62.useContext(FormItemContext);
21516
+ const fieldContext = React63.useContext(FormFieldContext);
21517
+ const itemContext = React63.useContext(FormItemContext);
21328
21518
  const { getFieldState, formState } = useFormContext();
21329
21519
  if (!fieldContext) {
21330
21520
  throw new Error("useFormField must be used within FormField");
@@ -21340,16 +21530,16 @@ var useFormField = () => {
21340
21530
  ...fieldState
21341
21531
  };
21342
21532
  };
21343
- var FormItemContext = React62.createContext({});
21344
- var FormItem = React62.forwardRef(({ className, ...props }, ref) => {
21345
- const id = React62.useId();
21533
+ var FormItemContext = React63.createContext({});
21534
+ var FormItem = React63.forwardRef(({ className, ...props }, ref) => {
21535
+ const id = React63.useId();
21346
21536
  return /* @__PURE__ */ jsx67(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx67("div", { ref, className: cn("space-y-2", className), ...props }) });
21347
21537
  });
21348
21538
  FormItem.displayName = "FormItem";
21349
- var FormLabel = React62.forwardRef(
21539
+ var FormLabel = React63.forwardRef(
21350
21540
  ({ className, children, required, ...props }, ref) => {
21351
21541
  const { error, formItemId } = useFormField();
21352
- const config = React62.useContext(FormConfigContext);
21542
+ const config = React63.useContext(FormConfigContext);
21353
21543
  const sizeClass = config.size === "sm" ? "text-xs" : config.size === "lg" ? "text-base" : "text-sm";
21354
21544
  return /* @__PURE__ */ jsxs57(Label, { ref, className: cn(sizeClass, error && "text-destructive", className), htmlFor: formItemId, ...props, children: [
21355
21545
  children,
@@ -21358,7 +21548,7 @@ var FormLabel = React62.forwardRef(
21358
21548
  }
21359
21549
  );
21360
21550
  FormLabel.displayName = "FormLabel";
21361
- var FormControl = React62.forwardRef(({ ...props }, ref) => {
21551
+ var FormControl = React63.forwardRef(({ ...props }, ref) => {
21362
21552
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
21363
21553
  return /* @__PURE__ */ jsx67(
21364
21554
  "div",
@@ -21372,12 +21562,12 @@ var FormControl = React62.forwardRef(({ ...props }, ref) => {
21372
21562
  );
21373
21563
  });
21374
21564
  FormControl.displayName = "FormControl";
21375
- var FormDescription = React62.forwardRef(({ className, ...props }, ref) => {
21565
+ var FormDescription = React63.forwardRef(({ className, ...props }, ref) => {
21376
21566
  const { formDescriptionId } = useFormField();
21377
21567
  return /* @__PURE__ */ jsx67("p", { ref, id: formDescriptionId, className: cn("text-sm text-muted-foreground", className), ...props });
21378
21568
  });
21379
21569
  FormDescription.displayName = "FormDescription";
21380
- var FormMessage = React62.forwardRef(({ className, children, ...props }, ref) => {
21570
+ var FormMessage = React63.forwardRef(({ className, children, ...props }, ref) => {
21381
21571
  const { error, formMessageId } = useFormField();
21382
21572
  const body = error ? String(error?.message) : children;
21383
21573
  if (!body) {
@@ -21386,7 +21576,7 @@ var FormMessage = React62.forwardRef(({ className, children, ...props }, ref) =>
21386
21576
  return /* @__PURE__ */ jsx67("p", { ref, id: formMessageId, className: cn("text-sm font-medium text-destructive", className), ...props, children: body });
21387
21577
  });
21388
21578
  FormMessage.displayName = "FormMessage";
21389
- var FormInput = React62.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ jsx67(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ jsx67(
21579
+ var FormInput = React63.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ jsx67(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ jsx67(
21390
21580
  FormField,
21391
21581
  {
21392
21582
  name,
@@ -21397,7 +21587,7 @@ var FormInput = React62.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */
21397
21587
  }
21398
21588
  ) }));
21399
21589
  FormInput.displayName = "FormInput";
21400
- var FormCheckbox = React62.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ jsx67(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ jsx67(
21590
+ var FormCheckbox = React63.forwardRef(({ name, ...props }, ref) => /* @__PURE__ */ jsx67(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ jsx67(
21401
21591
  FormField,
21402
21592
  {
21403
21593
  name,
@@ -21421,9 +21611,9 @@ var FormCheckbox = React62.forwardRef(({ name, ...props }, ref) => /* @__PURE__
21421
21611
  }
21422
21612
  ) }));
21423
21613
  FormCheckbox.displayName = "FormCheckbox";
21424
- var FormActions = React62.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx67("div", { ref, className: cn("flex gap-2 justify-end", className), ...props }));
21614
+ var FormActions = React63.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx67("div", { ref, className: cn("flex gap-2 justify-end", className), ...props }));
21425
21615
  FormActions.displayName = "FormActions";
21426
- var FormSubmitButton = React62.forwardRef(
21616
+ var FormSubmitButton = React63.forwardRef(
21427
21617
  ({ children, loading: loading2, ...props }, ref) => /* @__PURE__ */ jsx67(FormConfigContext.Consumer, { children: ({ size }) => /* @__PURE__ */ jsx67(Button_default, { ref, type: "submit", size: props.size ?? size, disabled: loading2, ...props, children }) })
21428
21618
  );
21429
21619
  FormSubmitButton.displayName = "FormSubmitButton";
@@ -21713,7 +21903,7 @@ var VARIANT_STYLES_ALERT = {
21713
21903
  };
21714
21904
 
21715
21905
  // ../../lib/i18n/translation-adapter.tsx
21716
- import * as React64 from "react";
21906
+ import * as React65 from "react";
21717
21907
  import { jsx as jsx72 } from "react/jsx-runtime";
21718
21908
  function isUnresolvedTranslation2(value, namespace, key) {
21719
21909
  return value === key || value === `${namespace}.${key}`;
@@ -21738,7 +21928,7 @@ function useTranslations(namespace) {
21738
21928
  const nextIntlBridge = useNextIntlBridge();
21739
21929
  const internalLocale = useUnderverseLocale();
21740
21930
  const internalT = useUnderverseTranslations(namespace);
21741
- return React64.useCallback((key, params) => {
21931
+ return React65.useCallback((key, params) => {
21742
21932
  if (nextIntlBridge) {
21743
21933
  const nextIntlResult = nextIntlBridge.translate(namespace, key, params);
21744
21934
  if (nextIntlResult.translated && !isUnresolvedTranslation2(nextIntlResult.translated, namespace, key)) {
@@ -21763,7 +21953,7 @@ function useLocale2() {
21763
21953
  }
21764
21954
 
21765
21955
  // src/components/UEditor/UEditor.tsx
21766
- import React74, { useEffect as useEffect35, useImperativeHandle as useImperativeHandle3, useMemo as useMemo25, useRef as useRef32 } from "react";
21956
+ import React75, { useEffect as useEffect35, useImperativeHandle as useImperativeHandle3, useMemo as useMemo25, useRef as useRef32 } from "react";
21767
21957
  import { useEditor, EditorContent } from "@tiptap/react";
21768
21958
 
21769
21959
  // src/components/UEditor/extensions.ts
@@ -21803,7 +21993,7 @@ import { common, createLowlight } from "lowlight";
21803
21993
  import { Extension } from "@tiptap/core";
21804
21994
  import Suggestion from "@tiptap/suggestion";
21805
21995
  import { ReactRenderer } from "@tiptap/react";
21806
- import React65, { forwardRef as forwardRef13, useEffect as useEffect30, useImperativeHandle, useRef as useRef26 } from "react";
21996
+ import React66, { forwardRef as forwardRef13, useEffect as useEffect30, useImperativeHandle, useRef as useRef26 } from "react";
21807
21997
  import {
21808
21998
  FileCode as FileCode2,
21809
21999
  Heading1,
@@ -21846,9 +22036,9 @@ var DEFAULT_MESSAGES = {
21846
22036
  tableDesc: "Insert a table"
21847
22037
  };
21848
22038
  function useResettingIndex2(resetToken) {
21849
- const [state, setState] = React65.useState({ resetToken, index: 0 });
22039
+ const [state, setState] = React66.useState({ resetToken, index: 0 });
21850
22040
  const selectedIndex = Object.is(state.resetToken, resetToken) ? state.index : 0;
21851
- const setSelectedIndex = React65.useCallback((nextIndex) => {
22041
+ const setSelectedIndex = React66.useCallback((nextIndex) => {
21852
22042
  setState((prev) => {
21853
22043
  const prevIndex = Object.is(prev.resetToken, resetToken) ? prev.index : 0;
21854
22044
  return {
@@ -22227,14 +22417,14 @@ import { Extension as Extension3 } from "@tiptap/core";
22227
22417
  import Suggestion2 from "@tiptap/suggestion";
22228
22418
  import { ReactRenderer as ReactRenderer2 } from "@tiptap/react";
22229
22419
  import { PluginKey } from "@tiptap/pm/state";
22230
- import React66, { forwardRef as forwardRef14, useImperativeHandle as useImperativeHandle2 } from "react";
22420
+ import React67, { forwardRef as forwardRef14, useImperativeHandle as useImperativeHandle2 } from "react";
22231
22421
  import { Smile as Smile2 } from "lucide-react";
22232
22422
  import tippy2 from "tippy.js";
22233
22423
  import { jsx as jsx74, jsxs as jsxs63 } from "react/jsx-runtime";
22234
22424
  function useResettingIndex3(resetToken) {
22235
- const [state, setState] = React66.useState({ resetToken, index: 0 });
22425
+ const [state, setState] = React67.useState({ resetToken, index: 0 });
22236
22426
  const selectedIndex = Object.is(state.resetToken, resetToken) ? state.index : 0;
22237
- const setSelectedIndex = React66.useCallback((nextIndex) => {
22427
+ const setSelectedIndex = React67.useCallback((nextIndex) => {
22238
22428
  setState((prev) => {
22239
22429
  const prevIndex = Object.is(prev.resetToken, resetToken) ? prev.index : 0;
22240
22430
  return {
@@ -22875,7 +23065,7 @@ function buildUEditorExtensions({
22875
23065
  }
22876
23066
 
22877
23067
  // src/components/UEditor/toolbar.tsx
22878
- import React71, { useRef as useRef30, useState as useState44 } from "react";
23068
+ import React72, { useRef as useRef30, useState as useState44 } from "react";
22879
23069
  import {
22880
23070
  AlignCenter,
22881
23071
  AlignJustify,
@@ -23362,7 +23552,7 @@ function fileToDataUrl2(file) {
23362
23552
  reader.readAsDataURL(file);
23363
23553
  });
23364
23554
  }
23365
- var ToolbarButton = React71.forwardRef(({ onClick, onMouseDown, active, disabled, children, title, className }, ref) => {
23555
+ var ToolbarButton = React72.forwardRef(({ onClick, onMouseDown, active, disabled, children, title, className }, ref) => {
23366
23556
  const button = /* @__PURE__ */ jsx79(
23367
23557
  "button",
23368
23558
  {
@@ -24580,7 +24770,7 @@ async function prepareUEditorContentForSave({
24580
24770
  }
24581
24771
 
24582
24772
  // src/components/UEditor/table-controls.tsx
24583
- import React73 from "react";
24773
+ import React74 from "react";
24584
24774
 
24585
24775
  // node_modules/prosemirror-model/dist/index.js
24586
24776
  function findDiffStart(a, b, pos) {
@@ -28756,6 +28946,19 @@ import {
28756
28946
  import { Fragment as Fragment27, jsx as jsx81, jsxs as jsxs71 } from "react/jsx-runtime";
28757
28947
  var FALLBACK_TABLE_ROW_HEIGHT = 44;
28758
28948
  var FALLBACK_TABLE_COLUMN_WIDTH = 160;
28949
+ var MENU_HOVER_PADDING = 18;
28950
+ var ROW_HANDLE_HOVER_WIDTH = 28;
28951
+ var COLUMN_HANDLE_HOVER_HEIGHT = 28;
28952
+ var ADD_COLUMN_HOVER_WIDTH = 24;
28953
+ var ADD_ROW_HOVER_HEIGHT = 24;
28954
+ var HANDLE_HOVER_RADIUS = 14;
28955
+ var DEFAULT_HOVER_STATE = {
28956
+ menuVisible: false,
28957
+ addColumnVisible: false,
28958
+ addRowVisible: false,
28959
+ rowHandleIndex: null,
28960
+ columnHandleIndex: null
28961
+ };
28759
28962
  function resolveElement(target) {
28760
28963
  if (target instanceof Element) return target;
28761
28964
  if (target instanceof Node) return target.parentElement;
@@ -28881,6 +29084,12 @@ function buildLayout(editor, surface, cell) {
28881
29084
  };
28882
29085
  }
28883
29086
  function getSelectedCell(editor) {
29087
+ const browserSelection = window.getSelection();
29088
+ const anchorElement = resolveElement(browserSelection?.anchorNode ?? null);
29089
+ const anchorCell = anchorElement?.closest?.("th,td");
29090
+ if (anchorCell instanceof HTMLTableCellElement) {
29091
+ return anchorCell;
29092
+ }
28884
29093
  const domAtPos = editor.view.domAtPos(editor.state.selection.from);
28885
29094
  return getCellFromTarget(domAtPos.node);
28886
29095
  }
@@ -28907,14 +29116,15 @@ function collectChildren(node) {
28907
29116
  }
28908
29117
  function TableControls({ editor, containerRef }) {
28909
29118
  const t = useSmartTranslations("UEditor");
28910
- const [layout, setLayout] = React73.useState(null);
28911
- const [dragPreview, setDragPreview] = React73.useState(null);
28912
- const layoutRef = React73.useRef(null);
28913
- const dragStateRef = React73.useRef(null);
28914
- React73.useEffect(() => {
29119
+ const [layout, setLayout] = React74.useState(null);
29120
+ const [dragPreview, setDragPreview] = React74.useState(null);
29121
+ const [hoverState, setHoverState] = React74.useState(DEFAULT_HOVER_STATE);
29122
+ const layoutRef = React74.useRef(null);
29123
+ const dragStateRef = React74.useRef(null);
29124
+ React74.useEffect(() => {
28915
29125
  layoutRef.current = layout;
28916
29126
  }, [layout]);
28917
- const syncFromCell = React73.useCallback((cell) => {
29127
+ const syncFromCell = React74.useCallback((cell) => {
28918
29128
  const surface = containerRef.current;
28919
29129
  if (!surface || !cell) {
28920
29130
  setLayout(null);
@@ -28922,10 +29132,10 @@ function TableControls({ editor, containerRef }) {
28922
29132
  }
28923
29133
  setLayout(buildLayout(editor, surface, cell));
28924
29134
  }, [containerRef, editor]);
28925
- const syncFromSelection = React73.useCallback(() => {
29135
+ const syncFromSelection = React74.useCallback(() => {
28926
29136
  syncFromCell(getSelectedCell(editor));
28927
29137
  }, [editor, syncFromCell]);
28928
- const refreshCurrentLayout = React73.useCallback(() => {
29138
+ const refreshCurrentLayout = React74.useCallback(() => {
28929
29139
  setLayout((prev) => {
28930
29140
  if (!prev) return prev;
28931
29141
  const surface = containerRef.current;
@@ -28935,22 +29145,55 @@ function TableControls({ editor, containerRef }) {
28935
29145
  return cell ? buildLayout(editor, surface, cell) : null;
28936
29146
  });
28937
29147
  }, [containerRef, editor]);
28938
- const clearDrag = React73.useCallback(() => {
29148
+ const clearDrag = React74.useCallback(() => {
28939
29149
  dragStateRef.current = null;
28940
29150
  setDragPreview(null);
28941
29151
  document.body.style.cursor = "";
28942
29152
  }, []);
28943
- React73.useEffect(() => {
29153
+ const updateHoverState = React74.useCallback((event) => {
29154
+ const activeLayout = layoutRef.current;
29155
+ const surface = containerRef.current;
29156
+ if (!activeLayout || !surface || dragStateRef.current) {
29157
+ setHoverState(DEFAULT_HOVER_STATE);
29158
+ return;
29159
+ }
29160
+ const surfaceRect = surface.getBoundingClientRect();
29161
+ const relativeX = event.clientX - surfaceRect.left + surface.scrollLeft;
29162
+ const relativeY = event.clientY - surfaceRect.top + surface.scrollTop;
29163
+ const rowHandleIndex = activeLayout.rowHandles.find((rowHandle) => relativeX >= activeLayout.tableLeft - ROW_HANDLE_HOVER_WIDTH && relativeX <= activeLayout.tableLeft && Math.abs(relativeY - rowHandle.center) <= HANDLE_HOVER_RADIUS)?.index ?? null;
29164
+ const columnHandleIndex = activeLayout.columnHandles.find((columnHandle) => relativeY >= activeLayout.tableTop - COLUMN_HANDLE_HOVER_HEIGHT && relativeY <= activeLayout.tableTop && Math.abs(relativeX - columnHandle.center) <= HANDLE_HOVER_RADIUS)?.index ?? null;
29165
+ const menuVisible = relativeX >= activeLayout.tableLeft - MENU_HOVER_PADDING && relativeX <= activeLayout.tableLeft + 42 && relativeY >= activeLayout.tableTop - COLUMN_HANDLE_HOVER_HEIGHT && relativeY <= activeLayout.tableTop + MENU_HOVER_PADDING;
29166
+ const addColumnVisible = relativeX >= activeLayout.tableLeft + activeLayout.tableWidth && relativeX <= activeLayout.tableLeft + activeLayout.tableWidth + ADD_COLUMN_HOVER_WIDTH && relativeY >= activeLayout.tableTop && relativeY <= activeLayout.tableTop + activeLayout.tableHeight;
29167
+ const addRowVisible = relativeY >= activeLayout.tableTop + activeLayout.tableHeight && relativeY <= activeLayout.tableTop + activeLayout.tableHeight + ADD_ROW_HOVER_HEIGHT && relativeX >= activeLayout.tableLeft && relativeX <= activeLayout.tableLeft + activeLayout.tableWidth;
29168
+ setHoverState((prev) => {
29169
+ if (prev.menuVisible === menuVisible && prev.addColumnVisible === addColumnVisible && prev.addRowVisible === addRowVisible && prev.rowHandleIndex === rowHandleIndex && prev.columnHandleIndex === columnHandleIndex) {
29170
+ return prev;
29171
+ }
29172
+ return {
29173
+ menuVisible,
29174
+ addColumnVisible,
29175
+ addRowVisible,
29176
+ rowHandleIndex,
29177
+ columnHandleIndex
29178
+ };
29179
+ });
29180
+ }, [containerRef]);
29181
+ React74.useEffect(() => {
28944
29182
  const proseMirror = editor.view.dom;
28945
29183
  const surface = containerRef.current;
28946
29184
  if (!surface) return void 0;
28947
29185
  const handleMouseOver = (event) => {
28948
29186
  if (dragStateRef.current) return;
28949
- syncFromCell(getCellFromTarget(event.target));
29187
+ const cell = getCellFromTarget(event.target);
29188
+ if (!cell) return;
29189
+ syncFromCell(cell);
29190
+ };
29191
+ const handleSurfaceMouseMove = (event) => {
29192
+ updateHoverState(event);
28950
29193
  };
28951
29194
  const handleMouseLeave = () => {
28952
29195
  if (dragStateRef.current) return;
28953
- syncFromSelection();
29196
+ setHoverState(DEFAULT_HOVER_STATE);
28954
29197
  };
28955
29198
  const handleFocusIn = () => {
28956
29199
  if (dragStateRef.current) return;
@@ -28959,6 +29202,7 @@ function TableControls({ editor, containerRef }) {
28959
29202
  proseMirror.addEventListener("mouseover", handleMouseOver);
28960
29203
  proseMirror.addEventListener("mouseleave", handleMouseLeave);
28961
29204
  proseMirror.addEventListener("focusin", handleFocusIn);
29205
+ surface.addEventListener("mousemove", handleSurfaceMouseMove);
28962
29206
  surface.addEventListener("scroll", refreshCurrentLayout, { passive: true });
28963
29207
  window.addEventListener("resize", refreshCurrentLayout);
28964
29208
  editor.on("selectionUpdate", syncFromSelection);
@@ -28968,13 +29212,14 @@ function TableControls({ editor, containerRef }) {
28968
29212
  proseMirror.removeEventListener("mouseover", handleMouseOver);
28969
29213
  proseMirror.removeEventListener("mouseleave", handleMouseLeave);
28970
29214
  proseMirror.removeEventListener("focusin", handleFocusIn);
29215
+ surface.removeEventListener("mousemove", handleSurfaceMouseMove);
28971
29216
  surface.removeEventListener("scroll", refreshCurrentLayout);
28972
29217
  window.removeEventListener("resize", refreshCurrentLayout);
28973
29218
  editor.off("selectionUpdate", syncFromSelection);
28974
29219
  editor.off("update", refreshCurrentLayout);
28975
29220
  };
28976
- }, [clearDrag, containerRef, editor, refreshCurrentLayout, syncFromCell, syncFromSelection]);
28977
- const runAtCellPos = React73.useCallback((cellPos, command, options) => {
29221
+ }, [clearDrag, containerRef, editor, refreshCurrentLayout, syncFromCell, syncFromSelection, updateHoverState]);
29222
+ const runAtCellPos = React74.useCallback((cellPos, command, options) => {
28978
29223
  if (cellPos == null) return false;
28979
29224
  focusCell(editor, cellPos);
28980
29225
  const result = command(editor.chain().focus(null, { scrollIntoView: false })).run();
@@ -28983,17 +29228,17 @@ function TableControls({ editor, containerRef }) {
28983
29228
  }
28984
29229
  return result;
28985
29230
  }, [editor, syncFromSelection]);
28986
- const runAtActiveCell = React73.useCallback((command, options) => {
29231
+ const runAtActiveCell = React74.useCallback((command, options) => {
28987
29232
  return runAtCellPos(layoutRef.current?.cellPos ?? null, command, options);
28988
29233
  }, [runAtCellPos]);
28989
- const getCurrentCornerCellPos = React73.useCallback(() => {
29234
+ const getCurrentCornerCellPos = React74.useCallback(() => {
28990
29235
  const activePos = layoutRef.current?.cellPos ?? editor.state.selection.from;
28991
29236
  return getLastCellPosFromState(editor, activePos);
28992
29237
  }, [editor]);
28993
- const runAtCornerCell = React73.useCallback((command, options) => {
29238
+ const runAtCornerCell = React74.useCallback((command, options) => {
28994
29239
  return runAtCellPos(getCurrentCornerCellPos(), command, options);
28995
29240
  }, [getCurrentCornerCellPos, runAtCellPos]);
28996
- const replaceTableAtCellPos = React73.useCallback((cellPos, updateTable) => {
29241
+ const replaceTableAtCellPos = React74.useCallback((cellPos, updateTable) => {
28997
29242
  if (cellPos == null) return false;
28998
29243
  const tableInfo = findTableInfo(editor, cellPos);
28999
29244
  if (!tableInfo) return false;
@@ -29003,10 +29248,10 @@ function TableControls({ editor, containerRef }) {
29003
29248
  requestAnimationFrame(syncFromSelection);
29004
29249
  return true;
29005
29250
  }, [editor, syncFromSelection]);
29006
- const createEmptyCellNode = React73.useCallback((cellNode) => {
29251
+ const createEmptyCellNode = React74.useCallback((cellNode) => {
29007
29252
  return cellNode.type.createAndFill(cellNode.attrs) ?? cellNode;
29008
29253
  }, []);
29009
- const duplicateRowAt = React73.useCallback((rowIndex, cellPos) => {
29254
+ const duplicateRowAt = React74.useCallback((rowIndex, cellPos) => {
29010
29255
  return replaceTableAtCellPos(cellPos, (tableNode) => {
29011
29256
  const rows = collectChildren(tableNode);
29012
29257
  const rowNode = rows[rowIndex];
@@ -29015,7 +29260,7 @@ function TableControls({ editor, containerRef }) {
29015
29260
  return tableNode.type.create(tableNode.attrs, rows);
29016
29261
  });
29017
29262
  }, [replaceTableAtCellPos]);
29018
- const clearRowAt = React73.useCallback((rowIndex, cellPos) => {
29263
+ const clearRowAt = React74.useCallback((rowIndex, cellPos) => {
29019
29264
  return replaceTableAtCellPos(cellPos, (tableNode) => {
29020
29265
  const rows = collectChildren(tableNode);
29021
29266
  const rowNode = rows[rowIndex];
@@ -29025,7 +29270,7 @@ function TableControls({ editor, containerRef }) {
29025
29270
  return tableNode.type.create(tableNode.attrs, rows);
29026
29271
  });
29027
29272
  }, [createEmptyCellNode, replaceTableAtCellPos]);
29028
- const duplicateColumnAt = React73.useCallback((columnIndex, cellPos) => {
29273
+ const duplicateColumnAt = React74.useCallback((columnIndex, cellPos) => {
29029
29274
  return replaceTableAtCellPos(cellPos, (tableNode) => {
29030
29275
  const rows = collectChildren(tableNode).map((rowNode) => {
29031
29276
  const cells = collectChildren(rowNode);
@@ -29037,7 +29282,7 @@ function TableControls({ editor, containerRef }) {
29037
29282
  return tableNode.type.create(tableNode.attrs, rows);
29038
29283
  });
29039
29284
  }, [replaceTableAtCellPos]);
29040
- const clearColumnAt = React73.useCallback((columnIndex, cellPos) => {
29285
+ const clearColumnAt = React74.useCallback((columnIndex, cellPos) => {
29041
29286
  return replaceTableAtCellPos(cellPos, (tableNode) => {
29042
29287
  const rows = collectChildren(tableNode).map((rowNode) => {
29043
29288
  const cells = collectChildren(rowNode);
@@ -29049,7 +29294,7 @@ function TableControls({ editor, containerRef }) {
29049
29294
  return tableNode.type.create(tableNode.attrs, rows);
29050
29295
  });
29051
29296
  }, [createEmptyCellNode, replaceTableAtCellPos]);
29052
- const expandTableBy = React73.useCallback((rows, cols) => {
29297
+ const expandTableBy = React74.useCallback((rows, cols) => {
29053
29298
  let ok = true;
29054
29299
  for (let index = 0; index < rows; index += 1) {
29055
29300
  ok = runAtCornerCell((chain) => chain.addRowAfter(), { sync: false });
@@ -29063,7 +29308,8 @@ function TableControls({ editor, containerRef }) {
29063
29308
  return true;
29064
29309
  }, [runAtCornerCell, syncFromSelection]);
29065
29310
  const canExpandTable = Boolean(layout);
29066
- React73.useEffect(() => {
29311
+ const controlsVisible = dragPreview !== null;
29312
+ React74.useEffect(() => {
29067
29313
  const handleMouseMove = (event) => {
29068
29314
  const dragState = dragStateRef.current;
29069
29315
  const activeLayout = layoutRef.current;
@@ -29150,7 +29396,7 @@ function TableControls({ editor, containerRef }) {
29150
29396
  window.removeEventListener("blur", clearDrag);
29151
29397
  };
29152
29398
  }, [clearDrag, containerRef, editor, expandTableBy, syncFromSelection]);
29153
- const menuItems = React73.useMemo(() => {
29399
+ const menuItems = React74.useMemo(() => {
29154
29400
  if (!layout) return [];
29155
29401
  return [
29156
29402
  {
@@ -29203,7 +29449,7 @@ function TableControls({ editor, containerRef }) {
29203
29449
  }
29204
29450
  ];
29205
29451
  }, [layout, runAtActiveCell, t]);
29206
- const getRowHandleMenuItems = React73.useCallback((rowHandle) => [
29452
+ const getRowHandleMenuItems = React74.useCallback((rowHandle) => [
29207
29453
  {
29208
29454
  label: t("tableMenu.addRowBefore"),
29209
29455
  icon: ArrowUp2,
@@ -29231,7 +29477,7 @@ function TableControls({ editor, containerRef }) {
29231
29477
  destructive: true
29232
29478
  }
29233
29479
  ], [clearRowAt, duplicateRowAt, runAtCellPos, t]);
29234
- const getColumnHandleMenuItems = React73.useCallback((columnHandle) => [
29480
+ const getColumnHandleMenuItems = React74.useCallback((columnHandle) => [
29235
29481
  {
29236
29482
  label: t("tableMenu.addColumnBefore"),
29237
29483
  icon: ArrowLeft2,
@@ -29274,107 +29520,147 @@ function TableControls({ editor, containerRef }) {
29274
29520
  const expandPreviewHeight = dragPreview?.kind === "add-row" ? layout.tableHeight + dragPreview.previewRows * layout.avgRowHeight : layout.tableHeight;
29275
29521
  const dragStatusText = dragPreview?.kind === "row" ? `${t("tableMenu.dragRow")} ${dragPreview.originIndex + 1} -> ${dragPreview.targetIndex + 1}` : dragPreview?.kind === "column" ? `${t("tableMenu.dragColumn")} ${dragPreview.originIndex + 1} -> ${dragPreview.targetIndex + 1}` : dragPreview?.kind === "add-row" ? `+${dragPreview.previewRows}R` : dragPreview?.kind === "add-column" ? `+${dragPreview.previewCols}C` : null;
29276
29522
  return /* @__PURE__ */ jsxs71(Fragment27, { children: [
29277
- layout.rowHandles.map((rowHandle) => /* @__PURE__ */ jsx81("div", { className: "absolute z-30", style: { top: Math.max(8, rowHandle.center - 12), left: rowHandleLeft }, children: /* @__PURE__ */ jsx81(
29278
- DropdownMenu,
29279
- {
29280
- placement: "right",
29281
- items: getRowHandleMenuItems(rowHandle),
29282
- trigger: /* @__PURE__ */ jsx81(
29283
- "button",
29284
- {
29285
- type: "button",
29286
- "aria-label": `${t("tableMenu.dragRow")} ${rowHandle.index + 1}`,
29287
- title: `${t("tableMenu.dragRow")} ${rowHandle.index + 1}`,
29288
- onMouseDown: (event) => {
29289
- event.preventDefault();
29290
- event.stopPropagation();
29291
- dragStateRef.current = {
29292
- kind: "row",
29293
- originIndex: rowHandle.index,
29294
- targetIndex: rowHandle.index,
29295
- anchorPos: rowHandle.cellPos
29296
- };
29297
- setDragPreview({
29298
- kind: "row",
29299
- originIndex: rowHandle.index,
29300
- targetIndex: rowHandle.index,
29301
- targetStart: rowHandle.start,
29302
- targetSize: rowHandle.size
29303
- });
29304
- document.body.style.cursor = "grabbing";
29305
- },
29306
- className: cn(
29307
- "inline-flex h-6 w-6 items-center justify-center rounded-full",
29308
- "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29309
- "transition-colors hover:bg-accent hover:text-foreground"
29310
- ),
29311
- children: /* @__PURE__ */ jsx81(GripVertical3, { className: "h-3.5 w-3.5" })
29312
- }
29313
- )
29314
- }
29315
- ) }, `row-handle-${rowHandle.index}`)),
29316
- layout.columnHandles.map((columnHandle) => /* @__PURE__ */ jsx81("div", { className: "absolute z-30", style: { top: columnHandleTop, left: Math.max(8, columnHandle.center - 12) }, children: /* @__PURE__ */ jsx81(
29317
- DropdownMenu,
29318
- {
29319
- placement: "bottom-start",
29320
- items: getColumnHandleMenuItems(columnHandle),
29321
- trigger: /* @__PURE__ */ jsx81(
29322
- "button",
29323
- {
29324
- type: "button",
29325
- "aria-label": `${t("tableMenu.dragColumn")} ${columnHandle.index + 1}`,
29326
- title: `${t("tableMenu.dragColumn")} ${columnHandle.index + 1}`,
29327
- onMouseDown: (event) => {
29328
- event.preventDefault();
29329
- event.stopPropagation();
29330
- dragStateRef.current = {
29331
- kind: "column",
29332
- originIndex: columnHandle.index,
29333
- targetIndex: columnHandle.index,
29334
- anchorPos: columnHandle.cellPos
29335
- };
29336
- setDragPreview({
29337
- kind: "column",
29338
- originIndex: columnHandle.index,
29339
- targetIndex: columnHandle.index,
29340
- targetStart: columnHandle.start,
29341
- targetSize: columnHandle.size
29342
- });
29343
- document.body.style.cursor = "grabbing";
29344
- },
29345
- className: cn(
29346
- "inline-flex h-6 w-6 items-center justify-center rounded-full",
29347
- "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29348
- "transition-colors hover:bg-accent hover:text-foreground"
29349
- ),
29350
- children: /* @__PURE__ */ jsx81(GripHorizontal, { className: "h-3.5 w-3.5" })
29351
- }
29352
- )
29353
- }
29354
- ) }, `column-handle-${columnHandle.index}`)),
29355
- /* @__PURE__ */ jsx81("div", { className: "pointer-events-none absolute z-30", style: { top: menuTop, left: menuLeft }, children: /* @__PURE__ */ jsx81(
29356
- DropdownMenu,
29523
+ layout.rowHandles.map((rowHandle) => {
29524
+ const visible = controlsVisible || hoverState.rowHandleIndex === rowHandle.index;
29525
+ if (!visible) return null;
29526
+ return /* @__PURE__ */ jsx81(
29527
+ "div",
29528
+ {
29529
+ className: "absolute z-30",
29530
+ style: {
29531
+ top: Math.max(8, rowHandle.center - 12),
29532
+ left: rowHandleLeft
29533
+ },
29534
+ children: /* @__PURE__ */ jsx81(
29535
+ DropdownMenu,
29536
+ {
29537
+ placement: "right",
29538
+ items: getRowHandleMenuItems(rowHandle),
29539
+ trigger: /* @__PURE__ */ jsx81(
29540
+ "button",
29541
+ {
29542
+ type: "button",
29543
+ "aria-label": `${t("tableMenu.dragRow")} ${rowHandle.index + 1}`,
29544
+ title: `${t("tableMenu.dragRow")} ${rowHandle.index + 1}`,
29545
+ onMouseDown: (event) => {
29546
+ event.preventDefault();
29547
+ event.stopPropagation();
29548
+ dragStateRef.current = {
29549
+ kind: "row",
29550
+ originIndex: rowHandle.index,
29551
+ targetIndex: rowHandle.index,
29552
+ anchorPos: rowHandle.cellPos
29553
+ };
29554
+ setDragPreview({
29555
+ kind: "row",
29556
+ originIndex: rowHandle.index,
29557
+ targetIndex: rowHandle.index,
29558
+ targetStart: rowHandle.start,
29559
+ targetSize: rowHandle.size
29560
+ });
29561
+ document.body.style.cursor = "grabbing";
29562
+ },
29563
+ className: cn(
29564
+ "inline-flex h-6 w-6 items-center justify-center rounded-full",
29565
+ "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29566
+ "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground"
29567
+ ),
29568
+ children: /* @__PURE__ */ jsx81(GripVertical3, { className: "h-3.5 w-3.5" })
29569
+ }
29570
+ )
29571
+ }
29572
+ )
29573
+ },
29574
+ `row-handle-${rowHandle.index}`
29575
+ );
29576
+ }),
29577
+ layout.columnHandles.map((columnHandle) => {
29578
+ const visible = controlsVisible || hoverState.columnHandleIndex === columnHandle.index;
29579
+ if (!visible) return null;
29580
+ return /* @__PURE__ */ jsx81(
29581
+ "div",
29582
+ {
29583
+ className: "absolute z-30",
29584
+ style: {
29585
+ top: columnHandleTop,
29586
+ left: Math.max(8, columnHandle.center - 12)
29587
+ },
29588
+ children: /* @__PURE__ */ jsx81(
29589
+ DropdownMenu,
29590
+ {
29591
+ placement: "bottom-start",
29592
+ items: getColumnHandleMenuItems(columnHandle),
29593
+ trigger: /* @__PURE__ */ jsx81(
29594
+ "button",
29595
+ {
29596
+ type: "button",
29597
+ "aria-label": `${t("tableMenu.dragColumn")} ${columnHandle.index + 1}`,
29598
+ title: `${t("tableMenu.dragColumn")} ${columnHandle.index + 1}`,
29599
+ onMouseDown: (event) => {
29600
+ event.preventDefault();
29601
+ event.stopPropagation();
29602
+ dragStateRef.current = {
29603
+ kind: "column",
29604
+ originIndex: columnHandle.index,
29605
+ targetIndex: columnHandle.index,
29606
+ anchorPos: columnHandle.cellPos
29607
+ };
29608
+ setDragPreview({
29609
+ kind: "column",
29610
+ originIndex: columnHandle.index,
29611
+ targetIndex: columnHandle.index,
29612
+ targetStart: columnHandle.start,
29613
+ targetSize: columnHandle.size
29614
+ });
29615
+ document.body.style.cursor = "grabbing";
29616
+ },
29617
+ className: cn(
29618
+ "inline-flex h-6 w-6 items-center justify-center rounded-full",
29619
+ "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29620
+ "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground"
29621
+ ),
29622
+ children: /* @__PURE__ */ jsx81(GripHorizontal, { className: "h-3.5 w-3.5" })
29623
+ }
29624
+ )
29625
+ }
29626
+ )
29627
+ },
29628
+ `column-handle-${columnHandle.index}`
29629
+ );
29630
+ }),
29631
+ (controlsVisible || hoverState.menuVisible) && /* @__PURE__ */ jsx81(
29632
+ "div",
29357
29633
  {
29358
- placement: "bottom-start",
29359
- items: menuItems,
29360
- trigger: /* @__PURE__ */ jsx81(
29361
- "button",
29634
+ className: "absolute z-30",
29635
+ style: {
29636
+ top: menuTop,
29637
+ left: menuLeft
29638
+ },
29639
+ children: /* @__PURE__ */ jsx81(
29640
+ DropdownMenu,
29362
29641
  {
29363
- type: "button",
29364
- "aria-label": t("tableMenu.openControls"),
29365
- title: t("tableMenu.openControls"),
29366
- onMouseDown: (event) => event.preventDefault(),
29367
- className: cn(
29368
- "pointer-events-auto inline-flex h-7 w-7 items-center justify-center rounded-full",
29369
- "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29370
- "transition-colors hover:bg-accent hover:text-foreground"
29371
- ),
29372
- children: /* @__PURE__ */ jsx81(MoreHorizontal2, { className: "h-4 w-4" })
29642
+ placement: "bottom-start",
29643
+ items: menuItems,
29644
+ trigger: /* @__PURE__ */ jsx81(
29645
+ "button",
29646
+ {
29647
+ type: "button",
29648
+ "aria-label": t("tableMenu.openControls"),
29649
+ title: t("tableMenu.openControls"),
29650
+ onMouseDown: (event) => event.preventDefault(),
29651
+ className: cn(
29652
+ "pointer-events-auto inline-flex h-7 w-7 items-center justify-center rounded-full",
29653
+ "border border-border/70 bg-background/95 text-muted-foreground shadow-sm backdrop-blur",
29654
+ "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground"
29655
+ ),
29656
+ children: /* @__PURE__ */ jsx81(MoreHorizontal2, { className: "h-4 w-4" })
29657
+ }
29658
+ )
29373
29659
  }
29374
29660
  )
29375
29661
  }
29376
- ) }),
29377
- /* @__PURE__ */ jsx81(
29662
+ ),
29663
+ (controlsVisible || hoverState.addColumnVisible) && /* @__PURE__ */ jsx81(
29378
29664
  "button",
29379
29665
  {
29380
29666
  type: "button",
@@ -29392,13 +29678,18 @@ function TableControls({ editor, containerRef }) {
29392
29678
  className: cn(
29393
29679
  "absolute z-30 inline-flex items-center justify-center rounded-md",
29394
29680
  "border border-border/70 bg-muted/40 text-muted-foreground shadow-sm backdrop-blur",
29395
- "transition-colors hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
29681
+ "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
29396
29682
  ),
29397
- style: { top: columnRailTop, left: columnRailLeft, width: 18, height: layout.tableHeight },
29683
+ style: {
29684
+ top: columnRailTop,
29685
+ left: columnRailLeft,
29686
+ width: 18,
29687
+ height: layout.tableHeight
29688
+ },
29398
29689
  children: /* @__PURE__ */ jsx81("span", { className: "text-sm font-medium leading-none", children: "+" })
29399
29690
  }
29400
29691
  ),
29401
- /* @__PURE__ */ jsx81(
29692
+ (controlsVisible || hoverState.addRowVisible) && /* @__PURE__ */ jsx81(
29402
29693
  "button",
29403
29694
  {
29404
29695
  type: "button",
@@ -29416,9 +29707,14 @@ function TableControls({ editor, containerRef }) {
29416
29707
  className: cn(
29417
29708
  "absolute z-30 inline-flex items-center justify-center rounded-md",
29418
29709
  "border border-border/70 bg-muted/40 text-muted-foreground shadow-sm backdrop-blur",
29419
- "transition-colors hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
29710
+ "transition-[opacity,transform,colors] duration-150 hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed"
29420
29711
  ),
29421
- style: { top: rowRailTop, left: rowRailLeft, width: layout.tableWidth, height: 16 },
29712
+ style: {
29713
+ top: rowRailTop,
29714
+ left: rowRailLeft,
29715
+ width: layout.tableWidth,
29716
+ height: 16
29717
+ },
29422
29718
  children: /* @__PURE__ */ jsx81("span", { className: "text-sm font-medium leading-none", children: "+" })
29423
29719
  }
29424
29720
  ),
@@ -29553,6 +29849,19 @@ function resolveEventElement(target) {
29553
29849
  if (target instanceof Node) return target.parentElement;
29554
29850
  return null;
29555
29851
  }
29852
+ function getSelectionTableCell(view) {
29853
+ const browserSelection = window.getSelection();
29854
+ const anchorElement = resolveEventElement(browserSelection?.anchorNode ?? null);
29855
+ const anchorCell = anchorElement?.closest?.("th,td");
29856
+ if (anchorCell instanceof HTMLElement) {
29857
+ return anchorCell;
29858
+ }
29859
+ const { from } = view.state.selection;
29860
+ const domAtPos = view.domAtPos(from);
29861
+ const element = resolveEventElement(domAtPos.node);
29862
+ const cell = element?.closest?.("th,td");
29863
+ return cell instanceof HTMLElement ? cell : null;
29864
+ }
29556
29865
  function isRowResizeHotspot(cell, clientX, clientY) {
29557
29866
  const rect = cell.getBoundingClientRect();
29558
29867
  const nearBottom = rect.bottom - clientY <= TABLE_RESIZE_HIT_ZONE;
@@ -29579,7 +29888,17 @@ function getRelativeBoundaryMetrics(surface, table, row, cell) {
29579
29888
  columnRight: cellRect.right - surfaceRect.left + surface.scrollLeft
29580
29889
  };
29581
29890
  }
29582
- var UEditor = React74.forwardRef(({
29891
+ function getRelativeCellMetrics(surface, cell) {
29892
+ const surfaceRect = surface.getBoundingClientRect();
29893
+ const cellRect = cell.getBoundingClientRect();
29894
+ return {
29895
+ left: cellRect.left - surfaceRect.left + surface.scrollLeft,
29896
+ top: cellRect.top - surfaceRect.top + surface.scrollTop,
29897
+ width: cellRect.width,
29898
+ height: cellRect.height
29899
+ };
29900
+ }
29901
+ var UEditor = React75.forwardRef(({
29583
29902
  content = "",
29584
29903
  onChange,
29585
29904
  onHtmlChange,
@@ -29606,33 +29925,66 @@ var UEditor = React74.forwardRef(({
29606
29925
  const editorContentRef = useRef32(null);
29607
29926
  const tableColumnGuideRef = useRef32(null);
29608
29927
  const tableRowGuideRef = useRef32(null);
29928
+ const activeTableCellHighlightRef = useRef32(null);
29929
+ const hoveredTableCellRef = useRef32(null);
29930
+ const activeTableCellRef = useRef32(null);
29609
29931
  const rowResizeStateRef = useRef32(null);
29610
- const setEditorResizeCursor = React74.useCallback((cursor) => {
29932
+ const setEditorResizeCursor = React75.useCallback((cursor) => {
29611
29933
  const proseMirror = editorContentRef.current?.querySelector(".ProseMirror");
29612
29934
  if (proseMirror) {
29613
29935
  proseMirror.style.cursor = cursor;
29614
29936
  }
29615
29937
  }, []);
29616
- const hideColumnGuide = React74.useCallback(() => {
29938
+ const hideColumnGuide = React75.useCallback(() => {
29617
29939
  editorContentRef.current?.classList.remove("resize-cursor");
29618
29940
  const guide = tableColumnGuideRef.current;
29619
29941
  if (guide) {
29620
29942
  guide.style.opacity = "0";
29621
29943
  }
29622
29944
  }, []);
29623
- const hideRowGuide = React74.useCallback(() => {
29945
+ const hideRowGuide = React75.useCallback(() => {
29624
29946
  editorContentRef.current?.classList.remove("resize-row-cursor");
29625
29947
  const guide = tableRowGuideRef.current;
29626
29948
  if (guide) {
29627
29949
  guide.style.opacity = "0";
29628
29950
  }
29629
29951
  }, []);
29630
- const clearAllTableResizeHover = React74.useCallback(() => {
29952
+ const clearAllTableResizeHover = React75.useCallback(() => {
29631
29953
  setEditorResizeCursor("");
29632
29954
  hideColumnGuide();
29633
29955
  hideRowGuide();
29634
29956
  }, [hideColumnGuide, hideRowGuide, setEditorResizeCursor]);
29635
- const showColumnGuide = React74.useCallback((table, row, cell) => {
29957
+ const updateActiveCellHighlight = React75.useCallback((cell) => {
29958
+ const surface = editorContentRef.current;
29959
+ const highlight = activeTableCellHighlightRef.current;
29960
+ if (!highlight) return;
29961
+ if (!surface || !cell) {
29962
+ highlight.style.display = "none";
29963
+ return;
29964
+ }
29965
+ const metrics = getRelativeCellMetrics(surface, cell);
29966
+ highlight.style.display = "block";
29967
+ highlight.style.left = `${metrics.left}px`;
29968
+ highlight.style.top = `${metrics.top}px`;
29969
+ highlight.style.width = `${metrics.width}px`;
29970
+ highlight.style.height = `${metrics.height}px`;
29971
+ }, []);
29972
+ const setActiveTableCell = React75.useCallback((cell) => {
29973
+ if (activeTableCellRef.current === cell) return;
29974
+ activeTableCellRef.current = cell;
29975
+ updateActiveCellHighlight(activeTableCellRef.current);
29976
+ }, [updateActiveCellHighlight]);
29977
+ const clearActiveTableCell = React75.useCallback(() => {
29978
+ activeTableCellRef.current = null;
29979
+ updateActiveCellHighlight(null);
29980
+ }, [updateActiveCellHighlight]);
29981
+ const setHoveredTableCell = React75.useCallback((cell) => {
29982
+ hoveredTableCellRef.current = cell;
29983
+ }, []);
29984
+ const clearHoveredTableCell = React75.useCallback(() => {
29985
+ hoveredTableCellRef.current = null;
29986
+ }, []);
29987
+ const showColumnGuide = React75.useCallback((table, row, cell) => {
29636
29988
  const surface = editorContentRef.current;
29637
29989
  const guide = tableColumnGuideRef.current;
29638
29990
  if (!surface || !guide) return;
@@ -29645,7 +29997,7 @@ var UEditor = React74.forwardRef(({
29645
29997
  surface.classList.add("resize-cursor");
29646
29998
  setEditorResizeCursor("col-resize");
29647
29999
  }, [setEditorResizeCursor]);
29648
- const showRowGuide = React74.useCallback((table, row, cell) => {
30000
+ const showRowGuide = React75.useCallback((table, row, cell) => {
29649
30001
  const surface = editorContentRef.current;
29650
30002
  const guide = tableRowGuideRef.current;
29651
30003
  if (!surface || !guide) return;
@@ -29813,6 +30165,10 @@ var UEditor = React74.forwardRef(({
29813
30165
  onJsonChange?.(editor2.getJSON());
29814
30166
  }
29815
30167
  });
30168
+ const syncActiveTableCellFromSelection = React75.useCallback(() => {
30169
+ if (!editor) return;
30170
+ setActiveTableCell(getSelectionTableCell(editor.view));
30171
+ }, [editor, setActiveTableCell]);
29816
30172
  useImperativeHandle3(
29817
30173
  ref,
29818
30174
  () => ({
@@ -29845,9 +30201,27 @@ var UEditor = React74.forwardRef(({
29845
30201
  useEffect35(() => {
29846
30202
  if (!editor) return void 0;
29847
30203
  const proseMirror = editor.view.dom;
30204
+ const surface = editorContentRef.current;
30205
+ let selectionSyncTimeoutId = 0;
30206
+ const scheduleActiveCellSync = (fallbackCell = null) => {
30207
+ requestAnimationFrame(() => {
30208
+ setActiveTableCell(getSelectionTableCell(editor.view) ?? fallbackCell);
30209
+ });
30210
+ window.clearTimeout(selectionSyncTimeoutId);
30211
+ selectionSyncTimeoutId = window.setTimeout(() => {
30212
+ setActiveTableCell(getSelectionTableCell(editor.view) ?? fallbackCell);
30213
+ }, 0);
30214
+ };
30215
+ const handleSelectionChange = () => {
30216
+ scheduleActiveCellSync();
30217
+ };
30218
+ const handleActiveCellLayoutChange = () => {
30219
+ updateActiveCellHighlight(activeTableCellRef.current);
30220
+ };
29848
30221
  const handleEditorMouseMove = (event) => {
29849
30222
  const activeRowResize = rowResizeStateRef.current;
29850
30223
  if (activeRowResize) {
30224
+ setHoveredTableCell(activeRowResize.cellElement);
29851
30225
  showRowGuide(activeRowResize.tableElement, activeRowResize.rowElement, activeRowResize.cellElement);
29852
30226
  return;
29853
30227
  }
@@ -29858,12 +30232,15 @@ var UEditor = React74.forwardRef(({
29858
30232
  }
29859
30233
  const cell = target.closest("th,td");
29860
30234
  if (!(cell instanceof HTMLElement)) {
30235
+ clearHoveredTableCell();
29861
30236
  clearAllTableResizeHover();
29862
30237
  return;
29863
30238
  }
30239
+ setHoveredTableCell(cell);
29864
30240
  const row = cell.closest("tr");
29865
30241
  const table = cell.closest("table");
29866
30242
  if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) {
30243
+ clearHoveredTableCell();
29867
30244
  clearAllTableResizeHover();
29868
30245
  return;
29869
30246
  }
@@ -29882,6 +30259,7 @@ var UEditor = React74.forwardRef(({
29882
30259
  clearAllTableResizeHover();
29883
30260
  };
29884
30261
  const handleEditorMouseLeave = () => {
30262
+ clearHoveredTableCell();
29885
30263
  if (!rowResizeStateRef.current) {
29886
30264
  clearAllTableResizeHover();
29887
30265
  }
@@ -29889,15 +30267,24 @@ var UEditor = React74.forwardRef(({
29889
30267
  const handleEditorMouseDown = (event) => {
29890
30268
  if (event.button !== 0) return;
29891
30269
  const target = resolveEventElement(event.target);
29892
- if (!(target instanceof Element)) return;
30270
+ if (!(target instanceof Element)) {
30271
+ clearActiveTableCell();
30272
+ return;
30273
+ }
29893
30274
  const cell = target.closest("th,td");
29894
- if (!(cell instanceof HTMLTableCellElement)) return;
30275
+ if (!(cell instanceof HTMLTableCellElement)) {
30276
+ clearActiveTableCell();
30277
+ return;
30278
+ }
30279
+ setActiveTableCell(cell);
30280
+ scheduleActiveCellSync(cell);
29895
30281
  const row = cell.closest("tr");
29896
30282
  const table = cell.closest("table");
29897
30283
  if (!(row instanceof HTMLTableRowElement) || !(table instanceof HTMLTableElement)) return;
29898
30284
  if (!isRowResizeHotspot(cell, event.clientX, event.clientY)) {
29899
30285
  return;
29900
30286
  }
30287
+ setHoveredTableCell(cell);
29901
30288
  const rowInfo = findTableRowNodeInfo(editor.view, row);
29902
30289
  if (!rowInfo) return;
29903
30290
  rowResizeStateRef.current = {
@@ -29958,6 +30345,7 @@ var UEditor = React74.forwardRef(({
29958
30345
  clearPreviewRowHeight(state.rowElement);
29959
30346
  rowResizeStateRef.current = null;
29960
30347
  document.body.style.cursor = "";
30348
+ clearHoveredTableCell();
29961
30349
  clearAllTableResizeHover();
29962
30350
  };
29963
30351
  const handleWindowBlur = () => {
@@ -29966,30 +30354,53 @@ var UEditor = React74.forwardRef(({
29966
30354
  clearPreviewRowHeight(state.rowElement);
29967
30355
  rowResizeStateRef.current = null;
29968
30356
  document.body.style.cursor = "";
30357
+ clearHoveredTableCell();
29969
30358
  clearAllTableResizeHover();
29970
30359
  };
29971
30360
  proseMirror.addEventListener("mousemove", handleEditorMouseMove);
29972
30361
  proseMirror.addEventListener("mouseleave", handleEditorMouseLeave);
29973
30362
  proseMirror.addEventListener("mousedown", handleEditorMouseDown);
30363
+ proseMirror.addEventListener("click", handleSelectionChange);
30364
+ proseMirror.addEventListener("mouseup", handleSelectionChange);
30365
+ proseMirror.addEventListener("keyup", handleSelectionChange);
30366
+ proseMirror.addEventListener("focusin", handleSelectionChange);
30367
+ document.addEventListener("selectionchange", handleSelectionChange);
30368
+ surface?.addEventListener("scroll", handleActiveCellLayoutChange, { passive: true });
30369
+ window.addEventListener("resize", handleActiveCellLayoutChange);
29974
30370
  window.addEventListener("mousemove", handlePointerMove);
29975
30371
  document.addEventListener("pointermove", handlePointerMove);
29976
30372
  window.addEventListener("mouseup", handlePointerUp);
29977
30373
  document.addEventListener("pointerup", handlePointerUp);
29978
30374
  window.addEventListener("blur", handleWindowBlur);
30375
+ editor.on("selectionUpdate", syncActiveTableCellFromSelection);
30376
+ editor.on("focus", syncActiveTableCellFromSelection);
30377
+ syncActiveTableCellFromSelection();
29979
30378
  return () => {
29980
30379
  proseMirror.removeEventListener("mousemove", handleEditorMouseMove);
29981
30380
  proseMirror.removeEventListener("mouseleave", handleEditorMouseLeave);
29982
30381
  proseMirror.removeEventListener("mousedown", handleEditorMouseDown);
30382
+ proseMirror.removeEventListener("click", handleSelectionChange);
30383
+ proseMirror.removeEventListener("mouseup", handleSelectionChange);
30384
+ proseMirror.removeEventListener("keyup", handleSelectionChange);
30385
+ proseMirror.removeEventListener("focusin", handleSelectionChange);
30386
+ document.removeEventListener("selectionchange", handleSelectionChange);
30387
+ surface?.removeEventListener("scroll", handleActiveCellLayoutChange);
30388
+ window.removeEventListener("resize", handleActiveCellLayoutChange);
29983
30389
  window.removeEventListener("mousemove", handlePointerMove);
29984
30390
  document.removeEventListener("pointermove", handlePointerMove);
29985
30391
  window.removeEventListener("mouseup", handlePointerUp);
29986
30392
  document.removeEventListener("pointerup", handlePointerUp);
29987
30393
  window.removeEventListener("blur", handleWindowBlur);
30394
+ editor.off("selectionUpdate", syncActiveTableCellFromSelection);
30395
+ editor.off("focus", syncActiveTableCellFromSelection);
30396
+ window.clearTimeout(selectionSyncTimeoutId);
29988
30397
  document.body.style.cursor = "";
30398
+ clearActiveTableCell();
30399
+ clearHoveredTableCell();
29989
30400
  clearAllTableResizeHover();
29990
30401
  rowResizeStateRef.current = null;
29991
30402
  };
29992
- }, [clearAllTableResizeHover, editor, hideColumnGuide, hideRowGuide, showColumnGuide, showRowGuide]);
30403
+ }, [clearActiveTableCell, clearAllTableResizeHover, clearHoveredTableCell, editor, hideColumnGuide, hideRowGuide, setHoveredTableCell, showColumnGuide, showRowGuide, syncActiveTableCellFromSelection, updateActiveCellHighlight]);
29993
30404
  if (!editor) {
29994
30405
  return /* @__PURE__ */ jsx82(
29995
30406
  "div",
@@ -30041,6 +30452,14 @@ var UEditor = React74.forwardRef(({
30041
30452
  className: "pointer-events-none absolute z-20 bg-primary opacity-0 transition-opacity duration-100"
30042
30453
  }
30043
30454
  ),
30455
+ /* @__PURE__ */ jsx82(
30456
+ "span",
30457
+ {
30458
+ ref: activeTableCellHighlightRef,
30459
+ "aria-hidden": "true",
30460
+ className: "pointer-events-none hidden absolute z-20 rounded-[2px] border-2 border-[#2383e2] bg-[#2383e2]/[0.06] transition-[left,top,width,height] duration-100"
30461
+ }
30462
+ ),
30044
30463
  editable && /* @__PURE__ */ jsx82(TableControls, { editor, containerRef: editorContentRef }),
30045
30464
  /* @__PURE__ */ jsx82(
30046
30465
  EditorContent,