@rowsncolumns/spreadsheet 10.2.72 → 10.2.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/cjs/api/evaluate-formula.d.ts +17 -0
  2. package/dist/cjs/api/evaluate-formula.d.ts.map +1 -0
  3. package/dist/cjs/api/evaluate-formula.js +141 -0
  4. package/dist/cjs/api/go-to.d.ts +19 -0
  5. package/dist/cjs/api/go-to.d.ts.map +1 -0
  6. package/dist/cjs/api/go-to.js +49 -0
  7. package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
  8. package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
  9. package/dist/cjs/atoms/evaluate-formula-dialog.atom.js +36 -0
  10. package/dist/cjs/atoms/go-to-dialog.atom.d.ts +36 -0
  11. package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -0
  12. package/dist/cjs/atoms/go-to-dialog.atom.js +34 -0
  13. package/dist/cjs/atoms/index.d.ts +4 -0
  14. package/dist/cjs/atoms/index.d.ts.map +1 -1
  15. package/dist/cjs/atoms/index.js +4 -0
  16. package/dist/cjs/atoms/paste-special-dialog.atom.d.ts +29 -0
  17. package/dist/cjs/atoms/paste-special-dialog.atom.d.ts.map +1 -0
  18. package/dist/cjs/atoms/paste-special-dialog.atom.js +34 -0
  19. package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
  20. package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
  21. package/dist/cjs/atoms/remove-duplicates-dialog.atom.js +37 -0
  22. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  23. package/dist/cjs/components/canvas-grid/index.js +109 -3
  24. package/dist/cjs/components/canvas-grid/typings.d.ts +54 -8
  25. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  26. package/dist/cjs/components/context-menu/index.d.ts +16 -3
  27. package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
  28. package/dist/cjs/components/context-menu/index.js +112 -5
  29. package/dist/cjs/components/context-menu/total-row-functions.d.ts +32 -0
  30. package/dist/cjs/components/context-menu/total-row-functions.d.ts.map +1 -0
  31. package/dist/cjs/components/context-menu/total-row-functions.js +62 -0
  32. package/dist/cjs/components/evaluate-formula-dialog/index.d.ts +34 -0
  33. package/dist/cjs/components/evaluate-formula-dialog/index.d.ts.map +1 -0
  34. package/dist/cjs/components/evaluate-formula-dialog/index.js +106 -0
  35. package/dist/cjs/components/go-to-dialog/index.d.ts +35 -0
  36. package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -0
  37. package/dist/cjs/components/go-to-dialog/index.js +93 -0
  38. package/dist/cjs/components/paste-special-dialog/index.d.ts +29 -0
  39. package/dist/cjs/components/paste-special-dialog/index.d.ts.map +1 -0
  40. package/dist/cjs/components/paste-special-dialog/index.js +99 -0
  41. package/dist/cjs/components/remove-duplicates-dialog/index.d.ts +37 -0
  42. package/dist/cjs/components/remove-duplicates-dialog/index.d.ts.map +1 -0
  43. package/dist/cjs/components/remove-duplicates-dialog/index.js +91 -0
  44. package/dist/cjs/components/toolbar/index.d.ts +1 -0
  45. package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
  46. package/dist/cjs/components/toolbar/index.js +1 -0
  47. package/dist/cjs/components/toolbar/text-rotation-selector.d.ts +29 -0
  48. package/dist/cjs/components/toolbar/text-rotation-selector.d.ts.map +1 -0
  49. package/dist/cjs/components/toolbar/text-rotation-selector.js +78 -0
  50. package/dist/cjs/components/toolbar/text-wrap-selector.d.ts +7 -1
  51. package/dist/cjs/components/toolbar/text-wrap-selector.d.ts.map +1 -1
  52. package/dist/cjs/components/toolbar/text-wrap-selector.js +10 -3
  53. package/dist/cjs/hooks/use-copy-paste-cut.d.ts +20 -1
  54. package/dist/cjs/hooks/use-copy-paste-cut.d.ts.map +1 -1
  55. package/dist/cjs/hooks/use-copy-paste-cut.js +27 -21
  56. package/dist/cjs/hooks/use-keyboard-handler.d.ts +17 -2
  57. package/dist/cjs/hooks/use-keyboard-handler.d.ts.map +1 -1
  58. package/dist/cjs/hooks/use-keyboard-handler.js +186 -21
  59. package/dist/cjs/index.d.ts +6 -1
  60. package/dist/cjs/index.d.ts.map +1 -1
  61. package/dist/cjs/index.js +7 -1
  62. package/dist/cjs/release-date.js +1 -1
  63. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  64. package/dist/cjs/types.d.ts +173 -1
  65. package/dist/cjs/types.d.ts.map +1 -1
  66. package/dist/esm/api/evaluate-formula.d.ts +17 -0
  67. package/dist/esm/api/evaluate-formula.d.ts.map +1 -0
  68. package/dist/esm/api/evaluate-formula.js +109 -0
  69. package/dist/esm/api/go-to.d.ts +19 -0
  70. package/dist/esm/api/go-to.d.ts.map +1 -0
  71. package/dist/esm/api/go-to.js +41 -0
  72. package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
  73. package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
  74. package/dist/esm/atoms/evaluate-formula-dialog.atom.js +25 -0
  75. package/dist/esm/atoms/go-to-dialog.atom.d.ts +36 -0
  76. package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -0
  77. package/dist/esm/atoms/go-to-dialog.atom.js +25 -0
  78. package/dist/esm/atoms/index.d.ts +4 -0
  79. package/dist/esm/atoms/index.d.ts.map +1 -1
  80. package/dist/esm/atoms/index.js +4 -0
  81. package/dist/esm/atoms/paste-special-dialog.atom.d.ts +29 -0
  82. package/dist/esm/atoms/paste-special-dialog.atom.d.ts.map +1 -0
  83. package/dist/esm/atoms/paste-special-dialog.atom.js +25 -0
  84. package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
  85. package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
  86. package/dist/esm/atoms/remove-duplicates-dialog.atom.js +26 -0
  87. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  88. package/dist/esm/components/canvas-grid/index.js +107 -3
  89. package/dist/esm/components/canvas-grid/typings.d.ts +54 -8
  90. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  91. package/dist/esm/components/context-menu/index.d.ts +16 -3
  92. package/dist/esm/components/context-menu/index.d.ts.map +1 -1
  93. package/dist/esm/components/context-menu/index.js +114 -7
  94. package/dist/esm/components/context-menu/total-row-functions.d.ts +32 -0
  95. package/dist/esm/components/context-menu/total-row-functions.d.ts.map +1 -0
  96. package/dist/esm/components/context-menu/total-row-functions.js +50 -0
  97. package/dist/esm/components/evaluate-formula-dialog/index.d.ts +34 -0
  98. package/dist/esm/components/evaluate-formula-dialog/index.d.ts.map +1 -0
  99. package/dist/esm/components/evaluate-formula-dialog/index.js +86 -0
  100. package/dist/esm/components/go-to-dialog/index.d.ts +35 -0
  101. package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -0
  102. package/dist/esm/components/go-to-dialog/index.js +84 -0
  103. package/dist/esm/components/paste-special-dialog/index.d.ts +29 -0
  104. package/dist/esm/components/paste-special-dialog/index.d.ts.map +1 -0
  105. package/dist/esm/components/paste-special-dialog/index.js +90 -0
  106. package/dist/esm/components/remove-duplicates-dialog/index.d.ts +37 -0
  107. package/dist/esm/components/remove-duplicates-dialog/index.d.ts.map +1 -0
  108. package/dist/esm/components/remove-duplicates-dialog/index.js +82 -0
  109. package/dist/esm/components/toolbar/index.d.ts +1 -0
  110. package/dist/esm/components/toolbar/index.d.ts.map +1 -1
  111. package/dist/esm/components/toolbar/index.js +1 -0
  112. package/dist/esm/components/toolbar/text-rotation-selector.d.ts +29 -0
  113. package/dist/esm/components/toolbar/text-rotation-selector.d.ts.map +1 -0
  114. package/dist/esm/components/toolbar/text-rotation-selector.js +71 -0
  115. package/dist/esm/components/toolbar/text-wrap-selector.d.ts +7 -1
  116. package/dist/esm/components/toolbar/text-wrap-selector.d.ts.map +1 -1
  117. package/dist/esm/components/toolbar/text-wrap-selector.js +10 -3
  118. package/dist/esm/hooks/use-copy-paste-cut.d.ts +20 -1
  119. package/dist/esm/hooks/use-copy-paste-cut.d.ts.map +1 -1
  120. package/dist/esm/hooks/use-copy-paste-cut.js +10 -4
  121. package/dist/esm/hooks/use-keyboard-handler.d.ts +17 -2
  122. package/dist/esm/hooks/use-keyboard-handler.d.ts.map +1 -1
  123. package/dist/esm/hooks/use-keyboard-handler.js +173 -21
  124. package/dist/esm/index.d.ts +6 -1
  125. package/dist/esm/index.d.ts.map +1 -1
  126. package/dist/esm/index.js +5 -0
  127. package/dist/esm/release-date.js +1 -1
  128. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  129. package/dist/esm/types.d.ts +173 -1
  130. package/dist/esm/types.d.ts.map +1 -1
  131. package/dist/spreadsheet.min.css +1 -1
  132. package/package.json +6 -6
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import type { NamedRange, SheetRange } from "../../types";
3
+ export type GoToDialogProps = {
4
+ /** Controls visibility. */
5
+ open: boolean;
6
+ onOpenChange(open: boolean): void;
7
+ /** Defined names listed in the "Go to" box; picking one fills the reference. */
8
+ namedRanges?: NamedRange[] | null;
9
+ /** Shown as the placeholder of the reference box (the current location). */
10
+ currentAddress?: string;
11
+ /**
12
+ * Resolves the typed text to a range, or `undefined` when it is not a
13
+ * valid reference / name (the dialog then shows Excel's error).
14
+ */
15
+ resolve(reference: string): SheetRange | undefined;
16
+ /** Receives the resolved range; the host selects and scrolls to it. */
17
+ onNavigate(range: SheetRange): void;
18
+ };
19
+ /**
20
+ * Excel's Go To dialog (`Ctrl+G` / `F5`): type a cell or range reference,
21
+ * optionally sheet-qualified, or pick a defined name, and the selection
22
+ * jumps there. Resolution is delegated to `resolve` so the dialog stays
23
+ * UI-only.
24
+ */
25
+ export declare const GoToDialog: React.MemoExoticComponent<({ open, onOpenChange, namedRanges, currentAddress, resolve, onNavigate, }: GoToDialogProps) => React.JSX.Element>;
26
+ /**
27
+ * Host-mounted, atom-driven wrapper around {@link GoToDialog}. Mount it once
28
+ * in the host alongside the other editor dialogs; the grid opens it on
29
+ * `Ctrl+G` / `F5` (unless the host handles `onRequestGoTo` itself).
30
+ */
31
+ export declare const GoToDialogHost: {
32
+ (): React.JSX.Element | null;
33
+ displayName: string;
34
+ };
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/go-to-dialog/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAQ3D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG;IAC5B,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,gFAAgF;IAChF,WAAW,CAAC,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAClC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACnD,uEAAuE;IACvE,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CACrC,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,wGAQlB,eAAe,uBA4GnB,CAAC;AAUF;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;CA8B1B,CAAC"}
@@ -0,0 +1,84 @@
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 { useGoToDialogState, useRegisterGoToDialogHost, } from "../../atoms/go-to-dialog.atom";
6
+ const INVALID_REFERENCE = "Reference is not valid.";
7
+ /**
8
+ * Excel's Go To dialog (`Ctrl+G` / `F5`): type a cell or range reference,
9
+ * optionally sheet-qualified, or pick a defined name, and the selection
10
+ * jumps there. Resolution is delegated to `resolve` so the dialog stays
11
+ * UI-only.
12
+ */
13
+ export const GoToDialog = memo(({ open, onOpenChange, namedRanges, currentAddress, resolve, onNavigate, }) => {
14
+ const [reference, setReference] = useState("");
15
+ const [error, setError] = useState(undefined);
16
+ const navigate = useCallback((text) => {
17
+ const range = resolve(text);
18
+ if (!range) {
19
+ setError(INVALID_REFERENCE);
20
+ return;
21
+ }
22
+ onNavigate(range);
23
+ onOpenChange(false);
24
+ }, [resolve, onNavigate, onOpenChange]);
25
+ const names = (namedRanges ?? []).filter((range) => range.range && !range.hidden);
26
+ return (React.createElement(Dialog, { open: open, onOpenChange: onOpenChange, modal: true },
27
+ React.createElement(DialogContent, { className: "w-[360px] max-w-[92vw] pointer-events-auto" },
28
+ React.createElement(DialogTitle, null, "Go To"),
29
+ React.createElement(DialogCloseButton, null),
30
+ React.createElement(DialogBody, { className: "flex flex-col gap-3" },
31
+ React.createElement("form", { className: "flex flex-col gap-3", onSubmit: (e) => {
32
+ e.preventDefault();
33
+ navigate(reference);
34
+ } },
35
+ React.createElement("div", { className: "flex flex-col gap-1" },
36
+ React.createElement("span", { className: "text-xs font-medium" }, "Go to:"),
37
+ 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: () => {
38
+ setReference(range.name);
39
+ setError(undefined);
40
+ }, onDoubleClick: () => navigate(range.name) }, range.name)))))),
41
+ React.createElement("label", { className: "flex flex-col gap-1 text-xs font-medium" },
42
+ "Reference:",
43
+ React.createElement(Input, { autoFocus: true, value: reference, placeholder: currentAddress, "aria-invalid": error ? true : undefined, onChange: (e) => {
44
+ setReference(e.target.value);
45
+ setError(undefined);
46
+ } })),
47
+ error ? (React.createElement("div", { role: "alert", className: "text-xs text-rnc-destructive" }, error)) : null,
48
+ React.createElement(Separator, null),
49
+ React.createElement("div", { className: "flex justify-end gap-2" },
50
+ React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: () => onOpenChange(false) }, "Cancel"),
51
+ React.createElement(Button, { type: "submit", size: "sm", disabled: !reference.trim() }, "OK")))))));
52
+ });
53
+ GoToDialog.displayName = "GoToDialog";
54
+ const currentAddressOf = (request) => {
55
+ const address = cellToAddress(request.activeCell) ?? "";
56
+ const sheetName = request.getSheetName?.(request.sheetId);
57
+ return sheetName ? `${sheetName}!${address}` : address;
58
+ };
59
+ /**
60
+ * Host-mounted, atom-driven wrapper around {@link GoToDialog}. Mount it once
61
+ * in the host alongside the other editor dialogs; the grid opens it on
62
+ * `Ctrl+G` / `F5` (unless the host handles `onRequestGoTo` itself).
63
+ */
64
+ export const GoToDialogHost = () => {
65
+ useRegisterGoToDialogHost();
66
+ const [request, setRequest] = useGoToDialogState();
67
+ const close = useCallback(() => setRequest(null), [setRequest]);
68
+ const resolve = useCallback((reference) => request
69
+ ? resolveGoToReference(reference, {
70
+ sheetId: request.sheetId,
71
+ namedRanges: request.namedRanges,
72
+ getSheetId: request.getSheetId,
73
+ rowCount: request.rowCount,
74
+ columnCount: request.columnCount,
75
+ })
76
+ : undefined, [request]);
77
+ if (!request)
78
+ return null;
79
+ return (React.createElement(GoToDialog, { open: true, onOpenChange: (open) => {
80
+ if (!open)
81
+ close();
82
+ }, namedRanges: request.namedRanges, currentAddress: currentAddressOf(request), resolve: resolve, onNavigate: request.onNavigate }));
83
+ };
84
+ GoToDialogHost.displayName = "GoToDialogHost";
@@ -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";
@@ -10,6 +10,7 @@ export * from "./font-family-selector";
10
10
  export * from "./text-horizontal-align-selector";
11
11
  export * from "./text-vertical-align-selector";
12
12
  export * from "./text-wrap-selector";
13
+ export * from "./text-rotation-selector";
13
14
  export * from "./text-format-selector";
14
15
  export * from "./theme-selector";
15
16
  export * from "./table-actions";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/index.tsx"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/index.tsx"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC"}
@@ -10,6 +10,7 @@ export * from "./font-family-selector";
10
10
  export * from "./text-horizontal-align-selector";
11
11
  export * from "./text-vertical-align-selector";
12
12
  export * from "./text-wrap-selector";
13
+ export * from "./text-rotation-selector";
13
14
  export * from "./text-format-selector";
14
15
  export * from "./theme-selector";
15
16
  export * from "./table-actions";
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import type { CellFormat } from "../../types";
3
+ export type TextRotationValue = NonNullable<CellFormat["textRotation"]>;
4
+ type TextRotationPreset = {
5
+ label: string;
6
+ value: TextRotationValue;
7
+ };
8
+ /**
9
+ * Excel's Orientation menu. Degrees are counter-clockwise positive (Excel's
10
+ * convention, what the renderer and the xlsx round-trip use), so "clockwise"
11
+ * presets are negative. `0` is horizontal — used instead of clearing the
12
+ * property so both engines apply it as a plain format write.
13
+ */
14
+ export declare const TEXT_ROTATION_PRESETS: TextRotationPreset[];
15
+ export declare const MIN_TEXT_ROTATION = -90;
16
+ export declare const MAX_TEXT_ROTATION = 90;
17
+ /**
18
+ * Coerce free-form degree input into the supported -90…90 range. Returns
19
+ * `undefined` for input that is not a finite number.
20
+ */
21
+ export declare const clampTextRotation: (input: string | number) => number | undefined;
22
+ type TextRotationSelectorProps = {
23
+ disabled?: boolean;
24
+ value?: CellFormat["textRotation"];
25
+ onChange(value: TextRotationValue): void;
26
+ };
27
+ export declare const TextRotationSelector: ({ value, onChange, disabled, }: TextRotationSelectorProps) => React.JSX.Element;
28
+ export {};
29
+ //# sourceMappingURL=text-rotation-selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-rotation-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/text-rotation-selector.tsx"],"names":[],"mappings":"AAaA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;AAExE,KAAK,kBAAkB,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,iBAAiB,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,kBAAkB,EAOrD,CAAC;AAEF,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,GAAG,MAAM,uBAMvD,CAAC;AAEF,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,gCAIlC,yBAAyB,sBAoF3B,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { MdOutlineArrowDropDown, RotateCounterClockwiseIcon, } from "@rowsncolumns/icons";
2
+ import { Button, IconButton, Input, Popover, PopoverContent, PopoverTrigger, Stack, } from "@rowsncolumns/ui";
3
+ import React, { useEffect, useState } from "react";
4
+ import { useFocusSheet } from "../../atoms";
5
+ /**
6
+ * Excel's Orientation menu. Degrees are counter-clockwise positive (Excel's
7
+ * convention, what the renderer and the xlsx round-trip use), so "clockwise"
8
+ * presets are negative. `0` is horizontal — used instead of clearing the
9
+ * property so both engines apply it as a plain format write.
10
+ */
11
+ export const TEXT_ROTATION_PRESETS = [
12
+ { label: "Horizontal", value: 0 },
13
+ { label: "Angle counterclockwise", value: 45 },
14
+ { label: "Angle clockwise", value: -45 },
15
+ { label: "Vertical text", value: "vertical" },
16
+ { label: "Rotate text up", value: 90 },
17
+ { label: "Rotate text down", value: -90 },
18
+ ];
19
+ export const MIN_TEXT_ROTATION = -90;
20
+ export const MAX_TEXT_ROTATION = 90;
21
+ /**
22
+ * Coerce free-form degree input into the supported -90…90 range. Returns
23
+ * `undefined` for input that is not a finite number.
24
+ */
25
+ export const clampTextRotation = (input) => {
26
+ const value = typeof input === "number" ? input : Number(input);
27
+ if (!Number.isFinite(value)) {
28
+ return undefined;
29
+ }
30
+ return Math.max(MIN_TEXT_ROTATION, Math.min(MAX_TEXT_ROTATION, value));
31
+ };
32
+ export const TextRotationSelector = ({ value, onChange, disabled, }) => {
33
+ const focusSheet = useFocusSheet();
34
+ const [isOpen, onOpenChange] = useState(false);
35
+ const current = value ?? 0;
36
+ const [degrees, setDegrees] = useState(typeof current === "number" ? String(current) : "");
37
+ // Reflect the active cell's rotation in the custom-degrees field whenever
38
+ // the popover opens on a different value.
39
+ useEffect(() => {
40
+ if (isOpen) {
41
+ setDegrees(typeof current === "number" ? String(current) : "");
42
+ }
43
+ }, [isOpen, current]);
44
+ const close = () => onOpenChange(false);
45
+ const apply = (next) => {
46
+ onChange(next);
47
+ focusSheet?.();
48
+ close();
49
+ };
50
+ const applyDegrees = () => {
51
+ const next = clampTextRotation(degrees);
52
+ if (next !== undefined) {
53
+ apply(next);
54
+ }
55
+ };
56
+ return (React.createElement(Popover, { open: isOpen, onOpenChange: onOpenChange },
57
+ React.createElement(PopoverTrigger, { asChild: true },
58
+ React.createElement(IconButton, { size: "lg", tooltip: "Text orientation", "aria-label": "Text orientation", disabled: disabled },
59
+ React.createElement(RotateCounterClockwiseIcon, null),
60
+ React.createElement(MdOutlineArrowDropDown, null))),
61
+ React.createElement(PopoverContent, { align: "start" },
62
+ React.createElement(Stack, { direction: "vertical", className: "gap-1 min-w-[200px]" },
63
+ TEXT_ROTATION_PRESETS.map((preset) => (React.createElement(Button, { key: preset.label, size: "xs", variant: current === preset.value ? "primary" : "ghost", className: "justify-start", "aria-pressed": current === preset.value, onClick: () => apply(preset.value) }, preset.label))),
64
+ React.createElement("form", { className: "flex items-center gap-1 pt-1 m-0", onSubmit: (e) => {
65
+ e.preventDefault();
66
+ applyDegrees();
67
+ } },
68
+ React.createElement("label", { className: "font-system text-xs shrink-0", htmlFor: "rnc-text-rotation-degrees" }, "Degrees"),
69
+ React.createElement(Input, { id: "rnc-text-rotation-degrees", type: "number", min: MIN_TEXT_ROTATION, max: MAX_TEXT_ROTATION, step: 1, value: degrees, onChange: (e) => setDegrees(e.target.value), className: "w-[72px]" }),
70
+ React.createElement(Button, { size: "xs", type: "submit" }, "Apply"))))));
71
+ };
@@ -3,7 +3,13 @@ import type { WrapStrategy } from "../../types";
3
3
  type TextWrapSelectorProps = {
4
4
  value?: WrapStrategy | null;
5
5
  onChange(value: WrapStrategy): void;
6
+ /**
7
+ * Excel's "Shrink to fit": scale the font down so the text fits the cell.
8
+ * The toggle only renders when `onChangeShrinkToFit` is provided.
9
+ */
10
+ shrinkToFit?: boolean | null;
11
+ onChangeShrinkToFit?(value: boolean): void;
6
12
  };
7
- export declare const TextWrapSelector: ({ value, onChange, }: TextWrapSelectorProps) => React.JSX.Element;
13
+ export declare const TextWrapSelector: ({ value, onChange, shrinkToFit, onChangeShrinkToFit, }: TextWrapSelectorProps) => React.JSX.Element;
8
14
  export {};
9
15
  //# sourceMappingURL=text-wrap-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-wrap-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/text-wrap-selector.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,KAAK,qBAAqB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,sBAG9B,qBAAqB,sBA8DvB,CAAC"}
1
+ {"version":3,"file":"text-wrap-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/text-wrap-selector.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,KAAK,qBAAqB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,mBAAmB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,wDAK9B,qBAAqB,sBAoFvB,CAAC"}
@@ -3,13 +3,13 @@ import { MdOutlineArrowDropDown } from "@rowsncolumns/icons";
3
3
  import { IconButton, Popover, PopoverContent, PopoverTrigger, Stack, } from "@rowsncolumns/ui";
4
4
  import React, { useState } from "react";
5
5
  import { useFocusSheet } from "../../atoms";
6
- export const TextWrapSelector = ({ value = "overflow", onChange, }) => {
6
+ export const TextWrapSelector = ({ value = "overflow", onChange, shrinkToFit, onChangeShrinkToFit, }) => {
7
7
  const focusSheet = useFocusSheet();
8
8
  const [isOpen, onOpenChange] = useState(false);
9
9
  const close = () => onOpenChange(false);
10
10
  return (React.createElement(Popover, { open: isOpen, onOpenChange: onOpenChange },
11
11
  React.createElement(PopoverTrigger, { asChild: true },
12
- React.createElement(IconButton, { size: "lg", tooltip: "Text wrapping" },
12
+ React.createElement(IconButton, { size: "lg", tooltip: "Text wrapping", "aria-label": "Text wrapping" },
13
13
  value === "overflow" ? (React.createElement(TextOverflowIcon, null)) : value === "wrap" ? (React.createElement(TextOverflowWrapIcon, null)) : (React.createElement(TextoverflowClipIcon, null)),
14
14
  React.createElement(MdOutlineArrowDropDown, null))),
15
15
  React.createElement(PopoverContent, { align: "start" },
@@ -31,5 +31,12 @@ export const TextWrapSelector = ({ value = "overflow", onChange, }) => {
31
31
  focusSheet?.();
32
32
  close();
33
33
  } },
34
- React.createElement(TextoverflowClipIcon, null))))));
34
+ React.createElement(TextoverflowClipIcon, null)),
35
+ onChangeShrinkToFit ? (React.createElement(IconButton, { size: "lg", isActive: !!shrinkToFit, "aria-pressed": !!shrinkToFit, tooltip: "Shrink text to fit the cell", "aria-label": "Shrink text to fit the cell", className: "w-full justify-start gap-1", onClick: () => {
36
+ onChangeShrinkToFit(!shrinkToFit);
37
+ focusSheet?.();
38
+ close();
39
+ } },
40
+ React.createElement(TextoverflowClipIcon, null),
41
+ React.createElement("span", { className: "text-xs" }, "Shrink to fit"))) : null))));
35
42
  };
@@ -31,11 +31,30 @@ export type UseCopyPasteCutProps = {
31
31
  onCut: CanvasGridProps["onCut"];
32
32
  };
33
33
  export type PasteSpecialType = "Transposed" | "Formatting" | "Value" | "Formula" | "Link";
34
+ /** Excel Paste Special → Operation: combine each pasted cell with the cell it lands on. */
35
+ export type PasteSpecialOperation = "add" | "subtract" | "multiply" | "divide";
36
+ /**
37
+ * Excel's Paste Special dialog as one request. `paste` picks what is
38
+ * pasted (all / values / formulas / formats), `operation` combines the pasted
39
+ * value with the destination (`=dest+source`, formulas stay formulas),
40
+ * `skipBlanks` leaves the destination untouched where the clipboard cell is
41
+ * empty and `transpose` swaps rows and columns. Every flag composes with the
42
+ * others; the legacy single-mode {@link PasteSpecialType} strings map onto
43
+ * the same fields.
44
+ */
45
+ export type PasteSpecialOptions = {
46
+ paste?: "all" | "values" | "formulas" | "formats";
47
+ operation?: PasteSpecialOperation;
48
+ skipBlanks?: boolean;
49
+ transpose?: boolean;
50
+ };
51
+ /** Argument accepted by `paste()` — a legacy single mode or the full dialog request. */
52
+ export type PasteSpecialRequest = PasteSpecialType | PasteSpecialOptions;
34
53
  export declare const useCopyPasteCut: ({ selections, activeCell, sheetId, onPaste, onCopy, onCut, focusSheet, }: UseCopyPasteCutProps) => {
35
54
  selections: SelectionArea[];
36
55
  copy: () => void;
37
56
  cut: () => void;
38
- paste: (pasteSpecialType?: PasteSpecialType) => Promise<void>;
57
+ paste: (pasteSpecial?: PasteSpecialRequest) => Promise<void>;
39
58
  copiedSelections: any[];
40
59
  clearCopiedSelections: () => void;
41
60
  onInternalCopy: (e: ClipboardEvent, isCutOperation?: boolean) => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"use-copy-paste-cut.d.ts","sourceRoot":"","sources":["../../../hooks/use-copy-paste-cut.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAWvE,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,mCAAmC,CAAC;AAK3C,MAAM,MAAM,oBAAoB,GAAG;IACjC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,aAAa,CAAC;IAC1B;;OAEG;IACH,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B;;OAEG;IACH,UAAU,IAAI,IAAI,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACpC;;OAEG;IACH,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,YAAY,GACZ,OAAO,GACP,SAAS,GACT,MAAM,CAAC;AAEX,eAAO,MAAM,eAAe,GAAI,0EAQ7B,oBAAoB;;;;+BAoRO,gBAAgB;;;wBA1KtC,cAAc;yBAuDd,cAAc;uBAtBd,cAAc;CAmMrB,CAAC"}
1
+ {"version":3,"file":"use-copy-paste-cut.d.ts","sourceRoot":"","sources":["../../../hooks/use-copy-paste-cut.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAYvE,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,mCAAmC,CAAC;AAK3C,MAAM,MAAM,oBAAoB,GAAG;IACjC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,UAAU,EAAE,aAAa,CAAC;IAC1B;;OAEG;IACH,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B;;OAEG;IACH,UAAU,IAAI,IAAI,CAAC;IACnB;;OAEG;IACH,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACpC;;OAEG;IACH,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,YAAY,GACZ,OAAO,GACP,SAAS,GACT,MAAM,CAAC;AAEX,2FAA2F;AAC3F,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE/E;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAClD,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wFAAwF;AACxF,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAEzE,eAAO,MAAM,eAAe,GAAI,0EAQ7B,oBAAoB;;;;2BAoRG,mBAAmB;;;wBA1KrC,cAAc;yBAuDd,cAAc;uBAtBd,cAAc;CA0MrB,CAAC"}