@rowsncolumns/spreadsheet-state 29.2.0 → 29.3.2
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/border.d.ts +1 -1
- package/dist/cjs/api/border.d.ts.map +1 -1
- package/dist/cjs/commands.d.ts +36 -3
- package/dist/cjs/commands.d.ts.map +1 -1
- package/dist/cjs/components/editors/cell-format/alignment-tab.d.ts +27 -0
- package/dist/cjs/components/editors/cell-format/alignment-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/alignment-tab.js +83 -0
- package/dist/cjs/components/editors/cell-format/border-edges.d.ts +42 -0
- package/dist/cjs/components/editors/cell-format/border-edges.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/border-edges.js +118 -0
- package/dist/cjs/components/editors/cell-format/border-tab.d.ts +21 -0
- package/dist/cjs/components/editors/cell-format/border-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/border-tab.js +123 -0
- package/dist/cjs/components/editors/cell-format/fill-tab.d.ts +14 -0
- package/dist/cjs/components/editors/cell-format/fill-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/fill-tab.js +54 -0
- package/dist/cjs/components/editors/cell-format/font-tab.d.ts +18 -0
- package/dist/cjs/components/editors/cell-format/font-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/font-tab.js +99 -0
- package/dist/cjs/components/editors/cell-format/list-box.d.ts +35 -0
- package/dist/cjs/components/editors/cell-format/list-box.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/list-box.js +37 -0
- package/dist/cjs/components/editors/cell-format/number-formats.d.ts +90 -0
- package/dist/cjs/components/editors/cell-format/number-formats.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/number-formats.js +403 -0
- package/dist/cjs/components/editors/cell-format/number-tab.d.ts +12 -0
- package/dist/cjs/components/editors/cell-format/number-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/number-tab.js +194 -0
- package/dist/cjs/components/editors/cell-format/protection-tab.d.ts +13 -0
- package/dist/cjs/components/editors/cell-format/protection-tab.d.ts.map +1 -0
- package/dist/cjs/components/editors/cell-format/protection-tab.js +13 -0
- package/dist/cjs/components/editors/cell-format-editor.d.ts +31 -3
- package/dist/cjs/components/editors/cell-format-editor.d.ts.map +1 -1
- package/dist/cjs/components/editors/cell-format-editor.js +182 -636
- package/dist/cjs/components/editors/conditional-format-data-bar-options.d.ts +16 -0
- package/dist/cjs/components/editors/conditional-format-data-bar-options.d.ts.map +1 -0
- package/dist/cjs/components/editors/conditional-format-data-bar-options.js +166 -0
- package/dist/cjs/components/editors/conditional-format-editor.d.ts +7 -1
- package/dist/cjs/components/editors/conditional-format-editor.d.ts.map +1 -1
- package/dist/cjs/components/editors/conditional-format-editor.js +51 -9
- package/dist/cjs/components/editors/conditional-format-icon-set-options.d.ts +17 -0
- package/dist/cjs/components/editors/conditional-format-icon-set-options.d.ts.map +1 -0
- package/dist/cjs/components/editors/conditional-format-icon-set-options.js +163 -0
- package/dist/cjs/components/editors/conditional-format-rule-editor.d.ts +9 -0
- package/dist/cjs/components/editors/conditional-format-rule-editor.d.ts.map +1 -1
- package/dist/cjs/components/editors/conditional-format-rule-editor.js +97 -125
- package/dist/cjs/components/editors/sheet-filter.d.ts +20 -9
- package/dist/cjs/components/editors/sheet-filter.d.ts.map +1 -1
- package/dist/cjs/components/editors/sheet-filter.js +44 -12
- package/dist/cjs/hooks/use-conditional-formatting-rules.d.ts +2 -0
- package/dist/cjs/hooks/use-conditional-formatting-rules.d.ts.map +1 -1
- package/dist/cjs/hooks/use-conditional-formatting-rules.js +22 -1
- package/dist/cjs/hooks/use-spreadsheet-state.d.ts +2 -0
- package/dist/cjs/hooks/use-spreadsheet-state.d.ts.map +1 -1
- package/dist/cjs/hooks/use-spreadsheet-state.js +4 -2
- package/dist/cjs/interface/change-border.d.ts.map +1 -1
- package/dist/cjs/interface/change-border.js +8 -1
- package/dist/cjs/interface/conditional-formatting-rules.d.ts +11 -1
- package/dist/cjs/interface/conditional-formatting-rules.d.ts.map +1 -1
- package/dist/cjs/interface/conditional-formatting-rules.js +44 -1
- package/dist/cjs/sheet-cell.d.ts +8 -0
- package/dist/cjs/sheet-cell.d.ts.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/api/border.d.ts +1 -1
- package/dist/esm/api/border.d.ts.map +1 -1
- package/dist/esm/commands.d.ts +36 -3
- package/dist/esm/commands.d.ts.map +1 -1
- package/dist/esm/components/editors/cell-format/alignment-tab.d.ts +27 -0
- package/dist/esm/components/editors/cell-format/alignment-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/alignment-tab.js +81 -0
- package/dist/esm/components/editors/cell-format/border-edges.d.ts +42 -0
- package/dist/esm/components/editors/cell-format/border-edges.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/border-edges.js +79 -0
- package/dist/esm/components/editors/cell-format/border-tab.d.ts +21 -0
- package/dist/esm/components/editors/cell-format/border-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/border-tab.js +103 -0
- package/dist/esm/components/editors/cell-format/fill-tab.d.ts +14 -0
- package/dist/esm/components/editors/cell-format/fill-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/fill-tab.js +49 -0
- package/dist/esm/components/editors/cell-format/font-tab.d.ts +18 -0
- package/dist/esm/components/editors/cell-format/font-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/font-tab.js +88 -0
- package/dist/esm/components/editors/cell-format/list-box.d.ts +35 -0
- package/dist/esm/components/editors/cell-format/list-box.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/list-box.js +30 -0
- package/dist/esm/components/editors/cell-format/number-formats.d.ts +90 -0
- package/dist/esm/components/editors/cell-format/number-formats.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/number-formats.js +383 -0
- package/dist/esm/components/editors/cell-format/number-tab.d.ts +12 -0
- package/dist/esm/components/editors/cell-format/number-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/number-tab.js +165 -0
- package/dist/esm/components/editors/cell-format/protection-tab.d.ts +13 -0
- package/dist/esm/components/editors/cell-format/protection-tab.d.ts.map +1 -0
- package/dist/esm/components/editors/cell-format/protection-tab.js +9 -0
- package/dist/esm/components/editors/cell-format-editor.d.ts +31 -3
- package/dist/esm/components/editors/cell-format-editor.d.ts.map +1 -1
- package/dist/esm/components/editors/cell-format-editor.js +148 -615
- package/dist/esm/components/editors/conditional-format-data-bar-options.d.ts +16 -0
- package/dist/esm/components/editors/conditional-format-data-bar-options.d.ts.map +1 -0
- package/dist/esm/components/editors/conditional-format-data-bar-options.js +145 -0
- package/dist/esm/components/editors/conditional-format-editor.d.ts +7 -1
- package/dist/esm/components/editors/conditional-format-editor.d.ts.map +1 -1
- package/dist/esm/components/editors/conditional-format-editor.js +39 -8
- package/dist/esm/components/editors/conditional-format-icon-set-options.d.ts +17 -0
- package/dist/esm/components/editors/conditional-format-icon-set-options.d.ts.map +1 -0
- package/dist/esm/components/editors/conditional-format-icon-set-options.js +139 -0
- package/dist/esm/components/editors/conditional-format-rule-editor.d.ts +9 -0
- package/dist/esm/components/editors/conditional-format-rule-editor.d.ts.map +1 -1
- package/dist/esm/components/editors/conditional-format-rule-editor.js +60 -85
- package/dist/esm/components/editors/sheet-filter.d.ts +20 -9
- package/dist/esm/components/editors/sheet-filter.d.ts.map +1 -1
- package/dist/esm/components/editors/sheet-filter.js +31 -11
- package/dist/esm/hooks/use-conditional-formatting-rules.d.ts +2 -0
- package/dist/esm/hooks/use-conditional-formatting-rules.d.ts.map +1 -1
- package/dist/esm/hooks/use-conditional-formatting-rules.js +19 -1
- package/dist/esm/hooks/use-spreadsheet-state.d.ts +2 -0
- package/dist/esm/hooks/use-spreadsheet-state.d.ts.map +1 -1
- package/dist/esm/hooks/use-spreadsheet-state.js +4 -2
- package/dist/esm/interface/change-border.d.ts.map +1 -1
- package/dist/esm/interface/change-border.js +8 -1
- package/dist/esm/interface/conditional-formatting-rules.d.ts +11 -1
- package/dist/esm/interface/conditional-formatting-rules.d.ts.map +1 -1
- package/dist/esm/interface/conditional-formatting-rules.js +29 -1
- package/dist/esm/sheet-cell.d.ts +8 -0
- package/dist/esm/sheet-cell.d.ts.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CellFormat, HorizontalAlign, ReadingOrder, VerticalAlign } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
type HorizontalChoice = HorizontalAlign | "general";
|
|
4
|
+
/** Excel's Horizontal list, in Excel's order. */
|
|
5
|
+
export declare const HORIZONTAL_OPTIONS: {
|
|
6
|
+
value: HorizontalChoice;
|
|
7
|
+
label: string;
|
|
8
|
+
}[];
|
|
9
|
+
/** Excel's Vertical list. */
|
|
10
|
+
export declare const VERTICAL_OPTIONS: {
|
|
11
|
+
value: VerticalAlign;
|
|
12
|
+
label: string;
|
|
13
|
+
}[];
|
|
14
|
+
export declare const TEXT_DIRECTION_OPTIONS: {
|
|
15
|
+
value: ReadingOrder;
|
|
16
|
+
label: string;
|
|
17
|
+
}[];
|
|
18
|
+
export type AlignmentTabProps = {
|
|
19
|
+
format: CellFormat;
|
|
20
|
+
onChange(patch: Partial<CellFormat>): void;
|
|
21
|
+
merged: boolean;
|
|
22
|
+
onChangeMerged(merged: boolean): void;
|
|
23
|
+
};
|
|
24
|
+
/** Format Cells → Alignment. */
|
|
25
|
+
export declare const AlignmentTab: ({ format, onChange, merged, onChangeMerged, }: AlignmentTabProps) => React.JSX.Element;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=alignment-tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alignment-tab.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/alignment-tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACd,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAAC;AAEpD,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,EAAE;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAS1E,CAAC;AAEF,6BAA6B;AAC7B,eAAO,MAAM,gBAAgB,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAMrE,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAI1E,CAAC;AAKF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,gCAAgC;AAChC,eAAO,MAAM,YAAY,GAAI,+CAK1B,iBAAiB,sBA2InB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Input } from "@rowsncolumns/ui";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { CheckboxField, SelectField } from "./list-box";
|
|
4
|
+
/** Excel's Horizontal list, in Excel's order. */
|
|
5
|
+
export const HORIZONTAL_OPTIONS = [
|
|
6
|
+
{ value: "general", label: "General" },
|
|
7
|
+
{ value: "left", label: "Left (Indent)" },
|
|
8
|
+
{ value: "center", label: "Center" },
|
|
9
|
+
{ value: "right", label: "Right (Indent)" },
|
|
10
|
+
{ value: "fill", label: "Fill" },
|
|
11
|
+
{ value: "justify", label: "Justify" },
|
|
12
|
+
{ value: "centerContinuous", label: "Center Across Selection" },
|
|
13
|
+
{ value: "distributed", label: "Distributed (Indent)" },
|
|
14
|
+
];
|
|
15
|
+
/** Excel's Vertical list. */
|
|
16
|
+
export const VERTICAL_OPTIONS = [
|
|
17
|
+
{ value: "top", label: "Top" },
|
|
18
|
+
{ value: "middle", label: "Center" },
|
|
19
|
+
{ value: "bottom", label: "Bottom" },
|
|
20
|
+
{ value: "justify", label: "Justify" },
|
|
21
|
+
{ value: "distributed", label: "Distributed" },
|
|
22
|
+
];
|
|
23
|
+
export const TEXT_DIRECTION_OPTIONS = [
|
|
24
|
+
{ value: "context", label: "Context" },
|
|
25
|
+
{ value: "ltr", label: "Left-to-Right" },
|
|
26
|
+
{ value: "rtl", label: "Right-to-Left" },
|
|
27
|
+
];
|
|
28
|
+
/** Horizontal modes Excel lets you indent. */
|
|
29
|
+
const INDENTABLE = new Set(["left", "right", "distributed"]);
|
|
30
|
+
/** Format Cells → Alignment. */
|
|
31
|
+
export const AlignmentTab = ({ format, onChange, merged, onChangeMerged, }) => {
|
|
32
|
+
const horizontal = format.horizontalAlignment ?? "general";
|
|
33
|
+
const vertical = format.verticalAlignment ?? "bottom";
|
|
34
|
+
const rotation = format.textRotation;
|
|
35
|
+
const stacked = rotation === "vertical";
|
|
36
|
+
const degrees = typeof rotation === "number" ? rotation : 0;
|
|
37
|
+
const wrap = format.wrapStrategy === "wrap";
|
|
38
|
+
const shrink = !!format.shrinkToFit;
|
|
39
|
+
return (React.createElement("div", { className: "grid grid-cols-[1fr_160px] gap-4" },
|
|
40
|
+
React.createElement("div", { className: "flex flex-col gap-3" },
|
|
41
|
+
React.createElement("fieldset", { className: "flex flex-col gap-2 border-0 p-0" },
|
|
42
|
+
React.createElement("legend", { className: "pb-1 font-system text-sm" }, "Text alignment"),
|
|
43
|
+
React.createElement("div", { className: "grid grid-cols-[1fr_90px] gap-2" },
|
|
44
|
+
React.createElement(SelectField, { label: "Horizontal", value: horizontal, options: HORIZONTAL_OPTIONS, onChange: (value) => onChange({
|
|
45
|
+
horizontalAlignment: value === "general" ? undefined : value,
|
|
46
|
+
...(INDENTABLE.has(value) ? {} : { indent: 0 }),
|
|
47
|
+
}) }),
|
|
48
|
+
React.createElement("label", { className: "flex flex-col gap-1 font-system text-sm" },
|
|
49
|
+
"Indent",
|
|
50
|
+
React.createElement(Input, { "aria-label": "Indent", type: "number", min: 0, max: 250, className: "text-xs", disabled: !INDENTABLE.has(horizontal), value: format.indent ?? 0, onChange: (event) => {
|
|
51
|
+
const n = parseInt(event.target.value, 10);
|
|
52
|
+
onChange({
|
|
53
|
+
indent: Number.isFinite(n) ? Math.max(0, Math.min(250, n)) : 0,
|
|
54
|
+
});
|
|
55
|
+
} }))),
|
|
56
|
+
React.createElement(SelectField, { label: "Vertical", value: vertical, options: VERTICAL_OPTIONS, onChange: (value) => onChange({ verticalAlignment: value }) })),
|
|
57
|
+
React.createElement("fieldset", { className: "flex flex-col gap-1.5 border-0 p-0" },
|
|
58
|
+
React.createElement("legend", { className: "pb-1 font-system text-sm" }, "Text control"),
|
|
59
|
+
React.createElement(CheckboxField, { label: "Wrap text", checked: wrap, onChange: (checked) => onChange({
|
|
60
|
+
wrapStrategy: checked ? "wrap" : "overflow",
|
|
61
|
+
// Excel: wrap and shrink to fit are exclusive.
|
|
62
|
+
...(checked ? { shrinkToFit: false } : {}),
|
|
63
|
+
}) }),
|
|
64
|
+
React.createElement(CheckboxField, { label: "Shrink to fit", checked: shrink, disabled: wrap, onChange: (checked) => onChange({ shrinkToFit: checked }) }),
|
|
65
|
+
React.createElement(CheckboxField, { label: "Merge cells", checked: merged, onChange: onChangeMerged })),
|
|
66
|
+
React.createElement("fieldset", { className: "flex flex-col gap-1 border-0 p-0" },
|
|
67
|
+
React.createElement("legend", { className: "pb-1 font-system text-sm" }, "Right-to-left"),
|
|
68
|
+
React.createElement(SelectField, { label: "Text direction", value: format.readingOrder ?? "context", options: TEXT_DIRECTION_OPTIONS, onChange: (value) => onChange({ readingOrder: value }) }))),
|
|
69
|
+
React.createElement("fieldset", { className: "flex flex-col gap-2 border-0 p-0" },
|
|
70
|
+
React.createElement("legend", { className: "pb-1 font-system text-sm" }, "Orientation"),
|
|
71
|
+
React.createElement("button", { type: "button", "aria-pressed": stacked, "aria-label": "Vertical text", className: `flex h-20 w-8 flex-col items-center justify-center rounded-sm border border-solid text-xs leading-tight ${stacked ? "border-rnc-primary" : "border-rnc-border"}`, onClick: () => onChange({ textRotation: stacked ? 0 : "vertical" }) }, "Text".split("").map((ch, i) => (React.createElement("span", { key: i }, ch)))),
|
|
72
|
+
React.createElement("input", { "aria-label": "Orientation", type: "range", min: -90, max: 90, step: 1, value: degrees, disabled: stacked, onChange: (event) => onChange({ textRotation: Number(event.target.value) }) }),
|
|
73
|
+
React.createElement("label", { className: "flex items-center gap-1 font-system text-xs" },
|
|
74
|
+
React.createElement(Input, { "aria-label": "Degrees", type: "number", min: -90, max: 90, className: "w-16 text-xs", value: degrees, disabled: stacked, onChange: (event) => {
|
|
75
|
+
const n = parseInt(event.target.value, 10);
|
|
76
|
+
onChange({
|
|
77
|
+
textRotation: Number.isFinite(n) ? Math.max(-90, Math.min(90, n)) : 0,
|
|
78
|
+
});
|
|
79
|
+
} }),
|
|
80
|
+
"Degrees"))));
|
|
81
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BorderLocation, Borders, BorderStyle, Color } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
export type EdgeKey = "top" | "insideHorizontal" | "bottom" | "left" | "insideVertical" | "right" | "diagonalUp" | "diagonalDown";
|
|
3
|
+
export type EdgeLine = {
|
|
4
|
+
style: BorderStyle;
|
|
5
|
+
color: Color;
|
|
6
|
+
};
|
|
7
|
+
/** `null` = no border on that edge. */
|
|
8
|
+
export type EdgeState = EdgeLine | null;
|
|
9
|
+
export type EdgeMap = Partial<Record<EdgeKey, EdgeState>>;
|
|
10
|
+
/** The line style the Line Style list picks; `"none"` removes the edge clicked. */
|
|
11
|
+
export type LineStyleChoice = BorderStyle | "none";
|
|
12
|
+
/** Excel's Line Style list, in Excel's order (None, then thin → thick). */
|
|
13
|
+
export declare const LINE_STYLES: {
|
|
14
|
+
value: LineStyleChoice;
|
|
15
|
+
label: string;
|
|
16
|
+
}[];
|
|
17
|
+
export declare const EDGE_LOCATION: Record<EdgeKey, BorderLocation>;
|
|
18
|
+
export declare const ALL_EDGES: EdgeKey[];
|
|
19
|
+
/** The active cell's own edges, as the preview opens on them. */
|
|
20
|
+
export declare const edgesFromBorders: (borders: Borders | null | undefined) => EdgeMap;
|
|
21
|
+
/**
|
|
22
|
+
* Click an edge: Excel removes it when it already carries the current style
|
|
23
|
+
* and colour (or the style is None), otherwise draws it in them.
|
|
24
|
+
*/
|
|
25
|
+
export declare const toggleEdge: (shown: EdgeMap, key: EdgeKey, lineStyle: LineStyleChoice, color: Color) => EdgeState;
|
|
26
|
+
/** The None / Outline / Inside presets: the edges they set. */
|
|
27
|
+
export declare const presetEdges: (preset: "none" | "outline" | "inside", lineStyle: LineStyleChoice, color: Color) => EdgeMap;
|
|
28
|
+
export type BorderCommand = {
|
|
29
|
+
location: BorderLocation;
|
|
30
|
+
/** Both `undefined` = remove the edge (the `onChangeBorder` clear contract). */
|
|
31
|
+
color: Color | undefined;
|
|
32
|
+
style: BorderStyle | undefined;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The `onChangeBorder` calls for the edited edges. A None preset that touched
|
|
36
|
+
* every edge is one `"none"` command (clears diagonals too); everything else
|
|
37
|
+
* is one command per edited edge, removals first so a redraw lands on top.
|
|
38
|
+
*/
|
|
39
|
+
export declare const toBorderCommands: (edits: EdgeMap, options?: {
|
|
40
|
+
clearedAll?: boolean;
|
|
41
|
+
}) => BorderCommand[];
|
|
42
|
+
//# sourceMappingURL=border-edges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"border-edges.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/border-edges.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EACd,OAAO,EACP,WAAW,EACX,KAAK,EACN,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,OAAO,GACf,KAAK,GACL,kBAAkB,GAClB,QAAQ,GACR,MAAM,GACN,gBAAgB,GAChB,OAAO,GACP,YAAY,GACZ,cAAc,CAAC;AAEnB,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAC5D,uCAAuC;AACvC,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;AACxC,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAE1D,mFAAmF;AACnF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,EAAE;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAelE,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,cAAc,CASzD,CAAC;AAIF,eAAO,MAAM,SAAS,EAAE,OAAO,EAA0D,CAAC;AAE1F,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,GAAI,SAAS,OAAO,GAAG,IAAI,GAAG,SAAS,KAAG,OAOtE,CAAC;AAKF;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,OAAO,EACd,KAAK,OAAO,EACZ,WAAW,eAAe,EAC1B,OAAO,KAAK,KACX,SAG8B,CAAC;AAElC,+DAA+D;AAC/D,eAAO,MAAM,WAAW,GACtB,QAAQ,MAAM,GAAG,SAAS,GAAG,QAAQ,EACrC,WAAW,eAAe,EAC1B,OAAO,KAAK,KACX,OAYF,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,gFAAgF;IAChF,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,OAAO,OAAO,EACd,UAAS;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,KACrC,aAAa,EAgBf,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Excel's Line Style list, in Excel's order (None, then thin → thick). */
|
|
2
|
+
export const LINE_STYLES = [
|
|
3
|
+
{ value: "none", label: "None" },
|
|
4
|
+
{ value: "hair", label: "Hair" },
|
|
5
|
+
{ value: "dotted", label: "Dotted" },
|
|
6
|
+
{ value: "dashDotDot", label: "Dash dot dot" },
|
|
7
|
+
{ value: "dashDot", label: "Dash dot" },
|
|
8
|
+
{ value: "dashed", label: "Dashed" },
|
|
9
|
+
{ value: "solid", label: "Thin" },
|
|
10
|
+
{ value: "mediumDashDotDot", label: "Medium dash dot dot" },
|
|
11
|
+
{ value: "slantDashDot", label: "Slanted dash dot" },
|
|
12
|
+
{ value: "mediumDashDot", label: "Medium dash dot" },
|
|
13
|
+
{ value: "mediumDashed", label: "Medium dashed" },
|
|
14
|
+
{ value: "solid_medium", label: "Medium" },
|
|
15
|
+
{ value: "solid_thick", label: "Thick" },
|
|
16
|
+
{ value: "double", label: "Double" },
|
|
17
|
+
];
|
|
18
|
+
export const EDGE_LOCATION = {
|
|
19
|
+
top: "top",
|
|
20
|
+
insideHorizontal: "horizontal",
|
|
21
|
+
bottom: "bottom",
|
|
22
|
+
left: "left",
|
|
23
|
+
insideVertical: "vertical",
|
|
24
|
+
right: "right",
|
|
25
|
+
diagonalUp: "diagonalUp",
|
|
26
|
+
diagonalDown: "diagonalDown",
|
|
27
|
+
};
|
|
28
|
+
const OUTLINE = ["top", "bottom", "left", "right"];
|
|
29
|
+
const INSIDE = ["insideHorizontal", "insideVertical"];
|
|
30
|
+
export const ALL_EDGES = [...OUTLINE, ...INSIDE, "diagonalUp", "diagonalDown"];
|
|
31
|
+
/** The active cell's own edges, as the preview opens on them. */
|
|
32
|
+
export const edgesFromBorders = (borders) => {
|
|
33
|
+
const map = {};
|
|
34
|
+
for (const key of ["top", "bottom", "left", "right", "diagonalUp", "diagonalDown"]) {
|
|
35
|
+
const b = borders?.[key];
|
|
36
|
+
if (b?.style)
|
|
37
|
+
map[key] = { style: b.style, color: b.color ?? "#000000" };
|
|
38
|
+
}
|
|
39
|
+
return map;
|
|
40
|
+
};
|
|
41
|
+
const sameLine = (a, b) => !!a && a.style === b.style && JSON.stringify(a.color) === JSON.stringify(b.color);
|
|
42
|
+
/**
|
|
43
|
+
* Click an edge: Excel removes it when it already carries the current style
|
|
44
|
+
* and colour (or the style is None), otherwise draws it in them.
|
|
45
|
+
*/
|
|
46
|
+
export const toggleEdge = (shown, key, lineStyle, color) => lineStyle === "none" || sameLine(shown[key], { style: lineStyle, color })
|
|
47
|
+
? null
|
|
48
|
+
: { style: lineStyle, color };
|
|
49
|
+
/** The None / Outline / Inside presets: the edges they set. */
|
|
50
|
+
export const presetEdges = (preset, lineStyle, color) => {
|
|
51
|
+
if (preset === "none" || lineStyle === "none") {
|
|
52
|
+
return Object.fromEntries((preset === "none" ? ALL_EDGES : preset === "outline" ? OUTLINE : INSIDE).map((k) => [k, null]));
|
|
53
|
+
}
|
|
54
|
+
const line = { style: lineStyle, color };
|
|
55
|
+
return Object.fromEntries((preset === "outline" ? OUTLINE : INSIDE).map((k) => [k, line]));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The `onChangeBorder` calls for the edited edges. A None preset that touched
|
|
59
|
+
* every edge is one `"none"` command (clears diagonals too); everything else
|
|
60
|
+
* is one command per edited edge, removals first so a redraw lands on top.
|
|
61
|
+
*/
|
|
62
|
+
export const toBorderCommands = (edits, options = {}) => {
|
|
63
|
+
const commands = [];
|
|
64
|
+
if (options.clearedAll) {
|
|
65
|
+
commands.push({ location: "none", color: undefined, style: undefined });
|
|
66
|
+
}
|
|
67
|
+
const keys = ALL_EDGES.filter((k) => k in edits);
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
if (edits[key] === null && !options.clearedAll) {
|
|
70
|
+
commands.push({ location: EDGE_LOCATION[key], color: undefined, style: undefined });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
const line = edits[key];
|
|
75
|
+
if (line)
|
|
76
|
+
commands.push({ location: EDGE_LOCATION[key], color: line.color, style: line.style });
|
|
77
|
+
}
|
|
78
|
+
return commands;
|
|
79
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Color, SpreadsheetTheme } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { type EdgeMap, type LineStyleChoice } from "./border-edges";
|
|
4
|
+
export type BorderTabProps = {
|
|
5
|
+
/** The edges as shown: the active cell's, with the user's edits on top. */
|
|
6
|
+
edges: EdgeMap;
|
|
7
|
+
onEdit(edits: EdgeMap, options?: {
|
|
8
|
+
clearedAll?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
lineStyle: LineStyleChoice;
|
|
11
|
+
onLineStyle(style: LineStyleChoice): void;
|
|
12
|
+
color: Color;
|
|
13
|
+
onColor(color: Color): void;
|
|
14
|
+
/** Inside edges exist only for a multi-row / multi-column selection. */
|
|
15
|
+
hasInsideRows: boolean;
|
|
16
|
+
hasInsideColumns: boolean;
|
|
17
|
+
theme: SpreadsheetTheme;
|
|
18
|
+
};
|
|
19
|
+
/** Format Cells → Border. */
|
|
20
|
+
export declare const BorderTab: ({ edges, onEdit, lineStyle, onLineStyle, color, onColor, hasInsideRows, hasInsideColumns, theme, }: BorderTabProps) => React.JSX.Element;
|
|
21
|
+
//# sourceMappingURL=border-tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"border-tab.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/border-tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEzE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAEL,KAAK,OAAO,EAEZ,KAAK,eAAe,EAGrB,MAAM,gBAAgB,CAAC;AA2FxB,MAAM,MAAM,cAAc,GAAG;IAC3B,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,SAAS,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC1C,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,wEAAwE;IACxE,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,6BAA6B;AAC7B,eAAO,MAAM,SAAS,GAAI,oGAUvB,cAAc,sBA+HhB,CAAC"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { BorderColorSelector, getStringifiedColor } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { LINE_STYLES, presetEdges, toggleEdge, } from "./border-edges";
|
|
4
|
+
import { ListBox } from "./list-box";
|
|
5
|
+
const DASH = {
|
|
6
|
+
hair: "1 1",
|
|
7
|
+
dotted: "1 2",
|
|
8
|
+
dashDotDot: "4 2 1 2 1 2",
|
|
9
|
+
dashDot: "4 2 1 2",
|
|
10
|
+
dashed: "3 2",
|
|
11
|
+
mediumDashDotDot: "4 2 1 2 1 2",
|
|
12
|
+
slantDashDot: "4 2 1 2",
|
|
13
|
+
mediumDashDot: "4 2 1 2",
|
|
14
|
+
mediumDashed: "4 2",
|
|
15
|
+
};
|
|
16
|
+
const WIDTH = {
|
|
17
|
+
mediumDashDotDot: 2,
|
|
18
|
+
slantDashDot: 2,
|
|
19
|
+
mediumDashDot: 2,
|
|
20
|
+
mediumDashed: 2,
|
|
21
|
+
solid_medium: 2,
|
|
22
|
+
solid_thick: 3,
|
|
23
|
+
};
|
|
24
|
+
/** One SVG line in a style's dash / weight (double = two thin lines). */
|
|
25
|
+
const StyledLine = ({ x1, y1, x2, y2, style, color, }) => {
|
|
26
|
+
if (style === "none")
|
|
27
|
+
return null;
|
|
28
|
+
if (style === "double") {
|
|
29
|
+
const dx = y2 - y1;
|
|
30
|
+
const dy = x1 - x2;
|
|
31
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
32
|
+
const [ox, oy] = [(dx / len) * 1.5, (dy / len) * 1.5];
|
|
33
|
+
return (React.createElement(React.Fragment, null,
|
|
34
|
+
React.createElement("line", { x1: x1 + ox, y1: y1 + oy, x2: x2 + ox, y2: y2 + oy, stroke: color, strokeWidth: 1 }),
|
|
35
|
+
React.createElement("line", { x1: x1 - ox, y1: y1 - oy, x2: x2 - ox, y2: y2 - oy, stroke: color, strokeWidth: 1 })));
|
|
36
|
+
}
|
|
37
|
+
return (React.createElement("line", { x1: x1, y1: y1, x2: x2, y2: y2, stroke: color, strokeWidth: WIDTH[style] ?? 1, strokeDasharray: DASH[style] }));
|
|
38
|
+
};
|
|
39
|
+
/** Preview geometry (a 2×2 grid when the selection has inside edges). */
|
|
40
|
+
const W = 160;
|
|
41
|
+
const H = 100;
|
|
42
|
+
const EDGE_GEOMETRY = {
|
|
43
|
+
top: [10, 10, W - 10, 10],
|
|
44
|
+
insideHorizontal: [10, H / 2, W - 10, H / 2],
|
|
45
|
+
bottom: [10, H - 10, W - 10, H - 10],
|
|
46
|
+
left: [10, 10, 10, H - 10],
|
|
47
|
+
insideVertical: [W / 2, 10, W / 2, H - 10],
|
|
48
|
+
right: [W - 10, 10, W - 10, H - 10],
|
|
49
|
+
diagonalUp: [10, H - 10, W - 10, 10],
|
|
50
|
+
diagonalDown: [10, 10, W - 10, H - 10],
|
|
51
|
+
};
|
|
52
|
+
const EDGE_BUTTONS = [
|
|
53
|
+
{ key: "top", label: "Top border" },
|
|
54
|
+
{ key: "insideHorizontal", label: "Inside horizontal border" },
|
|
55
|
+
{ key: "bottom", label: "Bottom border" },
|
|
56
|
+
{ key: "diagonalUp", label: "Diagonal up border" },
|
|
57
|
+
{ key: "left", label: "Left border" },
|
|
58
|
+
{ key: "insideVertical", label: "Inside vertical border" },
|
|
59
|
+
{ key: "right", label: "Right border" },
|
|
60
|
+
{ key: "diagonalDown", label: "Diagonal down border" },
|
|
61
|
+
];
|
|
62
|
+
/** Format Cells → Border. */
|
|
63
|
+
export const BorderTab = ({ edges, onEdit, lineStyle, onLineStyle, color, onColor, hasInsideRows, hasInsideColumns, theme, }) => {
|
|
64
|
+
const enabled = (key) => key === "insideHorizontal" ? hasInsideRows : key === "insideVertical" ? hasInsideColumns : true;
|
|
65
|
+
const cssColor = (c) => getStringifiedColor(c, theme) ?? "#000000";
|
|
66
|
+
return (React.createElement("div", { className: "grid grid-cols-[150px_1fr] gap-4" },
|
|
67
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
68
|
+
React.createElement("span", { className: "font-system text-sm" }, "Line style"),
|
|
69
|
+
React.createElement(ListBox, { label: "Line style", value: lineStyle, className: "max-h-[220px]", items: LINE_STYLES.map(({ value, label }) => ({
|
|
70
|
+
value,
|
|
71
|
+
label: (React.createElement("span", { className: "flex items-center gap-1", title: label }, value === "none" ? ("None") : (React.createElement("svg", { width: "90", height: "8", "aria-label": label },
|
|
72
|
+
React.createElement(StyledLine, { x1: 0, y1: 4, x2: 90, y2: 4, style: value, color: "currentColor" }))))),
|
|
73
|
+
})), onChange: onLineStyle }),
|
|
74
|
+
React.createElement("span", { className: "font-system text-sm" }, "Color"),
|
|
75
|
+
React.createElement(BorderColorSelector, { color: color, onChange: onColor, theme: theme })),
|
|
76
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
77
|
+
React.createElement("span", { className: "font-system text-sm" }, "Presets"),
|
|
78
|
+
React.createElement("div", { className: "flex gap-2" }, [
|
|
79
|
+
["none", "None"],
|
|
80
|
+
["outline", "Outline"],
|
|
81
|
+
["inside", "Inside"],
|
|
82
|
+
].map(([preset, label]) => (React.createElement("button", { type: "button", key: preset, className: "rounded-sm border border-solid border-rnc-border px-2 py-1 text-xs", disabled: preset === "inside" && !hasInsideRows && !hasInsideColumns, onClick: () => onEdit(presetEdges(preset, lineStyle, color), {
|
|
83
|
+
clearedAll: preset === "none",
|
|
84
|
+
}) }, label)))),
|
|
85
|
+
React.createElement("span", { className: "font-system text-sm" }, "Border"),
|
|
86
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
87
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-1", role: "group", "aria-label": "Border edges" }, EDGE_BUTTONS.map(({ key, label }) => (React.createElement("button", { type: "button", key: key, "aria-label": label, "aria-pressed": !!edges[key], disabled: !enabled(key), className: `h-7 w-7 rounded-sm border border-solid text-[10px] ${edges[key] ? "border-rnc-primary bg-rnc-muted" : "border-rnc-border"}`, onClick: () => onEdit({ [key]: toggleEdge(edges, key, lineStyle, color) }) },
|
|
88
|
+
React.createElement("svg", { width: "16", height: "16", "aria-hidden": "true" }, (() => {
|
|
89
|
+
const [x1, y1, x2, y2] = EDGE_GEOMETRY[key];
|
|
90
|
+
return (React.createElement("line", { x1: (x1 / W) * 16, y1: (y1 / H) * 16, x2: (x2 / W) * 16, y2: (y2 / H) * 16, stroke: "currentColor" }));
|
|
91
|
+
})()))))),
|
|
92
|
+
React.createElement("svg", { "data-testid": "border-preview", width: W, height: H, className: "rounded-sm border border-solid border-rnc-border bg-rnc-background" },
|
|
93
|
+
React.createElement("text", { x: W / 2, y: H / 2 + 4, textAnchor: "middle", fontSize: "11", fill: "currentColor" }, "Text"),
|
|
94
|
+
Object.keys(EDGE_GEOMETRY).map((key) => {
|
|
95
|
+
const line = edges[key];
|
|
96
|
+
if (!line || !enabled(key))
|
|
97
|
+
return null;
|
|
98
|
+
const [x1, y1, x2, y2] = EDGE_GEOMETRY[key];
|
|
99
|
+
return (React.createElement("g", { key: key, "data-edge": key },
|
|
100
|
+
React.createElement(StyledLine, { x1: x1, y1: y1, x2: x2, y2: y2, style: line.style, color: cssColor(line.color) })));
|
|
101
|
+
}))),
|
|
102
|
+
React.createElement("p", { className: "font-system text-xs text-rnc-muted-foreground" }, "The selected border style can be applied by clicking the presets, preview diagram or the buttons above."))));
|
|
103
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CellFill, SpreadsheetTheme } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
export type FillTabProps = {
|
|
4
|
+
fill: CellFill;
|
|
5
|
+
onChange(fill: CellFill): void;
|
|
6
|
+
theme: SpreadsheetTheme;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Format Cells → Fill: Background Color (No Color, the palette, More
|
|
10
|
+
* Colors…), Fill Effects… (two-colour gradient), Pattern Color and Pattern
|
|
11
|
+
* Style, and a Sample. A solid colour replaces a gradient, as in Excel.
|
|
12
|
+
*/
|
|
13
|
+
export declare const FillTab: ({ fill, onChange, theme }: FillTabProps) => React.JSX.Element;
|
|
14
|
+
//# sourceMappingURL=fill-tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill-tab.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/fill-tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAS5E,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAYF;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAAI,2BAA2B,YAAY,sBA8E9D,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getStringifiedColor, gradientToCss, GradientFillPicker, PatternFillPicker, STANDARD_COLORS, } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import { Button, ColorPicker, HexColorPicker } from "@rowsncolumns/ui";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
/** CSS for the Sample box (gradient, else the solid colour; patterns show their base colour). */
|
|
5
|
+
const sampleStyle = (fill, theme) => fill.gradient?.stops?.length
|
|
6
|
+
? { backgroundImage: gradientToCss(fill.gradient) }
|
|
7
|
+
: {
|
|
8
|
+
backgroundColor: fill.backgroundColor
|
|
9
|
+
? getStringifiedColor(fill.backgroundColor, theme)
|
|
10
|
+
: undefined,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Format Cells → Fill: Background Color (No Color, the palette, More
|
|
14
|
+
* Colors…), Fill Effects… (two-colour gradient), Pattern Color and Pattern
|
|
15
|
+
* Style, and a Sample. A solid colour replaces a gradient, as in Excel.
|
|
16
|
+
*/
|
|
17
|
+
export const FillTab = ({ fill, onChange, theme }) => {
|
|
18
|
+
const [panel, setPanel] = useState("none");
|
|
19
|
+
const solid = fill.backgroundColor
|
|
20
|
+
? getStringifiedColor(fill.backgroundColor, theme)
|
|
21
|
+
: undefined;
|
|
22
|
+
const pickSolid = (color) => onChange({
|
|
23
|
+
...fill,
|
|
24
|
+
backgroundColor: color,
|
|
25
|
+
gradient: undefined,
|
|
26
|
+
});
|
|
27
|
+
return (React.createElement("div", { className: "grid grid-cols-[1fr_200px] gap-4" },
|
|
28
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
29
|
+
React.createElement("span", { className: "font-system text-sm" }, "Background Color"),
|
|
30
|
+
React.createElement(Button, { size: "xs", variant: "outline", className: "justify-center", onClick: () => onChange({
|
|
31
|
+
backgroundColor: undefined,
|
|
32
|
+
fillPattern: undefined,
|
|
33
|
+
foregroundColor: undefined,
|
|
34
|
+
gradient: undefined,
|
|
35
|
+
}) }, "No Color"),
|
|
36
|
+
React.createElement("div", { role: "group", "aria-label": "Background color", className: "flex flex-col gap-1" },
|
|
37
|
+
React.createElement(ColorPicker, { value: solid, onChange: pickSolid, className: "flex-wrap gap-1" }),
|
|
38
|
+
React.createElement(ColorPicker, { value: solid, colors: STANDARD_COLORS, onChange: pickSolid, className: "gap-1" })),
|
|
39
|
+
React.createElement("div", { className: "flex gap-2" },
|
|
40
|
+
React.createElement(Button, { size: "xs", variant: "outline", "aria-expanded": panel === "effects", onClick: () => setPanel(panel === "effects" ? "none" : "effects") }, "Fill Effects..."),
|
|
41
|
+
React.createElement(Button, { size: "xs", variant: "outline", "aria-expanded": panel === "more", onClick: () => setPanel(panel === "more" ? "none" : "more") }, "More Colors...")),
|
|
42
|
+
panel === "more" ? (React.createElement(HexColorPicker, { value: solid ?? "#FFFFFF", onChange: pickSolid })) : null,
|
|
43
|
+
panel === "effects" ? (React.createElement("div", { "data-testid": "fill-effects" },
|
|
44
|
+
React.createElement(GradientFillPicker, { fill: fill, theme: theme, onChangeFill: onChange }))) : null),
|
|
45
|
+
React.createElement("div", { className: "flex flex-col gap-2" },
|
|
46
|
+
React.createElement(PatternFillPicker, { fill: fill, theme: theme, onChangeFill: onChange }),
|
|
47
|
+
React.createElement("span", { className: "font-system text-sm" }, "Sample"),
|
|
48
|
+
React.createElement("div", { "data-testid": "fill-sample", className: "h-10 rounded-sm border border-solid border-rnc-border", style: sampleStyle(fill, theme) }))));
|
|
49
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SpreadsheetTheme, TextFormat, UnderlineStyle } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
type UnderlineChoice = "none" | UnderlineStyle;
|
|
4
|
+
/** Excel's Underline list. */
|
|
5
|
+
export declare const UNDERLINE_OPTIONS: {
|
|
6
|
+
value: UnderlineChoice;
|
|
7
|
+
label: string;
|
|
8
|
+
}[];
|
|
9
|
+
export type FontTabProps = {
|
|
10
|
+
textFormat: TextFormat;
|
|
11
|
+
onChange(patch: Partial<TextFormat>): void;
|
|
12
|
+
theme: SpreadsheetTheme;
|
|
13
|
+
isDarkMode?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/** Format Cells → Font. */
|
|
16
|
+
export declare const FontTab: ({ textFormat, onChange, theme, isDarkMode, }: FontTabProps) => React.JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=font-tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-tab.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/font-tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EACV,cAAc,EACf,MAAM,2BAA2B,CAAC;AAOnC,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,KAAK,eAAe,GAAG,MAAM,GAAG,cAAc,CAAC;AAS/C,8BAA8B;AAC9B,eAAO,MAAM,iBAAiB,EAAE;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAMxE,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAoCF,2BAA2B;AAC3B,eAAO,MAAM,OAAO,GAAI,8CAKrB,YAAY,sBA2Gd,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { FontFamilySelector, FontSizeSelector, getStringifiedColor, TextColorSelector, } from "@rowsncolumns/spreadsheet";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { CheckboxField, ListBox, SelectField } from "./list-box";
|
|
4
|
+
const FONT_STYLES = [
|
|
5
|
+
{ value: "regular", label: "Regular" },
|
|
6
|
+
{ value: "italic", label: "Italic" },
|
|
7
|
+
{ value: "bold", label: "Bold" },
|
|
8
|
+
{ value: "boldItalic", label: "Bold Italic" },
|
|
9
|
+
];
|
|
10
|
+
/** Excel's Underline list. */
|
|
11
|
+
export const UNDERLINE_OPTIONS = [
|
|
12
|
+
{ value: "none", label: "None" },
|
|
13
|
+
{ value: "single", label: "Single" },
|
|
14
|
+
{ value: "double", label: "Double" },
|
|
15
|
+
{ value: "singleAccounting", label: "Single Accounting" },
|
|
16
|
+
{ value: "doubleAccounting", label: "Double Accounting" },
|
|
17
|
+
];
|
|
18
|
+
/** CSS for the Preview box: the double / accounting underlines as CSS decorations. */
|
|
19
|
+
const previewStyle = (tf, theme) => {
|
|
20
|
+
const style = tf.underline ? tf.underlineStyle ?? "single" : undefined;
|
|
21
|
+
const lines = [
|
|
22
|
+
style ? "underline" : "",
|
|
23
|
+
tf.strikethrough ? "line-through" : "",
|
|
24
|
+
]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(" ");
|
|
27
|
+
return {
|
|
28
|
+
fontFamily: tf.fontFamily ?? undefined,
|
|
29
|
+
fontSize: tf.fontSize ? `${tf.fontSize}pt` : undefined,
|
|
30
|
+
fontWeight: tf.bold ? "bold" : undefined,
|
|
31
|
+
fontStyle: tf.italic ? "italic" : undefined,
|
|
32
|
+
color: tf.color ? getStringifiedColor(tf.color, theme) : undefined,
|
|
33
|
+
textDecorationLine: lines || undefined,
|
|
34
|
+
textDecorationStyle: style === "double" || style === "doubleAccounting" ? "double" : undefined,
|
|
35
|
+
textUnderlineOffset: style === "singleAccounting" || style === "doubleAccounting"
|
|
36
|
+
? "0.3em"
|
|
37
|
+
: undefined,
|
|
38
|
+
verticalAlign: tf.baselineOffset === "superscript"
|
|
39
|
+
? "super"
|
|
40
|
+
: tf.baselineOffset === "subscript"
|
|
41
|
+
? "sub"
|
|
42
|
+
: undefined,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** Format Cells → Font. */
|
|
46
|
+
export const FontTab = ({ textFormat, onChange, theme, isDarkMode, }) => {
|
|
47
|
+
const fontStyle = textFormat.bold && textFormat.italic
|
|
48
|
+
? "boldItalic"
|
|
49
|
+
: textFormat.bold
|
|
50
|
+
? "bold"
|
|
51
|
+
: textFormat.italic
|
|
52
|
+
? "italic"
|
|
53
|
+
: "regular";
|
|
54
|
+
const underline = textFormat.underline
|
|
55
|
+
? textFormat.underlineStyle ?? "single"
|
|
56
|
+
: "none";
|
|
57
|
+
return (React.createElement("div", { className: "flex flex-col gap-3" },
|
|
58
|
+
React.createElement("div", { className: "grid grid-cols-[1fr_120px_80px] gap-2" },
|
|
59
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
60
|
+
React.createElement("span", { className: "font-system text-sm" }, "Font"),
|
|
61
|
+
React.createElement(FontFamilySelector, { theme: theme, value: textFormat.fontFamily, onChange: (fontFamily) => onChange({ fontFamily }), triggerVariant: "outline" })),
|
|
62
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
63
|
+
React.createElement("span", { className: "font-system text-sm" }, "Font style"),
|
|
64
|
+
React.createElement(ListBox, { label: "Font style", value: fontStyle, items: FONT_STYLES, onChange: (value) => onChange({
|
|
65
|
+
bold: value === "bold" || value === "boldItalic",
|
|
66
|
+
italic: value === "italic" || value === "boldItalic",
|
|
67
|
+
}) })),
|
|
68
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
69
|
+
React.createElement("span", { className: "font-system text-sm" }, "Size"),
|
|
70
|
+
React.createElement(FontSizeSelector, { value: textFormat.fontSize, onChange: (fontSize) => onChange({ fontSize }) }))),
|
|
71
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-2" },
|
|
72
|
+
React.createElement(SelectField, { label: "Underline", value: underline, options: UNDERLINE_OPTIONS, onChange: (value) => onChange(value === "none"
|
|
73
|
+
? { underline: false }
|
|
74
|
+
: { underline: true, underlineStyle: value }) }),
|
|
75
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
76
|
+
React.createElement("span", { className: "font-system text-sm" }, "Color"),
|
|
77
|
+
React.createElement(TextColorSelector, { color: textFormat.color, theme: theme, isDarkMode: isDarkMode, onChange: (color) => onChange({ color }) }))),
|
|
78
|
+
React.createElement("div", { className: "grid grid-cols-2 gap-2" },
|
|
79
|
+
React.createElement("fieldset", { className: "flex flex-col gap-1.5 border-0 p-0" },
|
|
80
|
+
React.createElement("legend", { className: "pb-1 font-system text-sm" }, "Effects"),
|
|
81
|
+
React.createElement(CheckboxField, { label: "Strikethrough", checked: !!textFormat.strikethrough, onChange: (strikethrough) => onChange({ strikethrough }) }),
|
|
82
|
+
React.createElement(CheckboxField, { label: "Superscript", checked: textFormat.baselineOffset === "superscript", onChange: (on) => onChange({ baselineOffset: on ? "superscript" : null }) }),
|
|
83
|
+
React.createElement(CheckboxField, { label: "Subscript", checked: textFormat.baselineOffset === "subscript", onChange: (on) => onChange({ baselineOffset: on ? "subscript" : null }) })),
|
|
84
|
+
React.createElement("div", { className: "flex flex-col gap-1" },
|
|
85
|
+
React.createElement("span", { className: "font-system text-sm" }, "Preview"),
|
|
86
|
+
React.createElement("div", { "data-testid": "font-preview", className: "flex h-14 items-center justify-center overflow-hidden rounded-sm border border-solid border-rnc-border bg-rnc-background" },
|
|
87
|
+
React.createElement("span", { style: previewStyle(textFormat, theme) }, "AaBbCcYyZz"))))));
|
|
88
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type ListBoxItem<T extends string> = {
|
|
3
|
+
value: T;
|
|
4
|
+
label: React.ReactNode;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Excel's always-open selection lists (Category, Type, Font style, Line
|
|
8
|
+
* style …): a listbox with arrow-key navigation, not a dropdown.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ListBox: <T extends string>({ label, items, value, onChange, className, }: {
|
|
11
|
+
label: string;
|
|
12
|
+
items: ListBoxItem<T>[];
|
|
13
|
+
value: T | undefined;
|
|
14
|
+
onChange(value: T): void;
|
|
15
|
+
className?: string;
|
|
16
|
+
}) => React.JSX.Element;
|
|
17
|
+
/** A labelled native checkbox (Excel's Text control / Effects / Protection boxes). */
|
|
18
|
+
export declare const CheckboxField: ({ label, checked, disabled, onChange, }: {
|
|
19
|
+
label: string;
|
|
20
|
+
checked: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
onChange(checked: boolean): void;
|
|
23
|
+
}) => React.JSX.Element;
|
|
24
|
+
/** A labelled native select for the short option lists. */
|
|
25
|
+
export declare const SelectField: <T extends string>({ label, value, options, onChange, disabled, }: {
|
|
26
|
+
label: string;
|
|
27
|
+
value: T;
|
|
28
|
+
options: {
|
|
29
|
+
value: T;
|
|
30
|
+
label: string;
|
|
31
|
+
}[];
|
|
32
|
+
onChange(value: T): void;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
}) => React.JSX.Element;
|
|
35
|
+
//# sourceMappingURL=list-box.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-box.d.ts","sourceRoot":"","sources":["../../../../../components/editors/cell-format/list-box.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,EAAE,+CAMvC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,sBAoCA,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,aAAa,GAAI,yCAK3B;IACD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC,sBAcA,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,EAAE,gDAM3C;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,sBAiBA,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Excel's always-open selection lists (Category, Type, Font style, Line
|
|
4
|
+
* style …): a listbox with arrow-key navigation, not a dropdown.
|
|
5
|
+
*/
|
|
6
|
+
export const ListBox = ({ label, items, value, onChange, className = "", }) => {
|
|
7
|
+
const index = items.findIndex((item) => item.value === value);
|
|
8
|
+
return (React.createElement("div", { role: "listbox", "aria-label": label, tabIndex: 0, className: `max-h-[180px] overflow-auto rounded-sm border border-solid border-rnc-border text-xs outline-none focus-visible:ring-1 focus-visible:ring-rnc-primary ${className}`, onKeyDown: (event) => {
|
|
9
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp")
|
|
10
|
+
return;
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
13
|
+
const next = items[Math.max(0, Math.min(items.length - 1, index + step))];
|
|
14
|
+
if (next)
|
|
15
|
+
onChange(next.value);
|
|
16
|
+
} }, items.map((item) => {
|
|
17
|
+
const selected = item.value === value;
|
|
18
|
+
return (React.createElement("div", { key: item.value, role: "option", "aria-selected": selected, className: `cursor-default px-1.5 py-0.5 ${selected
|
|
19
|
+
? "bg-rnc-primary text-rnc-primary-foreground"
|
|
20
|
+
: "hover:bg-rnc-muted"}`, onClick: () => onChange(item.value) }, item.label));
|
|
21
|
+
})));
|
|
22
|
+
};
|
|
23
|
+
/** A labelled native checkbox (Excel's Text control / Effects / Protection boxes). */
|
|
24
|
+
export const CheckboxField = ({ label, checked, disabled, onChange, }) => (React.createElement("label", { className: `flex items-center gap-1.5 font-system text-xs ${disabled ? "opacity-50" : ""}` },
|
|
25
|
+
React.createElement("input", { type: "checkbox", checked: checked, disabled: disabled, onChange: (event) => onChange(event.target.checked) }),
|
|
26
|
+
label));
|
|
27
|
+
/** A labelled native select for the short option lists. */
|
|
28
|
+
export const SelectField = ({ label, value, options, onChange, disabled, }) => (React.createElement("label", { className: "flex flex-col gap-1 font-system text-sm" },
|
|
29
|
+
label,
|
|
30
|
+
React.createElement("select", { "aria-label": label, className: "rounded-sm border border-solid border-rnc-border bg-rnc-background px-1 py-1 text-xs text-rnc-foreground", value: value, disabled: disabled, onChange: (event) => onChange(event.target.value) }, options.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label))))));
|