@rowsncolumns/spreadsheet 10.4.1 → 10.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/cjs/atoms/sheet-context.atom.d.ts +2 -0
  2. package/dist/cjs/atoms/sheet-context.atom.d.ts.map +1 -1
  3. package/dist/cjs/atoms/sheet-context.atom.js +8 -2
  4. package/dist/cjs/atoms/sort-dialog.atom.d.ts +6 -2
  5. package/dist/cjs/atoms/sort-dialog.atom.d.ts.map +1 -1
  6. package/dist/cjs/components/canvas-grid/index.d.ts.map +1 -1
  7. package/dist/cjs/components/canvas-grid/index.js +4 -2
  8. package/dist/cjs/components/canvas-grid/typings.d.ts +10 -0
  9. package/dist/cjs/components/canvas-grid/typings.d.ts.map +1 -1
  10. package/dist/cjs/components/filter-box/date-tree.d.ts +23 -5
  11. package/dist/cjs/components/filter-box/date-tree.d.ts.map +1 -1
  12. package/dist/cjs/components/filter-box/date-tree.js +76 -11
  13. package/dist/cjs/components/filter-box/filter-by-condition.d.ts +26 -1
  14. package/dist/cjs/components/filter-box/filter-by-condition.d.ts.map +1 -1
  15. package/dist/cjs/components/filter-box/filter-by-condition.js +108 -9
  16. package/dist/cjs/components/filter-box/index.d.ts.map +1 -1
  17. package/dist/cjs/components/filter-box/index.js +21 -5
  18. package/dist/cjs/components/formula-input/index.d.ts.map +1 -1
  19. package/dist/cjs/components/formula-input/index.js +26 -4
  20. package/dist/cjs/components/sheet-tabs/index.d.ts.map +1 -1
  21. package/dist/cjs/components/sheet-tabs/index.js +19 -5
  22. package/dist/cjs/components/sort-range-dialog/index.d.ts +20 -3
  23. package/dist/cjs/components/sort-range-dialog/index.d.ts.map +1 -1
  24. package/dist/cjs/components/sort-range-dialog/index.js +20 -6
  25. package/dist/cjs/components/toolbar/cell-style-selector.d.ts +9 -0
  26. package/dist/cjs/components/toolbar/cell-style-selector.d.ts.map +1 -1
  27. package/dist/cjs/components/toolbar/cell-style-selector.js +12 -3
  28. package/dist/cjs/components/toolbar/merge-cells-selector.d.ts +27 -1
  29. package/dist/cjs/components/toolbar/merge-cells-selector.d.ts.map +1 -1
  30. package/dist/cjs/components/toolbar/merge-cells-selector.js +106 -8
  31. package/dist/cjs/components/toolbar/table-style-editor.d.ts +85 -0
  32. package/dist/cjs/components/toolbar/table-style-editor.d.ts.map +1 -0
  33. package/dist/cjs/components/toolbar/table-style-editor.js +235 -0
  34. package/dist/cjs/components/toolbar/table-style-selector.d.ts +7 -7
  35. package/dist/cjs/components/toolbar/table-style-selector.d.ts.map +1 -1
  36. package/dist/cjs/components/toolbar/table-style-selector.js +77 -87
  37. package/dist/cjs/config.d.ts +6 -0
  38. package/dist/cjs/config.d.ts.map +1 -1
  39. package/dist/cjs/config.js +7 -1
  40. package/dist/cjs/hooks/use-conditional-formatting.d.ts +8 -0
  41. package/dist/cjs/hooks/use-conditional-formatting.d.ts.map +1 -1
  42. package/dist/cjs/hooks/use-conditional-formatting.js +128 -77
  43. package/dist/cjs/hooks/use-editable.d.ts.map +1 -1
  44. package/dist/cjs/hooks/use-editable.js +59 -1
  45. package/dist/cjs/hooks/use-selection.d.ts +6 -1
  46. package/dist/cjs/hooks/use-selection.d.ts.map +1 -1
  47. package/dist/cjs/hooks/use-selection.js +37 -1
  48. package/dist/cjs/release-date.js +1 -1
  49. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  50. package/dist/cjs/types.d.ts +64 -0
  51. package/dist/cjs/types.d.ts.map +1 -1
  52. package/dist/esm/atoms/sheet-context.atom.d.ts +2 -0
  53. package/dist/esm/atoms/sheet-context.atom.d.ts.map +1 -1
  54. package/dist/esm/atoms/sheet-context.atom.js +3 -0
  55. package/dist/esm/atoms/sort-dialog.atom.d.ts +6 -2
  56. package/dist/esm/atoms/sort-dialog.atom.d.ts.map +1 -1
  57. package/dist/esm/components/canvas-grid/index.d.ts.map +1 -1
  58. package/dist/esm/components/canvas-grid/index.js +3 -1
  59. package/dist/esm/components/canvas-grid/typings.d.ts +10 -0
  60. package/dist/esm/components/canvas-grid/typings.d.ts.map +1 -1
  61. package/dist/esm/components/filter-box/date-tree.d.ts +23 -5
  62. package/dist/esm/components/filter-box/date-tree.d.ts.map +1 -1
  63. package/dist/esm/components/filter-box/date-tree.js +62 -10
  64. package/dist/esm/components/filter-box/filter-by-condition.d.ts +26 -1
  65. package/dist/esm/components/filter-box/filter-by-condition.d.ts.map +1 -1
  66. package/dist/esm/components/filter-box/filter-by-condition.js +78 -12
  67. package/dist/esm/components/filter-box/index.d.ts.map +1 -1
  68. package/dist/esm/components/filter-box/index.js +21 -5
  69. package/dist/esm/components/formula-input/index.d.ts.map +1 -1
  70. package/dist/esm/components/formula-input/index.js +26 -4
  71. package/dist/esm/components/sheet-tabs/index.d.ts.map +1 -1
  72. package/dist/esm/components/sheet-tabs/index.js +20 -6
  73. package/dist/esm/components/sort-range-dialog/index.d.ts +20 -3
  74. package/dist/esm/components/sort-range-dialog/index.d.ts.map +1 -1
  75. package/dist/esm/components/sort-range-dialog/index.js +17 -5
  76. package/dist/esm/components/toolbar/cell-style-selector.d.ts +9 -0
  77. package/dist/esm/components/toolbar/cell-style-selector.d.ts.map +1 -1
  78. package/dist/esm/components/toolbar/cell-style-selector.js +11 -2
  79. package/dist/esm/components/toolbar/merge-cells-selector.d.ts +27 -1
  80. package/dist/esm/components/toolbar/merge-cells-selector.d.ts.map +1 -1
  81. package/dist/esm/components/toolbar/merge-cells-selector.js +94 -10
  82. package/dist/esm/components/toolbar/table-style-editor.d.ts +85 -0
  83. package/dist/esm/components/toolbar/table-style-editor.d.ts.map +1 -0
  84. package/dist/esm/components/toolbar/table-style-editor.js +189 -0
  85. package/dist/esm/components/toolbar/table-style-selector.d.ts +7 -7
  86. package/dist/esm/components/toolbar/table-style-selector.d.ts.map +1 -1
  87. package/dist/esm/components/toolbar/table-style-selector.js +74 -81
  88. package/dist/esm/config.d.ts +6 -0
  89. package/dist/esm/config.d.ts.map +1 -1
  90. package/dist/esm/config.js +6 -0
  91. package/dist/esm/hooks/use-conditional-formatting.d.ts +8 -0
  92. package/dist/esm/hooks/use-conditional-formatting.d.ts.map +1 -1
  93. package/dist/esm/hooks/use-conditional-formatting.js +50 -13
  94. package/dist/esm/hooks/use-editable.d.ts.map +1 -1
  95. package/dist/esm/hooks/use-editable.js +48 -2
  96. package/dist/esm/hooks/use-selection.d.ts +6 -1
  97. package/dist/esm/hooks/use-selection.d.ts.map +1 -1
  98. package/dist/esm/hooks/use-selection.js +37 -1
  99. package/dist/esm/release-date.js +1 -1
  100. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  101. package/dist/esm/types.d.ts +64 -0
  102. package/dist/esm/types.d.ts.map +1 -1
  103. package/dist/spreadsheet.min.css +1 -1
  104. package/package.json +5 -5
@@ -1,12 +1,82 @@
1
1
  import { areaIntersects, Direction, isEqualCells } from "@rowsncolumns/grid";
2
- import { AiOutlineMergeCells, MdOutlineArrowDropDown, } from "@rowsncolumns/icons";
3
- import { DropdownMenuPortal, IconButton, Stack } from "@rowsncolumns/ui";
2
+ import { AiOutlineMergeCells, Cross2Icon, MdOutlineArrowDropDown, } from "@rowsncolumns/icons";
3
+ import { Button, Dialog, DialogBody, DialogClose, DialogCloseButton, DialogContent, DialogDescription, DialogTitle, DropdownMenuPortal, IconButton, Stack, } from "@rowsncolumns/ui";
4
4
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@rowsncolumns/ui";
5
- import React from "react";
6
- import { useFocusSheet } from "../../atoms";
5
+ import React, { useState } from "react";
6
+ import { useFocusSheet, useGetFormattedValue } from "../../atoms";
7
7
  import { DEFAULT_ARRAY } from "../../config";
8
- export const MergeCellsSelector = ({ activeCell, selections = DEFAULT_ARRAY, sheetId, merges, tables, onMerge, onUnMerge, }) => {
8
+ /** Excel's wording for a merge that would drop cell values. */
9
+ export const MERGE_DISCARDS_VALUES_TITLE = "Merge cells";
10
+ export const MERGE_DISCARDS_VALUES_MESSAGE = "Merging cells only keeps the upper-left value and discards other values.";
11
+ /**
12
+ * The regions `onMergeCells` merges for a toolbar request: each row (merge
13
+ * horizontally), each column (merge vertically), every area of a multi-area
14
+ * selection, or the last selection as one block — the store action's own rules.
15
+ */
16
+ export const mergeRegionsForSelection = (selections, direction) => {
17
+ if (!selections.length)
18
+ return [];
19
+ const { range } = selections[selections.length - 1];
20
+ if (direction === Direction.Right) {
21
+ const rows = [];
22
+ for (let r = range.startRowIndex; r <= range.endRowIndex; r++) {
23
+ rows.push({ ...range, startRowIndex: r, endRowIndex: r });
24
+ }
25
+ return rows;
26
+ }
27
+ if (direction === Direction.Down) {
28
+ const cols = [];
29
+ for (let c = range.startColumnIndex; c <= range.endColumnIndex; c++) {
30
+ cols.push({ ...range, startColumnIndex: c, endColumnIndex: c });
31
+ }
32
+ return cols;
33
+ }
34
+ if (selections.length > 1)
35
+ return selections.map((sel) => sel.range);
36
+ return [range];
37
+ };
38
+ /**
39
+ * Would this merge discard values? Excel warns when more than one cell of a
40
+ * merged block holds a value (the merge keeps only the upper-left one). A
41
+ * headless / programmatic merge never asks — this gate lives in the toolbar.
42
+ */
43
+ export const mergeDiscardsValues = ({ sheetId, selections, direction, getFormattedValue, }) => {
44
+ for (const region of mergeRegionsForSelection(selections, direction)) {
45
+ let filled = 0;
46
+ for (let r = region.startRowIndex; r <= region.endRowIndex; r++) {
47
+ for (let c = region.startColumnIndex; c <= region.endColumnIndex; c++) {
48
+ const value = getFormattedValue(sheetId, r, c);
49
+ if (value !== null && value !== undefined && value !== "") {
50
+ filled++;
51
+ if (filled > 1)
52
+ return true;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ return false;
58
+ };
59
+ export const MergeCellsSelector = ({ activeCell, selections = DEFAULT_ARRAY, sheetId, merges, tables, onMerge, onUnMerge, getFormattedValue: getFormattedValueProp, }) => {
9
60
  const focusSheet = useFocusSheet();
61
+ const gridGetFormattedValue = useGetFormattedValue();
62
+ const getFormattedValue = getFormattedValueProp ?? gridGetFormattedValue;
63
+ // A merge waiting on the "discards other values" confirmation.
64
+ const [pendingMerge, setPendingMerge] = useState(null);
65
+ const requestMerge = (direction) => {
66
+ if (getFormattedValue &&
67
+ mergeDiscardsValues({ sheetId, selections, direction, getFormattedValue })) {
68
+ setPendingMerge({ direction });
69
+ return;
70
+ }
71
+ onMerge(sheetId, activeCell, selections, direction);
72
+ };
73
+ const confirmPendingMerge = () => {
74
+ if (pendingMerge) {
75
+ onMerge(sheetId, activeCell, selections, pendingMerge.direction);
76
+ }
77
+ setPendingMerge(null);
78
+ focusSheet?.();
79
+ };
10
80
  const isMerged = !!merges?.find((merge) => isEqualCells({ rowIndex: merge.startRowIndex, columnIndex: merge.startColumnIndex }, activeCell));
11
81
  const selection = selections[selections.length - 1];
12
82
  // Excel greys the merge controls out for a selection that touches a table.
@@ -23,7 +93,7 @@ export const MergeCellsSelector = ({ activeCell, selections = DEFAULT_ARRAY, she
23
93
  onUnMerge(sheetId, activeCell, selections);
24
94
  }
25
95
  else {
26
- onMerge(sheetId, activeCell, selections);
96
+ requestMerge();
27
97
  }
28
98
  }, isActive: isMerged, tooltip: isMerged ? "Unmerge All" : "Merge all", disabled: disableMenu },
29
99
  React.createElement(AiOutlineMergeCells, null)),
@@ -34,19 +104,33 @@ export const MergeCellsSelector = ({ activeCell, selections = DEFAULT_ARRAY, she
34
104
  React.createElement(DropdownMenuPortal, null,
35
105
  React.createElement(DropdownMenuContent, { align: "end" },
36
106
  React.createElement(DropdownMenuItem, { disabled: isMerged || selectionTouchesTable, onClick: () => {
37
- onMerge(sheetId, activeCell, selections);
107
+ requestMerge();
38
108
  focusSheet?.();
39
109
  } }, "Merge all"),
40
110
  React.createElement(DropdownMenuItem, { disabled: isMerged || selectionTouchesTable || !canMergeVertically, onClick: () => {
41
- onMerge(sheetId, activeCell, selections, Direction.Down);
111
+ requestMerge(Direction.Down);
42
112
  focusSheet?.();
43
113
  } }, "Merge vertically"),
44
114
  React.createElement(DropdownMenuItem, { onClick: () => {
45
- onMerge(sheetId, activeCell, selections, Direction.Right);
115
+ requestMerge(Direction.Right);
46
116
  focusSheet?.();
47
117
  }, disabled: isMerged || selectionTouchesTable || !canMergeHorizontally }, "Merge horizontally"),
48
118
  React.createElement(DropdownMenuItem, { disabled: !isMerged, onClick: () => {
49
119
  onUnMerge(sheetId, activeCell, selections);
50
120
  focusSheet?.();
51
- } }, "Unmerge"))))));
121
+ } }, "Unmerge")))),
122
+ React.createElement(Dialog, { modal: true, open: pendingMerge !== null, onOpenChange: (open) => {
123
+ if (!open)
124
+ setPendingMerge(null);
125
+ } },
126
+ React.createElement(DialogContent, null,
127
+ React.createElement(DialogTitle, null, MERGE_DISCARDS_VALUES_TITLE),
128
+ React.createElement(DialogDescription, null, MERGE_DISCARDS_VALUES_MESSAGE),
129
+ React.createElement(DialogBody, null,
130
+ React.createElement(Stack, { className: "gap-2" },
131
+ React.createElement(Button, { variant: "primary", onClick: confirmPendingMerge }, "Continue"),
132
+ React.createElement(Button, { onClick: () => setPendingMerge(null) }, "Cancel"))),
133
+ React.createElement(DialogClose, { asChild: true },
134
+ React.createElement(DialogCloseButton, null,
135
+ React.createElement(Cross2Icon, null)))))));
52
136
  };
@@ -0,0 +1,85 @@
1
+ import React from "react";
2
+ import type { BandingProperties, CustomTableStyle, Maybe } from "../../types";
3
+ import type { SpreadsheetTheme } from "../canvas-grid/typings";
4
+ /**
5
+ * The style elements a custom table style can colour — the `rowProperties` fields the grid's
6
+ * banding resolves (`api/banded-ranges.ts`), in the order of Excel's New Table Style dialog.
7
+ * The first three are what the gallery swatches show and what every created style carries; the
8
+ * rest are only written when the user picks a colour for them.
9
+ */
10
+ export declare const TABLE_STYLE_ELEMENTS: readonly [{
11
+ readonly key: "header";
12
+ readonly label: "Header row";
13
+ readonly property: "headerColor";
14
+ }, {
15
+ readonly key: "firstBand";
16
+ readonly label: "First row stripe";
17
+ readonly property: "firstBandColor";
18
+ }, {
19
+ readonly key: "secondBand";
20
+ readonly label: "Second row stripe";
21
+ readonly property: "secondBandColor";
22
+ }, {
23
+ readonly key: "firstColumn";
24
+ readonly label: "First column";
25
+ readonly property: "firstColumnColor";
26
+ }, {
27
+ readonly key: "lastColumn";
28
+ readonly label: "Last column";
29
+ readonly property: "lastColumnColor";
30
+ }, {
31
+ readonly key: "totalRow";
32
+ readonly label: "Total row";
33
+ readonly property: "totalRowColor";
34
+ }];
35
+ export type TableStyleElementKey = (typeof TABLE_STYLE_ELEMENTS)[number]["key"];
36
+ /** The colour picked for each element; `undefined` leaves the element unpainted. */
37
+ export type TableStyleColors = Partial<Record<TableStyleElementKey, string>>;
38
+ /** The colours a fresh style starts from (the default palette's accent 1 and its light tint). */
39
+ export declare const DEFAULT_TABLE_STYLE_COLORS: TableStyleColors;
40
+ /**
41
+ * The banding a custom table style paints from its element colours. Header and the two stripes
42
+ * are always present (unset ones stay unpainted); the optional elements are written only when
43
+ * coloured, so a style that only picks the first three keeps its historical shape.
44
+ */
45
+ export declare const buildCustomTableStyle: (name: string, colors: TableStyleColors) => CustomTableStyle;
46
+ /** The element colours of an existing custom style (theme colours resolved to hex). */
47
+ export declare const getTableStyleColors: (style: CustomTableStyle, theme: SpreadsheetTheme) => TableStyleColors;
48
+ /**
49
+ * Why a style name cannot be used, or `undefined` when it can: empty, a built-in style's name
50
+ * (the gallery tells the two apart by name), or another custom style's name (case-insensitive,
51
+ * like Excel). `currentName` is the style being edited, which may keep its own name.
52
+ */
53
+ export declare const getTableStyleNameError: (name: string, existingNames: readonly string[], currentName?: string) => string | undefined;
54
+ export type TableStyleEditorProps = {
55
+ /** The custom style being modified; omit to create a new one. */
56
+ style?: CustomTableStyle;
57
+ /** The name a new style starts with (ignored when `style` is given). */
58
+ defaultName?: string;
59
+ theme: SpreadsheetTheme;
60
+ /** Names of the workbook's custom styles, for the duplicate-name check. */
61
+ existingNames?: readonly string[];
62
+ /** Create (or, when `style` is given, save) — receives the style the picks produce. */
63
+ onSubmit(style: CustomTableStyle): void;
64
+ onCancel(): void;
65
+ /** Offered as "Delete style" while editing; the footer omits it when absent. */
66
+ onDelete?(style: CustomTableStyle): void;
67
+ className?: string;
68
+ };
69
+ /**
70
+ * Excel's New Table Style / Modify Table Style dialog, laid out for the Format as Table gallery:
71
+ * name row, the table elements with a colour button each (the toolbar's colour popover), a live
72
+ * preview of the table the picks produce, and an Excel-ordered footer.
73
+ */
74
+ export declare const TableStyleEditor: ({ style, defaultName, theme, existingNames, onSubmit, onCancel, onDelete, className, }: TableStyleEditorProps) => React.JSX.Element;
75
+ /**
76
+ * The table a style paints: a header row over four banded rows, with the first / last column and
77
+ * total row emphasis when the style colours them. Uses the grid's banding rules
78
+ * (`api/banded-ranges.ts`): the first body row is the first stripe, the last body row is the
79
+ * total row, and an edge column's colour outranks the stripe under it.
80
+ */
81
+ export declare const TableStylePreviewCard: ({ rowProperties, theme, }: {
82
+ rowProperties: Maybe<BandingProperties>;
83
+ theme: SpreadsheetTheme;
84
+ }) => React.JSX.Element;
85
+ //# sourceMappingURL=table-style-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-style-editor.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/table-style-editor.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAmC,MAAM,OAAO,CAAC;AAIxD,OAAO,KAAK,EACV,iBAAiB,EAEjB,gBAAgB,EAChB,KAAK,EACN,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EAW/B,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;AAEhF,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7E,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,EAAE,gBAIxC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,MAAM,EACZ,QAAQ,gBAAgB,KACvB,gBAUF,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,gBAAgB,EACvB,OAAO,gBAAgB,KACtB,gBAWF,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,MAAM,MAAM,EACZ,eAAe,SAAS,MAAM,EAAE,EAChC,cAAc,MAAM,KACnB,MAAM,GAAG,SAaX,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,iEAAiE;IACjE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,gBAAgB,CAAC;IACxB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACxC,QAAQ,IAAI,IAAI,CAAC;IACjB,gFAAgF;IAChF,QAAQ,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,wFAS9B,qBAAqB,sBAuIvB,CAAC;AAkEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,2BAGnC;IACD,aAAa,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACxC,KAAK,EAAE,gBAAgB,CAAC;CACzB,sBA2EA,CAAC"}
@@ -0,0 +1,189 @@
1
+ import { ChevronDownIcon } from "@rowsncolumns/icons";
2
+ import { Button, cn, Input, Popover, PopoverContent, PopoverTrigger, Separator, } from "@rowsncolumns/ui";
3
+ import { getContrastColor } from "@rowsncolumns/utils";
4
+ import React, { useId, useMemo, useState } from "react";
5
+ import { parseTintedColor } from "../../api/banded-ranges";
6
+ import { getStringifiedColor } from "../../themes";
7
+ import { ColorSelector } from "./color-selector";
8
+ /**
9
+ * The style elements a custom table style can colour — the `rowProperties` fields the grid's
10
+ * banding resolves (`api/banded-ranges.ts`), in the order of Excel's New Table Style dialog.
11
+ * The first three are what the gallery swatches show and what every created style carries; the
12
+ * rest are only written when the user picks a colour for them.
13
+ */
14
+ export const TABLE_STYLE_ELEMENTS = [
15
+ { key: "header", label: "Header row", property: "headerColor" },
16
+ { key: "firstBand", label: "First row stripe", property: "firstBandColor" },
17
+ { key: "secondBand", label: "Second row stripe", property: "secondBandColor" },
18
+ { key: "firstColumn", label: "First column", property: "firstColumnColor" },
19
+ { key: "lastColumn", label: "Last column", property: "lastColumnColor" },
20
+ { key: "totalRow", label: "Total row", property: "totalRowColor" },
21
+ ];
22
+ /** The colours a fresh style starts from (the default palette's accent 1 and its light tint). */
23
+ export const DEFAULT_TABLE_STYLE_COLORS = {
24
+ header: "#4472c4",
25
+ firstBand: "#b4c6e7",
26
+ secondBand: "#ffffff",
27
+ };
28
+ /**
29
+ * The banding a custom table style paints from its element colours. Header and the two stripes
30
+ * are always present (unset ones stay unpainted); the optional elements are written only when
31
+ * coloured, so a style that only picks the first three keeps its historical shape.
32
+ */
33
+ export const buildCustomTableStyle = (name, colors) => {
34
+ const rowProperties = {
35
+ headerColor: colors.header,
36
+ firstBandColor: colors.firstBand,
37
+ secondBandColor: colors.secondBand,
38
+ };
39
+ if (colors.firstColumn)
40
+ rowProperties.firstColumnColor = colors.firstColumn;
41
+ if (colors.lastColumn)
42
+ rowProperties.lastColumnColor = colors.lastColumn;
43
+ if (colors.totalRow)
44
+ rowProperties.totalRowColor = colors.totalRow;
45
+ return { name, bandedRange: { rowProperties } };
46
+ };
47
+ /** The element colours of an existing custom style (theme colours resolved to hex). */
48
+ export const getTableStyleColors = (style, theme) => {
49
+ const rowProperties = style.bandedRange.rowProperties ?? {};
50
+ const colors = {};
51
+ for (const { key, property } of TABLE_STYLE_ELEMENTS) {
52
+ const color = getStringifiedColor(parseTintedColor(rowProperties[property]), theme);
53
+ if (color)
54
+ colors[key] = color;
55
+ }
56
+ return colors;
57
+ };
58
+ const BUILT_IN_STYLE_NAME = /^TableStyle(Light|Medium|Dark)\d+$/i;
59
+ /**
60
+ * Why a style name cannot be used, or `undefined` when it can: empty, a built-in style's name
61
+ * (the gallery tells the two apart by name), or another custom style's name (case-insensitive,
62
+ * like Excel). `currentName` is the style being edited, which may keep its own name.
63
+ */
64
+ export const getTableStyleNameError = (name, existingNames, currentName) => {
65
+ const trimmed = name.trim();
66
+ if (!trimmed)
67
+ return "Enter a name for the style.";
68
+ if (BUILT_IN_STYLE_NAME.test(trimmed)) {
69
+ return "That name belongs to a built-in style.";
70
+ }
71
+ const lower = trimmed.toLowerCase();
72
+ const taken = existingNames.some((existing) => existing.toLowerCase() === lower &&
73
+ existing.toLowerCase() !== currentName?.toLowerCase());
74
+ return taken ? `A table style named “${trimmed}” already exists.` : undefined;
75
+ };
76
+ /**
77
+ * Excel's New Table Style / Modify Table Style dialog, laid out for the Format as Table gallery:
78
+ * name row, the table elements with a colour button each (the toolbar's colour popover), a live
79
+ * preview of the table the picks produce, and an Excel-ordered footer.
80
+ */
81
+ export const TableStyleEditor = ({ style, defaultName = "", theme, existingNames = [], onSubmit, onCancel, onDelete, className, }) => {
82
+ const isEditing = !!style;
83
+ const nameId = useId();
84
+ const [name, setName] = useState(style?.name ?? defaultName);
85
+ const [colors, setColors] = useState(() => style ? getTableStyleColors(style, theme) : DEFAULT_TABLE_STYLE_COLORS);
86
+ const nameError = getTableStyleNameError(name, existingNames, style?.name);
87
+ const preview = useMemo(() => buildCustomTableStyle(name.trim(), colors), [name, colors]);
88
+ const setColor = (key, color) => setColors((prev) => ({ ...prev, [key]: color }));
89
+ const hasAnyColor = TABLE_STYLE_ELEMENTS.some(({ key }) => colors[key]);
90
+ return (React.createElement("form", { "data-testid": "table-style-editor", "aria-label": isEditing ? "Edit table style" : "New table style", className: cn("m-0 p-2 flex flex-col gap-3 font-system text-xs", className),
91
+ // The gallery is a Radix menu: keep typing in the fields from driving its typeahead.
92
+ onKeyDown: (event) => event.stopPropagation(), onSubmit: (event) => {
93
+ event.preventDefault();
94
+ if (!nameError) {
95
+ onSubmit(preview);
96
+ }
97
+ } },
98
+ React.createElement("div", { className: "font-medium text-sm" }, isEditing ? "Edit table style" : "New table style"),
99
+ React.createElement("div", { className: "grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-x-3 gap-y-1" },
100
+ React.createElement("label", { htmlFor: nameId, className: "truncate whitespace-nowrap" }, "Name"),
101
+ React.createElement(Input, { id: nameId, value: name, onChange: (event) => setName(event.target.value), autoComplete: "off", "aria-invalid": nameError ? true : undefined, "aria-describedby": nameError ? `${nameId}-error` : undefined, className: "text-xs" }),
102
+ nameError ? (React.createElement(React.Fragment, null,
103
+ React.createElement("span", { "aria-hidden": "true" }),
104
+ React.createElement("p", { id: `${nameId}-error`, role: "alert", className: "m-0 text-rnc-destructive" }, nameError))) : null),
105
+ React.createElement("div", { className: "flex items-start gap-4" },
106
+ React.createElement("div", { role: "group", "aria-label": "Table elements", className: "flex-1 min-w-0 flex flex-col gap-1.5" },
107
+ React.createElement("div", { className: "text-rnc-muted-foreground" }, "Table element"),
108
+ TABLE_STYLE_ELEMENTS.map(({ key, label }) => (React.createElement("div", { key: key, className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 h-7" },
109
+ React.createElement("span", { className: "truncate whitespace-nowrap", title: label }, label),
110
+ React.createElement(ElementColorButton, { label: label, color: colors[key], theme: theme, onChange: (color) => setColor(key, color) }))))),
111
+ React.createElement("div", { className: "w-[180px] shrink-0 flex flex-col gap-1.5" },
112
+ React.createElement("div", { className: "text-rnc-muted-foreground" }, "Preview"),
113
+ React.createElement(TableStylePreviewCard, { rowProperties: preview.bandedRange.rowProperties, theme: theme }),
114
+ React.createElement(Button, { type: "button", variant: "link", size: "xs", className: "self-start no-underline hover:underline", disabled: !hasAnyColor, onClick: () => setColors({}) }, "Clear all colors"))),
115
+ React.createElement(Separator, null),
116
+ React.createElement("div", { className: "flex items-center gap-2" },
117
+ isEditing && onDelete ? (React.createElement(Button, { type: "button", variant: "ghost", size: "xs", className: "text-rnc-destructive hover:text-rnc-destructive", onClick: () => onDelete(style) }, "Delete style")) : null,
118
+ React.createElement("div", { className: "ml-auto flex items-center gap-2" },
119
+ React.createElement(Button, { type: "button", variant: "ghost", size: "xs", onClick: onCancel }, "Cancel"),
120
+ React.createElement(Button, { type: "submit", variant: "primary", size: "xs", disabled: !!nameError }, isEditing ? "Save" : "Create")))));
121
+ };
122
+ /** "No colour" swatch: a red diagonal on the popover background, as the toolbar's fill button. */
123
+ const NO_COLOR_BACKGROUND = "linear-gradient(to top right, transparent 44%, #d32f2f 44%, #d32f2f 56%, transparent 56%)";
124
+ /**
125
+ * One element's colour: a swatch button (current colour + chevron) opening the shared colour
126
+ * popover the toolbar's fill / text colour buttons use (theme colours, standard colours, custom,
127
+ * and "No color").
128
+ */
129
+ const ElementColorButton = ({ label, color, theme, onChange, }) => {
130
+ const [isOpen, setIsOpen] = useState(false);
131
+ return (React.createElement(Popover, { modal: false, open: isOpen, onOpenChange: setIsOpen },
132
+ React.createElement(PopoverTrigger, { asChild: true },
133
+ React.createElement(Button, { type: "button", variant: "outline", size: "xs", "aria-label": `${label} color: ${color ? color.toUpperCase() : "No color"}`, "aria-haspopup": "dialog", "aria-expanded": isOpen, className: "h-7 w-14 px-1 py-0 gap-1 justify-between rounded-sm" },
134
+ React.createElement("span", { "data-testid": "element-color-swatch", "aria-hidden": "true", className: "block w-5 h-5 rounded-sm border border-solid border-rnc-border", style: color
135
+ ? { backgroundColor: color }
136
+ : { backgroundImage: NO_COLOR_BACKGROUND } }),
137
+ React.createElement(ChevronDownIcon, { className: "w-3 h-3 text-rnc-muted-foreground" }))),
138
+ React.createElement(PopoverContent, { align: "end" },
139
+ React.createElement(ColorSelector, { color: color, theme: theme, resetButtonTitle: "No color", onChange: (next) => {
140
+ onChange(next ? getStringifiedColor(next, theme) : undefined);
141
+ setIsOpen(false);
142
+ } }))));
143
+ };
144
+ const PREVIEW_COLUMNS = 5;
145
+ const PREVIEW_BODY_ROWS = 4;
146
+ /**
147
+ * The table a style paints: a header row over four banded rows, with the first / last column and
148
+ * total row emphasis when the style colours them. Uses the grid's banding rules
149
+ * (`api/banded-ranges.ts`): the first body row is the first stripe, the last body row is the
150
+ * total row, and an edge column's colour outranks the stripe under it.
151
+ */
152
+ export const TableStylePreviewCard = ({ rowProperties, theme, }) => {
153
+ const resolve = (color) => getStringifiedColor(parseTintedColor(color), theme) ?? undefined;
154
+ const headerColor = resolve(rowProperties?.headerColor);
155
+ const firstBand = resolve(rowProperties?.firstBandColor);
156
+ const secondBand = resolve(rowProperties?.secondBandColor);
157
+ const firstColumn = resolve(rowProperties?.firstColumnColor);
158
+ const lastColumn = resolve(rowProperties?.lastColumnColor);
159
+ const totalRow = resolve(rowProperties?.totalRowColor);
160
+ const dark = getStringifiedColor({ theme: 1 }, theme);
161
+ const light = getStringifiedColor({ theme: 0 }, theme);
162
+ const barColor = (background) => background ? getContrastColor(background, dark, light) : undefined;
163
+ const cellClass = "table-cell border border-solid border-rnc-border/70 align-middle px-1.5";
164
+ return (React.createElement("div", { "data-testid": "table-style-preview", className: "table border-collapse table-fixed w-full h-[108px] rounded-sm bg-rnc-popover" },
165
+ React.createElement("div", { className: "table-row", "data-testid": "preview-header-row" }, Array.from({ length: PREVIEW_COLUMNS }, (_, column) => (React.createElement("div", { key: column, className: cellClass, style: { backgroundColor: headerColor } },
166
+ React.createElement("span", { className: "block h-1.5 w-3/4 rounded-full", style: {
167
+ backgroundColor: barColor(headerColor) ?? dark,
168
+ opacity: headerColor ? 0.9 : 0.6,
169
+ } }))))),
170
+ Array.from({ length: PREVIEW_BODY_ROWS }, (_, row) => {
171
+ const isTotalRow = row === PREVIEW_BODY_ROWS - 1;
172
+ const stripe = row % 2 === 0 ? firstBand : secondBand;
173
+ const rowBackground = isTotalRow ? totalRow ?? stripe : stripe;
174
+ return (React.createElement("div", { className: "table-row", key: row, "data-testid": "preview-body-row" }, Array.from({ length: PREVIEW_COLUMNS }, (_, column) => {
175
+ const edge = column === 0
176
+ ? firstColumn
177
+ : column === PREVIEW_COLUMNS - 1
178
+ ? lastColumn
179
+ : undefined;
180
+ const background = edge ?? rowBackground;
181
+ return (React.createElement("div", { key: column, className: cellClass, style: { backgroundColor: background } },
182
+ React.createElement("span", { className: "block h-1 rounded-full", style: {
183
+ width: `${55 + ((row * 3 + column * 5) % 4) * 10}%`,
184
+ backgroundColor: barColor(background) ?? dark,
185
+ opacity: 0.35,
186
+ } })));
187
+ })));
188
+ })));
189
+ };
@@ -2,6 +2,7 @@ import type { CellInterface, SelectionArea } from "@rowsncolumns/grid";
2
2
  import { findMatchingBandProperty } from "@rowsncolumns/utils";
3
3
  import React from "react";
4
4
  export { findMatchingBandProperty };
5
+ export { buildCustomTableStyle } from "./table-style-editor";
5
6
  import type { BandedDefinition, CustomTableStyle, TableStylePreview, TableTheme, TableView } from "../../types";
6
7
  import type { CanvasGridProps, SelectionAttributes, SpreadsheetTheme } from "../canvas-grid/typings";
7
8
  export type TableStyleSelectorProps = {
@@ -26,6 +27,11 @@ export type TableStyleSelectorProps = {
26
27
  * without it the selector keeps the styles it created for the session.
27
28
  */
28
29
  onCreateCustomTableStyle?(style: CustomTableStyle): void;
30
+ /**
31
+ * Called with a host-kept custom style the user deletes from the style editor (right-click a
32
+ * Custom swatch → Delete style); without it host-kept styles cannot be deleted from the gallery.
33
+ */
34
+ onDeleteCustomTableStyle?(style: CustomTableStyle): void;
29
35
  };
30
36
  /** Excel's Table Style Options (Table Design tab), in ribbon order. */
31
37
  declare const STYLE_OPTIONS: Array<{
@@ -39,12 +45,6 @@ declare const STYLE_OPTIONS: Array<{
39
45
  * does); a custom style has no palette to re-resolve from and keeps its banding.
40
46
  */
41
47
  export declare const getStyleOptionUpdate: (table: TableView, key: (typeof STYLE_OPTIONS)[number]["key"], checked: boolean) => Partial<TableView>;
42
- /** The banding a New Table Style paints from its three colours (unset elements stay unpainted). */
43
- export declare const buildCustomTableStyle: (name: string, colors: {
44
- header?: string;
45
- firstBand?: string;
46
- secondBand?: string;
47
- }) => CustomTableStyle;
48
48
  type TableStyleList = [
49
49
  theme: TableTheme,
50
50
  firtBandStripeTint: number | undefined,
@@ -57,5 +57,5 @@ type TableStyleList = [
57
57
  * importers and both engines resolve a theme name into) plus a per-swatch preview style.
58
58
  */
59
59
  export declare const getTableBorderStyles: (showRowStripes: boolean | undefined, showColumnStripes: boolean | undefined, theme?: SpreadsheetTheme) => Map<string, TableStyleList[]>;
60
- export declare const TableStyleSelector: React.MemoExoticComponent<({ tables, sheetId, theme, activeCell, selections, onUpdateTable, onCreateTable, onRequestCreateTable, onPreviewTableStyle, customTableStyles, onCreateCustomTableStyle, }: TableStyleSelectorProps) => React.JSX.Element>;
60
+ export declare const TableStyleSelector: React.MemoExoticComponent<({ tables, sheetId, theme, activeCell, selections, onUpdateTable, onCreateTable, onRequestCreateTable, onPreviewTableStyle, customTableStyles, onCreateCustomTableStyle, onDeleteCustomTableStyle, }: TableStyleSelectorProps) => React.JSX.Element>;
61
61
  //# sourceMappingURL=table-style-selector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"table-style-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/table-style-selector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAkBvE,OAAO,EACL,wBAAwB,EAGzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAuD,MAAM,OAAO,CAAC;AAG5E,OAAO,EAAE,wBAAwB,EAAE,CAAC;AAKpC,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAEhB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IACjD,UAAU,EAAE,aAAa,CAAC;IAC1B,aAAa,CAAC,CACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EACxB,YAAY,EAAE,OAAO,CAAC,SAAS,CAAC,GAC/B,IAAI,CAAC;IACR,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAChD,4FAA4F;IAC5F,oBAAoB,CAAC,EAAE,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;OAGG;IACH,mBAAmB,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9D,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC;;;OAGG;IACH,wBAAwB,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC1D,CAAC;AAEF,uEAAuE;AACvE,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC;IACzB,GAAG,EACC,WAAW,GACX,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;CACvB,CAQA,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,SAAS,EAChB,KAAK,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,EAC1C,SAAS,OAAO,KACf,OAAO,CAAC,SAAS,CAqBnB,CAAC;AAsBF,mGAAmG;AACnG,eAAO,MAAM,qBAAqB,GAChC,MAAM,MAAM,EACZ,QAAQ;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,KACnE,gBASD,CAAC;AAEH,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,UAAU;IACjB,kBAAkB,EAAE,MAAM,GAAG,SAAS;IACtC,cAAc,EAAE,MAAM,GAAG,SAAS;IAClC,cAAc,EAAE,gBAAgB,GAAG,SAAS;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa;CAC5B,CAAC;AAKF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,gBAAgB,OAAO,GAAG,SAAS,EACnC,mBAAmB,OAAO,GAAG,SAAS,EACtC,QAAQ,gBAAgB,kCAoBzB,CAAC;AAEF,eAAO,MAAM,kBAAkB,wMAa1B,uBAAuB,uBAoS3B,CAAC"}
1
+ {"version":3,"file":"table-style-selector.d.ts","sourceRoot":"","sources":["../../../../components/toolbar/table-style-selector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAevE,OAAO,EACL,wBAAwB,EAGzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAuD,MAAM,OAAO,CAAC;AAG5E,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,OAAO,KAAK,EACV,gBAAgB,EAEhB,gBAAgB,EAEhB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAGhC,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IACjD,UAAU,EAAE,aAAa,CAAC;IAC1B,aAAa,CAAC,CACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EACxB,YAAY,EAAE,OAAO,CAAC,SAAS,CAAC,GAC/B,IAAI,CAAC;IACR,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,CAAC;IAChD,4FAA4F;IAC5F,oBAAoB,CAAC,EAAE,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;OAGG;IACH,mBAAmB,CAAC,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC;IAC9D,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC;;;OAGG;IACH,wBAAwB,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACzD;;;OAGG;IACH,wBAAwB,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC1D,CAAC;AAEF,uEAAuE;AACvE,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC;IACzB,GAAG,EACC,WAAW,GACX,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;CACvB,CAQA,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,OAAO,SAAS,EAChB,KAAK,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,EAC1C,SAAS,OAAO,KACf,OAAO,CAAC,SAAS,CAqBnB,CAAC;AAEF,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,UAAU;IACjB,kBAAkB,EAAE,MAAM,GAAG,SAAS;IACtC,cAAc,EAAE,MAAM,GAAG,SAAS;IAClC,cAAc,EAAE,gBAAgB,GAAG,SAAS;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa;CAC5B,CAAC;AAKF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,gBAAgB,OAAO,GAAG,SAAS,EACnC,mBAAmB,OAAO,GAAG,SAAS,EACtC,QAAQ,gBAAgB,kCAoBzB,CAAC;AAEF,eAAO,MAAM,kBAAkB,kOAc1B,uBAAuB,uBA+W3B,CAAC"}