@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.1

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/cell-types.js +20 -0
  4. package/dist/cell-types.js.map +1 -0
  5. package/dist/code/code-formula-bar.js +6 -0
  6. package/dist/code/code-formula-bar.js.map +1 -0
  7. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  8. package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +12 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/language.js +51 -0
  12. package/dist/code/language.js.map +1 -0
  13. package/dist/code/style.d.ts +1 -0
  14. package/dist/code/style.js +2 -0
  15. package/dist/collaboration/anchor.js +143 -0
  16. package/dist/collaboration/anchor.js.map +1 -0
  17. package/dist/collaboration/index.js +770 -0
  18. package/dist/collaboration/index.js.map +1 -0
  19. package/dist/collaboration/peers.js +87 -0
  20. package/dist/collaboration/peers.js.map +1 -0
  21. package/dist/collaboration/shared.js +450 -0
  22. package/dist/collaboration/shared.js.map +1 -0
  23. package/dist/components/cell-panel.js +6 -0
  24. package/dist/components/cell-panel.js.map +1 -0
  25. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
  26. package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  27. package/dist/components/cell-renders.js +40 -0
  28. package/dist/components/cell-renders.js.map +1 -0
  29. package/dist/components/date-panel.js +6 -0
  30. package/dist/components/date-panel.js.map +1 -0
  31. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
  32. package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  33. package/dist/components/filter-panel.js +6 -0
  34. package/dist/components/filter-panel.js.map +1 -0
  35. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
  36. package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/format-dialog.js +6 -0
  38. package/dist/components/format-dialog.js.map +1 -0
  39. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
  40. package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  41. package/dist/components/format-list.js +6 -0
  42. package/dist/components/format-list.js.map +1 -0
  43. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
  44. package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
  45. package/dist/components/format-presets.js +97 -0
  46. package/dist/components/format-presets.js.map +1 -0
  47. package/dist/components/option-panel.js +6 -0
  48. package/dist/components/option-panel.js.map +1 -0
  49. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
  50. package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/sheet-comment.js +6 -0
  52. package/dist/components/sheet-comment.js.map +1 -0
  53. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
  54. package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/sheet-formula-bar.js +6 -0
  56. package/dist/components/sheet-formula-bar.js.map +1 -0
  57. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
  58. package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/sheet-link.js +6 -0
  60. package/dist/components/sheet-link.js.map +1 -0
  61. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
  62. package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/sheet-menu.js +6 -0
  64. package/dist/components/sheet-menu.js.map +1 -0
  65. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
  66. package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/sheet-name-box.js +6 -0
  68. package/dist/components/sheet-name-box.js.map +1 -0
  69. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
  70. package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/sheet-note.js +6 -0
  72. package/dist/components/sheet-note.js.map +1 -0
  73. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
  74. package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/sheet-outline.js +6 -0
  76. package/dist/components/sheet-outline.js.map +1 -0
  77. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
  78. package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
  79. package/dist/components/sheet-tabs.js +6 -0
  80. package/dist/components/sheet-tabs.js.map +1 -0
  81. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
  82. package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
  83. package/dist/components/sheet-toolbar.js +6 -0
  84. package/dist/components/sheet-toolbar.js.map +1 -0
  85. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
  86. package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  87. package/dist/components/validation-dialog.js +6 -0
  88. package/dist/components/validation-dialog.js.map +1 -0
  89. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
  90. package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
  91. package/dist/context.js +12 -0
  92. package/dist/context.js.map +1 -0
  93. package/dist/core/address.js +256 -0
  94. package/dist/core/address.js.map +1 -0
  95. package/dist/core/axis.js +72 -0
  96. package/dist/core/axis.js.map +1 -0
  97. package/dist/core/cell-type.js +30 -0
  98. package/dist/core/cell-type.js.map +1 -0
  99. package/dist/core/clipboard.js +141 -0
  100. package/dist/core/clipboard.js.map +1 -0
  101. package/dist/core/commands.js +63 -0
  102. package/dist/core/commands.js.map +1 -0
  103. package/dist/core/comments.js +74 -0
  104. package/dist/core/comments.js.map +1 -0
  105. package/dist/core/conditional.js +362 -0
  106. package/dist/core/conditional.js.map +1 -0
  107. package/dist/core/csv.js +191 -0
  108. package/dist/core/csv.js.map +1 -0
  109. package/dist/core/data.js +118 -0
  110. package/dist/core/data.js.map +1 -0
  111. package/dist/core/document.js +187 -0
  112. package/dist/core/document.js.map +1 -0
  113. package/dist/core/extension.js +60 -0
  114. package/dist/core/extension.js.map +1 -0
  115. package/dist/core/file.js +336 -0
  116. package/dist/core/file.js.map +1 -0
  117. package/dist/core/fill.js +153 -0
  118. package/dist/core/fill.js.map +1 -0
  119. package/dist/core/find.js +42 -0
  120. package/dist/core/find.js.map +1 -0
  121. package/dist/core/format.js +404 -0
  122. package/dist/core/format.js.map +1 -0
  123. package/dist/core/group.js +134 -0
  124. package/dist/core/group.js.map +1 -0
  125. package/dist/core/history.js +68 -0
  126. package/dist/core/history.js.map +1 -0
  127. package/dist/core/index.js +17 -0
  128. package/dist/core/link.js +34 -0
  129. package/dist/core/link.js.map +1 -0
  130. package/dist/core/merge.js +105 -0
  131. package/dist/core/merge.js.map +1 -0
  132. package/dist/core/model.js +48 -0
  133. package/dist/core/model.js.map +1 -0
  134. package/dist/core/nav.js +44 -0
  135. package/dist/core/nav.js.map +1 -0
  136. package/dist/core/paint.js +106 -0
  137. package/dist/core/paint.js.map +1 -0
  138. package/dist/core/plain-cell-types.js +54 -0
  139. package/dist/core/plain-cell-types.js.map +1 -0
  140. package/dist/core/protect.js +246 -0
  141. package/dist/core/protect.js.map +1 -0
  142. package/dist/core/selection.js +37 -0
  143. package/dist/core/selection.js.map +1 -0
  144. package/dist/core/transaction.js +1582 -0
  145. package/dist/core/transaction.js.map +1 -0
  146. package/dist/core/validation.js +135 -0
  147. package/dist/core/validation.js.map +1 -0
  148. package/dist/core/versions.js +161 -0
  149. package/dist/core/versions.js.map +1 -0
  150. package/dist/export/color.js +36 -0
  151. package/dist/export/color.js.map +1 -0
  152. package/dist/export/draw.js +316 -0
  153. package/dist/export/draw.js.map +1 -0
  154. package/dist/export/index.js +390 -0
  155. package/dist/export/index.js.map +1 -0
  156. package/dist/formula/edit.js +210 -0
  157. package/dist/formula/edit.js.map +1 -0
  158. package/dist/formula/engine.js +526 -0
  159. package/dist/formula/engine.js.map +1 -0
  160. package/dist/formula/error.js +39 -0
  161. package/dist/formula/error.js.map +1 -0
  162. package/dist/formula/functions.js +1493 -0
  163. package/dist/formula/functions.js.map +1 -0
  164. package/dist/formula/help.js +408 -0
  165. package/dist/formula/help.js.map +1 -0
  166. package/dist/formula/index.js +7 -0
  167. package/dist/formula/parse.js +369 -0
  168. package/dist/formula/parse.js.map +1 -0
  169. package/dist/formula/values.js +123 -0
  170. package/dist/formula/values.js.map +1 -0
  171. package/dist/formulas.js +30 -0
  172. package/dist/formulas.js.map +1 -0
  173. package/dist/index.js +39 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/locale.js +1312 -0
  176. package/dist/locale.js.map +1 -0
  177. package/dist/sheet.js +6 -0
  178. package/dist/sheet.js.map +1 -0
  179. package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
  180. package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/style.css +1 -0
  182. package/dist/style.d.ts +1 -0
  183. package/dist/style.js +17 -0
  184. package/dist/types/cell-types.d.ts +2 -0
  185. package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
  186. package/dist/types/code/index.d.ts +12 -0
  187. package/dist/types/code/language.d.ts +13 -0
  188. package/dist/types/collaboration/anchor.d.ts +65 -0
  189. package/dist/types/collaboration/index.d.ts +59 -0
  190. package/dist/types/collaboration/peers.d.ts +21 -0
  191. package/dist/types/collaboration/shared.d.ts +75 -0
  192. package/dist/types/components/cell-panel.vue.d.ts +20 -0
  193. package/dist/types/components/cell-renders.d.ts +9 -0
  194. package/dist/types/components/date-panel.vue.d.ts +9 -0
  195. package/dist/types/components/filter-panel.vue.d.ts +21 -0
  196. package/dist/types/components/format-dialog.vue.d.ts +24 -0
  197. package/dist/types/components/format-list.vue.d.ts +25 -0
  198. package/dist/types/components/format-presets.d.ts +30 -0
  199. package/dist/types/components/option-panel.vue.d.ts +13 -0
  200. package/dist/types/components/sheet-comment.vue.d.ts +29 -0
  201. package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
  202. package/dist/types/components/sheet-link.vue.d.ts +32 -0
  203. package/dist/types/components/sheet-menu.vue.d.ts +20 -0
  204. package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
  205. package/dist/types/components/sheet-note.vue.d.ts +22 -0
  206. package/dist/types/components/sheet-outline.vue.d.ts +36 -0
  207. package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
  208. package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
  209. package/dist/types/components/validation-dialog.vue.d.ts +25 -0
  210. package/dist/types/context.d.ts +189 -0
  211. package/dist/types/core/address.d.ts +109 -0
  212. package/dist/types/core/axis.d.ts +23 -0
  213. package/dist/types/core/cell-type.d.ts +157 -0
  214. package/dist/types/core/clipboard.d.ts +48 -0
  215. package/dist/types/core/commands.d.ts +76 -0
  216. package/dist/types/core/comments.d.ts +22 -0
  217. package/dist/types/core/conditional.d.ts +114 -0
  218. package/dist/types/core/csv.d.ts +93 -0
  219. package/dist/types/core/data.d.ts +26 -0
  220. package/dist/types/core/document.d.ts +49 -0
  221. package/dist/types/core/extension.d.ts +177 -0
  222. package/dist/types/core/file.d.ts +301 -0
  223. package/dist/types/core/fill.d.ts +23 -0
  224. package/dist/types/core/find.d.ts +39 -0
  225. package/dist/types/core/format.d.ts +47 -0
  226. package/dist/types/core/group.d.ts +76 -0
  227. package/dist/types/core/history.d.ts +27 -0
  228. package/dist/types/core/index.d.ts +33 -0
  229. package/dist/types/core/link.d.ts +14 -0
  230. package/dist/types/core/merge.d.ts +26 -0
  231. package/dist/types/core/model.d.ts +363 -0
  232. package/dist/types/core/nav.d.ts +15 -0
  233. package/dist/types/core/paint.d.ts +19 -0
  234. package/dist/types/core/plain-cell-types.d.ts +6 -0
  235. package/dist/types/core/protect.d.ts +34 -0
  236. package/dist/types/core/selection.d.ts +25 -0
  237. package/dist/types/core/transaction.d.ts +470 -0
  238. package/dist/types/core/validation.d.ts +55 -0
  239. package/dist/types/core/versions.d.ts +38 -0
  240. package/dist/types/export/color.d.ts +7 -0
  241. package/dist/types/export/draw.d.ts +98 -0
  242. package/dist/types/export/index.d.ts +47 -0
  243. package/dist/types/formula/edit.d.ts +62 -0
  244. package/dist/types/formula/engine.d.ts +99 -0
  245. package/dist/types/formula/error.d.ts +30 -0
  246. package/dist/types/formula/functions.d.ts +62 -0
  247. package/dist/types/formula/help.d.ts +12 -0
  248. package/dist/types/formula/index.d.ts +13 -0
  249. package/dist/types/formula/parse.d.ts +92 -0
  250. package/dist/types/formula/values.d.ts +65 -0
  251. package/dist/types/formulas.d.ts +32 -0
  252. package/dist/types/index.d.ts +225 -0
  253. package/dist/types/locale.d.ts +531 -0
  254. package/dist/types/sheet.vue.d.ts +60 -0
  255. package/dist/types/types.d.ts +498 -0
  256. package/dist/types/use-comments.d.ts +47 -0
  257. package/dist/types/use-messages.d.ts +7 -0
  258. package/dist/use-comments.js +130 -0
  259. package/dist/use-comments.js.map +1 -0
  260. package/dist/use-messages.js +29 -0
  261. package/dist/use-messages.js.map +1 -0
  262. package/package.json +93 -4
@@ -0,0 +1,336 @@
1
+ import { boundsOf, parseRange } from "./address.js";
2
+ import { formatValue } from "./format.js";
3
+ import { cellOf, colCountOf, rowCountOf, valueOf } from "./model.js";
4
+ import { shownValue } from "../formula/values.js";
5
+ import { dataBounds, readCSV, sheetToCSV } from "./csv.js";
6
+ import { resolveSheetExtensions } from "./extension.js";
7
+ import { resolveCellType } from "./cell-type.js";
8
+ import { plainCellTypes } from "./plain-cell-types.js";
9
+ import { labelOf } from "./commands.js";
10
+ import { SheetLooks, layStyle } from "./conditional.js";
11
+ import { sheetKey } from "./transaction.js";
12
+ import { SheetFormulaEngine } from "../formula/engine.js";
13
+ import { plainStyle } from "./paint.js";
14
+ import { sheetMessagesFor } from "../locale.js";
15
+ // src/core/file.ts
16
+ /** Declares an exporter; only gives it a type. */
17
+ function defineSheetExporter(exporter) {
18
+ return exporter;
19
+ }
20
+ var exporterInfo = (exporter, messages) => ({
21
+ name: exporter.name,
22
+ label: labelOf(exporter.label, messages),
23
+ icon: exporter.icon,
24
+ extension: exporter.extension,
25
+ mime: exporter.mime,
26
+ scopes: [...exporter.scopes ?? [
27
+ "workbook",
28
+ "sheet",
29
+ "selection"
30
+ ]]
31
+ });
32
+ var importerInfo = (importer, messages) => ({
33
+ name: importer.name,
34
+ label: labelOf(importer.label, messages),
35
+ accept: [...importer.accept]
36
+ });
37
+ function defineSheetImporter(importer) {
38
+ return importer;
39
+ }
40
+ /**
41
+ * An import or export that can't be done, for a reason people can be told:
42
+ * the grid shows `reason` in its hint, while `message` is for developers.
43
+ * `code` tells the cases apart, such as `'locked'`.
44
+ */
45
+ var SheetFileError = class extends Error {
46
+ code;
47
+ reason;
48
+ name = "SheetFileError";
49
+ constructor(message, code, reason, options) {
50
+ super(message, options);
51
+ this.code = code;
52
+ this.reason = reason;
53
+ }
54
+ };
55
+ /** What to tell people of a failed import or export: an error's `reason` when it gives one. */
56
+ function reasonOf(error) {
57
+ if (error instanceof Error) return "reason" in error && typeof error.reason === "string" ? error.reason : error.message;
58
+ return String(error);
59
+ }
60
+ /** Whether an importer takes a file, by its name, type or first bytes. */
61
+ async function importerTakes(importer, file) {
62
+ const name = "name" in file ? String(file.name).toLowerCase() : "";
63
+ for (const accept of importer.accept) {
64
+ const want = accept.toLowerCase();
65
+ if (want.startsWith(".") ? name.endsWith(want) : matchType(file.type, want)) return true;
66
+ }
67
+ if (!importer.sniff) return false;
68
+ const head = new Uint8Array(await file.slice(0, 512).arrayBuffer());
69
+ return importer.sniff(head);
70
+ }
71
+ var matchType = (type, want) => !!type && (want.endsWith("/*") ? type.startsWith(want.slice(0, -1)) : type === want);
72
+ /** The first importer that takes a file, or `undefined`. */
73
+ async function findImporter(importers, file) {
74
+ for (const importer of importers) if (await importerTakes(importer, file)) return importer;
75
+ }
76
+ /** The cell types by name: the built-in ones, then these, the first named winning. */
77
+ function cellTypeMap(added = []) {
78
+ const map = /* @__PURE__ */ new Map();
79
+ for (const type of plainCellTypes) map.set(type.name, type);
80
+ const seen = /* @__PURE__ */ new Set();
81
+ for (const type of added) {
82
+ if (seen.has(type.name)) continue;
83
+ seen.add(type.name);
84
+ map.set(type.name, type);
85
+ }
86
+ return map;
87
+ }
88
+ /** What exporters read: a workbook and how its cells show. */
89
+ function createFileSource(book, options = {}) {
90
+ const scope = options.scope ?? "workbook";
91
+ const types = options.cellTypes ?? cellTypeMap();
92
+ const locale = options.locale;
93
+ let result = options.result;
94
+ let evaluate = options.evaluate;
95
+ if (!result || !evaluate) {
96
+ const engine = new SheetFormulaEngine(book, {
97
+ functions: options.functions,
98
+ locale
99
+ });
100
+ result ??= (key, row, col) => engine.result(key, row, col);
101
+ evaluate ??= (formula, key) => engine.evaluate(formula, key);
102
+ }
103
+ const read = result;
104
+ const work = evaluate;
105
+ const at = (sheet, row) => book.sheets[sheet]?.cells[row];
106
+ const input = (sheet, row, col) => at(sheet, row)?.[col];
107
+ const resultOf = (sheet, row, col) => {
108
+ const data = book.sheets[sheet];
109
+ if (!data) return null;
110
+ const value = valueOf(input(sheet, row, col));
111
+ if (typeof value !== "string" || !value.startsWith("=")) return value;
112
+ return read(sheetKey(data), row, col);
113
+ };
114
+ const shown = (sheet, row, col) => {
115
+ const data = book.sheets[sheet];
116
+ if (!data) return null;
117
+ const key = sheetKey(data);
118
+ const value = valueOf(input(sheet, row, col));
119
+ if (typeof value !== "string" || !value.startsWith("=")) return value;
120
+ return shownValue(value, (r, c) => read(key, r, c), row, col);
121
+ };
122
+ const typeOf = (sheet, row, col) => resolveCellType(cellOf(input(sheet, row, col)), book, types);
123
+ const textOf = (sheet, row, col) => {
124
+ const value = shown(sheet, row, col);
125
+ const typed = typeOf(sheet, row, col);
126
+ const own = typed?.definition.format?.(value, typed.setup);
127
+ if (own !== void 0) return own;
128
+ return formatValue(value, cellOf(input(sheet, row, col))?.format, locale);
129
+ };
130
+ const looks = /* @__PURE__ */ new Map();
131
+ const looksOf = (sheet) => {
132
+ let found = looks.get(sheet);
133
+ if (!found) {
134
+ const data = book.sheets[sheet];
135
+ const key = sheetKey(data);
136
+ found = new SheetLooks(data.conditionalFormats, {
137
+ valueAt: (row, col) => resultOf(sheet, row, col),
138
+ textAt: (row, col) => textOf(sheet, row, col),
139
+ evaluate: (formula) => work(formula, key),
140
+ styles: book.styles,
141
+ size: {
142
+ rows: rowCountOf(data),
143
+ cols: colCountOf(data)
144
+ },
145
+ lang: locale
146
+ });
147
+ looks.set(sheet, found);
148
+ }
149
+ return found;
150
+ };
151
+ const styleOf = (sheet, row, col) => {
152
+ if (!book.sheets[sheet]) return {};
153
+ const name = cellOf(input(sheet, row, col))?.style;
154
+ const own = name ? book.styles?.[name] : void 0;
155
+ const look = book.sheets[sheet].conditionalFormats?.length ? looksOf(sheet).lookAt(row, col)?.style : void 0;
156
+ const style = look ? layStyle(own, look) : own;
157
+ return style ? plainStyle(style) : {};
158
+ };
159
+ const index = options.sheet ?? 0;
160
+ const pick = (i) => {
161
+ const data = book.sheets[i];
162
+ return {
163
+ index: i,
164
+ data,
165
+ bounds: dataBounds(data)
166
+ };
167
+ };
168
+ let sheets;
169
+ if (scope === "workbook") sheets = book.sheets.map((_, i) => pick(i));
170
+ else {
171
+ if (!book.sheets[index]) throw new RangeError(`[@dou-ui/sheet] there is no sheet ${index}`);
172
+ const sheet = pick(index);
173
+ if (options.bounds) sheet.bounds = options.bounds;
174
+ sheets = [sheet];
175
+ }
176
+ return {
177
+ book,
178
+ scope,
179
+ sheets,
180
+ colWidth: options.colWidth ?? 96,
181
+ rowHeight: options.rowHeight ?? 28,
182
+ locale,
183
+ messages: options.messages ?? sheetMessagesFor(locale),
184
+ value: (sheet, row, col) => valueOf(input(sheet, row, col)),
185
+ result: resultOf,
186
+ text: textOf,
187
+ plain(sheet, row, col) {
188
+ const value = shown(sheet, row, col);
189
+ const typed = typeOf(sheet, row, col);
190
+ return typed?.definition.plain ? typed.definition.plain(value, typed.setup) : value;
191
+ },
192
+ style: styleOf,
193
+ format: (sheet, row, col) => cellOf(input(sheet, row, col))?.format,
194
+ cellType: typeOf,
195
+ signal: options.signal,
196
+ view: options.view
197
+ };
198
+ }
199
+ /** The scope an exporter writes: the one asked for when it can, otherwise its first. */
200
+ function scopeFor(exporter, asked) {
201
+ const scopes = exporter.scopes ?? [
202
+ "workbook",
203
+ "sheet",
204
+ "selection"
205
+ ];
206
+ if (asked && !scopes.includes(asked)) throw new RangeError(`[@dou-ui/sheet] the exporter "${exporter.name}" can't export a ${asked}; it exports ${scopes.join(", ")}`);
207
+ return asked ?? scopes[0];
208
+ }
209
+ /** An exporter's output as a `Blob`. */
210
+ var toBlob = (out, mime) => typeof out === "string" ? new Blob([out], { type: mime }) : out;
211
+ /**
212
+ * Writes a workbook with an exporter, without a grid: in a script, a
213
+ * worker or on a server. Formulas are worked out on the way.
214
+ */
215
+ async function exportWorkbook(book, exporter, options = {}) {
216
+ if (exporter.needs === "view") throw new Error(`[@dou-ui/sheet] the exporter "${exporter.name}" draws the grid, so it only works in DouSheet`);
217
+ const resolved = resolveSheetExtensions(options.extensions);
218
+ const scope = scopeFor(exporter, options.scope);
219
+ let bounds;
220
+ if (options.range != null) {
221
+ const range = parseRange(options.range);
222
+ if (!range) throw new RangeError(`[@dou-ui/sheet] "${options.range}" isn't a range`);
223
+ bounds = boundsOf(range);
224
+ }
225
+ let sheet;
226
+ if (options.sheet != null) {
227
+ const ref = options.sheet;
228
+ sheet = typeof ref === "number" ? ref : book.sheets.findIndex((s) => s.id === ref || s.id == null && s.name === ref);
229
+ if (!book.sheets[sheet]) throw new RangeError(`[@dou-ui/sheet] there is no sheet "${ref}"`);
230
+ }
231
+ const source = createFileSource(book, {
232
+ scope,
233
+ sheet,
234
+ bounds,
235
+ cellTypes: cellTypeMap(resolved.cellTypes),
236
+ functions: resolved.functions,
237
+ locale: options.locale,
238
+ messages: options.messages,
239
+ colWidth: options.colWidth,
240
+ rowHeight: options.rowHeight,
241
+ signal: options.signal
242
+ });
243
+ return toBlob(await exporter.export(source, options.options), exporter.mime);
244
+ }
245
+ /** Reads a file with an importer, without a grid. */
246
+ async function readWorkbook(file, importer, options = {}) {
247
+ const messages = options.messages ?? sheetMessagesFor(options.locale);
248
+ return await importer.import(file, {
249
+ messages,
250
+ locale: options.locale,
251
+ signal: options.signal
252
+ }, options.options);
253
+ }
254
+ /** The name of a file without its extension, for a sheet's name. */
255
+ var baseName = (file) => {
256
+ return ("name" in file ? String(file.name) : "").replace(/\.[^.]*$/, "");
257
+ };
258
+ /** CSV: one sheet (or the selected cells), with the cells' values. */
259
+ var csvExporter = defineSheetExporter({
260
+ name: "csv",
261
+ label: (m) => m.file.csv,
262
+ extension: "csv",
263
+ mime: "text/csv",
264
+ scopes: ["sheet", "selection"],
265
+ export(source, options = {}) {
266
+ const { index, data, bounds } = source.sheets[0];
267
+ if (!bounds) return options.bom === false ? "" : "";
268
+ return sheetToCSV(data, {
269
+ range: {
270
+ start: {
271
+ row: bounds.top,
272
+ col: bounds.left
273
+ },
274
+ end: {
275
+ row: bounds.bottom,
276
+ col: bounds.right
277
+ }
278
+ },
279
+ locale: source.locale,
280
+ result: (row, col) => source.result(index, row, col),
281
+ cellText: (_input, _value, row, col) => source.text(index, row, col),
282
+ cellPlain: (_input, _value, row, col) => source.plain(index, row, col),
283
+ delimiter: options.delimiter,
284
+ text: options.text,
285
+ formulas: options.formulas,
286
+ bom: options.bom,
287
+ newline: options.newline,
288
+ escapeFormulas: options.escapeFormulas
289
+ });
290
+ }
291
+ });
292
+ /** CSV and tab-separated text: a new sheet named after the file. */
293
+ var csvImporter = defineSheetImporter({
294
+ name: "csv",
295
+ label: (m) => m.file.csv,
296
+ accept: [
297
+ ".csv",
298
+ ".tsv",
299
+ ".txt",
300
+ "text/csv",
301
+ "text/tab-separated-values",
302
+ "text/plain"
303
+ ],
304
+ async import(file) {
305
+ const text = await file.text();
306
+ return { book: {
307
+ version: 1,
308
+ sheets: [{
309
+ name: baseName(file) || "Sheet1",
310
+ cells: readCSV(text)
311
+ }]
312
+ } };
313
+ }
314
+ });
315
+ /**
316
+ * Puts the built-in exporters (or importers) and the extensions' together,
317
+ * by name: an extension's replaces a built-in one of the same name; two
318
+ * extensions with the same name are an error.
319
+ */
320
+ function mergeFileHandlers(builtin, added, kind) {
321
+ const owner = /* @__PURE__ */ new Map();
322
+ const own = [];
323
+ for (const { extension, handler } of added) {
324
+ const taken = owner.get(handler.name);
325
+ if (taken) throw new Error(`[@dou-ui/sheet] the ${kind} "${handler.name}" is added by both "${taken}" and "${extension}"`);
326
+ owner.set(handler.name, extension);
327
+ own.push(handler);
328
+ }
329
+ if (kind === "importer") return [...own, ...builtin.filter((handler) => !owner.has(handler.name))];
330
+ const byName = new Map(own.map((handler) => [handler.name, handler]));
331
+ const builtinNames = new Set(builtin.map((handler) => handler.name));
332
+ return [...builtin.map((handler) => byName.get(handler.name) ?? handler), ...own.filter((handler) => !builtinNames.has(handler.name))];
333
+ }
334
+ export { SheetFileError, baseName, cellTypeMap, createFileSource, csvExporter, csvImporter, defineSheetExporter, defineSheetImporter, exportWorkbook, exporterInfo, findImporter, importerInfo, importerTakes, mergeFileHandlers, readWorkbook, reasonOf, scopeFor, toBlob };
335
+
336
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","names":[],"sources":["../../src/core/file.ts"],"sourcesContent":["import type { Component } from 'vue'\nimport { SheetFormulaEngine } from '../formula/engine'\nimport type { SheetFunction } from '../formula/functions'\nimport { shownValue, type SheetResult } from '../formula/values'\nimport { sheetMessagesFor, type SheetMessages } from '../locale'\nimport { boundsOf, parseRange, type SheetBounds } from './address'\nimport { resolveCellType, type SheetCellType, type SheetResolvedType } from './cell-type'\nimport { plainCellTypes } from './plain-cell-types'\nimport { dataBounds, readCSV, sheetToCSV } from './csv'\nimport type { SheetExtension } from './extension'\nimport { resolveSheetExtensions } from './extension'\nimport { formatValue } from './format'\nimport {\n cellOf,\n valueOf,\n type SheetCellInput,\n type SheetData,\n type SheetStyle,\n type SheetValue,\n type SheetWorkbook,\n} from './model'\nimport { sheetKey, type SheetRef } from './transaction'\nimport { labelOf, type SheetLabel } from './commands'\nimport { layStyle, SheetLooks } from './conditional'\nimport { plainStyle } from './paint'\nimport { colCountOf, rowCountOf } from './model'\nimport type { SheetDrawing } from '../export/draw'\n\n/*\n * Files in and out. An exporter turns the workbook (or a sheet, or the\n * selection) into a file; an importer reads a file into a workbook. Both\n * only see data, so they also run outside the grid, e.g. on a server.\n */\n\n/** What is exported: every sheet, the sheet shown, or the selected cells. */\nexport type SheetExportScope = 'workbook' | 'sheet' | 'selection'\n\n/** A sheet to export, with the cells to write. */\nexport interface SheetFileSheet {\n /** Its place in the workbook. */\n index: number\n data: SheetData\n /** The cells to write: the selection, or from `A1` to the last cell with data; `null` when it is empty. */\n bounds: SheetBounds | null\n}\n\n/** What an exporter reads: the workbook and how its cells show, without the grid. */\nexport interface SheetFileSource {\n /** The workbook as it is stored. */\n book: SheetWorkbook\n /** What is being exported. */\n scope: SheetExportScope\n /** The sheets to write, in order: one, unless `scope` is `'workbook'`. */\n sheets: SheetFileSheet[]\n /** The language dates and numbers are written in. */\n locale?: string\n messages: SheetMessages\n /** The grid's default column width and row height in px, for columns and rows without their own. */\n colWidth: number\n rowHeight: number\n /** A cell's value as stored: for a formula, the formula. */\n value: (sheet: number, row: number, col: number) => SheetValue\n /** What a cell comes to: its formula's result (an error as a `SheetError`), or its value. */\n result: (sheet: number, row: number, col: number) => SheetResult\n /** The text a cell shows, with its format and cell type, as the grid draws it. */\n text: (sheet: number, row: number, col: number) => string\n /**\n * The value to write into another spreadsheet program: the cell type's\n * `plain`, or what the cell comes to (an error as its code, such as `#DIV/0!`).\n */\n plain: (sheet: number, row: number, col: number) => SheetValue\n /**\n * How the cell looks: its named style with what its conditional formats\n * give it over it, `tone` and `fill` as the colors they are in the light\n * theme (in `color` and `background`). `{}` for a plain cell.\n */\n style: (sheet: number, row: number, col: number) => SheetStyle\n /** The cell's number or date format. */\n format: (sheet: number, row: number, col: number) => string | undefined\n /** The cell's type and its settings, or `undefined` for a plain cell. */\n cellType: (sheet: number, row: number, col: number) => SheetResolvedType | undefined\n /** Aborted when the export is called off. */\n signal?: AbortSignal\n /** Only for exporters with `needs: 'view'`, in the grid: the cells as the grid draws them. */\n view?: SheetFileView\n}\n\n/** What an exporter with `needs: 'view'` gets of the grid. */\nexport interface SheetFileView {\n /**\n * The cells exported laid out as the grid draws them: column widths, row\n * heights, text and looks in plain colors of the page's theme.\n */\n drawing: (options?: {\n headers?: boolean\n gridlines?: boolean\n background?: boolean\n }) => SheetDrawing\n}\n\n/** A way to write the workbook as a file, such as CSV; see `defineSheetExporter`. */\nexport interface SheetExporter<Options = any> {\n /** Unique among the exporters; `exportFile` takes it. */\n name: string\n /** Its name in the toolbar's export menu. */\n label: SheetLabel\n /**\n * Its icon in the toolbar's export menu; by default a file icon for its\n * `extension`, so an exporter needn't load icons (and Vue), as in a Worker.\n */\n icon?: Component\n /** The file name's extension, without the dot, e.g. `xlsx`. */\n extension: string\n /** The file's media type. */\n mime: string\n /** What it can export, the first being what the toolbar exports. Defaults to every scope. */\n scopes?: SheetExportScope[]\n /**\n * `'view'` for an exporter that draws the cells as the grid shows them\n * (sizes, the page's colors), which gets `source.view` and only works in\n * the grid; `exportWorkbook` refuses it. Defaults to `'data'`.\n */\n needs?: 'data' | 'view'\n /** Writes the file. A string is wrapped in a `Blob` of `mime`. */\n export: (source: SheetFileSource, options?: Options) => Blob | string | Promise<Blob | string>\n}\n\n/**\n * The options an exporter or importer takes; for either of two, either's,\n * so one picked at run time, e.g. by a message, can be passed as it is.\n */\ntype OptionsOf<Handler> = Handler extends\n SheetExporter<infer Options> | SheetImporter<infer Options, any>\n ? Options\n : never\n\n/** What an importer gives back, as it declares it. */\ntype ResultOf<Importer> =\n Importer extends SheetImporter<any, infer Result> ? Result : SheetImportResult\n\n/** Declares an exporter; only gives it a type. */\nexport function defineSheetExporter<Options = any>(\n exporter: SheetExporter<Options>,\n): SheetExporter<Options> {\n return exporter\n}\n\n/** An exporter as menus show it, its label in the sheet's language. */\nexport interface SheetExporterInfo {\n name: string\n label: string\n icon?: Component\n extension: string\n mime: string\n /** What it can export, the first being what it exports by default. */\n scopes: SheetExportScope[]\n}\n\n/** An importer as menus show it, its label in the sheet's language. */\nexport interface SheetImporterInfo {\n name: string\n label: string\n accept: string[]\n}\n\nexport const exporterInfo = (\n exporter: SheetExporter,\n messages: SheetMessages,\n): SheetExporterInfo => ({\n name: exporter.name,\n label: labelOf(exporter.label, messages),\n icon: exporter.icon,\n extension: exporter.extension,\n mime: exporter.mime,\n scopes: [...(exporter.scopes ?? ['workbook', 'sheet', 'selection'])],\n})\n\nexport const importerInfo = (\n importer: SheetImporter,\n messages: SheetMessages,\n): SheetImporterInfo => ({\n name: importer.name,\n label: labelOf(importer.label, messages),\n accept: [...importer.accept],\n})\n\n/** Something an importer couldn't bring over, such as images or borders. */\nexport interface SheetImportWarning {\n /** A short name for what was lost, e.g. `'border'`. */\n code: string\n /** It in words, for people. */\n message: string\n /** How many times it came up. */\n count?: number\n /** The sheet it was on. */\n sheet?: string\n}\n\n/** What an importer read. */\nexport interface SheetImportResult {\n book: SheetWorkbook\n warnings?: SheetImportWarning[]\n}\n\n/** What an importer gets besides the file. */\nexport interface SheetImportContext {\n messages: SheetMessages\n locale?: string\n signal?: AbortSignal\n}\n\n/** A way to read a file into a workbook, such as CSV; see `defineSheetImporter`. */\nexport interface SheetImporter<\n Options = any,\n Result extends SheetImportResult = SheetImportResult,\n> {\n /** Unique among the importers; `importFile`'s `importer` takes it. */\n name: string\n /**\n * What the files it reads are called, such as “CSV 文件”: in the hints the\n * grid shows about a file without a name, and in `useSheet().importers`\n * for a menu of your own.\n */\n label: SheetLabel\n /**\n * The files it reads, as the file picker's `accept` takes them: extensions\n * such as `.csv` and media types such as `text/csv`.\n */\n accept: string[]\n /** Tells from the file's first bytes whether it reads it, for files whose name says nothing. */\n sniff?: (head: Uint8Array) => boolean\n import: (file: Blob, context: SheetImportContext, options?: Options) => Result | Promise<Result>\n}\n\n/**\n * Declares an importer; only gives it a type. `defineSheetImporter<Options, Result>`\n * also says what it gives back, such as warnings it always has.\n */\nexport function defineSheetImporter<Options = any>(\n importer: SheetImporter<Options>,\n): SheetImporter<Options>\nexport function defineSheetImporter<Options, Result extends SheetImportResult>(\n importer: SheetImporter<Options, Result>,\n): SheetImporter<Options, Result>\nexport function defineSheetImporter(importer: SheetImporter) {\n return importer\n}\n\n/**\n * An import or export that can't be done, for a reason people can be told:\n * the grid shows `reason` in its hint, while `message` is for developers.\n * `code` tells the cases apart, such as `'locked'`.\n */\nexport class SheetFileError<Code extends string = string> extends Error {\n override name = 'SheetFileError'\n constructor(\n message: string,\n readonly code: Code,\n readonly reason: string,\n options?: ErrorOptions,\n ) {\n super(message, options)\n }\n}\n\n/** What to tell people of a failed import or export: an error's `reason` when it gives one. */\nexport function reasonOf(error: unknown) {\n if (error instanceof Error)\n return 'reason' in error && typeof error.reason === 'string' ? error.reason : error.message\n return String(error)\n}\n\n/** Whether an importer takes a file, by its name, type or first bytes. */\nexport async function importerTakes(importer: SheetImporter, file: Blob) {\n const name = 'name' in file ? String((file as File).name).toLowerCase() : ''\n for (const accept of importer.accept) {\n const want = accept.toLowerCase()\n if (want.startsWith('.') ? name.endsWith(want) : matchType(file.type, want)) return true\n }\n if (!importer.sniff) return false\n const head = new Uint8Array(await file.slice(0, 512).arrayBuffer())\n return importer.sniff(head)\n}\n\nconst matchType = (type: string, want: string) =>\n !!type && (want.endsWith('/*') ? type.startsWith(want.slice(0, -1)) : type === want)\n\n/** The first importer that takes a file, or `undefined`. */\nexport async function findImporter(importers: readonly SheetImporter[], file: Blob) {\n for (const importer of importers) if (await importerTakes(importer, file)) return importer\n return undefined\n}\n\n/** How a source reads a workbook. */\nexport interface SheetFileSourceOptions {\n scope?: SheetExportScope\n /** The sheet for `'sheet'` and `'selection'`; the first by default. */\n sheet?: number\n /** For `'selection'` (or to write part of a sheet): the cells. */\n bounds?: SheetBounds\n /** What formulas come to, by sheet key; a new engine works them out by default. */\n result?: (key: string, row: number, col: number) => SheetResult\n cellTypes?: ReadonlyMap<string, SheetCellType>\n functions?: readonly SheetFunction[]\n locale?: string\n messages?: SheetMessages\n /** The grid's default column width in px; `96` by default. */\n colWidth?: number\n /** The grid's default row height in px; `28` by default. */\n rowHeight?: number\n /** Works out a formula as if it were in a cell of a sheet, by key, for conditional formats; with `result`. */\n evaluate?: (formula: string, key: string) => unknown\n signal?: AbortSignal\n view?: SheetFileView\n}\n\n/** The cell types by name: the built-in ones, then these, the first named winning. */\nexport function cellTypeMap(added: readonly SheetCellType[] = []) {\n const map = new Map<string, SheetCellType>()\n for (const type of plainCellTypes) map.set(type.name, type)\n const seen = new Set<string>()\n for (const type of added) {\n if (seen.has(type.name)) continue\n seen.add(type.name)\n map.set(type.name, type)\n }\n return map\n}\n\n/** What exporters read: a workbook and how its cells show. */\nexport function createFileSource(\n book: SheetWorkbook,\n options: SheetFileSourceOptions = {},\n): SheetFileSource {\n const scope = options.scope ?? 'workbook'\n const types = options.cellTypes ?? cellTypeMap()\n const locale = options.locale\n let result = options.result\n let evaluate = options.evaluate\n if (!result || !evaluate) {\n const engine = new SheetFormulaEngine(book, {\n functions: options.functions,\n locale,\n })\n result ??= (key, row, col) => engine.result(key, row, col)\n evaluate ??= (formula, key) => engine.evaluate(formula, key)\n }\n const read = result\n const work = evaluate\n const at = (sheet: number, row: number) => book.sheets[sheet]?.cells[row]\n const input = (sheet: number, row: number, col: number): SheetCellInput => at(sheet, row)?.[col]\n const resultOf = (sheet: number, row: number, col: number): SheetResult => {\n const data = book.sheets[sheet]\n if (!data) return null\n const value = valueOf(input(sheet, row, col))\n if (typeof value !== 'string' || !value.startsWith('=')) return value\n return read(sheetKey(data), row, col)\n }\n const shown = (sheet: number, row: number, col: number): SheetValue => {\n const data = book.sheets[sheet]\n if (!data) return null\n const key = sheetKey(data)\n const value = valueOf(input(sheet, row, col))\n if (typeof value !== 'string' || !value.startsWith('=')) return value\n // errors as their code, as the grid shows them\n return shownValue(value, (r, c) => read(key, r, c), row, col) as SheetValue\n }\n const typeOf = (sheet: number, row: number, col: number) =>\n resolveCellType(cellOf(input(sheet, row, col)), book, types)\n const textOf = (sheet: number, row: number, col: number) => {\n const value = shown(sheet, row, col)\n const typed = typeOf(sheet, row, col)\n const own = typed?.definition.format?.(value, typed.setup)\n if (own !== undefined) return own\n return formatValue(value, cellOf(input(sheet, row, col))?.format, locale)\n }\n // conditional formats, worked out for a sheet the first time one of its cells is asked about\n const looks = new Map<number, SheetLooks>()\n const looksOf = (sheet: number) => {\n let found = looks.get(sheet)\n if (!found) {\n const data = book.sheets[sheet]!\n const key = sheetKey(data)\n found = new SheetLooks(data.conditionalFormats, {\n valueAt: (row, col) => resultOf(sheet, row, col),\n textAt: (row, col) => textOf(sheet, row, col),\n evaluate: (formula) => work(formula, key),\n styles: book.styles,\n size: { rows: rowCountOf(data), cols: colCountOf(data) },\n lang: locale,\n })\n looks.set(sheet, found)\n }\n return found\n }\n const styleOf = (sheet: number, row: number, col: number) => {\n if (!book.sheets[sheet]) return {}\n const name = cellOf(input(sheet, row, col))?.style\n const own = name ? book.styles?.[name] : undefined\n const look = book.sheets[sheet]!.conditionalFormats?.length\n ? looksOf(sheet).lookAt(row, col)?.style\n : undefined\n const style = look ? layStyle(own, look) : own\n return style ? plainStyle(style) : {}\n }\n\n const index = options.sheet ?? 0\n const pick = (i: number): SheetFileSheet => {\n const data = book.sheets[i]!\n return { index: i, data, bounds: dataBounds(data) }\n }\n let sheets: SheetFileSheet[]\n if (scope === 'workbook') sheets = book.sheets.map((_, i) => pick(i))\n else {\n if (!book.sheets[index]) throw new RangeError(`[@dou-ui/sheet] there is no sheet ${index}`)\n const sheet = pick(index)\n if (options.bounds) sheet.bounds = options.bounds\n sheets = [sheet]\n }\n\n return {\n book,\n scope,\n sheets,\n colWidth: options.colWidth ?? 96,\n rowHeight: options.rowHeight ?? 28,\n locale,\n messages: options.messages ?? sheetMessagesFor(locale),\n value: (sheet, row, col) => valueOf(input(sheet, row, col)),\n result: resultOf,\n text: textOf,\n plain(sheet, row, col) {\n const value = shown(sheet, row, col)\n const typed = typeOf(sheet, row, col)\n return typed?.definition.plain ? typed.definition.plain(value, typed.setup) : value\n },\n style: styleOf,\n format: (sheet, row, col) => cellOf(input(sheet, row, col))?.format,\n cellType: typeOf,\n signal: options.signal,\n view: options.view,\n }\n}\n\n/** Options of `exportWorkbook`. */\nexport interface SheetExportWorkbookOptions<Options = any> {\n /** The extensions whose cell types and functions the workbook uses. */\n extensions?: (SheetExtension | SheetExtension[])[]\n /** `'workbook'` by default, or the exporter's first scope when it can't. */\n scope?: SheetExportScope\n /** The sheet, by index, `id` or name, for `'sheet'` and `'selection'`; the first by default. */\n sheet?: SheetRef\n /** The cells, such as `'A1:D20'`, for `'selection'`. */\n range?: string\n locale?: string\n messages?: SheetMessages\n /** The default column width and row height in px the workbook is laid out with, as `DouSheet`'s `col-width` and `row-height`. */\n colWidth?: number\n rowHeight?: number\n signal?: AbortSignal\n /** Passed on to the exporter. */\n options?: Options\n}\n\n/** The scope an exporter writes: the one asked for when it can, otherwise its first. */\nexport function scopeFor(exporter: SheetExporter, asked?: SheetExportScope) {\n const scopes = exporter.scopes ?? ['workbook', 'sheet', 'selection']\n if (asked && !scopes.includes(asked))\n throw new RangeError(\n `[@dou-ui/sheet] the exporter \"${exporter.name}\" can't export a ${asked}; it exports ${scopes.join(', ')}`,\n )\n return asked ?? scopes[0]!\n}\n\n/** An exporter's output as a `Blob`. */\nexport const toBlob = (out: Blob | string, mime: string) =>\n typeof out === 'string' ? new Blob([out], { type: mime }) : out\n\n/**\n * Writes a workbook with an exporter, without a grid: in a script, a\n * worker or on a server. Formulas are worked out on the way.\n */\nexport async function exportWorkbook<Exporter extends SheetExporter>(\n book: SheetWorkbook,\n exporter: Exporter,\n options: SheetExportWorkbookOptions<OptionsOf<Exporter>> = {},\n): Promise<Blob> {\n if (exporter.needs === 'view')\n throw new Error(\n `[@dou-ui/sheet] the exporter \"${exporter.name}\" draws the grid, so it only works in DouSheet`,\n )\n const resolved = resolveSheetExtensions(options.extensions)\n const scope = scopeFor(exporter, options.scope)\n let bounds: SheetBounds | undefined\n if (options.range != null) {\n const range = parseRange(options.range)\n if (!range) throw new RangeError(`[@dou-ui/sheet] \"${options.range}\" isn't a range`)\n bounds = boundsOf(range)\n }\n let sheet: number | undefined\n if (options.sheet != null) {\n const ref = options.sheet\n sheet =\n typeof ref === 'number'\n ? ref\n : book.sheets.findIndex((s) => s.id === ref || (s.id == null && s.name === ref))\n if (!book.sheets[sheet]) throw new RangeError(`[@dou-ui/sheet] there is no sheet \"${ref}\"`)\n }\n const source = createFileSource(book, {\n scope,\n sheet,\n bounds,\n cellTypes: cellTypeMap(resolved.cellTypes),\n functions: resolved.functions,\n locale: options.locale,\n messages: options.messages,\n colWidth: options.colWidth,\n rowHeight: options.rowHeight,\n signal: options.signal,\n })\n return toBlob(await exporter.export(source, options.options), exporter.mime)\n}\n\n/** Options of `readWorkbook`. */\nexport interface SheetReadWorkbookOptions<Options = any> {\n locale?: string\n messages?: SheetMessages\n signal?: AbortSignal\n /** Passed on to the importer. */\n options?: Options\n}\n\n/** Reads a file with an importer, without a grid. */\nexport async function readWorkbook<Importer extends SheetImporter>(\n file: Blob,\n importer: Importer,\n options: SheetReadWorkbookOptions<OptionsOf<Importer>> = {},\n): Promise<ResultOf<Importer>> {\n const messages = options.messages ?? sheetMessagesFor(options.locale)\n const result = await importer.import(\n file,\n { messages, locale: options.locale, signal: options.signal },\n options.options,\n )\n return result as ResultOf<Importer>\n}\n\n/** The name of a file without its extension, for a sheet's name. */\nexport const baseName = (file: Blob) => {\n const name = 'name' in file ? String((file as File).name) : ''\n return name.replace(/\\.[^.]*$/, '')\n}\n\n/** Options of the CSV exporter. */\nexport interface SheetCSVExportOptions {\n /** `,` by default. */\n delimiter?: string\n /** Starts with a byte order mark, so programs that guess the encoding read it as UTF-8. On by default. */\n bom?: boolean\n /**\n * Writes what the cells show (`¥1,280.00`, `2026年3月5日`) instead of\n * values a spreadsheet can sort and add up (`1280`, `2026-03-05`, what\n * formulas come to). Off by default.\n */\n text?: boolean\n /** Writes formulas as written instead of what they come to. Ignored with `text`. */\n formulas?: boolean\n /** Line breaks between rows; `\\r\\n` by default. */\n newline?: string\n /** Puts a `'` before text a spreadsheet program would run as a formula. On by default. */\n escapeFormulas?: boolean\n}\n\n/** CSV: one sheet (or the selected cells), with the cells' values. */\nexport const csvExporter = defineSheetExporter<SheetCSVExportOptions>({\n name: 'csv',\n label: (m) => m.file.csv,\n extension: 'csv',\n mime: 'text/csv',\n scopes: ['sheet', 'selection'],\n export(source, options = {}) {\n const { index, data, bounds } = source.sheets[0]!\n if (!bounds) return options.bom === false ? '' : ''\n return sheetToCSV(data, {\n range: {\n start: { row: bounds.top, col: bounds.left },\n end: { row: bounds.bottom, col: bounds.right },\n },\n locale: source.locale,\n result: (row, col) => source.result(index, row, col),\n cellText: (_input, _value, row, col) => source.text(index, row, col),\n cellPlain: (_input, _value, row, col) => source.plain(index, row, col),\n delimiter: options.delimiter,\n text: options.text,\n formulas: options.formulas,\n bom: options.bom,\n newline: options.newline,\n escapeFormulas: options.escapeFormulas,\n })\n },\n})\n\n/** CSV and tab-separated text: a new sheet named after the file. */\nexport const csvImporter = defineSheetImporter({\n name: 'csv',\n label: (m) => m.file.csv,\n accept: ['.csv', '.tsv', '.txt', 'text/csv', 'text/tab-separated-values', 'text/plain'],\n async import(file) {\n const text = await file.text()\n const name = baseName(file) || 'Sheet1'\n return { book: { version: 1, sheets: [{ name, cells: readCSV(text) }] } }\n },\n})\n\n/** A label as text. */\nexport const fileLabel = (label: SheetLabel, messages: SheetMessages) => labelOf(label, messages)\n\n/**\n * Puts the built-in exporters (or importers) and the extensions' together,\n * by name: an extension's replaces a built-in one of the same name; two\n * extensions with the same name are an error.\n */\nexport function mergeFileHandlers<T extends { name: string }>(\n builtin: readonly T[],\n added: readonly { extension: string; handler: T }[],\n kind: 'exporter' | 'importer',\n) {\n const owner = new Map<string, string>()\n const own: T[] = []\n for (const { extension, handler } of added) {\n const taken = owner.get(handler.name)\n if (taken)\n throw new Error(\n `[@dou-ui/sheet] the ${kind} \"${handler.name}\" is added by both \"${taken}\" and \"${extension}\"`,\n )\n owner.set(handler.name, extension)\n own.push(handler)\n }\n if (kind === 'importer')\n // importers ask the added ones first\n return [...own, ...builtin.filter((handler) => !owner.has(handler.name))]\n // exporters keep the built-in ones first, as the menu shows them, one replaced in its place\n const byName = new Map(own.map((handler) => [handler.name, handler]))\n const builtinNames = new Set(builtin.map((handler) => handler.name))\n return [\n ...builtin.map((handler) => byName.get(handler.name) ?? handler),\n ...own.filter((handler) => !builtinNames.has(handler.name)),\n ]\n}\n"],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ // src/core/fill.ts
2
+ var DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
3
+ var TIME = /^(\d{2}):(\d{2})$/;
4
+ var NUMBERED = /^(.*?)(\d+)(\D*)$/;
5
+ var DAY = 864e5;
6
+ function kindOf(value) {
7
+ if (typeof value === "number") return {
8
+ kind: "number",
9
+ n: value
10
+ };
11
+ if (typeof value !== "string" || value.startsWith("=")) return { kind: "other" };
12
+ const date = DATE.exec(value);
13
+ if (date) return {
14
+ kind: "date",
15
+ day: Date.UTC(+date[1], +date[2] - 1, +date[3]) / DAY
16
+ };
17
+ const time = TIME.exec(value);
18
+ if (time) return {
19
+ kind: "time",
20
+ minute: +time[1] * 60 + +time[2]
21
+ };
22
+ const numbered = NUMBERED.exec(value);
23
+ if (numbered && (numbered[1] || numbered[3]) && numbered[2].length <= 15) return {
24
+ kind: "numbered",
25
+ before: numbered[1],
26
+ n: Number(numbered[2]),
27
+ width: numbered[2].length,
28
+ after: numbered[3]
29
+ };
30
+ return { kind: "other" };
31
+ }
32
+ var round = (n) => Number(n.toPrecision(15));
33
+ var pad = (n, width = 2) => String(n).padStart(width, "0");
34
+ function dateText(day) {
35
+ const date = /* @__PURE__ */ new Date(day * DAY);
36
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
37
+ }
38
+ /** The step between equal-spaced numbers, or `null` when they aren't. */
39
+ function evenStep(list) {
40
+ const step = list[1] - list[0];
41
+ for (let i = 2; i < list.length; i++) if (round(list[i] - list[i - 1]) !== round(step)) return null;
42
+ return step;
43
+ }
44
+ /** The best straight line through the numbers, as a function of their place. */
45
+ function trend(list) {
46
+ const n = list.length;
47
+ const even = evenStep(list);
48
+ if (even != null) return (i) => round(list[n - 1] + even * (i - n + 1));
49
+ const meanX = (n - 1) / 2;
50
+ const meanY = list.reduce((a, b) => a + b, 0) / n;
51
+ let top = 0;
52
+ let bottom = 0;
53
+ list.forEach((y, x) => {
54
+ top += (x - meanX) * (y - meanY);
55
+ bottom += (x - meanX) ** 2;
56
+ });
57
+ const slope = top / bottom;
58
+ return (i) => round(meanY + slope * (i - meanX));
59
+ }
60
+ /**
61
+ * Months between dates, evenly: on the same day of each month, or each on
62
+ * the last day of its month (`1-31`, `2-28` go on `3-31`, `4-30`). `null`
63
+ * otherwise.
64
+ */
65
+ function monthStep(days) {
66
+ const parts = days.map((day) => {
67
+ const date = /* @__PURE__ */ new Date(day * DAY);
68
+ const next = /* @__PURE__ */ new Date((day + 1) * DAY);
69
+ return {
70
+ month: date.getUTCFullYear() * 12 + date.getUTCMonth(),
71
+ date: date.getUTCDate(),
72
+ last: next.getUTCDate() === 1
73
+ };
74
+ });
75
+ const sameDay = parts.every((part) => part.date === parts[0].date);
76
+ const lastDays = parts.every((part) => part.last);
77
+ if (!sameDay && !lastDays) return null;
78
+ const step = evenStep(parts.map((part) => part.month));
79
+ if (!step) return null;
80
+ return {
81
+ step,
82
+ first: parts[0].month,
83
+ date: sameDay ? parts[0].date : 31
84
+ };
85
+ }
86
+ /** The `count` values that follow `values`, as the fill handle continues them. */
87
+ function continueValues(values, count, options = {}) {
88
+ const { mode = "auto", back = false } = options;
89
+ const copy = mode === "copy";
90
+ const one = back ? -1 : 1;
91
+ const n = values.length;
92
+ const repeat = () => Array.from({ length: count }, (_, i) => values[(n + i) % n] ?? null);
93
+ if (n === 0 || count <= 0) return [];
94
+ const kinds = values.map(kindOf);
95
+ const first = kinds[0];
96
+ if (!kinds.every((kind) => kind.kind === first.kind) || first.kind === "other") return repeat();
97
+ const after = (i) => n + i;
98
+ switch (first.kind) {
99
+ case "number": {
100
+ const list = kinds.map((kind) => kind.n);
101
+ if (n === 1 && mode === "series") return Array.from({ length: count }, (_, i) => round(list[0] + one * (i + 1)));
102
+ if (n === 1 || copy) return repeat();
103
+ const at = trend(list);
104
+ return Array.from({ length: count }, (_, i) => at(after(i)));
105
+ }
106
+ case "date": {
107
+ if (copy) return repeat();
108
+ const days = kinds.map((kind) => kind.day);
109
+ const months = n > 1 ? monthStep(days) : null;
110
+ if (n > 1 && !months && evenStep(days) == null) return repeat();
111
+ if (months) return Array.from({ length: count }, (_, i) => {
112
+ const month = months.first + months.step * after(i);
113
+ const year = Math.floor(month / 12);
114
+ const last = new Date(Date.UTC(year, month % 12 + 1, 0)).getUTCDate();
115
+ return `${year}-${pad(month % 12 + 1)}-${pad(Math.min(months.date, last))}`;
116
+ });
117
+ const step = n === 1 ? one : days[1] - days[0];
118
+ return Array.from({ length: count }, (_, i) => dateText(days[n - 1] + step * (i + 1)));
119
+ }
120
+ case "time": {
121
+ if (copy) return repeat();
122
+ const minutes = kinds.map((kind) => kind.minute);
123
+ const step = n === 1 ? 60 * one : evenStep(minutes);
124
+ if (step == null) return repeat();
125
+ return Array.from({ length: count }, (_, i) => {
126
+ const m = ((minutes[n - 1] + step * (i + 1)) % 1440 + 1440) % 1440;
127
+ return `${pad(Math.floor(m / 60))}:${pad(m % 60)}`;
128
+ });
129
+ }
130
+ case "numbered": {
131
+ if (copy) return repeat();
132
+ const list = kinds;
133
+ if (list.some((kind) => kind.before !== first.before || kind.after !== first.after)) return repeat();
134
+ const numbers = list.map((kind) => kind.n);
135
+ const step = n === 1 ? one : evenStep(numbers);
136
+ if (step == null) return repeat();
137
+ return Array.from({ length: count }, (_, i) => {
138
+ const value = Math.max(0, numbers[n - 1] + step * (i + 1));
139
+ return `${first.before}${String(value).padStart(first.width, "0")}${first.after}`;
140
+ });
141
+ }
142
+ }
143
+ }
144
+ /**
145
+ * Whether filling in `auto` mode continues the values rather than copying
146
+ * them. Ctrl / ⌘ while dragging the fill handle does the other.
147
+ */
148
+ function fillContinues(values) {
149
+ return continueValues(values, values.length).some((value, i) => value !== values[i]);
150
+ }
151
+ export { continueValues, fillContinues };
152
+
153
+ //# sourceMappingURL=fill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fill.js","names":[],"sources":["../../src/core/fill.ts"],"sourcesContent":["import type { SheetValue } from './model'\n\n/*\n * Continuing a run of values, as dragging the fill handle does: numbers by\n * their trend, dates by days or months, times by hours, text ending in a\n * number (\"第 1 周\") by that number; anything else repeats.\n */\n\nconst DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/\nconst TIME = /^(\\d{2}):(\\d{2})$/\nconst NUMBERED = /^(.*?)(\\d+)(\\D*)$/\n\ntype Kind =\n | { kind: 'number'; n: number }\n | { kind: 'date'; day: number }\n | { kind: 'time'; minute: number }\n | { kind: 'numbered'; before: string; n: number; width: number; after: string }\n | { kind: 'other' }\n\nconst DAY = 86_400_000\n\nfunction kindOf(value: SheetValue): Kind {\n if (typeof value === 'number') return { kind: 'number', n: value }\n if (typeof value !== 'string' || value.startsWith('=')) return { kind: 'other' }\n const date = DATE.exec(value)\n if (date) return { kind: 'date', day: Date.UTC(+date[1]!, +date[2]! - 1, +date[3]!) / DAY }\n const time = TIME.exec(value)\n if (time) return { kind: 'time', minute: +time[1]! * 60 + +time[2]! }\n const numbered = NUMBERED.exec(value)\n if (numbered && (numbered[1] || numbered[3]) && numbered[2]!.length <= 15)\n return {\n kind: 'numbered',\n before: numbered[1]!,\n n: Number(numbered[2]),\n width: numbered[2]!.length,\n after: numbered[3]!,\n }\n return { kind: 'other' }\n}\n\nconst round = (n: number) => Number(n.toPrecision(15))\n\nconst pad = (n: number, width = 2) => String(n).padStart(width, '0')\n\nfunction dateText(day: number) {\n const date = new Date(day * DAY)\n return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`\n}\n\n/** The step between equal-spaced numbers, or `null` when they aren't. */\nfunction evenStep(list: number[]) {\n const step = list[1]! - list[0]!\n for (let i = 2; i < list.length; i++)\n if (round(list[i]! - list[i - 1]!) !== round(step)) return null\n return step\n}\n\n/** The best straight line through the numbers, as a function of their place. */\nfunction trend(list: number[]) {\n const n = list.length\n const even = evenStep(list)\n if (even != null) return (i: number) => round(list[n - 1]! + even * (i - n + 1))\n const meanX = (n - 1) / 2\n const meanY = list.reduce((a, b) => a + b, 0) / n\n let top = 0\n let bottom = 0\n list.forEach((y, x) => {\n top += (x - meanX) * (y - meanY)\n bottom += (x - meanX) ** 2\n })\n const slope = top / bottom\n return (i: number) => round(meanY + slope * (i - meanX))\n}\n\n/**\n * Months between dates, evenly: on the same day of each month, or each on\n * the last day of its month (`1-31`, `2-28` go on `3-31`, `4-30`). `null`\n * otherwise.\n */\nfunction monthStep(days: number[]) {\n const parts = days.map((day) => {\n const date = new Date(day * DAY)\n const next = new Date((day + 1) * DAY)\n return {\n month: date.getUTCFullYear() * 12 + date.getUTCMonth(),\n date: date.getUTCDate(),\n last: next.getUTCDate() === 1,\n }\n })\n const sameDay = parts.every((part) => part.date === parts[0]!.date)\n const lastDays = parts.every((part) => part.last)\n if (!sameDay && !lastDays) return null\n const step = evenStep(parts.map((part) => part.month))\n if (!step) return null\n return { step, first: parts[0]!.month, date: sameDay ? parts[0]!.date : 31 }\n}\n\n/**\n * How the fill handle fills: `auto` continues runs it recognises and\n * copies a single number; `series` also counts a single number up by one;\n * `copy` only repeats.\n */\nexport type SheetFillMode = 'auto' | 'series' | 'copy'\n\nexport interface SheetFillOptions {\n /** @default 'auto' */\n mode?: SheetFillMode\n /**\n * The values are in reverse, filling up or left: a single value counts\n * down.\n */\n back?: boolean\n}\n\n/** The `count` values that follow `values`, as the fill handle continues them. */\nexport function continueValues(\n values: readonly SheetValue[],\n count: number,\n options: SheetFillOptions = {},\n): SheetValue[] {\n const { mode = 'auto', back = false } = options\n const copy = mode === 'copy'\n const one = back ? -1 : 1\n const n = values.length\n const repeat = () => Array.from({ length: count }, (_, i) => values[(n + i) % n] ?? null)\n if (n === 0 || count <= 0) return []\n const kinds = values.map(kindOf)\n const first = kinds[0]!\n const same = kinds.every((kind) => kind.kind === first.kind)\n if (!same || first.kind === 'other') return repeat()\n const after = (i: number) => n + i\n switch (first.kind) {\n case 'number': {\n const list = kinds.map((kind) => (kind as { n: number }).n)\n // one number copies, unless asked for a series\n if (n === 1 && mode === 'series')\n return Array.from({ length: count }, (_, i) => round(list[0]! + one * (i + 1)))\n if (n === 1 || copy) return repeat()\n const at = trend(list)\n return Array.from({ length: count }, (_, i) => at(after(i)))\n }\n case 'date': {\n if (copy) return repeat()\n const days = kinds.map((kind) => (kind as { day: number }).day)\n // the same day of each month goes on by months, other even steps by days\n const months = n > 1 ? monthStep(days) : null\n if (n > 1 && !months && evenStep(days) == null) return repeat()\n if (months) {\n return Array.from({ length: count }, (_, i) => {\n const month = months.first + months.step * after(i)\n const year = Math.floor(month / 12)\n const last = new Date(Date.UTC(year, (month % 12) + 1, 0)).getUTCDate()\n return `${year}-${pad((month % 12) + 1)}-${pad(Math.min(months.date, last))}`\n })\n }\n const step = n === 1 ? one : days[1]! - days[0]!\n return Array.from({ length: count }, (_, i) => dateText(days[n - 1]! + step * (i + 1)))\n }\n case 'time': {\n if (copy) return repeat()\n const minutes = kinds.map((kind) => (kind as { minute: number }).minute)\n const step = n === 1 ? 60 * one : evenStep(minutes)\n if (step == null) return repeat()\n return Array.from({ length: count }, (_, i) => {\n const m = (((minutes[n - 1]! + step * (i + 1)) % 1440) + 1440) % 1440\n return `${pad(Math.floor(m / 60))}:${pad(m % 60)}`\n })\n }\n case 'numbered': {\n if (copy) return repeat()\n const list = kinds as Extract<Kind, { kind: 'numbered' }>[]\n if (list.some((kind) => kind.before !== first.before || kind.after !== first.after))\n return repeat()\n const numbers = list.map((kind) => kind.n)\n const step = n === 1 ? one : evenStep(numbers)\n if (step == null) return repeat()\n return Array.from({ length: count }, (_, i) => {\n const value = Math.max(0, numbers[n - 1]! + step * (i + 1))\n return `${first.before}${String(value).padStart(first.width, '0')}${first.after}`\n })\n }\n }\n}\n\n/**\n * Whether filling in `auto` mode continues the values rather than copying\n * them. Ctrl / ⌘ while dragging the fill handle does the other.\n */\nexport function fillContinues(values: readonly SheetValue[]) {\n const next = continueValues(values, values.length)\n return next.some((value, i) => value !== values[i])\n}\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ // src/core/find.ts
2
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3
+ /**
4
+ * Tells whether a cell's text matches the query; `null` when there is
5
+ * nothing to look for.
6
+ */
7
+ function cellMatcher(query) {
8
+ const { search } = query;
9
+ if (!search) return null;
10
+ if (query.caseSensitive) return query.wholeCell ? (text) => text === search : (text) => text.includes(search);
11
+ const lower = search.toLowerCase();
12
+ return query.wholeCell ? (text) => text.toLowerCase() === lower : (text) => text.toLowerCase().includes(lower);
13
+ }
14
+ /**
15
+ * The text with every match of the query replaced; the text as it is when
16
+ * nothing in it matches. With `wholeCell` the whole text is replaced.
17
+ */
18
+ function replaceInText(text, query) {
19
+ const test = cellMatcher(query);
20
+ if (!test || !test(text)) return text;
21
+ const replace = query.replace ?? "";
22
+ if (query.wholeCell) return replace;
23
+ const re = new RegExp(escapeRegExp(query.search), query.caseSensitive ? "g" : "gi");
24
+ return text.replace(re, () => replace);
25
+ }
26
+ /** Whether `a` comes before `b`: by sheet, then row, then column. */
27
+ var foundBefore = (a, b) => a.sheet !== b.sheet ? a.sheet < b.sheet : a.row !== b.row ? a.row < b.row : a.col < b.col;
28
+ /** The index of the first match at or after `at` (wrapping round to the first); `-1` for none. */
29
+ function foundFrom(list, at) {
30
+ if (!list.length) return -1;
31
+ let lo = 0;
32
+ let hi = list.length;
33
+ while (lo < hi) {
34
+ const mid = lo + hi >> 1;
35
+ if (foundBefore(list[mid], at)) lo = mid + 1;
36
+ else hi = mid;
37
+ }
38
+ return lo === list.length ? 0 : lo;
39
+ }
40
+ export { cellMatcher, foundBefore, foundFrom, replaceInText };
41
+
42
+ //# sourceMappingURL=find.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.js","names":[],"sources":["../../src/core/find.ts"],"sourcesContent":["/** What to look for in the cells, and how. */\nexport interface SheetSearchQuery {\n /** The text to find. */\n search: string\n /** What `replace` puts in place of each match. */\n replace?: string\n /** Tells upper and lower case apart. */\n caseSensitive?: boolean\n /** A cell matches only when its whole text is the search, not when it contains it. */\n wholeCell?: boolean\n /**\n * Where to look: `'values'` (the default) reads the text cells show, a\n * formula's result included; `'formulas'` reads what was typed, so a\n * formula is found by the formula itself.\n */\n lookIn?: 'values' | 'formulas'\n /** Which cells: the sheet shown (the default) or every sheet of the workbook. */\n scope?: 'sheet' | 'workbook'\n}\n\n/** A cell that matches, on the sheet with index `sheet`. */\nexport interface SheetMatch {\n sheet: number\n row: number\n col: number\n}\n\nconst escapeRegExp = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Tells whether a cell's text matches the query; `null` when there is\n * nothing to look for.\n */\nexport function cellMatcher(query: SheetSearchQuery): ((text: string) => boolean) | null {\n const { search } = query\n if (!search) return null\n if (query.caseSensitive)\n return query.wholeCell ? (text) => text === search : (text) => text.includes(search)\n const lower = search.toLowerCase()\n return query.wholeCell\n ? (text) => text.toLowerCase() === lower\n : (text) => text.toLowerCase().includes(lower)\n}\n\n/**\n * The text with every match of the query replaced; the text as it is when\n * nothing in it matches. With `wholeCell` the whole text is replaced.\n */\nexport function replaceInText(text: string, query: SheetSearchQuery): string {\n const test = cellMatcher(query)\n if (!test || !test(text)) return text\n const replace = query.replace ?? ''\n if (query.wholeCell) return replace\n const re = new RegExp(escapeRegExp(query.search), query.caseSensitive ? 'g' : 'gi')\n // a function, so `$&` and the like in the replacement stay as typed\n return text.replace(re, () => replace)\n}\n\n/** Whether `a` comes before `b`: by sheet, then row, then column. */\nexport const foundBefore = (a: SheetMatch, b: SheetMatch) =>\n a.sheet !== b.sheet ? a.sheet < b.sheet : a.row !== b.row ? a.row < b.row : a.col < b.col\n\n/** The index of the first match at or after `at` (wrapping round to the first); `-1` for none. */\nexport function foundFrom(list: readonly SheetMatch[], at: SheetMatch): number {\n if (!list.length) return -1\n let lo = 0\n let hi = list.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n if (foundBefore(list[mid]!, at)) lo = mid + 1\n else hi = mid\n }\n return lo === list.length ? 0 : lo\n}\n"],"mappings":";AA2BA,IAAM,gBAAgB,SAAiB,KAAK,QAAQ,uBAAuB,MAAM;;;;;AAMjF,SAAgB,YAAY,OAA6D;CACvF,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,MAAM,eACR,OAAO,MAAM,aAAa,SAAS,SAAS,UAAU,SAAS,KAAK,SAAS,MAAM;CACrF,MAAM,QAAQ,OAAO,YAAY;CACjC,OAAO,MAAM,aACR,SAAS,KAAK,YAAY,MAAM,SAChC,SAAS,KAAK,YAAY,CAAC,CAAC,SAAS,KAAK;AACjD;;;;;AAMA,SAAgB,cAAc,MAAc,OAAiC;CAC3E,MAAM,OAAO,YAAY,KAAK;CAC9B,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,GAAG,OAAO;CACjC,MAAM,UAAU,MAAM,WAAW;CACjC,IAAI,MAAM,WAAW,OAAO;CAC5B,MAAM,KAAK,IAAI,OAAO,aAAa,MAAM,MAAM,GAAG,MAAM,gBAAgB,MAAM,IAAI;CAElF,OAAO,KAAK,QAAQ,UAAU,OAAO;AACvC;;AAGA,IAAa,eAAe,GAAe,MACzC,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;;AAGxF,SAAgB,UAAU,MAA6B,IAAwB;CAC7E,IAAI,CAAC,KAAK,QAAQ,OAAO;CACzB,IAAI,KAAK;CACT,IAAI,KAAK,KAAK;CACd,OAAO,KAAK,IAAI;EACd,MAAM,MAAO,KAAK,MAAO;EACzB,IAAI,YAAY,KAAK,MAAO,EAAE,GAAG,KAAK,MAAM;OACvC,KAAK;CACZ;CACA,OAAO,OAAO,KAAK,SAAS,IAAI;AAClC"}