@umodeapporg/ui 0.14.0 → 0.16.0

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.
Files changed (61) hide show
  1. package/dist/components/Form/ControlledImage/index.d.ts +3 -0
  2. package/dist/components/Form/ControlledImage/index.js +2 -1
  3. package/dist/components/Form/ControlledImage/index.js.map +1 -1
  4. package/dist/components/UI/DataTable/DataTable.js +15 -4
  5. package/dist/components/UI/DataTable/DataTable.js.map +1 -1
  6. package/dist/components/UI/DataTable/DataTableBody.d.ts +5 -1
  7. package/dist/components/UI/DataTable/DataTableBody.js +15 -2
  8. package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
  9. package/dist/components/UI/DataTable/DataTableBodyCell.js +11 -0
  10. package/dist/components/UI/DataTable/DataTableBodyCell.js.map +1 -1
  11. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +7 -10
  12. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
  13. package/dist/components/UI/DataTable/grid/GridProvider.d.ts +3 -0
  14. package/dist/components/UI/DataTable/grid/GridProvider.js +25 -3
  15. package/dist/components/UI/DataTable/grid/GridProvider.js.map +1 -1
  16. package/dist/components/UI/DataTable/grid/gridStore.d.ts +10 -2
  17. package/dist/components/UI/DataTable/grid/gridStore.js +30 -3
  18. package/dist/components/UI/DataTable/grid/gridStore.js.map +1 -1
  19. package/dist/components/UI/DataTable/grid/gridTypes.d.ts +17 -5
  20. package/dist/components/UI/DataTable/grid/gridTypes.js.map +1 -1
  21. package/dist/components/UI/DataTable/grid/useGridKeyboard.d.ts +4 -2
  22. package/dist/components/UI/DataTable/grid/useGridKeyboard.js +25 -11
  23. package/dist/components/UI/DataTable/grid/useGridKeyboard.js.map +1 -1
  24. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.d.ts +6 -0
  25. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js +164 -74
  26. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js.map +1 -1
  27. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.d.ts +8 -0
  28. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js +67 -0
  29. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js.map +1 -0
  30. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.d.ts +8 -0
  31. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js +39 -0
  32. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js.map +1 -0
  33. package/dist/components/UI/ImageAiAdjust/imageAiReview.d.ts +22 -0
  34. package/dist/components/UI/ImageAiAdjust/imageAiReview.js +37 -0
  35. package/dist/components/UI/ImageAiAdjust/imageAiReview.js.map +1 -0
  36. package/dist/components/UI/ImageAiAdjust/imageAiVerification.d.ts +19 -0
  37. package/dist/components/UI/ImageAiAdjust/imageAiVerification.js +25 -0
  38. package/dist/components/UI/ImageAiAdjust/imageAiVerification.js.map +1 -0
  39. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.d.ts +25 -2
  40. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js +44 -19
  41. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js.map +1 -1
  42. package/dist/components/UI/ImageMaskPainter/index.d.ts +19 -0
  43. package/dist/components/UI/ImageMaskPainter/index.js +267 -0
  44. package/dist/components/UI/ImageMaskPainter/index.js.map +1 -0
  45. package/dist/components/UI/ImageMaskPainter/maskStrokes.d.ts +19 -0
  46. package/dist/components/UI/ImageMaskPainter/maskStrokes.js +71 -0
  47. package/dist/components/UI/ImageMaskPainter/maskStrokes.js.map +1 -0
  48. package/dist/helpers/imageFile.d.ts +16 -0
  49. package/dist/helpers/imageFile.js +59 -10
  50. package/dist/helpers/imageFile.js.map +1 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +12 -10
  53. package/dist/index.js.map +1 -1
  54. package/dist/locales/en/ui.json.d.ts +36 -1
  55. package/dist/locales/en/ui.json.js +7 -1
  56. package/dist/locales/en/ui.json.js.map +1 -1
  57. package/dist/locales/index.d.ts +70 -0
  58. package/dist/locales/pt/ui.json.d.ts +36 -1
  59. package/dist/locales/pt/ui.json.js +7 -1
  60. package/dist/locales/pt/ui.json.js.map +1 -1
  61. package/package.json +5 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DataTableBodyCell.js","sources":["../../../../src/components/UI/DataTable/DataTableBodyCell.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\nimport classNames from 'classnames';\n\nimport GridFillHandle from './grid/GridFillHandle';\nimport { useGridCellRegistry, useGridCellState, useGridStoreApi } from './grid/GridProvider';\nimport {\n buildGridCellKey,\n GRID_CELL_ACTIVE,\n GRID_CELL_DATA_ATTR,\n GRID_CELL_FILL_HANDLE,\n GRID_CELL_FILL_PREVIEW,\n GRID_CELL_SAVE_ERROR,\n GRID_CELL_SAVING,\n GRID_CELL_SELECTED,\n GRID_CELL_TABBABLE,\n GRID_COLUMN_DATA_ATTR,\n GRID_ROW_DATA_ATTR,\n} from './grid/gridTypes';\n\ninterface IDataTableBodyCellProps {\n /** TanStack row id: unique across nesting levels, unlike the entity id. */\n rowId: string;\n columnId: string;\n /** Actions columns hold controls, never a value: they stay out of the matrix. */\n navigable: boolean;\n /** Whether this column accepts a drag-to-fill from the selection corner. */\n fillEnabled?: boolean;\n className: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\n/**\n * The `<td>` of a data row. Outside a spreadsheet-enabled table it renders exactly\n * what it always did; inside one it also carries the roving tabindex, the grid\n * semantics and its own subscription to the grid state, so moving the active cell\n * re-renders two cells instead of the table.\n */\nconst DataTableBodyCell: React.FC<IDataTableBodyCellProps> = ({\n rowId,\n columnId,\n navigable,\n fillEnabled = false,\n className,\n style,\n children,\n}) => {\n const store = useGridStoreApi();\n const registry = useGridCellRegistry();\n const cellState = useGridCellState(rowId, columnId);\n\n const isGridCell = !!store && !!registry && navigable;\n const cellKey = buildGridCellKey(rowId, columnId);\n\n const registerCell = useCallback(\n (element: HTMLTableCellElement | null) => {\n if (!registry) return;\n registry.register(cellKey, element);\n },\n [registry, cellKey],\n );\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent<HTMLTableCellElement>) => {\n store?.getState().setActiveCell({ rowId, columnId }, { extend: event.shiftKey });\n },\n [store, rowId, columnId],\n );\n\n if (!isGridCell) {\n return (\n <td className={className} style={style}>\n {children}\n </td>\n );\n }\n\n const isActive = (cellState & GRID_CELL_ACTIVE) !== 0;\n const isSelected = (cellState & GRID_CELL_SELECTED) !== 0;\n const isFillPreview = (cellState & GRID_CELL_FILL_PREVIEW) !== 0;\n const showsFillHandle = fillEnabled && (cellState & GRID_CELL_FILL_HANDLE) !== 0;\n const isSaving = (cellState & GRID_CELL_SAVING) !== 0;\n const hasSaveError = (cellState & GRID_CELL_SAVE_ERROR) !== 0;\n\n return (\n <td\n ref={registerCell}\n role=\"gridcell\"\n aria-selected={isSelected}\n tabIndex={isActive || (cellState & GRID_CELL_TABBABLE) !== 0 ? 0 : -1}\n onMouseDown={handleMouseDown}\n {...{ [GRID_CELL_DATA_ATTR]: cellKey, [GRID_ROW_DATA_ATTR]: rowId, [GRID_COLUMN_DATA_ATTR]: columnId }}\n className={classNames(\n className,\n 'outline-none',\n // `!` because the frozen first/actions cells paint `bg-inherit`, which is\n // the same utility group: the state fill has to win regardless of the\n // order Tailwind emits them in.\n isSelected && !isActive && '!bg-brand-soft/40',\n isFillPreview && '!bg-brand-soft/60',\n (isActive || showsFillHandle) && 'relative',\n isActive && 'z-20 ring-2 ring-inset ring-brand',\n isFillPreview && 'ring-1 ring-inset ring-brand-border',\n isSaving && 'opacity-70',\n hasSaveError && '!bg-danger-soft ring-1 ring-inset ring-danger-border',\n )}\n style={style}\n >\n {children}\n {showsFillHandle && <GridFillHandle />}\n </td>\n );\n};\n\nexport default DataTableBodyCell;\n"],"names":[],"mappings":";;;;;;AAsCA,MAAM,oBAAuD,CAAC;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,QAAQ,gBAAA;AACd,QAAM,WAAW,oBAAA;AACjB,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAElD,QAAM,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,YAAY;AAC5C,QAAM,UAAU,iBAAiB,OAAO,QAAQ;AAEhD,QAAM,eAAe;AAAA,IACnB,CAAC,YAAyC;AACxC,UAAI,CAAC,SAAU;AACf,eAAS,SAAS,SAAS,OAAO;AAAA,IACpC;AAAA,IACA,CAAC,UAAU,OAAO;AAAA,EAAA;AAGpB,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAAkD;AACjD,aAAO,SAAA,EAAW,cAAc,EAAE,OAAO,SAAA,GAAY,EAAE,QAAQ,MAAM,UAAU;AAAA,IACjF;AAAA,IACA,CAAC,OAAO,OAAO,QAAQ;AAAA,EAAA;AAGzB,MAAI,CAAC,YAAY;AACf,WACE,oBAAC,MAAA,EAAG,WAAsB,OACvB,SAAA,CACH;AAAA,EAEJ;AAEA,QAAM,YAAY,YAAY,sBAAsB;AACpD,QAAM,cAAc,YAAY,wBAAwB;AACxD,QAAM,iBAAiB,YAAY,4BAA4B;AAC/D,QAAM,kBAAkB,gBAAgB,YAAY,2BAA2B;AAC/E,QAAM,YAAY,YAAY,sBAAsB;AACpD,QAAM,gBAAgB,YAAY,0BAA0B;AAE5D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,UAAU,aAAa,YAAY,wBAAwB,IAAI,IAAI;AAAA,MACnE,aAAa;AAAA,MACZ,GAAG,EAAE,CAAC,mBAAmB,GAAG,SAAS,CAAC,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAA;AAAA,MAC5F,WAAW;AAAA,QACT;AAAA,QACA;AAAA;AAAA;AAAA;AAAA,QAIA,cAAc,CAAC,YAAY;AAAA,QAC3B,iBAAiB;AAAA,SAChB,YAAY,oBAAoB;AAAA,QACjC,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAAA;AAAA,MAElB;AAAA,MAEC,UAAA;AAAA,QAAA;AAAA,QACA,uCAAoB,gBAAA,CAAA,CAAe;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG1C;"}
1
+ {"version":3,"file":"DataTableBodyCell.js","sources":["../../../../src/components/UI/DataTable/DataTableBodyCell.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\nimport classNames from 'classnames';\n\nimport GridFillHandle from './grid/GridFillHandle';\nimport { useGridCellRegistry, useGridCellState, useGridStoreApi } from './grid/GridProvider';\nimport {\n buildGridCellKey,\n GRID_CELL_ACTIVE,\n GRID_CELL_DATA_ATTR,\n GRID_CELL_FILL_HANDLE,\n GRID_CELL_FILL_PREVIEW,\n GRID_CELL_SAVE_ERROR,\n GRID_CELL_SAVING,\n GRID_CELL_SELECTED,\n GRID_CELL_TABBABLE,\n GRID_COLUMN_DATA_ATTR,\n GRID_ROW_DATA_ATTR,\n} from './grid/gridTypes';\n\ninterface IDataTableBodyCellProps {\n /** TanStack row id: unique across nesting levels, unlike the entity id. */\n rowId: string;\n columnId: string;\n /** Actions columns hold controls, never a value: they stay out of the matrix. */\n navigable: boolean;\n /** Whether this column accepts a drag-to-fill from the selection corner. */\n fillEnabled?: boolean;\n className: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n}\n\n/**\n * The `<td>` of a data row. Outside a spreadsheet-enabled table it renders exactly\n * what it always did; inside one it also carries the roving tabindex, the grid\n * semantics and its own subscription to the grid state, so moving the active cell\n * re-renders two cells instead of the table.\n */\nconst DataTableBodyCell: React.FC<IDataTableBodyCellProps> = ({\n rowId,\n columnId,\n navigable,\n fillEnabled = false,\n className,\n style,\n children,\n}) => {\n const store = useGridStoreApi();\n const registry = useGridCellRegistry();\n const cellState = useGridCellState(rowId, columnId);\n\n const isGridCell = !!store && !!registry && navigable;\n const cellKey = buildGridCellKey(rowId, columnId);\n\n const registerCell = useCallback(\n (element: HTMLTableCellElement | null) => {\n if (!registry) return;\n registry.register(cellKey, element);\n },\n [registry, cellKey],\n );\n\n const handleMouseDown = useCallback(\n (event: React.MouseEvent<HTMLTableCellElement>) => {\n store?.getState().setActiveCell({ rowId, columnId }, { extend: event.shiftKey });\n },\n [store, rowId, columnId],\n );\n\n // One click edits. It opens on `click`, not on `mousedown`: by then the browser has\n // already moved the focus to the cell, so it cannot pull it back out of the editor,\n // and a picker's popup opens after the press that would otherwise count as outside it.\n const handleClick = useCallback(\n (event: React.MouseEvent<HTMLTableCellElement>) => {\n if (!store || !registry || event.shiftKey || event.button !== 0) return;\n if (!event.currentTarget.contains(event.target as Node)) return;\n\n const coord = { rowId, columnId };\n if (!registry.canEditCell(coord)) return;\n\n store.getState().startEditing(coord, { entry: 'pointer' });\n },\n [store, registry, rowId, columnId],\n );\n\n if (!isGridCell) {\n return (\n <td className={className} style={style}>\n {children}\n </td>\n );\n }\n\n const isActive = (cellState & GRID_CELL_ACTIVE) !== 0;\n const isSelected = (cellState & GRID_CELL_SELECTED) !== 0;\n const isFillPreview = (cellState & GRID_CELL_FILL_PREVIEW) !== 0;\n const showsFillHandle = fillEnabled && (cellState & GRID_CELL_FILL_HANDLE) !== 0;\n const isSaving = (cellState & GRID_CELL_SAVING) !== 0;\n const hasSaveError = (cellState & GRID_CELL_SAVE_ERROR) !== 0;\n\n return (\n <td\n ref={registerCell}\n role=\"gridcell\"\n aria-selected={isSelected}\n tabIndex={isActive || (cellState & GRID_CELL_TABBABLE) !== 0 ? 0 : -1}\n onMouseDown={handleMouseDown}\n onClick={handleClick}\n {...{ [GRID_CELL_DATA_ATTR]: cellKey, [GRID_ROW_DATA_ATTR]: rowId, [GRID_COLUMN_DATA_ATTR]: columnId }}\n className={classNames(\n className,\n 'outline-none',\n // `!` because the frozen first/actions cells paint `bg-inherit`, which is\n // the same utility group: the state fill has to win regardless of the\n // order Tailwind emits them in.\n isSelected && !isActive && '!bg-brand-soft/40',\n isFillPreview && '!bg-brand-soft/60',\n (isActive || showsFillHandle) && 'relative',\n isActive && 'z-20 ring-2 ring-inset ring-brand',\n isFillPreview && 'ring-1 ring-inset ring-brand-border',\n isSaving && 'opacity-70',\n hasSaveError && '!bg-danger-soft ring-1 ring-inset ring-danger-border',\n )}\n style={style}\n >\n {children}\n {showsFillHandle && <GridFillHandle />}\n </td>\n );\n};\n\nexport default DataTableBodyCell;\n"],"names":[],"mappings":";;;;;;AAsCA,MAAM,oBAAuD,CAAC;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,QAAQ,gBAAA;AACd,QAAM,WAAW,oBAAA;AACjB,QAAM,YAAY,iBAAiB,OAAO,QAAQ;AAElD,QAAM,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,YAAY;AAC5C,QAAM,UAAU,iBAAiB,OAAO,QAAQ;AAEhD,QAAM,eAAe;AAAA,IACnB,CAAC,YAAyC;AACxC,UAAI,CAAC,SAAU;AACf,eAAS,SAAS,SAAS,OAAO;AAAA,IACpC;AAAA,IACA,CAAC,UAAU,OAAO;AAAA,EAAA;AAGpB,QAAM,kBAAkB;AAAA,IACtB,CAAC,UAAkD;AACjD,aAAO,SAAA,EAAW,cAAc,EAAE,OAAO,SAAA,GAAY,EAAE,QAAQ,MAAM,UAAU;AAAA,IACjF;AAAA,IACA,CAAC,OAAO,OAAO,QAAQ;AAAA,EAAA;AAMzB,QAAM,cAAc;AAAA,IAClB,CAAC,UAAkD;AACjD,UAAI,CAAC,SAAS,CAAC,YAAY,MAAM,YAAY,MAAM,WAAW,EAAG;AACjE,UAAI,CAAC,MAAM,cAAc,SAAS,MAAM,MAAc,EAAG;AAEzD,YAAM,QAAQ,EAAE,OAAO,SAAA;AACvB,UAAI,CAAC,SAAS,YAAY,KAAK,EAAG;AAElC,YAAM,WAAW,aAAa,OAAO,EAAE,OAAO,WAAW;AAAA,IAC3D;AAAA,IACA,CAAC,OAAO,UAAU,OAAO,QAAQ;AAAA,EAAA;AAGnC,MAAI,CAAC,YAAY;AACf,WACE,oBAAC,MAAA,EAAG,WAAsB,OACvB,SAAA,CACH;AAAA,EAEJ;AAEA,QAAM,YAAY,YAAY,sBAAsB;AACpD,QAAM,cAAc,YAAY,wBAAwB;AACxD,QAAM,iBAAiB,YAAY,4BAA4B;AAC/D,QAAM,kBAAkB,gBAAgB,YAAY,2BAA2B;AAC/E,QAAM,YAAY,YAAY,sBAAsB;AACpD,QAAM,gBAAgB,YAAY,0BAA0B;AAE5D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,UAAU,aAAa,YAAY,wBAAwB,IAAI,IAAI;AAAA,MACnE,aAAa;AAAA,MACb,SAAS;AAAA,MACR,GAAG,EAAE,CAAC,mBAAmB,GAAG,SAAS,CAAC,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAA;AAAA,MAC5F,WAAW;AAAA,QACT;AAAA,QACA;AAAA;AAAA;AAAA;AAAA,QAIA,cAAc,CAAC,YAAY;AAAA,QAC3B,iBAAiB;AAAA,SAChB,YAAY,oBAAoB;AAAA,QACjC,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAAA;AAAA,MAElB;AAAA,MAEC,UAAA;AAAA,QAAA;AAAA,QACA,uCAAoB,gBAAA,CAAA,CAAe;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAG1C;"}
@@ -66,8 +66,13 @@ const DataTableEditableCell = ({
66
66
  useEffect(() => {
67
67
  if (!isGridCell || !editable) return;
68
68
  if (isEditing) {
69
- if (!wasEditingRef.current) valueBeforeEditRef.current = editable.getValue(row);
69
+ if (wasEditingRef.current) return;
70
70
  wasEditingRef.current = true;
71
+ valueBeforeEditRef.current = editable.getValue(row);
72
+ const typedSeed = gridStore?.getState().editingSeed;
73
+ if (typedSeed == null) return;
74
+ setDraftText(typedSeed);
75
+ onCellChange(row, column.id, typedSeed);
71
76
  return;
72
77
  }
73
78
  if (!wasEditingRef.current) return;
@@ -210,15 +215,7 @@ const DataTableEditableCell = ({
210
215
  }
211
216
  );
212
217
  if (hasGrid && !isGridEditModeOn) return readCell;
213
- if (isGridCell) {
214
- if (isEditing) return renderEditor();
215
- if (disabled) return readCell;
216
- return (
217
- // The cell itself is the focus target and the keyboard entry point (Enter / F2);
218
- // the double click is the mouse equivalent, exactly like a spreadsheet.
219
- /* @__PURE__ */ jsx("div", { onDoubleClick: () => gridStore?.getState().startEditing({ rowId: gridRowId, columnId: column.id }), children: readCell })
220
- );
221
- }
218
+ if (isGridCell) return isEditing ? renderEditor() : readCell;
222
219
  if (!cellEditing) return renderEditor();
223
220
  return /* @__PURE__ */ jsx("div", { ref: containerRef, onKeyDown: handleKeyDown, ...{ [EDITABLE_CELL_ATTR]: cellKey }, children: isEditing ? renderEditor() : disabled ? readCell : /* @__PURE__ */ jsx(ControlledTooltip, { content: t("dataTable.editCell"), children: /* @__PURE__ */ jsx(
224
221
  "div",
@@ -1 +1 @@
1
- {"version":3,"file":"DataTableEditableCell.js","sources":["../../../../../src/components/UI/DataTable/cells/DataTableEditableCell.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport DatePicker from '@/components/DateTime/DatePicker';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatIsoDate } from '@/helpers/dateHelpers';\nimport { useHoverCapable } from '@/hooks/useHoverCapable';\nimport { useInlineEditor } from '@/hooks/useInlineEditor';\n\nimport { useGridCellState, useGridStoreApi } from '../grid/GridProvider';\nimport { GRID_CELL_EDITING } from '../grid/gridTypes';\nimport { useGridEditMode } from '../grid/useGridInstance';\nimport type { IDataTableColumnConfig, IDataTableRow } from '../types';\nimport { buildEditableCellKey, EDITABLE_CELL_ATTR, useDataTableCellEditing } from '../useCellEditingState';\n\n/**\n * Marks the typeable editable inputs so `keyboardNav` can walk them in DOM order.\n * Only text/number inputs opt in; date and select cells drive their own popovers\n * and fall back to native Tab traversal.\n */\nconst EDITABLE_INPUT_ATTR = 'data-datatable-editable-input';\n\ntype TEditableValue = string | number | Array<string> | null;\n\n/** Multi-select devolve um array novo a cada leitura, então `===` nunca casaria. */\nconst isSameEditableValue = (current: TEditableValue, previous: TEditableValue): boolean => {\n if (Array.isArray(current) || Array.isArray(previous)) {\n if (!Array.isArray(current) || !Array.isArray(previous)) return false;\n return current.length === previous.length && current.every((entry, index) => entry === previous[index]);\n }\n\n return current === previous;\n};\n\n/** `,` for pt-BR, `.` for en: derived from the locale itself, no language table. */\nconst resolveDecimalSeparator = (language: string): string => {\n const separator = (1.1).toLocaleString(language || 'pt-BR').replace(/\\d/g, '');\n return separator || '.';\n};\n\nconst focusNextEditableInput = (current: HTMLElement): void => {\n const inputs = Array.from(document.querySelectorAll<HTMLElement>(`[${EDITABLE_INPUT_ATTR}]`));\n const index = inputs.indexOf(current);\n if (index === -1) return;\n inputs[index + 1]?.focus();\n};\n\ninterface IDataTableEditableCellProps<T extends IDataTableRow> {\n column: IDataTableColumnConfig<T>;\n row: T;\n /** TanStack row id: present only inside a spreadsheet-enabled table. */\n gridRowId?: string;\n onCellChange: (row: T, columnId: string, value: unknown) => void;\n /** When true, pressing Enter moves focus to the next editable input (DOM order). */\n keyboardNav?: boolean;\n /** When true, the cell shows the value as text until it is clicked. */\n clickToEdit?: boolean;\n}\n\n/**\n * Controlled inline editor for a single cell. Reads the current value from the\n * host-owned row via `column.editable.getValue` and reports every edit through\n * `onCellChange`; the host re-renders with the updated row. Validation is derived\n * from the current value on each render, so no local state is kept.\n *\n * With `clickToEdit`, the cell renders the value as text and only swaps in the editor\n * once clicked, no editor is mounted before that.\n */\nconst DataTableEditableCell = <T extends IDataTableRow>({\n column,\n row,\n gridRowId,\n onCellChange,\n keyboardNav = false,\n clickToEdit = false,\n}: IDataTableEditableCellProps<T>) => {\n const { t, i18n } = useTranslation('ui');\n const hoverCapable = useHoverCapable();\n const cellEditing = useDataTableCellEditing();\n const gridStore = useGridStoreApi();\n const gridCellState = useGridCellState(gridRowId ?? '', column.id);\n const cellKey = buildEditableCellKey(row.id, column.id);\n\n // Inside a spreadsheet-enabled table the grid store owns the editing state, so\n // the legacy click-to-edit context and its document listener stay out of the way.\n const isGridEditModeOn = useGridEditMode(gridStore);\n const hasGrid = !!gridStore && !!gridRowId;\n const isGridCell = hasGrid && isGridEditModeOn;\n const isEditing = isGridCell\n ? (gridCellState & GRID_CELL_EDITING) !== 0\n : clickToEdit\n ? cellEditing?.editingCellKey === cellKey\n : true;\n const showsReadView = isGridCell || clickToEdit;\n\n const { containerRef, handleKeyDown } = useInlineEditor(\n Boolean(!isGridCell && clickToEdit && isEditing),\n () => cellEditing?.stopEditing(),\n (direction) => cellEditing?.navigate(cellKey, direction),\n );\n\n const editable = column.editable;\n const valueBeforeEditRef = useRef<string | number | Array<string> | null>(null);\n const wasEditingRef = useRef(false);\n\n // The cell is controlled by the value the host ALREADY parsed, so a half-typed\n // decimal is erased on the same keystroke: `1,` parses to the number 1 and comes\n // back as `\"1\"`, taking with it the separator just typed. The draft holds the exact\n // text while the field is focused; the host still receives every keystroke and\n // remains the source of truth.\n const [draftText, setDraftText] = useState<string | null>(null);\n\n useEffect(() => {\n if (!isEditing) setDraftText(null);\n }, [isEditing]);\n\n // Escape restores what the cell held when the editor opened. The editing model is\n // controlled (every keystroke already reached the host), so cancelling means\n // writing the snapshot back, not dropping local state.\n useEffect(() => {\n if (!isGridCell || !editable) return;\n\n if (isEditing) {\n if (!wasEditingRef.current) valueBeforeEditRef.current = editable.getValue(row);\n wasEditingRef.current = true;\n return;\n }\n\n if (!wasEditingRef.current) return;\n wasEditingRef.current = false;\n\n if (gridStore?.getState().editingOutcome !== 'cancel') return;\n if (isSameEditableValue(editable.getValue(row), valueBeforeEditRef.current)) return;\n\n onCellChange(row, column.id, valueBeforeEditRef.current);\n }, [isEditing, isGridCell, editable, gridStore, onCellChange, row, column.id]);\n\n if (!editable) return null;\n\n const rawValue = editable.getValue(row);\n const disabled = editable.disabled?.(row) ?? false;\n const error = editable.validate?.(rawValue, row) ?? null;\n const arrayValue = Array.isArray(rawValue) ? rawValue.map(String) : [];\n const stringValue = rawValue == null || Array.isArray(rawValue) ? '' : String(rawValue);\n\n // `String()` on a number always writes a dot, so a pt-BR user would get back a\n // separator that is not theirs. Hosts that keep the raw text (the edit buffer) are\n // untouched: only an actual number reaches this branch.\n const decimalValue =\n editable.type === 'number' && typeof rawValue === 'number'\n ? stringValue.replace('.', resolveDecimalSeparator(i18n.language))\n : stringValue;\n\n const renderEditor = () => {\n if (editable.renderEditor) {\n return editable.renderEditor({\n value: rawValue,\n onChange: (nextValue) => onCellChange(row, column.id, nextValue),\n disabled,\n label: column.header,\n });\n }\n\n if (editable.type === 'multiselect') {\n return (\n <ControlledSelect\n className=\"w-full [&_.controlled-select-container]:!min-w-0 [&_.controlled-select-input]:!min-w-0\"\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={arrayValue}\n onChangeHook={(selected) => onCellChange(row, column.id, Array.isArray(selected) ? selected : [selected])}\n multiple\n searchable\n clearable\n disabled={disabled}\n error={!!error}\n />\n );\n }\n\n if (editable.type === 'select') {\n return (\n // `ControlledSelect` ships hard min-widths (`min-w-52` on the field and the MT\n // container's `min-w-[200px]`) that overflow narrow table cells and spill into\n // the neighbouring column; force both to shrink so the editor fits its column\n // like the text/number editors do.\n <ControlledSelect\n className=\"w-full [&_.controlled-select-container]:!min-w-0 [&_.controlled-select-input]:!min-w-0\"\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={stringValue}\n onChangeHook={(selected) => onCellChange(row, column.id, selected)}\n multiple={false}\n searchable\n clearable\n disabled={disabled}\n error={!!error}\n />\n );\n }\n\n if (editable.type === 'date') {\n const parsed = stringValue ? new Date(stringValue) : undefined;\n const startValue = parsed && !Number.isNaN(parsed.getTime()) ? parsed : undefined;\n\n return (\n <DatePicker\n startValue={startValue}\n onUpdate={(value) => onCellChange(row, column.id, value ?? null)}\n disabled={disabled}\n />\n );\n }\n\n // `number` renders a text input with a decimal input mode rather than\n // `type=\"number\"`, so pt-BR comma decimals are not blocked by the browser; the\n // host parses the raw string it receives in `onCellChange`.\n return (\n <ControlledInput\n value={draftText ?? decimalValue}\n onChange={(event) => {\n setDraftText(event.target.value);\n onCellChange(row, column.id, event.target.value);\n }}\n onBlur={() => setDraftText(null)}\n onKeyDown={\n keyboardNav && !isGridCell\n ? (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n focusNextEditableInput(event.currentTarget);\n }\n }\n : undefined\n }\n inputMode={editable.type === 'number' ? 'decimal' : undefined}\n disabled={disabled}\n error={!!error}\n errorMessage={error ?? undefined}\n aria-label={column.header}\n {...{ [EDITABLE_INPUT_ATTR]: '' }}\n // The Material-Tailwind input container ships a hard `min-w-[200px]` that\n // overflows narrow table cells; force it to shrink so the editor fits its\n // column (the cell padding then provides the gap between adjacent inputs).\n containerClassName=\"min-w-0\"\n />\n );\n };\n\n if (!hasGrid && (!showsReadView || !cellEditing)) return renderEditor();\n\n const resolveOptionLabel = (optionValue: string): string =>\n (editable.options ?? []).find((option) => String(option.value) === optionValue)?.label ?? optionValue;\n\n const resolveDisplayText = (): string => {\n if (editable.displayValue) return editable.displayValue(row);\n if (editable.type === 'multiselect') return arrayValue.map(resolveOptionLabel).join(', ');\n if (stringValue === '') return '';\n if (editable.type === 'select') return resolveOptionLabel(stringValue);\n if (editable.type === 'date') return formatIsoDate(stringValue);\n return decimalValue;\n };\n\n const displayText = resolveDisplayText();\n\n // Uma coluna com renderização própria (chip de etapa, status, thumbnail) mantém\n // esse visual no modo de leitura: só o editor aberto troca a célula por um input.\n const displayContent =\n !editable.displayValue && column.render ? (\n column.render(row)\n ) : (\n <span className={classNames('min-w-0 flex-1 truncate', displayText === '' && 'text-foreground-faint')}>\n {displayText === '' ? '-' : displayText}\n </span>\n );\n\n // Com o grid montado mas o modo de edição desligado, a célula é só o valor: sem\n // lápis, sem realce e sem clique, para não prometer uma edição que não abre.\n const showsEditAffordance = !disabled && (!hasGrid || isGridEditModeOn);\n\n const readCell = (\n <div\n className={classNames(\n // A mesma altura do editor (h-10), para a linha não pular quando a célula é aberta.\n 'flex min-h-10 w-full items-center gap-1 rounded-md border border-transparent px-2 py-1.5 text-sm',\n disabled && 'text-foreground-faint',\n !disabled && !showsEditAffordance && 'text-foreground',\n showsEditAffordance &&\n 'group cursor-pointer text-foreground transition-colors hover:border-brand-border hover:bg-brand-soft/50',\n error && 'border-danger-border',\n )}\n >\n <div className=\"min-w-0 flex-1 truncate\">{displayContent}</div>\n {showsEditAffordance && (\n // On touch there is no hover, so a hover-only pencil means the cell never\n // advertises that it can be edited. Keep it visible (dimmed) there instead.\n <MaterialSymbol\n icon=\"edit\"\n size={13}\n className={classNames(\n 'shrink-0 text-brand-emphasis transition-opacity',\n hoverCapable ? 'opacity-0 group-hover:opacity-100' : 'opacity-60',\n )}\n />\n )}\n </div>\n );\n\n // Fora do modo de edição a tabela é de leitura: nenhum editor fica montado.\n if (hasGrid && !isGridEditModeOn) return readCell;\n\n if (isGridCell) {\n if (isEditing) return renderEditor();\n if (disabled) return readCell;\n\n return (\n // The cell itself is the focus target and the keyboard entry point (Enter / F2);\n // the double click is the mouse equivalent, exactly like a spreadsheet.\n <div onDoubleClick={() => gridStore?.getState().startEditing({ rowId: gridRowId, columnId: column.id })}>\n {readCell}\n </div>\n );\n }\n\n if (!cellEditing) return renderEditor();\n\n return (\n <div ref={containerRef} onKeyDown={handleKeyDown} {...{ [EDITABLE_CELL_ATTR]: cellKey }}>\n {isEditing ? (\n renderEditor()\n ) : disabled ? (\n readCell\n ) : (\n <ControlledTooltip content={t('dataTable.editCell')}>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => cellEditing.startEditing(cellKey)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n cellEditing.startEditing(cellKey);\n }}\n className=\"rounded-md focus:outline-none focus-visible:ring-1 focus-visible:ring-brand-border\"\n >\n {readCell}\n </div>\n </ControlledTooltip>\n )}\n </div>\n );\n};\n\nexport default DataTableEditableCell;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DataTableEditableCell.js","sources":["../../../../../src/components/UI/DataTable/cells/DataTableEditableCell.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport DatePicker from '@/components/DateTime/DatePicker';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport { ControlledSelect } from '@/components/Form/ControlledSelect';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { formatIsoDate } from '@/helpers/dateHelpers';\nimport { useHoverCapable } from '@/hooks/useHoverCapable';\nimport { useInlineEditor } from '@/hooks/useInlineEditor';\n\nimport { useGridCellState, useGridStoreApi } from '../grid/GridProvider';\nimport { GRID_CELL_EDITING } from '../grid/gridTypes';\nimport { useGridEditMode } from '../grid/useGridInstance';\nimport type { IDataTableColumnConfig, IDataTableRow } from '../types';\nimport { buildEditableCellKey, EDITABLE_CELL_ATTR, useDataTableCellEditing } from '../useCellEditingState';\n\n/**\n * Marks the typeable editable inputs so `keyboardNav` can walk them in DOM order.\n * Only text/number inputs opt in; date and select cells drive their own popovers\n * and fall back to native Tab traversal.\n */\nconst EDITABLE_INPUT_ATTR = 'data-datatable-editable-input';\n\ntype TEditableValue = string | number | Array<string> | null;\n\n/** Multi-select devolve um array novo a cada leitura, então `===` nunca casaria. */\nconst isSameEditableValue = (current: TEditableValue, previous: TEditableValue): boolean => {\n if (Array.isArray(current) || Array.isArray(previous)) {\n if (!Array.isArray(current) || !Array.isArray(previous)) return false;\n return current.length === previous.length && current.every((entry, index) => entry === previous[index]);\n }\n\n return current === previous;\n};\n\n/** `,` for pt-BR, `.` for en: derived from the locale itself, no language table. */\nconst resolveDecimalSeparator = (language: string): string => {\n const separator = (1.1).toLocaleString(language || 'pt-BR').replace(/\\d/g, '');\n return separator || '.';\n};\n\nconst focusNextEditableInput = (current: HTMLElement): void => {\n const inputs = Array.from(document.querySelectorAll<HTMLElement>(`[${EDITABLE_INPUT_ATTR}]`));\n const index = inputs.indexOf(current);\n if (index === -1) return;\n inputs[index + 1]?.focus();\n};\n\ninterface IDataTableEditableCellProps<T extends IDataTableRow> {\n column: IDataTableColumnConfig<T>;\n row: T;\n /** TanStack row id: present only inside a spreadsheet-enabled table. */\n gridRowId?: string;\n onCellChange: (row: T, columnId: string, value: unknown) => void;\n /** When true, pressing Enter moves focus to the next editable input (DOM order). */\n keyboardNav?: boolean;\n /** When true, the cell shows the value as text until it is clicked. */\n clickToEdit?: boolean;\n}\n\n/**\n * Controlled inline editor for a single cell. Reads the current value from the\n * host-owned row via `column.editable.getValue` and reports every edit through\n * `onCellChange`; the host re-renders with the updated row. Validation is derived\n * from the current value on each render, so no local state is kept.\n *\n * With `clickToEdit`, the cell renders the value as text and only swaps in the editor\n * once clicked, no editor is mounted before that.\n */\nconst DataTableEditableCell = <T extends IDataTableRow>({\n column,\n row,\n gridRowId,\n onCellChange,\n keyboardNav = false,\n clickToEdit = false,\n}: IDataTableEditableCellProps<T>) => {\n const { t, i18n } = useTranslation('ui');\n const hoverCapable = useHoverCapable();\n const cellEditing = useDataTableCellEditing();\n const gridStore = useGridStoreApi();\n const gridCellState = useGridCellState(gridRowId ?? '', column.id);\n const cellKey = buildEditableCellKey(row.id, column.id);\n\n // Inside a spreadsheet-enabled table the grid store owns the editing state, so\n // the legacy click-to-edit context and its document listener stay out of the way.\n const isGridEditModeOn = useGridEditMode(gridStore);\n const hasGrid = !!gridStore && !!gridRowId;\n const isGridCell = hasGrid && isGridEditModeOn;\n const isEditing = isGridCell\n ? (gridCellState & GRID_CELL_EDITING) !== 0\n : clickToEdit\n ? cellEditing?.editingCellKey === cellKey\n : true;\n const showsReadView = isGridCell || clickToEdit;\n\n const { containerRef, handleKeyDown } = useInlineEditor(\n Boolean(!isGridCell && clickToEdit && isEditing),\n () => cellEditing?.stopEditing(),\n (direction) => cellEditing?.navigate(cellKey, direction),\n );\n\n const editable = column.editable;\n const valueBeforeEditRef = useRef<string | number | Array<string> | null>(null);\n const wasEditingRef = useRef(false);\n\n // The cell is controlled by the value the host ALREADY parsed, so a half-typed\n // decimal is erased on the same keystroke: `1,` parses to the number 1 and comes\n // back as `\"1\"`, taking with it the separator just typed. The draft holds the exact\n // text while the field is focused; the host still receives every keystroke and\n // remains the source of truth.\n const [draftText, setDraftText] = useState<string | null>(null);\n\n useEffect(() => {\n if (!isEditing) setDraftText(null);\n }, [isEditing]);\n\n // Escape restores what the cell held when the editor opened. The editing model is\n // controlled (every keystroke already reached the host), so cancelling means\n // writing the snapshot back, not dropping local state.\n useEffect(() => {\n if (!isGridCell || !editable) return;\n\n if (isEditing) {\n if (wasEditingRef.current) return;\n wasEditingRef.current = true;\n valueBeforeEditRef.current = editable.getValue(row);\n\n // The key that opened the editor IS the new value, so the first keystroke is not lost.\n const typedSeed = gridStore?.getState().editingSeed;\n if (typedSeed == null) return;\n setDraftText(typedSeed);\n onCellChange(row, column.id, typedSeed);\n return;\n }\n\n if (!wasEditingRef.current) return;\n wasEditingRef.current = false;\n\n if (gridStore?.getState().editingOutcome !== 'cancel') return;\n if (isSameEditableValue(editable.getValue(row), valueBeforeEditRef.current)) return;\n\n onCellChange(row, column.id, valueBeforeEditRef.current);\n }, [isEditing, isGridCell, editable, gridStore, onCellChange, row, column.id]);\n\n if (!editable) return null;\n\n const rawValue = editable.getValue(row);\n const disabled = editable.disabled?.(row) ?? false;\n const error = editable.validate?.(rawValue, row) ?? null;\n const arrayValue = Array.isArray(rawValue) ? rawValue.map(String) : [];\n const stringValue = rawValue == null || Array.isArray(rawValue) ? '' : String(rawValue);\n\n // `String()` on a number always writes a dot, so a pt-BR user would get back a\n // separator that is not theirs. Hosts that keep the raw text (the edit buffer) are\n // untouched: only an actual number reaches this branch.\n const decimalValue =\n editable.type === 'number' && typeof rawValue === 'number'\n ? stringValue.replace('.', resolveDecimalSeparator(i18n.language))\n : stringValue;\n\n const renderEditor = () => {\n if (editable.renderEditor) {\n return editable.renderEditor({\n value: rawValue,\n onChange: (nextValue) => onCellChange(row, column.id, nextValue),\n disabled,\n label: column.header,\n });\n }\n\n if (editable.type === 'multiselect') {\n return (\n <ControlledSelect\n className=\"w-full [&_.controlled-select-container]:!min-w-0 [&_.controlled-select-input]:!min-w-0\"\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={arrayValue}\n onChangeHook={(selected) => onCellChange(row, column.id, Array.isArray(selected) ? selected : [selected])}\n multiple\n searchable\n clearable\n disabled={disabled}\n error={!!error}\n />\n );\n }\n\n if (editable.type === 'select') {\n return (\n // `ControlledSelect` ships hard min-widths (`min-w-52` on the field and the MT\n // container's `min-w-[200px]`) that overflow narrow table cells and spill into\n // the neighbouring column; force both to shrink so the editor fits its column\n // like the text/number editors do.\n <ControlledSelect\n className=\"w-full [&_.controlled-select-container]:!min-w-0 [&_.controlled-select-input]:!min-w-0\"\n options={Array.isArray(editable.options) ? editable.options : []}\n selectedValue={stringValue}\n onChangeHook={(selected) => onCellChange(row, column.id, selected)}\n multiple={false}\n searchable\n clearable\n disabled={disabled}\n error={!!error}\n />\n );\n }\n\n if (editable.type === 'date') {\n const parsed = stringValue ? new Date(stringValue) : undefined;\n const startValue = parsed && !Number.isNaN(parsed.getTime()) ? parsed : undefined;\n\n return (\n <DatePicker\n startValue={startValue}\n onUpdate={(value) => onCellChange(row, column.id, value ?? null)}\n disabled={disabled}\n />\n );\n }\n\n // `number` renders a text input with a decimal input mode rather than\n // `type=\"number\"`, so pt-BR comma decimals are not blocked by the browser; the\n // host parses the raw string it receives in `onCellChange`.\n return (\n <ControlledInput\n value={draftText ?? decimalValue}\n onChange={(event) => {\n setDraftText(event.target.value);\n onCellChange(row, column.id, event.target.value);\n }}\n onBlur={() => setDraftText(null)}\n onKeyDown={\n keyboardNav && !isGridCell\n ? (event) => {\n if (event.key === 'Enter') {\n event.preventDefault();\n focusNextEditableInput(event.currentTarget);\n }\n }\n : undefined\n }\n inputMode={editable.type === 'number' ? 'decimal' : undefined}\n disabled={disabled}\n error={!!error}\n errorMessage={error ?? undefined}\n aria-label={column.header}\n {...{ [EDITABLE_INPUT_ATTR]: '' }}\n // The Material-Tailwind input container ships a hard `min-w-[200px]` that\n // overflows narrow table cells; force it to shrink so the editor fits its\n // column (the cell padding then provides the gap between adjacent inputs).\n containerClassName=\"min-w-0\"\n />\n );\n };\n\n if (!hasGrid && (!showsReadView || !cellEditing)) return renderEditor();\n\n const resolveOptionLabel = (optionValue: string): string =>\n (editable.options ?? []).find((option) => String(option.value) === optionValue)?.label ?? optionValue;\n\n const resolveDisplayText = (): string => {\n if (editable.displayValue) return editable.displayValue(row);\n if (editable.type === 'multiselect') return arrayValue.map(resolveOptionLabel).join(', ');\n if (stringValue === '') return '';\n if (editable.type === 'select') return resolveOptionLabel(stringValue);\n if (editable.type === 'date') return formatIsoDate(stringValue);\n return decimalValue;\n };\n\n const displayText = resolveDisplayText();\n\n // Uma coluna com renderização própria (chip de etapa, status, thumbnail) mantém\n // esse visual no modo de leitura: só o editor aberto troca a célula por um input.\n const displayContent =\n !editable.displayValue && column.render ? (\n column.render(row)\n ) : (\n <span className={classNames('min-w-0 flex-1 truncate', displayText === '' && 'text-foreground-faint')}>\n {displayText === '' ? '-' : displayText}\n </span>\n );\n\n // Com o grid montado mas o modo de edição desligado, a célula é só o valor: sem\n // lápis, sem realce e sem clique, para não prometer uma edição que não abre.\n const showsEditAffordance = !disabled && (!hasGrid || isGridEditModeOn);\n\n const readCell = (\n <div\n className={classNames(\n // A mesma altura do editor (h-10), para a linha não pular quando a célula é aberta.\n 'flex min-h-10 w-full items-center gap-1 rounded-md border border-transparent px-2 py-1.5 text-sm',\n disabled && 'text-foreground-faint',\n !disabled && !showsEditAffordance && 'text-foreground',\n showsEditAffordance &&\n 'group cursor-pointer text-foreground transition-colors hover:border-brand-border hover:bg-brand-soft/50',\n error && 'border-danger-border',\n )}\n >\n <div className=\"min-w-0 flex-1 truncate\">{displayContent}</div>\n {showsEditAffordance && (\n // On touch there is no hover, so a hover-only pencil means the cell never\n // advertises that it can be edited. Keep it visible (dimmed) there instead.\n <MaterialSymbol\n icon=\"edit\"\n size={13}\n className={classNames(\n 'shrink-0 text-brand-emphasis transition-opacity',\n hoverCapable ? 'opacity-0 group-hover:opacity-100' : 'opacity-60',\n )}\n />\n )}\n </div>\n );\n\n // Fora do modo de edição a tabela é de leitura: nenhum editor fica montado.\n if (hasGrid && !isGridEditModeOn) return readCell;\n\n // The `<td>` owns the gestures that open the editor: a click, Enter, F2 or typing.\n if (isGridCell) return isEditing ? renderEditor() : readCell;\n\n if (!cellEditing) return renderEditor();\n\n return (\n <div ref={containerRef} onKeyDown={handleKeyDown} {...{ [EDITABLE_CELL_ATTR]: cellKey }}>\n {isEditing ? (\n renderEditor()\n ) : disabled ? (\n readCell\n ) : (\n <ControlledTooltip content={t('dataTable.editCell')}>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => cellEditing.startEditing(cellKey)}\n onKeyDown={(event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n cellEditing.startEditing(cellKey);\n }}\n className=\"rounded-md focus:outline-none focus-visible:ring-1 focus-visible:ring-brand-border\"\n >\n {readCell}\n </div>\n </ControlledTooltip>\n )}\n </div>\n );\n};\n\nexport default DataTableEditableCell;\n"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ import { IGridCellCoord } from './gridTypes';
5
5
  export interface IGridCellRegistry {
6
6
  register: (cellKey: string, element: HTMLElement | null) => void;
7
7
  focusCell: (coord: IGridCellCoord) => void;
8
+ /** Whether a click on this cell opens its editor. */
9
+ canEditCell: (coord: IGridCellCoord) => boolean;
8
10
  /** Hands a finished fill drag to the host table, which owns the row data. */
9
11
  commitFill: (result: IGridFillResult) => void;
10
12
  }
@@ -19,6 +21,7 @@ export declare const useGridCellRegistry: () => IGridCellRegistry | null;
19
21
  export declare const useGridCellState: (rowId: string, columnId: string) => number;
20
22
  interface IGridProviderProps {
21
23
  store: StoreApi<IGridState>;
24
+ canEditCell: (coord: IGridCellCoord) => boolean;
22
25
  onFillCommit?: (result: IGridFillResult) => void;
23
26
  children: React.ReactNode;
24
27
  }
@@ -19,6 +19,15 @@ const useGridCellState = (rowId, columnId) => {
19
19
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
20
20
  };
21
21
  const EDITOR_FOCUS_SELECTOR = 'input, textarea, select, [contenteditable="true"], button, [tabindex]';
22
+ const isPickerTrigger = (element) => element instanceof HTMLInputElement && element.readOnly;
23
+ const isTextField = (element) => element instanceof HTMLInputElement && !element.readOnly || element instanceof HTMLTextAreaElement;
24
+ const placeCaretAtEnd = (field) => {
25
+ try {
26
+ const end = field.value.length;
27
+ field.setSelectionRange(end, end);
28
+ } catch {
29
+ }
30
+ };
22
31
  const GridEditingFocus = ({
23
32
  store,
24
33
  elements
@@ -32,17 +41,30 @@ const GridEditingFocus = ({
32
41
  );
33
42
  useEffect(() => {
34
43
  if (!editingKey) return;
35
- elements.get(editingKey)?.querySelector(EDITOR_FOCUS_SELECTOR)?.focus();
36
- }, [editingKey, elements]);
44
+ const editor = elements.get(editingKey)?.querySelector(EDITOR_FOCUS_SELECTOR);
45
+ if (!editor) return;
46
+ editor.focus({ preventScroll: true });
47
+ const { editingEntry, editingSeed } = store.getState();
48
+ if (isPickerTrigger(editor)) {
49
+ if (editingEntry !== "keyboard") editor.click();
50
+ return;
51
+ }
52
+ if (!isTextField(editor)) return;
53
+ if (editingEntry === "keyboard" && editingSeed == null) editor.select();
54
+ else placeCaretAtEnd(editor);
55
+ }, [editingKey, elements, store]);
37
56
  return null;
38
57
  };
39
- const GridProvider = ({ store, onFillCommit, children }) => {
58
+ const GridProvider = ({ store, canEditCell, onFillCommit, children }) => {
40
59
  const elementsRef = useRef(/* @__PURE__ */ new Map());
41
60
  const onFillCommitRef = useRef(onFillCommit);
42
61
  onFillCommitRef.current = onFillCommit;
62
+ const canEditCellRef = useRef(canEditCell);
63
+ canEditCellRef.current = canEditCell;
43
64
  const registry = useMemo(
44
65
  () => ({
45
66
  commitFill: (result) => onFillCommitRef.current?.(result),
67
+ canEditCell: (coord) => canEditCellRef.current(coord),
46
68
  register: (cellKey, element) => {
47
69
  if (element) elementsRef.current.set(cellKey, element);
48
70
  else elementsRef.current.delete(cellKey);
@@ -1 +1 @@
1
- {"version":3,"file":"GridProvider.js","sources":["../../../../../src/components/UI/DataTable/grid/GridProvider.tsx"],"sourcesContent":["import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { type StoreApi } from 'zustand/vanilla';\n\nimport { type IGridFillResult, type IGridState, selectGridCellState } from './gridStore';\nimport { buildGridCellKey, type IGridCellCoord } from './gridTypes';\n\nexport interface IGridCellRegistry {\n register: (cellKey: string, element: HTMLElement | null) => void;\n focusCell: (coord: IGridCellCoord) => void;\n /** Hands a finished fill drag to the host table, which owns the row data. */\n commitFill: (result: IGridFillResult) => void;\n}\n\nconst GridStoreContext = createContext<StoreApi<IGridState> | null>(null);\nconst GridCellRegistryContext = createContext<IGridCellRegistry | null>(null);\n\n/** The grid store of the closest spreadsheet-enabled table, or `null` outside one. */\nexport const useGridStoreApi = (): StoreApi<IGridState> | null => useContext(GridStoreContext);\n\nexport const useGridCellRegistry = (): IGridCellRegistry | null => useContext(GridCellRegistryContext);\n\n/**\n * Per-cell subscription to the grid state, as a bitmask. Reading a primitive\n * through `useSyncExternalStore` means moving the active cell re-renders the two\n * cells that changed, never the table.\n */\nexport const useGridCellState = (rowId: string, columnId: string): number => {\n const store = useGridStoreApi();\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => (store ? store.subscribe(onStoreChange) : () => undefined),\n [store],\n );\n\n const getSnapshot = useCallback(\n () => (store ? selectGridCellState(store.getState(), rowId, columnId) : 0),\n [store, rowId, columnId],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\nconst EDITOR_FOCUS_SELECTOR = 'input, textarea, select, [contenteditable=\"true\"], button, [tabindex]';\n\n/**\n * Moves the focus into the editor once React has actually mounted it. The store\n * subscription cannot do this: it runs during the state update, before the cell\n * has swapped its read view for the editor.\n */\nconst GridEditingFocus: React.FC<{ store: StoreApi<IGridState>; elements: Map<string, HTMLElement> }> = ({\n store,\n elements,\n}) => {\n const editingKey = useSyncExternalStore(\n useCallback((onStoreChange: () => void) => store.subscribe(onStoreChange), [store]),\n () => {\n const editing = store.getState().editingCell;\n return editing ? buildGridCellKey(editing.rowId, editing.columnId) : null;\n },\n );\n\n useEffect(() => {\n if (!editingKey) return;\n elements.get(editingKey)?.querySelector<HTMLElement>(EDITOR_FOCUS_SELECTOR)?.focus();\n }, [editingKey, elements]);\n\n return null;\n};\n\ninterface IGridProviderProps {\n store: StoreApi<IGridState>;\n onFillCommit?: (result: IGridFillResult) => void;\n children: React.ReactNode;\n}\n\n/**\n * Owns the DOM side of the grid: the cell element registry and the focus/scroll\n * effect. It subscribes imperatively, so following the active cell costs no\n * render of its own.\n */\nconst GridProvider: React.FC<IGridProviderProps> = ({ store, onFillCommit, children }) => {\n const elementsRef = useRef(new Map<string, HTMLElement>());\n\n // Read through a ref so the registry identity never changes: it reaches every\n // cell, and a new object there would re-render the whole body.\n const onFillCommitRef = useRef(onFillCommit);\n onFillCommitRef.current = onFillCommit;\n\n const registry = useMemo<IGridCellRegistry>(\n () => ({\n commitFill: (result) => onFillCommitRef.current?.(result),\n register: (cellKey, element) => {\n if (element) elementsRef.current.set(cellKey, element);\n else elementsRef.current.delete(cellKey);\n },\n focusCell: (coord) => {\n const element = elementsRef.current.get(buildGridCellKey(coord.rowId, coord.columnId));\n if (!element) return;\n element.focus({ preventScroll: true });\n element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n },\n }),\n [],\n );\n\n useEffect(\n () =>\n store.subscribe((state, previous) => {\n const activeChanged =\n state.activeCell?.rowId !== previous.activeCell?.rowId ||\n state.activeCell?.columnId !== previous.activeCell?.columnId;\n const editingEnded = !!previous.editingCell && !state.editingCell;\n\n if (!activeChanged && !editingEnded) return;\n if (state.editingCell) return;\n\n const coord = state.activeCell;\n if (!coord) return;\n\n const element = elementsRef.current.get(buildGridCellKey(coord.rowId, coord.columnId));\n if (!element) return;\n\n // Leaving edit mode has to claim the focus BEFORE the editor unmounts,\n // otherwise the browser drops it on the body and the arrows stop working.\n if (editingEnded || !element.contains(document.activeElement)) element.focus({ preventScroll: true });\n\n element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }),\n [store],\n );\n\n return (\n <GridStoreContext.Provider value={store}>\n <GridCellRegistryContext.Provider value={registry}>\n <GridEditingFocus store={store} elements={elementsRef.current} />\n {children}\n </GridCellRegistryContext.Provider>\n </GridStoreContext.Provider>\n );\n};\n\nexport default GridProvider;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GridProvider.js","sources":["../../../../../src/components/UI/DataTable/grid/GridProvider.tsx"],"sourcesContent":["import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { type StoreApi } from 'zustand/vanilla';\n\nimport { type IGridFillResult, type IGridState, selectGridCellState } from './gridStore';\nimport { buildGridCellKey, type IGridCellCoord } from './gridTypes';\n\nexport interface IGridCellRegistry {\n register: (cellKey: string, element: HTMLElement | null) => void;\n focusCell: (coord: IGridCellCoord) => void;\n /** Whether a click on this cell opens its editor. */\n canEditCell: (coord: IGridCellCoord) => boolean;\n /** Hands a finished fill drag to the host table, which owns the row data. */\n commitFill: (result: IGridFillResult) => void;\n}\n\nconst GridStoreContext = createContext<StoreApi<IGridState> | null>(null);\nconst GridCellRegistryContext = createContext<IGridCellRegistry | null>(null);\n\n/** The grid store of the closest spreadsheet-enabled table, or `null` outside one. */\nexport const useGridStoreApi = (): StoreApi<IGridState> | null => useContext(GridStoreContext);\n\nexport const useGridCellRegistry = (): IGridCellRegistry | null => useContext(GridCellRegistryContext);\n\n/**\n * Per-cell subscription to the grid state, as a bitmask. Reading a primitive\n * through `useSyncExternalStore` means moving the active cell re-renders the two\n * cells that changed, never the table.\n */\nexport const useGridCellState = (rowId: string, columnId: string): number => {\n const store = useGridStoreApi();\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => (store ? store.subscribe(onStoreChange) : () => undefined),\n [store],\n );\n\n const getSnapshot = useCallback(\n () => (store ? selectGridCellState(store.getState(), rowId, columnId) : 0),\n [store, rowId, columnId],\n );\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n};\n\nconst EDITOR_FOCUS_SELECTOR = 'input, textarea, select, [contenteditable=\"true\"], button, [tabindex]';\n\n/** Select and date editors render a read-only input that opens their popup on click. */\nconst isPickerTrigger = (element: HTMLElement): boolean => element instanceof HTMLInputElement && element.readOnly;\n\nconst isTextField = (element: HTMLElement): element is HTMLInputElement | HTMLTextAreaElement =>\n (element instanceof HTMLInputElement && !element.readOnly) || element instanceof HTMLTextAreaElement;\n\nconst placeCaretAtEnd = (field: HTMLInputElement | HTMLTextAreaElement): void => {\n // `setSelectionRange` throws on input types without a caret (email, number).\n try {\n const end = field.value.length;\n field.setSelectionRange(end, end);\n } catch {\n // The browser already put the caret somewhere usable.\n }\n};\n\n/**\n * Moves the focus into the editor once React has actually mounted it. The store\n * subscription cannot do this: it runs during the state update, before the cell\n * has swapped its read view for the editor.\n */\nconst GridEditingFocus: React.FC<{ store: StoreApi<IGridState>; elements: Map<string, HTMLElement> }> = ({\n store,\n elements,\n}) => {\n const editingKey = useSyncExternalStore(\n useCallback((onStoreChange: () => void) => store.subscribe(onStoreChange), [store]),\n () => {\n const editing = store.getState().editingCell;\n return editing ? buildGridCellKey(editing.rowId, editing.columnId) : null;\n },\n );\n\n useEffect(() => {\n if (!editingKey) return;\n\n const editor = elements.get(editingKey)?.querySelector<HTMLElement>(EDITOR_FOCUS_SELECTOR);\n if (!editor) return;\n\n editor.focus({ preventScroll: true });\n\n const { editingEntry, editingSeed } = store.getState();\n\n if (isPickerTrigger(editor)) {\n if (editingEntry !== 'keyboard') editor.click();\n return;\n }\n\n if (!isTextField(editor)) return;\n\n if (editingEntry === 'keyboard' && editingSeed == null) editor.select();\n else placeCaretAtEnd(editor);\n }, [editingKey, elements, store]);\n\n return null;\n};\n\ninterface IGridProviderProps {\n store: StoreApi<IGridState>;\n canEditCell: (coord: IGridCellCoord) => boolean;\n onFillCommit?: (result: IGridFillResult) => void;\n children: React.ReactNode;\n}\n\n/**\n * Owns the DOM side of the grid: the cell element registry and the focus/scroll\n * effect. It subscribes imperatively, so following the active cell costs no\n * render of its own.\n */\nconst GridProvider: React.FC<IGridProviderProps> = ({ store, canEditCell, onFillCommit, children }) => {\n const elementsRef = useRef(new Map<string, HTMLElement>());\n\n // Read through a ref so the registry identity never changes: it reaches every\n // cell, and a new object there would re-render the whole body.\n const onFillCommitRef = useRef(onFillCommit);\n onFillCommitRef.current = onFillCommit;\n const canEditCellRef = useRef(canEditCell);\n canEditCellRef.current = canEditCell;\n\n const registry = useMemo<IGridCellRegistry>(\n () => ({\n commitFill: (result) => onFillCommitRef.current?.(result),\n canEditCell: (coord) => canEditCellRef.current(coord),\n register: (cellKey, element) => {\n if (element) elementsRef.current.set(cellKey, element);\n else elementsRef.current.delete(cellKey);\n },\n focusCell: (coord) => {\n const element = elementsRef.current.get(buildGridCellKey(coord.rowId, coord.columnId));\n if (!element) return;\n element.focus({ preventScroll: true });\n element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n },\n }),\n [],\n );\n\n useEffect(\n () =>\n store.subscribe((state, previous) => {\n const activeChanged =\n state.activeCell?.rowId !== previous.activeCell?.rowId ||\n state.activeCell?.columnId !== previous.activeCell?.columnId;\n const editingEnded = !!previous.editingCell && !state.editingCell;\n\n if (!activeChanged && !editingEnded) return;\n if (state.editingCell) return;\n\n const coord = state.activeCell;\n if (!coord) return;\n\n const element = elementsRef.current.get(buildGridCellKey(coord.rowId, coord.columnId));\n if (!element) return;\n\n // Leaving edit mode has to claim the focus BEFORE the editor unmounts,\n // otherwise the browser drops it on the body and the arrows stop working.\n if (editingEnded || !element.contains(document.activeElement)) element.focus({ preventScroll: true });\n\n element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }),\n [store],\n );\n\n return (\n <GridStoreContext.Provider value={store}>\n <GridCellRegistryContext.Provider value={registry}>\n <GridEditingFocus store={store} elements={elementsRef.current} />\n {children}\n </GridCellRegistryContext.Provider>\n </GridStoreContext.Provider>\n );\n};\n\nexport default GridProvider;\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { StoreApi } from 'zustand/vanilla';
2
- import { IGridCellCoord, TGridSelectionMode } from './gridTypes';
2
+ import { IGridCellCoord, IGridStartEditingOptions, TGridEditingEntry, TGridSelectionMode } from './gridTypes';
3
3
  export interface IGridMatrix {
4
4
  rowIds: Array<string>;
5
5
  columnIds: Array<string>;
@@ -32,6 +32,9 @@ export interface IGridState {
32
32
  selectionAnchor: IGridCellCoord | null;
33
33
  selectionRect: IGridSelectionRect | null;
34
34
  editingCell: IGridCellCoord | null;
35
+ editingEntry: TGridEditingEntry | null;
36
+ /** Typed text that replaces the value of the cell being opened; `null` keeps the value. */
37
+ editingSeed: string | null;
35
38
  /** How the last edit ended, so the cell knows whether to restore its value. */
36
39
  editingOutcome: TGridEditingOutcome | null;
37
40
  /** Selection snapshot taken when the fill drag started. */
@@ -51,7 +54,12 @@ export interface IGridState {
51
54
  moveActiveToEdge: (rowDelta: number, columnDelta: number, options?: {
52
55
  extend?: boolean;
53
56
  }) => void;
54
- startEditing: (coord?: IGridCellCoord) => void;
57
+ /**
58
+ * Tab traversal over the cells that accept an editor, opening the one it lands on.
59
+ * Returns `false` past the first/last editable cell so the focus can leave the grid.
60
+ */
61
+ moveToEditableCell: (direction: 1 | -1, canEditCell: (coord: IGridCellCoord) => boolean) => boolean;
62
+ startEditing: (coord?: IGridCellCoord, options?: IGridStartEditingOptions) => void;
55
63
  stopEditing: (outcome?: TGridEditingOutcome) => void;
56
64
  clearSelection: () => void;
57
65
  startFill: () => void;
@@ -50,6 +50,8 @@ const createGridStore = (selectionMode = "cell", initialEditMode = true) => crea
50
50
  selectionAnchor: null,
51
51
  selectionRect: null,
52
52
  editingCell: null,
53
+ editingEntry: null,
54
+ editingSeed: null,
53
55
  editingOutcome: null,
54
56
  fillSourceRect: null,
55
57
  fillTargetRect: null,
@@ -141,11 +143,36 @@ const createGridStore = (selectionMode = "cell", initialEditMode = true) => crea
141
143
  const next = coordAt(matrix, nextRow, nextColumn);
142
144
  if (next) focusCoord(next, options?.extend ?? false);
143
145
  },
144
- startEditing: (coord) => {
145
- const { activeCell, editingCell } = get();
146
+ moveToEditableCell: (direction, canEditCell) => {
147
+ const { matrix, activeCell } = get();
148
+ const columnCount = matrix.columnIds.length;
149
+ const cellCount = matrix.rowIds.length * columnCount;
150
+ if (cellCount === 0) return false;
151
+ const rowIndex = activeCell ? matrix.rowIndexById.get(activeCell.rowId) : void 0;
152
+ const columnIndex = activeCell ? matrix.columnIndexById.get(activeCell.columnId) : void 0;
153
+ const currentPosition = rowIndex != null && columnIndex != null ? rowIndex * columnCount + columnIndex : -1;
154
+ for (let position = currentPosition + direction; position >= 0 && position < cellCount; position += direction) {
155
+ const candidate = coordAt(matrix, Math.floor(position / columnCount), position % columnCount);
156
+ if (!candidate || !canEditCell(candidate)) continue;
157
+ focusCoord(candidate, false);
158
+ get().startEditing(candidate, { entry: "keyboard" });
159
+ return true;
160
+ }
161
+ return false;
162
+ },
163
+ startEditing: (coord, options) => {
164
+ const { activeCell, editingCell, matrix } = get();
146
165
  const target = coord ?? activeCell;
147
166
  if (!target || isSameCoord(editingCell, target)) return;
148
- set({ activeCell: target, editingCell: target, editingOutcome: null });
167
+ set({
168
+ activeCell: target,
169
+ selectionAnchor: target,
170
+ selectionRect: buildRect(matrix, target, target),
171
+ editingCell: target,
172
+ editingEntry: options?.entry ?? "keyboard",
173
+ editingSeed: options?.seed ?? null,
174
+ editingOutcome: null
175
+ });
149
176
  },
150
177
  stopEditing: (outcome = "commit") => {
151
178
  if (!get().editingCell) return;
@@ -1 +1 @@
1
- {"version":3,"file":"gridStore.js","sources":["../../../../../src/components/UI/DataTable/grid/gridStore.ts"],"sourcesContent":["import { createStore, type StoreApi } from 'zustand/vanilla';\n\nimport {\n buildGridCellKey,\n GRID_CELL_ACTIVE,\n GRID_CELL_EDITING,\n GRID_CELL_FILL_HANDLE,\n GRID_CELL_FILL_PREVIEW,\n GRID_CELL_SAVE_ERROR,\n GRID_CELL_SAVING,\n GRID_CELL_SELECTED,\n GRID_CELL_TABBABLE,\n type IGridCellCoord,\n type TGridSelectionMode,\n} from './gridTypes';\n\nexport interface IGridMatrix {\n rowIds: Array<string>;\n columnIds: Array<string>;\n rowIndexById: Map<string, number>;\n columnIndexById: Map<string, number>;\n}\n\nexport interface IGridSelectionRect {\n rowStart: number;\n rowEnd: number;\n columnStart: number;\n columnEnd: number;\n}\n\nexport interface IGridMoveOptions {\n /** Keeps the selection anchor and stretches the rectangle to the new cell. */\n extend?: boolean;\n /** Tab-style traversal: past the last column continues on the next row. */\n wrap?: boolean;\n}\n\nexport type TGridEditingOutcome = 'commit' | 'cancel';\n\nexport interface IGridState {\n /**\n * Enquanto falso a tabela é uma tabela comum: nenhuma célula é focável, o\n * clique na linha segue sendo do host e nada do grid aparece.\n */\n isEditMode: boolean;\n setEditMode: (isEditMode: boolean) => void;\n selectionMode: TGridSelectionMode;\n matrix: IGridMatrix;\n activeCell: IGridCellCoord | null;\n selectionAnchor: IGridCellCoord | null;\n selectionRect: IGridSelectionRect | null;\n editingCell: IGridCellCoord | null;\n /** How the last edit ended, so the cell knows whether to restore its value. */\n editingOutcome: TGridEditingOutcome | null;\n /** Selection snapshot taken when the fill drag started. */\n fillSourceRect: IGridSelectionRect | null;\n /** Cells the running fill drag would write. Null while the pointer is inside the source. */\n fillTargetRect: IGridSelectionRect | null;\n /** Per-cell persistence state fed by the host's commit queue. */\n cellStatuses: ReadonlyMap<string, string>;\n setCellStatuses: (statuses: ReadonlyMap<string, string>) => void;\n setSelectionMode: (mode: TGridSelectionMode) => void;\n setMatrix: (rowIds: Array<string>, columnIds: Array<string>) => void;\n setActiveCell: (coord: IGridCellCoord, options?: { extend?: boolean }) => void;\n /** Returns whether the active cell actually moved, so the host can let Tab leave the grid. */\n moveActiveCell: (rowDelta: number, columnDelta: number, options?: IGridMoveOptions) => boolean;\n moveActiveToEdge: (rowDelta: number, columnDelta: number, options?: { extend?: boolean }) => void;\n startEditing: (coord?: IGridCellCoord) => void;\n stopEditing: (outcome?: TGridEditingOutcome) => void;\n clearSelection: () => void;\n startFill: () => void;\n updateFillTarget: (coord: IGridCellCoord) => void;\n /** Clears the drag state and hands back what should be written, if anything. */\n endFill: () => IGridFillResult | null;\n cancelFill: () => void;\n}\n\nexport interface IGridFillResult {\n sourceRect: IGridSelectionRect;\n targetRect: IGridSelectionRect;\n matrix: IGridMatrix;\n}\n\nconst EMPTY_MATRIX: IGridMatrix = {\n rowIds: [],\n columnIds: [],\n rowIndexById: new Map(),\n columnIndexById: new Map(),\n};\n\nconst sameIds = (current: Array<string>, next: Array<string>): boolean =>\n current.length === next.length && current.every((id, index) => id === next[index]);\n\nconst buildIndex = (ids: Array<string>): Map<string, number> => new Map(ids.map((id, index) => [id, index]));\n\nconst buildRect = (\n matrix: IGridMatrix,\n anchor: IGridCellCoord | null,\n focus: IGridCellCoord | null,\n): IGridSelectionRect | null => {\n if (!anchor || !focus) return null;\n\n const anchorRow = matrix.rowIndexById.get(anchor.rowId);\n const anchorColumn = matrix.columnIndexById.get(anchor.columnId);\n const focusRow = matrix.rowIndexById.get(focus.rowId);\n const focusColumn = matrix.columnIndexById.get(focus.columnId);\n\n if (anchorRow == null || anchorColumn == null || focusRow == null || focusColumn == null) return null;\n\n return {\n rowStart: Math.min(anchorRow, focusRow),\n rowEnd: Math.max(anchorRow, focusRow),\n columnStart: Math.min(anchorColumn, focusColumn),\n columnEnd: Math.max(anchorColumn, focusColumn),\n };\n};\n\nconst coordAt = (matrix: IGridMatrix, rowIndex: number, columnIndex: number): IGridCellCoord | null => {\n const rowId = matrix.rowIds[rowIndex];\n const columnId = matrix.columnIds[columnIndex];\n if (rowId == null || columnId == null) return null;\n\n return { rowId, columnId };\n};\n\nconst isSameCoord = (left: IGridCellCoord | null, right: IGridCellCoord | null): boolean =>\n left?.rowId === right?.rowId && left?.columnId === right?.columnId;\n\nexport const createGridStore = (\n selectionMode: TGridSelectionMode = 'cell',\n initialEditMode = true,\n): StoreApi<IGridState> =>\n createStore<IGridState>((set, get) => {\n const focusCoord = (coord: IGridCellCoord, extend: boolean) => {\n const { matrix, selectionAnchor, selectionMode: mode, editingCell } = get();\n const anchor = extend && mode === 'range' ? (selectionAnchor ?? coord) : coord;\n\n set({\n activeCell: coord,\n selectionAnchor: anchor,\n selectionRect: buildRect(matrix, anchor, coord),\n editingCell: null,\n // Moving away from an open editor keeps what was typed: only Escape discards it.\n editingOutcome: editingCell ? 'commit' : null,\n });\n };\n\n return {\n isEditMode: initialEditMode,\n selectionMode,\n matrix: EMPTY_MATRIX,\n activeCell: null,\n selectionAnchor: null,\n selectionRect: null,\n editingCell: null,\n editingOutcome: null,\n fillSourceRect: null,\n fillTargetRect: null,\n cellStatuses: new Map<string, string>(),\n\n setEditMode: (isEditMode) => {\n if (get().isEditMode === isEditMode) return;\n\n // Sair do modo devolve a tabela ao estado neutro: nada selecionado, nada\n // em edição, nenhum arraste pela metade.\n set({\n isEditMode,\n activeCell: null,\n selectionAnchor: null,\n selectionRect: null,\n editingCell: null,\n editingOutcome: null,\n fillSourceRect: null,\n fillTargetRect: null,\n });\n },\n\n setCellStatuses: (statuses) => {\n if (get().cellStatuses === statuses) return;\n set({ cellStatuses: statuses });\n },\n\n setSelectionMode: (mode) => {\n if (get().selectionMode === mode) return;\n set({ selectionMode: mode });\n },\n\n setMatrix: (rowIds, columnIds) => {\n const { matrix, activeCell, selectionAnchor, editingCell } = get();\n if (sameIds(matrix.rowIds, rowIds) && sameIds(matrix.columnIds, columnIds)) return;\n\n const nextMatrix: IGridMatrix = {\n rowIds,\n columnIds,\n rowIndexById: buildIndex(rowIds),\n columnIndexById: buildIndex(columnIds),\n };\n\n // A row or column that left the viewport (pagination, filter, column\n // hidden) takes its active/editing state with it, so the grid never\n // points at a cell the user cannot see.\n const stillExists = (coord: IGridCellCoord | null): coord is IGridCellCoord =>\n !!coord && nextMatrix.rowIndexById.has(coord.rowId) && nextMatrix.columnIndexById.has(coord.columnId);\n\n const nextActive = stillExists(activeCell) ? activeCell : null;\n const nextAnchor = stillExists(selectionAnchor) ? selectionAnchor : nextActive;\n\n set({\n matrix: nextMatrix,\n activeCell: nextActive,\n selectionAnchor: nextAnchor,\n selectionRect: buildRect(nextMatrix, nextAnchor, nextActive),\n editingCell: stillExists(editingCell) ? editingCell : null,\n });\n },\n\n setActiveCell: (coord, options) => {\n const { activeCell, editingCell } = get();\n const extend = options?.extend ?? false;\n\n // Um clique DENTRO da célula que já está sendo editada pertence ao editor,\n // não à navegação: reposicionar o cursor aqui fecharia o editor ainda no\n // mousedown, e um select nunca chegaria a abrir a lista.\n if (!extend && isSameCoord(editingCell, coord)) return;\n if (!extend && isSameCoord(activeCell, coord) && !editingCell) return;\n\n focusCoord(coord, extend);\n },\n\n moveActiveCell: (rowDelta, columnDelta, options) => {\n const { matrix, activeCell } = get();\n if (matrix.rowIds.length === 0 || matrix.columnIds.length === 0) return false;\n\n if (!activeCell) {\n const first = coordAt(matrix, 0, 0);\n if (first) focusCoord(first, false);\n return !!first;\n }\n\n const rowIndex = matrix.rowIndexById.get(activeCell.rowId);\n const columnIndex = matrix.columnIndexById.get(activeCell.columnId);\n if (rowIndex == null || columnIndex == null) return false;\n\n let nextRow = rowIndex + rowDelta;\n let nextColumn = columnIndex + columnDelta;\n\n if (options?.wrap && columnDelta !== 0) {\n if (nextColumn >= matrix.columnIds.length) {\n nextColumn = 0;\n nextRow += 1;\n } else if (nextColumn < 0) {\n nextColumn = matrix.columnIds.length - 1;\n nextRow -= 1;\n }\n }\n\n if (nextRow < 0 || nextRow >= matrix.rowIds.length) return false;\n if (nextColumn < 0 || nextColumn >= matrix.columnIds.length) return false;\n\n const next = coordAt(matrix, nextRow, nextColumn);\n if (!next) return false;\n\n focusCoord(next, options?.extend ?? false);\n return true;\n },\n\n moveActiveToEdge: (rowDelta, columnDelta, options) => {\n const { matrix, activeCell } = get();\n if (matrix.rowIds.length === 0 || matrix.columnIds.length === 0) return;\n\n const rowIndex = activeCell ? (matrix.rowIndexById.get(activeCell.rowId) ?? 0) : 0;\n const columnIndex = activeCell ? (matrix.columnIndexById.get(activeCell.columnId) ?? 0) : 0;\n\n const nextRow = rowDelta === 0 ? rowIndex : rowDelta > 0 ? matrix.rowIds.length - 1 : 0;\n const nextColumn = columnDelta === 0 ? columnIndex : columnDelta > 0 ? matrix.columnIds.length - 1 : 0;\n\n const next = coordAt(matrix, nextRow, nextColumn);\n if (next) focusCoord(next, options?.extend ?? false);\n },\n\n startEditing: (coord) => {\n const { activeCell, editingCell } = get();\n const target = coord ?? activeCell;\n if (!target || isSameCoord(editingCell, target)) return;\n\n set({ activeCell: target, editingCell: target, editingOutcome: null });\n },\n\n stopEditing: (outcome = 'commit') => {\n if (!get().editingCell) return;\n set({ editingCell: null, editingOutcome: outcome });\n },\n\n clearSelection: () => {\n const { activeCell, matrix } = get();\n set({\n selectionAnchor: activeCell,\n selectionRect: buildRect(matrix, activeCell, activeCell),\n });\n },\n\n startFill: () => {\n const { selectionRect } = get();\n if (!selectionRect) return;\n set({ fillSourceRect: selectionRect, fillTargetRect: null, editingCell: null });\n },\n\n updateFillTarget: (coord) => {\n const { fillSourceRect, fillTargetRect, matrix } = get();\n if (!fillSourceRect) return;\n\n const rowIndex = matrix.rowIndexById.get(coord.rowId);\n if (rowIndex == null) return;\n\n // Vertical only for now. The rect shape is already 2D, so a horizontal\n // drag is a second branch here and nothing else in the layer changes.\n let nextTarget: IGridSelectionRect | null = null;\n if (rowIndex > fillSourceRect.rowEnd) {\n nextTarget = { ...fillSourceRect, rowStart: fillSourceRect.rowEnd + 1, rowEnd: rowIndex };\n } else if (rowIndex < fillSourceRect.rowStart) {\n nextTarget = { ...fillSourceRect, rowStart: rowIndex, rowEnd: fillSourceRect.rowStart - 1 };\n }\n\n const isSameTarget =\n nextTarget?.rowStart === fillTargetRect?.rowStart && nextTarget?.rowEnd === fillTargetRect?.rowEnd;\n if (isSameTarget) return;\n\n set({ fillTargetRect: nextTarget });\n },\n\n endFill: () => {\n const { fillSourceRect, fillTargetRect, matrix } = get();\n set({ fillSourceRect: null, fillTargetRect: null });\n if (!fillSourceRect || !fillTargetRect) return null;\n\n return { sourceRect: fillSourceRect, targetRect: fillTargetRect, matrix };\n },\n\n cancelFill: () => {\n if (!get().fillSourceRect) return;\n set({ fillSourceRect: null, fillTargetRect: null });\n },\n };\n });\n\n/**\n * Bitmask of what this cell currently is. A primitive keeps the per-cell\n * subscription cheap: `useSyncExternalStore` compares it by value, so a cell\n * only re-renders when its own state actually changes.\n */\nconst isInsideRect = (rect: IGridSelectionRect | null, rowIndex?: number, columnIndex?: number): boolean =>\n !!rect &&\n rowIndex != null &&\n columnIndex != null &&\n rowIndex >= rect.rowStart &&\n rowIndex <= rect.rowEnd &&\n columnIndex >= rect.columnStart &&\n columnIndex <= rect.columnEnd;\n\nexport const selectGridCellState = (state: IGridState, rowId: string, columnId: string): number => {\n if (!state.isEditMode) return 0;\n\n let mask = 0;\n\n if (isSameCoord(state.activeCell, { rowId, columnId })) mask |= GRID_CELL_ACTIVE;\n if (isSameCoord(state.editingCell, { rowId, columnId })) mask |= GRID_CELL_EDITING;\n\n const rowIndex = state.matrix.rowIndexById.get(rowId);\n const columnIndex = state.matrix.columnIndexById.get(columnId);\n\n const rect = state.selectionRect;\n if (isInsideRect(rect, rowIndex, columnIndex)) mask |= GRID_CELL_SELECTED;\n if (rect && rowIndex === rect.rowEnd && columnIndex === rect.columnEnd) mask |= GRID_CELL_FILL_HANDLE;\n if (isInsideRect(state.fillTargetRect, rowIndex, columnIndex)) mask |= GRID_CELL_FILL_PREVIEW;\n\n if (state.cellStatuses.size > 0) {\n const status = state.cellStatuses.get(buildGridCellKey(rowId, columnId));\n if (status === 'saving' || status === 'pending') mask |= GRID_CELL_SAVING;\n if (status === 'error') mask |= GRID_CELL_SAVE_ERROR;\n }\n\n // Roving tabindex needs an entry point before the first interaction: without\n // an active cell the top-left cell is the one Tab reaches from outside.\n if (!state.activeCell && state.matrix.rowIds[0] === rowId && state.matrix.columnIds[0] === columnId) {\n mask |= GRID_CELL_TABBABLE;\n }\n\n return mask;\n};\n"],"names":[],"mappings":";;AAmFA,MAAM,eAA4B;AAAA,EAChC,QAAQ,CAAA;AAAA,EACR,WAAW,CAAA;AAAA,EACX,kCAAkB,IAAA;AAAA,EAClB,qCAAqB,IAAA;AACvB;AAEA,MAAM,UAAU,CAAC,SAAwB,SACvC,QAAQ,WAAW,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,UAAU,OAAO,KAAK,KAAK,CAAC;AAEnF,MAAM,aAAa,CAAC,QAA4C,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC;AAE3G,MAAM,YAAY,CAChB,QACA,QACA,UAC8B;AAC9B,MAAI,CAAC,UAAU,CAAC,MAAO,QAAO;AAE9B,QAAM,YAAY,OAAO,aAAa,IAAI,OAAO,KAAK;AACtD,QAAM,eAAe,OAAO,gBAAgB,IAAI,OAAO,QAAQ;AAC/D,QAAM,WAAW,OAAO,aAAa,IAAI,MAAM,KAAK;AACpD,QAAM,cAAc,OAAO,gBAAgB,IAAI,MAAM,QAAQ;AAE7D,MAAI,aAAa,QAAQ,gBAAgB,QAAQ,YAAY,QAAQ,eAAe,KAAM,QAAO;AAEjG,SAAO;AAAA,IACL,UAAU,KAAK,IAAI,WAAW,QAAQ;AAAA,IACtC,QAAQ,KAAK,IAAI,WAAW,QAAQ;AAAA,IACpC,aAAa,KAAK,IAAI,cAAc,WAAW;AAAA,IAC/C,WAAW,KAAK,IAAI,cAAc,WAAW;AAAA,EAAA;AAEjD;AAEA,MAAM,UAAU,CAAC,QAAqB,UAAkB,gBAA+C;AACrG,QAAM,QAAQ,OAAO,OAAO,QAAQ;AACpC,QAAM,WAAW,OAAO,UAAU,WAAW;AAC7C,MAAI,SAAS,QAAQ,YAAY,KAAM,QAAO;AAE9C,SAAO,EAAE,OAAO,SAAA;AAClB;AAEA,MAAM,cAAc,CAAC,MAA6B,UAChD,MAAM,UAAU,OAAO,SAAS,MAAM,aAAa,OAAO;AAErD,MAAM,kBAAkB,CAC7B,gBAAoC,QACpC,kBAAkB,SAElB,YAAwB,CAAC,KAAK,QAAQ;AACpC,QAAM,aAAa,CAAC,OAAuB,WAAoB;AAC7D,UAAM,EAAE,QAAQ,iBAAiB,eAAe,MAAM,YAAA,IAAgB,IAAA;AACtE,UAAM,SAAS,UAAU,SAAS,UAAW,mBAAmB,QAAS;AAEzE,QAAI;AAAA,MACF,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,eAAe,UAAU,QAAQ,QAAQ,KAAK;AAAA,MAC9C,aAAa;AAAA;AAAA,MAEb,gBAAgB,cAAc,WAAW;AAAA,IAAA,CAC1C;AAAA,EACH;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,kCAAkB,IAAA;AAAA,IAElB,aAAa,CAAC,eAAe;AAC3B,UAAI,IAAA,EAAM,eAAe,WAAY;AAIrC,UAAI;AAAA,QACF;AAAA,QACA,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,gBAAgB;AAAA,QAChB,gBAAgB;AAAA,MAAA,CACjB;AAAA,IACH;AAAA,IAEA,iBAAiB,CAAC,aAAa;AAC7B,UAAI,IAAA,EAAM,iBAAiB,SAAU;AACrC,UAAI,EAAE,cAAc,UAAU;AAAA,IAChC;AAAA,IAEA,kBAAkB,CAAC,SAAS;AAC1B,UAAI,IAAA,EAAM,kBAAkB,KAAM;AAClC,UAAI,EAAE,eAAe,MAAM;AAAA,IAC7B;AAAA,IAEA,WAAW,CAAC,QAAQ,cAAc;AAChC,YAAM,EAAE,QAAQ,YAAY,iBAAiB,YAAA,IAAgB,IAAA;AAC7D,UAAI,QAAQ,OAAO,QAAQ,MAAM,KAAK,QAAQ,OAAO,WAAW,SAAS,EAAG;AAE5E,YAAM,aAA0B;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,cAAc,WAAW,MAAM;AAAA,QAC/B,iBAAiB,WAAW,SAAS;AAAA,MAAA;AAMvC,YAAM,cAAc,CAAC,UACnB,CAAC,CAAC,SAAS,WAAW,aAAa,IAAI,MAAM,KAAK,KAAK,WAAW,gBAAgB,IAAI,MAAM,QAAQ;AAEtG,YAAM,aAAa,YAAY,UAAU,IAAI,aAAa;AAC1D,YAAM,aAAa,YAAY,eAAe,IAAI,kBAAkB;AAEpE,UAAI;AAAA,QACF,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,iBAAiB;AAAA,QACjB,eAAe,UAAU,YAAY,YAAY,UAAU;AAAA,QAC3D,aAAa,YAAY,WAAW,IAAI,cAAc;AAAA,MAAA,CACvD;AAAA,IACH;AAAA,IAEA,eAAe,CAAC,OAAO,YAAY;AACjC,YAAM,EAAE,YAAY,YAAA,IAAgB,IAAA;AACpC,YAAM,SAAS,SAAS,UAAU;AAKlC,UAAI,CAAC,UAAU,YAAY,aAAa,KAAK,EAAG;AAChD,UAAI,CAAC,UAAU,YAAY,YAAY,KAAK,KAAK,CAAC,YAAa;AAE/D,iBAAW,OAAO,MAAM;AAAA,IAC1B;AAAA,IAEA,gBAAgB,CAAC,UAAU,aAAa,YAAY;AAClD,YAAM,EAAE,QAAQ,WAAA,IAAe,IAAA;AAC/B,UAAI,OAAO,OAAO,WAAW,KAAK,OAAO,UAAU,WAAW,EAAG,QAAO;AAExE,UAAI,CAAC,YAAY;AACf,cAAM,QAAQ,QAAQ,QAAQ,GAAG,CAAC;AAClC,YAAI,MAAO,YAAW,OAAO,KAAK;AAClC,eAAO,CAAC,CAAC;AAAA,MACX;AAEA,YAAM,WAAW,OAAO,aAAa,IAAI,WAAW,KAAK;AACzD,YAAM,cAAc,OAAO,gBAAgB,IAAI,WAAW,QAAQ;AAClE,UAAI,YAAY,QAAQ,eAAe,KAAM,QAAO;AAEpD,UAAI,UAAU,WAAW;AACzB,UAAI,aAAa,cAAc;AAE/B,UAAI,SAAS,QAAQ,gBAAgB,GAAG;AACtC,YAAI,cAAc,OAAO,UAAU,QAAQ;AACzC,uBAAa;AACb,qBAAW;AAAA,QACb,WAAW,aAAa,GAAG;AACzB,uBAAa,OAAO,UAAU,SAAS;AACvC,qBAAW;AAAA,QACb;AAAA,MACF;AAEA,UAAI,UAAU,KAAK,WAAW,OAAO,OAAO,OAAQ,QAAO;AAC3D,UAAI,aAAa,KAAK,cAAc,OAAO,UAAU,OAAQ,QAAO;AAEpE,YAAM,OAAO,QAAQ,QAAQ,SAAS,UAAU;AAChD,UAAI,CAAC,KAAM,QAAO;AAElB,iBAAW,MAAM,SAAS,UAAU,KAAK;AACzC,aAAO;AAAA,IACT;AAAA,IAEA,kBAAkB,CAAC,UAAU,aAAa,YAAY;AACpD,YAAM,EAAE,QAAQ,WAAA,IAAe,IAAA;AAC/B,UAAI,OAAO,OAAO,WAAW,KAAK,OAAO,UAAU,WAAW,EAAG;AAEjE,YAAM,WAAW,aAAc,OAAO,aAAa,IAAI,WAAW,KAAK,KAAK,IAAK;AACjF,YAAM,cAAc,aAAc,OAAO,gBAAgB,IAAI,WAAW,QAAQ,KAAK,IAAK;AAE1F,YAAM,UAAU,aAAa,IAAI,WAAW,WAAW,IAAI,OAAO,OAAO,SAAS,IAAI;AACtF,YAAM,aAAa,gBAAgB,IAAI,cAAc,cAAc,IAAI,OAAO,UAAU,SAAS,IAAI;AAErG,YAAM,OAAO,QAAQ,QAAQ,SAAS,UAAU;AAChD,UAAI,KAAM,YAAW,MAAM,SAAS,UAAU,KAAK;AAAA,IACrD;AAAA,IAEA,cAAc,CAAC,UAAU;AACvB,YAAM,EAAE,YAAY,YAAA,IAAgB,IAAA;AACpC,YAAM,SAAS,SAAS;AACxB,UAAI,CAAC,UAAU,YAAY,aAAa,MAAM,EAAG;AAEjD,UAAI,EAAE,YAAY,QAAQ,aAAa,QAAQ,gBAAgB,MAAM;AAAA,IACvE;AAAA,IAEA,aAAa,CAAC,UAAU,aAAa;AACnC,UAAI,CAAC,IAAA,EAAM,YAAa;AACxB,UAAI,EAAE,aAAa,MAAM,gBAAgB,SAAS;AAAA,IACpD;AAAA,IAEA,gBAAgB,MAAM;AACpB,YAAM,EAAE,YAAY,OAAA,IAAW,IAAA;AAC/B,UAAI;AAAA,QACF,iBAAiB;AAAA,QACjB,eAAe,UAAU,QAAQ,YAAY,UAAU;AAAA,MAAA,CACxD;AAAA,IACH;AAAA,IAEA,WAAW,MAAM;AACf,YAAM,EAAE,cAAA,IAAkB,IAAA;AAC1B,UAAI,CAAC,cAAe;AACpB,UAAI,EAAE,gBAAgB,eAAe,gBAAgB,MAAM,aAAa,MAAM;AAAA,IAChF;AAAA,IAEA,kBAAkB,CAAC,UAAU;AAC3B,YAAM,EAAE,gBAAgB,gBAAgB,OAAA,IAAW,IAAA;AACnD,UAAI,CAAC,eAAgB;AAErB,YAAM,WAAW,OAAO,aAAa,IAAI,MAAM,KAAK;AACpD,UAAI,YAAY,KAAM;AAItB,UAAI,aAAwC;AAC5C,UAAI,WAAW,eAAe,QAAQ;AACpC,qBAAa,EAAE,GAAG,gBAAgB,UAAU,eAAe,SAAS,GAAG,QAAQ,SAAA;AAAA,MACjF,WAAW,WAAW,eAAe,UAAU;AAC7C,qBAAa,EAAE,GAAG,gBAAgB,UAAU,UAAU,QAAQ,eAAe,WAAW,EAAA;AAAA,MAC1F;AAEA,YAAM,eACJ,YAAY,aAAa,gBAAgB,YAAY,YAAY,WAAW,gBAAgB;AAC9F,UAAI,aAAc;AAElB,UAAI,EAAE,gBAAgB,YAAY;AAAA,IACpC;AAAA,IAEA,SAAS,MAAM;AACb,YAAM,EAAE,gBAAgB,gBAAgB,OAAA,IAAW,IAAA;AACnD,UAAI,EAAE,gBAAgB,MAAM,gBAAgB,MAAM;AAClD,UAAI,CAAC,kBAAkB,CAAC,eAAgB,QAAO;AAE/C,aAAO,EAAE,YAAY,gBAAgB,YAAY,gBAAgB,OAAA;AAAA,IACnE;AAAA,IAEA,YAAY,MAAM;AAChB,UAAI,CAAC,IAAA,EAAM,eAAgB;AAC3B,UAAI,EAAE,gBAAgB,MAAM,gBAAgB,MAAM;AAAA,IACpD;AAAA,EAAA;AAEJ,CAAC;AAOH,MAAM,eAAe,CAAC,MAAiC,UAAmB,gBACxE,CAAC,CAAC,QACF,YAAY,QACZ,eAAe,QACf,YAAY,KAAK,YACjB,YAAY,KAAK,UACjB,eAAe,KAAK,eACpB,eAAe,KAAK;AAEf,MAAM,sBAAsB,CAAC,OAAmB,OAAe,aAA6B;AACjG,MAAI,CAAC,MAAM,WAAY,QAAO;AAE9B,MAAI,OAAO;AAEX,MAAI,YAAY,MAAM,YAAY,EAAE,OAAO,SAAA,CAAU,EAAG,SAAQ;AAChE,MAAI,YAAY,MAAM,aAAa,EAAE,OAAO,SAAA,CAAU,EAAG,SAAQ;AAEjE,QAAM,WAAW,MAAM,OAAO,aAAa,IAAI,KAAK;AACpD,QAAM,cAAc,MAAM,OAAO,gBAAgB,IAAI,QAAQ;AAE7D,QAAM,OAAO,MAAM;AACnB,MAAI,aAAa,MAAM,UAAU,WAAW,EAAG,SAAQ;AACvD,MAAI,QAAQ,aAAa,KAAK,UAAU,gBAAgB,KAAK,UAAW,SAAQ;AAChF,MAAI,aAAa,MAAM,gBAAgB,UAAU,WAAW,EAAG,SAAQ;AAEvE,MAAI,MAAM,aAAa,OAAO,GAAG;AAC/B,UAAM,SAAS,MAAM,aAAa,IAAI,iBAAiB,OAAO,QAAQ,CAAC;AACvE,QAAI,WAAW,YAAY,WAAW,UAAW,SAAQ;AACzD,QAAI,WAAW,QAAS,SAAQ;AAAA,EAClC;AAIA,MAAI,CAAC,MAAM,cAAc,MAAM,OAAO,OAAO,CAAC,MAAM,SAAS,MAAM,OAAO,UAAU,CAAC,MAAM,UAAU;AACnG,YAAQ;AAAA,EACV;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"gridStore.js","sources":["../../../../../src/components/UI/DataTable/grid/gridStore.ts"],"sourcesContent":["import { createStore, type StoreApi } from 'zustand/vanilla';\n\nimport {\n buildGridCellKey,\n GRID_CELL_ACTIVE,\n GRID_CELL_EDITING,\n GRID_CELL_FILL_HANDLE,\n GRID_CELL_FILL_PREVIEW,\n GRID_CELL_SAVE_ERROR,\n GRID_CELL_SAVING,\n GRID_CELL_SELECTED,\n GRID_CELL_TABBABLE,\n type IGridCellCoord,\n type IGridStartEditingOptions,\n type TGridEditingEntry,\n type TGridSelectionMode,\n} from './gridTypes';\n\nexport interface IGridMatrix {\n rowIds: Array<string>;\n columnIds: Array<string>;\n rowIndexById: Map<string, number>;\n columnIndexById: Map<string, number>;\n}\n\nexport interface IGridSelectionRect {\n rowStart: number;\n rowEnd: number;\n columnStart: number;\n columnEnd: number;\n}\n\nexport interface IGridMoveOptions {\n /** Keeps the selection anchor and stretches the rectangle to the new cell. */\n extend?: boolean;\n /** Tab-style traversal: past the last column continues on the next row. */\n wrap?: boolean;\n}\n\nexport type TGridEditingOutcome = 'commit' | 'cancel';\n\nexport interface IGridState {\n /**\n * Enquanto falso a tabela é uma tabela comum: nenhuma célula é focável, o\n * clique na linha segue sendo do host e nada do grid aparece.\n */\n isEditMode: boolean;\n setEditMode: (isEditMode: boolean) => void;\n selectionMode: TGridSelectionMode;\n matrix: IGridMatrix;\n activeCell: IGridCellCoord | null;\n selectionAnchor: IGridCellCoord | null;\n selectionRect: IGridSelectionRect | null;\n editingCell: IGridCellCoord | null;\n editingEntry: TGridEditingEntry | null;\n /** Typed text that replaces the value of the cell being opened; `null` keeps the value. */\n editingSeed: string | null;\n /** How the last edit ended, so the cell knows whether to restore its value. */\n editingOutcome: TGridEditingOutcome | null;\n /** Selection snapshot taken when the fill drag started. */\n fillSourceRect: IGridSelectionRect | null;\n /** Cells the running fill drag would write. Null while the pointer is inside the source. */\n fillTargetRect: IGridSelectionRect | null;\n /** Per-cell persistence state fed by the host's commit queue. */\n cellStatuses: ReadonlyMap<string, string>;\n setCellStatuses: (statuses: ReadonlyMap<string, string>) => void;\n setSelectionMode: (mode: TGridSelectionMode) => void;\n setMatrix: (rowIds: Array<string>, columnIds: Array<string>) => void;\n setActiveCell: (coord: IGridCellCoord, options?: { extend?: boolean }) => void;\n /** Returns whether the active cell actually moved, so the host can let Tab leave the grid. */\n moveActiveCell: (rowDelta: number, columnDelta: number, options?: IGridMoveOptions) => boolean;\n moveActiveToEdge: (rowDelta: number, columnDelta: number, options?: { extend?: boolean }) => void;\n /**\n * Tab traversal over the cells that accept an editor, opening the one it lands on.\n * Returns `false` past the first/last editable cell so the focus can leave the grid.\n */\n moveToEditableCell: (direction: 1 | -1, canEditCell: (coord: IGridCellCoord) => boolean) => boolean;\n startEditing: (coord?: IGridCellCoord, options?: IGridStartEditingOptions) => void;\n stopEditing: (outcome?: TGridEditingOutcome) => void;\n clearSelection: () => void;\n startFill: () => void;\n updateFillTarget: (coord: IGridCellCoord) => void;\n /** Clears the drag state and hands back what should be written, if anything. */\n endFill: () => IGridFillResult | null;\n cancelFill: () => void;\n}\n\nexport interface IGridFillResult {\n sourceRect: IGridSelectionRect;\n targetRect: IGridSelectionRect;\n matrix: IGridMatrix;\n}\n\nconst EMPTY_MATRIX: IGridMatrix = {\n rowIds: [],\n columnIds: [],\n rowIndexById: new Map(),\n columnIndexById: new Map(),\n};\n\nconst sameIds = (current: Array<string>, next: Array<string>): boolean =>\n current.length === next.length && current.every((id, index) => id === next[index]);\n\nconst buildIndex = (ids: Array<string>): Map<string, number> => new Map(ids.map((id, index) => [id, index]));\n\nconst buildRect = (\n matrix: IGridMatrix,\n anchor: IGridCellCoord | null,\n focus: IGridCellCoord | null,\n): IGridSelectionRect | null => {\n if (!anchor || !focus) return null;\n\n const anchorRow = matrix.rowIndexById.get(anchor.rowId);\n const anchorColumn = matrix.columnIndexById.get(anchor.columnId);\n const focusRow = matrix.rowIndexById.get(focus.rowId);\n const focusColumn = matrix.columnIndexById.get(focus.columnId);\n\n if (anchorRow == null || anchorColumn == null || focusRow == null || focusColumn == null) return null;\n\n return {\n rowStart: Math.min(anchorRow, focusRow),\n rowEnd: Math.max(anchorRow, focusRow),\n columnStart: Math.min(anchorColumn, focusColumn),\n columnEnd: Math.max(anchorColumn, focusColumn),\n };\n};\n\nconst coordAt = (matrix: IGridMatrix, rowIndex: number, columnIndex: number): IGridCellCoord | null => {\n const rowId = matrix.rowIds[rowIndex];\n const columnId = matrix.columnIds[columnIndex];\n if (rowId == null || columnId == null) return null;\n\n return { rowId, columnId };\n};\n\nconst isSameCoord = (left: IGridCellCoord | null, right: IGridCellCoord | null): boolean =>\n left?.rowId === right?.rowId && left?.columnId === right?.columnId;\n\nexport const createGridStore = (\n selectionMode: TGridSelectionMode = 'cell',\n initialEditMode = true,\n): StoreApi<IGridState> =>\n createStore<IGridState>((set, get) => {\n const focusCoord = (coord: IGridCellCoord, extend: boolean) => {\n const { matrix, selectionAnchor, selectionMode: mode, editingCell } = get();\n const anchor = extend && mode === 'range' ? (selectionAnchor ?? coord) : coord;\n\n set({\n activeCell: coord,\n selectionAnchor: anchor,\n selectionRect: buildRect(matrix, anchor, coord),\n editingCell: null,\n // Moving away from an open editor keeps what was typed: only Escape discards it.\n editingOutcome: editingCell ? 'commit' : null,\n });\n };\n\n return {\n isEditMode: initialEditMode,\n selectionMode,\n matrix: EMPTY_MATRIX,\n activeCell: null,\n selectionAnchor: null,\n selectionRect: null,\n editingCell: null,\n editingEntry: null,\n editingSeed: null,\n editingOutcome: null,\n fillSourceRect: null,\n fillTargetRect: null,\n cellStatuses: new Map<string, string>(),\n\n setEditMode: (isEditMode) => {\n if (get().isEditMode === isEditMode) return;\n\n // Sair do modo devolve a tabela ao estado neutro: nada selecionado, nada\n // em edição, nenhum arraste pela metade.\n set({\n isEditMode,\n activeCell: null,\n selectionAnchor: null,\n selectionRect: null,\n editingCell: null,\n editingOutcome: null,\n fillSourceRect: null,\n fillTargetRect: null,\n });\n },\n\n setCellStatuses: (statuses) => {\n if (get().cellStatuses === statuses) return;\n set({ cellStatuses: statuses });\n },\n\n setSelectionMode: (mode) => {\n if (get().selectionMode === mode) return;\n set({ selectionMode: mode });\n },\n\n setMatrix: (rowIds, columnIds) => {\n const { matrix, activeCell, selectionAnchor, editingCell } = get();\n if (sameIds(matrix.rowIds, rowIds) && sameIds(matrix.columnIds, columnIds)) return;\n\n const nextMatrix: IGridMatrix = {\n rowIds,\n columnIds,\n rowIndexById: buildIndex(rowIds),\n columnIndexById: buildIndex(columnIds),\n };\n\n // A row or column that left the viewport (pagination, filter, column\n // hidden) takes its active/editing state with it, so the grid never\n // points at a cell the user cannot see.\n const stillExists = (coord: IGridCellCoord | null): coord is IGridCellCoord =>\n !!coord && nextMatrix.rowIndexById.has(coord.rowId) && nextMatrix.columnIndexById.has(coord.columnId);\n\n const nextActive = stillExists(activeCell) ? activeCell : null;\n const nextAnchor = stillExists(selectionAnchor) ? selectionAnchor : nextActive;\n\n set({\n matrix: nextMatrix,\n activeCell: nextActive,\n selectionAnchor: nextAnchor,\n selectionRect: buildRect(nextMatrix, nextAnchor, nextActive),\n editingCell: stillExists(editingCell) ? editingCell : null,\n });\n },\n\n setActiveCell: (coord, options) => {\n const { activeCell, editingCell } = get();\n const extend = options?.extend ?? false;\n\n // Um clique DENTRO da célula que já está sendo editada pertence ao editor,\n // não à navegação: reposicionar o cursor aqui fecharia o editor ainda no\n // mousedown, e um select nunca chegaria a abrir a lista.\n if (!extend && isSameCoord(editingCell, coord)) return;\n if (!extend && isSameCoord(activeCell, coord) && !editingCell) return;\n\n focusCoord(coord, extend);\n },\n\n moveActiveCell: (rowDelta, columnDelta, options) => {\n const { matrix, activeCell } = get();\n if (matrix.rowIds.length === 0 || matrix.columnIds.length === 0) return false;\n\n if (!activeCell) {\n const first = coordAt(matrix, 0, 0);\n if (first) focusCoord(first, false);\n return !!first;\n }\n\n const rowIndex = matrix.rowIndexById.get(activeCell.rowId);\n const columnIndex = matrix.columnIndexById.get(activeCell.columnId);\n if (rowIndex == null || columnIndex == null) return false;\n\n let nextRow = rowIndex + rowDelta;\n let nextColumn = columnIndex + columnDelta;\n\n if (options?.wrap && columnDelta !== 0) {\n if (nextColumn >= matrix.columnIds.length) {\n nextColumn = 0;\n nextRow += 1;\n } else if (nextColumn < 0) {\n nextColumn = matrix.columnIds.length - 1;\n nextRow -= 1;\n }\n }\n\n if (nextRow < 0 || nextRow >= matrix.rowIds.length) return false;\n if (nextColumn < 0 || nextColumn >= matrix.columnIds.length) return false;\n\n const next = coordAt(matrix, nextRow, nextColumn);\n if (!next) return false;\n\n focusCoord(next, options?.extend ?? false);\n return true;\n },\n\n moveActiveToEdge: (rowDelta, columnDelta, options) => {\n const { matrix, activeCell } = get();\n if (matrix.rowIds.length === 0 || matrix.columnIds.length === 0) return;\n\n const rowIndex = activeCell ? (matrix.rowIndexById.get(activeCell.rowId) ?? 0) : 0;\n const columnIndex = activeCell ? (matrix.columnIndexById.get(activeCell.columnId) ?? 0) : 0;\n\n const nextRow = rowDelta === 0 ? rowIndex : rowDelta > 0 ? matrix.rowIds.length - 1 : 0;\n const nextColumn = columnDelta === 0 ? columnIndex : columnDelta > 0 ? matrix.columnIds.length - 1 : 0;\n\n const next = coordAt(matrix, nextRow, nextColumn);\n if (next) focusCoord(next, options?.extend ?? false);\n },\n\n moveToEditableCell: (direction, canEditCell) => {\n const { matrix, activeCell } = get();\n const columnCount = matrix.columnIds.length;\n const cellCount = matrix.rowIds.length * columnCount;\n if (cellCount === 0) return false;\n\n const rowIndex = activeCell ? matrix.rowIndexById.get(activeCell.rowId) : undefined;\n const columnIndex = activeCell ? matrix.columnIndexById.get(activeCell.columnId) : undefined;\n const currentPosition = rowIndex != null && columnIndex != null ? rowIndex * columnCount + columnIndex : -1;\n\n for (let position = currentPosition + direction; position >= 0 && position < cellCount; position += direction) {\n const candidate = coordAt(matrix, Math.floor(position / columnCount), position % columnCount);\n if (!candidate || !canEditCell(candidate)) continue;\n\n focusCoord(candidate, false);\n get().startEditing(candidate, { entry: 'keyboard' });\n return true;\n }\n\n return false;\n },\n\n startEditing: (coord, options) => {\n const { activeCell, editingCell, matrix } = get();\n const target = coord ?? activeCell;\n if (!target || isSameCoord(editingCell, target)) return;\n\n set({\n activeCell: target,\n selectionAnchor: target,\n selectionRect: buildRect(matrix, target, target),\n editingCell: target,\n editingEntry: options?.entry ?? 'keyboard',\n editingSeed: options?.seed ?? null,\n editingOutcome: null,\n });\n },\n\n stopEditing: (outcome = 'commit') => {\n if (!get().editingCell) return;\n set({ editingCell: null, editingOutcome: outcome });\n },\n\n clearSelection: () => {\n const { activeCell, matrix } = get();\n set({\n selectionAnchor: activeCell,\n selectionRect: buildRect(matrix, activeCell, activeCell),\n });\n },\n\n startFill: () => {\n const { selectionRect } = get();\n if (!selectionRect) return;\n set({ fillSourceRect: selectionRect, fillTargetRect: null, editingCell: null });\n },\n\n updateFillTarget: (coord) => {\n const { fillSourceRect, fillTargetRect, matrix } = get();\n if (!fillSourceRect) return;\n\n const rowIndex = matrix.rowIndexById.get(coord.rowId);\n if (rowIndex == null) return;\n\n // Vertical only for now. The rect shape is already 2D, so a horizontal\n // drag is a second branch here and nothing else in the layer changes.\n let nextTarget: IGridSelectionRect | null = null;\n if (rowIndex > fillSourceRect.rowEnd) {\n nextTarget = { ...fillSourceRect, rowStart: fillSourceRect.rowEnd + 1, rowEnd: rowIndex };\n } else if (rowIndex < fillSourceRect.rowStart) {\n nextTarget = { ...fillSourceRect, rowStart: rowIndex, rowEnd: fillSourceRect.rowStart - 1 };\n }\n\n const isSameTarget =\n nextTarget?.rowStart === fillTargetRect?.rowStart && nextTarget?.rowEnd === fillTargetRect?.rowEnd;\n if (isSameTarget) return;\n\n set({ fillTargetRect: nextTarget });\n },\n\n endFill: () => {\n const { fillSourceRect, fillTargetRect, matrix } = get();\n set({ fillSourceRect: null, fillTargetRect: null });\n if (!fillSourceRect || !fillTargetRect) return null;\n\n return { sourceRect: fillSourceRect, targetRect: fillTargetRect, matrix };\n },\n\n cancelFill: () => {\n if (!get().fillSourceRect) return;\n set({ fillSourceRect: null, fillTargetRect: null });\n },\n };\n });\n\n/**\n * Bitmask of what this cell currently is. A primitive keeps the per-cell\n * subscription cheap: `useSyncExternalStore` compares it by value, so a cell\n * only re-renders when its own state actually changes.\n */\nconst isInsideRect = (rect: IGridSelectionRect | null, rowIndex?: number, columnIndex?: number): boolean =>\n !!rect &&\n rowIndex != null &&\n columnIndex != null &&\n rowIndex >= rect.rowStart &&\n rowIndex <= rect.rowEnd &&\n columnIndex >= rect.columnStart &&\n columnIndex <= rect.columnEnd;\n\nexport const selectGridCellState = (state: IGridState, rowId: string, columnId: string): number => {\n if (!state.isEditMode) return 0;\n\n let mask = 0;\n\n if (isSameCoord(state.activeCell, { rowId, columnId })) mask |= GRID_CELL_ACTIVE;\n if (isSameCoord(state.editingCell, { rowId, columnId })) mask |= GRID_CELL_EDITING;\n\n const rowIndex = state.matrix.rowIndexById.get(rowId);\n const columnIndex = state.matrix.columnIndexById.get(columnId);\n\n const rect = state.selectionRect;\n if (isInsideRect(rect, rowIndex, columnIndex)) mask |= GRID_CELL_SELECTED;\n if (rect && rowIndex === rect.rowEnd && columnIndex === rect.columnEnd) mask |= GRID_CELL_FILL_HANDLE;\n if (isInsideRect(state.fillTargetRect, rowIndex, columnIndex)) mask |= GRID_CELL_FILL_PREVIEW;\n\n if (state.cellStatuses.size > 0) {\n const status = state.cellStatuses.get(buildGridCellKey(rowId, columnId));\n if (status === 'saving' || status === 'pending') mask |= GRID_CELL_SAVING;\n if (status === 'error') mask |= GRID_CELL_SAVE_ERROR;\n }\n\n // Roving tabindex needs an entry point before the first interaction: without\n // an active cell the top-left cell is the one Tab reaches from outside.\n if (!state.activeCell && state.matrix.rowIds[0] === rowId && state.matrix.columnIds[0] === columnId) {\n mask |= GRID_CELL_TABBABLE;\n }\n\n return mask;\n};\n"],"names":[],"mappings":"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"}
@@ -8,12 +8,24 @@ export interface IGridCellCoord {
8
8
  columnId: string;
9
9
  }
10
10
  export type TGridSelectionMode = 'cell' | 'range';
11
+ /**
12
+ * How an editor was opened, which decides what it does once it has the focus:
13
+ * - `pointer`: a click. Text keeps the caret at the end; a picker (select, date) opens its popup.
14
+ * - `keyboard`: Tab, Enter. Text is selected, so typing replaces it like a form field.
15
+ * - `typing`: a printable key on the active cell. The key becomes the new value; a picker opens.
16
+ */
17
+ export type TGridEditingEntry = 'pointer' | 'keyboard' | 'typing';
18
+ export interface IGridStartEditingOptions {
19
+ entry?: TGridEditingEntry;
20
+ /** Text that replaces the cell value as the editor opens (the key the user typed). */
21
+ seed?: string;
22
+ }
11
23
  /**
12
24
  * Quando a camada de planilha fica ativa:
13
- * - `toggle`: o usuário liga pelo botão do toolbar. É o padrão nas tabelas que
14
- * navegam no clique da linha, onde selecionar uma célula e abrir o registro
15
- * seriam o mesmo gesto.
16
- * - `always`: ativa desde a montagem, para tabelas cuja razão de existir é editar.
25
+ * - `always` (padrão): ativa desde a montagem. Numa tabela com `onRowClick`, o clique
26
+ * numa célula editável abre o editor e o clique no resto da linha abre o registro.
27
+ * - `toggle`: o usuário liga pelo botão do rodapé antes de editar. Só para tabelas em
28
+ * que a linha inteira precisa continuar sendo o alvo do clique.
17
29
  */
18
30
  export type TGridEditModeBehavior = 'toggle' | 'always';
19
31
  /**
@@ -27,7 +39,7 @@ export interface IGridNavigationConfig {
27
39
  selection?: TGridSelectionMode;
28
40
  /** Invoked by Ctrl/Cmd+S while the grid has focus. */
29
41
  onSave?: () => void;
30
- /** Defaults to `toggle` when the table has `onRowClick`, `always` otherwise. */
42
+ /** Defaults to `always`. */
31
43
  mode?: TGridEditModeBehavior;
32
44
  }
33
45
  export declare const GRID_CELL_ACTIVE = 1;
@@ -1 +1 @@
1
- {"version":3,"file":"gridTypes.js","sources":["../../../../../src/components/UI/DataTable/grid/gridTypes.ts"],"sourcesContent":["/**\n * Spreadsheet layer types. The grid coordinates are always the TanStack row id\n * (unique across nesting levels) plus the column id, so navigation never depends\n * on DOM order and stays correct with hidden, reordered and resized columns.\n */\nexport interface IGridCellCoord {\n rowId: string;\n columnId: string;\n}\n\nexport type TGridSelectionMode = 'cell' | 'range';\n\n/**\n * Quando a camada de planilha fica ativa:\n * - `toggle`: o usuário liga pelo botão do toolbar. É o padrão nas tabelas que\n * navegam no clique da linha, onde selecionar uma célula e abrir o registro\n * seriam o mesmo gesto.\n * - `always`: ativa desde a montagem, para tabelas cuja razão de existir é editar.\n */\nexport type TGridEditModeBehavior = 'toggle' | 'always';\n\n/**\n * The part of the spreadsheet config the grid layer itself understands. The\n * row-typed part (fill handlers) lives in `types.ts`, which extends this.\n */\nexport interface IGridNavigationConfig {\n /** Arrow/Tab/Enter navigation over an active cell. Defaults to `true`. */\n navigation?: boolean;\n /** `cell` keeps one cell selected; `range` enables Shift+Arrow / Shift+Click rectangles. */\n selection?: TGridSelectionMode;\n /** Invoked by Ctrl/Cmd+S while the grid has focus. */\n onSave?: () => void;\n /** Defaults to `toggle` when the table has `onRowClick`, `always` otherwise. */\n mode?: TGridEditModeBehavior;\n}\n\nexport const GRID_CELL_ACTIVE = 1;\nexport const GRID_CELL_SELECTED = 2;\nexport const GRID_CELL_EDITING = 4;\nexport const GRID_CELL_FILL_PREVIEW = 8;\n/** The cell that carries `tabIndex=0` while no cell is active yet. */\nexport const GRID_CELL_TABBABLE = 16;\n/** Bottom-right corner of the selection: the cell that renders the fill handle. */\nexport const GRID_CELL_FILL_HANDLE = 32;\nexport const GRID_CELL_SAVING = 64;\nexport const GRID_CELL_SAVE_ERROR = 128;\n\nexport const buildGridCellKey = (rowId: string, columnId: string): string => `${rowId}::${columnId}`;\n\n/** DOM hook used by the fill drag to resolve which cell is under the pointer. */\nexport const GRID_CELL_DATA_ATTR = 'data-grid-cell';\nexport const GRID_ROW_DATA_ATTR = 'data-grid-row';\nexport const GRID_COLUMN_DATA_ATTR = 'data-grid-column';\n"],"names":[],"mappings":"AAoCO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,yBAAyB;AAE/B,MAAM,qBAAqB;AAE3B,MAAM,wBAAwB;AAC9B,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAE7B,MAAM,mBAAmB,CAAC,OAAe,aAA6B,GAAG,KAAK,KAAK,QAAQ;AAG3F,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;"}
1
+ {"version":3,"file":"gridTypes.js","sources":["../../../../../src/components/UI/DataTable/grid/gridTypes.ts"],"sourcesContent":["/**\n * Spreadsheet layer types. The grid coordinates are always the TanStack row id\n * (unique across nesting levels) plus the column id, so navigation never depends\n * on DOM order and stays correct with hidden, reordered and resized columns.\n */\nexport interface IGridCellCoord {\n rowId: string;\n columnId: string;\n}\n\nexport type TGridSelectionMode = 'cell' | 'range';\n\n/**\n * How an editor was opened, which decides what it does once it has the focus:\n * - `pointer`: a click. Text keeps the caret at the end; a picker (select, date) opens its popup.\n * - `keyboard`: Tab, Enter. Text is selected, so typing replaces it like a form field.\n * - `typing`: a printable key on the active cell. The key becomes the new value; a picker opens.\n */\nexport type TGridEditingEntry = 'pointer' | 'keyboard' | 'typing';\n\nexport interface IGridStartEditingOptions {\n entry?: TGridEditingEntry;\n /** Text that replaces the cell value as the editor opens (the key the user typed). */\n seed?: string;\n}\n\n/**\n * Quando a camada de planilha fica ativa:\n * - `always` (padrão): ativa desde a montagem. Numa tabela com `onRowClick`, o clique\n * numa célula editável abre o editor e o clique no resto da linha abre o registro.\n * - `toggle`: o usuário liga pelo botão do rodapé antes de editar. Só para tabelas em\n * que a linha inteira precisa continuar sendo o alvo do clique.\n */\nexport type TGridEditModeBehavior = 'toggle' | 'always';\n\n/**\n * The part of the spreadsheet config the grid layer itself understands. The\n * row-typed part (fill handlers) lives in `types.ts`, which extends this.\n */\nexport interface IGridNavigationConfig {\n /** Arrow/Tab/Enter navigation over an active cell. Defaults to `true`. */\n navigation?: boolean;\n /** `cell` keeps one cell selected; `range` enables Shift+Arrow / Shift+Click rectangles. */\n selection?: TGridSelectionMode;\n /** Invoked by Ctrl/Cmd+S while the grid has focus. */\n onSave?: () => void;\n /** Defaults to `always`. */\n mode?: TGridEditModeBehavior;\n}\n\nexport const GRID_CELL_ACTIVE = 1;\nexport const GRID_CELL_SELECTED = 2;\nexport const GRID_CELL_EDITING = 4;\nexport const GRID_CELL_FILL_PREVIEW = 8;\n/** The cell that carries `tabIndex=0` while no cell is active yet. */\nexport const GRID_CELL_TABBABLE = 16;\n/** Bottom-right corner of the selection: the cell that renders the fill handle. */\nexport const GRID_CELL_FILL_HANDLE = 32;\nexport const GRID_CELL_SAVING = 64;\nexport const GRID_CELL_SAVE_ERROR = 128;\n\nexport const buildGridCellKey = (rowId: string, columnId: string): string => `${rowId}::${columnId}`;\n\n/** DOM hook used by the fill drag to resolve which cell is under the pointer. */\nexport const GRID_CELL_DATA_ATTR = 'data-grid-cell';\nexport const GRID_ROW_DATA_ATTR = 'data-grid-row';\nexport const GRID_COLUMN_DATA_ATTR = 'data-grid-column';\n"],"names":[],"mappings":"AAkDO,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB;AAC1B,MAAM,yBAAyB;AAE/B,MAAM,qBAAqB;AAE3B,MAAM,wBAAwB;AAC9B,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;AAE7B,MAAM,mBAAmB,CAAC,OAAe,aAA6B,GAAG,KAAK,KAAK,QAAQ;AAG3F,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;"}
@@ -4,8 +4,10 @@ import { IGridState } from './gridStore';
4
4
  import { IGridCellCoord } from './gridTypes';
5
5
  interface IUseGridKeyboardParams {
6
6
  store: StoreApi<IGridState> | null;
7
- /** Whether the cell can open an editor, so Enter/F2 know what to do. */
7
+ /** Whether the cell can open an editor, so Enter/F2/typing know what to do and Tab knows where to stop. */
8
8
  canEditCell?: (coord: IGridCellCoord) => boolean;
9
+ /** Whether the cell's editor is free text, so a typed key can become its value. */
10
+ acceptsTypedValue?: (coord: IGridCellCoord) => boolean;
9
11
  onSave?: () => void;
10
12
  }
11
13
  /**
@@ -19,5 +21,5 @@ export declare const isTextEntryTarget: (target: EventTarget | null) => boolean;
19
21
  * reached by bubbling from the focused cell. No document listeners, no per-cell
20
22
  * handlers, so two tables on the same screen never fight over the same keys.
21
23
  */
22
- export declare const useGridKeyboard: ({ store, canEditCell, onSave }: IUseGridKeyboardParams) => (event: React.KeyboardEvent<HTMLElement>) => void;
24
+ export declare const useGridKeyboard: ({ store, canEditCell, acceptsTypedValue, onSave }: IUseGridKeyboardParams) => (event: React.KeyboardEvent<HTMLElement>) => void;
23
25
  export {};