@zvndev/yable-react 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -631,8 +631,10 @@ interface TableBodyProps<TData extends RowData> {
631
631
  table: Table$1<TData>;
632
632
  clickableRows?: boolean;
633
633
  colgroup?: React__default.ReactNode;
634
+ /** Stable mousedown handler for the fill handle, lifted from `useFillHandle`. */
635
+ onFillHandleMouseDown?: (rowIndex: number, columnIndex: number, e: React__default.MouseEvent) => void;
634
636
  }
635
- declare function TableBody<TData extends RowData>({ table, clickableRows, colgroup, }: TableBodyProps<TData>): react_jsx_runtime.JSX.Element;
637
+ declare function TableBody<TData extends RowData>({ table, clickableRows, colgroup, onFillHandleMouseDown, }: TableBodyProps<TData>): react_jsx_runtime.JSX.Element;
636
638
 
637
639
  interface TableCellProps<TData extends RowData> {
638
640
  cell: Cell<TData, unknown>;
@@ -641,8 +643,11 @@ interface TableCellProps<TData extends RowData> {
641
643
  columnIndex: number;
642
644
  isFocused: boolean;
643
645
  isTabStop: boolean;
646
+ /** Mousedown handler for the fill handle; when present and the table has
647
+ * `enableFillHandle`, the focused cell renders a drag-to-fill corner. */
648
+ onFillHandleMouseDown?: (rowIndex: number, columnIndex: number, e: React__default.MouseEvent) => void;
644
649
  }
645
- declare function TableCell<TData extends RowData>({ cell, table, rowIndex, columnIndex, isFocused, isTabStop, }: TableCellProps<TData>): react_jsx_runtime.JSX.Element;
650
+ declare function TableCell<TData extends RowData>({ cell, table, rowIndex, columnIndex, isFocused, isTabStop, onFillHandleMouseDown, }: TableCellProps<TData>): react_jsx_runtime.JSX.Element;
646
651
 
647
652
  interface TableFooterProps<TData extends RowData> {
648
653
  table: Table$1<TData>;
package/dist/index.d.ts CHANGED
@@ -631,8 +631,10 @@ interface TableBodyProps<TData extends RowData> {
631
631
  table: Table$1<TData>;
632
632
  clickableRows?: boolean;
633
633
  colgroup?: React__default.ReactNode;
634
+ /** Stable mousedown handler for the fill handle, lifted from `useFillHandle`. */
635
+ onFillHandleMouseDown?: (rowIndex: number, columnIndex: number, e: React__default.MouseEvent) => void;
634
636
  }
635
- declare function TableBody<TData extends RowData>({ table, clickableRows, colgroup, }: TableBodyProps<TData>): react_jsx_runtime.JSX.Element;
637
+ declare function TableBody<TData extends RowData>({ table, clickableRows, colgroup, onFillHandleMouseDown, }: TableBodyProps<TData>): react_jsx_runtime.JSX.Element;
636
638
 
637
639
  interface TableCellProps<TData extends RowData> {
638
640
  cell: Cell<TData, unknown>;
@@ -641,8 +643,11 @@ interface TableCellProps<TData extends RowData> {
641
643
  columnIndex: number;
642
644
  isFocused: boolean;
643
645
  isTabStop: boolean;
646
+ /** Mousedown handler for the fill handle; when present and the table has
647
+ * `enableFillHandle`, the focused cell renders a drag-to-fill corner. */
648
+ onFillHandleMouseDown?: (rowIndex: number, columnIndex: number, e: React__default.MouseEvent) => void;
644
649
  }
645
- declare function TableCell<TData extends RowData>({ cell, table, rowIndex, columnIndex, isFocused, isTabStop, }: TableCellProps<TData>): react_jsx_runtime.JSX.Element;
650
+ declare function TableCell<TData extends RowData>({ cell, table, rowIndex, columnIndex, isFocused, isTabStop, onFillHandleMouseDown, }: TableCellProps<TData>): react_jsx_runtime.JSX.Element;
646
651
 
647
652
  interface TableFooterProps<TData extends RowData> {
648
653
  table: Table$1<TData>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { canCellEnterEditMode, functionalUpdate, createTable, getDefaultLocale, getFirstKeyboardCell, getResolvedFocusedCell, detectCellChanges } from '@zvndev/yable-core';
2
2
  export { CommitError, FormulaEngine, FormulaError, PivotEngine, UndoStack, aggregationFns, createColumnHelper, createLocale, createUndoRedoIntegration, en, filterFns, formulaFunctions, functionalUpdate, generatePivotColumnDefs, getDefaultLocale, getInitialPivotState, getPivotRowModel, resetLocale, setDefaultLocale, sortingFns } from '@zvndev/yable-core';
3
- import React3, { createContext, useCallback, useMemo, useContext, useState, useRef, useEffect } from 'react';
3
+ import React4, { createContext, useCallback, useMemo, useContext, useState, useRef, useEffect } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { createPortal } from 'react-dom';
6
6
  import { getInitialRowDragState, moveRow } from '@zvndev/yable-core/features/rowDragging';
@@ -2188,13 +2188,39 @@ function CellStatusBadge(props) {
2188
2188
  }
2189
2189
  );
2190
2190
  }
2191
+ function FillHandle({
2192
+ rowIndex,
2193
+ columnIndex,
2194
+ onMouseDown
2195
+ }) {
2196
+ const handleMouseDown = useCallback(
2197
+ (e) => {
2198
+ e.preventDefault();
2199
+ e.stopPropagation();
2200
+ onMouseDown(rowIndex, columnIndex, e);
2201
+ },
2202
+ [rowIndex, columnIndex, onMouseDown]
2203
+ );
2204
+ return /* @__PURE__ */ jsx(
2205
+ "div",
2206
+ {
2207
+ className: "yable-fill-handle",
2208
+ onMouseDown: handleMouseDown,
2209
+ role: "presentation",
2210
+ "aria-hidden": "true",
2211
+ title: "Drag to fill",
2212
+ children: /* @__PURE__ */ jsx("div", { className: "yable-fill-handle-dot" })
2213
+ }
2214
+ );
2215
+ }
2191
2216
  function TableCell({
2192
2217
  cell,
2193
2218
  table,
2194
2219
  rowIndex,
2195
2220
  columnIndex,
2196
2221
  isFocused,
2197
- isTabStop
2222
+ isTabStop,
2223
+ onFillHandleMouseDown
2198
2224
  }) {
2199
2225
  const column = cell.column;
2200
2226
  const isEditing = cell.getIsEditing();
@@ -2310,6 +2336,7 @@ function TableCell({
2310
2336
  const cellStyleDef = column.columnDef.cellStyle;
2311
2337
  const userStyle = typeof cellStyleDef === "function" ? cellStyleDef(cell.getContext()) : cellStyleDef;
2312
2338
  const mergedStyle = userStyle ? { ...style, ...userStyle } : style;
2339
+ const showFillHandle = isFocused && Boolean(table.options.enableFillHandle) && onFillHandleMouseDown != null;
2313
2340
  const classNames = [
2314
2341
  "yable-td",
2315
2342
  isFocused && "yable-cell--focused",
@@ -2363,6 +2390,14 @@ function TableCell({
2363
2390
  onRetry: () => void table.retryCommit(cell.row.id, column.id),
2364
2391
  onDismiss: () => table.dismissCommit(cell.row.id, column.id)
2365
2392
  }
2393
+ ),
2394
+ showFillHandle && onFillHandleMouseDown && /* @__PURE__ */ jsx(
2395
+ FillHandle,
2396
+ {
2397
+ rowIndex,
2398
+ columnIndex,
2399
+ onMouseDown: onFillHandleMouseDown
2400
+ }
2366
2401
  )
2367
2402
  ]
2368
2403
  }
@@ -2375,7 +2410,7 @@ function isInteractiveClickTarget(element) {
2375
2410
  );
2376
2411
  return interactive !== null;
2377
2412
  }
2378
- var ErrorBoundary = class extends React3.Component {
2413
+ var ErrorBoundary = class extends React4.Component {
2379
2414
  constructor(props) {
2380
2415
  super(props);
2381
2416
  this.state = { hasError: false, error: null };
@@ -2421,7 +2456,7 @@ var ErrorBoundary = class extends React3.Component {
2421
2456
  return this.props.children;
2422
2457
  }
2423
2458
  };
2424
- var CellErrorBoundary = class extends React3.Component {
2459
+ var CellErrorBoundary = class extends React4.Component {
2425
2460
  constructor(props) {
2426
2461
  super(props);
2427
2462
  this.state = { hasError: false, error: null };
@@ -2494,7 +2529,8 @@ function MasterDetail({
2494
2529
  function TableBody({
2495
2530
  table,
2496
2531
  clickableRows,
2497
- colgroup
2532
+ colgroup,
2533
+ onFillHandleMouseDown
2498
2534
  }) {
2499
2535
  const rows = table.getRowModel().rows;
2500
2536
  const visibleColumns = table.getVisibleLeafColumns();
@@ -2563,7 +2599,8 @@ function TableBody({
2563
2599
  cellSelectionKey,
2564
2600
  pendingValuesKey: getPendingValuesKey(pendingValues[row.id]),
2565
2601
  clickable: clickableRows,
2566
- pinnedPosition
2602
+ pinnedPosition,
2603
+ onFillHandleMouseDown
2567
2604
  },
2568
2605
  row.id
2569
2606
  );
@@ -2632,6 +2669,7 @@ function TableBody({
2632
2669
  cellSelectionKey,
2633
2670
  pendingValuesKey: getPendingValuesKey(pendingValues[row.id]),
2634
2671
  clickable: clickableRows,
2672
+ onFillHandleMouseDown,
2635
2673
  virtualStyle: {
2636
2674
  position: "absolute",
2637
2675
  top: 0,
@@ -2666,7 +2704,8 @@ function TableRowInner({
2666
2704
  pendingValuesKey: _pendingValuesKey,
2667
2705
  clickable,
2668
2706
  pinnedPosition,
2669
- virtualStyle
2707
+ virtualStyle,
2708
+ onFillHandleMouseDown
2670
2709
  }) {
2671
2710
  const allCells = row.getAllCells();
2672
2711
  const visibleCells = visibleColumns.map((column) => allCells.find((cell) => cell.column.id === column.id)).filter((cell) => cell != null);
@@ -2746,7 +2785,8 @@ function TableRowInner({
2746
2785
  rowIndex,
2747
2786
  columnIndex,
2748
2787
  isFocused,
2749
- isTabStop
2788
+ isTabStop,
2789
+ onFillHandleMouseDown
2750
2790
  }
2751
2791
  )
2752
2792
  },
@@ -2780,7 +2820,7 @@ function areRowPropsEqual(prev, next) {
2780
2820
  if (prev.table !== next.table) return false;
2781
2821
  return true;
2782
2822
  }
2783
- var MemoizedTableRow = React3.memo(TableRowInner, areRowPropsEqual);
2823
+ var MemoizedTableRow = React4.memo(TableRowInner, areRowPropsEqual);
2784
2824
  function getPendingValuesKey(values) {
2785
2825
  if (!values) return "";
2786
2826
  return Object.keys(values).sort().map((key) => `${key}:${String(values[key])}`).join("|");
@@ -2998,8 +3038,8 @@ function StatusDivider() {
2998
3038
  return /* @__PURE__ */ jsx("span", { className: "yable-status-bar-divider", "aria-hidden": "true" });
2999
3039
  }
3000
3040
  function PanelGroup({ children }) {
3001
- const items = React3.Children.toArray(children).filter(Boolean);
3002
- return /* @__PURE__ */ jsx(Fragment, { children: items.map((child, i) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
3041
+ const items = React4.Children.toArray(children).filter(Boolean);
3042
+ return /* @__PURE__ */ jsx(Fragment, { children: items.map((child, i) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
3003
3043
  i > 0 && /* @__PURE__ */ jsx(StatusDivider, {}),
3004
3044
  child
3005
3045
  ] }, i)) });
@@ -4002,6 +4042,85 @@ function isEditableTarget(element) {
4002
4042
  }
4003
4043
  return element.isContentEditable;
4004
4044
  }
4045
+ function useFillHandle(table, options = {}) {
4046
+ const { enabled = true } = options;
4047
+ const [dragState, setDragState] = useState({
4048
+ isDragging: false,
4049
+ sourceCell: null,
4050
+ currentCell: null
4051
+ });
4052
+ const dragRef = useRef(dragState);
4053
+ dragRef.current = dragState;
4054
+ const onFillHandleMouseDown = useCallback(
4055
+ (rowIndex, columnIndex, e) => {
4056
+ if (!enabled) return;
4057
+ e.preventDefault();
4058
+ e.stopPropagation();
4059
+ setDragState({
4060
+ isDragging: true,
4061
+ sourceCell: { rowIndex, columnIndex },
4062
+ currentCell: { rowIndex, columnIndex }
4063
+ });
4064
+ },
4065
+ [enabled]
4066
+ );
4067
+ useEffect(() => {
4068
+ if (!dragState.isDragging) return;
4069
+ const handleMouseMove = (e) => {
4070
+ const target = document.elementFromPoint(e.clientX, e.clientY);
4071
+ if (!target) return;
4072
+ const td = target.closest("td[data-column-id]");
4073
+ const tr = target.closest("tr[data-row-id]");
4074
+ if (!td || !tr) return;
4075
+ const columnId = td.getAttribute("data-column-id");
4076
+ const rowId = tr.getAttribute("data-row-id");
4077
+ if (!columnId || !rowId) return;
4078
+ const rows = table.getRowModel().rows;
4079
+ const columns = table.getVisibleLeafColumns();
4080
+ const rowIndex = rows.findIndex((r) => r.id === rowId);
4081
+ const columnIndex = columns.findIndex((c) => c.id === columnId);
4082
+ if (rowIndex === -1 || columnIndex === -1) return;
4083
+ setDragState((prev) => ({
4084
+ ...prev,
4085
+ currentCell: { rowIndex, columnIndex }
4086
+ }));
4087
+ };
4088
+ const handleMouseUp = () => {
4089
+ const current = dragRef.current;
4090
+ if (current.sourceCell && current.currentCell) {
4091
+ const source = current.sourceCell;
4092
+ const target = current.currentCell;
4093
+ if (source.rowIndex !== target.rowIndex || source.columnIndex !== target.columnIndex) {
4094
+ const sourceRange = {
4095
+ startRow: source.rowIndex,
4096
+ startCol: source.columnIndex,
4097
+ endRow: source.rowIndex,
4098
+ endCol: source.columnIndex
4099
+ };
4100
+ const targetRange = {
4101
+ startRow: Math.min(source.rowIndex, target.rowIndex),
4102
+ startCol: Math.min(source.columnIndex, target.columnIndex),
4103
+ endRow: Math.max(source.rowIndex, target.rowIndex),
4104
+ endCol: Math.max(source.columnIndex, target.columnIndex)
4105
+ };
4106
+ table.fillRange(sourceRange, targetRange);
4107
+ }
4108
+ }
4109
+ setDragState({
4110
+ isDragging: false,
4111
+ sourceCell: null,
4112
+ currentCell: null
4113
+ });
4114
+ };
4115
+ document.addEventListener("mousemove", handleMouseMove);
4116
+ document.addEventListener("mouseup", handleMouseUp);
4117
+ return () => {
4118
+ document.removeEventListener("mousemove", handleMouseMove);
4119
+ document.removeEventListener("mouseup", handleMouseUp);
4120
+ };
4121
+ }, [dragState.isDragging, table]);
4122
+ return { dragState, onFillHandleMouseDown };
4123
+ }
4005
4124
  function filterHeaderGroups(groups, visibleColumnIds) {
4006
4125
  return groups.map((group) => ({
4007
4126
  ...group,
@@ -4125,6 +4244,9 @@ function Table({
4125
4244
  const showColumnVirtualizationShell = canVirtualizeColumns;
4126
4245
  const contextMenu = useContextMenu();
4127
4246
  useKeyboardNavigation(table, { containerRef });
4247
+ const { onFillHandleMouseDown } = useFillHandle(table, {
4248
+ enabled: Boolean(table.options.enableFillHandle)
4249
+ });
4128
4250
  const [announcement, setAnnouncement] = useState("");
4129
4251
  const prevSortingRef = useRef(table.getState().sorting);
4130
4252
  const prevFilterCountRef = useRef(rows.length);
@@ -4212,7 +4334,15 @@ function Table({
4212
4334
  children: [
4213
4335
  colgroup,
4214
4336
  /* @__PURE__ */ jsx(TableHeader, { table: renderTable, floatingFilters: resolvedFloatingFilters }),
4215
- /* @__PURE__ */ jsx(TableBody, { table: renderTable, clickableRows: resolvedClickableRows, colgroup }),
4337
+ /* @__PURE__ */ jsx(
4338
+ TableBody,
4339
+ {
4340
+ table: renderTable,
4341
+ clickableRows: resolvedClickableRows,
4342
+ colgroup,
4343
+ onFillHandleMouseDown
4344
+ }
4345
+ ),
4216
4346
  footer && /* @__PURE__ */ jsx(TableFooter, { table: renderTable })
4217
4347
  ]
4218
4348
  }
@@ -4900,110 +5030,6 @@ function isEditableTarget2(el) {
4900
5030
  if (el.isContentEditable) return true;
4901
5031
  return false;
4902
5032
  }
4903
- function useFillHandle(table, options = {}) {
4904
- const { enabled = true } = options;
4905
- const [dragState, setDragState] = useState({
4906
- isDragging: false,
4907
- sourceCell: null,
4908
- currentCell: null
4909
- });
4910
- const dragRef = useRef(dragState);
4911
- dragRef.current = dragState;
4912
- const onFillHandleMouseDown = useCallback(
4913
- (rowIndex, columnIndex, e) => {
4914
- if (!enabled) return;
4915
- e.preventDefault();
4916
- e.stopPropagation();
4917
- setDragState({
4918
- isDragging: true,
4919
- sourceCell: { rowIndex, columnIndex },
4920
- currentCell: { rowIndex, columnIndex }
4921
- });
4922
- },
4923
- [enabled]
4924
- );
4925
- useEffect(() => {
4926
- if (!dragState.isDragging) return;
4927
- const handleMouseMove = (e) => {
4928
- const target = document.elementFromPoint(e.clientX, e.clientY);
4929
- if (!target) return;
4930
- const td = target.closest("td[data-column-id]");
4931
- const tr = target.closest("tr[data-row-id]");
4932
- if (!td || !tr) return;
4933
- const columnId = td.getAttribute("data-column-id");
4934
- const rowId = tr.getAttribute("data-row-id");
4935
- if (!columnId || !rowId) return;
4936
- const rows = table.getRowModel().rows;
4937
- const columns = table.getVisibleLeafColumns();
4938
- const rowIndex = rows.findIndex((r) => r.id === rowId);
4939
- const columnIndex = columns.findIndex((c) => c.id === columnId);
4940
- if (rowIndex === -1 || columnIndex === -1) return;
4941
- setDragState((prev) => ({
4942
- ...prev,
4943
- currentCell: { rowIndex, columnIndex }
4944
- }));
4945
- };
4946
- const handleMouseUp = () => {
4947
- const current = dragRef.current;
4948
- if (current.sourceCell && current.currentCell) {
4949
- const source = current.sourceCell;
4950
- const target = current.currentCell;
4951
- if (source.rowIndex !== target.rowIndex || source.columnIndex !== target.columnIndex) {
4952
- const sourceRange = {
4953
- startRow: source.rowIndex,
4954
- startCol: source.columnIndex,
4955
- endRow: source.rowIndex,
4956
- endCol: source.columnIndex
4957
- };
4958
- const targetRange = {
4959
- startRow: Math.min(source.rowIndex, target.rowIndex),
4960
- startCol: Math.min(source.columnIndex, target.columnIndex),
4961
- endRow: Math.max(source.rowIndex, target.rowIndex),
4962
- endCol: Math.max(source.columnIndex, target.columnIndex)
4963
- };
4964
- table.fillRange(sourceRange, targetRange);
4965
- }
4966
- }
4967
- setDragState({
4968
- isDragging: false,
4969
- sourceCell: null,
4970
- currentCell: null
4971
- });
4972
- };
4973
- document.addEventListener("mousemove", handleMouseMove);
4974
- document.addEventListener("mouseup", handleMouseUp);
4975
- return () => {
4976
- document.removeEventListener("mousemove", handleMouseMove);
4977
- document.removeEventListener("mouseup", handleMouseUp);
4978
- };
4979
- }, [dragState.isDragging, table]);
4980
- return { dragState, onFillHandleMouseDown };
4981
- }
4982
- function FillHandle({
4983
- rowIndex,
4984
- columnIndex,
4985
- onMouseDown
4986
- }) {
4987
- const handleMouseDown = useCallback(
4988
- (e) => {
4989
- e.preventDefault();
4990
- e.stopPropagation();
4991
- onMouseDown(rowIndex, columnIndex, e);
4992
- },
4993
- [rowIndex, columnIndex, onMouseDown]
4994
- );
4995
- return /* @__PURE__ */ jsx(
4996
- "div",
4997
- {
4998
- className: "yable-fill-handle",
4999
- onMouseDown: handleMouseDown,
5000
- role: "presentation",
5001
- "aria-hidden": "true",
5002
- title: "Drag to fill",
5003
- children: /* @__PURE__ */ jsx("div", { className: "yable-fill-handle-dot" })
5004
- }
5005
- );
5006
- }
5007
5033
  function GripIcon() {
5008
5034
  return /* @__PURE__ */ jsxs(
5009
5035
  "svg",