@rowsncolumns/spreadsheet 10.5.14 → 10.5.15

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.
@@ -143,7 +143,11 @@ export const useEditorSubmit = ({ sheetId, onChangeActiveSheet, setActiveCell, o
143
143
  * actually changed and a rule with an error alert rejects it.
144
144
  */
145
145
  if (value !== previousValue && getCommitValidation) {
146
- const enforcement = getCommitValidation(editingSheetId, activeCell.rowIndex, activeCell.columnIndex, value, previousValue);
146
+ // Only a list (`ONE_OF_RANGE`) rule resolves asynchronously; every other
147
+ // decision stays synchronous so an ordinary commit still lands in the
148
+ // same tick as the keystroke.
149
+ const pending = getCommitValidation(editingSheetId, activeCell.rowIndex, activeCell.columnIndex, value, previousValue);
150
+ const enforcement = pending instanceof Promise ? await pending : pending;
147
151
  if (enforcement) {
148
152
  const { decision, alert } = enforcement;
149
153
  const request = {
@@ -32,6 +32,11 @@ type UseGetValuesFromRangeProps<T extends CellData> = {
32
32
  * `CanvasGridProps["getValidationSourceValue"]`. Defaults to `getFormattedValue`.
33
33
  */
34
34
  getValidationSourceValue?: CanvasGridProps["getValidationSourceValue"];
35
+ /**
36
+ * Optional engine-side resolver for the list sources the tokenizer cannot resolve — see
37
+ * `CanvasGridProps["resolveValidationSourceValues"]`.
38
+ */
39
+ resolveValidationSourceValues?: CanvasGridProps["resolveValidationSourceValues"];
35
40
  };
36
41
  /**
37
42
  * Get all user entered values from a range
@@ -39,6 +44,6 @@ type UseGetValuesFromRangeProps<T extends CellData> = {
39
44
  * @param param0
40
45
  * @returns
41
46
  */
42
- export declare const useGetValuesFromRange: <T extends CellData>({ sheetId, getFormattedValue, getValidationValue, getSheetId, getSelectionsFromFormula, onRequestCalculate, getValidationSourceValue, }: UseGetValuesFromRangeProps<T>) => readonly [(address: string) => ConditionValue[], (address: string) => ConditionValue[], (address: string, rowIndex: number, columnIndex: number, targetSheetId?: number) => Promise<ConditionValue[]>];
47
+ export declare const useGetValuesFromRange: <T extends CellData>({ sheetId, getFormattedValue, getValidationValue, getSheetId, getSelectionsFromFormula, onRequestCalculate, getValidationSourceValue, resolveValidationSourceValues, }: UseGetValuesFromRangeProps<T>) => readonly [(address: string) => ConditionValue[], (address: string) => ConditionValue[], (address: string, rowIndex: number, columnIndex: number, targetSheetId?: number) => Promise<ConditionValue[]>];
43
48
  export {};
44
49
  //# sourceMappingURL=use-get-values-from-range.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-get-values-from-range.d.ts","sourceRoot":"","sources":["../../../hooks/use-get-values-from-range.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAGlB,KAAK,0BAA0B,CAAC,CAAC,SAAS,QAAQ,IAAI;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,iBAAiB,CACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,kBAAkB,EAAE,CAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,qBAAqB,KACzB,mBAAmB,CAAC;IACzB;;;OAGG;IACH,wBAAwB,CACtB,OAAO,EAAE,MAAM,GACd,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IACxC;;OAEG;IACH,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC1C,kBAAkB,CAAC,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;IAC3D;;;OAGG;IACH,wBAAwB,CAAC,EAAE,eAAe,CAAC,0BAA0B,CAAC,CAAC;CACxE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,QAAQ,EAAE,yIAQvD,0BAA0B,CAAC,CAAC,CAAC,yBAqDhB,MAAM,KAAG,cAAc,EAAE,YASzB,MAAM,KAAG,cAAc,EAAE,YAUxB,MAAM,YACL,MAAM,eACH,MAAM,kBACJ,MAAM,KACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CA4EjC,CAAC"}
1
+ {"version":3,"file":"use-get-values-from-range.d.ts","sourceRoot":"","sources":["../../../hooks/use-get-values-from-range.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACpB,MAAM,UAAU,CAAC;AAGlB,KAAK,0BAA0B,CAAC,CAAC,SAAS,QAAQ,IAAI;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,iBAAiB,CACf,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,kBAAkB,EAAE,CAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,qBAAqB,KACzB,mBAAmB,CAAC;IACzB;;;OAGG;IACH,wBAAwB,CACtB,OAAO,EAAE,MAAM,GACd,aAAa,CAAC,mBAAmB,CAAC,EAAE,CAAC;IACxC;;OAEG;IACH,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC1C,kBAAkB,CAAC,EAAE,eAAe,CAAC,oBAAoB,CAAC,CAAC;IAC3D;;;OAGG;IACH,wBAAwB,CAAC,EAAE,eAAe,CAAC,0BAA0B,CAAC,CAAC;IACvE;;;OAGG;IACH,6BAA6B,CAAC,EAAE,eAAe,CAAC,+BAA+B,CAAC,CAAC;CAClF,CAAC;AA0BF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,QAAQ,EAAE,wKASvD,0BAA0B,CAAC,CAAC,CAAC,yBAqDhB,MAAM,KAAG,cAAc,EAAE,YASzB,MAAM,KAAG,cAAc,EAAE,YAoBxB,MAAM,YACL,MAAM,eACH,MAAM,kBACJ,MAAM,KACpB,OAAO,CAAC,cAAc,EAAE,CAAC,CAkFjC,CAAC"}
@@ -1,13 +1,34 @@
1
1
  import { checkIfStringIsReference, iterateCells } from "@rowsncolumns/utils";
2
2
  import { useCallback } from "react";
3
3
  import { useCallbackRef } from "./use-callback-ref";
4
+ /**
5
+ * The reference / array functions the LEGACY calculator previews as an array through
6
+ * `onRequestCalculate(…, { isArray: true })` — the only sources it can resolve beyond plain
7
+ * ranges. The engine resolver (`resolveValidationSourceValues`) takes any function call.
8
+ */
9
+ const LEGACY_CALC_SOURCE = /(OFFSET|INDIRECT|FILTER|UNIQUE|SORT|SEQUENCE)\s*\(/i;
10
+ /** Any function call — `OFFSET(`, `INDEX(`, `_xlfn.XLOOKUP(`, `CHOOSE(` — in a list source. */
11
+ const FUNCTION_CALL = /[A-Z_\\][A-Z0-9_.]*\s*\(/i;
12
+ /**
13
+ * Dropdown options from a resolver's display texts: blanks dropped, duplicates collapsed,
14
+ * first-seen order kept — the same shape `getValuesFromRange` builds from cells.
15
+ */
16
+ const conditionValuesFromTexts = (texts) => {
17
+ const userEnteredValues = new Map();
18
+ for (const userEnteredValue of texts) {
19
+ if (userEnteredValue) {
20
+ userEnteredValues.set(userEnteredValue, { userEnteredValue });
21
+ }
22
+ }
23
+ return [...userEnteredValues.values()];
24
+ };
4
25
  /**
5
26
  * Get all user entered values from a range
6
27
  * used in data validation
7
28
  * @param param0
8
29
  * @returns
9
30
  */
10
- export const useGetValuesFromRange = ({ sheetId, getFormattedValue, getValidationValue, getSheetId, getSelectionsFromFormula, onRequestCalculate, getValidationSourceValue, }) => {
31
+ export const useGetValuesFromRange = ({ sheetId, getFormattedValue, getValidationValue, getSheetId, getSelectionsFromFormula, onRequestCalculate, getValidationSourceValue, resolveValidationSourceValues, }) => {
11
32
  // Windowed hosts supply an off-window-safe reader for validation source ranges
12
33
  // (dropdown options + commit checks) — the plain `getFormattedValue` only sees the
13
34
  // loaded viewport there. Defaults to `getFormattedValue`.
@@ -40,64 +61,72 @@ export const useGetValuesFromRange = ({ sheetId, getFormattedValue, getValidatio
40
61
  const getEffectiveValuesFromRange = useCallbackRef(useCallback((address) => {
41
62
  return [...getValuesFromRange(address, getValidationValue).values()];
42
63
  }, [getValidationValue, getValuesFromRange]));
64
+ /**
65
+ * The options a list (`ONE_OF_RANGE`) source denotes at a cell — the dropdown's items and
66
+ * the commit gate's accept list. A plain or sheet-qualified range, a range-type name and a
67
+ * structured reference resolve locally from the tokenizer's selections (off-window-safe
68
+ * through `readSourceValue`). Anything else — a formula-valued name (`=RegionList` defined
69
+ * as `=Table1[Region]` / `=OFFSET(…,COUNTA(…),1)`), any function call, a source the
70
+ * tokenizer found nothing in — goes through the engine resolver when the host supplies
71
+ * one; the legacy calculator previews its reference/array functions as an array; the
72
+ * tokenizer's selections are the last resort. An empty list means "nothing resolvable".
73
+ */
43
74
  const getFormattedValuesFromRangeAsync = useCallbackRef(useCallback(async (address, rowIndex, columnIndex, targetSheetId = sheetId) => {
44
75
  const trimmedAddress = address?.trim() ?? "";
76
+ if (!trimmedAddress) {
77
+ return [];
78
+ }
45
79
  const isFormula = trimmedAddress.startsWith("=");
46
80
  const formulaBody = isFormula ? trimmedAddress.slice(1) : trimmedAddress;
47
81
  const hasDirectReference = checkIfStringIsReference(formulaBody);
48
- const shouldCalc = isFormula &&
49
- /(OFFSET|INDIRECT|FILTER|UNIQUE|SORT|SEQUENCE)\s*\(/i.test(formulaBody);
50
- if (!onRequestCalculate || !shouldCalc) {
51
- const selections = getSelectionsFromFormula(address);
52
- if (hasDirectReference || selections.length) {
53
- return [...getValuesFromRange(address, readSourceValue).values()];
54
- }
55
- return [];
82
+ const isFunctionCall = FUNCTION_CALL.test(formulaBody);
83
+ const selections = hasDirectReference
84
+ ? []
85
+ : getSelectionsFromFormula(address);
86
+ if (hasDirectReference || (selections.length && !isFunctionCall)) {
87
+ return [...getValuesFromRange(address, readSourceValue).values()];
56
88
  }
57
- const result = await onRequestCalculate(trimmedAddress, targetSheetId, rowIndex, columnIndex, { isArray: true, disablePreview: true });
58
- const userEnteredValues = new Map();
59
- const pushValue = (value) => {
60
- if (value === null || value === undefined) {
61
- return;
89
+ if (resolveValidationSourceValues) {
90
+ const texts = await resolveValidationSourceValues(trimmedAddress, targetSheetId, rowIndex, columnIndex);
91
+ if (texts) {
92
+ return conditionValuesFromTexts(texts);
62
93
  }
63
- if (Array.isArray(value)) {
64
- for (const entry of value) {
65
- pushValue(entry);
94
+ }
95
+ if (onRequestCalculate && isFormula && LEGACY_CALC_SOURCE.test(formulaBody)) {
96
+ const result = await onRequestCalculate(trimmedAddress, targetSheetId, rowIndex, columnIndex, { isArray: true, disablePreview: true });
97
+ const userEnteredValues = new Map();
98
+ const pushValue = (value) => {
99
+ if (value === null || value === undefined) {
100
+ return;
66
101
  }
67
- return;
68
- }
69
- const userEnteredValue = value instanceof Date
70
- ? value.toISOString()
71
- : typeof value === "boolean"
72
- ? value
73
- ? "TRUE"
74
- : "FALSE"
75
- : String(value);
76
- userEnteredValues.set(userEnteredValue, { userEnteredValue });
77
- };
78
- if (Array.isArray(result)) {
79
- if (Array.isArray(result[0])) {
80
- for (const row of result) {
81
- for (const entry of row) {
102
+ if (Array.isArray(value)) {
103
+ for (const entry of value) {
82
104
  pushValue(entry);
83
105
  }
106
+ return;
84
107
  }
85
- }
86
- else {
87
- for (const entry of result) {
88
- pushValue(entry);
89
- }
90
- }
91
- }
92
- else {
108
+ const userEnteredValue = value instanceof Date
109
+ ? value.toISOString()
110
+ : typeof value === "boolean"
111
+ ? value
112
+ ? "TRUE"
113
+ : "FALSE"
114
+ : String(value);
115
+ userEnteredValues.set(userEnteredValue, { userEnteredValue });
116
+ };
93
117
  pushValue(result);
118
+ return [...userEnteredValues.values()];
119
+ }
120
+ if (selections.length) {
121
+ return [...getValuesFromRange(address, readSourceValue).values()];
94
122
  }
95
- return [...userEnteredValues.values()];
123
+ return [];
96
124
  }, [
97
125
  readSourceValue,
98
126
  getSelectionsFromFormula,
99
127
  getValuesFromRange,
100
128
  onRequestCalculate,
129
+ resolveValidationSourceValues,
101
130
  sheetId,
102
131
  ]));
103
132
  return [
@@ -1,3 +1,3 @@
1
1
  export const releaseDate =
2
2
  // @ts-ignore
3
- new Date("2026-10-28") ?? new Date("2025-06-18");
3
+ new Date("2026-10-29") ?? new Date("2025-06-18");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/spreadsheet",
3
- "version": "10.5.14",
3
+ "version": "10.5.15",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",