@rowsncolumns/spreadsheet 10.2.77 → 10.3.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 (176) hide show
  1. package/dist/cjs/api/insert-options.d.ts +67 -0
  2. package/dist/cjs/api/insert-options.d.ts.map +1 -0
  3. package/dist/cjs/api/insert-options.js +104 -0
  4. package/dist/cjs/api/move-overwrite.d.ts +14 -0
  5. package/dist/cjs/api/move-overwrite.d.ts.map +1 -0
  6. package/dist/cjs/api/move-overwrite.js +29 -0
  7. package/dist/cjs/api/multi-selection.d.ts +59 -0
  8. package/dist/cjs/api/multi-selection.d.ts.map +1 -0
  9. package/dist/cjs/api/multi-selection.js +166 -0
  10. package/dist/cjs/atoms/index.d.ts +3 -0
  11. package/dist/cjs/atoms/index.d.ts.map +1 -1
  12. package/dist/cjs/atoms/index.js +3 -0
  13. package/dist/cjs/atoms/insert-cells-dialog.atom.d.ts +36 -0
  14. package/dist/cjs/atoms/insert-cells-dialog.atom.d.ts.map +1 -0
  15. package/dist/cjs/atoms/insert-cells-dialog.atom.js +34 -0
  16. package/dist/cjs/atoms/insert-options.atom.d.ts +21 -0
  17. package/dist/cjs/atoms/insert-options.atom.d.ts.map +1 -0
  18. package/dist/cjs/atoms/insert-options.atom.js +11 -0
  19. package/dist/cjs/atoms/move-overwrite-confirm.atom.d.ts +24 -0
  20. package/dist/cjs/atoms/move-overwrite-confirm.atom.d.ts.map +1 -0
  21. package/dist/cjs/atoms/move-overwrite-confirm.atom.js +42 -0
  22. package/dist/cjs/atoms/sheet-context.atom.d.ts +2 -0
  23. package/dist/cjs/atoms/sheet-context.atom.d.ts.map +1 -1
  24. package/dist/cjs/atoms/sheet-context.atom.js +15 -2
  25. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  26. package/dist/cjs/components/canvas-grid/index.js +284 -63
  27. package/dist/cjs/components/canvas-grid/typings.d.ts +47 -5
  28. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  29. package/dist/cjs/components/cell-editor/formula-result-preview.d.ts +4 -1
  30. package/dist/cjs/components/cell-editor/formula-result-preview.d.ts.map +1 -1
  31. package/dist/cjs/components/cell-editor/formula-result-preview.js +2 -2
  32. package/dist/cjs/components/cell-editor/index.d.ts +7 -1
  33. package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
  34. package/dist/cjs/components/cell-editor/index.js +3 -3
  35. package/dist/cjs/components/cell-input/cell-input.d.ts +13 -0
  36. package/dist/cjs/components/cell-input/cell-input.d.ts.map +1 -1
  37. package/dist/cjs/components/cell-input/cell-input.js +8 -3
  38. package/dist/cjs/components/cell-input/formula-bar-cell-input.d.ts.map +1 -1
  39. package/dist/cjs/components/cell-input/formula-bar-cell-input.js +3 -1
  40. package/dist/cjs/components/context-menu/index.d.ts +16 -1
  41. package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
  42. package/dist/cjs/components/context-menu/index.js +245 -24
  43. package/dist/cjs/components/floating-cell-editor/index.d.ts.map +1 -1
  44. package/dist/cjs/components/floating-cell-editor/index.js +13 -5
  45. package/dist/cjs/components/insert-cells-dialog/index.d.ts +35 -0
  46. package/dist/cjs/components/insert-cells-dialog/index.d.ts.map +1 -0
  47. package/dist/cjs/components/insert-cells-dialog/index.js +84 -0
  48. package/dist/cjs/components/insert-options-smart-tag/index.d.ts +25 -0
  49. package/dist/cjs/components/insert-options-smart-tag/index.d.ts.map +1 -0
  50. package/dist/cjs/components/insert-options-smart-tag/index.js +41 -0
  51. package/dist/cjs/components/move-overwrite-confirm-dialog/index.d.ts +8 -0
  52. package/dist/cjs/components/move-overwrite-confirm-dialog/index.d.ts.map +1 -0
  53. package/dist/cjs/components/move-overwrite-confirm-dialog/index.js +34 -0
  54. package/dist/cjs/components/selection/index.d.ts +1 -1
  55. package/dist/cjs/components/selection/index.d.ts.map +1 -1
  56. package/dist/cjs/components/selection/index.js +4 -2
  57. package/dist/cjs/components/selection/types.d.ts +8 -0
  58. package/dist/cjs/components/selection/types.d.ts.map +1 -1
  59. package/dist/cjs/config.d.ts +6 -0
  60. package/dist/cjs/config.d.ts.map +1 -1
  61. package/dist/cjs/config.js +7 -1
  62. package/dist/cjs/hooks/use-cell-renderer.d.ts +7 -1
  63. package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
  64. package/dist/cjs/hooks/use-cell-renderer.js +8 -3
  65. package/dist/cjs/hooks/use-context-setter.d.ts.map +1 -1
  66. package/dist/cjs/hooks/use-context-setter.js +5 -1
  67. package/dist/cjs/hooks/use-copy-paste-cut.d.ts +5 -0
  68. package/dist/cjs/hooks/use-copy-paste-cut.d.ts.map +1 -1
  69. package/dist/cjs/hooks/use-copy-paste-cut.js +19 -0
  70. package/dist/cjs/hooks/use-editor-change.d.ts +4 -1
  71. package/dist/cjs/hooks/use-editor-change.d.ts.map +1 -1
  72. package/dist/cjs/hooks/use-editor-change.js +4 -3
  73. package/dist/cjs/hooks/use-editor-submit.d.ts +8 -1
  74. package/dist/cjs/hooks/use-editor-submit.d.ts.map +1 -1
  75. package/dist/cjs/hooks/use-editor-submit.js +9 -1
  76. package/dist/cjs/hooks/use-move-columns-rows.d.ts.map +1 -1
  77. package/dist/cjs/hooks/use-move-columns-rows.js +6 -0
  78. package/dist/cjs/hooks/use-selection-renderer.d.ts +9 -1
  79. package/dist/cjs/hooks/use-selection-renderer.d.ts.map +1 -1
  80. package/dist/cjs/hooks/use-selection-renderer.js +26 -2
  81. package/dist/cjs/hooks/use-selection.d.ts +13 -5
  82. package/dist/cjs/hooks/use-selection.d.ts.map +1 -1
  83. package/dist/cjs/hooks/use-selection.js +48 -6
  84. package/dist/cjs/index.d.ts +7 -0
  85. package/dist/cjs/index.d.ts.map +1 -1
  86. package/dist/cjs/index.js +7 -0
  87. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  88. package/dist/esm/api/insert-options.d.ts +67 -0
  89. package/dist/esm/api/insert-options.d.ts.map +1 -0
  90. package/dist/esm/api/insert-options.js +91 -0
  91. package/dist/esm/api/move-overwrite.d.ts +14 -0
  92. package/dist/esm/api/move-overwrite.d.ts.map +1 -0
  93. package/dist/esm/api/move-overwrite.js +25 -0
  94. package/dist/esm/api/multi-selection.d.ts +59 -0
  95. package/dist/esm/api/multi-selection.d.ts.map +1 -0
  96. package/dist/esm/api/multi-selection.js +92 -0
  97. package/dist/esm/atoms/index.d.ts +3 -0
  98. package/dist/esm/atoms/index.d.ts.map +1 -1
  99. package/dist/esm/atoms/index.js +3 -0
  100. package/dist/esm/atoms/insert-cells-dialog.atom.d.ts +36 -0
  101. package/dist/esm/atoms/insert-cells-dialog.atom.d.ts.map +1 -0
  102. package/dist/esm/atoms/insert-cells-dialog.atom.js +25 -0
  103. package/dist/esm/atoms/insert-options.atom.d.ts +21 -0
  104. package/dist/esm/atoms/insert-options.atom.d.ts.map +1 -0
  105. package/dist/esm/atoms/insert-options.atom.js +5 -0
  106. package/dist/esm/atoms/move-overwrite-confirm.atom.d.ts +24 -0
  107. package/dist/esm/atoms/move-overwrite-confirm.atom.d.ts.map +1 -0
  108. package/dist/esm/atoms/move-overwrite-confirm.atom.js +29 -0
  109. package/dist/esm/atoms/sheet-context.atom.d.ts +2 -0
  110. package/dist/esm/atoms/sheet-context.atom.d.ts.map +1 -1
  111. package/dist/esm/atoms/sheet-context.atom.js +10 -0
  112. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  113. package/dist/esm/components/canvas-grid/index.js +236 -19
  114. package/dist/esm/components/canvas-grid/typings.d.ts +47 -5
  115. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  116. package/dist/esm/components/cell-editor/formula-result-preview.d.ts +4 -1
  117. package/dist/esm/components/cell-editor/formula-result-preview.d.ts.map +1 -1
  118. package/dist/esm/components/cell-editor/formula-result-preview.js +3 -3
  119. package/dist/esm/components/cell-editor/index.d.ts +7 -1
  120. package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
  121. package/dist/esm/components/cell-editor/index.js +3 -3
  122. package/dist/esm/components/cell-input/cell-input.d.ts +13 -0
  123. package/dist/esm/components/cell-input/cell-input.d.ts.map +1 -1
  124. package/dist/esm/components/cell-input/cell-input.js +10 -5
  125. package/dist/esm/components/cell-input/formula-bar-cell-input.d.ts.map +1 -1
  126. package/dist/esm/components/cell-input/formula-bar-cell-input.js +3 -1
  127. package/dist/esm/components/context-menu/index.d.ts +16 -1
  128. package/dist/esm/components/context-menu/index.d.ts.map +1 -1
  129. package/dist/esm/components/context-menu/index.js +193 -16
  130. package/dist/esm/components/floating-cell-editor/index.d.ts.map +1 -1
  131. package/dist/esm/components/floating-cell-editor/index.js +12 -6
  132. package/dist/esm/components/insert-cells-dialog/index.d.ts +35 -0
  133. package/dist/esm/components/insert-cells-dialog/index.d.ts.map +1 -0
  134. package/dist/esm/components/insert-cells-dialog/index.js +79 -0
  135. package/dist/esm/components/insert-options-smart-tag/index.d.ts +25 -0
  136. package/dist/esm/components/insert-options-smart-tag/index.d.ts.map +1 -0
  137. package/dist/esm/components/insert-options-smart-tag/index.js +35 -0
  138. package/dist/esm/components/move-overwrite-confirm-dialog/index.d.ts +8 -0
  139. package/dist/esm/components/move-overwrite-confirm-dialog/index.d.ts.map +1 -0
  140. package/dist/esm/components/move-overwrite-confirm-dialog/index.js +30 -0
  141. package/dist/esm/components/selection/index.d.ts +1 -1
  142. package/dist/esm/components/selection/index.d.ts.map +1 -1
  143. package/dist/esm/components/selection/index.js +4 -2
  144. package/dist/esm/components/selection/types.d.ts +8 -0
  145. package/dist/esm/components/selection/types.d.ts.map +1 -1
  146. package/dist/esm/config.d.ts +6 -0
  147. package/dist/esm/config.d.ts.map +1 -1
  148. package/dist/esm/config.js +6 -0
  149. package/dist/esm/hooks/use-cell-renderer.d.ts +7 -1
  150. package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
  151. package/dist/esm/hooks/use-cell-renderer.js +9 -4
  152. package/dist/esm/hooks/use-context-setter.d.ts.map +1 -1
  153. package/dist/esm/hooks/use-context-setter.js +5 -1
  154. package/dist/esm/hooks/use-copy-paste-cut.d.ts +5 -0
  155. package/dist/esm/hooks/use-copy-paste-cut.d.ts.map +1 -1
  156. package/dist/esm/hooks/use-copy-paste-cut.js +19 -0
  157. package/dist/esm/hooks/use-editor-change.d.ts +4 -1
  158. package/dist/esm/hooks/use-editor-change.d.ts.map +1 -1
  159. package/dist/esm/hooks/use-editor-change.js +5 -4
  160. package/dist/esm/hooks/use-editor-submit.d.ts +8 -1
  161. package/dist/esm/hooks/use-editor-submit.d.ts.map +1 -1
  162. package/dist/esm/hooks/use-editor-submit.js +10 -2
  163. package/dist/esm/hooks/use-move-columns-rows.d.ts.map +1 -1
  164. package/dist/esm/hooks/use-move-columns-rows.js +6 -0
  165. package/dist/esm/hooks/use-selection-renderer.d.ts +9 -1
  166. package/dist/esm/hooks/use-selection-renderer.d.ts.map +1 -1
  167. package/dist/esm/hooks/use-selection-renderer.js +26 -2
  168. package/dist/esm/hooks/use-selection.d.ts +13 -5
  169. package/dist/esm/hooks/use-selection.d.ts.map +1 -1
  170. package/dist/esm/hooks/use-selection.js +39 -3
  171. package/dist/esm/index.d.ts +7 -0
  172. package/dist/esm/index.d.ts.map +1 -1
  173. package/dist/esm/index.js +7 -0
  174. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  175. package/dist/spreadsheet.min.css +1 -1
  176. package/package.json +6 -6
@@ -3,26 +3,193 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronRightIcon, ClipboardIc
3
3
  import { DropdownLeftSlot, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownRightSlot, } from "@rowsncolumns/ui";
4
4
  import { addressToCell, cellToAddress, number2Alpha } from "@rowsncolumns/utils";
5
5
  import React, { useCallback, useMemo, useRef } from "react";
6
+ import { bandsForAreas, bandsFromIndexes, classifyMultiSelection, defaultDeleteCellsChoice, defaultInsertCellsChoice, indexesForAreas, wholeColumnPredicate, wholeColumnPredicateFromHeaderIds, wholeRowPredicate, wholeRowPredicateFromHeaderIds, } from "../../api/multi-selection";
6
7
  import { getProtectedRange, isProtectedRange } from "../../api/validation";
7
8
  import { useGetContentfulRangeAroundCell } from "../../atoms";
8
9
  import { useIsAdvancedFilterDialogMounted, useOpenAdvancedFilterDialog, } from "../../atoms/advanced-filter-dialog.atom";
9
10
  import { useIsGoalSeekDialogMounted, useOpenGoalSeekDialog, } from "../../atoms/goal-seek-dialog.atom";
11
+ import { useIsInsertCellsDialogMounted, useOpenInsertCellsDialog, } from "../../atoms/insert-cells-dialog.atom";
10
12
  import { useIsRemoveDuplicatesDialogMounted, useOpenRemoveDuplicatesDialog, } from "../../atoms/remove-duplicates-dialog.atom";
11
13
  import { useIsSubtotalDialogMounted, useOpenSubtotalDialog, } from "../../atoms/subtotal-dialog.atom";
12
14
  import { useIsTextToColumnsDialogMounted, useOpenTextToColumnsDialog, } from "../../atoms/text-to-columns-dialog.atom";
13
- import { BLANK_LABEL } from "../../config";
14
- import { createArrayRange, generateMultiDimTitle } from "../../helpers";
15
+ import { useShowValidationAlert } from "../../atoms/validation-alert.atom";
16
+ import { BLANK_LABEL, MULTIPLE_SELECTIONS_ERROR } from "../../config";
17
+ import { generateMultiDimTitle } from "../../helpers";
15
18
  import { getTotalRowFunctionFormula, getTotalRowFunctionFromFormula, TOTAL_ROW_FUNCTIONS, } from "./total-row-functions";
16
- export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selections, basicFilter, isTableHeader, onClearContents, onHideColumn, onHideRow, onGroupRows, onUngroupRows, onGroupColumns, onUngroupColumns, onDeleteRow, onDeleteColumn, onInsertColumn, onInsertRow, onInsertCellsShiftDown, onInsertCellsShiftRight, onDeleteCellsShiftLeft, onDeleteCellsShiftUp, onFreezeRow, onFreezeColumn, onCopy, onCut, onPaste, onRequestPasteSpecial, onRequestEvaluateFormula, onRequestAddWatch, isActiveCellFormula, onInsertNote, onProtectRange, onUnProtectRange, onSortRange, onSortColumn, onRequestDefineNamedRange, onRequestResize, onRequestEditTable, onRequestEditPivotTable, onRequestDeletePivotTable, onPivotDrillDown, onRequestFormatCells, onRequestConditionalFormat, onRequestDataValidation, onRemoveTable, onClearFormatting, onCreateTable, onCreateBasicFilter, onFilterTable, onSortTable, getFormattedValuesFromRange, focusSheet, isSheetFocused, getFormattedValue, onInsertTableColumn, onDeleteTableColumn, onInsertTableRow, onDeleteTableRow, onSplitTextToColumns, onRemoveDuplicates, onGoalSeek, onSubtotal, onAdvancedFilter, onChange, getCellData, isHiddenColumn, isHiddenRow, onShowColumn, onShowRow, tables, pivotTables, protectedRanges, sheetId, frozenRows, frozenColumns, selectedRowHeadersIds, selectedColumnHeadersIds, readonly = false, onAddQuickEdit, enableMagicFill, }) => {
19
+ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selections, basicFilter, isTableHeader, onClearContents, onHideColumn, onHideRow, onGroupRows, onUngroupRows, onGroupColumns, onUngroupColumns, onDeleteRow, onDeleteColumn, onInsertColumn, onInsertRow, onInsertCellsShiftDown, onInsertCellsShiftRight, onDeleteCellsShiftLeft, onDeleteCellsShiftUp, onFreezeRow, onFreezeColumn, onCopy, onCut, hasCutSelection, onInsertCutCells, onPaste, onRequestPasteSpecial, onRequestEvaluateFormula, onRequestAddWatch, isActiveCellFormula, onInsertNote, onProtectRange, onUnProtectRange, onSortRange, onSortColumn, onRequestDefineNamedRange, onRequestResize, onRequestEditTable, onRequestEditPivotTable, onRequestDeletePivotTable, onPivotDrillDown, onRequestFormatCells, onRequestConditionalFormat, onRequestDataValidation, onRemoveTable, onClearFormatting, onCreateTable, onCreateBasicFilter, onFilterTable, onSortTable, getFormattedValuesFromRange, focusSheet, isSheetFocused, getFormattedValue, onInsertTableColumn, onDeleteTableColumn, onInsertTableRow, onDeleteTableRow, onSplitTextToColumns, onRemoveDuplicates, onGoalSeek, onSubtotal, onAdvancedFilter, onChange, getCellData, isHiddenColumn, isHiddenRow, onShowColumn, onShowRow, tables, pivotTables, protectedRanges, sheetId, frozenRows, frozenColumns, selectedRowHeadersIds, selectedColumnHeadersIds, rowCount, columnCount, readonly = false, onAddQuickEdit, enableMagicFill, }) => {
17
20
  const isHeader = isColumnHeader || isRowHeader;
18
21
  const shouldFocusSheetRef = useRef(true);
19
22
  const multiColumnTitle = generateMultiDimTitle(selectedColumnHeadersIds, "y");
20
- const selection = selections.length
21
- ? selections[0]
22
- : selectionFromActiveCell(activeCell)[0];
23
+ const selectionAreas = selections.length
24
+ ? selections
25
+ : selectionFromActiveCell(activeCell);
26
+ const selection = selectionAreas[0];
23
27
  const multiRowTitle = generateMultiDimTitle(selectedRowHeadersIds, "x");
24
- const selectedRowIndexes = createArrayRange(selection.range.startRowIndex, selection.range.endRowIndex);
25
- const selectedColumnIndexes = createArrayRange(selection.range.startColumnIndex, selection.range.endColumnIndex);
28
+ // Every Ctrl-click area counts, not just the first (Excel INS-08 / UI-07).
29
+ const selectedRowIndexes = indexesForAreas(selectionAreas, "row");
30
+ const selectedColumnIndexes = indexesForAreas(selectionAreas, "column");
31
+ const isWholeRow = columnCount != null
32
+ ? wholeRowPredicate(columnCount)
33
+ : wholeRowPredicateFromHeaderIds(selectedRowHeadersIds);
34
+ const isWholeColumn = rowCount != null
35
+ ? wholeColumnPredicate(rowCount)
36
+ : wholeColumnPredicateFromHeaderIds(selectedColumnHeadersIds);
37
+ const multiSelectionKind = classifyMultiSelection(selectionAreas, {
38
+ isWholeRow,
39
+ isWholeColumn,
40
+ });
41
+ const showValidationAlert = useShowValidationAlert();
42
+ const isInsertCellsDialogMounted = useIsInsertCellsDialogMounted();
43
+ const openInsertCellsDialog = useOpenInsertCellsDialog();
44
+ /** Excel: "That command cannot be used on multiple selections." */
45
+ const refuseMultipleSelections = (title) => {
46
+ shouldFocusSheetRef.current = false;
47
+ void showValidationAlert({
48
+ style: "stop",
49
+ title,
50
+ message: MULTIPLE_SELECTIONS_ERROR,
51
+ confirm: false,
52
+ }).then(() => focusSheet());
53
+ };
54
+ /**
55
+ * The entire-row bands a row insert / delete acts on — one per selected
56
+ * area, bottom-up, so inserting above each band never shifts a band still
57
+ * to be processed (Excel INS-08). Falls back to the header bookkeeping, then
58
+ * to the active row, when no area is a whole row.
59
+ */
60
+ const rowBands = () => {
61
+ const areas = selectionAreas.filter(({ range }) => isWholeRow(range));
62
+ const bands = areas.length
63
+ ? bandsForAreas(areas, "row")
64
+ : bandsFromIndexes(selectedRowHeadersIds);
65
+ return bands.length
66
+ ? bands
67
+ : [{ start: activeCell.rowIndex, end: activeCell.rowIndex }];
68
+ };
69
+ const columnBands = () => {
70
+ const areas = selectionAreas.filter(({ range }) => isWholeColumn(range));
71
+ const bands = areas.length
72
+ ? bandsForAreas(areas, "column")
73
+ : bandsFromIndexes(selectedColumnHeadersIds);
74
+ return bands.length
75
+ ? bands
76
+ : [{ start: activeCell.columnIndex, end: activeCell.columnIndex }];
77
+ };
78
+ const bandSize = (band) => band.end - band.start + 1;
79
+ const insertRowsAbove = () => {
80
+ for (const band of rowBands()) {
81
+ onInsertRow?.(sheetId, band.start, bandSize(band));
82
+ }
83
+ };
84
+ const insertRowsBelow = () => {
85
+ for (const band of rowBands()) {
86
+ onInsertRow?.(sheetId, band.end + 1, bandSize(band));
87
+ }
88
+ };
89
+ const insertColumnsLeft = () => {
90
+ for (const band of columnBands()) {
91
+ onInsertColumn?.(sheetId, band.start, bandSize(band));
92
+ }
93
+ };
94
+ const insertColumnsRight = () => {
95
+ for (const band of columnBands()) {
96
+ onInsertColumn?.(sheetId, band.end + 1, bandSize(band));
97
+ }
98
+ };
99
+ const isSingleWholeRow = multiSelectionKind === "single" && isWholeRow(selection.range);
100
+ const isSingleWholeColumn = multiSelectionKind === "single" && isWholeColumn(selection.range);
101
+ /**
102
+ * Excel's cell-menu Insert: entire rows / columns insert directly, a
103
+ * multi-area selection inserts per area or is refused, and a plain cell
104
+ * range opens the Insert dialog (UI-06 / UI-07). The direct submenu remains
105
+ * only while no `InsertCellsDialogHost` is mounted.
106
+ */
107
+ const usesDirectInsertSubmenu = multiSelectionKind === "single" &&
108
+ !isSingleWholeRow &&
109
+ !isSingleWholeColumn &&
110
+ !isInsertCellsDialogMounted;
111
+ const handleInsertCells = () => {
112
+ if (multiSelectionKind === "mixed") {
113
+ refuseMultipleSelections("Insert");
114
+ return;
115
+ }
116
+ if (multiSelectionKind === "all-rows" || isSingleWholeRow) {
117
+ insertRowsAbove();
118
+ return;
119
+ }
120
+ if (multiSelectionKind === "all-columns" || isSingleWholeColumn) {
121
+ insertColumnsLeft();
122
+ return;
123
+ }
124
+ shouldFocusSheetRef.current = false;
125
+ openInsertCellsDialog({
126
+ mode: "insert",
127
+ defaultChoice: defaultInsertCellsChoice(selection.range),
128
+ onApply: (choice) => {
129
+ switch (choice) {
130
+ case "shift-right":
131
+ onInsertCellsShiftRight?.(sheetId, activeCell, selections);
132
+ break;
133
+ case "shift-down":
134
+ onInsertCellsShiftDown?.(sheetId, activeCell, selections);
135
+ break;
136
+ case "entire-row":
137
+ onInsertRow?.(sheetId, selection.range.startRowIndex, selectedRowIndexes.length);
138
+ break;
139
+ case "entire-column":
140
+ onInsertColumn?.(sheetId, selection.range.startColumnIndex, selectedColumnIndexes.length);
141
+ break;
142
+ }
143
+ focusSheet();
144
+ },
145
+ });
146
+ };
147
+ const handleDeleteCells = () => {
148
+ if (multiSelectionKind === "mixed") {
149
+ refuseMultipleSelections("Delete");
150
+ return;
151
+ }
152
+ if (multiSelectionKind === "all-rows" || isSingleWholeRow) {
153
+ onDeleteRow?.(sheetId, selectedRowIndexes);
154
+ return;
155
+ }
156
+ if (multiSelectionKind === "all-columns" || isSingleWholeColumn) {
157
+ onDeleteColumn?.(sheetId, selectedColumnIndexes);
158
+ return;
159
+ }
160
+ shouldFocusSheetRef.current = false;
161
+ openInsertCellsDialog({
162
+ mode: "delete",
163
+ defaultChoice: defaultDeleteCellsChoice(selection.range),
164
+ onApply: (choice) => {
165
+ switch (choice) {
166
+ case "shift-left":
167
+ onDeleteCellsShiftLeft?.(sheetId, activeCell, selections);
168
+ break;
169
+ case "shift-up":
170
+ onDeleteCellsShiftUp?.(sheetId, activeCell, selections);
171
+ break;
172
+ case "entire-row":
173
+ onDeleteRow?.(sheetId, selectedRowIndexes);
174
+ break;
175
+ case "entire-column":
176
+ onDeleteColumn?.(sheetId, selectedColumnIndexes);
177
+ break;
178
+ }
179
+ focusSheet();
180
+ },
181
+ });
182
+ };
183
+ const insertCellsLabel = multiSelectionKind === "all-rows" || isSingleWholeRow
184
+ ? `Insert ${selectedRowIndexes.length} row above`
185
+ : multiSelectionKind === "all-columns" || isSingleWholeColumn
186
+ ? `Insert ${selectedColumnIndexes.length} column left`
187
+ : "Insert…";
188
+ const deleteCellsLabel = multiSelectionKind === "all-rows" || isSingleWholeRow
189
+ ? `Delete row ${generateMultiDimTitle(selectedRowIndexes, "x") ?? ""}`
190
+ : multiSelectionKind === "all-columns" || isSingleWholeColumn
191
+ ? `Delete column ${generateMultiDimTitle(selectedColumnIndexes, "y") ?? ""}`
192
+ : "Delete…";
26
193
  const hiddenColumns = selectedColumnIndexes.filter((index) => isHiddenColumn(index));
27
194
  const hiddenRows = selectedRowIndexes.filter((index) => isHiddenRow(index));
28
195
  // Check for active table
@@ -329,6 +496,10 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
329
496
  React.createElement(ClipboardIcon, null)),
330
497
  "Paste ",
331
498
  React.createElement(DropdownRightSlot, null, "\u2318V")),
499
+ onInsertCutCells ? (React.createElement(DropdownMenuItem, { onClick: () => onInsertCutCells(isRowHeader ? "rows" : isColumnHeader ? "columns" : "cells"), disabled: readonly || !hasCutSelection },
500
+ React.createElement(DropdownLeftSlot, null,
501
+ React.createElement(ClipboardIcon, null)),
502
+ "Insert Cut Cells")) : null,
332
503
  React.createElement(DropdownMenuSub, null,
333
504
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
334
505
  React.createElement(DropdownLeftSlot, null,
@@ -373,13 +544,13 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
373
544
  React.createElement(DropdownMenuItem, { onClick: () => onClearContents?.(sheetId, activeCell, selections), disabled: readonly }, "Clear contents"))),
374
545
  React.createElement(DropdownMenuSeparator, null),
375
546
  isColumnHeader ? (React.createElement(React.Fragment, null,
376
- React.createElement(DropdownMenuItem, { onClick: () => onInsertColumn?.(sheetId, activeCell.columnIndex, selectedColumnHeadersIds.length), disabled: readonly },
547
+ React.createElement(DropdownMenuItem, { onClick: insertColumnsLeft, disabled: readonly },
377
548
  React.createElement(DropdownLeftSlot, null,
378
549
  React.createElement(PlusIcon, null)),
379
550
  "Insert ",
380
551
  selectedColumnHeadersIds.length,
381
552
  " column left"),
382
- React.createElement(DropdownMenuItem, { onClick: () => onInsertColumn?.(sheetId, activeCell.columnIndex + selectedColumnHeadersIds.length, selectedColumnHeadersIds.length), disabled: readonly },
553
+ React.createElement(DropdownMenuItem, { onClick: insertColumnsRight, disabled: readonly },
383
554
  React.createElement(DropdownLeftSlot, null,
384
555
  React.createElement(PlusIcon, null)),
385
556
  "Insert ",
@@ -433,13 +604,13 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
433
604
  React.createElement(SortUpIcon, null)),
434
605
  "Sort sheet Z to A"))) : null,
435
606
  isRowHeader ? (React.createElement(React.Fragment, null,
436
- React.createElement(DropdownMenuItem, { onClick: () => onInsertRow?.(sheetId, activeCell.rowIndex, selectedRowHeadersIds.length), disabled: readonly },
607
+ React.createElement(DropdownMenuItem, { onClick: insertRowsAbove, disabled: readonly },
437
608
  React.createElement(DropdownLeftSlot, null,
438
609
  React.createElement(PlusIcon, null)),
439
610
  "Insert ",
440
611
  selectedRowHeadersIds.length,
441
612
  " row above"),
442
- React.createElement(DropdownMenuItem, { onClick: () => onInsertRow?.(sheetId, activeCell.rowIndex + selectedRowHeadersIds.length, selectedRowHeadersIds.length), disabled: readonly },
613
+ React.createElement(DropdownMenuItem, { onClick: insertRowsBelow, disabled: readonly },
443
614
  React.createElement(DropdownLeftSlot, null,
444
615
  React.createElement(PlusIcon, null)),
445
616
  "Insert ",
@@ -508,7 +679,7 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
508
679
  React.createElement(DropdownLeftSlot, null,
509
680
  React.createElement(MdOutlineFactCheck, null)),
510
681
  "Data validation")) : null)) : (React.createElement(React.Fragment, null,
511
- React.createElement(DropdownMenuSub, null,
682
+ activeTable || usesDirectInsertSubmenu ? (React.createElement(DropdownMenuSub, null,
512
683
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
513
684
  React.createElement(DropdownLeftSlot, null,
514
685
  React.createElement(PlusIcon, null)),
@@ -556,8 +727,11 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
556
727
  React.createElement(DropdownMenuItem, { onClick: () => onInsertCellsShiftDown?.(sheetId, activeCell, selections), disabled: readonly },
557
728
  React.createElement(DropdownLeftSlot, null,
558
729
  React.createElement(PlusIcon, null)),
559
- "Insert cells and shift cells down"))))),
560
- React.createElement(DropdownMenuSub, null,
730
+ "Insert cells and shift cells down")))))) : (React.createElement(DropdownMenuItem, { onClick: handleInsertCells, disabled: readonly },
731
+ React.createElement(DropdownLeftSlot, null,
732
+ React.createElement(PlusIcon, null)),
733
+ insertCellsLabel)),
734
+ activeTable || usesDirectInsertSubmenu ? (React.createElement(DropdownMenuSub, null,
561
735
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
562
736
  React.createElement(DropdownLeftSlot, null,
563
737
  React.createElement(TrashIcon, null)),
@@ -593,7 +767,10 @@ export const ContextMenu = ({ isRowHeader, isColumnHeader, activeCell, selection
593
767
  React.createElement(DropdownMenuItem, { onClick: () => onDeleteCellsShiftUp?.(sheetId, activeCell, selections), disabled: readonly },
594
768
  React.createElement(DropdownLeftSlot, null,
595
769
  React.createElement(TrashIcon, null)),
596
- "Delete cells and shift cells up"))))),
770
+ "Delete cells and shift cells up")))))) : (React.createElement(DropdownMenuItem, { onClick: handleDeleteCells, disabled: readonly },
771
+ React.createElement(DropdownLeftSlot, null,
772
+ React.createElement(TrashIcon, null)),
773
+ deleteCellsLabel)),
597
774
  React.createElement(DropdownMenuSeparator, null),
598
775
  React.createElement(DropdownMenuSub, null,
599
776
  React.createElement(DropdownMenuSubTrigger, { disabled: readonly },
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/floating-cell-editor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGvE,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAU3C,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgBrD,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,qBAAqB,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAC/C;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,aAAa,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAClD;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IACtC;;OAEG;IACH,kBAAkB,CAAC,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;IAC3D;;OAEG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;IAC7C;;OAEG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAEnD;;OAEG;IACH,+BAA+B,CAAC,EAAE,eAAe,CAAC,iCAAiC,CAAC,CAAC;IACrF;;OAEG;IACH,6BAA6B,CAAC,EAAE,eAAe,CAAC,+BAA+B,CAAC,CAAC;IACjF;;OAEG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACnD,CAAC;AAEF,eAAO,MAAM,kBAAkB,mRAkB1B,uBAAuB,uBAgW3B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/floating-cell-editor/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGvE,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAU3C,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAgBrD,MAAM,MAAM,uBAAuB,GAAG;IACpC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,qBAAqB,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAC/C;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,aAAa,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAClD;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;IACtC;;OAEG;IACH,kBAAkB,CAAC,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;IAC3D;;OAEG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;IAC7C;;OAEG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAEnD;;OAEG;IACH,+BAA+B,CAAC,EAAE,eAAe,CAAC,iCAAiC,CAAC,CAAC;IACrF;;OAEG;IACH,6BAA6B,CAAC,EAAE,eAAe,CAAC,+BAA+B,CAAC,CAAC;IACjF;;OAEG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACnD,CAAC;AAEF,eAAO,MAAM,kBAAkB,mRAkB1B,uBAAuB,uBAwW3B,CAAC"}
@@ -1,17 +1,21 @@
1
1
  import { isEqualCells } from "@rowsncolumns/grid";
2
2
  import { CheckIcon, Cross1Icon, Pencil1Icon } from "@rowsncolumns/icons";
3
3
  import { cn, IconButton } from "@rowsncolumns/ui";
4
+ import { canonicalizeCellInput, localizeCellInput } from "@rowsncolumns/utils";
4
5
  import { useSelectionAwareChangeFormatting } from "../../hooks/use-selection-aware-formatting";
5
6
  import { memo, useCallback, useDeferredValue, useMemo, useRef, useState, } from "react";
6
7
  import React from "react";
7
- import { useEditingCell, useFocusSheet, useFormulaModeValue, useSetActiveEditor, useSetFormulaMode, useSetFormulaSelections, useSpreadsheet, } from "../../atoms";
8
+ import { useEditingCell, useFocusSheet, useFormulaLocale, useFormulaModeValue, useSetActiveEditor, useSetFormulaMode, useSetFormulaSelections, useSpreadsheet, } from "../../atoms";
8
9
  import { DEFAULT_ARRAY } from "../../config";
9
10
  import { useIsomorphicLayoutEffect } from "../../hooks";
10
11
  import { FormulaInput } from "../formula-input";
11
12
  import { BackgroundColorSelector, ButtonBold, ButtonInsertColumnRight, ButtonInsertRowBelow, ButtonItalic, ButtonStrikethrough, ButtonSubscript, ButtonSuperscript, ButtonUnderline, TextColorSelector, TextHorizontalAlignSelector, ToolbarSeparator, } from "../toolbar";
12
13
  export const FloatingCellEditor = memo(({ initialValue, initialTextFormatRuns, className, activeCell, sheetId, cellFormat, theme, isDarkMode, functionDescriptions, selections = DEFAULT_ARRAY, onChange, onChangeFormatting: onChangeFormattingProp, onInsertRow, onInsertColumn, ColorSelector, ...props }) => {
13
14
  const formulaInputRef = useRef(null);
14
- const [value, setValue] = useState(initialValue ?? "");
15
+ // The stored (canonical) entry is shown and edited in the grid's formula locale; the commit
16
+ // below hands the canonical text back to the host.
17
+ const formulaLocale = useFormulaLocale();
18
+ const [value, setValue] = useState(() => localizeCellInput(initialValue ?? "", formulaLocale));
15
19
  const [textFormatRuns, setTextFormatRuns] = useState(initialTextFormatRuns);
16
20
  const setFormulaMode = useSetFormulaMode();
17
21
  const formulaMode = useFormulaModeValue();
@@ -54,15 +58,16 @@ export const FloatingCellEditor = memo(({ initialValue, initialTextFormatRuns, c
54
58
  // Set value when not editing, or when editing but not in formula mode
55
59
  if (!isEditing || !formulaMode) {
56
60
  const currentValue = formulaInputRef.current?.getValue();
57
- const newValue = initialValue ?? "";
61
+ const newValue = localizeCellInput(initialValue ?? "", formulaLocale);
58
62
  if (currentValue !== newValue && (currentValue || newValue)) {
59
63
  setValue(newValue);
60
64
  }
61
65
  }
62
- }, [initialValue, isEditing, formulaMode]);
66
+ }, [initialValue, isEditing, formulaMode, formulaLocale]);
63
67
  const handleSubmit = useCallback((value, textFormatRuns) => {
64
68
  if (editingCellCoordinate) {
65
- onChange?.(editingCellCoordinate.sheetId, editingCellCoordinate, value, textFormatRuns, initialValue, initialValue !== value, initialTextFormatRuns);
69
+ const canonicalValue = canonicalizeCellInput(value, formulaLocale);
70
+ onChange?.(editingCellCoordinate.sheetId, editingCellCoordinate, canonicalValue, textFormatRuns, initialValue, initialValue !== canonicalValue, initialTextFormatRuns);
66
71
  if (!isEqualCells(editingCellCoordinate, activeCell)) {
67
72
  setActiveCell?.(editingCellCoordinate.sheetId, editingCellCoordinate, false);
68
73
  }
@@ -76,6 +81,7 @@ export const FloatingCellEditor = memo(({ initialValue, initialTextFormatRuns, c
76
81
  stopEditing,
77
82
  focusSheet,
78
83
  onChange,
84
+ formulaLocale,
79
85
  ]);
80
86
  const onFocus = useCallback(() => {
81
87
  if (!isEditing) {
@@ -97,7 +103,7 @@ export const FloatingCellEditor = memo(({ initialValue, initialTextFormatRuns, c
97
103
  lineHeight: "normal",
98
104
  fontSize: "1rem",
99
105
  minHeight: 44,
100
- }, functionDescriptions: functionDescriptions, getMentions: getMentions, ...props })),
106
+ }, functionDescriptions: functionDescriptions, getMentions: getMentions, formulaLocale: formulaLocale, ...props })),
101
107
  isEditing ? (React.createElement(React.Fragment, null,
102
108
  React.createElement(IconButton, { tooltip: "Save", variant: "primary", type: "submit", className: "min-w-8 min-h-8" },
103
109
  React.createElement(CheckIcon, null)),
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import type { DeleteCellsChoice, InsertCellsChoice } from "../../api/multi-selection";
3
+ export type InsertCellsDialogProps = {
4
+ mode: "insert";
5
+ /** Controls visibility. */
6
+ open: boolean;
7
+ onOpenChange(open: boolean): void;
8
+ /** Pre-selected entry — Excel derives it from the selection shape (UI-06). */
9
+ defaultChoice: InsertCellsChoice;
10
+ onApply(choice: InsertCellsChoice): void;
11
+ } | {
12
+ mode: "delete";
13
+ open: boolean;
14
+ onOpenChange(open: boolean): void;
15
+ defaultChoice: DeleteCellsChoice;
16
+ onApply(choice: DeleteCellsChoice): void;
17
+ };
18
+ /**
19
+ * Excel's Insert / Delete cells dialog (right-click a cell → "Insert…" /
20
+ * "Delete…"): Shift cells right|down (left|up for Delete), Entire row, Entire
21
+ * column. UI-only — the opener turns the choice into the matching structural
22
+ * command. Header menus never show it: entire rows / columns insert directly.
23
+ */
24
+ export declare const InsertCellsDialog: React.MemoExoticComponent<(props: InsertCellsDialogProps) => React.JSX.Element>;
25
+ /**
26
+ * Host-mounted, atom-driven wrapper around {@link InsertCellsDialog}. Mount it
27
+ * once in the host alongside the other editor dialogs; the grid opens it from
28
+ * the cell context menu. Remounted per request so each opening starts from the
29
+ * request's default.
30
+ */
31
+ export declare const InsertCellsDialogHost: {
32
+ (): React.JSX.Element | null;
33
+ displayName: string;
34
+ };
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/insert-cells-dialog/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAEtE,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AAoBnC,MAAM,MAAM,sBAAsB,GAC9B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,8EAA8E;IAC9E,aAAa,EAAE,iBAAiB,CAAC;IACjC,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C,CAAC;AAEN;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,oCAAgB,sBAAsB,uBA0ElE,CAAC;AAIH;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;CAyBjC,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { Button, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogTitle, Separator, } from "@rowsncolumns/ui";
2
+ import React, { memo, useCallback, useEffect, useState } from "react";
3
+ import { useInsertCellsDialogState, useRegisterInsertCellsDialogHost, } from "../../atoms/insert-cells-dialog.atom";
4
+ const INSERT_CHOICES = [
5
+ { value: "shift-right", label: "Shift cells right" },
6
+ { value: "shift-down", label: "Shift cells down" },
7
+ { value: "entire-row", label: "Entire row" },
8
+ { value: "entire-column", label: "Entire column" },
9
+ ];
10
+ const DELETE_CHOICES = [
11
+ { value: "shift-left", label: "Shift cells left" },
12
+ { value: "shift-up", label: "Shift cells up" },
13
+ { value: "entire-row", label: "Entire row" },
14
+ { value: "entire-column", label: "Entire column" },
15
+ ];
16
+ /**
17
+ * Excel's Insert / Delete cells dialog (right-click a cell → "Insert…" /
18
+ * "Delete…"): Shift cells right|down (left|up for Delete), Entire row, Entire
19
+ * column. UI-only — the opener turns the choice into the matching structural
20
+ * command. Header menus never show it: entire rows / columns insert directly.
21
+ */
22
+ export const InsertCellsDialog = memo((props) => {
23
+ const { mode, open, onOpenChange, defaultChoice } = props;
24
+ const [choice, setChoice] = useState(defaultChoice);
25
+ // Re-seed on every open (the request decides the default).
26
+ useEffect(() => {
27
+ if (open)
28
+ setChoice(defaultChoice);
29
+ }, [open, defaultChoice]);
30
+ const commit = useCallback(() => {
31
+ if (props.mode === "insert") {
32
+ props.onApply(choice);
33
+ }
34
+ else {
35
+ props.onApply(choice);
36
+ }
37
+ onOpenChange(false);
38
+ }, [props, choice, onOpenChange]);
39
+ const title = mode === "insert" ? "Insert" : "Delete";
40
+ const choices = mode === "insert" ? INSERT_CHOICES : DELETE_CHOICES;
41
+ return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange, modal: true },
42
+ React.createElement(DialogContent, { className: "w-[300px] max-w-[92vw] pointer-events-auto" },
43
+ React.createElement(DialogTitle, null, title),
44
+ React.createElement(DialogCloseButton, null),
45
+ React.createElement(DialogBody, { className: "flex flex-col gap-4" },
46
+ React.createElement("form", { className: "flex flex-col gap-4", onSubmit: (e) => {
47
+ e.preventDefault();
48
+ commit();
49
+ } },
50
+ React.createElement("fieldset", { className: "flex flex-col gap-1 min-w-0 border-0 p-0 m-0" },
51
+ React.createElement("legend", { className: "text-xs font-medium mb-1" }, title),
52
+ choices.map((entry) => (React.createElement("label", { key: entry.value, className: "flex items-center gap-2 text-xs py-0.5 cursor-pointer" },
53
+ React.createElement("input", { type: "radio", name: `${mode}-cells-choice`, value: entry.value, checked: choice === entry.value, onChange: () => setChoice(entry.value) }),
54
+ entry.label)))),
55
+ React.createElement(Separator, null),
56
+ React.createElement("div", { className: "flex justify-end gap-2" },
57
+ React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
58
+ React.createElement(Button, { type: "submit", size: "sm", autoFocus: true }, "OK")))))));
59
+ });
60
+ InsertCellsDialog.displayName = "InsertCellsDialog";
61
+ /**
62
+ * Host-mounted, atom-driven wrapper around {@link InsertCellsDialog}. Mount it
63
+ * once in the host alongside the other editor dialogs; the grid opens it from
64
+ * the cell context menu. Remounted per request so each opening starts from the
65
+ * request's default.
66
+ */
67
+ export const InsertCellsDialogHost = () => {
68
+ useRegisterInsertCellsDialogHost();
69
+ const [request, setRequest] = useInsertCellsDialogState();
70
+ const close = useCallback(() => setRequest(null), [setRequest]);
71
+ if (!request)
72
+ return null;
73
+ const onOpenChange = (open) => {
74
+ if (!open)
75
+ close();
76
+ };
77
+ return request.mode === "insert" ? (React.createElement(InsertCellsDialog, { mode: "insert", open: true, onOpenChange: onOpenChange, defaultChoice: request.defaultChoice, onApply: request.onApply })) : (React.createElement(InsertCellsDialog, { mode: "delete", open: true, onOpenChange: onOpenChange, defaultChoice: request.defaultChoice, onApply: request.onApply }));
78
+ };
79
+ InsertCellsDialogHost.displayName = "InsertCellsDialogHost";
@@ -0,0 +1,25 @@
1
+ import React from "react";
2
+ import type { InsertFormatOption, InsertOptionsAxis } from "../../api/insert-options";
3
+ export type InsertOptionsSmartTagProps = {
4
+ /** Position/size of the selection the tag is anchored to (bottom-left). */
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ axis: InsertOptionsAxis;
10
+ /** The entry currently applied to the inserted band. */
11
+ option: InsertFormatOption;
12
+ onChooseOption(option: InsertFormatOption): void;
13
+ };
14
+ /**
15
+ * Excel's Insert Options smart tag: the small paintbrush button that appears at
16
+ * the inserted rows / columns right after an insert and offers "Format Same As
17
+ * Above / Below / Clear Formatting" (Left / Right for columns). The insert
18
+ * already applied "Same As Above / Left"; picking another entry re-formats the
19
+ * band. Same skin as the Auto Fill Options tag.
20
+ */
21
+ export declare const InsertOptionsSmartTag: {
22
+ ({ x, y, width: _width, height, axis, option, onChooseOption, }: InsertOptionsSmartTagProps): React.JSX.Element;
23
+ displayName: string;
24
+ };
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/insert-options-smart-tag/index.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,0BAA0B,GAAG;IACvC,2EAA2E;IAC3E,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,iBAAiB,CAAC;IACxB,wDAAwD;IACxD,MAAM,EAAE,kBAAkB,CAAC;IAC3B,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAClD,CAAC;AAgBF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;qEAQ/B,0BAA0B;;CA0C5B,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { CheckIcon, PaintBrush } from "@rowsncolumns/icons";
2
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItemIndicator, DropdownMenuPortal, DropdownMenuTrigger, IconButton, } from "@rowsncolumns/ui";
3
+ import React, { useState } from "react";
4
+ const ROW_ENTRIES = [
5
+ { option: "before", label: "Format Same As Above" },
6
+ { option: "after", label: "Format Same As Below" },
7
+ { option: "clear", label: "Clear Formatting" },
8
+ ];
9
+ const COLUMN_ENTRIES = [
10
+ { option: "before", label: "Format Same As Left" },
11
+ { option: "after", label: "Format Same As Right" },
12
+ { option: "clear", label: "Clear Formatting" },
13
+ ];
14
+ /**
15
+ * Excel's Insert Options smart tag: the small paintbrush button that appears at
16
+ * the inserted rows / columns right after an insert and offers "Format Same As
17
+ * Above / Below / Clear Formatting" (Left / Right for columns). The insert
18
+ * already applied "Same As Above / Left"; picking another entry re-formats the
19
+ * band. Same skin as the Auto Fill Options tag.
20
+ */
21
+ export const InsertOptionsSmartTag = ({ x, y, width: _width, height, axis, option, onChooseOption, }) => {
22
+ const [open, setOpen] = useState(false);
23
+ const entries = axis === "row" ? ROW_ENTRIES : COLUMN_ENTRIES;
24
+ return (React.createElement("div", { className: "absolute pointer-events-auto flex items-start justify-start", style: { left: x, top: y + height }, onMouseDown: (e) => e.stopPropagation(), "data-testid": "insert-options" },
25
+ React.createElement(DropdownMenu, { modal: false, open: open, onOpenChange: setOpen },
26
+ React.createElement(DropdownMenuTrigger, { asChild: true },
27
+ React.createElement(IconButton, { tooltip: "Insert Options", "aria-label": "Insert Options", className: "shadow-sm bg-rnc-background border-rnc-border border border-solid rounded-md" },
28
+ React.createElement(PaintBrush, null))),
29
+ React.createElement(DropdownMenuPortal, null,
30
+ React.createElement(DropdownMenuContent, { align: "start", onCloseAutoFocus: (event) => event.preventDefault() }, entries.map((entry) => (React.createElement(DropdownMenuCheckboxItem, { key: entry.option, checked: option === entry.option, onSelect: () => onChooseOption(entry.option) },
31
+ React.createElement(DropdownMenuItemIndicator, null,
32
+ React.createElement(CheckIcon, null)),
33
+ entry.label))))))));
34
+ };
35
+ InsertOptionsSmartTag.displayName = "InsertOptionsSmartTag";
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ /**
3
+ * Excel's drag-move overwrite prompt ("There's already data here. Do you want to
4
+ * replace it?"). Mount once next to the grid; while mounted, a plain drag-move
5
+ * onto non-empty cells waits for OK / Cancel here instead of `window.confirm`.
6
+ */
7
+ export declare const MoveOverwriteConfirmDialogHost: React.MemoExoticComponent<() => React.JSX.Element>;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/move-overwrite-confirm-dialog/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAe,MAAM,OAAO,CAAC;AAOpC;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,oDAgCzC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { Cross2Icon } from "@rowsncolumns/icons";
2
+ import { Button, Dialog, DialogBody, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogTitle, Stack, } from "@rowsncolumns/ui";
3
+ import React, { memo } from "react";
4
+ import { useMoveOverwriteConfirmState, useRegisterMoveOverwriteConfirmHost, } from "../../atoms/move-overwrite-confirm.atom";
5
+ /**
6
+ * Excel's drag-move overwrite prompt ("There's already data here. Do you want to
7
+ * replace it?"). Mount once next to the grid; while mounted, a plain drag-move
8
+ * onto non-empty cells waits for OK / Cancel here instead of `window.confirm`.
9
+ */
10
+ export const MoveOverwriteConfirmDialogHost = memo(() => {
11
+ useRegisterMoveOverwriteConfirmHost();
12
+ const [request] = useMoveOverwriteConfirmState();
13
+ const isOpen = Boolean(request);
14
+ const answer = (replace) => request?.resolve(replace);
15
+ return (React.createElement(Dialog, { modal: true, open: isOpen, onOpenChange: (open) => {
16
+ if (!open)
17
+ answer(false);
18
+ } },
19
+ React.createElement(DialogContent, null,
20
+ React.createElement(DialogTitle, null, "Replace destination cells?"),
21
+ React.createElement(DialogDescription, null, request?.message),
22
+ React.createElement(DialogBody, null,
23
+ React.createElement(Stack, { className: "gap-2" },
24
+ React.createElement(Button, { variant: "primary", onClick: () => answer(true) }, "OK"),
25
+ React.createElement(Button, { onClick: () => answer(false) }, "Cancel"))),
26
+ React.createElement(DialogClose, { asChild: true },
27
+ React.createElement(DialogCloseButton, null,
28
+ React.createElement(Cross2Icon, null))))));
29
+ });
30
+ MoveOverwriteConfirmDialogHost.displayName = "MoveOverwriteConfirmDialogHost";
@@ -5,5 +5,5 @@ import type { SelectionNewProps } from "./types";
5
5
  * @param param0
6
6
  * @returns
7
7
  */
8
- export declare const Selection: React.MemoExoticComponent<({ x, y, width, height, fillHandleSize, showFillHandle, offset, stroke, strokeLeftColor, strokeTopColor, strokeRightColor, strokeBottomColor, strokeWidth, strokeTopWidth, strokeRightWidth, strokeBottomWidth, strokeLeftWidth, strokeStyle, fillOpacity, draggable, isDragging, borderCoverWidth, type, range, title, keepTitleWithinBounds, keepTitleWithinVerticalBounds, flipTitleToBottom, table, pivotTable, onMouseDownTitle, onMouseDownResizeHandle, onMouseDownMoveHandle, TitleComponent, onMouseDown, onRequestEditTable, onRequestEditPivotTable, onFlashEnd, zIndex, index, id, showResizeHandles, resizeHandleBorderColor, resizeHandleSize, fill, duration, selectionCount, enableMagicFill, sheetId, activeCell, selection, MagicFillComponent, metadata, readonly, showPasteMenu, onSplitTextToColumns, agentSuggestion, isHovered, MagicFillContent, QuickEdit, PasteMenu, FillOptionsMenu, fillOptions, onChooseFillType, onOpenFillSeriesDialog, onCloseFillOptions, SuggestionAction, onRemoveQuickEdit, onAddQuickEdit, }: SelectionNewProps) => React.JSX.Element>;
8
+ export declare const Selection: React.MemoExoticComponent<({ x, y, width, height, fillHandleSize, showFillHandle, offset, stroke, strokeLeftColor, strokeTopColor, strokeRightColor, strokeBottomColor, strokeWidth, strokeTopWidth, strokeRightWidth, strokeBottomWidth, strokeLeftWidth, strokeStyle, fillOpacity, draggable, isDragging, borderCoverWidth, type, range, title, keepTitleWithinBounds, keepTitleWithinVerticalBounds, flipTitleToBottom, table, pivotTable, onMouseDownTitle, onMouseDownResizeHandle, onMouseDownMoveHandle, TitleComponent, onMouseDown, onRequestEditTable, onRequestEditPivotTable, onFlashEnd, zIndex, index, id, showResizeHandles, resizeHandleBorderColor, resizeHandleSize, fill, duration, selectionCount, enableMagicFill, sheetId, activeCell, selection, MagicFillComponent, metadata, readonly, showPasteMenu, onSplitTextToColumns, agentSuggestion, isHovered, MagicFillContent, QuickEdit, PasteMenu, FillOptionsMenu, fillOptions, onChooseFillType, onOpenFillSeriesDialog, onCloseFillOptions, InsertOptionsSmartTag, insertOptions, onChooseInsertOption, SuggestionAction, onRemoveQuickEdit, onAddQuickEdit, }: SelectionNewProps) => React.JSX.Element>;
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/selection/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAcpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,SAAS,+gCAuEjB,iBAAiB,uBA0TrB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/selection/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAepC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,SAAS,2kCA0EjB,iBAAiB,uBAqUrB,CAAC"}