@rowsncolumns/spreadsheet 10.2.73 → 10.2.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api/evaluate-formula.d.ts +17 -0
- package/dist/cjs/api/evaluate-formula.d.ts.map +1 -0
- package/dist/cjs/api/evaluate-formula.js +141 -0
- package/dist/cjs/api/excel-validation-kinds.d.ts +71 -0
- package/dist/cjs/api/excel-validation-kinds.d.ts.map +1 -0
- package/dist/cjs/api/excel-validation-kinds.js +304 -0
- package/dist/cjs/api/go-to-special.d.ts +32 -0
- package/dist/cjs/api/go-to-special.d.ts.map +1 -0
- package/dist/cjs/api/go-to-special.js +111 -0
- package/dist/cjs/api/go-to.d.ts +19 -0
- package/dist/cjs/api/go-to.d.ts.map +1 -0
- package/dist/cjs/api/go-to.js +49 -0
- package/dist/cjs/api/validation-tooltip.d.ts +17 -0
- package/dist/cjs/api/validation-tooltip.d.ts.map +1 -0
- package/dist/cjs/api/validation-tooltip.js +42 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts +28 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/advanced-filter-dialog.atom.js +32 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/evaluate-formula-dialog.atom.js +36 -0
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts +46 -0
- package/dist/cjs/atoms/go-to-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/go-to-dialog.atom.js +34 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts +29 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/goal-seek-dialog.atom.js +30 -0
- package/dist/cjs/atoms/index.d.ts +5 -0
- package/dist/cjs/atoms/index.d.ts.map +1 -1
- package/dist/cjs/atoms/index.js +5 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.d.ts +29 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/paste-special-dialog.atom.js +34 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/remove-duplicates-dialog.atom.js +37 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.d.ts +39 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/subtotal-dialog.atom.js +30 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts +34 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/text-to-columns-dialog.atom.js +36 -0
- package/dist/cjs/atoms/watch-window.atom.d.ts +37 -0
- package/dist/cjs/atoms/watch-window.atom.d.ts.map +1 -0
- package/dist/cjs/atoms/watch-window.atom.js +61 -0
- package/dist/cjs/components/advanced-filter-dialog/index.d.ts +28 -0
- package/dist/cjs/components/advanced-filter-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/advanced-filter-dialog/index.js +134 -0
- package/dist/cjs/components/calculation-options-dialog/index.d.ts +33 -0
- package/dist/cjs/components/calculation-options-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/calculation-options-dialog/index.js +69 -0
- package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/cjs/components/canvas-grid/index.js +208 -19
- package/dist/cjs/components/canvas-grid/typings.d.ts +72 -9
- package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.d.ts +30 -0
- package/dist/cjs/components/cell/alignment.d.ts.map +1 -1
- package/dist/cjs/components/cell/alignment.js +56 -1
- package/dist/cjs/components/cell/index.d.ts.map +1 -1
- package/dist/cjs/components/cell/index.js +15 -7
- package/dist/cjs/components/cell-diff/index.d.ts +3 -2
- package/dist/cjs/components/cell-diff/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.d.ts.map +1 -1
- package/dist/cjs/components/cell-editor/index.js +2 -1
- package/dist/cjs/components/context-menu/index.d.ts +21 -3
- package/dist/cjs/components/context-menu/index.d.ts.map +1 -1
- package/dist/cjs/components/context-menu/index.js +256 -6
- package/dist/cjs/components/context-menu/total-row-functions.d.ts +32 -0
- package/dist/cjs/components/context-menu/total-row-functions.d.ts.map +1 -0
- package/dist/cjs/components/context-menu/total-row-functions.js +62 -0
- package/dist/cjs/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/cjs/components/embeds/embed-component.js +16 -1
- package/dist/cjs/components/evaluate-formula-dialog/index.d.ts +34 -0
- package/dist/cjs/components/evaluate-formula-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/evaluate-formula-dialog/index.js +106 -0
- package/dist/cjs/components/go-to-dialog/index.d.ts +45 -0
- package/dist/cjs/components/go-to-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/go-to-dialog/index.js +136 -0
- package/dist/cjs/components/goal-seek-dialog/index.d.ts +32 -0
- package/dist/cjs/components/goal-seek-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/goal-seek-dialog/index.js +149 -0
- package/dist/cjs/components/paste-special-dialog/index.d.ts +29 -0
- package/dist/cjs/components/paste-special-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/paste-special-dialog/index.js +99 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.d.ts +37 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/remove-duplicates-dialog/index.js +91 -0
- package/dist/cjs/components/subtotal-dialog/index.d.ts +30 -0
- package/dist/cjs/components/subtotal-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/subtotal-dialog/index.js +126 -0
- package/dist/cjs/components/text-to-columns-dialog/index.d.ts +37 -0
- package/dist/cjs/components/text-to-columns-dialog/index.d.ts.map +1 -0
- package/dist/cjs/components/text-to-columns-dialog/index.js +166 -0
- package/dist/cjs/components/toolbar/background-color-selector.d.ts +13 -1
- package/dist/cjs/components/toolbar/background-color-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/background-color-selector.js +57 -11
- package/dist/cjs/components/toolbar/cell-style-selector.d.ts +15 -2
- package/dist/cjs/components/toolbar/cell-style-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/cell-style-selector.js +56 -3
- package/dist/cjs/components/toolbar/fill-selectors.d.ts +49 -0
- package/dist/cjs/components/toolbar/fill-selectors.d.ts.map +1 -0
- package/dist/cjs/components/toolbar/fill-selectors.js +196 -0
- package/dist/cjs/components/toolbar/index.d.ts +2 -0
- package/dist/cjs/components/toolbar/index.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/index.js +2 -0
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/text-horizontal-align-selector.js +23 -4
- package/dist/cjs/components/toolbar/text-rotation-selector.d.ts +29 -0
- package/dist/cjs/components/toolbar/text-rotation-selector.d.ts.map +1 -0
- package/dist/cjs/components/toolbar/text-rotation-selector.js +78 -0
- package/dist/cjs/components/toolbar/text-wrap-selector.d.ts +7 -1
- package/dist/cjs/components/toolbar/text-wrap-selector.d.ts.map +1 -1
- package/dist/cjs/components/toolbar/text-wrap-selector.js +10 -3
- package/dist/cjs/components/watch-window/index.d.ts +25 -0
- package/dist/cjs/components/watch-window/index.d.ts.map +1 -0
- package/dist/cjs/components/watch-window/index.js +63 -0
- package/dist/cjs/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/cjs/hooks/use-cell-renderer.js +17 -6
- package/dist/cjs/hooks/use-copy-paste-cut.d.ts +20 -1
- package/dist/cjs/hooks/use-copy-paste-cut.d.ts.map +1 -1
- package/dist/cjs/hooks/use-copy-paste-cut.js +25 -20
- package/dist/cjs/hooks/use-get-tooltip.d.ts.map +1 -1
- package/dist/cjs/hooks/use-get-tooltip.js +7 -2
- package/dist/cjs/hooks/use-keyboard-handler.d.ts +17 -2
- package/dist/cjs/hooks/use-keyboard-handler.d.ts.map +1 -1
- package/dist/cjs/hooks/use-keyboard-handler.js +206 -31
- package/dist/cjs/index.d.ts +14 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +15 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/types.d.ts +289 -4
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +19 -1
- package/dist/esm/api/evaluate-formula.d.ts +17 -0
- package/dist/esm/api/evaluate-formula.d.ts.map +1 -0
- package/dist/esm/api/evaluate-formula.js +109 -0
- package/dist/esm/api/excel-validation-kinds.d.ts +71 -0
- package/dist/esm/api/excel-validation-kinds.d.ts.map +1 -0
- package/dist/esm/api/excel-validation-kinds.js +287 -0
- package/dist/esm/api/go-to-special.d.ts +32 -0
- package/dist/esm/api/go-to-special.d.ts.map +1 -0
- package/dist/esm/api/go-to-special.js +96 -0
- package/dist/esm/api/go-to.d.ts +19 -0
- package/dist/esm/api/go-to.d.ts.map +1 -0
- package/dist/esm/api/go-to.js +41 -0
- package/dist/esm/api/validation-tooltip.d.ts +17 -0
- package/dist/esm/api/validation-tooltip.d.ts.map +1 -0
- package/dist/esm/api/validation-tooltip.js +34 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts +28 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/advanced-filter-dialog.atom.js +21 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts +36 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/evaluate-formula-dialog.atom.js +25 -0
- package/dist/esm/atoms/go-to-dialog.atom.d.ts +46 -0
- package/dist/esm/atoms/go-to-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/go-to-dialog.atom.js +25 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.d.ts +29 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/goal-seek-dialog.atom.js +21 -0
- package/dist/esm/atoms/index.d.ts +5 -0
- package/dist/esm/atoms/index.d.ts.map +1 -1
- package/dist/esm/atoms/index.js +5 -0
- package/dist/esm/atoms/paste-special-dialog.atom.d.ts +29 -0
- package/dist/esm/atoms/paste-special-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/paste-special-dialog.atom.js +25 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts +45 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/remove-duplicates-dialog.atom.js +26 -0
- package/dist/esm/atoms/subtotal-dialog.atom.d.ts +39 -0
- package/dist/esm/atoms/subtotal-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/subtotal-dialog.atom.js +21 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts +34 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.d.ts.map +1 -0
- package/dist/esm/atoms/text-to-columns-dialog.atom.js +25 -0
- package/dist/esm/atoms/watch-window.atom.d.ts +37 -0
- package/dist/esm/atoms/watch-window.atom.d.ts.map +1 -0
- package/dist/esm/atoms/watch-window.atom.js +37 -0
- package/dist/esm/components/advanced-filter-dialog/index.d.ts +28 -0
- package/dist/esm/components/advanced-filter-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/advanced-filter-dialog/index.js +125 -0
- package/dist/esm/components/calculation-options-dialog/index.d.ts +33 -0
- package/dist/esm/components/calculation-options-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/calculation-options-dialog/index.js +61 -0
- package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
- package/dist/esm/components/canvas-grid/index.js +202 -19
- package/dist/esm/components/canvas-grid/typings.d.ts +72 -9
- package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.d.ts +30 -0
- package/dist/esm/components/cell/alignment.d.ts.map +1 -1
- package/dist/esm/components/cell/alignment.js +51 -0
- package/dist/esm/components/cell/index.d.ts.map +1 -1
- package/dist/esm/components/cell/index.js +16 -8
- package/dist/esm/components/cell-diff/index.d.ts +3 -2
- package/dist/esm/components/cell-diff/index.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.d.ts.map +1 -1
- package/dist/esm/components/cell-editor/index.js +2 -1
- package/dist/esm/components/context-menu/index.d.ts +21 -3
- package/dist/esm/components/context-menu/index.d.ts.map +1 -1
- package/dist/esm/components/context-menu/index.js +251 -9
- package/dist/esm/components/context-menu/total-row-functions.d.ts +32 -0
- package/dist/esm/components/context-menu/total-row-functions.d.ts.map +1 -0
- package/dist/esm/components/context-menu/total-row-functions.js +50 -0
- package/dist/esm/components/embeds/embed-component.d.ts.map +1 -1
- package/dist/esm/components/embeds/embed-component.js +17 -1
- package/dist/esm/components/evaluate-formula-dialog/index.d.ts +34 -0
- package/dist/esm/components/evaluate-formula-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/evaluate-formula-dialog/index.js +86 -0
- package/dist/esm/components/go-to-dialog/index.d.ts +45 -0
- package/dist/esm/components/go-to-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/go-to-dialog/index.js +124 -0
- package/dist/esm/components/goal-seek-dialog/index.d.ts +32 -0
- package/dist/esm/components/goal-seek-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/goal-seek-dialog/index.js +125 -0
- package/dist/esm/components/paste-special-dialog/index.d.ts +29 -0
- package/dist/esm/components/paste-special-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/paste-special-dialog/index.js +90 -0
- package/dist/esm/components/remove-duplicates-dialog/index.d.ts +37 -0
- package/dist/esm/components/remove-duplicates-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/remove-duplicates-dialog/index.js +82 -0
- package/dist/esm/components/subtotal-dialog/index.d.ts +30 -0
- package/dist/esm/components/subtotal-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/subtotal-dialog/index.js +115 -0
- package/dist/esm/components/text-to-columns-dialog/index.d.ts +37 -0
- package/dist/esm/components/text-to-columns-dialog/index.d.ts.map +1 -0
- package/dist/esm/components/text-to-columns-dialog/index.js +147 -0
- package/dist/esm/components/toolbar/background-color-selector.d.ts +13 -1
- package/dist/esm/components/toolbar/background-color-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/background-color-selector.js +61 -11
- package/dist/esm/components/toolbar/cell-style-selector.d.ts +15 -2
- package/dist/esm/components/toolbar/cell-style-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/cell-style-selector.js +46 -2
- package/dist/esm/components/toolbar/fill-selectors.d.ts +49 -0
- package/dist/esm/components/toolbar/fill-selectors.d.ts.map +1 -0
- package/dist/esm/components/toolbar/fill-selectors.js +162 -0
- package/dist/esm/components/toolbar/index.d.ts +2 -0
- package/dist/esm/components/toolbar/index.d.ts.map +1 -1
- package/dist/esm/components/toolbar/index.js +2 -0
- package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts +8 -0
- package/dist/esm/components/toolbar/text-horizontal-align-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/text-horizontal-align-selector.js +21 -5
- package/dist/esm/components/toolbar/text-rotation-selector.d.ts +29 -0
- package/dist/esm/components/toolbar/text-rotation-selector.d.ts.map +1 -0
- package/dist/esm/components/toolbar/text-rotation-selector.js +71 -0
- package/dist/esm/components/toolbar/text-wrap-selector.d.ts +7 -1
- package/dist/esm/components/toolbar/text-wrap-selector.d.ts.map +1 -1
- package/dist/esm/components/toolbar/text-wrap-selector.js +10 -3
- package/dist/esm/components/watch-window/index.d.ts +25 -0
- package/dist/esm/components/watch-window/index.d.ts.map +1 -0
- package/dist/esm/components/watch-window/index.js +54 -0
- package/dist/esm/hooks/use-cell-renderer.d.ts.map +1 -1
- package/dist/esm/hooks/use-cell-renderer.js +17 -6
- package/dist/esm/hooks/use-copy-paste-cut.d.ts +20 -1
- package/dist/esm/hooks/use-copy-paste-cut.d.ts.map +1 -1
- package/dist/esm/hooks/use-copy-paste-cut.js +8 -3
- package/dist/esm/hooks/use-get-tooltip.d.ts.map +1 -1
- package/dist/esm/hooks/use-get-tooltip.js +7 -2
- package/dist/esm/hooks/use-keyboard-handler.d.ts +17 -2
- package/dist/esm/hooks/use-keyboard-handler.d.ts.map +1 -1
- package/dist/esm/hooks/use-keyboard-handler.js +193 -31
- package/dist/esm/index.d.ts +14 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +13 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/types.d.ts +289 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +18 -0
- package/dist/spreadsheet.min.css +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Button, ColorPicker, HexColorPicker, Stack } from "@rowsncolumns/ui";
|
|
2
|
+
import React, { useMemo, useState } from "react";
|
|
3
|
+
import { getPatternCanvas } from "../../api/fill-patterns";
|
|
4
|
+
import { getStringifiedColor, STANDARD_COLORS } from "../../themes";
|
|
5
|
+
/** Excel's Format Cells → Fill → Pattern Style menu, in Excel's order. */
|
|
6
|
+
export const FILL_PATTERN_OPTIONS = [
|
|
7
|
+
{ pattern: "gray0625", label: "6.25% Gray" },
|
|
8
|
+
{ pattern: "gray125", label: "12.5% Gray" },
|
|
9
|
+
{ pattern: "lightGray", label: "25% Gray" },
|
|
10
|
+
{ pattern: "mediumGray", label: "50% Gray" },
|
|
11
|
+
{ pattern: "darkGray", label: "75% Gray" },
|
|
12
|
+
{ pattern: "darkHorizontal", label: "Horizontal Stripe" },
|
|
13
|
+
{ pattern: "darkVertical", label: "Vertical Stripe" },
|
|
14
|
+
{ pattern: "darkDown", label: "Reverse Diagonal Stripe" },
|
|
15
|
+
{ pattern: "darkUp", label: "Diagonal Stripe" },
|
|
16
|
+
{ pattern: "darkGrid", label: "Diagonal Crosshatch" },
|
|
17
|
+
{ pattern: "darkTrellis", label: "Thick Diagonal Crosshatch" },
|
|
18
|
+
{ pattern: "lightHorizontal", label: "Thin Horizontal Stripe" },
|
|
19
|
+
{ pattern: "lightVertical", label: "Thin Vertical Stripe" },
|
|
20
|
+
{ pattern: "lightDown", label: "Thin Reverse Diagonal Stripe" },
|
|
21
|
+
{ pattern: "lightUp", label: "Thin Diagonal Stripe" },
|
|
22
|
+
{ pattern: "lightGrid", label: "Thin Horizontal Crosshatch" },
|
|
23
|
+
{ pattern: "lightTrellis", label: "Thin Diagonal Crosshatch" },
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Excel's Fill Effects → Shading styles. `degree` follows OOXML
|
|
27
|
+
* `<gradientFill degree>`: 0 runs left → right, 90 top → bottom.
|
|
28
|
+
*/
|
|
29
|
+
export const GRADIENT_PRESETS = [
|
|
30
|
+
{ label: "Horizontal", gradient: { type: "linear", degree: 90 } },
|
|
31
|
+
{ label: "Vertical", gradient: { type: "linear", degree: 0 } },
|
|
32
|
+
{ label: "Diagonal up", gradient: { type: "linear", degree: 45 } },
|
|
33
|
+
{ label: "Diagonal down", gradient: { type: "linear", degree: 135 } },
|
|
34
|
+
{
|
|
35
|
+
label: "From center",
|
|
36
|
+
gradient: { type: "path", left: 0.5, right: 0.5, top: 0.5, bottom: 0.5 },
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
/** Two-stop gradient from a preset and two colours. */
|
|
40
|
+
export const buildGradientFill = (preset, color1, color2) => ({
|
|
41
|
+
...preset,
|
|
42
|
+
stops: [
|
|
43
|
+
{ position: 0, color: color1 },
|
|
44
|
+
{ position: 1, color: color2 },
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* CSS preview of a gradient fill. OOXML measures the linear angle from the
|
|
49
|
+
* left→right axis; CSS from the bottom→top axis, so CSS = degree + 90.
|
|
50
|
+
*/
|
|
51
|
+
export const gradientToCss = (gradient) => {
|
|
52
|
+
const stops = gradient.stops
|
|
53
|
+
.slice()
|
|
54
|
+
.sort((a, b) => a.position - b.position)
|
|
55
|
+
.map((stop) => `${stop.color} ${Math.round(stop.position * 100)}%`)
|
|
56
|
+
.join(", ");
|
|
57
|
+
if (gradient.type === "path") {
|
|
58
|
+
return `radial-gradient(circle, ${stops})`;
|
|
59
|
+
}
|
|
60
|
+
return `linear-gradient(${((gradient.degree ?? 0) + 90) % 360}deg, ${stops})`;
|
|
61
|
+
};
|
|
62
|
+
const DEFAULT_PATTERN_COLOR = "#000000";
|
|
63
|
+
const PatternSwatch = ({ pattern, foregroundColor, backgroundColor, label, }) => {
|
|
64
|
+
const url = useMemo(() => {
|
|
65
|
+
const canvas = getPatternCanvas(pattern, foregroundColor, backgroundColor);
|
|
66
|
+
try {
|
|
67
|
+
return canvas?.toDataURL() ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}, [pattern, foregroundColor, backgroundColor]);
|
|
73
|
+
return (React.createElement("span", { "aria-hidden": "true", className: "block w-full h-full border border-solid border-rnc-border", style: url
|
|
74
|
+
? { backgroundImage: `url(${url})`, backgroundRepeat: "repeat" }
|
|
75
|
+
: { backgroundColor }, title: label }));
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Pattern fill authoring (Excel: Format Cells → Fill → Pattern Color /
|
|
79
|
+
* Pattern Style). The pattern paints in `foregroundColor` over the solid
|
|
80
|
+
* `backgroundColor`; a gradient on the cell is cleared, as Excel treats the
|
|
81
|
+
* two as exclusive.
|
|
82
|
+
*/
|
|
83
|
+
export const PatternFillPicker = ({ fill, theme, onChangeFill, }) => {
|
|
84
|
+
const background = fill?.backgroundColor
|
|
85
|
+
? getStringifiedColor(fill.backgroundColor, theme)
|
|
86
|
+
: undefined;
|
|
87
|
+
const patternColor = fill?.foregroundColor
|
|
88
|
+
? getStringifiedColor(fill.foregroundColor, theme)
|
|
89
|
+
: DEFAULT_PATTERN_COLOR;
|
|
90
|
+
const activePattern = fill?.fillPattern &&
|
|
91
|
+
fill.fillPattern !== "none" &&
|
|
92
|
+
fill.fillPattern !== "solid"
|
|
93
|
+
? fill.fillPattern
|
|
94
|
+
: undefined;
|
|
95
|
+
const apply = (pattern, foregroundColor) => onChangeFill({
|
|
96
|
+
backgroundColor: fill?.backgroundColor ?? undefined,
|
|
97
|
+
fillPattern: pattern,
|
|
98
|
+
foregroundColor: pattern ? foregroundColor : undefined,
|
|
99
|
+
gradient: undefined,
|
|
100
|
+
});
|
|
101
|
+
return (React.createElement(Stack, { direction: "vertical", className: "gap-1" },
|
|
102
|
+
React.createElement(Button, { variant: "outline", size: "xs", tabIndex: -1, className: "justify-center py-1", onClick: () => apply(undefined, undefined) }, "No pattern"),
|
|
103
|
+
React.createElement("div", { className: "pt-2 text-xs text-left" }, "Pattern style"),
|
|
104
|
+
React.createElement("div", { role: "group", "aria-label": "Pattern style", className: "grid grid-cols-6 gap-1" }, FILL_PATTERN_OPTIONS.map(({ pattern, label }) => (React.createElement("button", { type: "button", key: pattern, "aria-label": label, "aria-pressed": activePattern === pattern, className: `w-6 h-6 p-0 border-2 border-solid rounded-sm bg-transparent cursor-pointer ${activePattern === pattern
|
|
105
|
+
? "border-rnc-primary"
|
|
106
|
+
: "border-transparent"}`, onClick: () => apply(pattern, fill?.foregroundColor ? patternColor : undefined) },
|
|
107
|
+
React.createElement(PatternSwatch, { pattern: pattern, foregroundColor: patternColor, backgroundColor: background, label: label }))))),
|
|
108
|
+
React.createElement("div", { className: "pt-2 text-xs text-left" }, "Pattern color"),
|
|
109
|
+
React.createElement(ColorPicker, { value: patternColor, colors: STANDARD_COLORS, onChange: (color) => apply(activePattern ?? "darkGray", color), className: "gap-1" }),
|
|
110
|
+
React.createElement("div", { className: "flex items-center gap-1" },
|
|
111
|
+
React.createElement(HexColorPicker, { value: patternColor, onChange: (color) => apply(activePattern ?? "darkGray", color) }))));
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Gradient fill authoring (Excel: Fill Effects → Gradient, two colours +
|
|
115
|
+
* shading style). Picking a style applies immediately with the two colours;
|
|
116
|
+
* changing a colour re-applies when the cell already has a gradient.
|
|
117
|
+
*/
|
|
118
|
+
export const GradientFillPicker = ({ fill, theme, onChangeFill, }) => {
|
|
119
|
+
const existing = fill?.gradient ?? undefined;
|
|
120
|
+
const [color1, setColor1] = useState(() => existing?.stops?.[0]?.color ??
|
|
121
|
+
(fill?.backgroundColor
|
|
122
|
+
? getStringifiedColor(fill.backgroundColor, theme)
|
|
123
|
+
: "#FFFFFF"));
|
|
124
|
+
const [color2, setColor2] = useState(() => existing?.stops?.[existing.stops.length - 1]?.color ?? "#4472C4");
|
|
125
|
+
const apply = (preset, c1 = color1, c2 = color2) => onChangeFill({
|
|
126
|
+
backgroundColor: undefined,
|
|
127
|
+
fillPattern: undefined,
|
|
128
|
+
foregroundColor: undefined,
|
|
129
|
+
gradient: buildGradientFill(preset, c1, c2),
|
|
130
|
+
});
|
|
131
|
+
const reapply = (c1, c2) => {
|
|
132
|
+
if (!existing)
|
|
133
|
+
return;
|
|
134
|
+
const { stops: _stops, ...preset } = existing;
|
|
135
|
+
apply(preset, c1, c2);
|
|
136
|
+
};
|
|
137
|
+
const isActive = (preset) => !!existing &&
|
|
138
|
+
existing.type === preset.type &&
|
|
139
|
+
(preset.type === "path" || (existing.degree ?? 0) === (preset.degree ?? 0));
|
|
140
|
+
return (React.createElement(Stack, { direction: "vertical", className: "gap-1" },
|
|
141
|
+
React.createElement(Button, { variant: "outline", size: "xs", tabIndex: -1, className: "justify-center py-1", onClick: () => onChangeFill({
|
|
142
|
+
backgroundColor: fill?.backgroundColor ?? undefined,
|
|
143
|
+
fillPattern: fill?.fillPattern ?? undefined,
|
|
144
|
+
foregroundColor: fill?.foregroundColor ?? undefined,
|
|
145
|
+
gradient: undefined,
|
|
146
|
+
}) }, "No gradient"),
|
|
147
|
+
React.createElement("div", { className: "grid grid-cols-[auto_1fr] items-center gap-2 pt-2 text-xs" },
|
|
148
|
+
React.createElement("span", null, "Color 1"),
|
|
149
|
+
React.createElement(HexColorPicker, { value: color1, onChange: (color) => {
|
|
150
|
+
setColor1(color);
|
|
151
|
+
reapply(color, color2);
|
|
152
|
+
} }),
|
|
153
|
+
React.createElement("span", null, "Color 2"),
|
|
154
|
+
React.createElement(HexColorPicker, { value: color2, onChange: (color) => {
|
|
155
|
+
setColor2(color);
|
|
156
|
+
reapply(color1, color);
|
|
157
|
+
} })),
|
|
158
|
+
React.createElement("div", { className: "pt-2 text-xs text-left" }, "Shading style"),
|
|
159
|
+
React.createElement("div", { role: "group", "aria-label": "Shading style", className: "grid grid-cols-5 gap-1" }, GRADIENT_PRESETS.map(({ label, gradient }) => (React.createElement("button", { type: "button", key: label, "aria-label": label, "aria-pressed": isActive(gradient), className: `w-8 h-6 p-0 border-2 border-solid rounded-sm cursor-pointer ${isActive(gradient) ? "border-rnc-primary" : "border-rnc-border"}`, style: {
|
|
160
|
+
backgroundImage: gradientToCss(buildGradientFill(gradient, color1, color2)),
|
|
161
|
+
}, onClick: () => apply(gradient) }))))));
|
|
162
|
+
};
|
|
@@ -4,12 +4,14 @@ export * from "./scale-selector";
|
|
|
4
4
|
export * from "./font-size-selector";
|
|
5
5
|
export * from "./text-color-selector";
|
|
6
6
|
export * from "./background-color-selector";
|
|
7
|
+
export * from "./fill-selectors";
|
|
7
8
|
export * from "./merge-cells-selector";
|
|
8
9
|
export * from "./border-selector";
|
|
9
10
|
export * from "./font-family-selector";
|
|
10
11
|
export * from "./text-horizontal-align-selector";
|
|
11
12
|
export * from "./text-vertical-align-selector";
|
|
12
13
|
export * from "./text-wrap-selector";
|
|
14
|
+
export * from "./text-rotation-selector";
|
|
13
15
|
export * from "./text-format-selector";
|
|
14
16
|
export * from "./theme-selector";
|
|
15
17
|
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,kBAAkB,CAAC;AACjC,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"}
|
|
@@ -4,12 +4,14 @@ export * from "./scale-selector";
|
|
|
4
4
|
export * from "./font-size-selector";
|
|
5
5
|
export * from "./text-color-selector";
|
|
6
6
|
export * from "./background-color-selector";
|
|
7
|
+
export * from "./fill-selectors";
|
|
7
8
|
export * from "./merge-cells-selector";
|
|
8
9
|
export * from "./border-selector";
|
|
9
10
|
export * from "./font-family-selector";
|
|
10
11
|
export * from "./text-horizontal-align-selector";
|
|
11
12
|
export * from "./text-vertical-align-selector";
|
|
12
13
|
export * from "./text-wrap-selector";
|
|
14
|
+
export * from "./text-rotation-selector";
|
|
13
15
|
export * from "./text-format-selector";
|
|
14
16
|
export * from "./theme-selector";
|
|
15
17
|
export * from "./table-actions";
|
|
@@ -5,6 +5,14 @@ type TextHorizontalAlignSelectorProps = {
|
|
|
5
5
|
value: HorizontalAlign | undefined | null;
|
|
6
6
|
onChange(value: HorizontalAlign): void;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Excel's remaining Format Cells → Alignment → Horizontal entries. They have
|
|
10
|
+
* no toolbar glyph, so they render as labelled buttons under the icon row.
|
|
11
|
+
*/
|
|
12
|
+
export declare const EXTENDED_HORIZONTAL_ALIGNMENTS: ReadonlyArray<{
|
|
13
|
+
value: HorizontalAlign;
|
|
14
|
+
label: string;
|
|
15
|
+
}>;
|
|
8
16
|
export declare const TextHorizontalAlignSelector: ({ value, onChange, disabled, }: TextHorizontalAlignSelectorProps) => React.JSX.Element;
|
|
9
17
|
export {};
|
|
10
18
|
//# sourceMappingURL=text-horizontal-align-selector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-horizontal-align-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/text-horizontal-align-selector.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"text-horizontal-align-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/text-horizontal-align-selector.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,KAAK,gCAAgC,GAAG;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,aAAa,CAAC;IACzD,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAGA,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,gCAIzC,gCAAgC,sBA6FlC,CAAC"}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import { MdFormatAlignCenter, MdFormatAlignLeft, MdFormatAlignRight, MdOutlineArrowDropDown, } from "@rowsncolumns/icons";
|
|
2
|
-
import { IconButton } from "@rowsncolumns/ui";
|
|
1
|
+
import { MdFormatAlignCenter, MdFormatAlignJustify, MdFormatAlignLeft, MdFormatAlignRight, MdOutlineArrowDropDown, } from "@rowsncolumns/icons";
|
|
2
|
+
import { Button, IconButton } from "@rowsncolumns/ui";
|
|
3
3
|
import { Popover, PopoverContent, PopoverTrigger } from "@rowsncolumns/ui";
|
|
4
4
|
import { Stack } from "@rowsncolumns/ui";
|
|
5
5
|
import React, { useState } from "react";
|
|
6
6
|
import { useFocusSheet } from "../../atoms";
|
|
7
|
+
/**
|
|
8
|
+
* Excel's remaining Format Cells → Alignment → Horizontal entries. They have
|
|
9
|
+
* no toolbar glyph, so they render as labelled buttons under the icon row.
|
|
10
|
+
*/
|
|
11
|
+
export const EXTENDED_HORIZONTAL_ALIGNMENTS = [
|
|
12
|
+
{ value: "distributed", label: "Distributed" },
|
|
13
|
+
{ value: "fill", label: "Fill" },
|
|
14
|
+
];
|
|
7
15
|
export const TextHorizontalAlignSelector = ({ value = "left", onChange, disabled, }) => {
|
|
8
16
|
const focusSheet = useFocusSheet();
|
|
9
17
|
const [isOpen, onOpenChange] = useState(false);
|
|
10
18
|
const close = () => onOpenChange(false);
|
|
19
|
+
const select = (next) => {
|
|
20
|
+
onChange(next);
|
|
21
|
+
focusSheet?.();
|
|
22
|
+
close();
|
|
23
|
+
};
|
|
11
24
|
return (React.createElement(Popover, { open: isOpen, onOpenChange: onOpenChange },
|
|
12
25
|
React.createElement(PopoverTrigger, { asChild: true },
|
|
13
|
-
React.createElement(IconButton, { size: "lg", tooltip: "Horizontal align", disabled: disabled },
|
|
14
|
-
value === "right" ? (React.createElement(MdFormatAlignRight, null)) : value === "center" ? (React.createElement(MdFormatAlignCenter, null)) : (React.createElement(MdFormatAlignLeft, null)),
|
|
26
|
+
React.createElement(IconButton, { size: "lg", tooltip: "Horizontal align", "aria-label": "Horizontal align", disabled: disabled },
|
|
27
|
+
value === "right" ? (React.createElement(MdFormatAlignRight, null)) : value === "center" || value === "centerContinuous" ? (React.createElement(MdFormatAlignCenter, null)) : value === "justify" || value === "distributed" ? (React.createElement(MdFormatAlignJustify, null)) : (React.createElement(MdFormatAlignLeft, null)),
|
|
15
28
|
React.createElement(MdOutlineArrowDropDown, null))),
|
|
16
29
|
React.createElement(PopoverContent, { align: "start" },
|
|
17
30
|
React.createElement(Stack, { direction: "horizontal", className: "gap-1 max-w-[140px] flex-wrap" },
|
|
@@ -32,5 +45,8 @@ export const TextHorizontalAlignSelector = ({ value = "left", onChange, disabled
|
|
|
32
45
|
focusSheet?.();
|
|
33
46
|
close();
|
|
34
47
|
} },
|
|
35
|
-
React.createElement(MdFormatAlignRight, null))
|
|
48
|
+
React.createElement(MdFormatAlignRight, null)),
|
|
49
|
+
React.createElement(IconButton, { size: "lg", tooltip: "Justify", "aria-label": "Justify", isActive: value === "justify", onClick: () => select("justify") },
|
|
50
|
+
React.createElement(MdFormatAlignJustify, null))),
|
|
51
|
+
React.createElement(Stack, { direction: "horizontal", className: "gap-1 pt-1 max-w-[140px]" }, EXTENDED_HORIZONTAL_ALIGNMENTS.map(({ value: option, label }) => (React.createElement(Button, { key: option, type: "button", variant: value === option ? "secondary" : "ghost", size: "xs", "aria-pressed": value === option, onClick: () => select(option) }, label)))))));
|
|
36
52
|
};
|
|
@@ -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;
|
|
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
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type WatchWindowContext, type WatchWindowEntry } from "../../atoms/watch-window.atom";
|
|
3
|
+
import type { SheetCoordinate } from "../../types";
|
|
4
|
+
export type WatchWindowProps = {
|
|
5
|
+
entries: WatchWindowEntry[];
|
|
6
|
+
context: WatchWindowContext | null;
|
|
7
|
+
onRemove(cell: SheetCoordinate): void;
|
|
8
|
+
onClose(): void;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Excel's Watch Window: a floating list of cells with their sheet, address,
|
|
12
|
+
* current value and formula, refreshed with the grid. Double-click a row to
|
|
13
|
+
* jump to the cell. Presentational; {@link WatchWindowHost} wires the atoms.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WatchWindow: React.MemoExoticComponent<({ entries, context, onRemove, onClose }: WatchWindowProps) => React.JSX.Element>;
|
|
16
|
+
/**
|
|
17
|
+
* Host-mounted, atom-driven Watch Window. Mount once alongside the other
|
|
18
|
+
* dialogs; the grid's context menu offers "Add to Watch Window" while a
|
|
19
|
+
* host is mounted and republishes live getters on each render.
|
|
20
|
+
*/
|
|
21
|
+
export declare const WatchWindowHost: {
|
|
22
|
+
(): React.JSX.Element | null;
|
|
23
|
+
displayName: string;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/watch-window/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,OAAO,EAML,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,sEACoB,gBAAgB,uBA6E3D,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;CAgB3B,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Button, IconButton } from "@rowsncolumns/ui";
|
|
2
|
+
import { Cross1Icon } from "@rowsncolumns/icons";
|
|
3
|
+
import { cellToAddress } from "@rowsncolumns/utils";
|
|
4
|
+
import React, { memo, useCallback } from "react";
|
|
5
|
+
import { useRegisterWatchWindowHost, useRemoveWatch, useWatchWindowContext, useWatchWindowEntries, useWatchWindowOpen, } from "../../atoms/watch-window.atom";
|
|
6
|
+
/**
|
|
7
|
+
* Excel's Watch Window: a floating list of cells with their sheet, address,
|
|
8
|
+
* current value and formula, refreshed with the grid. Double-click a row to
|
|
9
|
+
* jump to the cell. Presentational; {@link WatchWindowHost} wires the atoms.
|
|
10
|
+
*/
|
|
11
|
+
export const WatchWindow = memo(({ entries, context, onRemove, onClose }) => (React.createElement("div", { role: "dialog", "aria-label": "Watch Window", className: "fixed bottom-4 right-4 z-50 w-[420px] max-w-[92vw] rounded-md border border-rnc-border bg-rnc-background text-rnc-foreground shadow-lg pointer-events-auto" },
|
|
12
|
+
React.createElement("div", { className: "flex items-center justify-between px-3 py-2 border-b border-rnc-border" },
|
|
13
|
+
React.createElement("span", { className: "text-sm font-medium" }, "Watch Window"),
|
|
14
|
+
React.createElement(IconButton, { "aria-label": "Close Watch Window", onClick: onClose },
|
|
15
|
+
React.createElement(Cross1Icon, null))),
|
|
16
|
+
entries.length === 0 ? (React.createElement("div", { className: "px-3 py-4 text-xs text-rnc-foreground/60" }, "No cells are being watched. Right-click a cell \u2192 Add to Watch Window.")) : (React.createElement("table", { className: "w-full text-xs border-collapse" },
|
|
17
|
+
React.createElement("thead", null,
|
|
18
|
+
React.createElement("tr", { className: "text-left text-rnc-foreground/70" },
|
|
19
|
+
React.createElement("th", { className: "px-3 py-1 font-medium" }, "Sheet"),
|
|
20
|
+
React.createElement("th", { className: "px-2 py-1 font-medium" }, "Cell"),
|
|
21
|
+
React.createElement("th", { className: "px-2 py-1 font-medium" }, "Value"),
|
|
22
|
+
React.createElement("th", { className: "px-2 py-1 font-medium" }, "Formula"),
|
|
23
|
+
React.createElement("th", { className: "px-2 py-1" }))),
|
|
24
|
+
React.createElement("tbody", null, entries.map((entry) => {
|
|
25
|
+
const address = cellToAddress(entry) ?? "";
|
|
26
|
+
const sheetName = context?.getSheetName?.(entry.sheetId) ?? String(entry.sheetId);
|
|
27
|
+
const value = context?.getFormattedValue(entry.sheetId, entry.rowIndex, entry.columnIndex);
|
|
28
|
+
const formula = context?.getFormula(entry.sheetId, entry.rowIndex, entry.columnIndex);
|
|
29
|
+
return (React.createElement("tr", { key: `${entry.sheetId}:${entry.rowIndex}:${entry.columnIndex}`, className: "cursor-default hover:bg-rnc-accent", onDoubleClick: () => context?.onNavigate(entry), "data-testid": "watch-row" },
|
|
30
|
+
React.createElement("td", { className: "px-3 py-1 truncate max-w-[110px]" }, sheetName),
|
|
31
|
+
React.createElement("td", { className: "px-2 py-1 font-mono" }, address),
|
|
32
|
+
React.createElement("td", { className: "px-2 py-1 truncate max-w-[120px]" }, value ?? ""),
|
|
33
|
+
React.createElement("td", { className: "px-2 py-1 font-mono truncate max-w-[140px]" }, formula ?? ""),
|
|
34
|
+
React.createElement("td", { className: "px-2 py-1 text-right" },
|
|
35
|
+
React.createElement(Button, { variant: "ghost", size: "xs", "aria-label": `Remove watch ${sheetName}!${address}`, onClick: () => onRemove(entry) }, "Delete"))));
|
|
36
|
+
})))))));
|
|
37
|
+
WatchWindow.displayName = "WatchWindow";
|
|
38
|
+
/**
|
|
39
|
+
* Host-mounted, atom-driven Watch Window. Mount once alongside the other
|
|
40
|
+
* dialogs; the grid's context menu offers "Add to Watch Window" while a
|
|
41
|
+
* host is mounted and republishes live getters on each render.
|
|
42
|
+
*/
|
|
43
|
+
export const WatchWindowHost = () => {
|
|
44
|
+
useRegisterWatchWindowHost();
|
|
45
|
+
const [entries] = useWatchWindowEntries();
|
|
46
|
+
const [open, setOpen] = useWatchWindowOpen();
|
|
47
|
+
const context = useWatchWindowContext();
|
|
48
|
+
const removeWatch = useRemoveWatch();
|
|
49
|
+
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
50
|
+
if (!open)
|
|
51
|
+
return null;
|
|
52
|
+
return (React.createElement(WatchWindow, { entries: entries, context: context, onRemove: removeWatch, onClose: close }));
|
|
53
|
+
};
|
|
54
|
+
WatchWindowHost.displayName = "WatchWindowHost";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cell-renderer.d.ts","sourceRoot":"","sources":["../../../hooks/use-cell-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAsB,MAAM,OAAO,CAAC;AAI3C,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"use-cell-renderer.d.ts","sourceRoot":"","sources":["../../../hooks/use-cell-renderer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { isNil } from "@rowsncolumns/utils";
|
|
|
3
3
|
import React, { useCallback } from "react";
|
|
4
4
|
import { getHeaderFill } from "../api/colors";
|
|
5
5
|
import { showValidationDropdownArrow } from "../api/validation";
|
|
6
|
+
import { alignmentImpliesWrap, isFillAlignment, } from "../components/cell/alignment";
|
|
6
7
|
import { CellBackground } from "../components/cell/cell-background";
|
|
7
8
|
import { CellDiff as CellDiffRenderer } from "../components/cell-diff";
|
|
8
9
|
import { CellOverlayRenderer } from "../components/cell-overlay";
|
|
@@ -50,10 +51,17 @@ export const useCellRenderer = ({ sheetId, scale, showGridLines, showZeros, isRo
|
|
|
50
51
|
// Calculated column
|
|
51
52
|
const isCalculatedColumn = isTableHeader(rowIndex, columnIndex) &&
|
|
52
53
|
isCalculatedColumnHeader(rowIndex, columnIndex);
|
|
53
|
-
// Cell overflow
|
|
54
|
+
// Cell overflow. Excel's justify / distributed alignments wrap inside
|
|
55
|
+
// the cell's own width even without `wrapStrategy: "wrap"`, and fill
|
|
56
|
+
// repeats the text to the cell edge — none of them ever spill into
|
|
57
|
+
// the neighbouring cells.
|
|
54
58
|
const wrapStrategy = effectiveCellFormat?.wrapStrategy;
|
|
55
|
-
const
|
|
56
|
-
const
|
|
59
|
+
const wrapsByAlignment = alignmentImpliesWrap(horizontalAlignment);
|
|
60
|
+
const fillsCell = isFillAlignment(horizontalAlignment);
|
|
61
|
+
const shouldWrap = wrapStrategy === "wrap" || wrapsByAlignment;
|
|
62
|
+
const isWrapEnabled = (wrapStrategy ?? "overflow") === "overflow" &&
|
|
63
|
+
!wrapsByAlignment &&
|
|
64
|
+
!fillsCell;
|
|
57
65
|
// Normalize display value for both rendering and overflow calculation
|
|
58
66
|
const displayFormattedValue = normalizeDisplayText(formattedValue, shouldWrap);
|
|
59
67
|
const shouldRecalcTextWidth = isWrapEnabled || wrapStrategy === "clip";
|
|
@@ -105,9 +113,12 @@ export const useCellRenderer = ({ sheetId, scale, showGridLines, showZeros, isRo
|
|
|
105
113
|
}
|
|
106
114
|
// shrinkToFit needs the natural text width to compare against the
|
|
107
115
|
// cell's available width; rotated text uses it to size the (unrotated)
|
|
108
|
-
// text box that gets pivoted around the cell edge
|
|
109
|
-
//
|
|
110
|
-
|
|
116
|
+
// text box that gets pivoted around the cell edge; fill alignment
|
|
117
|
+
// repeats the text `floor(cell / natural)` times. Without this, the
|
|
118
|
+
// Konva Text renders with width 0 (rotation) or a single unfilled copy.
|
|
119
|
+
const textWidth = (overflowRight && shouldRecalcTextWidth) ||
|
|
120
|
+
containsTextInside ||
|
|
121
|
+
fillsCell
|
|
111
122
|
? getTextDimension(sheetId, rowIndex, columnIndex, displayFormattedValue).width
|
|
112
123
|
: 0;
|
|
113
124
|
const enableOverflow = shouldOverflow && cellWidth > props.width;
|
|
@@ -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: (
|
|
57
|
+
paste: (pasteSpecial?: PasteSpecialRequest) => Promise<void>;
|
|
39
58
|
copiedSelections: any[];
|
|
40
59
|
clearCopiedSelections: () => void;
|
|
41
60
|
onInternalCopy: (e: ClipboardEvent, isCutOperation?: boolean) => boolean;
|