@rowsncolumns/spreadsheet 10.2.73 → 10.2.75
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/cjs/api/evaluate-formula.d.ts +17 -0
- package/dist/cjs/api/evaluate-formula.d.ts.map +1 -0
- package/dist/cjs/api/evaluate-formula.js +141 -0
- package/dist/cjs/api/excel-validation-kinds.d.ts +71 -0
- package/dist/cjs/api/excel-validation-kinds.d.ts.map +1 -0
- package/dist/cjs/api/excel-validation-kinds.js +304 -0
- package/dist/cjs/api/go-to-special.d.ts +32 -0
- package/dist/cjs/api/go-to-special.d.ts.map +1 -0
- package/dist/cjs/api/go-to-special.js +111 -0
- package/dist/cjs/api/go-to.d.ts +19 -0
- package/dist/cjs/api/go-to.d.ts.map +1 -0
- package/dist/cjs/api/go-to.js +49 -0
- package/dist/cjs/api/validation-tooltip.d.ts +17 -0
- package/dist/cjs/api/validation-tooltip.d.ts.map +1 -0
- package/dist/cjs/api/validation-tooltip.js +42 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts +28 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.js +32 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.js +36 -0
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts +46 -0
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/go-to-dialog.atom.js +34 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts +29 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.js +30 -0
- package/dist/cjs/atoms/index.d.ts +5 -0
- package/dist/cjs/atoms/index.d.ts.map +1 -1
- package/dist/cjs/atoms/index.js +5 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.d.ts +29 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.js +34 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.js +37 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.d.ts +39 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.js +30 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts +34 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.js +36 -0
- package/dist/cjs/atoms/watch-window.atom.d.ts +37 -0
- package/dist/cjs/atoms/watch-window.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/watch-window.atom.js +61 -0
- package/dist/cjs/components/advanced-filter-dialog/index.d.ts +28 -0
- package/dist/cjs/components/advanced-filter-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/advanced-filter-dialog/index.js +134 -0
- package/dist/cjs/components/calculation-options-dialog/index.d.ts +33 -0
- package/dist/cjs/components/calculation-options-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/calculation-options-dialog/index.js +69 -0
- package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/cjs/components/canvas-grid/index.js +208 -19
- package/dist/cjs/components/canvas-grid/typings.d.ts +72 -9
- package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.d.ts +30 -0
- package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.js +56 -1
- package/dist/cjs/components/cell/index.d.ts.map +1 -1
- package/dist/cjs/components/cell/index.js +15 -7
- package/dist/cjs/components/cell-diff/index.d.ts +3 -2
- package/dist/cjs/components/cell-diff/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.js +2 -1
- package/dist/cjs/components/context-menu/index.d.ts +21 -3
- package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
- package/dist/cjs/components/context-menu/index.js +256 -6
- package/dist/cjs/components/context-menu/total-row-functions.d.ts +32 -0
- package/dist/cjs/components/context-menu/total-row-functions.d.ts.map +1 -0
- package/dist/cjs/components/context-menu/total-row-functions.js +62 -0
- package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/cjs/components/embeds/embed-component.js +16 -1
- package/dist/cjs/components/evaluate-formula-dialog/index.d.ts +34 -0
- package/dist/cjs/components/evaluate-formula-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/evaluate-formula-dialog/index.js +106 -0
- package/dist/cjs/components/go-to-dialog/index.d.ts +45 -0
- package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/go-to-dialog/index.js +136 -0
- package/dist/cjs/components/goal-seek-dialog/index.d.ts +32 -0
- package/dist/cjs/components/goal-seek-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/goal-seek-dialog/index.js +149 -0
- package/dist/cjs/components/paste-special-dialog/index.d.ts +29 -0
- package/dist/cjs/components/paste-special-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/paste-special-dialog/index.js +99 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.d.ts +37 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.js +91 -0
- package/dist/cjs/components/subtotal-dialog/index.d.ts +30 -0
- package/dist/cjs/components/subtotal-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/subtotal-dialog/index.js +126 -0
- package/dist/cjs/components/text-to-columns-dialog/index.d.ts +37 -0
- package/dist/cjs/components/text-to-columns-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/text-to-columns-dialog/index.js +166 -0
- package/dist/cjs/components/toolbar/background-color-selector.d.ts +13 -1
- package/dist/cjs/components/toolbar/background-color-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/background-color-selector.js +57 -11
- package/dist/cjs/components/toolbar/cell-style-selector.d.ts +15 -2
- package/dist/cjs/components/toolbar/cell-style-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/cell-style-selector.js +56 -3
- package/dist/cjs/components/toolbar/fill-selectors.d.ts +49 -0
- package/dist/cjs/components/toolbar/fill-selectors.d.ts.map +1 -0
- package/dist/cjs/components/toolbar/fill-selectors.js +196 -0
- package/dist/cjs/components/toolbar/index.d.ts +2 -0
- package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/index.js +2 -0
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.js +23 -4
- package/dist/cjs/components/toolbar/text-rotation-selector.d.ts +29 -0
- package/dist/cjs/components/toolbar/text-rotation-selector.d.ts.map +1 -0
- package/dist/cjs/components/toolbar/text-rotation-selector.js +78 -0
- package/dist/cjs/components/toolbar/text-wrap-selector.d.ts +7 -1
- package/dist/cjs/components/toolbar/text-wrap-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/text-wrap-selector.js +10 -3
- package/dist/cjs/components/watch-window/index.d.ts +25 -0
- package/dist/cjs/components/watch-window/index.d.ts.map +1 -0
- package/dist/cjs/components/watch-window/index.js +63 -0
- package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-cell-renderer.js +17 -6
- package/dist/cjs/hooks/use-copy-paste-cut.d.ts +20 -1
- package/dist/cjs/hooks/use-copy-paste-cut.d.ts.map +1 -1
- package/dist/cjs/hooks/use-copy-paste-cut.js +25 -20
- package/dist/cjs/hooks/use-get-tooltip.d.ts.map +1 -1
- package/dist/cjs/hooks/use-get-tooltip.js +7 -2
- package/dist/cjs/hooks/use-keyboard-handler.d.ts +17 -2
- package/dist/cjs/hooks/use-keyboard-handler.d.ts.map +1 -1
- package/dist/cjs/hooks/use-keyboard-handler.js +206 -31
- package/dist/cjs/index.d.ts +14 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +15 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +289 -4
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +19 -1
- package/dist/esm/api/evaluate-formula.d.ts +17 -0
- package/dist/esm/api/evaluate-formula.d.ts.map +1 -0
- package/dist/esm/api/evaluate-formula.js +109 -0
- package/dist/esm/api/excel-validation-kinds.d.ts +71 -0
- package/dist/esm/api/excel-validation-kinds.d.ts.map +1 -0
- package/dist/esm/api/excel-validation-kinds.js +287 -0
- package/dist/esm/api/go-to-special.d.ts +32 -0
- package/dist/esm/api/go-to-special.d.ts.map +1 -0
- package/dist/esm/api/go-to-special.js +96 -0
- package/dist/esm/api/go-to.d.ts +19 -0
- package/dist/esm/api/go-to.d.ts.map +1 -0
- package/dist/esm/api/go-to.js +41 -0
- package/dist/esm/api/validation-tooltip.d.ts +17 -0
- package/dist/esm/api/validation-tooltip.d.ts.map +1 -0
- package/dist/esm/api/validation-tooltip.js +34 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts +28 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.js +21 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.js +25 -0
- package/dist/esm/atoms/go-to-dialog.atom.d.ts +46 -0
- package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/go-to-dialog.atom.js +25 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.d.ts +29 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.js +21 -0
- package/dist/esm/atoms/index.d.ts +5 -0
- package/dist/esm/atoms/index.d.ts.map +1 -1
- package/dist/esm/atoms/index.js +5 -0
- package/dist/esm/atoms/paste-special-dialog.atom.d.ts +29 -0
- package/dist/esm/atoms/paste-special-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/paste-special-dialog.atom.js +25 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.js +26 -0
- package/dist/esm/atoms/subtotal-dialog.atom.d.ts +39 -0
- package/dist/esm/atoms/subtotal-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/subtotal-dialog.atom.js +21 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts +34 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.js +25 -0
- package/dist/esm/atoms/watch-window.atom.d.ts +37 -0
- package/dist/esm/atoms/watch-window.atom.d.ts.map +1 -0
- package/dist/esm/atoms/watch-window.atom.js +37 -0
- package/dist/esm/components/advanced-filter-dialog/index.d.ts +28 -0
- package/dist/esm/components/advanced-filter-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/advanced-filter-dialog/index.js +125 -0
- package/dist/esm/components/calculation-options-dialog/index.d.ts +33 -0
- package/dist/esm/components/calculation-options-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/calculation-options-dialog/index.js +61 -0
- package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/esm/components/canvas-grid/index.js +202 -19
- package/dist/esm/components/canvas-grid/typings.d.ts +72 -9
- package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.d.ts +30 -0
- package/dist/esm/components/cell/alignment.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.js +51 -0
- package/dist/esm/components/cell/index.d.ts.map +1 -1
- package/dist/esm/components/cell/index.js +16 -8
- package/dist/esm/components/cell-diff/index.d.ts +3 -2
- package/dist/esm/components/cell-diff/index.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.js +2 -1
- package/dist/esm/components/context-menu/index.d.ts +21 -3
- package/dist/esm/components/context-menu/index.d.ts.map +1 -1
- package/dist/esm/components/context-menu/index.js +251 -9
- package/dist/esm/components/context-menu/total-row-functions.d.ts +32 -0
- package/dist/esm/components/context-menu/total-row-functions.d.ts.map +1 -0
- package/dist/esm/components/context-menu/total-row-functions.js +50 -0
- package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/esm/components/embeds/embed-component.js +17 -1
- package/dist/esm/components/evaluate-formula-dialog/index.d.ts +34 -0
- package/dist/esm/components/evaluate-formula-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/evaluate-formula-dialog/index.js +86 -0
- package/dist/esm/components/go-to-dialog/index.d.ts +45 -0
- package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/go-to-dialog/index.js +124 -0
- package/dist/esm/components/goal-seek-dialog/index.d.ts +32 -0
- package/dist/esm/components/goal-seek-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/goal-seek-dialog/index.js +125 -0
- package/dist/esm/components/paste-special-dialog/index.d.ts +29 -0
- package/dist/esm/components/paste-special-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/paste-special-dialog/index.js +90 -0
- package/dist/esm/components/remove-duplicates-dialog/index.d.ts +37 -0
- package/dist/esm/components/remove-duplicates-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/remove-duplicates-dialog/index.js +82 -0
- package/dist/esm/components/subtotal-dialog/index.d.ts +30 -0
- package/dist/esm/components/subtotal-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/subtotal-dialog/index.js +115 -0
- package/dist/esm/components/text-to-columns-dialog/index.d.ts +37 -0
- package/dist/esm/components/text-to-columns-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/text-to-columns-dialog/index.js +147 -0
- package/dist/esm/components/toolbar/background-color-selector.d.ts +13 -1
- package/dist/esm/components/toolbar/background-color-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/background-color-selector.js +61 -11
- package/dist/esm/components/toolbar/cell-style-selector.d.ts +15 -2
- package/dist/esm/components/toolbar/cell-style-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/cell-style-selector.js +46 -2
- package/dist/esm/components/toolbar/fill-selectors.d.ts +49 -0
- package/dist/esm/components/toolbar/fill-selectors.d.ts.map +1 -0
- package/dist/esm/components/toolbar/fill-selectors.js +162 -0
- package/dist/esm/components/toolbar/index.d.ts +2 -0
- package/dist/esm/components/toolbar/index.d.ts.map +1 -1
- package/dist/esm/components/toolbar/index.js +2 -0
- package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
- package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/text-horizontal-align-selector.js +21 -5
- package/dist/esm/components/toolbar/text-rotation-selector.d.ts +29 -0
- package/dist/esm/components/toolbar/text-rotation-selector.d.ts.map +1 -0
- package/dist/esm/components/toolbar/text-rotation-selector.js +71 -0
- package/dist/esm/components/toolbar/text-wrap-selector.d.ts +7 -1
- package/dist/esm/components/toolbar/text-wrap-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/text-wrap-selector.js +10 -3
- package/dist/esm/components/watch-window/index.d.ts +25 -0
- package/dist/esm/components/watch-window/index.d.ts.map +1 -0
- package/dist/esm/components/watch-window/index.js +54 -0
- package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/esm/hooks/use-cell-renderer.js +17 -6
- package/dist/esm/hooks/use-copy-paste-cut.d.ts +20 -1
- package/dist/esm/hooks/use-copy-paste-cut.d.ts.map +1 -1
- package/dist/esm/hooks/use-copy-paste-cut.js +8 -3
- package/dist/esm/hooks/use-get-tooltip.d.ts.map +1 -1
- package/dist/esm/hooks/use-get-tooltip.js +7 -2
- package/dist/esm/hooks/use-keyboard-handler.d.ts +17 -2
- package/dist/esm/hooks/use-keyboard-handler.d.ts.map +1 -1
- package/dist/esm/hooks/use-keyboard-handler.js +193 -31
- package/dist/esm/index.d.ts +14 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +13 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +289 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +18 -0
- package/dist/spreadsheet.min.css +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Button, cn, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogTitle, Input, Separator, } from "@rowsncolumns/ui";
|
|
2
|
+
import { cellToAddress } from "@rowsncolumns/utils";
|
|
3
|
+
import React, { memo, useCallback, useState } from "react";
|
|
4
|
+
import { resolveGoToReference } from "../../api/go-to";
|
|
5
|
+
import { GO_TO_SPECIAL_OPTIONS, } from "../../api/go-to-special";
|
|
6
|
+
import { useGoToDialogState, useRegisterGoToDialogHost, } from "../../atoms/go-to-dialog.atom";
|
|
7
|
+
const INVALID_REFERENCE = "Reference is not valid.";
|
|
8
|
+
const NO_CELLS_FOUND = "No cells were found.";
|
|
9
|
+
/**
|
|
10
|
+
* Excel's Go To dialog (`Ctrl+G` / `F5`): type a cell or range reference,
|
|
11
|
+
* optionally sheet-qualified, or pick a defined name, and the selection
|
|
12
|
+
* jumps there. Resolution is delegated to `resolve` so the dialog stays
|
|
13
|
+
* UI-only.
|
|
14
|
+
*/
|
|
15
|
+
export const GoToDialog = memo(({ open, onOpenChange, namedRanges, currentAddress, resolve, onNavigate, special, }) => {
|
|
16
|
+
const [reference, setReference] = useState("");
|
|
17
|
+
const [error, setError] = useState(undefined);
|
|
18
|
+
const [showSpecial, setShowSpecial] = useState(false);
|
|
19
|
+
const [specialKind, setSpecialKind] = useState("blanks");
|
|
20
|
+
const selectSpecial = useCallback(() => {
|
|
21
|
+
if (!special)
|
|
22
|
+
return;
|
|
23
|
+
const ranges = special.resolve(specialKind);
|
|
24
|
+
if (!ranges.length) {
|
|
25
|
+
setError(NO_CELLS_FOUND);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
special.onSelectRanges(ranges);
|
|
29
|
+
onOpenChange(false);
|
|
30
|
+
}, [special, specialKind, onOpenChange]);
|
|
31
|
+
const navigate = useCallback((text) => {
|
|
32
|
+
const range = resolve(text);
|
|
33
|
+
if (!range) {
|
|
34
|
+
setError(INVALID_REFERENCE);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
onNavigate(range);
|
|
38
|
+
onOpenChange(false);
|
|
39
|
+
}, [resolve, onNavigate, onOpenChange]);
|
|
40
|
+
const names = (namedRanges ?? []).filter((range) => range.range && !range.hidden);
|
|
41
|
+
return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange, modal: true },
|
|
42
|
+
React.createElement(DialogContent, { className: "w-[360px] max-w-[92vw] pointer-events-auto" },
|
|
43
|
+
React.createElement(DialogTitle, null, showSpecial ? "Go To Special" : "Go To"),
|
|
44
|
+
React.createElement(DialogCloseButton, null),
|
|
45
|
+
React.createElement(DialogBody, { className: "flex flex-col gap-3" }, showSpecial && special ? (React.createElement("form", { className: "flex flex-col gap-3", onSubmit: (e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
selectSpecial();
|
|
48
|
+
} },
|
|
49
|
+
React.createElement("fieldset", { className: "flex flex-col gap-1 border-0 p-0 m-0" },
|
|
50
|
+
React.createElement("legend", { className: "text-xs font-medium pb-1" }, "Select"),
|
|
51
|
+
GO_TO_SPECIAL_OPTIONS.map(({ kind, label }) => (React.createElement("label", { key: kind, className: "flex items-center gap-2 text-xs cursor-pointer" },
|
|
52
|
+
React.createElement("input", { type: "radio", name: "goToSpecial", value: kind, checked: specialKind === kind, onChange: () => {
|
|
53
|
+
setSpecialKind(kind);
|
|
54
|
+
setError(undefined);
|
|
55
|
+
} }),
|
|
56
|
+
label)))),
|
|
57
|
+
error ? (React.createElement("div", { role: "alert", className: "text-xs text-rnc-destructive" }, error)) : null,
|
|
58
|
+
React.createElement(Separator, null),
|
|
59
|
+
React.createElement("div", { className: "flex justify-between gap-2" },
|
|
60
|
+
React.createElement(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
|
|
61
|
+
setShowSpecial(false);
|
|
62
|
+
setError(undefined);
|
|
63
|
+
} }, "Back"),
|
|
64
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
65
|
+
React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
66
|
+
React.createElement(Button, { type: "submit", size: "sm" }, "OK"))))) : (React.createElement("form", { className: "flex flex-col gap-3", onSubmit: (e) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
navigate(reference);
|
|
69
|
+
} },
|
|
70
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
71
|
+
React.createElement("span", { className: "text-xs font-medium" }, "Go to:"),
|
|
72
|
+
React.createElement("div", { role: "listbox", "aria-label": "Defined names", className: "h-[140px] overflow-auto rounded border border-rnc-border text-xs" }, names.length === 0 ? (React.createElement("div", { className: "px-2 py-1.5 text-rnc-foreground/60" }, "No defined names")) : (names.map((range) => (React.createElement("div", { key: String(range.namedRangeId), role: "option", "aria-selected": reference === range.name, className: cn("px-2 py-1 cursor-default select-none hover:bg-rnc-accent", reference === range.name && "bg-rnc-accent"), onClick: () => {
|
|
73
|
+
setReference(range.name);
|
|
74
|
+
setError(undefined);
|
|
75
|
+
}, onDoubleClick: () => navigate(range.name) }, range.name)))))),
|
|
76
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
77
|
+
"Reference:",
|
|
78
|
+
React.createElement(Input, { autoFocus: true, value: reference, placeholder: currentAddress, "aria-invalid": error ? true : undefined, onChange: (e) => {
|
|
79
|
+
setReference(e.target.value);
|
|
80
|
+
setError(undefined);
|
|
81
|
+
} })),
|
|
82
|
+
error ? (React.createElement("div", { role: "alert", className: "text-xs text-rnc-destructive" }, error)) : null,
|
|
83
|
+
React.createElement(Separator, null),
|
|
84
|
+
React.createElement("div", { className: "flex justify-between gap-2" },
|
|
85
|
+
special ? (React.createElement(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
|
|
86
|
+
setShowSpecial(true);
|
|
87
|
+
setError(undefined);
|
|
88
|
+
} }, "Special\u2026")) : (React.createElement("span", null)),
|
|
89
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
90
|
+
React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
91
|
+
React.createElement(Button, { type: "submit", size: "sm", disabled: !reference.trim() }, "OK")))))))));
|
|
92
|
+
});
|
|
93
|
+
GoToDialog.displayName = "GoToDialog";
|
|
94
|
+
const currentAddressOf = (request) => {
|
|
95
|
+
const address = cellToAddress(request.activeCell) ?? "";
|
|
96
|
+
const sheetName = request.getSheetName?.(request.sheetId);
|
|
97
|
+
return sheetName ? `${sheetName}!${address}` : address;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Host-mounted, atom-driven wrapper around {@link GoToDialog}. Mount it once
|
|
101
|
+
* in the host alongside the other editor dialogs; the grid opens it on
|
|
102
|
+
* `Ctrl+G` / `F5` (unless the host handles `onRequestGoTo` itself).
|
|
103
|
+
*/
|
|
104
|
+
export const GoToDialogHost = () => {
|
|
105
|
+
useRegisterGoToDialogHost();
|
|
106
|
+
const [request, setRequest] = useGoToDialogState();
|
|
107
|
+
const close = useCallback(() => setRequest(null), [setRequest]);
|
|
108
|
+
const resolve = useCallback((reference) => request
|
|
109
|
+
? resolveGoToReference(reference, {
|
|
110
|
+
sheetId: request.sheetId,
|
|
111
|
+
namedRanges: request.namedRanges,
|
|
112
|
+
getSheetId: request.getSheetId,
|
|
113
|
+
rowCount: request.rowCount,
|
|
114
|
+
columnCount: request.columnCount,
|
|
115
|
+
})
|
|
116
|
+
: undefined, [request]);
|
|
117
|
+
if (!request)
|
|
118
|
+
return null;
|
|
119
|
+
return (React.createElement(GoToDialog, { open: true, onOpenChange: (open) => {
|
|
120
|
+
if (!open)
|
|
121
|
+
close();
|
|
122
|
+
}, namedRanges: request.namedRanges, currentAddress: currentAddressOf(request), resolve: resolve, onNavigate: request.onNavigate, special: request.special }));
|
|
123
|
+
};
|
|
124
|
+
GoToDialogHost.displayName = "GoToDialogHost";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { GoalSeekResult } from "../../types";
|
|
3
|
+
export type GoalSeekDialogProps = {
|
|
4
|
+
/** Controls visibility. */
|
|
5
|
+
open: boolean;
|
|
6
|
+
onOpenChange(open: boolean): void;
|
|
7
|
+
/** Default "Set cell" (A1 reference of the active cell). */
|
|
8
|
+
initialSetCell: string;
|
|
9
|
+
/**
|
|
10
|
+
* Runs the search with the three fields (A1 references + the numeric
|
|
11
|
+
* target). Resolves to the result, or `undefined` when the engine reports
|
|
12
|
+
* none (the rnc engine iterates natively and streams the cells back).
|
|
13
|
+
*/
|
|
14
|
+
onApply(setCell: string, targetValue: number, changingCell: string): Promise<GoalSeekResult | undefined>;
|
|
15
|
+
};
|
|
16
|
+
/** Excel's status sentence for a finished search. */
|
|
17
|
+
export declare const goalSeekStatusText: (setCell: string, result: GoalSeekResult | undefined) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Excel's Goal Seek dialog: Set cell / To value / By changing cell, then the
|
|
20
|
+
* Goal Seek Status sentence with target and current value.
|
|
21
|
+
*/
|
|
22
|
+
export declare const GoalSeekDialog: React.MemoExoticComponent<({ open, onOpenChange, initialSetCell, onApply }: GoalSeekDialogProps) => React.JSX.Element>;
|
|
23
|
+
/**
|
|
24
|
+
* Host-mounted, atom-driven wrapper around {@link GoalSeekDialog}. Mount
|
|
25
|
+
* once in the host; the context menu opens it via `useOpenGoalSeekDialog`
|
|
26
|
+
* and only offers the item while a host is mounted.
|
|
27
|
+
*/
|
|
28
|
+
export declare const GoalSeekDialogHost: {
|
|
29
|
+
(): React.JSX.Element;
|
|
30
|
+
displayName: string;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/goal-seek-dialog/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAMtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,mBAAmB,GAAG;IAChC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CACL,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CACxC,CAAC;AAKF,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,MAAM,EACf,QAAQ,cAAc,GAAG,SAAS,KACjC,MAKF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,8EACyB,mBAAmB,uBAyJtE,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;CAkB9B,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Cross1Icon } from "@rowsncolumns/icons";
|
|
2
|
+
import { Button, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogDescription, DialogTitle, Input, Separator, } from "@rowsncolumns/ui";
|
|
3
|
+
import { addressToCell } from "@rowsncolumns/utils";
|
|
4
|
+
import React, { memo, useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { useGoalSeekDialogState, useRegisterGoalSeekDialogHost, } from "../../atoms/goal-seek-dialog.atom";
|
|
6
|
+
const isCellReference = (text) => /^\$?[A-Za-z]{1,3}\$?\d+$/.test(text.trim()) && addressToCell(text.trim()) != null;
|
|
7
|
+
/** Excel's status sentence for a finished search. */
|
|
8
|
+
export const goalSeekStatusText = (setCell, result) => {
|
|
9
|
+
if (!result)
|
|
10
|
+
return `Goal Seeking with Cell ${setCell} finished.`;
|
|
11
|
+
return result.status === "found"
|
|
12
|
+
? `Goal Seeking with Cell ${setCell} found a solution.`
|
|
13
|
+
: `Goal Seeking with Cell ${setCell} may not have found a solution.`;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Excel's Goal Seek dialog: Set cell / To value / By changing cell, then the
|
|
17
|
+
* Goal Seek Status sentence with target and current value.
|
|
18
|
+
*/
|
|
19
|
+
export const GoalSeekDialog = memo(({ open, onOpenChange, initialSetCell, onApply }) => {
|
|
20
|
+
const [setCell, setSetCell] = useState(initialSetCell);
|
|
21
|
+
const [target, setTarget] = useState("");
|
|
22
|
+
const [changingCell, setChangingCell] = useState("");
|
|
23
|
+
const [error, setError] = useState(undefined);
|
|
24
|
+
const [busy, setBusy] = useState(false);
|
|
25
|
+
const [result, setResult] = useState(undefined);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (open) {
|
|
28
|
+
setSetCell(initialSetCell);
|
|
29
|
+
setTarget("");
|
|
30
|
+
setChangingCell("");
|
|
31
|
+
setError(undefined);
|
|
32
|
+
setBusy(false);
|
|
33
|
+
setResult(undefined);
|
|
34
|
+
}
|
|
35
|
+
// Only reset on open transitions.
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [open]);
|
|
38
|
+
const submit = useCallback(async (event) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
const targetValue = Number(target.trim());
|
|
41
|
+
if (!isCellReference(setCell)) {
|
|
42
|
+
setError("Set cell must be a single cell reference, e.g. B4.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (target.trim() === "" || !Number.isFinite(targetValue)) {
|
|
46
|
+
setError("To value must be a number.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!isCellReference(changingCell)) {
|
|
50
|
+
setError("By changing cell must be a single cell reference, e.g. A1.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setError(undefined);
|
|
54
|
+
setBusy(true);
|
|
55
|
+
try {
|
|
56
|
+
const outcome = await onApply(setCell.trim().toUpperCase(), targetValue, changingCell.trim().toUpperCase());
|
|
57
|
+
setResult({ setCell: setCell.trim().toUpperCase(), result: outcome });
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
setError(err instanceof Error ? err.message : "Goal Seek failed.");
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
setBusy(false);
|
|
64
|
+
}
|
|
65
|
+
}, [setCell, target, changingCell, onApply]);
|
|
66
|
+
return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange },
|
|
67
|
+
React.createElement(DialogContent, { className: "w-[360px] max-w-[92vw] pointer-events-auto" },
|
|
68
|
+
React.createElement(DialogTitle, null, result ? "Goal Seek Status" : "Goal Seek"),
|
|
69
|
+
React.createElement(DialogDescription, null, "Find the input value that makes a formula reach a target."),
|
|
70
|
+
React.createElement(DialogCloseButton, { onClick: () => onOpenChange(false) },
|
|
71
|
+
React.createElement(Cross1Icon, null)),
|
|
72
|
+
React.createElement(DialogBody, { className: "flex flex-col gap-3 font-system text-sm" }, result ? (React.createElement("div", { className: "flex flex-col gap-2", role: "status" },
|
|
73
|
+
React.createElement("div", null, goalSeekStatusText(result.setCell, result.result)),
|
|
74
|
+
result.result ? (React.createElement("dl", { className: "grid grid-cols-2 gap-1 text-xs" },
|
|
75
|
+
React.createElement("dt", { className: "text-rnc-foreground/70" }, "Target value:"),
|
|
76
|
+
React.createElement("dd", null, result.result.targetValue),
|
|
77
|
+
React.createElement("dt", { className: "text-rnc-foreground/70" }, "Current value:"),
|
|
78
|
+
React.createElement("dd", null, result.result.currentValue === undefined
|
|
79
|
+
? "—"
|
|
80
|
+
: String(result.result.currentValue)))) : null,
|
|
81
|
+
React.createElement(Separator, null),
|
|
82
|
+
React.createElement("div", { className: "flex justify-end gap-2" },
|
|
83
|
+
React.createElement(Button, { size: "sm", type: "button", onClick: () => onOpenChange(false) }, "OK")))) : (React.createElement("form", { className: "flex flex-col gap-3", onSubmit: submit },
|
|
84
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
85
|
+
"Set cell:",
|
|
86
|
+
React.createElement(Input, { autoFocus: true, value: setCell, "aria-label": "Set cell", onChange: (e) => {
|
|
87
|
+
setSetCell(e.target.value);
|
|
88
|
+
setError(undefined);
|
|
89
|
+
} })),
|
|
90
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
91
|
+
"To value:",
|
|
92
|
+
React.createElement(Input, { value: target, "aria-label": "To value", inputMode: "decimal", onChange: (e) => {
|
|
93
|
+
setTarget(e.target.value);
|
|
94
|
+
setError(undefined);
|
|
95
|
+
} })),
|
|
96
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
97
|
+
"By changing cell:",
|
|
98
|
+
React.createElement(Input, { value: changingCell, "aria-label": "By changing cell", onChange: (e) => {
|
|
99
|
+
setChangingCell(e.target.value);
|
|
100
|
+
setError(undefined);
|
|
101
|
+
} })),
|
|
102
|
+
error ? (React.createElement("div", { role: "alert", className: "text-xs text-rnc-destructive" }, error)) : null,
|
|
103
|
+
React.createElement(Separator, null),
|
|
104
|
+
React.createElement("div", { className: "flex justify-end gap-2" },
|
|
105
|
+
React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
106
|
+
React.createElement(Button, { type: "submit", size: "sm", disabled: busy }, busy ? "Seeking…" : "OK"))))))));
|
|
107
|
+
});
|
|
108
|
+
GoalSeekDialog.displayName = "GoalSeekDialog";
|
|
109
|
+
/**
|
|
110
|
+
* Host-mounted, atom-driven wrapper around {@link GoalSeekDialog}. Mount
|
|
111
|
+
* once in the host; the context menu opens it via `useOpenGoalSeekDialog`
|
|
112
|
+
* and only offers the item while a host is mounted.
|
|
113
|
+
*/
|
|
114
|
+
export const GoalSeekDialogHost = () => {
|
|
115
|
+
useRegisterGoalSeekDialogHost();
|
|
116
|
+
const [request, setRequest] = useGoalSeekDialogState();
|
|
117
|
+
const close = useCallback(() => setRequest(null), [setRequest]);
|
|
118
|
+
return (React.createElement(GoalSeekDialog, { open: request != null, onOpenChange: (open) => {
|
|
119
|
+
if (!open)
|
|
120
|
+
close();
|
|
121
|
+
}, initialSetCell: request?.setCell ?? "", onApply: (setCell, targetValue, changingCell) => request
|
|
122
|
+
? request.onApply(setCell, targetValue, changingCell)
|
|
123
|
+
: Promise.resolve(undefined) }));
|
|
124
|
+
};
|
|
125
|
+
GoalSeekDialogHost.displayName = "GoalSeekDialogHost";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { PasteSpecialOptions } from "../../hooks/use-copy-paste-cut";
|
|
3
|
+
export type PasteSpecialDialogProps = {
|
|
4
|
+
/** Controls visibility. */
|
|
5
|
+
open: boolean;
|
|
6
|
+
onOpenChange(open: boolean): void;
|
|
7
|
+
/**
|
|
8
|
+
* Emits the assembled request. The host routes it to the grid's
|
|
9
|
+
* `paste(options)`; the dialog never touches the clipboard itself.
|
|
10
|
+
*/
|
|
11
|
+
onApply(options: PasteSpecialOptions): void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Excel's Paste Special dialog: what to paste (all / formulas / values /
|
|
15
|
+
* formats), an arithmetic operation against the destination, skip blanks and
|
|
16
|
+
* transpose. Every choice composes into one `PasteSpecialOptions` request.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PasteSpecialDialog: React.MemoExoticComponent<({ open, onOpenChange, onApply }: PasteSpecialDialogProps) => React.JSX.Element>;
|
|
19
|
+
/**
|
|
20
|
+
* Host-mounted, atom-driven wrapper around {@link PasteSpecialDialog}. Mount
|
|
21
|
+
* it once in the host alongside the other editor dialogs; the grid opens it
|
|
22
|
+
* from the context menu ("Paste special…") and `Ctrl+Alt+V`. Remounted per
|
|
23
|
+
* request so each opening starts from Excel's defaults.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PasteSpecialDialogHost: {
|
|
26
|
+
(): React.JSX.Element | null;
|
|
27
|
+
displayName: string;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/paste-special-dialog/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAM3D,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,gCAAgC,CAAC;AAoBxC,MAAM,MAAM,uBAAuB,GAAG;IACpC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC7C,CAAC;AAyCF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,8DACK,uBAAuB,uBA2F1D,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB;;;CAclC,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Button, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogTitle, Separator, } from "@rowsncolumns/ui";
|
|
2
|
+
import React, { memo, useCallback, useState } from "react";
|
|
3
|
+
import { usePasteSpecialDialogState, useRegisterPasteSpecialDialogHost, } from "../../atoms/paste-special-dialog.atom";
|
|
4
|
+
const PASTE_MODES = [
|
|
5
|
+
{ value: "all", label: "All" },
|
|
6
|
+
{ value: "formulas", label: "Formulas" },
|
|
7
|
+
{ value: "values", label: "Values" },
|
|
8
|
+
{ value: "formats", label: "Formats" },
|
|
9
|
+
];
|
|
10
|
+
const OPERATIONS = [
|
|
11
|
+
{ value: "none", label: "None" },
|
|
12
|
+
{ value: "add", label: "Add" },
|
|
13
|
+
{ value: "subtract", label: "Subtract" },
|
|
14
|
+
{ value: "multiply", label: "Multiply" },
|
|
15
|
+
{ value: "divide", label: "Divide" },
|
|
16
|
+
];
|
|
17
|
+
const RadioGroup = ({ name, legend, value, options, onChange, disabled, }) => (React.createElement("fieldset", { className: "flex flex-col gap-1 min-w-0 disabled:opacity-50", disabled: disabled },
|
|
18
|
+
React.createElement("legend", { className: "text-xs font-medium mb-1" }, legend),
|
|
19
|
+
options.map((option) => (React.createElement("label", { key: option.value, className: "flex items-center gap-2 text-xs py-0.5 cursor-pointer" },
|
|
20
|
+
React.createElement("input", { type: "radio", name: name, value: option.value, checked: value === option.value, disabled: disabled, onChange: () => onChange(option.value) }),
|
|
21
|
+
option.label)))));
|
|
22
|
+
/**
|
|
23
|
+
* Excel's Paste Special dialog: what to paste (all / formulas / values /
|
|
24
|
+
* formats), an arithmetic operation against the destination, skip blanks and
|
|
25
|
+
* transpose. Every choice composes into one `PasteSpecialOptions` request.
|
|
26
|
+
*/
|
|
27
|
+
export const PasteSpecialDialog = memo(({ open, onOpenChange, onApply }) => {
|
|
28
|
+
const [paste, setPaste] = useState("all");
|
|
29
|
+
const [operation, setOperation] = useState("none");
|
|
30
|
+
const [skipBlanks, setSkipBlanks] = useState(false);
|
|
31
|
+
const [transpose, setTranspose] = useState(false);
|
|
32
|
+
// Excel greys Operation out under Paste → Formats (a formats paste never
|
|
33
|
+
// changes destination values); the choice is kept but not applied.
|
|
34
|
+
const operationDisabled = paste === "formats";
|
|
35
|
+
const commit = useCallback(() => {
|
|
36
|
+
onApply({
|
|
37
|
+
paste,
|
|
38
|
+
operation: operation === "none" || operationDisabled ? undefined : operation,
|
|
39
|
+
skipBlanks,
|
|
40
|
+
transpose,
|
|
41
|
+
});
|
|
42
|
+
onOpenChange(false);
|
|
43
|
+
}, [
|
|
44
|
+
onApply,
|
|
45
|
+
onOpenChange,
|
|
46
|
+
paste,
|
|
47
|
+
operation,
|
|
48
|
+
operationDisabled,
|
|
49
|
+
skipBlanks,
|
|
50
|
+
transpose,
|
|
51
|
+
]);
|
|
52
|
+
return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange, modal: true },
|
|
53
|
+
React.createElement(DialogContent, { className: "w-[380px] max-w-[92vw] pointer-events-auto" },
|
|
54
|
+
React.createElement(DialogTitle, null, "Paste Special"),
|
|
55
|
+
React.createElement(DialogCloseButton, null),
|
|
56
|
+
React.createElement(DialogBody, { className: "flex flex-col gap-4" },
|
|
57
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-4" },
|
|
58
|
+
React.createElement(RadioGroup, { name: "paste-special-paste", legend: "Paste", value: paste, options: PASTE_MODES, onChange: setPaste }),
|
|
59
|
+
React.createElement(RadioGroup, { name: "paste-special-operation", legend: "Operation", value: operation, options: OPERATIONS, onChange: setOperation, disabled: operationDisabled })),
|
|
60
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
61
|
+
React.createElement("label", { className: "flex items-center gap-2 text-xs py-0.5 cursor-pointer" },
|
|
62
|
+
React.createElement("input", { type: "checkbox", checked: skipBlanks, onChange: (e) => setSkipBlanks(e.target.checked) }),
|
|
63
|
+
"Skip blanks"),
|
|
64
|
+
React.createElement("label", { className: "flex items-center gap-2 text-xs py-0.5 cursor-pointer" },
|
|
65
|
+
React.createElement("input", { type: "checkbox", checked: transpose, onChange: (e) => setTranspose(e.target.checked) }),
|
|
66
|
+
"Transpose")),
|
|
67
|
+
React.createElement(Separator, null),
|
|
68
|
+
React.createElement("div", { className: "flex justify-end gap-2" },
|
|
69
|
+
React.createElement(Button, { variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
70
|
+
React.createElement(Button, { size: "sm", onClick: commit }, "OK"))))));
|
|
71
|
+
});
|
|
72
|
+
PasteSpecialDialog.displayName = "PasteSpecialDialog";
|
|
73
|
+
/**
|
|
74
|
+
* Host-mounted, atom-driven wrapper around {@link PasteSpecialDialog}. Mount
|
|
75
|
+
* it once in the host alongside the other editor dialogs; the grid opens it
|
|
76
|
+
* from the context menu ("Paste special…") and `Ctrl+Alt+V`. Remounted per
|
|
77
|
+
* request so each opening starts from Excel's defaults.
|
|
78
|
+
*/
|
|
79
|
+
export const PasteSpecialDialogHost = () => {
|
|
80
|
+
useRegisterPasteSpecialDialogHost();
|
|
81
|
+
const [request, setRequest] = usePasteSpecialDialogState();
|
|
82
|
+
const close = useCallback(() => setRequest(null), [setRequest]);
|
|
83
|
+
if (!request)
|
|
84
|
+
return null;
|
|
85
|
+
return (React.createElement(PasteSpecialDialog, { open: true, onOpenChange: (open) => {
|
|
86
|
+
if (!open)
|
|
87
|
+
close();
|
|
88
|
+
}, onApply: request.onApply }));
|
|
89
|
+
};
|
|
90
|
+
PasteSpecialDialogHost.displayName = "PasteSpecialDialogHost";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type RemoveDuplicatesDialogColumn } from "../../atoms/remove-duplicates-dialog.atom";
|
|
3
|
+
import type { RemoveDuplicatesOptions } from "../../types";
|
|
4
|
+
export type { RemoveDuplicatesDialogColumn };
|
|
5
|
+
export type RemoveDuplicatesDialogProps = {
|
|
6
|
+
/** Controls visibility. */
|
|
7
|
+
open: boolean;
|
|
8
|
+
onOpenChange(open: boolean): void;
|
|
9
|
+
/** Columns of the range, in sheet order. All start checked. */
|
|
10
|
+
columns: RemoveDuplicatesDialogColumn[];
|
|
11
|
+
/** Initial state of "My data has headers". */
|
|
12
|
+
initialHasHeader?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Emits the chosen compare columns and header flag. The host routes these
|
|
15
|
+
* to `onRemoveDuplicates`; the dialog never touches state directly.
|
|
16
|
+
*/
|
|
17
|
+
onApply(options: RemoveDuplicatesOptions): void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Excel's "Remove Duplicates" dialog: a "My data has headers" toggle plus a
|
|
21
|
+
* checklist of the columns that decide whether two rows are duplicates.
|
|
22
|
+
* Unchecked columns still travel with their row — only the comparison
|
|
23
|
+
* narrows.
|
|
24
|
+
*/
|
|
25
|
+
export declare const RemoveDuplicatesDialog: React.MemoExoticComponent<({ open, onOpenChange, columns, initialHasHeader, onApply, }: RemoveDuplicatesDialogProps) => React.JSX.Element>;
|
|
26
|
+
/**
|
|
27
|
+
* Host-mounted, atom-driven wrapper around {@link RemoveDuplicatesDialog}.
|
|
28
|
+
* Mount this once in the host (alongside the other editor dialogs); the
|
|
29
|
+
* context menu opens it via `useOpenRemoveDuplicatesDialog`. While no host is
|
|
30
|
+
* mounted the context menu keeps its one-click de-dupe, so adopting the
|
|
31
|
+
* dialog is opt-in per host.
|
|
32
|
+
*/
|
|
33
|
+
export declare const RemoveDuplicatesDialogHost: {
|
|
34
|
+
(): React.JSX.Element;
|
|
35
|
+
displayName: string;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/remove-duplicates-dialog/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAE/E,OAAO,EACL,KAAK,4BAA4B,EAGlC,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,YAAY,EAAE,4BAA4B,EAAE,CAAC;AAE7C,MAAM,MAAM,2BAA2B,GAAG;IACxC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,+DAA+D;IAC/D,OAAO,EAAE,4BAA4B,EAAE,CAAC;IACxC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,0FAO9B,2BAA2B,uBAqI/B,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;;;CAetC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Cross1Icon } from "@rowsncolumns/icons";
|
|
2
|
+
import { Button, Dialog, DialogBody, DialogCloseButton, DialogContent, DialogDescription, DialogTitle, Separator, } from "@rowsncolumns/ui";
|
|
3
|
+
import React, { memo, useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useRegisterRemoveDuplicatesDialogHost, useRemoveDuplicatesDialogState, } from "../../atoms/remove-duplicates-dialog.atom";
|
|
5
|
+
/**
|
|
6
|
+
* Excel's "Remove Duplicates" dialog: a "My data has headers" toggle plus a
|
|
7
|
+
* checklist of the columns that decide whether two rows are duplicates.
|
|
8
|
+
* Unchecked columns still travel with their row — only the comparison
|
|
9
|
+
* narrows.
|
|
10
|
+
*/
|
|
11
|
+
export const RemoveDuplicatesDialog = memo(({ open, onOpenChange, columns, initialHasHeader = false, onApply, }) => {
|
|
12
|
+
const allColumnIndexes = useMemo(() => columns.map((column) => column.columnIndex), [columns]);
|
|
13
|
+
const [hasHeader, setHasHeader] = useState(initialHasHeader);
|
|
14
|
+
const [checked, setChecked] = useState(() => new Set(allColumnIndexes));
|
|
15
|
+
// Re-seed when reopened for a different range.
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (open) {
|
|
18
|
+
setHasHeader(initialHasHeader);
|
|
19
|
+
setChecked(new Set(allColumnIndexes));
|
|
20
|
+
}
|
|
21
|
+
// Only reset on open transitions.
|
|
22
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
+
}, [open]);
|
|
24
|
+
const toggleColumn = useCallback((columnIndex) => {
|
|
25
|
+
setChecked((prev) => {
|
|
26
|
+
const next = new Set(prev);
|
|
27
|
+
if (next.has(columnIndex)) {
|
|
28
|
+
next.delete(columnIndex);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
next.add(columnIndex);
|
|
32
|
+
}
|
|
33
|
+
return next;
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
36
|
+
const commit = useCallback(() => {
|
|
37
|
+
const columnIndices = allColumnIndexes.filter((columnIndex) => checked.has(columnIndex));
|
|
38
|
+
onApply({ columnIndices, hasHeader });
|
|
39
|
+
onOpenChange(false);
|
|
40
|
+
}, [allColumnIndexes, checked, hasHeader, onApply, onOpenChange]);
|
|
41
|
+
return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange },
|
|
42
|
+
React.createElement(DialogContent, { className: "w-[420px] max-w-[92vw] pointer-events-auto" },
|
|
43
|
+
React.createElement(DialogTitle, null, "Remove duplicates"),
|
|
44
|
+
React.createElement(DialogDescription, null, "Select the columns that contain duplicate values. Rows that match in every selected column are removed, keeping the first one."),
|
|
45
|
+
React.createElement(DialogCloseButton, { onClick: () => onOpenChange(false) },
|
|
46
|
+
React.createElement(Cross1Icon, null)),
|
|
47
|
+
React.createElement(DialogBody, { className: "flex flex-col gap-3" },
|
|
48
|
+
React.createElement("label", { className: "font-system text-sm flex items-center gap-1" },
|
|
49
|
+
React.createElement("input", { type: "checkbox", name: "hasHeader", checked: hasHeader, onChange: (e) => setHasHeader(e.target.checked) }),
|
|
50
|
+
"My data has headers"),
|
|
51
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
52
|
+
React.createElement(Button, { variant: "outline", size: "xs", type: "button", onClick: () => setChecked(new Set(allColumnIndexes)) }, "Select all"),
|
|
53
|
+
React.createElement(Button, { variant: "outline", size: "xs", type: "button", onClick: () => setChecked(new Set()) }, "Unselect all")),
|
|
54
|
+
React.createElement("div", { className: "flex flex-col gap-1 max-h-[240px] overflow-y-auto border border-rnc-border rounded p-2", role: "group", "aria-label": "Columns" }, columns.map((column) => {
|
|
55
|
+
const text = hasHeader && column.header ? column.header : column.label;
|
|
56
|
+
return (React.createElement("label", { key: column.columnIndex, className: "font-system text-sm flex items-center gap-1" },
|
|
57
|
+
React.createElement("input", { type: "checkbox", name: `column-${column.columnIndex}`, checked: checked.has(column.columnIndex), onChange: () => toggleColumn(column.columnIndex) }),
|
|
58
|
+
React.createElement("span", { className: "truncate", title: text }, text)));
|
|
59
|
+
})),
|
|
60
|
+
React.createElement(Separator, null),
|
|
61
|
+
React.createElement("div", { className: "flex justify-end gap-2" },
|
|
62
|
+
React.createElement(Button, { variant: "outline", size: "sm", type: "button", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
63
|
+
React.createElement(Button, { size: "sm", type: "button", onClick: commit, disabled: checked.size === 0 }, "OK"))))));
|
|
64
|
+
});
|
|
65
|
+
RemoveDuplicatesDialog.displayName = "RemoveDuplicatesDialog";
|
|
66
|
+
/**
|
|
67
|
+
* Host-mounted, atom-driven wrapper around {@link RemoveDuplicatesDialog}.
|
|
68
|
+
* Mount this once in the host (alongside the other editor dialogs); the
|
|
69
|
+
* context menu opens it via `useOpenRemoveDuplicatesDialog`. While no host is
|
|
70
|
+
* mounted the context menu keeps its one-click de-dupe, so adopting the
|
|
71
|
+
* dialog is opt-in per host.
|
|
72
|
+
*/
|
|
73
|
+
export const RemoveDuplicatesDialogHost = () => {
|
|
74
|
+
useRegisterRemoveDuplicatesDialogHost();
|
|
75
|
+
const [request, setRequest] = useRemoveDuplicatesDialogState();
|
|
76
|
+
const close = useCallback(() => setRequest(null), [setRequest]);
|
|
77
|
+
return (React.createElement(RemoveDuplicatesDialog, { open: request != null, onOpenChange: (open) => {
|
|
78
|
+
if (!open)
|
|
79
|
+
close();
|
|
80
|
+
}, columns: request?.columns ?? [], initialHasHeader: request?.hasHeader, onApply: (options) => request?.onApply(options) }));
|
|
81
|
+
};
|
|
82
|
+
RemoveDuplicatesDialogHost.displayName = "RemoveDuplicatesDialogHost";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type SubtotalDialogChoices, type SubtotalDialogColumn } from "../../atoms/subtotal-dialog.atom";
|
|
3
|
+
export type { SubtotalDialogChoices, SubtotalDialogColumn };
|
|
4
|
+
export type SubtotalDialogProps = {
|
|
5
|
+
/** Controls visibility. */
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange(open: boolean): void;
|
|
8
|
+
/** Columns of the range, in sheet order. */
|
|
9
|
+
columns: SubtotalDialogColumn[];
|
|
10
|
+
/** Initial state of "My data has headers". */
|
|
11
|
+
initialHasHeader?: boolean;
|
|
12
|
+
/** Emits the choices; the host adds the range and routes to `onSubtotal`. */
|
|
13
|
+
onApply(choices: SubtotalDialogChoices): void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Excel's Subtotal dialog: "At each change in" (group column), "Use
|
|
17
|
+
* function", "Add subtotal to" (aggregate columns), "Replace current
|
|
18
|
+
* subtotals". Summary rows are always placed below the data.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SubtotalDialog: React.MemoExoticComponent<({ open, onOpenChange, columns, initialHasHeader, onApply, }: SubtotalDialogProps) => React.JSX.Element>;
|
|
21
|
+
/**
|
|
22
|
+
* Host-mounted, atom-driven wrapper around {@link SubtotalDialog}. Mount
|
|
23
|
+
* once in the host; the context menu opens it via `useOpenSubtotalDialog`
|
|
24
|
+
* and only offers the item while a host is mounted.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SubtotalDialogHost: {
|
|
27
|
+
(): React.JSX.Element;
|
|
28
|
+
displayName: string;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/subtotal-dialog/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAE/E,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAG1B,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAoB5D,MAAM,MAAM,mBAAmB,GAAG;IAChC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,4CAA4C;IAC5C,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,OAAO,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,0FAOtB,mBAAmB,uBAoKvB,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;CAe9B,CAAC"}
|