@rowsncolumns/spreadsheet 10.2.74 → 10.2.76
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/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/fill-series.d.ts +38 -0
- package/dist/cjs/api/fill-series.d.ts.map +1 -0
- package/dist/cjs/api/fill-series.js +89 -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/spreadsheet-api.d.ts +6 -2
- package/dist/cjs/api/spreadsheet-api.d.ts.map +1 -1
- package/dist/cjs/api/spreadsheet-api.js +9 -3
- 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/fill-options.atom.d.ts +36 -0
- package/dist/cjs/atoms/fill-options.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/fill-options.atom.js +11 -0
- package/dist/cjs/atoms/fill-series-dialog.atom.d.ts +26 -0
- package/dist/cjs/atoms/fill-series-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/fill-series-dialog.atom.js +14 -0
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts +11 -1
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -1
- 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 +3 -0
- package/dist/cjs/atoms/index.d.ts.map +1 -1
- package/dist/cjs/atoms/index.js +3 -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 +328 -71
- package/dist/cjs/components/canvas-grid/typings.d.ts +31 -5
- 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/helpers.d.ts +9 -0
- package/dist/cjs/components/cell-editor/helpers.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/helpers.js +8 -1
- package/dist/cjs/components/cell-editor/helpers.spec.js +14 -0
- package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.js +7 -3
- package/dist/cjs/components/context-menu/index.d.ts +6 -1
- package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
- package/dist/cjs/components/context-menu/index.js +146 -3
- 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/fill-series-dialog/index.d.ts +29 -0
- package/dist/cjs/components/fill-series-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/fill-series-dialog/index.js +156 -0
- package/dist/cjs/components/go-to-dialog/index.d.ts +11 -1
- package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -1
- package/dist/cjs/components/go-to-dialog/index.js +67 -24
- 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/selection/fill-options-menu.d.ts +32 -0
- package/dist/cjs/components/selection/fill-options-menu.d.ts.map +1 -0
- package/dist/cjs/components/selection/fill-options-menu.js +65 -0
- package/dist/cjs/components/selection/index.d.ts +1 -1
- package/dist/cjs/components/selection/index.d.ts.map +1 -1
- package/dist/cjs/components/selection/index.js +4 -2
- package/dist/cjs/components/selection/types.d.ts +11 -1
- package/dist/cjs/components/selection/types.d.ts.map +1 -1
- 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 +1 -0
- package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/index.js +1 -0
- package/dist/cjs/components/toolbar/text-format-selector.d.ts +9 -0
- package/dist/cjs/components/toolbar/text-format-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/text-format-selector.js +15 -3
- 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/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-editable.d.ts +11 -2
- package/dist/cjs/hooks/use-editable.d.ts.map +1 -1
- package/dist/cjs/hooks/use-editable.js +8 -0
- package/dist/cjs/hooks/use-editor-submit.d.ts +32 -3
- package/dist/cjs/hooks/use-editor-submit.d.ts.map +1 -1
- package/dist/cjs/hooks/use-editor-submit.js +111 -2
- 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.map +1 -1
- package/dist/cjs/hooks/use-keyboard-handler.js +27 -14
- package/dist/cjs/hooks/use-selection-renderer.d.ts +13 -2
- package/dist/cjs/hooks/use-selection-renderer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-selection-renderer.js +21 -4
- package/dist/cjs/hooks/use-selection.d.ts +54 -4
- package/dist/cjs/hooks/use-selection.d.ts.map +1 -1
- package/dist/cjs/hooks/use-selection.js +232 -37
- package/dist/cjs/index.d.ts +12 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/release-date.js +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +156 -5
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +19 -1
- 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/fill-series.d.ts +38 -0
- package/dist/esm/api/fill-series.d.ts.map +1 -0
- package/dist/esm/api/fill-series.js +78 -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/spreadsheet-api.d.ts +6 -2
- package/dist/esm/api/spreadsheet-api.d.ts.map +1 -1
- package/dist/esm/api/spreadsheet-api.js +8 -3
- 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/fill-options.atom.d.ts +36 -0
- package/dist/esm/atoms/fill-options.atom.d.ts.map +1 -0
- package/dist/esm/atoms/fill-options.atom.js +5 -0
- package/dist/esm/atoms/fill-series-dialog.atom.d.ts +26 -0
- package/dist/esm/atoms/fill-series-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/fill-series-dialog.atom.js +9 -0
- package/dist/esm/atoms/go-to-dialog.atom.d.ts +11 -1
- package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -1
- 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 +3 -0
- package/dist/esm/atoms/index.d.ts.map +1 -1
- package/dist/esm/atoms/index.js +3 -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 +266 -26
- package/dist/esm/components/canvas-grid/typings.d.ts +31 -5
- 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/helpers.d.ts +9 -0
- package/dist/esm/components/cell-editor/helpers.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/helpers.js +6 -0
- package/dist/esm/components/cell-editor/helpers.spec.js +15 -1
- package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.js +8 -4
- package/dist/esm/components/context-menu/index.d.ts +6 -1
- package/dist/esm/components/context-menu/index.d.ts.map +1 -1
- package/dist/esm/components/context-menu/index.js +139 -4
- 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/fill-series-dialog/index.d.ts +29 -0
- package/dist/esm/components/fill-series-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/fill-series-dialog/index.js +151 -0
- package/dist/esm/components/go-to-dialog/index.d.ts +11 -1
- package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -1
- package/dist/esm/components/go-to-dialog/index.js +63 -23
- 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/selection/fill-options-menu.d.ts +32 -0
- package/dist/esm/components/selection/fill-options-menu.d.ts.map +1 -0
- package/dist/esm/components/selection/fill-options-menu.js +63 -0
- package/dist/esm/components/selection/index.d.ts +1 -1
- package/dist/esm/components/selection/index.d.ts.map +1 -1
- package/dist/esm/components/selection/index.js +4 -2
- package/dist/esm/components/selection/types.d.ts +11 -1
- package/dist/esm/components/selection/types.d.ts.map +1 -1
- 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 +1 -0
- package/dist/esm/components/toolbar/index.d.ts.map +1 -1
- package/dist/esm/components/toolbar/index.js +1 -0
- package/dist/esm/components/toolbar/text-format-selector.d.ts +9 -0
- package/dist/esm/components/toolbar/text-format-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/text-format-selector.js +14 -2
- 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/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-editable.d.ts +11 -2
- package/dist/esm/hooks/use-editable.d.ts.map +1 -1
- package/dist/esm/hooks/use-editable.js +8 -0
- package/dist/esm/hooks/use-editor-submit.d.ts +32 -3
- package/dist/esm/hooks/use-editor-submit.d.ts.map +1 -1
- package/dist/esm/hooks/use-editor-submit.js +82 -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.map +1 -1
- package/dist/esm/hooks/use-keyboard-handler.js +27 -14
- package/dist/esm/hooks/use-selection-renderer.d.ts +13 -2
- package/dist/esm/hooks/use-selection-renderer.d.ts.map +1 -1
- package/dist/esm/hooks/use-selection-renderer.js +22 -5
- package/dist/esm/hooks/use-selection.d.ts +54 -4
- package/dist/esm/hooks/use-selection.d.ts.map +1 -1
- package/dist/esm/hooks/use-selection.js +220 -36
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +11 -0
- package/dist/esm/release-date.js +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +156 -5
- 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 +7 -7
|
@@ -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,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { AutoFillType } from "../../types";
|
|
3
|
+
export type FillOptionsMenuProps = {
|
|
4
|
+
/** Position/size of the selection the menu is anchored to (bottom-right). */
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
/**
|
|
10
|
+
* `"smart-tag"` — Excel's Auto Fill Options button after a left drag (the
|
|
11
|
+
* fill already ran). `"menu"` — the menu a right-button drag opens on
|
|
12
|
+
* release (nothing filled yet).
|
|
13
|
+
*/
|
|
14
|
+
mode: "smart-tag" | "menu";
|
|
15
|
+
/** The fill type currently applied (`undefined` = auto-detected). */
|
|
16
|
+
fillType?: AutoFillType;
|
|
17
|
+
/** Source has date-formatted cells → Fill Days / Weekdays / Months / Years. */
|
|
18
|
+
hasDates: boolean;
|
|
19
|
+
onChooseFillType(fillType: AutoFillType): void;
|
|
20
|
+
/** "Series…" (right-drag menu only). Hidden when omitted. */
|
|
21
|
+
onOpenSeriesDialog?(): void;
|
|
22
|
+
/** Menu dismissed without a choice. */
|
|
23
|
+
onClose?(): void;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Excel's fill-type chooser. Two skins over one list: the Auto Fill Options
|
|
27
|
+
* smart tag (a small button at the bottom-right of the filled range that
|
|
28
|
+
* opens the list on click) and the right-drag menu (opens immediately on
|
|
29
|
+
* release, adds Linear/Growth Trend and Series…). Flash Fill is out of scope.
|
|
30
|
+
*/
|
|
31
|
+
export declare const FillOptionsMenu: ({ x, y, width, height, mode, fillType, hasDates, onChooseFillType, onOpenSeriesDialog, onClose, }: FillOptionsMenuProps) => React.JSX.Element;
|
|
32
|
+
//# sourceMappingURL=fill-options-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill-options-menu.d.ts","sourceRoot":"","sources":["../../../../components/selection/fill-options-menu.tsx"],"names":[],"mappings":"AAYA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,oBAAoB,GAAG;IACjC,6EAA6E;IAC7E,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,IAAI,EAAE,WAAW,GAAG,MAAM,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,6DAA6D;IAC7D,kBAAkB,CAAC,IAAI,IAAI,CAAC;IAC5B,uCAAuC;IACvC,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB,CAAC;AAuBF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,mGAW7B,oBAAoB,sBA+EtB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CheckIcon, MdOutlineArrowDropDown } from "@rowsncolumns/icons";
|
|
2
|
+
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuPortal, DropdownMenuSeparator, DropdownMenuTrigger, IconButton, } from "@rowsncolumns/ui";
|
|
3
|
+
import React, { useRef, useState } from "react";
|
|
4
|
+
import { StyledFakeTriggerButton } from "../context-menu/style";
|
|
5
|
+
const BASE_ENTRIES = [
|
|
6
|
+
{ fillType: "FillCopy", label: "Copy Cells" },
|
|
7
|
+
{ fillType: "FillSeries", label: "Fill Series" },
|
|
8
|
+
{ fillType: "FillFormats", label: "Fill Formatting Only" },
|
|
9
|
+
{ fillType: "FillWithoutFormatting", label: "Fill Without Formatting" },
|
|
10
|
+
];
|
|
11
|
+
const DATE_ENTRIES = [
|
|
12
|
+
{ fillType: "FillDays", label: "Fill Days" },
|
|
13
|
+
{ fillType: "FillWeekdays", label: "Fill Weekdays" },
|
|
14
|
+
{ fillType: "FillMonths", label: "Fill Months" },
|
|
15
|
+
{ fillType: "FillYears", label: "Fill Years" },
|
|
16
|
+
];
|
|
17
|
+
const TREND_ENTRIES = [
|
|
18
|
+
{ fillType: "LinearTrend", label: "Linear Trend" },
|
|
19
|
+
{ fillType: "GrowthTrend", label: "Growth Trend" },
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Excel's fill-type chooser. Two skins over one list: the Auto Fill Options
|
|
23
|
+
* smart tag (a small button at the bottom-right of the filled range that
|
|
24
|
+
* opens the list on click) and the right-drag menu (opens immediately on
|
|
25
|
+
* release, adds Linear/Growth Trend and Series…). Flash Fill is out of scope.
|
|
26
|
+
*/
|
|
27
|
+
export const FillOptionsMenu = ({ x, y, width, height, mode, fillType, hasDates, onChooseFillType, onOpenSeriesDialog, onClose, }) => {
|
|
28
|
+
const [open, setOpen] = useState(mode === "menu");
|
|
29
|
+
const isMenu = mode === "menu";
|
|
30
|
+
// Radix fires `onSelect` and then closes; a close that follows a choice is
|
|
31
|
+
// not a cancel.
|
|
32
|
+
const choseRef = useRef(false);
|
|
33
|
+
const entries = [
|
|
34
|
+
...BASE_ENTRIES,
|
|
35
|
+
...(hasDates ? DATE_ENTRIES : []),
|
|
36
|
+
...(isMenu ? TREND_ENTRIES : []),
|
|
37
|
+
];
|
|
38
|
+
return (React.createElement("div", { className: "absolute pointer-events-auto flex items-start justify-start", style: { left: x + width, top: y + height }, onMouseDown: (e) => e.stopPropagation(), "data-testid": "fill-options" },
|
|
39
|
+
React.createElement(DropdownMenu, { modal: false, open: open, onOpenChange: (next) => {
|
|
40
|
+
setOpen(next);
|
|
41
|
+
if (!next && isMenu && !choseRef.current)
|
|
42
|
+
onClose?.();
|
|
43
|
+
if (!next)
|
|
44
|
+
choseRef.current = false;
|
|
45
|
+
} },
|
|
46
|
+
React.createElement(DropdownMenuTrigger, { asChild: true }, isMenu ? (React.createElement(StyledFakeTriggerButton, { "aria-label": "Fill options" })) : (React.createElement(IconButton, { tooltip: "Auto Fill Options", "aria-label": "Auto Fill Options", className: "shadow-sm bg-rnc-background border-rnc-border border border-solid rounded-md" },
|
|
47
|
+
React.createElement(MdOutlineArrowDropDown, null)))),
|
|
48
|
+
React.createElement(DropdownMenuPortal, null,
|
|
49
|
+
React.createElement(DropdownMenuContent, { align: "start", onCloseAutoFocus: (event) => event.preventDefault() },
|
|
50
|
+
entries.map((entry) => (React.createElement(DropdownMenuCheckboxItem, { key: entry.fillType, checked: fillType === entry.fillType, onSelect: () => {
|
|
51
|
+
choseRef.current = true;
|
|
52
|
+
onChooseFillType(entry.fillType);
|
|
53
|
+
} },
|
|
54
|
+
React.createElement(DropdownMenuItemIndicator, null,
|
|
55
|
+
React.createElement(CheckIcon, null)),
|
|
56
|
+
entry.label))),
|
|
57
|
+
isMenu && onOpenSeriesDialog ? (React.createElement(React.Fragment, null,
|
|
58
|
+
React.createElement(DropdownMenuSeparator, null),
|
|
59
|
+
React.createElement(DropdownMenuItem, { onSelect: () => {
|
|
60
|
+
choseRef.current = true;
|
|
61
|
+
onOpenSeriesDialog();
|
|
62
|
+
} }, "Series\u2026"))) : null)))));
|
|
63
|
+
};
|
|
@@ -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, 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, 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;
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import React, { memo } from "react";
|
|
2
|
+
import { FillOptionsMenu as DefaultFillOptionsMenu } from "./fill-options-menu";
|
|
2
3
|
import { MagicFill } from "./magic-fill";
|
|
3
4
|
import { PasteMenu as DefaultPasteMenu } from "./paste-menu";
|
|
4
5
|
import { QuickEdit as DefaultQuickEdit } from "./quick-edit";
|
|
@@ -10,7 +11,7 @@ import { SelectionTitle } from "./title";
|
|
|
10
11
|
* @param param0
|
|
11
12
|
* @returns
|
|
12
13
|
*/
|
|
13
|
-
export const Selection = memo(({ x = 0, y = 0, width = 0, height = 0, fillHandleSize = 8, showFillHandle, offset = 0, stroke, strokeLeftColor = stroke, strokeTopColor = stroke, strokeRightColor = stroke, strokeBottomColor = stroke, strokeWidth = 0, strokeTopWidth = strokeWidth, strokeRightWidth = strokeWidth, strokeBottomWidth = strokeWidth, strokeLeftWidth = strokeWidth, strokeStyle = "solid", fillOpacity = 1, draggable, isDragging, borderCoverWidth = 5, type, range, title, keepTitleWithinBounds, keepTitleWithinVerticalBounds, flipTitleToBottom, table, pivotTable, onMouseDownTitle, onMouseDownResizeHandle, onMouseDownMoveHandle, TitleComponent = SelectionTitle, onMouseDown, onRequestEditTable, onRequestEditPivotTable, onFlashEnd, zIndex, index, id, showResizeHandles, resizeHandleBorderColor, resizeHandleSize = 6, fill, duration, selectionCount, enableMagicFill, sheetId, activeCell, selection, MagicFillComponent = MagicFill, metadata, readonly, showPasteMenu, onSplitTextToColumns, agentSuggestion, isHovered, MagicFillContent, QuickEdit = DefaultQuickEdit, PasteMenu = DefaultPasteMenu, SuggestionAction = DefaultSuggestionAction, onRemoveQuickEdit, onAddQuickEdit, }) => {
|
|
14
|
+
export const Selection = memo(({ x = 0, y = 0, width = 0, height = 0, fillHandleSize = 8, showFillHandle, offset = 0, stroke, strokeLeftColor = stroke, strokeTopColor = stroke, strokeRightColor = stroke, strokeBottomColor = stroke, strokeWidth = 0, strokeTopWidth = strokeWidth, strokeRightWidth = strokeWidth, strokeBottomWidth = strokeWidth, strokeLeftWidth = strokeWidth, strokeStyle = "solid", fillOpacity = 1, draggable, isDragging, borderCoverWidth = 5, type, range, title, keepTitleWithinBounds, keepTitleWithinVerticalBounds, flipTitleToBottom, table, pivotTable, onMouseDownTitle, onMouseDownResizeHandle, onMouseDownMoveHandle, TitleComponent = SelectionTitle, onMouseDown, onRequestEditTable, onRequestEditPivotTable, onFlashEnd, zIndex, index, id, showResizeHandles, resizeHandleBorderColor, resizeHandleSize = 6, fill, duration, selectionCount, enableMagicFill, sheetId, activeCell, selection, MagicFillComponent = MagicFill, metadata, readonly, showPasteMenu, onSplitTextToColumns, agentSuggestion, isHovered, MagicFillContent, QuickEdit = DefaultQuickEdit, PasteMenu = DefaultPasteMenu, FillOptionsMenu = DefaultFillOptionsMenu, fillOptions, onChooseFillType, onOpenFillSeriesDialog, onCloseFillOptions, SuggestionAction = DefaultSuggestionAction, onRemoveQuickEdit, onAddQuickEdit, }) => {
|
|
14
15
|
// Show paste menu
|
|
15
16
|
const finalShowPasteMenu = !showPasteMenu
|
|
16
17
|
? false
|
|
@@ -187,6 +188,7 @@ export const Selection = memo(({ x = 0, y = 0, width = 0, height = 0, fillHandle
|
|
|
187
188
|
(type === "selection" ||
|
|
188
189
|
(type === "activeCell" && selectionCount === 0)) ? (React.createElement(MagicFillComponent, { sheetId: sheetId, x: x, y: y, height: height, width: width, type: type, activeCell: activeCell, selection: selection, isHovered: isHovered, MagicFillContent: MagicFillContent, onAddQuickEdit: onAddQuickEdit, backgroundColor: stroke })) : null,
|
|
189
190
|
type === "quickedit" ? (React.createElement(QuickEdit, { x: x, y: y, width: width, height: height, range: range, sheetId: sheetId, onRemoveQuickEdit: onRemoveQuickEdit })) : null,
|
|
190
|
-
finalShowPasteMenu && (React.createElement(PasteMenu, { x: x + fillHandleSize / 2, y: y + fillHandleSize / 2, width: width, height: height, onSplitTextToColumns: onSplitTextToColumns }))
|
|
191
|
+
finalShowPasteMenu && (React.createElement(PasteMenu, { x: x + fillHandleSize / 2, y: y + fillHandleSize / 2, width: width, height: height, onSplitTextToColumns: onSplitTextToColumns })),
|
|
192
|
+
fillOptions && type === "selection" && onChooseFillType ? (React.createElement(FillOptionsMenu, { x: x + fillHandleSize / 2, y: y + fillHandleSize / 2, width: width, height: height, mode: fillOptions.mode, fillType: fillOptions.fillType, hasDates: fillOptions.hasDates, onChooseFillType: onChooseFillType, onOpenSeriesDialog: onOpenFillSeriesDialog, onClose: onCloseFillOptions })) : null));
|
|
191
193
|
});
|
|
192
194
|
Selection.displayName = "Selection";
|
|
@@ -2,8 +2,9 @@ import type { CellInterface } from "@rowsncolumns/common-types";
|
|
|
2
2
|
import type { SelectionProps } from "@rowsncolumns/grid";
|
|
3
3
|
import type { ElementType } from "react";
|
|
4
4
|
import type { Maybe, PivotTable, SheetRange } from "../../types";
|
|
5
|
-
import type { TableView } from "../../types";
|
|
5
|
+
import type { AutoFillType, TableView } from "../../types";
|
|
6
6
|
import type { SelectionAttributes } from "../canvas-grid/typings";
|
|
7
|
+
import type { FillOptionsMenuProps } from "./fill-options-menu";
|
|
7
8
|
import type { MagicFillContentProps, MagicFillProps } from "./magic-fill";
|
|
8
9
|
import type { PasteMenuProps } from "./paste-menu";
|
|
9
10
|
import type { QuickEditProps } from "./quick-edit";
|
|
@@ -31,6 +32,15 @@ export type SelectionNewProps = SelectionProps<SelectionAttributes> & {
|
|
|
31
32
|
MagicFillContent?: React.ComponentType<MagicFillContentProps>;
|
|
32
33
|
QuickEdit?: React.ComponentType<QuickEditProps>;
|
|
33
34
|
PasteMenu?: React.ComponentType<PasteMenuProps>;
|
|
35
|
+
FillOptionsMenu?: React.ComponentType<FillOptionsMenuProps>;
|
|
36
|
+
/**
|
|
37
|
+
* Pending Auto Fill Options / right-drag fill menu for THIS selection
|
|
38
|
+
* (present only on the selection whose range is the fill target).
|
|
39
|
+
*/
|
|
40
|
+
fillOptions?: Pick<FillOptionsMenuProps, "mode" | "fillType" | "hasDates">;
|
|
41
|
+
onChooseFillType?(fillType: AutoFillType): void;
|
|
42
|
+
onOpenFillSeriesDialog?(): void;
|
|
43
|
+
onCloseFillOptions?(): void;
|
|
34
44
|
SuggestionAction?: React.ComponentType<SuggestionActionProps>;
|
|
35
45
|
readonly?: boolean;
|
|
36
46
|
showPasteMenu?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../components/selection/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../components/selection/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
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 { useRegisterSubtotalDialogHost, useSubtotalDialogState, } from "../../atoms/subtotal-dialog.atom";
|
|
5
|
+
import { SUBTOTAL_FUNCTIONS } from "../../types";
|
|
6
|
+
/**
|
|
7
|
+
* Excel's "Use function" labels. Excel's "Count" is COUNTA (non-empty cells,
|
|
8
|
+
* `SUBTOTAL(3,…)`) and "Count Numbers" is COUNT (`SUBTOTAL(2,…)`).
|
|
9
|
+
*/
|
|
10
|
+
const FUNCTION_LABELS = {
|
|
11
|
+
SUM: "Sum",
|
|
12
|
+
COUNTA: "Count",
|
|
13
|
+
AVERAGE: "Average",
|
|
14
|
+
MAX: "Max",
|
|
15
|
+
MIN: "Min",
|
|
16
|
+
PRODUCT: "Product",
|
|
17
|
+
COUNT: "Count Numbers",
|
|
18
|
+
STDEV: "StdDev",
|
|
19
|
+
STDEVP: "StdDevp",
|
|
20
|
+
VAR: "Var",
|
|
21
|
+
VARP: "Varp",
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Excel's Subtotal dialog: "At each change in" (group column), "Use
|
|
25
|
+
* function", "Add subtotal to" (aggregate columns), "Replace current
|
|
26
|
+
* subtotals". Summary rows are always placed below the data.
|
|
27
|
+
*/
|
|
28
|
+
export const SubtotalDialog = memo(({ open, onOpenChange, columns, initialHasHeader = true, onApply, }) => {
|
|
29
|
+
const [hasHeader, setHasHeader] = useState(initialHasHeader);
|
|
30
|
+
const [groupBy, setGroupBy] = useState(columns[0]?.columnIndex);
|
|
31
|
+
const [fn, setFn] = useState("SUM");
|
|
32
|
+
const [aggregates, setAggregates] = useState(() => new Set(columns.length ? [columns[columns.length - 1].columnIndex] : []));
|
|
33
|
+
const [replaceCurrent, setReplaceCurrent] = useState(true);
|
|
34
|
+
// Re-seed when reopened for a different range.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (open) {
|
|
37
|
+
setHasHeader(initialHasHeader);
|
|
38
|
+
setGroupBy(columns[0]?.columnIndex);
|
|
39
|
+
setFn("SUM");
|
|
40
|
+
setAggregates(new Set(columns.length ? [columns[columns.length - 1].columnIndex] : []));
|
|
41
|
+
setReplaceCurrent(true);
|
|
42
|
+
}
|
|
43
|
+
// Only reset on open transitions.
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
|
+
}, [open]);
|
|
46
|
+
const labelOf = useCallback((column) => hasHeader && column.header ? column.header : column.label, [hasHeader]);
|
|
47
|
+
const toggleAggregate = useCallback((columnIndex) => {
|
|
48
|
+
setAggregates((prev) => {
|
|
49
|
+
const next = new Set(prev);
|
|
50
|
+
if (next.has(columnIndex))
|
|
51
|
+
next.delete(columnIndex);
|
|
52
|
+
else
|
|
53
|
+
next.add(columnIndex);
|
|
54
|
+
return next;
|
|
55
|
+
});
|
|
56
|
+
}, []);
|
|
57
|
+
const canApply = groupBy !== undefined && aggregates.size > 0;
|
|
58
|
+
const orderedAggregates = useMemo(() => columns.map((c) => c.columnIndex).filter((c) => aggregates.has(c)), [columns, aggregates]);
|
|
59
|
+
const commit = useCallback(() => {
|
|
60
|
+
if (groupBy === undefined)
|
|
61
|
+
return;
|
|
62
|
+
onApply({
|
|
63
|
+
groupByColumnIndex: groupBy,
|
|
64
|
+
aggregateColumnIndices: orderedAggregates,
|
|
65
|
+
fn,
|
|
66
|
+
hasHeader,
|
|
67
|
+
replaceCurrent,
|
|
68
|
+
});
|
|
69
|
+
onOpenChange(false);
|
|
70
|
+
}, [groupBy, orderedAggregates, fn, hasHeader, replaceCurrent, onApply, onOpenChange]);
|
|
71
|
+
return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange },
|
|
72
|
+
React.createElement(DialogContent, { className: "w-[420px] max-w-[92vw] pointer-events-auto" },
|
|
73
|
+
React.createElement(DialogTitle, null, "Subtotal"),
|
|
74
|
+
React.createElement(DialogDescription, null, "Insert a total row after every change in a column, plus a grand total, and outline the groups."),
|
|
75
|
+
React.createElement(DialogCloseButton, { onClick: () => onOpenChange(false) },
|
|
76
|
+
React.createElement(Cross1Icon, null)),
|
|
77
|
+
React.createElement(DialogBody, { className: "flex flex-col gap-3 font-system text-sm" },
|
|
78
|
+
React.createElement("label", { className: "flex items-center gap-1" },
|
|
79
|
+
React.createElement("input", { type: "checkbox", name: "hasHeader", checked: hasHeader, onChange: (e) => setHasHeader(e.target.checked) }),
|
|
80
|
+
"My data has headers"),
|
|
81
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
82
|
+
"At each change in:",
|
|
83
|
+
React.createElement("select", { className: "h-8 rounded border border-rnc-border bg-transparent px-2 text-sm", "aria-label": "At each change in", value: groupBy ?? "", onChange: (e) => setGroupBy(Number(e.target.value)) }, columns.map((column) => (React.createElement("option", { key: column.columnIndex, value: column.columnIndex }, labelOf(column)))))),
|
|
84
|
+
React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
|
|
85
|
+
"Use function:",
|
|
86
|
+
React.createElement("select", { className: "h-8 rounded border border-rnc-border bg-transparent px-2 text-sm", "aria-label": "Use function", value: fn, onChange: (e) => setFn(e.target.value) }, SUBTOTAL_FUNCTIONS.map((name) => (React.createElement("option", { key: name, value: name }, FUNCTION_LABELS[name]))))),
|
|
87
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
88
|
+
React.createElement("span", { className: "text-xs font-medium" }, "Add subtotal to:"),
|
|
89
|
+
React.createElement("div", { className: "flex flex-col gap-1 max-h-[180px] overflow-y-auto border border-rnc-border rounded p-2", role: "group", "aria-label": "Add subtotal to" }, columns.map((column) => (React.createElement("label", { key: column.columnIndex, className: "flex items-center gap-1" },
|
|
90
|
+
React.createElement("input", { type: "checkbox", name: `subtotal-column-${column.columnIndex}`, checked: aggregates.has(column.columnIndex), onChange: () => toggleAggregate(column.columnIndex) }),
|
|
91
|
+
React.createElement("span", { className: "truncate", title: labelOf(column) }, labelOf(column))))))),
|
|
92
|
+
React.createElement("label", { className: "flex items-center gap-1" },
|
|
93
|
+
React.createElement("input", { type: "checkbox", name: "replaceCurrent", checked: replaceCurrent, onChange: (e) => setReplaceCurrent(e.target.checked) }),
|
|
94
|
+
"Replace current subtotals"),
|
|
95
|
+
React.createElement(Separator, null),
|
|
96
|
+
React.createElement("div", { className: "flex justify-end gap-2" },
|
|
97
|
+
React.createElement(Button, { variant: "outline", size: "sm", type: "button", onClick: () => onOpenChange(false) }, "Cancel"),
|
|
98
|
+
React.createElement(Button, { size: "sm", type: "button", onClick: commit, disabled: !canApply }, "OK"))))));
|
|
99
|
+
});
|
|
100
|
+
SubtotalDialog.displayName = "SubtotalDialog";
|
|
101
|
+
/**
|
|
102
|
+
* Host-mounted, atom-driven wrapper around {@link SubtotalDialog}. Mount
|
|
103
|
+
* once in the host; the context menu opens it via `useOpenSubtotalDialog`
|
|
104
|
+
* and only offers the item while a host is mounted.
|
|
105
|
+
*/
|
|
106
|
+
export const SubtotalDialogHost = () => {
|
|
107
|
+
useRegisterSubtotalDialogHost();
|
|
108
|
+
const [request, setRequest] = useSubtotalDialogState();
|
|
109
|
+
const close = useCallback(() => setRequest(null), [setRequest]);
|
|
110
|
+
return (React.createElement(SubtotalDialog, { open: request != null, onOpenChange: (open) => {
|
|
111
|
+
if (!open)
|
|
112
|
+
close();
|
|
113
|
+
}, columns: request?.columns ?? [], initialHasHeader: request?.hasHeader, onApply: (choices) => request?.onApply(choices) }));
|
|
114
|
+
};
|
|
115
|
+
SubtotalDialogHost.displayName = "SubtotalDialogHost";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SplitTextOptions } from "../../types";
|
|
3
|
+
export type TextToColumnsMode = "delimited" | "fixed";
|
|
4
|
+
export type TextToColumnsDialogProps = {
|
|
5
|
+
/** Controls visibility. */
|
|
6
|
+
open: boolean;
|
|
7
|
+
onOpenChange(open: boolean): void;
|
|
8
|
+
/** Texts of the first cells of the selected column — the preview / ruler. */
|
|
9
|
+
preview: string[];
|
|
10
|
+
/** Initial wizard step-1 choice (default Delimited). */
|
|
11
|
+
initialMode?: TextToColumnsMode;
|
|
12
|
+
/**
|
|
13
|
+
* Emits the chosen delimiter (Delimited) or column breaks (Fixed width).
|
|
14
|
+
* The host routes it to `onSplitTextToColumns`.
|
|
15
|
+
*/
|
|
16
|
+
onApply(options: {
|
|
17
|
+
delimiter: string;
|
|
18
|
+
} & SplitTextOptions): void;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Excel's Convert Text to Columns wizard, condensed: choose Delimited (pick
|
|
22
|
+
* a delimiter or type another) or Fixed width (click the ruler to add or
|
|
23
|
+
* remove break lines, or type the positions), with a live preview of the
|
|
24
|
+
* first rows.
|
|
25
|
+
*/
|
|
26
|
+
export declare const TextToColumnsDialog: React.MemoExoticComponent<({ open, onOpenChange, preview, initialMode, onApply, }: TextToColumnsDialogProps) => React.JSX.Element>;
|
|
27
|
+
/**
|
|
28
|
+
* Host-mounted, atom-driven wrapper around {@link TextToColumnsDialog}.
|
|
29
|
+
* Mount once in the host (alongside the other editor dialogs); the context
|
|
30
|
+
* menu opens it via `useOpenTextToColumnsDialog`. Without a host the menu
|
|
31
|
+
* keeps its one-click delimiter items only.
|
|
32
|
+
*/
|
|
33
|
+
export declare const TextToColumnsDialogHost: {
|
|
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/text-to-columns-dialog/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAM/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,OAAO,CAAC;AAEtD,MAAM,MAAM,wBAAwB,GAAG;IACrC,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wDAAwD;IACxD,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB,GAAG,IAAI,CAAC;CAClE,CAAC;AAiCF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,qFAO3B,wBAAwB,uBAgO5B,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;CAenC,CAAC"}
|