@rowsncolumns/utils 9.2.35

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 (154) hide show
  1. package/LICENSE.md +100 -0
  2. package/dist/cjs/array.d.ts +3 -0
  3. package/dist/cjs/array.d.ts.map +1 -0
  4. package/dist/cjs/array.js +19 -0
  5. package/dist/cjs/cell-data-helpers.d.ts +19 -0
  6. package/dist/cjs/cell-data-helpers.d.ts.map +1 -0
  7. package/dist/cjs/cell-data-helpers.js +64 -0
  8. package/dist/cjs/color.d.ts +20 -0
  9. package/dist/cjs/color.d.ts.map +1 -0
  10. package/dist/cjs/color.js +168 -0
  11. package/dist/cjs/colors.d.ts +2 -0
  12. package/dist/cjs/colors.d.ts.map +1 -0
  13. package/dist/cjs/colors.js +78 -0
  14. package/dist/cjs/conditional-format.d.ts +49 -0
  15. package/dist/cjs/conditional-format.d.ts.map +1 -0
  16. package/dist/cjs/conditional-format.js +227 -0
  17. package/dist/cjs/content-hash.d.ts +16 -0
  18. package/dist/cjs/content-hash.d.ts.map +1 -0
  19. package/dist/cjs/content-hash.js +72 -0
  20. package/dist/cjs/data-validation.d.ts +54 -0
  21. package/dist/cjs/data-validation.d.ts.map +1 -0
  22. package/dist/cjs/data-validation.js +249 -0
  23. package/dist/cjs/date.d.ts +15 -0
  24. package/dist/cjs/date.d.ts.map +1 -0
  25. package/dist/cjs/date.js +56 -0
  26. package/dist/cjs/dom.d.ts +10 -0
  27. package/dist/cjs/dom.d.ts.map +1 -0
  28. package/dist/cjs/dom.js +97 -0
  29. package/dist/cjs/formatter.d.ts +22 -0
  30. package/dist/cjs/formatter.d.ts.map +1 -0
  31. package/dist/cjs/formatter.js +264 -0
  32. package/dist/cjs/formula-refs.d.ts +7 -0
  33. package/dist/cjs/formula-refs.d.ts.map +1 -0
  34. package/dist/cjs/formula-refs.js +54 -0
  35. package/dist/cjs/formula.d.ts +80 -0
  36. package/dist/cjs/formula.d.ts.map +1 -0
  37. package/dist/cjs/formula.js +401 -0
  38. package/dist/cjs/index.d.ts +24 -0
  39. package/dist/cjs/index.d.ts.map +1 -0
  40. package/dist/cjs/index.js +26 -0
  41. package/dist/cjs/locale.d.ts +34 -0
  42. package/dist/cjs/locale.d.ts.map +1 -0
  43. package/dist/cjs/locale.js +260 -0
  44. package/dist/cjs/log.d.ts +4 -0
  45. package/dist/cjs/log.d.ts.map +1 -0
  46. package/dist/cjs/log.js +28 -0
  47. package/dist/cjs/memoize.d.ts +18 -0
  48. package/dist/cjs/memoize.d.ts.map +1 -0
  49. package/dist/cjs/memoize.js +128 -0
  50. package/dist/cjs/numbers.d.ts +19 -0
  51. package/dist/cjs/numbers.d.ts.map +1 -0
  52. package/dist/cjs/numbers.js +47 -0
  53. package/dist/cjs/selection.d.ts +88 -0
  54. package/dist/cjs/selection.d.ts.map +1 -0
  55. package/dist/cjs/selection.js +368 -0
  56. package/dist/cjs/shared-string-canonical.d.ts +18 -0
  57. package/dist/cjs/shared-string-canonical.d.ts.map +1 -0
  58. package/dist/cjs/shared-string-canonical.js +74 -0
  59. package/dist/cjs/sort.d.ts +15 -0
  60. package/dist/cjs/sort.d.ts.map +1 -0
  61. package/dist/cjs/sort.js +44 -0
  62. package/dist/cjs/string.d.ts +74 -0
  63. package/dist/cjs/string.d.ts.map +1 -0
  64. package/dist/cjs/string.js +237 -0
  65. package/dist/cjs/table-styles.d.ts +56 -0
  66. package/dist/cjs/table-styles.d.ts.map +1 -0
  67. package/dist/cjs/table-styles.js +362 -0
  68. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  69. package/dist/cjs/typeguards.d.ts +4 -0
  70. package/dist/cjs/typeguards.d.ts.map +1 -0
  71. package/dist/cjs/typeguards.js +15 -0
  72. package/dist/cjs/uuid.d.ts +8 -0
  73. package/dist/cjs/uuid.d.ts.map +1 -0
  74. package/dist/cjs/uuid.js +30 -0
  75. package/dist/cjs/vitest.config.d.ts +3 -0
  76. package/dist/cjs/vitest.config.d.ts.map +1 -0
  77. package/dist/cjs/vitest.config.js +9 -0
  78. package/dist/esm/array.d.ts +3 -0
  79. package/dist/esm/array.d.ts.map +1 -0
  80. package/dist/esm/array.js +14 -0
  81. package/dist/esm/cell-data-helpers.d.ts +19 -0
  82. package/dist/esm/cell-data-helpers.d.ts.map +1 -0
  83. package/dist/esm/cell-data-helpers.js +39 -0
  84. package/dist/esm/color.d.ts +20 -0
  85. package/dist/esm/color.d.ts.map +1 -0
  86. package/dist/esm/color.js +152 -0
  87. package/dist/esm/colors.d.ts +2 -0
  88. package/dist/esm/colors.d.ts.map +1 -0
  89. package/dist/esm/colors.js +75 -0
  90. package/dist/esm/conditional-format.d.ts +49 -0
  91. package/dist/esm/conditional-format.d.ts.map +1 -0
  92. package/dist/esm/conditional-format.js +219 -0
  93. package/dist/esm/content-hash.d.ts +16 -0
  94. package/dist/esm/content-hash.d.ts.map +1 -0
  95. package/dist/esm/content-hash.js +68 -0
  96. package/dist/esm/data-validation.d.ts +54 -0
  97. package/dist/esm/data-validation.d.ts.map +1 -0
  98. package/dist/esm/data-validation.js +237 -0
  99. package/dist/esm/date.d.ts +15 -0
  100. package/dist/esm/date.d.ts.map +1 -0
  101. package/dist/esm/date.js +46 -0
  102. package/dist/esm/dom.d.ts +10 -0
  103. package/dist/esm/dom.d.ts.map +1 -0
  104. package/dist/esm/dom.js +90 -0
  105. package/dist/esm/formatter.d.ts +22 -0
  106. package/dist/esm/formatter.d.ts.map +1 -0
  107. package/dist/esm/formatter.js +253 -0
  108. package/dist/esm/formula-refs.d.ts +7 -0
  109. package/dist/esm/formula-refs.d.ts.map +1 -0
  110. package/dist/esm/formula-refs.js +49 -0
  111. package/dist/esm/formula.d.ts +80 -0
  112. package/dist/esm/formula.d.ts.map +1 -0
  113. package/dist/esm/formula.js +298 -0
  114. package/dist/esm/index.d.ts +24 -0
  115. package/dist/esm/index.d.ts.map +1 -0
  116. package/dist/esm/index.js +23 -0
  117. package/dist/esm/locale.d.ts +34 -0
  118. package/dist/esm/locale.d.ts.map +1 -0
  119. package/dist/esm/locale.js +223 -0
  120. package/dist/esm/log.d.ts +4 -0
  121. package/dist/esm/log.d.ts.map +1 -0
  122. package/dist/esm/log.js +3 -0
  123. package/dist/esm/memoize.d.ts +18 -0
  124. package/dist/esm/memoize.d.ts.map +1 -0
  125. package/dist/esm/memoize.js +95 -0
  126. package/dist/esm/numbers.d.ts +19 -0
  127. package/dist/esm/numbers.d.ts.map +1 -0
  128. package/dist/esm/numbers.js +40 -0
  129. package/dist/esm/selection.d.ts +88 -0
  130. package/dist/esm/selection.d.ts.map +1 -0
  131. package/dist/esm/selection.js +311 -0
  132. package/dist/esm/shared-string-canonical.d.ts +18 -0
  133. package/dist/esm/shared-string-canonical.d.ts.map +1 -0
  134. package/dist/esm/shared-string-canonical.js +68 -0
  135. package/dist/esm/sort.d.ts +15 -0
  136. package/dist/esm/sort.d.ts.map +1 -0
  137. package/dist/esm/sort.js +39 -0
  138. package/dist/esm/string.d.ts +74 -0
  139. package/dist/esm/string.d.ts.map +1 -0
  140. package/dist/esm/string.js +213 -0
  141. package/dist/esm/table-styles.d.ts +56 -0
  142. package/dist/esm/table-styles.d.ts.map +1 -0
  143. package/dist/esm/table-styles.js +367 -0
  144. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -0
  145. package/dist/esm/typeguards.d.ts +4 -0
  146. package/dist/esm/typeguards.d.ts.map +1 -0
  147. package/dist/esm/typeguards.js +7 -0
  148. package/dist/esm/uuid.d.ts +8 -0
  149. package/dist/esm/uuid.d.ts.map +1 -0
  150. package/dist/esm/uuid.js +25 -0
  151. package/dist/esm/vitest.config.d.ts +3 -0
  152. package/dist/esm/vitest.config.d.ts.map +1 -0
  153. package/dist/esm/vitest.config.js +7 -0
  154. package/package.json +49 -0
@@ -0,0 +1,95 @@
1
+ import { contentHash } from "./content-hash";
2
+ import { isNil } from "./typeguards";
3
+ export const memoize = (fn) => {
4
+ const cache = new Map();
5
+ return (...args) => {
6
+ const key = String(args);
7
+ if (cache.has(key)) {
8
+ return cache.get(key);
9
+ }
10
+ const value = fn.call(undefined, ...args);
11
+ cache.set(key, value);
12
+ return value;
13
+ };
14
+ };
15
+ /**
16
+ * Mint a stable cellXfs key ("sid") for a CellFormat. The key is
17
+ * `contentHash` (FNV-1a-64 → 8-char base62) over a delimiter-separated
18
+ * canonical serialization of the format's defined properties — the
19
+ * previous 31-bit rolling int hash silently collided two different
20
+ * formats onto one registry key (first-writer wins, so e.g. a red
21
+ * format could hijack a blue one on import).
22
+ *
23
+ * Sids are minted at import/edit time and persisted in the doc's
24
+ * cellXfs map together with the cell references that carry them —
25
+ * readback resolves a stored sid against the stored map, never by
26
+ * re-hashing. Changing this algorithm therefore only affects newly
27
+ * minted keys; existing docs keep resolving their old sids.
28
+ */
29
+ export const getCellXfsKey = (userObj) => {
30
+ if (userObj === null || userObj === undefined) {
31
+ return String(0);
32
+ }
33
+ const obj = definedProps(userObj);
34
+ let serialized = "";
35
+ const hashString = (str) => {
36
+ // `\u0001` terminates every atom so adjacent strings can't merge
37
+ // into the same serialization (`{a:"bc"}` vs `{ab:"c"}`).
38
+ serialized += str + "\u0001";
39
+ };
40
+ const hashValue = (value) => {
41
+ if (value === null || value === undefined)
42
+ return;
43
+ if (typeof value === "object") {
44
+ // Custom serialization for known object types
45
+ const sortedKeys = Object.keys(value).sort();
46
+ serialized += "\u0002";
47
+ for (const key of sortedKeys) {
48
+ hashString(key);
49
+ hashValue(value[key]);
50
+ }
51
+ serialized += "\u0003";
52
+ }
53
+ else {
54
+ hashString(String(value));
55
+ }
56
+ };
57
+ // Hash each property in a consistent order. The list MUST cover every
58
+ // field that distinguishes one CellFormat from another — when two
59
+ // different formats hash to the same key, the cellXfs registry
60
+ // dedupes them, so cells lose any field that wasn't in this list.
61
+ const props = [
62
+ "backgroundColor",
63
+ "borders",
64
+ "textFormat",
65
+ "numberFormat",
66
+ "horizontalAlignment",
67
+ "verticalAlignment",
68
+ "wrapStrategy",
69
+ "indent",
70
+ "textRotation",
71
+ "shrinkToFit",
72
+ "relativeIndent",
73
+ "fillPattern",
74
+ "foregroundColor",
75
+ "gradient",
76
+ ];
77
+ for (const prop of props) {
78
+ if (obj[prop] !== undefined) {
79
+ hashString(String(prop)); // Include property name in hash
80
+ hashValue(obj[prop]);
81
+ }
82
+ }
83
+ return contentHash(serialized);
84
+ };
85
+ export const definedProps = (obj) => {
86
+ if (isNil(obj))
87
+ return obj;
88
+ const result = {};
89
+ for (const key in obj) {
90
+ if (!isNil(obj[key])) {
91
+ result[key] = obj[key];
92
+ }
93
+ }
94
+ return result;
95
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convert number to alphabet
3
+ * @param i
4
+ */
5
+ export declare const number2Alpha: (i: number) => string;
6
+ /**
7
+ * Converts a letter to number
8
+ * A = 1
9
+ * B => 2
10
+ * @param letters
11
+ */
12
+ export declare const alpha2number: (letters: string) => number;
13
+ /**
14
+ * Get min max of an array
15
+ * @param arr
16
+ */
17
+ export declare const getMinMaxArray: (arr: number[]) => number[];
18
+ export declare const isNumber: (value: any) => value is number;
19
+ //# sourceMappingURL=numbers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbers.d.ts","sourceRoot":"","sources":["../../numbers.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MAIxC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,MAE9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,aAa3C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,OAAO,GAAG,KAAG,KAAK,IAAI,MAAuB,CAAC"}
@@ -0,0 +1,40 @@
1
+ // Pre-compute alphabet string to avoid repeated string access
2
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
3
+ /**
4
+ * Convert number to alphabet
5
+ * @param i
6
+ */
7
+ export const number2Alpha = (i) => {
8
+ return ((i >= 26 ? number2Alpha(((i / 26) >> 0) - 1) : "") + ALPHABET[i % 26 >> 0]);
9
+ };
10
+ /**
11
+ * Converts a letter to number
12
+ * A = 1
13
+ * B => 2
14
+ * @param letters
15
+ */
16
+ export const alpha2number = (letters) => {
17
+ return letters.split("").reduce((r, a) => r * 26 + parseInt(a, 36) - 9, 0);
18
+ };
19
+ /**
20
+ * Get min max of an array
21
+ * @param arr
22
+ */
23
+ export const getMinMaxArray = (arr) => {
24
+ if (!arr.length)
25
+ return [0, 0];
26
+ // Loop, not spread — passing >~65k elements as call arguments throws
27
+ // `RangeError: Maximum call stack size exceeded` (selection-sized arrays
28
+ // reach millions).
29
+ let min = arr[0];
30
+ let max = arr[0];
31
+ for (let i = 1; i < arr.length; i++) {
32
+ const v = arr[i];
33
+ if (v < min)
34
+ min = v;
35
+ if (v > max)
36
+ max = v;
37
+ }
38
+ return [min, max];
39
+ };
40
+ export const isNumber = (value) => !isNaN(value);
@@ -0,0 +1,88 @@
1
+ import type { CellInterface, GridRange, SheetRange, SelectionArea } from "@rowsncolumns/common-types";
2
+ import { type ParsedToken } from "@rowsncolumns/calculator";
3
+ export type SelectionWithSheetName = SelectionArea & {
4
+ sheetName?: string;
5
+ };
6
+ export type SelectionFromTableResolver = (sheetId: number, activeCell?: CellInterface, tableName?: string, columnNames?: string[], thisRow?: boolean, specialItem?: ParsedToken["specialItem"]) => SelectionWithSheetName | undefined;
7
+ export type SelectionFromNamedRangeResolver = (variableName: string) => SelectionWithSheetName | unknown;
8
+ export declare const isSelection: (sel: unknown) => sel is SelectionWithSheetName;
9
+ export declare const MAX_ROW_COUNT = 1048576;
10
+ export declare const MAX_COLUMN_COUNT = 16384;
11
+ export declare const DEFAULT_ROW_COUNT = 1000;
12
+ export declare const DEFAULT_COLUMN_COUNT = 100;
13
+ export declare const DEFAULT_ROW_HEIGHT = 21;
14
+ export declare const DEFAULT_COLUMN_WIDTH = 100;
15
+ export declare const DEFAULT_ACTIVE_CELL: CellInterface;
16
+ export declare const addressToSelection: (address: string, rowCount?: number, columnCount?: number) => SelectionArea | undefined;
17
+ export declare const addressToSheetRange: (address: string, sheetId: number, rowCount?: number, columnCount?: number) => SheetRange | undefined;
18
+ /**
19
+ * Convert a selection object to A1 address form (optionally sheet-qualified).
20
+ */
21
+ export declare const selectionToAddress: (selection: SelectionArea | undefined, sheetName?: string, merges?: GridRange[] | null, rowCount?: number, columnCount?: number) => string | undefined;
22
+ export declare const checkIfStringIsReference: (value: string | undefined) => boolean;
23
+ export declare const generateSelectionsFromFormula: (value: string, _sheetId?: number, currentSheetName?: string, activeCell?: CellInterface, rowCount?: number, columnCount?: number, tokenizer?: (value: string | undefined, sheetName?: string) => ParsedToken[], getSelectionFromTable?: SelectionFromTableResolver, getSelectionFromNamedRange?: SelectionFromNamedRangeResolver) => SelectionWithSheetName[];
24
+ /**
25
+ * Check if two ranges are equal.
26
+ */
27
+ export declare const isAreasEqual: (area1: GridRange | undefined, area2: GridRange | undefined) => boolean;
28
+ /**
29
+ * Check if a cell is inside the provided range bounds.
30
+ */
31
+ export declare const isCellWithinBounds: (cell: CellInterface, bounds?: GridRange) => boolean;
32
+ /**
33
+ * Return a single-cell selection for the active cell.
34
+ */
35
+ export declare const selectionFromActiveCell: (activeCell: CellInterface | null) => SelectionArea[];
36
+ /**
37
+ * Check if two ranges overlap.
38
+ */
39
+ export declare const areaIntersects: (area1: GridRange, area2: GridRange | undefined) => boolean;
40
+ /**
41
+ * Check if `needle` range is fully inside `haystack`.
42
+ */
43
+ export declare const areaInsideArea: (needle: GridRange, haystack: GridRange) => boolean;
44
+ /**
45
+ * Intersection of two ranges, or `null` when they don't overlap.
46
+ */
47
+ export declare const areaIntersection: (area1: GridRange, area2: GridRange) => GridRange | null;
48
+ /**
49
+ * Re-scope a rule's ranges after a drag-move of `from` → `to` on `sheetId`, so
50
+ * range-scoped rules (conditional formats, data validations) travel with the
51
+ * moved cells the way Excel and Google Sheets carry them:
52
+ *
53
+ * - a range on another sheet, or untouched by the move, is kept as-is
54
+ * - a range that travelled entirely inside the moved area is shifted with it
55
+ * - a range that only partially overlaps the moved area keeps its original
56
+ * scope (the source cells left behind keep the rule) and gains the shifted
57
+ * overlap at the destination — unless one of the rule's ranges already
58
+ * covers that destination
59
+ *
60
+ * Returns the new ranges, or `null` when the move leaves every range
61
+ * untouched, so callers can skip the rule update entirely (no history entry,
62
+ * no re-evaluation).
63
+ */
64
+ export declare const rescopeSheetRangesOnMove: (ranges: readonly SheetRange[], sheetId: number, from: GridRange, to: GridRange) => SheetRange[] | null;
65
+ /**
66
+ * Convert a cell coordinate into a single-cell range.
67
+ */
68
+ export declare const cellToArea: ({ rowIndex, columnIndex, }: CellInterface) => GridRange;
69
+ /**
70
+ * Convert font size from points to pixels.
71
+ */
72
+ export declare const pointToPixel: (pt: number) => number;
73
+ /**
74
+ * Convert font size from pixels to points.
75
+ */
76
+ export declare const pixelToPoint: (px: number) => number;
77
+ /**
78
+ * Resolve a table column name to absolute column index.
79
+ */
80
+ export declare const getColumnIndexFromColumnName: (table: {
81
+ columns?: Array<{
82
+ name?: string | null;
83
+ }> | null;
84
+ range: {
85
+ startColumnIndex: number;
86
+ };
87
+ }, columnName: string | undefined) => number | undefined;
88
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../selection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,311 @@
1
+ import { desanitizeSheetName } from "./string";
2
+ import { isNil } from "./typeguards";
3
+ import { addressToCell, cellToAddress, sanitizeSheetName, selectionBoundsFromCells, } from "./string";
4
+ export const isSelection = (sel) => {
5
+ return !!sel?.range;
6
+ };
7
+ export const MAX_ROW_COUNT = 1_048_576;
8
+ export const MAX_COLUMN_COUNT = 16_384;
9
+ export const DEFAULT_ROW_COUNT = 1000;
10
+ export const DEFAULT_COLUMN_COUNT = 100;
11
+ export const DEFAULT_ROW_HEIGHT = 21;
12
+ export const DEFAULT_COLUMN_WIDTH = 100;
13
+ export const DEFAULT_ACTIVE_CELL = Object.freeze({
14
+ rowIndex: 1,
15
+ columnIndex: 1,
16
+ });
17
+ export const addressToSelection = (address, rowCount = MAX_ROW_COUNT, columnCount = MAX_COLUMN_COUNT) => {
18
+ let [from, to] = address.split(":");
19
+ if (!from)
20
+ return undefined;
21
+ if (!to)
22
+ to = from;
23
+ const fromCell = addressToCell(from, {
24
+ position: "start",
25
+ rowCount,
26
+ columnCount,
27
+ });
28
+ const toCell = addressToCell(to, {
29
+ position: "end",
30
+ rowCount,
31
+ columnCount,
32
+ });
33
+ if (!fromCell || !toCell)
34
+ return undefined;
35
+ return { range: selectionBoundsFromCells(fromCell, toCell) };
36
+ };
37
+ export const addressToSheetRange = (address, sheetId, rowCount = MAX_ROW_COUNT, columnCount = MAX_COLUMN_COUNT) => {
38
+ const selection = addressToSelection(address, rowCount, columnCount);
39
+ if (!selection) {
40
+ return undefined;
41
+ }
42
+ return {
43
+ sheetId,
44
+ ...selection.range,
45
+ };
46
+ };
47
+ /**
48
+ * Convert a selection object to A1 address form (optionally sheet-qualified).
49
+ */
50
+ export const selectionToAddress = (selection, sheetName, merges = [], rowCount = MAX_ROW_COUNT, columnCount = MAX_COLUMN_COUNT) => {
51
+ if (!selection) {
52
+ return undefined;
53
+ }
54
+ const mergedCell = merges?.find((merge) => isAreasEqual(selection.range, merge));
55
+ const { startRowIndex, startColumnIndex, endRowIndex, endColumnIndex } = mergedCell
56
+ ? cellToArea({
57
+ rowIndex: mergedCell.startRowIndex,
58
+ columnIndex: mergedCell.startColumnIndex,
59
+ })
60
+ : selection.range;
61
+ const isFullColumn = startRowIndex === 1 && endRowIndex === rowCount;
62
+ const isFullRow = startColumnIndex === 1 && endColumnIndex === columnCount;
63
+ const from = cellToAddress({
64
+ rowIndex: startRowIndex,
65
+ columnIndex: startColumnIndex,
66
+ }, undefined, undefined, isFullColumn, isFullRow);
67
+ const to = cellToAddress({
68
+ rowIndex: endRowIndex,
69
+ columnIndex: endColumnIndex,
70
+ }, undefined, undefined, isFullColumn, isFullRow);
71
+ const cellAddress = from === to && !isFullColumn && !isFullRow ? from : `${from}:${to || ""}`;
72
+ return `${sheetName ? sanitizeSheetName(sheetName) + "!" : ""}${cellAddress}`;
73
+ };
74
+ export const checkIfStringIsReference = (value) => {
75
+ if (!value)
76
+ return false;
77
+ const v = String(value).trim();
78
+ const expr = v.startsWith("=") ? v.slice(1) : v;
79
+ const refRegex = /^(?:[^!]+!)?\$?[A-Z]+\$?\d+(?::\$?[A-Z]+\$?\d+)?$/i;
80
+ return refRegex.test(expr);
81
+ };
82
+ export const generateSelectionsFromFormula = (value, _sheetId, currentSheetName, activeCell, rowCount = MAX_ROW_COUNT, columnCount = MAX_COLUMN_COUNT, tokenizer, getSelectionFromTable, getSelectionFromNamedRange) => {
83
+ const selections = [];
84
+ if (!value)
85
+ return selections;
86
+ if (tokenizer && !isNil(_sheetId)) {
87
+ const sheetId = _sheetId;
88
+ const tokens = tokenizer(value, currentSheetName) ?? [];
89
+ for (const token of tokens) {
90
+ if (token.structured && getSelectionFromTable) {
91
+ const structuredSelection = getSelectionFromTable(sheetId, activeCell, token.tableName, token.columnNames, token.thisRow, token.specialItem);
92
+ if (structuredSelection && isSelection(structuredSelection)) {
93
+ selections.push(structuredSelection);
94
+ }
95
+ }
96
+ if (token.isNamedRange && getSelectionFromNamedRange) {
97
+ const namedRangeSelection = getSelectionFromNamedRange(token.image);
98
+ if (namedRangeSelection && isSelection(namedRangeSelection)) {
99
+ selections.push(namedRangeSelection);
100
+ }
101
+ }
102
+ if (token.selection) {
103
+ const address = token.image.slice(token.image.indexOf("!") + 1);
104
+ const selectionRange = addressToSelection(address, rowCount, columnCount);
105
+ if (selectionRange) {
106
+ selections.push({
107
+ ...selectionRange,
108
+ sheetName: desanitizeSheetName(token.sheetName ?? currentSheetName),
109
+ });
110
+ }
111
+ }
112
+ }
113
+ return selections;
114
+ }
115
+ const input = value.startsWith("=") ? value.slice(1) : value;
116
+ const refPattern = /((?:'[^']+'|[^'!,]+)!)?\$?[A-Z]+\$?\d+(?::\$?[A-Z]+\$?\d+)?/g;
117
+ let m;
118
+ while ((m = refPattern.exec(input))) {
119
+ const token = m[0];
120
+ const [maybeSheet, addr] = token.includes("!")
121
+ ? [
122
+ token.slice(0, token.indexOf("!")),
123
+ token.slice(token.indexOf("!") + 1),
124
+ ]
125
+ : [undefined, token];
126
+ const sel = addressToSelection(addr, rowCount, columnCount);
127
+ if (sel) {
128
+ selections.push({
129
+ ...sel,
130
+ sheetName: maybeSheet
131
+ ? maybeSheet.replace(/!$/, "").replace(/^'|'$/g, "") || undefined
132
+ : currentSheetName,
133
+ });
134
+ }
135
+ }
136
+ return selections;
137
+ };
138
+ /**
139
+ * Check if two ranges are equal.
140
+ */
141
+ export const isAreasEqual = (area1, area2) => {
142
+ if (area1 === undefined || area2 === undefined) {
143
+ return false;
144
+ }
145
+ return (area1.endRowIndex === area2.endRowIndex &&
146
+ area1.startRowIndex === area2.startRowIndex &&
147
+ area1.startColumnIndex === area2.startColumnIndex &&
148
+ area1.endColumnIndex === area2.endColumnIndex);
149
+ };
150
+ /**
151
+ * Check if a cell is inside the provided range bounds.
152
+ */
153
+ export const isCellWithinBounds = (cell, bounds) => {
154
+ if (!bounds) {
155
+ return false;
156
+ }
157
+ if (cell.rowIndex < bounds.startRowIndex ||
158
+ cell.rowIndex > bounds.endRowIndex) {
159
+ return false;
160
+ }
161
+ if (cell.columnIndex < bounds.startColumnIndex ||
162
+ cell.columnIndex > bounds.endColumnIndex) {
163
+ return false;
164
+ }
165
+ return true;
166
+ };
167
+ /**
168
+ * Return a single-cell selection for the active cell.
169
+ */
170
+ export const selectionFromActiveCell = (activeCell) => {
171
+ if (!activeCell)
172
+ return [];
173
+ return [
174
+ {
175
+ range: {
176
+ startRowIndex: activeCell.rowIndex,
177
+ startColumnIndex: activeCell.columnIndex,
178
+ endRowIndex: activeCell.rowIndex,
179
+ endColumnIndex: activeCell.columnIndex,
180
+ },
181
+ },
182
+ ];
183
+ };
184
+ /**
185
+ * Check if two ranges overlap.
186
+ */
187
+ export const areaIntersects = (area1, area2) => {
188
+ if (!area2) {
189
+ return false;
190
+ }
191
+ if (area1.startColumnIndex > area2.endColumnIndex ||
192
+ area2.startColumnIndex > area1.endColumnIndex) {
193
+ return false;
194
+ }
195
+ if (area1.startRowIndex > area2.endRowIndex ||
196
+ area2.startRowIndex > area1.endRowIndex) {
197
+ return false;
198
+ }
199
+ return true;
200
+ };
201
+ /**
202
+ * Check if `needle` range is fully inside `haystack`.
203
+ */
204
+ export const areaInsideArea = (needle, haystack) => {
205
+ return (needle.startRowIndex >= haystack.startRowIndex &&
206
+ needle.endRowIndex <= haystack.endRowIndex &&
207
+ needle.startColumnIndex >= haystack.startColumnIndex &&
208
+ needle.endColumnIndex <= haystack.endColumnIndex);
209
+ };
210
+ /**
211
+ * Intersection of two ranges, or `null` when they don't overlap.
212
+ */
213
+ export const areaIntersection = (area1, area2) => {
214
+ const startRowIndex = Math.max(area1.startRowIndex, area2.startRowIndex);
215
+ const endRowIndex = Math.min(area1.endRowIndex, area2.endRowIndex);
216
+ const startColumnIndex = Math.max(area1.startColumnIndex, area2.startColumnIndex);
217
+ const endColumnIndex = Math.min(area1.endColumnIndex, area2.endColumnIndex);
218
+ if (startRowIndex > endRowIndex || startColumnIndex > endColumnIndex) {
219
+ return null;
220
+ }
221
+ return { startRowIndex, endRowIndex, startColumnIndex, endColumnIndex };
222
+ };
223
+ /**
224
+ * Re-scope a rule's ranges after a drag-move of `from` → `to` on `sheetId`, so
225
+ * range-scoped rules (conditional formats, data validations) travel with the
226
+ * moved cells the way Excel and Google Sheets carry them:
227
+ *
228
+ * - a range on another sheet, or untouched by the move, is kept as-is
229
+ * - a range that travelled entirely inside the moved area is shifted with it
230
+ * - a range that only partially overlaps the moved area keeps its original
231
+ * scope (the source cells left behind keep the rule) and gains the shifted
232
+ * overlap at the destination — unless one of the rule's ranges already
233
+ * covers that destination
234
+ *
235
+ * Returns the new ranges, or `null` when the move leaves every range
236
+ * untouched, so callers can skip the rule update entirely (no history entry,
237
+ * no re-evaluation).
238
+ */
239
+ export const rescopeSheetRangesOnMove = (ranges, sheetId, from, to) => {
240
+ const rowDelta = to.startRowIndex - from.startRowIndex;
241
+ const columnDelta = to.startColumnIndex - from.startColumnIndex;
242
+ if (rowDelta === 0 && columnDelta === 0) {
243
+ return null;
244
+ }
245
+ const shift = (range) => ({
246
+ ...range,
247
+ startRowIndex: range.startRowIndex + rowDelta,
248
+ endRowIndex: range.endRowIndex + rowDelta,
249
+ startColumnIndex: range.startColumnIndex + columnDelta,
250
+ endColumnIndex: range.endColumnIndex + columnDelta,
251
+ });
252
+ let changed = false;
253
+ const next = [];
254
+ const shiftedOverlaps = [];
255
+ for (const range of ranges) {
256
+ if (range.sheetId !== sheetId) {
257
+ next.push(range);
258
+ continue;
259
+ }
260
+ const overlap = areaIntersection(range, from);
261
+ if (!overlap) {
262
+ next.push(range);
263
+ continue;
264
+ }
265
+ if (areaInsideArea(range, from)) {
266
+ next.push(shift(range));
267
+ changed = true;
268
+ continue;
269
+ }
270
+ next.push(range);
271
+ shiftedOverlaps.push(shift({ ...overlap, sheetId: range.sheetId }));
272
+ }
273
+ for (const shifted of shiftedOverlaps) {
274
+ const covered = next.some((range) => range.sheetId === sheetId && areaInsideArea(shifted, range));
275
+ if (covered) {
276
+ continue;
277
+ }
278
+ next.push(shifted);
279
+ changed = true;
280
+ }
281
+ return changed ? next : null;
282
+ };
283
+ /**
284
+ * Convert a cell coordinate into a single-cell range.
285
+ */
286
+ export const cellToArea = ({ rowIndex, columnIndex, }) => {
287
+ return {
288
+ startRowIndex: rowIndex,
289
+ startColumnIndex: columnIndex,
290
+ endColumnIndex: columnIndex,
291
+ endRowIndex: rowIndex,
292
+ };
293
+ };
294
+ /**
295
+ * Convert font size from points to pixels.
296
+ */
297
+ export const pointToPixel = (pt) => Math.round((13 / 10) * pt);
298
+ /**
299
+ * Convert font size from pixels to points.
300
+ */
301
+ export const pixelToPoint = (px) => Math.round((px / 13) * 10);
302
+ /**
303
+ * Resolve a table column name to absolute column index.
304
+ */
305
+ export const getColumnIndexFromColumnName = (table, columnName) => {
306
+ const fromIndex = table.columns?.findIndex((col) => col.name === columnName);
307
+ if (fromIndex == null || fromIndex === -1) {
308
+ return undefined;
309
+ }
310
+ return table.range.startColumnIndex + fromIndex;
311
+ };
@@ -0,0 +1,18 @@
1
+ import type { TextFormatRun } from "@rowsncolumns/common-types";
2
+ /**
3
+ * Deterministic string form of a (text, runs) pair, suitable as input to
4
+ * `contentHash` for rich shared-string entries.
5
+ *
6
+ * Two rich entries that render identically produce the same canonical string
7
+ * regardless of property ordering or absent-vs-false boolean variations. The
8
+ * leading `RICH_MARKER` (control characters) guarantees a rich canonical can
9
+ * never equal any plain shared-string text, so plain↔rich never collide on
10
+ * the downstream content hash.
11
+ *
12
+ * IMPORTANT: This is the dedup key for shared-string IDs across sessions.
13
+ * Changing the format, key order, or stripping rules invalidates every
14
+ * existing rich-string ID. Mirror updates here in any other-language port
15
+ * (e.g. Python) used in the same collaboration session.
16
+ */
17
+ export declare const canonicalizeRichSharedString: (text: string, runs: readonly TextFormatRun[]) => string;
18
+ //# sourceMappingURL=shared-string-canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-string-canonical.d.ts","sourceRoot":"","sources":["../../shared-string-canonical.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,4BAA4B,CAAC;AAkDpC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GACvC,MAAM,MAAM,EACZ,MAAM,SAAS,aAAa,EAAE,KAC7B,MAGF,CAAC"}
@@ -0,0 +1,68 @@
1
+ // U+0001 (START OF HEADING). Cell text shouldn't contain control chars, so
2
+ // using it as a separator and as the `RICH_MARKER` prefix guarantees the rich
3
+ // canonical string can never collide with any plain shared-string value.
4
+ // Built via fromCharCode rather than a string literal so the control byte is
5
+ // visible in source.
6
+ const SEP = String.fromCharCode(1);
7
+ const RICH_MARKER = `${SEP}RICH${SEP}`;
8
+ const canonicalizeColor = (color) => {
9
+ if (color == null)
10
+ return "";
11
+ if (typeof color === "string")
12
+ return `s:${color}`;
13
+ const tint = color.tint == null ? "" : color.tint;
14
+ return `t:${color.theme}:${tint}`;
15
+ };
16
+ // Fixed key order. Skips null/undefined and false-equivalent booleans so a
17
+ // producer that writes `{ bold: true }` hashes identically to one that writes
18
+ // `{ bold: true, italic: false }`. Any new field added to `TextFormat` MUST be
19
+ // appended here, otherwise rich entries differing only in the new field will
20
+ // collapse and dedup incorrectly.
21
+ const canonicalizeFormat = (format) => {
22
+ if (!format)
23
+ return "";
24
+ const parts = [];
25
+ if (format.bold)
26
+ parts.push("b");
27
+ if (format.italic)
28
+ parts.push("i");
29
+ if (format.strikethrough)
30
+ parts.push("s");
31
+ if (format.underline)
32
+ parts.push("u");
33
+ if (format.color != null)
34
+ parts.push(`c=${canonicalizeColor(format.color)}`);
35
+ if (format.fontFamily)
36
+ parts.push(`ff=${format.fontFamily}`);
37
+ if (format.fontSize != null)
38
+ parts.push(`fs=${format.fontSize}`);
39
+ if (format.baselineOffset)
40
+ parts.push(`bo=${format.baselineOffset}`);
41
+ return parts.join(",");
42
+ };
43
+ const canonicalizeMention = (mention) => `v=${mention.value},l=${mention.label}`;
44
+ const canonicalizeRun = (run) => {
45
+ if (run.nodeType === "mention") {
46
+ return `M:${run.startIndex}:${run.endIndex}:${canonicalizeMention(run.mention)}`;
47
+ }
48
+ return `T:${run.startIndex}:${run.endIndex}:${canonicalizeFormat(run.format)}`;
49
+ };
50
+ /**
51
+ * Deterministic string form of a (text, runs) pair, suitable as input to
52
+ * `contentHash` for rich shared-string entries.
53
+ *
54
+ * Two rich entries that render identically produce the same canonical string
55
+ * regardless of property ordering or absent-vs-false boolean variations. The
56
+ * leading `RICH_MARKER` (control characters) guarantees a rich canonical can
57
+ * never equal any plain shared-string text, so plain↔rich never collide on
58
+ * the downstream content hash.
59
+ *
60
+ * IMPORTANT: This is the dedup key for shared-string IDs across sessions.
61
+ * Changing the format, key order, or stripping rules invalidates every
62
+ * existing rich-string ID. Mirror updates here in any other-language port
63
+ * (e.g. Python) used in the same collaboration session.
64
+ */
65
+ export const canonicalizeRichSharedString = (text, runs) => {
66
+ const runsKey = runs.map(canonicalizeRun).join(";");
67
+ return `${RICH_MARKER}${text}${SEP}${runsKey}`;
68
+ };
@@ -0,0 +1,15 @@
1
+ export type BaseSheet = {
2
+ index?: number | null;
3
+ title: string;
4
+ hidden?: boolean | null;
5
+ sheetId: number;
6
+ } & Record<string, any>;
7
+ /**
8
+ * Sort sheets by index
9
+ * @param sheets
10
+ * @returns
11
+ */
12
+ export declare const sortSheetsByIndex: <S extends BaseSheet>(sheets: S[], includeHidden?: boolean) => (S & {
13
+ index: number;
14
+ })[];
15
+ //# sourceMappingURL=sort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../../sort.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACxB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,SAAS,EACnD,QAAQ,CAAC,EAAE,EACX,gBAAgB,OAAO;;IA0CxB,CAAC"}