@rowsncolumns/rnc-engine 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/command-handlers.d.ts +16 -0
- package/dist/esm/command-handlers.d.ts.map +1 -1
- package/dist/esm/command-handlers.js +23 -0
- package/dist/esm/fold.d.ts.map +1 -1
- package/dist/esm/fold.js +5 -1
- package/dist/esm/index.d.ts +39 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/paste-reapply.d.ts +11 -0
- package/dist/esm/paste-reapply.d.ts.map +1 -0
- package/dist/esm/paste-reapply.js +37 -0
- package/dist/esm/paste-special.d.ts +78 -2
- package/dist/esm/paste-special.d.ts.map +1 -1
- package/dist/esm/paste-special.js +141 -1
- package/dist/esm/use-engine-core.d.ts +28 -2
- package/dist/esm/use-engine-core.d.ts.map +1 -1
- package/dist/esm/use-engine-core.js +225 -22
- package/dist/esm/wasm/rnc_wasm.d.ts +50 -36
- package/dist/esm/wasm/rnc_wasm.js +42 -0
- package/dist/esm/wasm/rnc_wasm_bg.wasm +0 -0
- package/dist/esm/wasm/rnc_wasm_bg.wasm.d.ts +38 -36
- package/package.json +6 -6
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { CellNode, CellRangeNode, isCellCoordinate, isCellNode, makeKey, } from "@rowsncolumns/dag";
|
|
4
4
|
import { DEFAULT_ACTIVE_CELL, DEFAULT_ARRAY, defaultSpreadsheetTheme, getBandedCells, getInitialActiveSheetId, ICON_SET_COLORS, ICON_SET_GLYPHS, useFocusEmbedOrChart, useFonts, useGetSheetDimensions, useSetTableAutoExpansion, useSetCalculatedColumnFill, } from "@rowsncolumns/spreadsheet";
|
|
5
5
|
import { applyAdvancedFilterPlan, applySubtotalPlan, CellXfsRegistry, collectPaintMerges, generateDataFromClipboard, getBandedCellFormat, layerBandedFormat, paintSourceForActiveCell, pickFormula, pickValue, planAdvancedFilter, planSubtotals, resolvePasteSpecialOptions, SharedStringRegistry, spreadMergeAnchorFormats, tilePaintMerges, transpose, useOnCopy, previewTableStyle, useOnTraceDependentsPrecedents, useShowError, CIRCULAR_REFERENCE_WARNING_TITLE, CircularReferenceTracker, formatCircularReferenceWarning, getDefaultCellFormat, } from "@rowsncolumns/spreadsheet-state";
|
|
6
|
-
import { getFormulaLocale, normalizeExternalUrl, resolveDefaultHorizontalAlignment, } from "@rowsncolumns/utils";
|
|
6
|
+
import { getFormulaLocale, normalizeExternalUrl, resolveDefaultHorizontalAlignment, uuidString, } from "@rowsncolumns/utils";
|
|
7
7
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { createCommandHandlers, rejectionDialog, } from "./command-handlers";
|
|
9
9
|
import { emptyRenderState, foldCommit, foldCommitStructuralOnly, foldWindow, foldWindows, frozenBandRanges, reconcileSheets, } from "./fold";
|
|
@@ -11,10 +11,14 @@ import { RncEngine, } from "./index";
|
|
|
11
11
|
import { cellKey, parseCellKey } from "./keys";
|
|
12
12
|
import { fallbackSheetAfterDelete } from "./sheet-fallback";
|
|
13
13
|
import { rescopeRulesAfterMove } from "./move-rescope";
|
|
14
|
-
import { applyPasteSpecialToTiles, clipboardCellToPasteCell, isSkippedPasteCell, } from "./paste-special";
|
|
14
|
+
import { applyPasteSpecialToTiles, clipboardCellToPasteCell, clipboardFormats, columnWidthCopies, isSkippedPasteCell, noteCopies, pasteLinkCells, reattachClipboardFormats, validationCopies, withoutBorders, } from "./paste-special";
|
|
15
15
|
import { pastedMerges } from "./paste-merges";
|
|
16
|
+
import { replayPasteEvent, snapshotClipboard } from "./paste-reapply";
|
|
16
17
|
import { tileClipboardToSelection } from "./paste-tiling";
|
|
17
18
|
import { windowRectReader } from "./rect-reader";
|
|
19
|
+
/** The width a column at no stored width takes (the grid's default, as `onCopy` reads it). */
|
|
20
|
+
const DEFAULT_PASTE_COLUMN_WIDTH = 100;
|
|
21
|
+
const EMPTY_ALLOW_EDIT_RANGES = [];
|
|
18
22
|
/** Minimum extra rows/columns to read on EACH side of the visible range so a small scroll reveals
|
|
19
23
|
* already-loaded cells (no flash) before the next `readWindow`. The buffer actually used scales with
|
|
20
24
|
* the viewport (see {@link windowBuffer}) and is never smaller than this. The window size stays
|
|
@@ -1683,6 +1687,8 @@ export function useEngineCore(options) {
|
|
|
1683
1687
|
endColumnIndex: right,
|
|
1684
1688
|
};
|
|
1685
1689
|
}, []);
|
|
1690
|
+
// Stable identity: reads through `engineRef`, so the grid's formula-bar effect never re-runs for it.
|
|
1691
|
+
const getArrayFormula = useCallback((sheetId, rowIndex, columnIndex) => engineRef.current?.arrayFormulaAt(sheetId, rowIndex, columnIndex), []);
|
|
1686
1692
|
const getDataColumnCount = useCallback((sheetId) => {
|
|
1687
1693
|
const rows = sheetData[String(sheetId)];
|
|
1688
1694
|
let max = 0;
|
|
@@ -2333,6 +2339,7 @@ export function useEngineCore(options) {
|
|
|
2333
2339
|
showFirstColumn: t.showFirstColumn,
|
|
2334
2340
|
showLastColumn: t.showLastColumn,
|
|
2335
2341
|
totalRow: t.totalRow ?? false,
|
|
2342
|
+
headerRow: t.headerRow !== false,
|
|
2336
2343
|
range: { ...t.range, sheetId },
|
|
2337
2344
|
...t.bandedRange,
|
|
2338
2345
|
})),
|
|
@@ -2571,6 +2578,7 @@ export function useEngineCore(options) {
|
|
|
2571
2578
|
paintFacetsRef.current = { bandedRanges: facets.bandedRanges, tables };
|
|
2572
2579
|
const namedRanges = facets.namedRanges;
|
|
2573
2580
|
const protectedRanges = facets.protectedRanges;
|
|
2581
|
+
const allowEditRanges = facets.allowEditRanges ?? EMPTY_ALLOW_EDIT_RANGES;
|
|
2574
2582
|
const embeds = facets.embeds;
|
|
2575
2583
|
const slicers = facets.slicers;
|
|
2576
2584
|
// Citations (workbook-level collection; a cell links to one via `citationId`). The grid renders the
|
|
@@ -2680,7 +2688,79 @@ export function useEngineCore(options) {
|
|
|
2680
2688
|
// cellXfs style-refs to an inline `CellFormat` on each parsed cell, which we forward per cell below;
|
|
2681
2689
|
// the engine applies it via `set_style` (streams on `styleChanges`). Only a still-unresolved
|
|
2682
2690
|
// `StyleReference` (no matching cellXfs entry) is skipped, since a bare ref can't cross to the engine.
|
|
2683
|
-
const
|
|
2691
|
+
const pasteInto = useCallback(async (e, sourceSheetId, destinationSheetId, copiedSelections, pasteActiveCell, pasteSelections, _finalSelections, isCutOperation) => {
|
|
2692
|
+
// Paste Special. The grid stashes the request under synthetic clipboard keys (same as
|
|
2693
|
+
// `useSpreadsheetState`; `resolvePasteSpecialOptions` reads both the legacy single mode and the
|
|
2694
|
+
// full options).
|
|
2695
|
+
const pasteSpecial = resolvePasteSpecialOptions(e?.clipboardData);
|
|
2696
|
+
// The copied range, kept for Paste Link, the notes / validation / column widths and the cut
|
|
2697
|
+
// source-clear below. A foreign clipboard (another app) has none.
|
|
2698
|
+
const from = copiedSelections?.[0]?.range;
|
|
2699
|
+
const pasteSource = from && sourceSheetId != null
|
|
2700
|
+
? { sheetId: sourceSheetId, sheetName: getSheetName(sourceSheetId), range: from }
|
|
2701
|
+
: undefined;
|
|
2702
|
+
// Destination top-left = the paste selection's start (else the active cell).
|
|
2703
|
+
const to = pasteSelections?.[pasteSelections.length - 1]?.range;
|
|
2704
|
+
const target = to
|
|
2705
|
+
? { rowIndex: to.startRowIndex, columnIndex: to.startColumnIndex }
|
|
2706
|
+
: pasteActiveCell;
|
|
2707
|
+
// Excel's Column widths / Keep Source Column Widths: each destination column takes the width
|
|
2708
|
+
// of the copied column it stands for (a source column at the sheet default takes that default).
|
|
2709
|
+
const copySourceColumnWidths = (source) => {
|
|
2710
|
+
const defaultWidth = state.sheets.find((s) => s.sheetId === source.sheetId)?.defaultColumnWidth ??
|
|
2711
|
+
DEFAULT_PASTE_COLUMN_WIDTH;
|
|
2712
|
+
for (const copy of columnWidthCopies(source.range, target.columnIndex)) {
|
|
2713
|
+
onCommand({
|
|
2714
|
+
command: "resize",
|
|
2715
|
+
sheetId: destinationSheetId,
|
|
2716
|
+
indexes: [copy.columnIndex],
|
|
2717
|
+
dimension: getColumnWidth(source.sheetId, copy.sourceColumnIndex) ?? defaultWidth,
|
|
2718
|
+
axis: "y",
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
// Paste Special → Comments and Notes / Validation / Column widths paste no cells: only the
|
|
2723
|
+
// copied range's notes, data-validation rules or column widths travel, in one undo step.
|
|
2724
|
+
// They need a copied range (Excel offers them only after a copy, never after a cut).
|
|
2725
|
+
if (pasteSpecial.notesOnly ||
|
|
2726
|
+
pasteSpecial.validationOnly ||
|
|
2727
|
+
pasteSpecial.columnWidths === "only") {
|
|
2728
|
+
if (!pasteSource || isCutOperation)
|
|
2729
|
+
return;
|
|
2730
|
+
batchHistory(() => {
|
|
2731
|
+
if (pasteSpecial.notesOnly) {
|
|
2732
|
+
const notes = noteCopies(pasteSource.range, target, (r, c) => getNote(pasteSource.sheetId, r, c), pasteSpecial.transpose);
|
|
2733
|
+
for (const { cell, note } of notes) {
|
|
2734
|
+
onCommand({
|
|
2735
|
+
command: "update-note",
|
|
2736
|
+
sheetId: destinationSheetId,
|
|
2737
|
+
activeCell: cell,
|
|
2738
|
+
note,
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
else if (pasteSpecial.validationOnly) {
|
|
2743
|
+
const rules = validationCopies(facets.dataValidations, pasteSource, destinationSheetId, target);
|
|
2744
|
+
for (const rule of rules) {
|
|
2745
|
+
onCommand({
|
|
2746
|
+
command: "create-data-validation",
|
|
2747
|
+
sheetId: destinationSheetId,
|
|
2748
|
+
range: rule.ranges[0],
|
|
2749
|
+
rule: { ...rule, id: uuidString() },
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
else {
|
|
2754
|
+
copySourceColumnWidths(pasteSource);
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
// Paste Link refers back to the copied cells, so it needs a copied range — Excel greys it out
|
|
2760
|
+
// after a cut.
|
|
2761
|
+
const pasteLink = Boolean(pasteSpecial.link);
|
|
2762
|
+
if (pasteLink && (!pasteSource || isCutOperation))
|
|
2763
|
+
return;
|
|
2684
2764
|
// A cut is a MOVE: Excel keeps the moved formulas' text verbatim (`=B1*2` cut from A1 to A5
|
|
2685
2765
|
// still reads `=B1*2`) and only repoints references INTO the moved block — the engine does
|
|
2686
2766
|
// that remap (`repoint_formulas_into_moved_block`), so the clipboard parse must not
|
|
@@ -2692,7 +2772,7 @@ export function useEngineCore(options) {
|
|
|
2692
2772
|
// Pasted image FILES arrive as floating pictures anchored at the active cell (Excel /
|
|
2693
2773
|
// Google Sheets Ctrl+V of a picture) — the same lossless `create-embed` the Insert → Image
|
|
2694
2774
|
// dialog emits, one undo entry for all of them, the first one selected like a new chart.
|
|
2695
|
-
const pastedEmbeds = parsed?.embeds ?? [];
|
|
2775
|
+
const pastedEmbeds = pasteLink ? [] : parsed?.embeds ?? [];
|
|
2696
2776
|
if (pastedEmbeds.length) {
|
|
2697
2777
|
batchHistory(() => {
|
|
2698
2778
|
for (const spec of pastedEmbeds) {
|
|
@@ -2701,35 +2781,42 @@ export function useEngineCore(options) {
|
|
|
2701
2781
|
});
|
|
2702
2782
|
focusEmbedOrChart(pastedEmbeds[0].embedId);
|
|
2703
2783
|
}
|
|
2704
|
-
|
|
2784
|
+
// Paste Link: one reference formula per copied cell instead of the copied cells.
|
|
2785
|
+
let results = pasteLink && pasteSource
|
|
2786
|
+
? pasteLinkCells(pasteSource, destinationSheetId)
|
|
2787
|
+
: parsed?.results ?? [];
|
|
2705
2788
|
if (!results.length)
|
|
2706
2789
|
return;
|
|
2707
2790
|
// The clipboard's own size and merges, before Paste Special / tiling reshape `results`.
|
|
2708
2791
|
const clipRows = results.length;
|
|
2709
2792
|
const clipCols = Math.max(0, ...results.map((row) => row.length));
|
|
2710
2793
|
const clipboardMerges = parsed?.merges ?? [];
|
|
2711
|
-
//
|
|
2712
|
-
//
|
|
2713
|
-
//
|
|
2714
|
-
// "resolve a formula to its value"); Formats rides a `format_only` flag the engine honors (apply
|
|
2715
|
-
// each cell's format, leave destination values intact); Operation / Skip blanks need the
|
|
2794
|
+
// Transpose/Values/Formulas transform the parsed cells UI-side (the engine can't "resolve a
|
|
2795
|
+
// formula to its value"); Formats rides a `format_only` flag the engine honors (apply each
|
|
2796
|
+
// cell's format, leave destination values intact); Operation / Skip blanks need the
|
|
2716
2797
|
// destination cells and are folded in after tiling (see `applyPasteSpecialToTiles`).
|
|
2717
|
-
const pasteSpecial = resolvePasteSpecialOptions(e?.clipboardData);
|
|
2718
2798
|
if (pasteSpecial.transpose) {
|
|
2719
2799
|
results = transpose(results);
|
|
2720
2800
|
}
|
|
2801
|
+
// Values & Number Formatting, Formulas & Number Formatting, Values & Source Formatting: the
|
|
2802
|
+
// source formats are read before the pick, which strips them.
|
|
2803
|
+
const keptFormats = pasteSpecial.numberFormats || pasteSpecial.sourceFormats
|
|
2804
|
+
? clipboardFormats(results)
|
|
2805
|
+
: undefined;
|
|
2721
2806
|
if (pasteSpecial.paste === "values") {
|
|
2722
2807
|
results = pickValue(results, destinationSheetId, initial.current.locale);
|
|
2723
2808
|
}
|
|
2724
2809
|
else if (pasteSpecial.paste === "formulas") {
|
|
2725
2810
|
results = pickFormula(results, destinationSheetId, initial.current.locale);
|
|
2726
2811
|
}
|
|
2812
|
+
if (keptFormats) {
|
|
2813
|
+
results = reattachClipboardFormats(results, keptFormats, pasteSpecial.sourceFormats ? "sourceFormats" : "numberFormats");
|
|
2814
|
+
}
|
|
2815
|
+
// All except borders (Paste Options ▸ No Borders): the destination keeps its own borders.
|
|
2816
|
+
if (pasteSpecial.noBorders) {
|
|
2817
|
+
results = withoutBorders(results);
|
|
2818
|
+
}
|
|
2727
2819
|
const formatOnly = pasteSpecial.paste === "formats";
|
|
2728
|
-
// Destination top-left = the paste selection's start (else the active cell).
|
|
2729
|
-
const to = pasteSelections?.[pasteSelections.length - 1]?.range;
|
|
2730
|
-
const target = to
|
|
2731
|
-
? { rowIndex: to.startRowIndex, columnIndex: to.startColumnIndex }
|
|
2732
|
-
: pasteActiveCell;
|
|
2733
2820
|
// A paste selection larger than the clipboard tiles it (copy G1, select G2:G5, paste fills all
|
|
2734
2821
|
// four), wrapping like `useOnPaste`. Tiling UI-side keeps the engine's `paste` a plain rectangle
|
|
2735
2822
|
// and the whole paste ONE undo entry; the tiles' formulas are shifted here (see `paste-tiling`).
|
|
@@ -2739,8 +2826,6 @@ export function useEngineCore(options) {
|
|
|
2739
2826
|
results = tileClipboardToSelection(results, to, pasteActiveCell, destinationSheetId, getSheetName(destinationSheetId), initial.current.locale);
|
|
2740
2827
|
}
|
|
2741
2828
|
const tiles = applyPasteSpecialToTiles(results, target, destinationSheetId, pasteSpecial, { getCellData, getUserEnteredFormat }, initial.current.locale);
|
|
2742
|
-
// The copied range top-left, kept for the cut source-clear below.
|
|
2743
|
-
const from = copiedSelections?.[0]?.range;
|
|
2744
2829
|
const cutMove = isCutOperation && from && sourceSheetId != null;
|
|
2745
2830
|
// COPY: `generateDataFromClipboard` has ALREADY re-anchored each relative formula ref to its
|
|
2746
2831
|
// destination (its `moveFormula(sourceCell → destinationCell)` pass overwrites the cell's `fv`), so
|
|
@@ -2759,8 +2844,9 @@ export function useEngineCore(options) {
|
|
|
2759
2844
|
// The clipboard rectangle is row-major from the copied range's top-left, so the note of the
|
|
2760
2845
|
// tile's source cell is looked up by offset; tiled repeats (copy only) reuse the modular offset.
|
|
2761
2846
|
const noteAt = (i, j) => {
|
|
2762
|
-
// A transposed paste no longer maps tiles back to source cells by offset
|
|
2763
|
-
|
|
2847
|
+
// A transposed paste no longer maps tiles back to source cells by offset; a link carries
|
|
2848
|
+
// only the reference.
|
|
2849
|
+
if (!from || sourceSheetId == null || pasteSpecial.transpose || pasteLink)
|
|
2764
2850
|
return undefined;
|
|
2765
2851
|
const height = from.endRowIndex - from.startRowIndex + 1;
|
|
2766
2852
|
const width = from.endColumnIndex - from.startColumnIndex + 1;
|
|
@@ -2790,10 +2876,11 @@ export function useEngineCore(options) {
|
|
|
2790
2876
|
// and a cut moves its own merges in the engine (rowsncolumns/spreadsheet#1234).
|
|
2791
2877
|
const merges = !isCutOperation &&
|
|
2792
2878
|
!pasteSpecial.transpose &&
|
|
2879
|
+
!pasteLink &&
|
|
2793
2880
|
(pasteSpecial.paste === "all" || formatOnly)
|
|
2794
2881
|
? pastedMerges(clipboardMerges, pasteActiveCell, target, clipRows, clipCols, tiles.length, Math.max(0, ...tiles.map((row) => row.length)))
|
|
2795
2882
|
: [];
|
|
2796
|
-
|
|
2883
|
+
const pasteCommand = {
|
|
2797
2884
|
command: "paste",
|
|
2798
2885
|
sheetId: destinationSheetId,
|
|
2799
2886
|
target,
|
|
@@ -2810,17 +2897,99 @@ export function useEngineCore(options) {
|
|
|
2810
2897
|
...(autoCalculatedColumnsRef.current === false
|
|
2811
2898
|
? { autoCalculatedColumns: false }
|
|
2812
2899
|
: {}),
|
|
2813
|
-
}
|
|
2900
|
+
};
|
|
2901
|
+
// Keep Source Column Widths: the cells and the widths are one undo step. A transposed block
|
|
2902
|
+
// no longer lines its columns up with the copied ones, so it takes no widths.
|
|
2903
|
+
if (pasteSpecial.columnWidths === "also" && pasteSource && !pasteSpecial.transpose) {
|
|
2904
|
+
batchHistory(() => {
|
|
2905
|
+
onCommand(pasteCommand);
|
|
2906
|
+
copySourceColumnWidths(pasteSource);
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
else {
|
|
2910
|
+
onCommand(pasteCommand);
|
|
2911
|
+
}
|
|
2814
2912
|
}, [
|
|
2815
2913
|
getSheetName,
|
|
2816
2914
|
getCellData,
|
|
2817
2915
|
getUserEnteredFormat,
|
|
2818
2916
|
getNote,
|
|
2917
|
+
getColumnWidth,
|
|
2819
2918
|
onCommand,
|
|
2820
2919
|
batchHistory,
|
|
2821
2920
|
focusEmbedOrChart,
|
|
2822
2921
|
getSheetDimensions,
|
|
2922
|
+
facets,
|
|
2923
|
+
state.sheets,
|
|
2823
2924
|
]);
|
|
2925
|
+
// The last paste, kept for Excel's Paste Options button (`onReapplyPaste`): its clipboard, its
|
|
2926
|
+
// placement arguments and the undo depth right after it. A cut is a move and has no Paste Options.
|
|
2927
|
+
const lastPasteRef = useRef(null);
|
|
2928
|
+
const onPaste = useCallback(async (e, sourceSheetId, destinationSheetId, copiedSelections, pasteActiveCell, pasteSelections, finalSelections, isCutOperation) => {
|
|
2929
|
+
// Read the clipboard before the first await: a browser paste event's data is gone after it.
|
|
2930
|
+
const entries = isCutOperation ? null : snapshotClipboard(e?.clipboardData);
|
|
2931
|
+
const depthBefore = engineRef.current?.undoDepth() ?? 0;
|
|
2932
|
+
lastPasteRef.current = null;
|
|
2933
|
+
await pasteInto(e, sourceSheetId, destinationSheetId, copiedSelections, pasteActiveCell, pasteSelections, finalSelections, isCutOperation);
|
|
2934
|
+
const depth = engineRef.current?.undoDepth() ?? 0;
|
|
2935
|
+
if (entries && depth > depthBefore) {
|
|
2936
|
+
lastPasteRef.current = {
|
|
2937
|
+
entries,
|
|
2938
|
+
args: [
|
|
2939
|
+
sourceSheetId,
|
|
2940
|
+
destinationSheetId,
|
|
2941
|
+
copiedSelections,
|
|
2942
|
+
pasteActiveCell,
|
|
2943
|
+
pasteSelections,
|
|
2944
|
+
finalSelections,
|
|
2945
|
+
],
|
|
2946
|
+
depth,
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
}, [pasteInto]);
|
|
2950
|
+
// Paste Options: undo the last paste and paste its clipboard again as `options`, at the same place.
|
|
2951
|
+
// The undone paste only moves to the redo stack, and the new paste clears it, so the result is ONE
|
|
2952
|
+
// undo step. Refused (false) once anything else has been written since the paste.
|
|
2953
|
+
const onReapplyPaste = useCallback(async (options) => {
|
|
2954
|
+
const last = lastPasteRef.current;
|
|
2955
|
+
const engine = engineRef.current;
|
|
2956
|
+
if (!last || !engine || engine.undoDepth() !== last.depth) {
|
|
2957
|
+
lastPasteRef.current = null;
|
|
2958
|
+
return false;
|
|
2959
|
+
}
|
|
2960
|
+
// The pasted range stays selected, as after the paste (the undo alone would restore the
|
|
2961
|
+
// selection from before it).
|
|
2962
|
+
const view = viewRef.current;
|
|
2963
|
+
undo();
|
|
2964
|
+
await onPaste(replayPasteEvent(last.entries, options), ...last.args, false);
|
|
2965
|
+
if (view)
|
|
2966
|
+
applyView(view);
|
|
2967
|
+
return lastPasteRef.current !== null;
|
|
2968
|
+
}, [onPaste, undo, applyView]);
|
|
2969
|
+
// Insert-copy. Two commits, the insert first: the engine refuses a band that would tear a merge /
|
|
2970
|
+
// table / array or push data off the sheet, and a refused insert must not let the paste overwrite
|
|
2971
|
+
// the destination (a `batch` runs every member whatever the one before it refused).
|
|
2972
|
+
const onInsertCopiedCells = useCallback(async (e, sheetId, from, to, shift) => {
|
|
2973
|
+
const direction = shift ??
|
|
2974
|
+
(to.range.startRowIndex === from.range.startRowIndex ? "right" : "down");
|
|
2975
|
+
const target = {
|
|
2976
|
+
rowIndex: to.range.startRowIndex,
|
|
2977
|
+
columnIndex: to.range.startColumnIndex,
|
|
2978
|
+
};
|
|
2979
|
+
const commit = onCommand({
|
|
2980
|
+
command: direction === "down"
|
|
2981
|
+
? "insert-cells-shift-down"
|
|
2982
|
+
: "insert-cells-shift-right",
|
|
2983
|
+
sheetId,
|
|
2984
|
+
activeCell: target,
|
|
2985
|
+
selections: [to],
|
|
2986
|
+
});
|
|
2987
|
+
if (!commit || commit.rejections?.length)
|
|
2988
|
+
return;
|
|
2989
|
+
// Opening the band slides a source that sits wholly in it; the paste reads the source's
|
|
2990
|
+
// notes from where they now are (the cells themselves already ride on the clipboard).
|
|
2991
|
+
await pasteInto(e, sheetId, sheetId, [{ ...from, range: rangeAfterInsertBand(from.range, to.range, direction) }], target, [to], [to], false);
|
|
2992
|
+
}, [onCommand, pasteInto]);
|
|
2824
2993
|
return useMemo(() => ({
|
|
2825
2994
|
...state,
|
|
2826
2995
|
...handlers,
|
|
@@ -2890,6 +3059,7 @@ export function useEngineCore(options) {
|
|
|
2890
3059
|
getSheetColumnCount,
|
|
2891
3060
|
getDataRowCount,
|
|
2892
3061
|
getDataRegion,
|
|
3062
|
+
getArrayFormula,
|
|
2893
3063
|
findAll,
|
|
2894
3064
|
getSeriesValuesFromRange,
|
|
2895
3065
|
getDomainValuesFromRange,
|
|
@@ -2929,6 +3099,7 @@ export function useEngineCore(options) {
|
|
|
2929
3099
|
tables,
|
|
2930
3100
|
namedRanges,
|
|
2931
3101
|
protectedRanges,
|
|
3102
|
+
allowEditRanges,
|
|
2932
3103
|
customTableStyles: facets.customTableStyles,
|
|
2933
3104
|
embeds,
|
|
2934
3105
|
slicers,
|
|
@@ -2988,6 +3159,8 @@ export function useEngineCore(options) {
|
|
|
2988
3159
|
onDeleteDataValidationRules,
|
|
2989
3160
|
onCopy,
|
|
2990
3161
|
onPaste,
|
|
3162
|
+
onInsertCopiedCells,
|
|
3163
|
+
onReapplyPaste,
|
|
2991
3164
|
}), [
|
|
2992
3165
|
formulaLocale,
|
|
2993
3166
|
state,
|
|
@@ -3051,6 +3224,7 @@ export function useEngineCore(options) {
|
|
|
3051
3224
|
getSheetColumnCount,
|
|
3052
3225
|
getDataRowCount,
|
|
3053
3226
|
getDataRegion,
|
|
3227
|
+
getArrayFormula,
|
|
3054
3228
|
findAll,
|
|
3055
3229
|
getSeriesValuesFromRange,
|
|
3056
3230
|
getDomainValuesFromRange,
|
|
@@ -3074,6 +3248,7 @@ export function useEngineCore(options) {
|
|
|
3074
3248
|
tables,
|
|
3075
3249
|
namedRanges,
|
|
3076
3250
|
protectedRanges,
|
|
3251
|
+
allowEditRanges,
|
|
3077
3252
|
facets.customTableStyles,
|
|
3078
3253
|
embeds,
|
|
3079
3254
|
slicers,
|
|
@@ -3102,5 +3277,33 @@ export function useEngineCore(options) {
|
|
|
3102
3277
|
onClearHistory,
|
|
3103
3278
|
onCopy,
|
|
3104
3279
|
onPaste,
|
|
3280
|
+
onInsertCopiedCells,
|
|
3281
|
+
onReapplyPaste,
|
|
3105
3282
|
]);
|
|
3106
3283
|
}
|
|
3284
|
+
/** Where `range` sits once cells open at `band` and shift `direction` (a range wholly in the band's
|
|
3285
|
+
* lanes, at or past its first line, slides by the band's size; anything else stays put). */
|
|
3286
|
+
export const rangeAfterInsertBand = (range, band, direction) => {
|
|
3287
|
+
if (direction === "down") {
|
|
3288
|
+
const inLanes = range.startColumnIndex >= band.startColumnIndex &&
|
|
3289
|
+
range.endColumnIndex <= band.endColumnIndex;
|
|
3290
|
+
if (!inLanes || range.startRowIndex < band.startRowIndex)
|
|
3291
|
+
return range;
|
|
3292
|
+
const size = band.endRowIndex - band.startRowIndex + 1;
|
|
3293
|
+
return {
|
|
3294
|
+
...range,
|
|
3295
|
+
startRowIndex: range.startRowIndex + size,
|
|
3296
|
+
endRowIndex: range.endRowIndex + size,
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
const inLanes = range.startRowIndex >= band.startRowIndex &&
|
|
3300
|
+
range.endRowIndex <= band.endRowIndex;
|
|
3301
|
+
if (!inLanes || range.startColumnIndex < band.startColumnIndex)
|
|
3302
|
+
return range;
|
|
3303
|
+
const size = band.endColumnIndex - band.startColumnIndex + 1;
|
|
3304
|
+
return {
|
|
3305
|
+
...range,
|
|
3306
|
+
startColumnIndex: range.startColumnIndex + size,
|
|
3307
|
+
endColumnIndex: range.endColumnIndex + size,
|
|
3308
|
+
};
|
|
3309
|
+
};
|
|
@@ -17,6 +17,11 @@ export class CollabSpreadsheetEngine {
|
|
|
17
17
|
* `syncRemote()` to rebuild the engine and get a `CommitResult` for the UI.
|
|
18
18
|
*/
|
|
19
19
|
applyUpdate(update: Uint8Array): void;
|
|
20
|
+
/**
|
|
21
|
+
* The legacy Ctrl+Shift+Enter array covering a 1-based cell (see the single-user engine's
|
|
22
|
+
* `arrayFormulaAt`), or `null`.
|
|
23
|
+
*/
|
|
24
|
+
arrayFormulaAt(sheet_id: number, row: number, col: number): string;
|
|
20
25
|
canRedo(): boolean;
|
|
21
26
|
canUndo(): boolean;
|
|
22
27
|
clearHistory(): void;
|
|
@@ -131,6 +136,13 @@ export class SpreadsheetEngine {
|
|
|
131
136
|
* Apply a `commands.ts` JSON command; returns `CommitResult` JSON.
|
|
132
137
|
*/
|
|
133
138
|
applyCommand(command_json: string): string;
|
|
139
|
+
/**
|
|
140
|
+
* The legacy Ctrl+Shift+Enter array covering a 1-based cell, as JSON
|
|
141
|
+
* `{"range":{startRowIndex,endRowIndex,startColumnIndex,endColumnIndex},"formula":"=…"}` —
|
|
142
|
+
* the formula every cell of the array shows (Excel's formula bar wraps it in `{…}`) and the
|
|
143
|
+
* range a re-entry replaces — or `null` outside every CSE array.
|
|
144
|
+
*/
|
|
145
|
+
arrayFormulaAt(sheet_id: number, row: number, col: number): string;
|
|
134
146
|
canRedo(): boolean;
|
|
135
147
|
/**
|
|
136
148
|
* Whether an undo / redo is currently available (for enabling toolbar buttons).
|
|
@@ -350,9 +362,47 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
350
362
|
|
|
351
363
|
export interface InitOutput {
|
|
352
364
|
readonly memory: WebAssembly.Memory;
|
|
365
|
+
readonly __wbg_collabspreadsheetengine_free: (a: number, b: number) => void;
|
|
366
|
+
readonly collabspreadsheetengine_addSheet: (a: number, b: number, c: number, d: number) => void;
|
|
367
|
+
readonly collabspreadsheetengine_applyCommand: (a: number, b: number, c: number) => [number, number, number, number];
|
|
368
|
+
readonly collabspreadsheetengine_applyUpdate: (a: number, b: number, c: number) => [number, number];
|
|
369
|
+
readonly collabspreadsheetengine_arrayFormulaAt: (a: number, b: number, c: number, d: number) => [number, number];
|
|
370
|
+
readonly collabspreadsheetengine_canRedo: (a: number) => number;
|
|
371
|
+
readonly collabspreadsheetengine_canUndo: (a: number) => number;
|
|
372
|
+
readonly collabspreadsheetengine_clearHistory: (a: number) => void;
|
|
373
|
+
readonly collabspreadsheetengine_dataRegion: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
374
|
+
readonly collabspreadsheetengine_dependents: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
375
|
+
readonly collabspreadsheetengine_documentFacets: (a: number) => [number, number];
|
|
376
|
+
readonly collabspreadsheetengine_encodeDiffV1: (a: number, b: number, c: number) => [number, number, number, number];
|
|
377
|
+
readonly collabspreadsheetengine_encodeStateAsUpdate: (a: number) => [number, number];
|
|
378
|
+
readonly collabspreadsheetengine_encodeStateVector: (a: number) => [number, number];
|
|
379
|
+
readonly collabspreadsheetengine_exportCsv: (a: number, b: number) => [number, number];
|
|
380
|
+
readonly collabspreadsheetengine_exportXlsx: (a: number) => [number, number, number, number];
|
|
381
|
+
readonly collabspreadsheetengine_hostOwnedCells: (a: number, b: number) => [number, number, number, number];
|
|
382
|
+
readonly collabspreadsheetengine_initGrid: (a: number, b: number, c: number, d: number) => void;
|
|
383
|
+
readonly collabspreadsheetengine_loadDocument: (a: number, b: number, c: number) => [number, number, number, number];
|
|
384
|
+
readonly collabspreadsheetengine_new: () => number;
|
|
385
|
+
readonly collabspreadsheetengine_newWithClientId: (a: number) => number;
|
|
386
|
+
readonly collabspreadsheetengine_precedents: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
387
|
+
readonly collabspreadsheetengine_rangeStats: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
388
|
+
readonly collabspreadsheetengine_readWindow: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
389
|
+
readonly collabspreadsheetengine_redo: (a: number) => [number, number, number, number];
|
|
390
|
+
readonly collabspreadsheetengine_redoDepth: (a: number) => number;
|
|
391
|
+
readonly collabspreadsheetengine_registerCustomFunction: (a: number, b: number, c: number) => void;
|
|
392
|
+
readonly collabspreadsheetengine_setWindowed: (a: number, b: number) => void;
|
|
393
|
+
readonly collabspreadsheetengine_sheetExtents: (a: number) => [number, number, number, number];
|
|
394
|
+
readonly collabspreadsheetengine_sheetIds: (a: number) => [number, number];
|
|
395
|
+
readonly collabspreadsheetengine_sheets: (a: number) => [number, number];
|
|
396
|
+
readonly collabspreadsheetengine_streamBegin: (a: number) => void;
|
|
397
|
+
readonly collabspreadsheetengine_streamEnd: (a: number) => void;
|
|
398
|
+
readonly collabspreadsheetengine_streamWrite: (a: number, b: number, c: number) => void;
|
|
399
|
+
readonly collabspreadsheetengine_syncRemote: (a: number) => [number, number, number, number];
|
|
400
|
+
readonly collabspreadsheetengine_undo: (a: number) => [number, number, number, number];
|
|
401
|
+
readonly collabspreadsheetengine_undoDepth: (a: number) => number;
|
|
353
402
|
readonly __wbg_spreadsheetengine_free: (a: number, b: number) => void;
|
|
354
403
|
readonly spreadsheetengine_addSheet: (a: number, b: number, c: number, d: number) => void;
|
|
355
404
|
readonly spreadsheetengine_applyCommand: (a: number, b: number, c: number) => [number, number, number, number];
|
|
405
|
+
readonly spreadsheetengine_arrayFormulaAt: (a: number, b: number, c: number, d: number) => [number, number];
|
|
356
406
|
readonly spreadsheetengine_canRedo: (a: number) => number;
|
|
357
407
|
readonly spreadsheetengine_canUndo: (a: number) => number;
|
|
358
408
|
readonly spreadsheetengine_clearHistory: (a: number) => void;
|
|
@@ -393,42 +443,6 @@ export interface InitOutput {
|
|
|
393
443
|
readonly spreadsheetengine_validationSourceValues: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
|
|
394
444
|
readonly spreadsheetengine_wasmMemoryBytes: (a: number) => number;
|
|
395
445
|
readonly transformRangeThroughOps: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
|
396
|
-
readonly __wbg_collabspreadsheetengine_free: (a: number, b: number) => void;
|
|
397
|
-
readonly collabspreadsheetengine_addSheet: (a: number, b: number, c: number, d: number) => void;
|
|
398
|
-
readonly collabspreadsheetengine_applyCommand: (a: number, b: number, c: number) => [number, number, number, number];
|
|
399
|
-
readonly collabspreadsheetengine_applyUpdate: (a: number, b: number, c: number) => [number, number];
|
|
400
|
-
readonly collabspreadsheetengine_canRedo: (a: number) => number;
|
|
401
|
-
readonly collabspreadsheetengine_canUndo: (a: number) => number;
|
|
402
|
-
readonly collabspreadsheetengine_clearHistory: (a: number) => void;
|
|
403
|
-
readonly collabspreadsheetengine_dataRegion: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
404
|
-
readonly collabspreadsheetengine_dependents: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
405
|
-
readonly collabspreadsheetengine_documentFacets: (a: number) => [number, number];
|
|
406
|
-
readonly collabspreadsheetengine_encodeDiffV1: (a: number, b: number, c: number) => [number, number, number, number];
|
|
407
|
-
readonly collabspreadsheetengine_encodeStateAsUpdate: (a: number) => [number, number];
|
|
408
|
-
readonly collabspreadsheetengine_encodeStateVector: (a: number) => [number, number];
|
|
409
|
-
readonly collabspreadsheetengine_exportCsv: (a: number, b: number) => [number, number];
|
|
410
|
-
readonly collabspreadsheetengine_exportXlsx: (a: number) => [number, number, number, number];
|
|
411
|
-
readonly collabspreadsheetengine_hostOwnedCells: (a: number, b: number) => [number, number, number, number];
|
|
412
|
-
readonly collabspreadsheetengine_initGrid: (a: number, b: number, c: number, d: number) => void;
|
|
413
|
-
readonly collabspreadsheetengine_loadDocument: (a: number, b: number, c: number) => [number, number, number, number];
|
|
414
|
-
readonly collabspreadsheetengine_new: () => number;
|
|
415
|
-
readonly collabspreadsheetengine_newWithClientId: (a: number) => number;
|
|
416
|
-
readonly collabspreadsheetengine_precedents: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
417
|
-
readonly collabspreadsheetengine_rangeStats: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
418
|
-
readonly collabspreadsheetengine_readWindow: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number, number];
|
|
419
|
-
readonly collabspreadsheetengine_redo: (a: number) => [number, number, number, number];
|
|
420
|
-
readonly collabspreadsheetengine_redoDepth: (a: number) => number;
|
|
421
|
-
readonly collabspreadsheetengine_registerCustomFunction: (a: number, b: number, c: number) => void;
|
|
422
|
-
readonly collabspreadsheetengine_setWindowed: (a: number, b: number) => void;
|
|
423
|
-
readonly collabspreadsheetengine_sheetExtents: (a: number) => [number, number, number, number];
|
|
424
|
-
readonly collabspreadsheetengine_sheetIds: (a: number) => [number, number];
|
|
425
|
-
readonly collabspreadsheetengine_sheets: (a: number) => [number, number];
|
|
426
|
-
readonly collabspreadsheetengine_streamBegin: (a: number) => void;
|
|
427
|
-
readonly collabspreadsheetengine_streamEnd: (a: number) => void;
|
|
428
|
-
readonly collabspreadsheetengine_streamWrite: (a: number, b: number, c: number) => void;
|
|
429
|
-
readonly collabspreadsheetengine_syncRemote: (a: number) => [number, number, number, number];
|
|
430
|
-
readonly collabspreadsheetengine_undo: (a: number) => [number, number, number, number];
|
|
431
|
-
readonly collabspreadsheetengine_undoDepth: (a: number) => number;
|
|
432
446
|
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
433
447
|
readonly __wbindgen_exn_store: (a: number) => void;
|
|
434
448
|
readonly __externref_table_alloc: () => number;
|
|
@@ -69,6 +69,26 @@ export class CollabSpreadsheetEngine {
|
|
|
69
69
|
throw takeFromExternrefTable0(ret[0]);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The legacy Ctrl+Shift+Enter array covering a 1-based cell (see the single-user engine's
|
|
74
|
+
* `arrayFormulaAt`), or `null`.
|
|
75
|
+
* @param {number} sheet_id
|
|
76
|
+
* @param {number} row
|
|
77
|
+
* @param {number} col
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
arrayFormulaAt(sheet_id, row, col) {
|
|
81
|
+
let deferred1_0;
|
|
82
|
+
let deferred1_1;
|
|
83
|
+
try {
|
|
84
|
+
const ret = wasm.collabspreadsheetengine_arrayFormulaAt(this.__wbg_ptr, sheet_id, row, col);
|
|
85
|
+
deferred1_0 = ret[0];
|
|
86
|
+
deferred1_1 = ret[1];
|
|
87
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
88
|
+
} finally {
|
|
89
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
72
92
|
/**
|
|
73
93
|
* @returns {boolean}
|
|
74
94
|
*/
|
|
@@ -592,6 +612,28 @@ export class SpreadsheetEngine {
|
|
|
592
612
|
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
|
593
613
|
}
|
|
594
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* The legacy Ctrl+Shift+Enter array covering a 1-based cell, as JSON
|
|
617
|
+
* `{"range":{startRowIndex,endRowIndex,startColumnIndex,endColumnIndex},"formula":"=…"}` —
|
|
618
|
+
* the formula every cell of the array shows (Excel's formula bar wraps it in `{…}`) and the
|
|
619
|
+
* range a re-entry replaces — or `null` outside every CSE array.
|
|
620
|
+
* @param {number} sheet_id
|
|
621
|
+
* @param {number} row
|
|
622
|
+
* @param {number} col
|
|
623
|
+
* @returns {string}
|
|
624
|
+
*/
|
|
625
|
+
arrayFormulaAt(sheet_id, row, col) {
|
|
626
|
+
let deferred1_0;
|
|
627
|
+
let deferred1_1;
|
|
628
|
+
try {
|
|
629
|
+
const ret = wasm.spreadsheetengine_arrayFormulaAt(this.__wbg_ptr, sheet_id, row, col);
|
|
630
|
+
deferred1_0 = ret[0];
|
|
631
|
+
deferred1_1 = ret[1];
|
|
632
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
633
|
+
} finally {
|
|
634
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
595
637
|
/**
|
|
596
638
|
* @returns {boolean}
|
|
597
639
|
*/
|
|
Binary file
|