@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
@@ -3,17 +3,24 @@
3
3
  * Excel groups a date column's AutoFilter checklist into a Year → Month → Day
4
4
  * tree with tri-state parents. The checklist only knows each cell's DISPLAYED
5
5
  * text, so a value is treated as a date when it parses as one AND looks like
6
- * one — digits separated by `/ - .` or a month name — which keeps plain
7
- * numbers ("2026") and text out. The tree is built only when every non-blank
6
+ * one — three digit groups separated by `/ - .` or a month name — which keeps
7
+ * plain numbers ("2026"), fractions / ranges ("1/2", "3-4") and text out. The tree is built only when every non-blank
8
8
  * value in the column is such a date (a mixed column stays a flat list, as a
9
- * text column would). Newest first at every level, matching Excel.
9
+ * text column would).
10
+ *
11
+ * Every level — years, months, days — is listed oldest-first (chronological).
12
+ * This deliberately diverges from Excel, which lists newest-first: product
13
+ * decision by Vinay, 2026-09-24. Do not "fix" it back to match Excel.
10
14
  */
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.triState = exports.buildDateTree = exports.parseFilterDateValue = void 0;
16
+ exports.classifyFilterColumn = exports.isFilterNumberValue = exports.triState = exports.buildDateTree = exports.parseFilterDateValue = void 0;
13
17
  var tslib_1 = require("tslib");
14
18
  var MIN_YEAR = 1900;
15
19
  var MAX_YEAR = 2199;
16
- var DATE_SHAPE = /(\d[/.\-]\d)|([A-Za-z]{3,}.*\d)|(\d.*[A-Za-z]{3,})/;
20
+ // An all-digit date needs three parts ("9/29/2026", "3.4.26"): two-part
21
+ // shapes ("3-4", "1/2") are ranges or fractions, though V8 parses them as
22
+ // 2001 dates. Month-name shapes ("4-Mar", "15 Mar 2026") need no year.
23
+ var DATE_SHAPE = /(\d[/.\-]\d+[/.\-]\d)|([A-Za-z]{3,}.*\d)|(\d.*[A-Za-z]{3,})/;
17
24
  var MONTH_NAMES = [
18
25
  "January",
19
26
  "February",
@@ -45,12 +52,16 @@ var parseFilterDateValue = function (value) {
45
52
  exports.parseFilterDateValue = parseFilterDateValue;
46
53
  /**
47
54
  * Year → Month → Day nodes for `values`, or `undefined` when the column is
48
- * not a date column. `blankLabel` (the "(Blanks)" entry) is skipped; callers
49
- * render it beside the tree.
55
+ * not a date column — the same `classifyFilterColumn` verdict the condition
56
+ * menu uses, so a column of decimals or ranges ("1.5", "3-4", which V8 also
57
+ * parses as 2001 dates) stays a flat list. `blankLabel` (the "(Blanks)"
58
+ * entry) is skipped; callers render it beside the tree.
50
59
  */
51
60
  var buildDateTree = function (values, blankLabel) {
52
61
  var e_1, _a, e_2, _b;
53
62
  var _c, _d, _e;
63
+ if ((0, exports.classifyFilterColumn)(values, blankLabel) !== "date")
64
+ return undefined;
54
65
  var dated = [];
55
66
  try {
56
67
  for (var values_1 = tslib_1.__values(values), values_1_1 = values_1.next(); !values_1_1.done; values_1_1 = values_1.next()) {
@@ -96,12 +107,12 @@ var buildDateTree = function (values, blankLabel) {
96
107
  }
97
108
  finally { if (e_2) throw e_2.error; }
98
109
  }
99
- var desc = function (a, b) { return b - a; };
100
- return tslib_1.__spreadArray([], tslib_1.__read(years.keys()), false).sort(desc).map(function (y) {
110
+ var asc = function (a, b) { return a - b; };
111
+ return tslib_1.__spreadArray([], tslib_1.__read(years.keys()), false).sort(asc).map(function (y) {
101
112
  var months = years.get(y);
102
- var monthNodes = tslib_1.__spreadArray([], tslib_1.__read(months.keys()), false).sort(desc).map(function (m) {
113
+ var monthNodes = tslib_1.__spreadArray([], tslib_1.__read(months.keys()), false).sort(asc).map(function (m) {
103
114
  var days = months.get(m);
104
- var dayNodes = tslib_1.__spreadArray([], tslib_1.__read(days.keys()), false).sort(desc).map(function (d) { return ({
115
+ var dayNodes = tslib_1.__spreadArray([], tslib_1.__read(days.keys()), false).sort(asc).map(function (d) { return ({
105
116
  key: "d:".concat(y, "-").concat(m + 1, "-").concat(d),
106
117
  label: String(d),
107
118
  values: days.get(d),
@@ -145,3 +156,57 @@ var triState = function (values, isVisible) {
145
156
  return checked === values.length ? true : "mixed";
146
157
  };
147
158
  exports.triState = triState;
159
+ var CURRENCY_SYMBOLS = /^[$€£¥₹]|[$€£¥₹]$/g;
160
+ // `\s` already covers the no-break and narrow no-break spaces locales pad thousands with.
161
+ var THOUSANDS_SEPARATORS = /[,\s']/g;
162
+ var NUMBER_SHAPE = /^[-+]?(\d+\.?\d*|\.\d+)(e[-+]?\d+)?%?$/i;
163
+ /** `true` when a displayed value reads as a number: "42", "-1,234.5", "$1,200", "12%", "(300)". */
164
+ var isFilterNumberValue = function (value) {
165
+ var text = value.trim();
166
+ if (!text)
167
+ return false;
168
+ if (text.startsWith("(") && text.endsWith(")")) {
169
+ text = text.slice(1, -1).trim();
170
+ }
171
+ text = text.replace(CURRENCY_SYMBOLS, "").trim();
172
+ text = text.replace(THOUSANDS_SEPARATORS, "");
173
+ return NUMBER_SHAPE.test(text) && Number.isFinite(Number(text.replace(/%$/, "")));
174
+ };
175
+ exports.isFilterNumberValue = isFilterNumberValue;
176
+ var classifyFilterColumn = function (values, blankLabel) {
177
+ var e_4, _a;
178
+ var seen = 0;
179
+ var allDates = true;
180
+ var allNumbers = true;
181
+ try {
182
+ for (var values_3 = tslib_1.__values(values), values_3_1 = values_3.next(); !values_3_1.done; values_3_1 = values_3.next()) {
183
+ var value = values_3_1.value;
184
+ if (value === blankLabel || !value.trim())
185
+ continue;
186
+ seen++;
187
+ if (allDates && !(0, exports.parseFilterDateValue)(value))
188
+ allDates = false;
189
+ if (allNumbers && !(0, exports.isFilterNumberValue)(value))
190
+ allNumbers = false;
191
+ if (!allDates && !allNumbers)
192
+ return "text";
193
+ }
194
+ }
195
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
196
+ finally {
197
+ try {
198
+ if (values_3_1 && !values_3_1.done && (_a = values_3.return)) _a.call(values_3);
199
+ }
200
+ finally { if (e_4) throw e_4.error; }
201
+ }
202
+ if (!seen)
203
+ return "text";
204
+ // Numbers first: V8 also parses "1.5" or "3-4" as a date (Jan 5 / Mar 4,
205
+ // 2001), so a decimals column would otherwise be offered date filters.
206
+ if (allNumbers)
207
+ return "number";
208
+ if (allDates)
209
+ return "date";
210
+ return "text";
211
+ };
212
+ exports.classifyFilterColumn = classifyFilterColumn;
@@ -1,9 +1,34 @@
1
1
  import React from "react";
2
+ import { CONDITION_LABELS } from "../../config";
2
3
  import type { ConditionType, FilterConditionJoin } from "../../types";
4
+ import type { FilterColumnType } from "./date-tree";
3
5
  /** Excel's "Top 10…" AutoFilter: the four `NUMBER_TOP_*` / `NUMBER_BOTTOM_*` condition types. */
4
6
  export declare const isTop10ConditionType: (type: ConditionType | undefined) => type is "NUMBER_TOP_N" | "NUMBER_BOTTOM_N" | "NUMBER_TOP_PERCENT" | "NUMBER_BOTTOM_PERCENT";
5
7
  export declare const top10ConditionType: (direction: "top" | "bottom", unit: "items" | "percent") => ConditionType;
8
+ type ConditionLabel = (typeof CONDITION_LABELS)[number];
9
+ /** One section of the condition menu; `label` renders as a group heading (Excel's "All Dates in the Period ▸"). */
10
+ export type ConditionOptionGroup = {
11
+ key: string;
12
+ label?: string;
13
+ options: ConditionLabel[];
14
+ };
15
+ export declare const DATE_PERIOD_GROUP_LABEL = "All dates in the period";
16
+ /**
17
+ * The condition menu Excel shows for a column: one family — "Text Filters",
18
+ * "Number Filters" or "Date Filters" (with the sixteen period conditions
19
+ * grouped under "All Dates in the Period") — plus the generic items. With no
20
+ * `columnType` (an unclassified column) every family is offered, grouped.
21
+ * A `current` condition from another family (a date filter stored on a column
22
+ * that now holds text) stays listed and selected so the user can see and
23
+ * clear it instead of it silently vanishing from the menu.
24
+ */
25
+ export declare const conditionOptionsFor: (labels: readonly ConditionLabel[], columnType: FilterColumnType | undefined, current: ConditionType | undefined) => ConditionOptionGroup[];
6
26
  type FilterByConditionProps = {
27
+ /**
28
+ * The column's predominant value type — picks the Excel family the
29
+ * condition menu offers. Omit to offer every family.
30
+ */
31
+ columnType?: FilterColumnType;
7
32
  conditionType?: ConditionType;
8
33
  fromValue: string;
9
34
  toValue: string;
@@ -20,6 +45,6 @@ type FilterByConditionProps = {
20
45
  onChangeCondition2To(value: string): void;
21
46
  onChangeConditionJoin(join: FilterConditionJoin): void;
22
47
  };
23
- export declare const FilterByCondition: ({ conditionType, fromValue, toValue, onChangeToValue, onChangeFromValue, onChangeConditionType, condition2Type, condition2From, condition2To, conditionJoin, onChangeCondition2Type, onChangeCondition2From, onChangeCondition2To, onChangeConditionJoin, }: FilterByConditionProps) => React.JSX.Element;
48
+ export declare const FilterByCondition: ({ columnType, conditionType, fromValue, toValue, onChangeToValue, onChangeFromValue, onChangeConditionType, condition2Type, condition2From, condition2To, conditionJoin, onChangeCondition2Type, onChangeCondition2From, onChangeCondition2To, onChangeConditionJoin, }: FilterByConditionProps) => React.JSX.Element;
24
49
  export {};
25
50
  //# sourceMappingURL=filter-by-condition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filter-by-condition.d.ts","sourceRoot":"","sources":["../../../../components/filter-box/filter-by-condition.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAKxC,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEtE,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,GAAI,MAAM,aAAa,GAAG,SAAS,gGAIlC,CAAC;AAEnC,eAAO,MAAM,kBAAkB,GAC7B,WAAW,KAAK,GAAG,QAAQ,EAC3B,MAAM,OAAO,GAAG,SAAS,KACxB,aAO4B,CAAC;AAchC,KAAK,sBAAsB,GAAG;IAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,CAAC,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;IAC9D,qEAAqE;IACrE,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,mBAAmB,CAAC;IACnC,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;IAC9D,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACxD,CAAC;AAsKF,eAAO,MAAM,iBAAiB,GAAI,6PAe/B,sBAAsB,sBAuFxB,CAAC"}
1
+ {"version":3,"file":"filter-by-condition.d.ts","sourceRoot":"","sources":["../../../../components/filter-box/filter-by-condition.tsx"],"names":[],"mappings":"AAcA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAkB,MAAM,cAAc,CAAC;AAGhE,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,GAAI,MAAM,aAAa,GAAG,SAAS,gGAIlC,CAAC;AAEnC,eAAO,MAAM,kBAAkB,GAC7B,WAAW,KAAK,GAAG,QAAQ,EAC3B,MAAM,OAAO,GAAG,SAAS,KACxB,aAO4B,CAAC;AAchC,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,mHAAmH;AACnH,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAoBjE;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,SAAS,cAAc,EAAE,EACjC,YAAY,gBAAgB,GAAG,SAAS,EACxC,SAAS,aAAa,GAAG,SAAS,KACjC,oBAAoB,EAmCtB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,CAAC,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;IAC9D,qEAAqE;IACrE,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,mBAAmB,CAAC;IACnC,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI,CAAC;IAC9D,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACxD,CAAC;AAsLF,eAAO,MAAM,iBAAiB,GAAI,yQAgB/B,sBAAsB,sBAyFxB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FilterByCondition = exports.top10ConditionType = exports.isTop10ConditionType = void 0;
3
+ exports.FilterByCondition = exports.conditionOptionsFor = exports.DATE_PERIOD_GROUP_LABEL = exports.top10ConditionType = exports.isTop10ConditionType = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var ui_1 = require("@rowsncolumns/ui");
6
6
  var react_1 = tslib_1.__importStar(require("react"));
@@ -36,8 +36,103 @@ var SECOND_CLAUSE_LABELS = config_1.CONDITION_LABELS.filter(function (_a) {
36
36
  condition !== "NUMBER_ABOVE_AVERAGE" &&
37
37
  condition !== "NUMBER_BELOW_AVERAGE";
38
38
  });
39
+ exports.DATE_PERIOD_GROUP_LABEL = "All dates in the period";
40
+ var FOREIGN_GROUP_LABEL = "Applied filter";
41
+ var isDatePeriodCondition = function (type) {
42
+ return /^DATE_IN_[QM]\d+$/.test(type);
43
+ };
44
+ /**
45
+ * Which Excel AutoFilter family a condition belongs to. Anything outside the
46
+ * text / number / date prefixes ("Is empty", "Errors", "Custom formula") is
47
+ * offered on every column.
48
+ */
49
+ var conditionFamily = function (type) {
50
+ if (type.startsWith("TEXT_"))
51
+ return "text";
52
+ if (type.startsWith("NUMBER_"))
53
+ return "number";
54
+ if (type.startsWith("DATE_"))
55
+ return isDatePeriodCondition(type) ? "period" : "date";
56
+ return "generic";
57
+ };
58
+ /**
59
+ * The condition menu Excel shows for a column: one family — "Text Filters",
60
+ * "Number Filters" or "Date Filters" (with the sixteen period conditions
61
+ * grouped under "All Dates in the Period") — plus the generic items. With no
62
+ * `columnType` (an unclassified column) every family is offered, grouped.
63
+ * A `current` condition from another family (a date filter stored on a column
64
+ * that now holds text) stays listed and selected so the user can see and
65
+ * clear it instead of it silently vanishing from the menu.
66
+ */
67
+ var conditionOptionsFor = function (labels, columnType, current) {
68
+ var e_1, _a;
69
+ var _b;
70
+ var families = columnType
71
+ ? [columnType]
72
+ : ["text", "number", "date"];
73
+ var groups = [];
74
+ var _loop_1 = function (family) {
75
+ groups.push({
76
+ key: family,
77
+ options: labels.filter(function (_a) {
78
+ var condition = _a.condition;
79
+ return conditionFamily(condition) === family;
80
+ }),
81
+ });
82
+ if (family === "date") {
83
+ groups.push({
84
+ key: "period",
85
+ label: exports.DATE_PERIOD_GROUP_LABEL,
86
+ options: labels.filter(function (_a) {
87
+ var condition = _a.condition;
88
+ return conditionFamily(condition) === "period";
89
+ }),
90
+ });
91
+ }
92
+ };
93
+ try {
94
+ for (var families_1 = tslib_1.__values(families), families_1_1 = families_1.next(); !families_1_1.done; families_1_1 = families_1.next()) {
95
+ var family = families_1_1.value;
96
+ _loop_1(family);
97
+ }
98
+ }
99
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
100
+ finally {
101
+ try {
102
+ if (families_1_1 && !families_1_1.done && (_a = families_1.return)) _a.call(families_1);
103
+ }
104
+ finally { if (e_1) throw e_1.error; }
105
+ }
106
+ groups.push({
107
+ key: "generic",
108
+ options: labels.filter(function (_a) {
109
+ var condition = _a.condition;
110
+ return conditionFamily(condition) === "generic";
111
+ }),
112
+ });
113
+ var offered = groups.filter(function (group) { return group.options.length > 0; });
114
+ if (current &&
115
+ !offered.some(function (group) { return group.options.some(function (_a) {
116
+ var condition = _a.condition;
117
+ return condition === current;
118
+ }); })) {
119
+ var foreign = (_b = labels.find(function (_a) {
120
+ var condition = _a.condition;
121
+ return condition === current;
122
+ })) !== null && _b !== void 0 ? _b : config_1.CONDITION_LABELS.find(function (_a) {
123
+ var condition = _a.condition;
124
+ return condition === current;
125
+ });
126
+ if (foreign) {
127
+ offered.unshift({ key: "current", label: FOREIGN_GROUP_LABEL, options: [foreign] });
128
+ }
129
+ }
130
+ return offered;
131
+ };
132
+ exports.conditionOptionsFor = conditionOptionsFor;
39
133
  var ConditionSelect = function (_a) {
40
- var value = _a.value, labels = _a.labels, onChange = _a.onChange, ariaLabel = _a.ariaLabel;
134
+ var value = _a.value, labels = _a.labels, columnType = _a.columnType, onChange = _a.onChange, ariaLabel = _a.ariaLabel;
135
+ var groups = (0, react_1.useMemo)(function () { return (0, exports.conditionOptionsFor)(labels, columnType, value); }, [labels, columnType, value]);
41
136
  return (react_1.default.createElement(ui_1.Select, { value: value !== null && value !== void 0 ? value : config_1.CONDITION_NONE, onValueChange: function (next) {
42
137
  return onChange(next === config_1.CONDITION_NONE ? undefined : next);
43
138
  } },
@@ -46,10 +141,14 @@ var ConditionSelect = function (_a) {
46
141
  react_1.default.createElement(ui_1.SelectPortal, null,
47
142
  react_1.default.createElement(ui_1.SelectContent, null,
48
143
  react_1.default.createElement(ui_1.SelectItem, { value: config_1.CONDITION_NONE }, "None"),
49
- labels.map(function (_a) {
50
- var condition = _a.condition, label = _a.label;
51
- return (react_1.default.createElement(ui_1.SelectItem, { key: condition, value: condition }, label));
52
- })))));
144
+ groups.map(function (group) { return (react_1.default.createElement(react_1.default.Fragment, { key: group.key },
145
+ react_1.default.createElement(ui_1.SelectSeparator, null),
146
+ react_1.default.createElement(ui_1.SelectGroup, null,
147
+ group.label ? react_1.default.createElement(ui_1.SelectLabel, null, group.label) : null,
148
+ group.options.map(function (_a) {
149
+ var condition = _a.condition, label = _a.label;
150
+ return (react_1.default.createElement(ui_1.SelectItem, { key: condition, value: condition }, label));
151
+ })))); })))));
53
152
  };
54
153
  var ValueInputs = function (_a) {
55
154
  var conditionType = _a.conditionType, fromValue = _a.fromValue, toValue = _a.toValue, onChangeFromValue = _a.onChangeFromValue, onChangeToValue = _a.onChangeToValue, namePrefix = _a.namePrefix;
@@ -104,20 +203,20 @@ var Top10Fields = function (_a) {
104
203
  react_1.default.createElement(ui_1.SelectItem, { value: "percent" }, "Percent"))))));
105
204
  };
106
205
  var FilterByCondition = function (_a) {
107
- var conditionType = _a.conditionType, fromValue = _a.fromValue, toValue = _a.toValue, onChangeToValue = _a.onChangeToValue, onChangeFromValue = _a.onChangeFromValue, onChangeConditionType = _a.onChangeConditionType, condition2Type = _a.condition2Type, condition2From = _a.condition2From, condition2To = _a.condition2To, conditionJoin = _a.conditionJoin, onChangeCondition2Type = _a.onChangeCondition2Type, onChangeCondition2From = _a.onChangeCondition2From, onChangeCondition2To = _a.onChangeCondition2To, onChangeConditionJoin = _a.onChangeConditionJoin;
206
+ var columnType = _a.columnType, conditionType = _a.conditionType, fromValue = _a.fromValue, toValue = _a.toValue, onChangeToValue = _a.onChangeToValue, onChangeFromValue = _a.onChangeFromValue, onChangeConditionType = _a.onChangeConditionType, condition2Type = _a.condition2Type, condition2From = _a.condition2From, condition2To = _a.condition2To, conditionJoin = _a.conditionJoin, onChangeCondition2Type = _a.onChangeCondition2Type, onChangeCondition2From = _a.onChangeCondition2From, onChangeCondition2To = _a.onChangeCondition2To, onChangeConditionJoin = _a.onChangeConditionJoin;
108
207
  // Excel's Custom AutoFilter shows the second clause row only on request;
109
208
  // it stays open while a second clause is stored on the column.
110
209
  var _b = tslib_1.__read((0, react_1.useState)(function () { return condition2Type !== undefined; }), 2), showSecondClause = _b[0], setShowSecondClause = _b[1];
111
210
  var isTop10 = (0, exports.isTop10ConditionType)(conditionType);
112
211
  return (react_1.default.createElement(react_1.default.Fragment, null,
113
212
  react_1.default.createElement("label", { className: "text-sm font-semibold pb-1 block" }, "Filter by condition"),
114
- react_1.default.createElement(ConditionSelect, { value: conditionType, labels: config_1.CONDITION_LABELS, onChange: function (type) { return onChangeConditionType === null || onChangeConditionType === void 0 ? void 0 : onChangeConditionType(type); }, ariaLabel: "Condition" }),
213
+ react_1.default.createElement(ConditionSelect, { value: conditionType, labels: config_1.CONDITION_LABELS, columnType: columnType, onChange: function (type) { return onChangeConditionType === null || onChangeConditionType === void 0 ? void 0 : onChangeConditionType(type); }, ariaLabel: "Condition" }),
115
214
  isTop10 && conditionType ? (react_1.default.createElement(Top10Fields, { conditionType: conditionType, count: fromValue, onChangeConditionType: function (type) { return onChangeConditionType === null || onChangeConditionType === void 0 ? void 0 : onChangeConditionType(type); }, onChangeCount: onChangeFromValue })) : (react_1.default.createElement(ValueInputs, { conditionType: conditionType, fromValue: fromValue, toValue: toValue, onChangeFromValue: onChangeFromValue, onChangeToValue: onChangeToValue, namePrefix: "" })),
116
215
  conditionType && !isTop10 ? (showSecondClause ? (react_1.default.createElement("div", { className: "mt-1 flex flex-col gap-1" },
117
216
  react_1.default.createElement("div", { role: "radiogroup", "aria-label": "Combine conditions", className: "flex gap-3 text-xs px-1" }, ["AND", "OR"].map(function (join) { return (react_1.default.createElement("label", { key: join, className: "flex items-center gap-1 cursor-pointer" },
118
217
  react_1.default.createElement("input", { type: "radio", name: "conditionJoin", value: join, checked: conditionJoin === join, onChange: function () { return onChangeConditionJoin(join); } }),
119
218
  join === "AND" ? "And" : "Or")); })),
120
- react_1.default.createElement(ConditionSelect, { value: condition2Type, labels: SECOND_CLAUSE_LABELS, onChange: onChangeCondition2Type, ariaLabel: "Second condition" }),
219
+ react_1.default.createElement(ConditionSelect, { value: condition2Type, labels: SECOND_CLAUSE_LABELS, columnType: columnType, onChange: onChangeCondition2Type, ariaLabel: "Second condition" }),
121
220
  react_1.default.createElement(ValueInputs, { conditionType: condition2Type, fromValue: condition2From, toValue: condition2To, onChangeFromValue: onChangeCondition2From, onChangeToValue: onChangeCondition2To, namePrefix: "second" }))) : (react_1.default.createElement(ui_1.Button, { type: "button", variant: "link", className: "text-xs px-1 mt-1 self-start", onClick: function () { return setShowSecondClause(true); } }, "Custom filter (And / Or)\u2026"))) : null));
122
221
  };
123
222
  exports.FilterByCondition = FilterByCondition;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/filter-box/index.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAI3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAMV,cAAc,EACd,UAAU,EAGV,SAAS,EACV,MAAM,aAAa,CAAC;AAKrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKvD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;IACtC,QAAQ,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;IAC1C,mBAAmB,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAC;IAC3D,mBAAmB,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,sEAAsE;IACtE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;CAC1B,CAAC;AAGF,eAAO,MAAM,WAAW,GACtB,QAAQ,UAAU,GAAG,SAAS,KAC7B,MAAM,IAAI,SAEZ,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,cAAc,GAAG,SAAS,KACnC,OAQA,CAAC;AA0BJ,eAAO,MAAM,gBAAgB,4NAexB,cAAc,uBA4kBlB,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS;YAAW,cAAc;;CAuC9C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/filter-box/index.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAM3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAMV,cAAc,EACd,UAAU,EAGV,SAAS,EACV,MAAM,aAAa,CAAC;AAKrB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKvD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,cAAc,CAAC,aAAa,CAAC,CAAC;IACtC,QAAQ,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;IAC1C,mBAAmB,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAC;IAC3D,mBAAmB,EAAE,cAAc,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,sEAAsE;IACtE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;CAC1B,CAAC;AAGF,eAAO,MAAM,WAAW,GACtB,QAAQ,UAAU,GAAG,SAAS,KAC7B,MAAM,IAAI,SAEZ,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,cAAc,GAAG,SAAS,KACnC,OAQA,CAAC;AA0BJ,eAAO,MAAM,gBAAgB,4NAexB,cAAc,uBA8lBlB,CAAC;AAIF,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS;YAAW,cAAc;;CAuC9C,CAAC"}
@@ -9,8 +9,10 @@ var utils_1 = require("@rowsncolumns/utils");
9
9
  var react_1 = tslib_1.__importStar(require("react"));
10
10
  var react_2 = require("react");
11
11
  var atoms_1 = require("../../atoms");
12
- var style_1 = require("../context-menu/style");
13
12
  var sort_dialog_atom_1 = require("../../atoms/sort-dialog.atom");
13
+ var config_1 = require("../../config");
14
+ var style_1 = require("../context-menu/style");
15
+ var date_tree_1 = require("./date-tree");
14
16
  var filter_by_color_1 = require("./filter-by-color");
15
17
  var filter_by_condition_1 = require("./filter-by-condition");
16
18
  var filter_by_value_1 = require("./filter-by-value");
@@ -89,6 +91,9 @@ exports.FilterBoxWrapper = (0, react_2.memo)(function (_a) {
89
91
  var _11 = tslib_1.__read((0, react_1.useState)((_z = filterCriteria === null || filterCriteria === void 0 ? void 0 : filterCriteria.conditionJoin) !== null && _z !== void 0 ? _z : "AND"), 2), conditionJoin = _11[0], onChangeConditionJoin = _11[1];
90
92
  // "Sort by Color" is a collapsed sub-menu of the dropdown, like Excel's.
91
93
  var _12 = tslib_1.__read((0, react_1.useState)(false), 2), showSortByColor = _12[0], setShowSortByColor = _12[1];
94
+ // Excel offers one condition family per column (Text / Number / Date
95
+ // Filters), read from the same displayed values the checklist shows.
96
+ var columnType = (0, react_2.useMemo)(function () { return (0, date_tree_1.classifyFilterColumn)(values, config_1.BLANK_LABEL); }, [values]);
92
97
  // Auto apply (Excel parity). `pendingAutoApplyTick` counts user edits made
93
98
  // while auto apply is on; the debounced effect below applies the newest
94
99
  // state once per burst.
@@ -135,13 +140,21 @@ exports.FilterBoxWrapper = (0, react_2.memo)(function (_a) {
135
140
  // filter box is currently anchored on (that's the column we computed
136
141
  // distinct colors/icons for). Other columns expose value + custom-list
137
142
  // sort only.
143
+ // Excel labels sort levels by the header row — a table's column names, or
144
+ // the first row of a filtered range (an AutoFilter range always starts on
145
+ // its header row). A blank header falls back to the column letter.
146
+ var getFormattedValue = (0, atoms_1.useGetFormattedValue)();
138
147
  var sortDialogColumns = (0, react_2.useMemo)(function () {
139
- var _a = filter.range, startColumnIndex = _a.startColumnIndex, endColumnIndex = _a.endColumnIndex;
148
+ var _a, _b;
149
+ var _c = filter.range, startColumnIndex = _c.startColumnIndex, endColumnIndex = _c.endColumnIndex, startRowIndex = _c.startRowIndex;
140
150
  var cols = [];
141
151
  for (var c = startColumnIndex; c <= endColumnIndex; c++) {
142
152
  var dim = c - startColumnIndex;
143
153
  var isActive = dim === dimensionIndex;
144
- cols.push(tslib_1.__assign({ dimensionIndex: dim, label: "Column ".concat((0, utils_1.number2Alpha)(c)) }, (isActive
154
+ var header = (0, exports.isTableView)(filter)
155
+ ? (_b = (_a = filter.columns) === null || _a === void 0 ? void 0 : _a[dim]) === null || _b === void 0 ? void 0 : _b.name
156
+ : getFormattedValue === null || getFormattedValue === void 0 ? void 0 : getFormattedValue(sheetId, startRowIndex, c);
157
+ cols.push(tslib_1.__assign({ dimensionIndex: dim, label: "Column ".concat((0, utils_1.number2Alpha)(c)), header: header !== null && header !== void 0 ? header : undefined }, (isActive
145
158
  ? {
146
159
  availableCellColors: availableCellColors,
147
160
  availableFontColors: availableFontColors,
@@ -151,7 +164,9 @@ exports.FilterBoxWrapper = (0, react_2.memo)(function (_a) {
151
164
  }
152
165
  return cols;
153
166
  }, [
154
- filter.range,
167
+ filter,
168
+ sheetId,
169
+ getFormattedValue,
155
170
  dimensionIndex,
156
171
  availableCellColors,
157
172
  availableFontColors,
@@ -369,6 +384,7 @@ exports.FilterBoxWrapper = (0, react_2.memo)(function (_a) {
369
384
  openSortDialog({
370
385
  columns: sortDialogColumns,
371
386
  customLists: customLists,
387
+ initialHasHeader: true,
372
388
  onApply: onApplySort,
373
389
  });
374
390
  onClose();
@@ -401,7 +417,7 @@ exports.FilterBoxWrapper = (0, react_2.memo)(function (_a) {
401
417
  react_1.default.createElement(icons_1.Cross2Icon, null)),
402
418
  clearLabel),
403
419
  react_1.default.createElement("div", { className: "py-2 px-2" },
404
- react_1.default.createElement(filter_by_condition_1.FilterByCondition, { conditionType: conditionType, fromValue: fromValue, toValue: toValue, onChangeConditionType: onUserChangeConditionType, onChangeFromValue: onUserChangeFromValue, onChangeToValue: onUserChangeToValue, condition2Type: condition2Type, condition2From: condition2From, condition2To: condition2To, conditionJoin: conditionJoin, onChangeCondition2Type: onUserChangeCondition2Type, onChangeCondition2From: onUserChangeCondition2From, onChangeCondition2To: onUserChangeCondition2To, onChangeConditionJoin: onUserChangeConditionJoin }),
420
+ react_1.default.createElement(filter_by_condition_1.FilterByCondition, { columnType: columnType, conditionType: conditionType, fromValue: fromValue, toValue: toValue, onChangeConditionType: onUserChangeConditionType, onChangeFromValue: onUserChangeFromValue, onChangeToValue: onUserChangeToValue, condition2Type: condition2Type, condition2From: condition2From, condition2To: condition2To, conditionJoin: conditionJoin, onChangeCondition2Type: onUserChangeCondition2Type, onChangeCondition2From: onUserChangeCondition2From, onChangeCondition2To: onUserChangeCondition2To, onChangeConditionJoin: onUserChangeConditionJoin }),
405
421
  react_1.default.createElement(ui_1.Separator, { className: "mt-2 mb-2" }),
406
422
  react_1.default.createElement(filter_by_color_1.FilterByColor, { colorFilter: colorFilter === undefined
407
423
  ? filterCriteria === null || filterCriteria === void 0 ? void 0 : filterCriteria.colorFilter
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/formula-input/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,kBAAkB,CAAC;AAGrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,KAAiB,MAAM,OAAO,CAAC;AAatC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,KAAK,iBAAiB,GAAG,cAAc,CAAC,OAAO,SAAS,CAAC,GAAG;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,sGAiIxB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/formula-input/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,kBAAkB,CAAC;AAGrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,KAAiB,MAAM,OAAO,CAAC;AActC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,KAAK,iBAAiB,GAAG,cAAc,CAAC,OAAO,SAAS,CAAC,GAAG;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,sGA8JxB,CAAC"}
@@ -8,6 +8,7 @@ var react_1 = require("react");
8
8
  var react_2 = tslib_1.__importStar(require("react"));
9
9
  var atoms_1 = require("../../atoms");
10
10
  var cell_input_context_atom_1 = require("../../atoms/cell-input-context.atom");
11
+ var use_isomorphic_layout_effect_1 = require("../../hooks/use-isomorphic-layout-effect");
11
12
  var use_merge_refs_1 = require("../../hooks/use-merge.refs");
12
13
  var cell_input_1 = require("../cell-input");
13
14
  /**
@@ -26,15 +27,36 @@ exports.FormulaInput = (0, react_1.forwardRef)(function (_a, forwardedRef) {
26
27
  var getSelectionsFromFormula = (0, atoms_1.useGetSelectionsFromFormula)();
27
28
  var onEditorKeyDown = (0, atoms_1.useOnEditorKeyDown)();
28
29
  var _e = (0, cell_input_context_atom_1.useCellInputContext)(), getTableNames = _e.getTableNames, getTableColumnNames = _e.getTableColumnNames, getNamedRanges = _e.getNamedRanges;
30
+ // Whether THIS input is the one holding formula mode on. `handleBlur` keeps the mode
31
+ // alive when focus moves to the grid — that is how a range gets picked — so an input
32
+ // that unmounts while the grid holds focus (Cancel / Save / the dialog's X / Esc) has
33
+ // to be the one to turn it off; otherwise the picked range keeps rendering and the
34
+ // grid stays in formula-selection mode with no editor left to receive the picks.
35
+ var ownsFormulaModeRef = (0, react_2.useRef)(false);
36
+ var enterFormulaMode = (0, react_1.useCallback)(function (nextValue) {
37
+ var formulaMode = (0, utils_1.isAFormula)(nextValue);
38
+ setFormulaMode(formulaMode);
39
+ ownsFormulaModeRef.current = formulaMode;
40
+ }, [setFormulaMode]);
29
41
  var closeFormulaMode = (0, react_1.useCallback)(function () {
30
42
  setFormulaMode(false);
31
43
  setFormulaSelections([]);
44
+ ownsFormulaModeRef.current = false;
32
45
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
33
46
  }, [onCancel, setFormulaMode, setFormulaSelections]);
47
+ // Layout (not passive) cleanup: when a host re-keys its range inputs ("Add range"),
48
+ // the departing input must release the mode before the new one's autoFocus claims it.
49
+ (0, use_isomorphic_layout_effect_1.useIsomorphicLayoutEffect)(function () { return function () {
50
+ if (ownsFormulaModeRef.current) {
51
+ ownsFormulaModeRef.current = false;
52
+ setFormulaMode(false);
53
+ setFormulaSelections([]);
54
+ }
55
+ }; }, [setFormulaMode, setFormulaSelections]);
34
56
  var handleFocus = (0, react_1.useCallback)(function (e) {
35
57
  var _a;
36
58
  // Set formula mode
37
- setFormulaMode((0, utils_1.isAFormula)(value));
59
+ enterFormulaMode(value);
38
60
  // Get selections from formula
39
61
  var selections = getSelectionsFromFormula === null || getSelectionsFromFormula === void 0 ? void 0 : getSelectionsFromFormula(value);
40
62
  if (selections) {
@@ -46,7 +68,7 @@ exports.FormulaInput = (0, react_1.forwardRef)(function (_a, forwardedRef) {
46
68
  onChange === null || onChange === void 0 ? void 0 : onChange(value);
47
69
  onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
48
70
  }, [
49
- setFormulaMode,
71
+ enterFormulaMode,
50
72
  setFormulaSelections,
51
73
  getSelectionsFromFormula,
52
74
  value,
@@ -73,7 +95,7 @@ exports.FormulaInput = (0, react_1.forwardRef)(function (_a, forwardedRef) {
73
95
  return;
74
96
  }
75
97
  // Set formula mode
76
- setFormulaMode((0, utils_1.isAFormula)(value));
98
+ enterFormulaMode(value);
77
99
  // Get selections from formula
78
100
  var selections = getSelectionsFromFormula === null || getSelectionsFromFormula === void 0 ? void 0 : getSelectionsFromFormula(value);
79
101
  if (selections) {
@@ -82,7 +104,7 @@ exports.FormulaInput = (0, react_1.forwardRef)(function (_a, forwardedRef) {
82
104
  }, [
83
105
  activeEditor,
84
106
  onChange,
85
- setFormulaMode,
107
+ enterFormulaMode,
86
108
  setFormulaSelections,
87
109
  getSelectionsFromFormula,
88
110
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/sheet-tabs/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAyB,MAAM,OAAO,CAAC;AAI9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,SAAS,KAAK,EAAE,QAAQ,CAAC,EAAE,KAClB,MACjD,CAAC;AAEF;;;;GAIG;AACH,iBAAS,kBAAkB,CAAC,CAAC,SAAS,KAAK,EAAE,EAC3C,MAAM,EACN,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAiC,EACjC,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,QAAQ,EACR,sBAA8B,GAC/B,EAAE,cAAc,CAAC,CAAC,CAAC,qBAqUnB;AAED,eAAO,MAAM,SAAS,EAA+B,OAAO,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/sheet-tabs/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAyB,MAAM,OAAO,CAAC;AAK9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,SAAS,KAAK,EAAE,QAAQ,CAAC,EAAE,KAClB,MACjD,CAAC;AAEF;;;;GAIG;AACH,iBAAS,kBAAkB,CAAC,CAAC,SAAS,KAAK,EAAE,EAC3C,MAAM,EACN,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAiC,EACjC,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,QAAQ,EACR,sBAA8B,GAC/B,EAAE,cAAc,CAAC,CAAC,CAAC,qBAkVnB;AAED,eAAO,MAAM,SAAS,EAA+B,OAAO,kBAAkB,CAAC"}
@@ -11,6 +11,7 @@ var utils_1 = require("@rowsncolumns/utils");
11
11
  var react_1 = require("react");
12
12
  var react_2 = tslib_1.__importStar(require("react"));
13
13
  var validation_1 = require("../../api/validation");
14
+ var validation_alert_atom_1 = require("../../atoms/validation-alert.atom");
14
15
  var use_isomorphic_layout_effect_1 = require("../../hooks/use-isomorphic-layout-effect");
15
16
  var context_menu_1 = require("./context-menu");
16
17
  var sheet_tab_1 = require("./sheet-tab");
@@ -57,16 +58,29 @@ function SheetTabsComponent(_a) {
57
58
  }
58
59
  return !(0, validation_1.isProtectedSheet)(sheetId, protectedRanges);
59
60
  }, [protectedRanges, readonly]);
61
+ var showValidationAlert = (0, validation_alert_atom_1.useShowValidationAlert)();
60
62
  var canRename = (0, react_1.useCallback)(function (sheetId, newName) {
61
- // Worksheet names cannot be empty or whitespace-only
62
- if ((0, utils_1.isNil)(newName) || newName.trim().length === 0) {
63
+ if ((0, utils_1.isNil)(newName) || !canEditSheet(sheetId)) {
63
64
  return false;
64
65
  }
65
- if (!canEditSheet(sheetId)) {
66
+ // Excel's worksheet-name rules (rowsncolumns/spreadsheet#939 U-01): at most 31 characters,
67
+ // none of `\ / ? * [ ] :`, no leading / trailing apostrophe, not `History`, and unique
68
+ // case-insensitively (a sheet may re-case its own title). Every refusal shows Excel's own
69
+ // dialog; a blank name is the editor's "nothing typed" and just restores the title.
70
+ var validation = (0, utils_1.validateSheetName)(newName, sheets, sheetId);
71
+ if (!validation.ok) {
72
+ if (validation.reason !== "blank") {
73
+ void showValidationAlert({
74
+ style: "stop",
75
+ title: "Error",
76
+ message: validation.message,
77
+ confirm: false,
78
+ });
79
+ }
66
80
  return false;
67
81
  }
68
- return !sheets.find(function (sheet) { return sheet.title === newName; });
69
- }, [canEditSheet, sheets]);
82
+ return true;
83
+ }, [canEditSheet, sheets, showValidationAlert]);
70
84
  var canDeleteSheet = (0, react_1.useCallback)(function (sheetId) {
71
85
  if (!canEditSheet(sheetId)) {
72
86
  return false;
@@ -10,7 +10,13 @@ import type { SortSpecs } from "../../types";
10
10
  */
11
11
  export type SortDialogColumn = {
12
12
  dimensionIndex: number;
13
+ /** Positional label ("Column B") — the fallback when the header cell is blank. */
13
14
  label: string;
15
+ /**
16
+ * Text of the column's first-row cell. Shown instead of `label` while
17
+ * "My data has headers" is on (Excel labels sort levels by header).
18
+ */
19
+ header?: string | null;
14
20
  availableCellColors?: string[];
15
21
  availableFontColors?: string[];
16
22
  availableIcons?: AvailableIcon[];
@@ -33,13 +39,24 @@ export type SortDialogProps = {
33
39
  * first column.
34
40
  */
35
41
  initialSpecs?: SortSpecs[];
42
+ /**
43
+ * Initial state of Excel's "My data has headers". The toggle renders when
44
+ * this is set or any column carries a `header`; a host that omits both keeps
45
+ * the header-less dialog.
46
+ */
47
+ initialHasHeader?: boolean;
36
48
  /**
37
49
  * Emits the assembled ordered `SortSpecs[]` (primary first, then
38
- * tiebreakers). The host routes these to `onSortTable` / `onSortRange`.
50
+ * tiebreakers) plus the header choice. The host routes these to
51
+ * `onSortTable` / `onSortRange` (trimming the header row when `hasHeader`).
39
52
  * The dialog never touches state directly.
40
53
  */
41
- onApply(specs: SortSpecs[]): void;
54
+ onApply(specs: SortSpecs[], options: {
55
+ hasHeader: boolean;
56
+ }): void;
42
57
  };
58
+ /** The label a sort level shows for a column: its header while the data has headers, else its letter. */
59
+ export declare const sortColumnLabel: (column: Pick<SortDialogColumn, "label" | "header">, hasHeader: boolean) => string;
43
60
  /**
44
61
  * Custom multi-level sort dialog. Mirrors Excel's "Sort…" dialog and
45
62
  * Google Sheets' "Sort range — advanced". Each row is a sort level:
@@ -51,7 +68,7 @@ export type SortDialogProps = {
51
68
  * `SortSpecs[]` via `onApply`; the host disambiguates table vs range and
52
69
  * routes through its existing sort callbacks.
53
70
  */
54
- export declare const SortRangeDialog: React.MemoExoticComponent<({ open, onOpenChange, columns, customLists, initialSpecs, onApply, }: SortDialogProps) => React.JSX.Element>;
71
+ export declare const SortRangeDialog: React.MemoExoticComponent<({ open, onOpenChange, columns, customLists, initialSpecs, initialHasHeader, onApply, }: SortDialogProps) => React.JSX.Element>;
55
72
  /**
56
73
  * Host-mounted, atom-driven wrapper around {@link SortRangeDialog}. Mount this
57
74
  * once in the host (alongside the other editor dialogs); the filter box opens
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/sort-range-dialog/index.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,aAAa,CAAC;AAQxD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,oCAAoC;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;CACnC,CAAC;AA2DF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,mGAQvB,eAAe,uBAiVnB,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;CAe/B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../components/sort-range-dialog/index.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,aAAa,CAAC;AAQxD;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,oCAAoC;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACpE,CAAC;AAEF,yGAAyG;AACzG,eAAO,MAAM,eAAe,GAC1B,QAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,QAAQ,CAAC,EAClD,WAAW,OAAO,KACjB,MAGF,CAAC;AA2DF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,qHASvB,eAAe,uBAiWnB,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;CAgB/B,CAAC"}