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

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,1582 @@
1
+ import { boundsOf, columnIndex, dropReferences, formatRange, moveReferences, parseCell, parseRange, renameReferences, shiftFormula, splitSheet } from "./address.js";
2
+ import { cellOf, colCountOf, compactCell, rowCountOf, valueOf } from "./model.js";
3
+ import { continueValues } from "./fill.js";
4
+ import { mergesOf, overlaps } from "./merge.js";
5
+ import { groupRange, groupSpan, groupsOf } from "./group.js";
6
+ import { sortRows, textCollator } from "./data.js";
7
+ import { rangesOf } from "./conditional.js";
8
+ // src/core/transaction.ts
9
+ /** The lists of rules a sheet keeps, each rule with a `range`. */
10
+ /** Throws when a rule's ranges aren't all ranges. */
11
+ function checkRuleRange(range) {
12
+ const texts = rangesOf(range);
13
+ if (!texts.length) throw new RangeError("[@dou-ui/sheet] a rule needs a range");
14
+ for (const text of texts) if (!parseRange(text)) throw new RangeError(`[@dou-ui/sheet] "${text}" isn't a range`);
15
+ }
16
+ /** A style written the same way whatever order its fields came in. */
17
+ var styleKey = (style) => JSON.stringify(style, Object.keys(style).sort());
18
+ /** What a step calls a sheet: its `id`, or its name when it has none. */
19
+ var sheetKey = (sheet) => sheet.id ?? sheet.name;
20
+ /**
21
+ * A batch of changes. Steps apply as they are added, so later calls see
22
+ * earlier ones; `doc` is the result. Cells and ranges without a sheet are
23
+ * on `sheet` (in the grid, the one shown).
24
+ */
25
+ var SheetTransaction = class {
26
+ before;
27
+ sheet;
28
+ steps = [];
29
+ /** What undoes each step, in the order the steps were made. */
30
+ undoing = [];
31
+ reversed = null;
32
+ /** The steps that undo this transaction, in the order to apply them. */
33
+ get inverse() {
34
+ this.reversed ??= this.undoing.slice().reverse();
35
+ return this.reversed;
36
+ }
37
+ meta = {};
38
+ book;
39
+ /** What this transaction has copied, so it copies each thing once. */
40
+ own = {
41
+ book: false,
42
+ sheets: false,
43
+ styles: false,
44
+ types: false
45
+ };
46
+ /** The sheets this transaction copied, and the rows it copied of each. */
47
+ ownSheets = /* @__PURE__ */ new WeakSet();
48
+ ownRows = /* @__PURE__ */ new WeakMap();
49
+ constructor(before, sheet = 0) {
50
+ this.before = before;
51
+ this.sheet = sheet;
52
+ this.book = before;
53
+ }
54
+ get doc() {
55
+ return this.book;
56
+ }
57
+ /** Whether the change makes any change: it has steps and wasn't cancelled. */
58
+ get changed() {
59
+ return this.steps.length > 0 && !this.stopped;
60
+ }
61
+ stopped = null;
62
+ /**
63
+ * Stops the change: none of it is applied or kept for undo, and the
64
+ * editor fires `transactionCancel` and says why. For an extension's `transaction`
65
+ * hook, e.g. to keep people from changing what they may not; hooks after
66
+ * it don't run. A change kept out of the history (`meta.history: false`),
67
+ * such as undo, redo or another person's, can't be cancelled and goes ahead.
68
+ */
69
+ cancel(reason) {
70
+ if (this.meta.history !== false) this.stopped = { reason };
71
+ else warnUncancellable();
72
+ return this;
73
+ }
74
+ /** Whether `cancel` stopped it. */
75
+ get cancelled() {
76
+ return this.stopped !== null;
77
+ }
78
+ /** What `cancel` was given, to tell people why. */
79
+ get cancelReason() {
80
+ return this.stopped?.reason;
81
+ }
82
+ setMeta(meta) {
83
+ Object.assign(this.meta, meta);
84
+ return this;
85
+ }
86
+ /** The index of a sheet, by index, `id` or name; `-1` when there is none. */
87
+ indexOf(ref = this.sheet) {
88
+ const { sheets } = this.book;
89
+ if (typeof ref === "number") return ref >= 0 && ref < sheets.length ? ref : -1;
90
+ const byId = sheets.findIndex((sheet) => sheet.id === ref);
91
+ return byId === -1 ? sheets.findIndex((sheet) => sheet.id == null && sheet.name === ref) : byId;
92
+ }
93
+ /** A sheet as it is now in this transaction. */
94
+ getSheet(ref = this.sheet) {
95
+ const index = this.indexOf(ref);
96
+ if (index === -1) throw new RangeError(`[@dou-ui/sheet] there is no sheet "${ref}"`);
97
+ return this.book.sheets[index];
98
+ }
99
+ getCell(at) {
100
+ const { index, row, col } = this.cellAt(at);
101
+ return this.book.sheets[index].cells[row]?.[col] ?? void 0;
102
+ }
103
+ step(step) {
104
+ const inverse = this.apply(step);
105
+ if (inverse) {
106
+ this.steps.push(step);
107
+ this.undoing.push(inverse);
108
+ this.reversed = null;
109
+ }
110
+ return this;
111
+ }
112
+ /** Replaces a cell; `undefined` empties it. */
113
+ setCell(at, cell) {
114
+ const { index, row, col } = this.cellAt(at);
115
+ return this.step({
116
+ type: "setCell",
117
+ sheet: this.keyOf(index),
118
+ row,
119
+ col,
120
+ cell
121
+ });
122
+ }
123
+ /** Changes some of a cell's fields and keeps the rest. */
124
+ updateCell(at, patch) {
125
+ const target = this.cellAt(at);
126
+ const point = {
127
+ sheet: target.index,
128
+ row: target.row,
129
+ col: target.col
130
+ };
131
+ return this.setCell(point, compactCell({
132
+ ...cellOf(this.getCell(point)),
133
+ ...patch
134
+ }));
135
+ }
136
+ /** Writes a cell's value and keeps its style and format. */
137
+ setValue(at, value) {
138
+ return this.updateCell(at, { value: value ?? void 0 });
139
+ }
140
+ /** Empties the values in a range, keeping styles and formats. */
141
+ clearValues(at) {
142
+ const { index, bounds } = this.rangeAt(at);
143
+ const { cells } = this.book.sheets[index];
144
+ const last = Math.min(bounds.bottom, cells.length - 1);
145
+ for (let row = bounds.top; row <= last; row++) {
146
+ const line = cells[row];
147
+ if (!line) continue;
148
+ const end = Math.min(bounds.right, line.length - 1);
149
+ for (let col = bounds.left; col <= end; col++) {
150
+ const cell = line[col];
151
+ if (cell != null && cell !== "" && (typeof cell !== "object" || cell.value != null)) this.setValue({
152
+ sheet: index,
153
+ row,
154
+ col
155
+ }, void 0);
156
+ }
157
+ }
158
+ return this;
159
+ }
160
+ /** Writes a cell's note; empty text or `undefined` takes it off. */
161
+ setNote(at, note) {
162
+ return this.updateCell(at, { note: note?.trim() ? note : void 0 });
163
+ }
164
+ /**
165
+ * Puts a comment thread on a cell, or takes it off with `undefined`. Use
166
+ * it with `{ history: false }`: a thread stays when the change before it
167
+ * is undone.
168
+ */
169
+ setComment(at, id) {
170
+ return this.updateCell(at, { comment: id || void 0 });
171
+ }
172
+ /** Takes the notes off the cells in a range, keeping what they hold. */
173
+ clearNotes(at) {
174
+ const { index, bounds } = this.rangeAt(at);
175
+ const { cells } = this.book.sheets[index];
176
+ const last = Math.min(bounds.bottom, cells.length - 1);
177
+ for (let row = bounds.top; row <= last; row++) {
178
+ const line = cells[row];
179
+ if (!line) continue;
180
+ const end = Math.min(bounds.right, line.length - 1);
181
+ for (let col = bounds.left; col <= end; col++) {
182
+ const cell = line[col];
183
+ if (cell && typeof cell === "object" && cell.note != null) this.setNote({
184
+ sheet: index,
185
+ row,
186
+ col
187
+ }, void 0);
188
+ }
189
+ }
190
+ return this;
191
+ }
192
+ /** Sets a row's height or a column's width in px; `undefined` for the default, `0` hides it. */
193
+ setSize(axis, index, size, sheet) {
194
+ const key = this.keyOf(this.mustIndex(sheet));
195
+ return this.step({
196
+ type: "setSize",
197
+ sheet: key,
198
+ axis,
199
+ index,
200
+ size
201
+ });
202
+ }
203
+ /** Changes a sheet's name, freeze, row or column count. */
204
+ updateSheet(patch, sheet) {
205
+ return this.step({
206
+ type: "updateSheet",
207
+ sheet: this.keyOf(this.mustIndex(sheet)),
208
+ patch
209
+ });
210
+ }
211
+ /** Changes the workbook's own settings, such as `protect`. */
212
+ updateBook(patch) {
213
+ return this.step({
214
+ type: "updateBook",
215
+ patch
216
+ });
217
+ }
218
+ /**
219
+ * Protects a sheet, or with `false` stops protecting it: its locked cells
220
+ * (all but those whose style has `locked: false`) can't be changed, nor
221
+ * can the sheet's rows, columns and rules, except what `options` allows.
222
+ */
223
+ protectSheet(protect, sheet) {
224
+ return this.updateSheet({ protect: protect === false ? void 0 : protect }, sheet);
225
+ }
226
+ setStyle(name, style) {
227
+ return this.step({
228
+ type: "setStyle",
229
+ name,
230
+ style
231
+ });
232
+ }
233
+ /** Adds, replaces or (with `undefined`) removes a named cell type, such as a list to pick from. */
234
+ setType(name, setup) {
235
+ return this.step({
236
+ type: "setType",
237
+ name,
238
+ setup
239
+ });
240
+ }
241
+ /**
242
+ * Writes a block of cells with its top left corner at `at`, row by row.
243
+ * `undefined` in the block empties a cell; a shorter row leaves the cells
244
+ * past it as they are.
245
+ */
246
+ setCells(at, block) {
247
+ const { index, row, col } = this.cellAt(at);
248
+ block.forEach((line, r) => line.forEach((cell, c) => this.setCell({
249
+ sheet: index,
250
+ row: row + r,
251
+ col: col + c
252
+ }, cell)));
253
+ return this;
254
+ }
255
+ /**
256
+ * Fills `to` from `from`, the cells it extends down, up, right or left:
257
+ * each line of cells continues as the fill handle does (see
258
+ * `continueValues`), with the styles and formats of the cells it repeats.
259
+ * Formulas are copied with their references moved along. `mode` is
260
+ * `auto` as dragging does, `series` to count a single number up too, or
261
+ * `copy` to only repeat, as Ctrl+D does. With `keepStyle`, the filled
262
+ * cells keep their own styles and formats, as on a protected sheet.
263
+ */
264
+ fill(from, to, options = {}) {
265
+ const { index, bounds: s } = this.rangeAt(from);
266
+ const t = this.rangeAt(to).bounds;
267
+ const vertical = t.left === s.left && t.right === s.right;
268
+ const horizontal = t.top === s.top && t.bottom === s.bottom;
269
+ if (vertical && horizontal) return this;
270
+ let direction;
271
+ if (vertical && t.top === s.top && t.bottom > s.bottom) direction = "down";
272
+ else if (vertical && t.bottom === s.bottom && t.top < s.top) direction = "up";
273
+ else if (horizontal && t.left === s.left && t.right > s.right) direction = "right";
274
+ else if (horizontal && t.right === s.right && t.left < s.left) direction = "left";
275
+ else throw new RangeError("[@dou-ui/sheet] the range to fill must extend the cells it is filled from");
276
+ const down = direction === "down" || direction === "up";
277
+ const forward = direction === "down" || direction === "right";
278
+ const count = down ? t.bottom - t.top - (s.bottom - s.top) : t.right - t.left - (s.right - s.left);
279
+ const length = down ? s.bottom - s.top + 1 : s.right - s.left + 1;
280
+ const lines = down ? [s.left, s.right] : [s.top, s.bottom];
281
+ const cells = this.book.sheets[index].cells;
282
+ for (let line = lines[0]; line <= lines[1]; line++) {
283
+ const points = Array.from({ length }, (_, i) => {
284
+ const along = forward ? i : length - 1 - i;
285
+ return down ? {
286
+ row: s.top + along,
287
+ col: line
288
+ } : {
289
+ row: line,
290
+ col: s.left + along
291
+ };
292
+ });
293
+ const sources = points.map((p) => cells[p.row]?.[p.col] ?? void 0);
294
+ const values = continueValues(sources.map(valueOf), count, {
295
+ mode: options.mode,
296
+ back: !forward
297
+ });
298
+ for (let i = 0; i < count; i++) {
299
+ const k = i % length;
300
+ const origin = points[k];
301
+ const step = (forward ? 1 : -1) * (length + i - k);
302
+ const point = down ? {
303
+ row: origin.row + step,
304
+ col: line
305
+ } : {
306
+ row: line,
307
+ col: origin.col + step
308
+ };
309
+ const source = cellOf(sources[k]);
310
+ const value = source?.value;
311
+ const next = typeof value === "string" && value.startsWith("=") ? shiftFormula(value, point.row - origin.row, point.col - origin.col) : values[i];
312
+ const look = options.keepStyle ? cellOf(cells[point.row]?.[point.col]) : source;
313
+ const target = {
314
+ sheet: index,
315
+ ...point
316
+ };
317
+ this.setCell(target, compactCell({
318
+ ...source,
319
+ value: next ?? void 0,
320
+ style: look?.style,
321
+ format: look?.format,
322
+ comment: cellOf(this.getCell(target))?.comment
323
+ }));
324
+ }
325
+ }
326
+ return this;
327
+ }
328
+ /**
329
+ * Sorts the rows of a range by the values in `keys`' columns; the cells
330
+ * outside the range stay. `header` keeps the first row in place. Equal rows
331
+ * keep their order, and empty values go last either way. Formulas move
332
+ * with their rows and their references move along, as when copied.
333
+ * `valueAt` gives what a cell comes to, e.g. a formula's result; the value
334
+ * as written is used without it. Ranges with merged cells are not sorted.
335
+ */
336
+ sortRange(at, keys, options = {}) {
337
+ const { index, bounds } = this.rangeIn(at);
338
+ const sheet = this.book.sheets[index];
339
+ const top = bounds.top + (options.header ? 1 : 0);
340
+ if (top >= bounds.bottom || !keys.length) return this;
341
+ const body = {
342
+ ...bounds,
343
+ top
344
+ };
345
+ if (mergesOf(sheet).touching(body).length) throw new RangeError("[@dou-ui/sheet] a range with merged cells cannot be sorted");
346
+ const rows = Array.from({ length: bounds.bottom - top + 1 }, (_, i) => top + i);
347
+ const cells = sheet.cells;
348
+ const valueAt = options.valueAt ?? ((row, col) => valueOf(cells[row]?.[col] ?? void 0));
349
+ const order = sortRows(rows, keys, valueAt, textCollator(options.lang));
350
+ const moved = order.map((from) => Array.from({ length: bounds.right - bounds.left + 1 }, (_, c) => cellOf(cells[from]?.[bounds.left + c] ?? void 0)));
351
+ order.forEach((from, i) => {
352
+ const to = top + i;
353
+ if (from === to) return;
354
+ moved[i].forEach((cell, c) => {
355
+ const value = cell?.value;
356
+ const next = typeof value === "string" && value.startsWith("=") ? compactCell({
357
+ ...cell,
358
+ value: shiftFormula(value, to - from, 0)
359
+ }) : compactCell(cell);
360
+ this.setCell({
361
+ sheet: index,
362
+ row: to,
363
+ col: bounds.left + c
364
+ }, next);
365
+ });
366
+ });
367
+ return this;
368
+ }
369
+ /**
370
+ * Puts a filter on a range, headings in its first row; `null` takes the
371
+ * filter off, showing the rows it hid. A new range drops the old rules.
372
+ */
373
+ setFilter(at) {
374
+ if (at == null) {
375
+ const index = this.mustIndex();
376
+ if (!this.book.sheets[index].filter) return this;
377
+ return this.updateSheet({ filter: void 0 }, index);
378
+ }
379
+ const { index, bounds } = this.rangeIn(at);
380
+ const old = this.book.sheets[index].filter;
381
+ const range = rangeText(bounds);
382
+ if (old?.range === range) return this;
383
+ return this.updateSheet({ filter: { range } }, index);
384
+ }
385
+ /**
386
+ * Sets which rows a column of the filter keeps; `undefined` keeps all.
387
+ * `sorted` marks the column the rows were sorted by, for its button.
388
+ */
389
+ filterColumn(col, rule, sheet = this.sheet) {
390
+ const index = this.mustIndex(sheet);
391
+ const filter = this.book.sheets[index].filter;
392
+ if (!filter) throw new RangeError("[@dou-ui/sheet] the sheet has no filter");
393
+ const columns = { ...filter.columns };
394
+ if (rule) columns[col] = rule;
395
+ else delete columns[col];
396
+ const next = {
397
+ ...filter,
398
+ columns: Object.keys(columns).length ? columns : void 0
399
+ };
400
+ if (!next.columns) delete next.columns;
401
+ return this.updateSheet({ filter: next }, index);
402
+ }
403
+ /** Remembers the filter column the rows were sorted by, or forgets it with `undefined`. */
404
+ markSorted(sorted, sheet = this.sheet) {
405
+ const index = this.mustIndex(sheet);
406
+ const filter = this.book.sheets[index].filter;
407
+ if (!filter) return this;
408
+ const next = {
409
+ ...filter,
410
+ sorted
411
+ };
412
+ if (!sorted) delete next.sorted;
413
+ return this.updateSheet({ filter: next }, index);
414
+ }
415
+ /**
416
+ * Adds a conditional format after the sheet's others; where two give the
417
+ * same part of a cell's look, the earlier one wins. The format's `range`
418
+ * names cells of `sheet`.
419
+ */
420
+ addConditionalFormat(format, sheet = this.sheet) {
421
+ return this.addRule("conditionalFormats", format, sheet);
422
+ }
423
+ /** Sets a sheet's conditional formats, in order, e.g. after moving one up; `[]` removes them all. */
424
+ setConditionalFormats(formats, sheet = this.sheet) {
425
+ return this.setRules("conditionalFormats", formats, sheet);
426
+ }
427
+ /**
428
+ * Removes the conditional formats whose cells overlap a range, such as
429
+ * the selection; without a range, all of the sheet's.
430
+ */
431
+ clearConditionalFormats(at) {
432
+ return this.clearRules("conditionalFormats", at);
433
+ }
434
+ /**
435
+ * Adds a validation rule after the sheet's others; where two cover a
436
+ * cell, the earlier one applies. The rule's `range` names cells of `sheet`.
437
+ */
438
+ addValidation(rule, sheet = this.sheet) {
439
+ return this.addRule("validations", rule, sheet);
440
+ }
441
+ /** Sets a sheet's validation rules, in order; `[]` removes them all. */
442
+ setValidations(rules, sheet = this.sheet) {
443
+ return this.setRules("validations", rules, sheet);
444
+ }
445
+ /**
446
+ * Removes the validation rules whose cells overlap a range, such as the
447
+ * selection; without a range, all of the sheet's.
448
+ */
449
+ clearValidations(at) {
450
+ return this.clearRules("validations", at);
451
+ }
452
+ addRule(key, rule, sheet) {
453
+ const index = this.mustIndex(sheet);
454
+ checkRuleRange(rule.range);
455
+ const rules = this.book.sheets[index][key] ?? [];
456
+ return this.updateSheet({ [key]: [...rules, rule] }, index);
457
+ }
458
+ setRules(key, rules, sheet) {
459
+ const index = this.mustIndex(sheet);
460
+ for (const rule of rules) checkRuleRange(rule.range);
461
+ return this.updateSheet({ [key]: rules.length ? [...rules] : void 0 }, index);
462
+ }
463
+ clearRules(key, at) {
464
+ const { index, bounds } = at == null ? {
465
+ index: this.mustIndex(),
466
+ bounds: null
467
+ } : this.rangeAt(at);
468
+ const rules = this.book.sheets[index][key];
469
+ if (!rules?.length) return this;
470
+ const kept = bounds ? rules.filter((rule) => rangesOf(rule.range).every((text) => {
471
+ const range = parseRange(text);
472
+ return range && !overlaps(boundsOf(range), bounds);
473
+ })) : [];
474
+ if (kept.length === rules.length) return this;
475
+ return this.updateSheet({ [key]: kept.length ? kept : void 0 }, index);
476
+ }
477
+ /**
478
+ * Groups rows so they can be folded away: `groupRows('3:8')` or
479
+ * `groupRows(2, 6)`. A group may hold other groups, at most eight deep;
480
+ * one beside a group of the same level joins it, and one that would cross
481
+ * a group, or nest too deep, throws a `RangeError`.
482
+ */
483
+ groupRows(at, count, sheet) {
484
+ const lines = linesOf("row", at, count);
485
+ return this.groupLines("row", lines.from, lines.count, sheet);
486
+ }
487
+ /** Groups columns so they can be folded away: `groupCols('B:D')`. */
488
+ groupCols(at, count, sheet) {
489
+ const lines = linesOf("col", at, count);
490
+ return this.groupLines("col", lines.from, lines.count, sheet);
491
+ }
492
+ /**
493
+ * Takes rows out of their groups: the groups that lie within them go (the
494
+ * outer ones, when groups nest); without any, the rows leave the innermost
495
+ * group they are in, which may split it in two.
496
+ */
497
+ ungroupRows(at, count, sheet) {
498
+ const lines = linesOf("row", at, count);
499
+ return this.ungroupLines("row", lines.from, lines.count, sheet);
500
+ }
501
+ /** Takes columns out of their groups, as `ungroupRows` does rows. */
502
+ ungroupCols(at, count, sheet) {
503
+ const lines = linesOf("col", at, count);
504
+ return this.ungroupLines("col", lines.from, lines.count, sheet);
505
+ }
506
+ /** Folds a group away: `collapseGroup('3:8')`, `range` being the group's rows or columns. */
507
+ collapseGroup(range, sheet) {
508
+ return this.foldGroup(range, true, sheet);
509
+ }
510
+ /** Shows a folded group again: `expandGroup('3:8')`. */
511
+ expandGroup(range, sheet) {
512
+ return this.foldGroup(range, false, sheet);
513
+ }
514
+ foldGroup(range, collapsed, sheet = this.sheet) {
515
+ const index = this.mustIndex(sheet);
516
+ const data = this.book.sheets[index];
517
+ const span = groupSpan(range);
518
+ const group = span && groupsOf(data).axis(span.axis).find(span.start, span.end);
519
+ if (!group) throw new RangeError(`[@dou-ui/sheet] there is no group "${range}"`);
520
+ if (group.collapsed === collapsed) return this;
521
+ return this.setGroups(index, (g, i) => i === group.index ? {
522
+ ...g,
523
+ collapsed
524
+ } : g);
525
+ }
526
+ /**
527
+ * Shows the groups of an axis down to a level, as the outline's level
528
+ * buttons do: level 1 folds every group, level 2 shows the outer groups
529
+ * with theirs folded, and one past the deepest shows them all.
530
+ */
531
+ showGroupLevel(axis, level, sheet = this.sheet) {
532
+ const index = this.mustIndex(sheet);
533
+ const laid = groupsOf(this.book.sheets[index]).axis(axis);
534
+ const fold = /* @__PURE__ */ new Map();
535
+ for (const g of laid.list) if (g.collapsed !== g.depth >= level) fold.set(g.index, !g.collapsed);
536
+ if (!fold.size) return this;
537
+ return this.setGroups(index, (g, i) => fold.has(i) ? {
538
+ ...g,
539
+ collapsed: fold.get(i)
540
+ } : g);
541
+ }
542
+ groupLines(axis, from, count, ref) {
543
+ if (count <= 0) return this;
544
+ const index = this.mustIndex(ref);
545
+ const data = this.book.sheets[index];
546
+ let start = from;
547
+ let end = from + count - 1;
548
+ const laid = groupsOf(data).axis(axis);
549
+ if (laid.find(start, end)) return this;
550
+ const problem = laid.problem(start, end);
551
+ if (problem === "deep") throw new RangeError(`[@dou-ui/sheet] "${groupRange(axis, start, end)}" would nest more than eight deep`);
552
+ if (problem) {
553
+ const other = groupRange(axis, problem.cross.start, problem.cross.end);
554
+ throw new RangeError(`[@dou-ui/sheet] "${groupRange(axis, start, end)}" would cross the group "${other}"`);
555
+ }
556
+ const joined = /* @__PURE__ */ new Set();
557
+ for (const g of laid.beside(start, end)) {
558
+ joined.add(g.index);
559
+ start = Math.min(start, g.start);
560
+ end = Math.max(end, g.end);
561
+ }
562
+ const kept = (data.groups ?? []).filter((_, i) => !joined.has(i));
563
+ return this.updateSheet({ groups: [...kept, { range: groupRange(axis, start, end) }] }, index);
564
+ }
565
+ ungroupLines(axis, from, count, ref) {
566
+ if (count <= 0) return this;
567
+ const index = this.mustIndex(ref);
568
+ const data = this.book.sheets[index];
569
+ const laid = groupsOf(data).axis(axis);
570
+ const end = from + count - 1;
571
+ const within = laid.list.filter((g) => g.start >= from && g.end <= end);
572
+ if (within.length) {
573
+ const gone = /* @__PURE__ */ new Set();
574
+ for (const g of within) if (!within.some((o) => o !== g && o.depth < g.depth && o.start <= g.start && o.end >= g.end)) gone.add(g.index);
575
+ return this.setGroups(index, (g, i) => gone.has(i) ? null : g);
576
+ }
577
+ const touching = laid.list.filter((g) => g.start <= end && g.end >= from);
578
+ if (!touching.length) return this;
579
+ const deepest = Math.max(...touching.map((g) => g.depth));
580
+ const parts = /* @__PURE__ */ new Map();
581
+ for (const g of touching) {
582
+ if (g.depth !== deepest) continue;
583
+ const group = data.groups[g.index];
584
+ const split = [];
585
+ const keep = (a, b) => split.push({
586
+ ...group,
587
+ range: groupRange(axis, a, b)
588
+ });
589
+ if (g.start < from) keep(g.start, from - 1);
590
+ if (g.end > end) keep(end + 1, g.end);
591
+ parts.set(g.index, split);
592
+ }
593
+ return this.setGroups(index, (g, i) => parts.get(i) ?? g);
594
+ }
595
+ /** Rewrites a sheet's groups: each to itself, another, several or none. */
596
+ setGroups(index, map) {
597
+ const next = [];
598
+ this.book.sheets[index].groups?.forEach((g, i) => {
599
+ const out = map(g, i);
600
+ if (Array.isArray(out)) next.push(...out);
601
+ else if (out) next.push(out);
602
+ });
603
+ return this.updateSheet({ groups: next.length ? next : void 0 }, index);
604
+ }
605
+ /**
606
+ * Adds empty rows before `at`: `insertRows(4, 2)` or `insertRows('5:6')`
607
+ * both add two rows where row 5 was. Frozen rows and the row count grow
608
+ * with them, and formulas pointing below follow their cells.
609
+ */
610
+ insertRows(at, count, sheet) {
611
+ const lines = linesOf("row", at, count);
612
+ return this.insertLines("row", lines.from, lines.count, sheet);
613
+ }
614
+ /** Adds empty columns before `at`, `insertCols('C')` or `insertCols(2)`. */
615
+ insertCols(at, count, sheet) {
616
+ const lines = linesOf("col", at, count);
617
+ return this.insertLines("col", lines.from, lines.count, sheet);
618
+ }
619
+ /**
620
+ * Removes rows with their cells and heights: `deleteRows('5:7')` or
621
+ * `deleteRows(4, 3)`. Formulas pointing at them read `#REF!`.
622
+ */
623
+ deleteRows(at, count, sheet) {
624
+ const lines = linesOf("row", at, count);
625
+ return this.deleteLines("row", lines.from, lines.count, sheet);
626
+ }
627
+ /** Removes columns with their cells and widths: `deleteCols('B:C')`. */
628
+ deleteCols(at, count, sheet) {
629
+ const lines = linesOf("col", at, count);
630
+ return this.deleteLines("col", lines.from, lines.count, sheet);
631
+ }
632
+ /**
633
+ * Moves rows to before row `to`, numbered as before the move:
634
+ * `moveRows('2:3', '6')` puts rows 2 and 3 above what was row 6.
635
+ * Formulas pointing at them follow.
636
+ */
637
+ moveRows(lines, to, sheet) {
638
+ const { from, count } = linesOf("row", lines);
639
+ return this.moveLines("row", from, count, linesOf("row", to).from, sheet);
640
+ }
641
+ /** Moves columns to before column `to`: `moveCols('C', 'A')`. */
642
+ moveCols(lines, to, sheet) {
643
+ const { from, count } = linesOf("col", lines);
644
+ return this.moveLines("col", from, count, linesOf("col", to).from, sheet);
645
+ }
646
+ /**
647
+ * Moves a block of cells so its top left corner is at `to`, as cutting
648
+ * and pasting does: the cells at `from` empty, those at `to` are
649
+ * replaced, and formulas pointing at the block follow it.
650
+ */
651
+ moveCells(from, to) {
652
+ const source = this.rangeAt(from);
653
+ const target = this.cellAt(to);
654
+ const b = source.bounds;
655
+ const rows = target.row - b.top;
656
+ const cols = target.col - b.left;
657
+ if (rows === 0 && cols === 0 && source.index === target.index) return this;
658
+ const cells = this.book.sheets[source.index].cells;
659
+ const block = Array.from({ length: b.bottom - b.top + 1 }, (_, r) => Array.from({ length: b.right - b.left + 1 }, (_cell, c) => cells[b.top + r]?.[b.left + c]));
660
+ for (let r = b.top; r <= b.bottom; r++) for (let c = b.left; c <= b.right; c++) this.setCell({
661
+ sheet: source.index,
662
+ row: r,
663
+ col: c
664
+ }, void 0);
665
+ this.setCells(target, block);
666
+ const dest = {
667
+ top: target.row,
668
+ left: target.col,
669
+ bottom: b.bottom + rows,
670
+ right: b.right + cols
671
+ };
672
+ const sourceName = this.book.sheets[source.index].name;
673
+ const destName = this.book.sheets[target.index].name;
674
+ const same = sourceName === destName;
675
+ this.rewriteReferences(destName, (r) => inside(r, dest) && !(same && inside(r, b)) ? null : r);
676
+ this.rewriteReferences(sourceName, (r) => {
677
+ if (!inside(r, b)) return r;
678
+ const moved = {
679
+ top: r.top + rows,
680
+ left: r.left + cols,
681
+ bottom: r.bottom + rows,
682
+ right: r.right + cols
683
+ };
684
+ return same ? moved : {
685
+ sheet: destName,
686
+ bounds: moved
687
+ };
688
+ });
689
+ return this;
690
+ }
691
+ insertLines(axis, index, count, ref) {
692
+ if (count <= 0) return this;
693
+ const at = this.mustIndex(ref);
694
+ const sheet = this.book.sheets[at];
695
+ this.step({
696
+ type: "insertLines",
697
+ sheet: this.keyOf(at),
698
+ axis,
699
+ index,
700
+ count
701
+ });
702
+ this.updateSheet(linesPatch(sheet, axis, (n) => n > index ? n + count : n, count), at);
703
+ const add = (n) => n >= index ? n + count : n;
704
+ return this.moveRanges(at, lineMap(axis, (a, b) => [add(a), add(b)]));
705
+ }
706
+ deleteLines(axis, index, count, ref) {
707
+ if (count <= 0) return this;
708
+ const at = this.mustIndex(ref);
709
+ const sheet = this.book.sheets[at];
710
+ this.step({
711
+ type: "removeLines",
712
+ sheet: this.keyOf(at),
713
+ axis,
714
+ index,
715
+ count
716
+ });
717
+ const left = (n) => n - Math.max(0, Math.min(n, index + count) - index);
718
+ this.updateSheet(linesPatch(sheet, axis, left, -count), at);
719
+ const end = index + count;
720
+ return this.moveRanges(at, lineMap(axis, (a, b) => {
721
+ if (a >= index && b < end) return null;
722
+ return [a < index ? a : a >= end ? a - count : index, b < index ? b : b >= end ? b - count : index - 1];
723
+ }));
724
+ }
725
+ moveLines(axis, from, count, to, ref) {
726
+ if (count <= 0 || to >= from && to <= from + count) return this;
727
+ const at = this.mustIndex(ref);
728
+ this.step({
729
+ type: "moveLines",
730
+ sheet: this.keyOf(at),
731
+ axis,
732
+ from,
733
+ count,
734
+ to
735
+ });
736
+ const place = movedIndex(from, count, to);
737
+ return this.moveRanges(at, lineMap(axis, (a, b) => {
738
+ const x = place(a);
739
+ const y = place(b);
740
+ return [Math.min(x, y), Math.max(x, y)];
741
+ }));
742
+ }
743
+ /**
744
+ * After rows or columns of a sheet change: rewrites formulas pointing at
745
+ * it, and moves its merged cells, dropping those that are gone or now
746
+ * one cell.
747
+ */
748
+ moveRanges(index, map) {
749
+ const sheet = this.book.sheets[index];
750
+ this.rewriteReferences(sheet.name, map);
751
+ this.moveFilter(index, map);
752
+ this.moveRules("conditionalFormats", index, map);
753
+ this.moveRules("validations", index, map);
754
+ this.moveGroups(index, map);
755
+ const merges = sheet.merges;
756
+ if (!merges?.length) return this;
757
+ const next = [];
758
+ for (const text of merges) {
759
+ const range = parseRange(text);
760
+ if (!range) continue;
761
+ const moved = map(boundsOf(range));
762
+ if (!moved) continue;
763
+ const b = "bounds" in moved ? moved.bounds : moved;
764
+ if (b.top === b.bottom && b.left === b.right) continue;
765
+ next.push(rangeText(b));
766
+ }
767
+ return next.length === merges.length && next.every((m, i) => m === merges[i]) ? this : this.updateSheet({ merges: next.length ? next : void 0 }, index);
768
+ }
769
+ /** Moves a sheet's rules with their cells; a rule goes when its cells do. */
770
+ moveRules(key, index, map) {
771
+ const rules = this.book.sheets[index][key];
772
+ if (!rules?.length) return;
773
+ const next = [];
774
+ for (const rule of rules) {
775
+ const texts = [];
776
+ for (const text of rangesOf(rule.range)) {
777
+ const range = parseRange(text);
778
+ const moved = range && map(boundsOf(range));
779
+ if (moved) texts.push(rangeText("bounds" in moved ? moved.bounds : moved));
780
+ }
781
+ if (!texts.length) continue;
782
+ const range = Array.isArray(rule.range) ? texts : texts[0];
783
+ const same = Array.isArray(rule.range) ? texts.length === rule.range.length && texts.every((t, i) => t === rule.range[i]) : range === rule.range;
784
+ next.push(same ? rule : {
785
+ ...rule,
786
+ range
787
+ });
788
+ }
789
+ if (next.length !== rules.length || next.some((rule, i) => rule !== rules[i])) this.updateSheet({ [key]: next.length ? next : void 0 }, index);
790
+ }
791
+ /** Moves a sheet's groups with their lines; a group goes when its lines do. */
792
+ moveGroups(index, map) {
793
+ const groups = this.book.sheets[index].groups;
794
+ if (!groups?.length) return;
795
+ const next = [];
796
+ for (const group of groups) {
797
+ const range = parseRange(group.range);
798
+ const moved = range && map(boundsOf(range));
799
+ if (!moved) continue;
800
+ const text = rangeText("bounds" in moved ? moved.bounds : moved);
801
+ next.push(text === group.range ? group : {
802
+ ...group,
803
+ range: text
804
+ });
805
+ }
806
+ if (next.length !== groups.length || next.some((g, i) => g !== groups[i])) this.updateSheet({ groups: next.length ? next : void 0 }, index);
807
+ }
808
+ /** Moves a sheet's filter with its rows and columns; it goes when its range does. */
809
+ moveFilter(index, map) {
810
+ const filter = this.book.sheets[index].filter;
811
+ if (!filter) return;
812
+ const range = parseRange(filter.range);
813
+ const moved = range && map(boundsOf(range));
814
+ if (!moved) {
815
+ this.updateSheet({ filter: void 0 }, index);
816
+ return;
817
+ }
818
+ const b = "bounds" in moved ? moved.bounds : moved;
819
+ const columns = {};
820
+ for (const [key, rule] of Object.entries(filter.columns ?? {})) {
821
+ const col = Number(key);
822
+ const to = map({
823
+ top: b.top,
824
+ bottom: b.top,
825
+ left: col,
826
+ right: col
827
+ });
828
+ if (!to) continue;
829
+ const at = "bounds" in to ? to.bounds : to;
830
+ if (at.left === at.right) columns[at.left] = rule;
831
+ }
832
+ const next = { range: rangeText(b) };
833
+ if (Object.keys(columns).length) next.columns = columns;
834
+ if (filter.sorted) {
835
+ const col = filter.sorted.col;
836
+ const to = map({
837
+ top: b.top,
838
+ bottom: b.top,
839
+ left: col,
840
+ right: col
841
+ });
842
+ const at = to && ("bounds" in to ? to.bounds : to);
843
+ if (at && at.left === at.right) next.sorted = {
844
+ ...filter.sorted,
845
+ col: at.left
846
+ };
847
+ }
848
+ if (JSON.stringify(next) !== JSON.stringify(filter)) this.updateSheet({ filter: next }, index);
849
+ }
850
+ /**
851
+ * Merges a range into one cell. Merges it touches are taken into it. The
852
+ * top left cell keeps its content; the values of the other cells are
853
+ * cleared, keeping their styles, unless `keep` leaves them hidden under the
854
+ * merge, to show again when it is split.
855
+ */
856
+ mergeCells(at, options = {}) {
857
+ const { index, bounds } = this.rangeAt(at);
858
+ const sheet = this.book.sheets[index];
859
+ const b = mergesOf(sheet).expand(bounds);
860
+ if (b.top === b.bottom && b.left === b.right) return this;
861
+ if (!options.keep) {
862
+ for (let row = b.top; row <= b.bottom; row++) for (let col = b.left; col <= b.right; col++) if ((row !== b.top || col !== b.left) && valueOf(sheet.cells[row]?.[col]) != null) this.setValue({
863
+ sheet: index,
864
+ row,
865
+ col
866
+ }, void 0);
867
+ }
868
+ const kept = (sheet.merges ?? []).filter((text) => {
869
+ const range = parseRange(text);
870
+ return !range || !overlaps(boundsOf(range), b);
871
+ });
872
+ return this.updateSheet({ merges: [...kept, rangeText(b)] }, index);
873
+ }
874
+ /** Splits the merged cells a range touches back into single cells. */
875
+ unmergeCells(at) {
876
+ const { index, bounds } = this.rangeAt(at);
877
+ const sheet = this.book.sheets[index];
878
+ if (!sheet.merges?.length) return this;
879
+ const kept = sheet.merges.filter((text) => {
880
+ const range = parseRange(text);
881
+ return !range || !overlaps(boundsOf(range), bounds);
882
+ });
883
+ if (kept.length === sheet.merges.length) return this;
884
+ return this.updateSheet({ merges: kept.length ? kept : void 0 }, index);
885
+ }
886
+ /**
887
+ * Changes how the cells in a range look: each cell's style with `patch`
888
+ * laid over it, where a field set to `undefined` (or `false`, but for
889
+ * `locked`) is taken off; `null` takes the whole style off. Cells that end up looking the
890
+ * same share one style: one in `styles` that matches, or a new one named
891
+ * `s1`, `s2`…. Whole columns and rows stop at the sheet's edge.
892
+ */
893
+ styleCells(at, patch) {
894
+ const { index, bounds } = this.rangeIn(at);
895
+ const next = /* @__PURE__ */ new Map();
896
+ for (let row = bounds.top; row <= bounds.bottom; row++) for (let col = bounds.left; col <= bounds.right; col++) {
897
+ const point = {
898
+ sheet: index,
899
+ row,
900
+ col
901
+ };
902
+ const style = cellOf(this.getCell(point))?.style;
903
+ if (!next.has(style)) next.set(style, this.styleFor(style, patch));
904
+ const name = next.get(style);
905
+ if (name !== style) this.updateCell(point, { style: name });
906
+ }
907
+ return this;
908
+ }
909
+ /**
910
+ * Sets the number or date format of the cells in a range (`undefined`
911
+ * for none), or changes each with a function of its format and value,
912
+ * such as `(format, value) => changeDecimals(format, value, 1)`.
913
+ */
914
+ formatCells(at, format) {
915
+ const { index, bounds } = this.rangeIn(at);
916
+ for (let row = bounds.top; row <= bounds.bottom; row++) for (let col = bounds.left; col <= bounds.right; col++) {
917
+ const point = {
918
+ sheet: index,
919
+ row,
920
+ col
921
+ };
922
+ const input = this.getCell(point);
923
+ const old = cellOf(input)?.format;
924
+ const value = typeof format === "function" ? format(old, valueOf(input)) : format;
925
+ if ((value || void 0) !== old) this.updateCell(point, { format: value || void 0 });
926
+ }
927
+ return this;
928
+ }
929
+ /** The name of a style that looks like `name`'s with `patch` laid over it. */
930
+ styleFor(name, patch) {
931
+ const styles = this.book.styles ?? {};
932
+ const style = {};
933
+ if (patch) {
934
+ for (const [key, value] of Object.entries({
935
+ ...name ? styles[name] : void 0,
936
+ ...patch
937
+ })) if (value !== void 0 && (value !== false || key === "locked") && value !== "") style[key] = value;
938
+ }
939
+ const key = styleKey(style);
940
+ if (key === "{}") return void 0;
941
+ if (name && styles[name] && styleKey(styles[name]) === key) return name;
942
+ for (const [other, look] of Object.entries(styles)) if (styleKey(look) === key) return other;
943
+ let n = 1;
944
+ while (styles[`s${n}`]) n++;
945
+ this.setStyle(`s${n}`, style);
946
+ return `s${n}`;
947
+ }
948
+ /**
949
+ * Adds a sheet, at the end or at `index`. Its name must not be taken
950
+ * (`uniqueName` gives one that isn't); it is `Sheet1`, `Sheet2`… when
951
+ * left out, whatever the language. Returns the transaction; the new sheet
952
+ * is `tr.getSheet(name)`.
953
+ */
954
+ addSheet(sheet = {}, index = this.book.sheets.length) {
955
+ const name = sheet.name ?? this.freeName("Sheet");
956
+ this.checkName(name, sheet.id);
957
+ const data = {
958
+ ...sheet,
959
+ name,
960
+ cells: sheet.cells ?? []
961
+ };
962
+ return this.step({
963
+ type: "insertSheet",
964
+ index,
965
+ data
966
+ });
967
+ }
968
+ /**
969
+ * Removes a sheet. The last one stays: a workbook always has a sheet.
970
+ * Formulas on other sheets pointing at it read `#REF!`.
971
+ */
972
+ removeSheet(sheet = this.sheet) {
973
+ const index = this.mustIndex(sheet);
974
+ if (this.book.sheets.length < 2) return this;
975
+ const { name } = this.book.sheets[index];
976
+ this.step({
977
+ type: "removeSheet",
978
+ sheet: this.keyOf(index)
979
+ });
980
+ return this.rewriteFormulas((formula) => dropReferences(formula, name));
981
+ }
982
+ /** Moves a sheet to `index`, counted among the sheets without it. */
983
+ moveSheet(sheet, index) {
984
+ return this.step({
985
+ type: "moveSheet",
986
+ sheet: this.keyOf(this.mustIndex(sheet)),
987
+ index
988
+ });
989
+ }
990
+ /** Renames a sheet; formulas naming it follow. The name must not be taken. */
991
+ renameSheet(sheet, name) {
992
+ const index = this.mustIndex(sheet);
993
+ const old = this.book.sheets[index];
994
+ if (old.name === name) return this;
995
+ this.checkName(name, old.id, index);
996
+ this.updateSheet({ name }, index);
997
+ return this.rewriteFormulas((formula) => renameReferences(formula, old.name, name));
998
+ }
999
+ /**
1000
+ * Adds a copy of a sheet right after it, named `name` or the sheet's name
1001
+ * with a number, such as `数据 (2)`. Formulas on the copy that name the
1002
+ * sheet keep pointing at the original.
1003
+ */
1004
+ duplicateSheet(sheet = this.sheet, name) {
1005
+ const index = this.mustIndex(sheet);
1006
+ const original = this.book.sheets[index];
1007
+ const { id: _id, ...source } = original;
1008
+ this.ownSheets.delete(original);
1009
+ this.ownRows.delete(original);
1010
+ const next = name ?? this.uniqueName(source.name);
1011
+ this.checkName(next);
1012
+ return this.step({
1013
+ type: "insertSheet",
1014
+ index: index + 1,
1015
+ data: {
1016
+ ...source,
1017
+ name: next
1018
+ }
1019
+ });
1020
+ }
1021
+ /**
1022
+ * `name` if no sheet has it, otherwise the name with the first free
1023
+ * number: `门店 (2)`, `门店 (3)`…
1024
+ */
1025
+ uniqueName(name) {
1026
+ const taken = new Set(this.book.sheets.map((sheet) => sheet.name));
1027
+ if (!taken.has(name)) return name;
1028
+ let n = 2;
1029
+ while (taken.has(`${name} (${n})`)) n++;
1030
+ return `${name} (${n})`;
1031
+ }
1032
+ /** Passes every formula of the workbook through `rewrite`. */
1033
+ rewriteFormulas(rewrite) {
1034
+ this.book.sheets.forEach((sheet, index) => sheet.cells.forEach((line, row) => line?.forEach((cell, col) => {
1035
+ const value = typeof cell === "object" ? cell?.value : cell;
1036
+ if (typeof value !== "string" || !value.startsWith("=")) return;
1037
+ const next = rewrite(value);
1038
+ if (next !== value) this.setValue({
1039
+ sheet: index,
1040
+ row,
1041
+ col
1042
+ }, next);
1043
+ })));
1044
+ return this.rewriteRuleFormulas((formula) => rewrite(formula));
1045
+ }
1046
+ /**
1047
+ * Rewrites the formulas of every sheet's rules: conditional formats' and
1048
+ * validations' formulas, and the references of validation lists.
1049
+ */
1050
+ rewriteRuleFormulas(rewrite) {
1051
+ this.book.sheets.forEach((sheet, index) => {
1052
+ for (const key of ["conditionalFormats", "validations"]) {
1053
+ const rules = sheet[key];
1054
+ if (!rules?.length) continue;
1055
+ let changed = false;
1056
+ const next = rules.map((rule) => {
1057
+ if ("formula" in rule) {
1058
+ const formula = rewrite(rule.formula, sheet);
1059
+ if (formula === rule.formula) return rule;
1060
+ changed = true;
1061
+ return {
1062
+ ...rule,
1063
+ formula
1064
+ };
1065
+ }
1066
+ if ("list" in rule && typeof rule.list === "string") {
1067
+ const list = rewrite(rule.list, sheet);
1068
+ if (list === rule.list) return rule;
1069
+ changed = true;
1070
+ return {
1071
+ ...rule,
1072
+ list
1073
+ };
1074
+ }
1075
+ return rule;
1076
+ });
1077
+ if (changed) this.updateSheet({ [key]: next }, index);
1078
+ }
1079
+ });
1080
+ return this;
1081
+ }
1082
+ /** The first name `prefix` + a number, from the count of sheets on, that no sheet has. */
1083
+ freeName(prefix) {
1084
+ const taken = new Set(this.book.sheets.map((sheet) => sheet.name));
1085
+ let n = this.book.sheets.length + 1;
1086
+ while (taken.has(`${prefix}${n}`)) n++;
1087
+ return `${prefix}${n}`;
1088
+ }
1089
+ checkName(name, id, self = -1) {
1090
+ if (!name.trim()) throw new RangeError("[@dou-ui/sheet] a sheet needs a name");
1091
+ this.book.sheets.forEach((sheet, i) => {
1092
+ if (i === self) return;
1093
+ if (sheet.name === name) throw new RangeError(`[@dou-ui/sheet] there is a sheet "${name}" already`);
1094
+ if (id != null && sheet.id === id) throw new RangeError(`[@dou-ui/sheet] there is a sheet "${id}" already`);
1095
+ });
1096
+ }
1097
+ /** Rewrites the references to cells on sheet `name` in every formula of the workbook. */
1098
+ rewriteReferences(name, map) {
1099
+ this.book.sheets.forEach((sheet, index) => {
1100
+ sheet.cells.forEach((line, row) => line?.forEach((cell, col) => {
1101
+ const value = typeof cell === "object" ? cell?.value : cell;
1102
+ if (typeof value !== "string" || !value.startsWith("=")) return;
1103
+ const next = moveReferences(value, sheet.name, name, map);
1104
+ if (next !== value) this.setValue({
1105
+ sheet: index,
1106
+ row,
1107
+ col
1108
+ }, next);
1109
+ }));
1110
+ });
1111
+ return this.rewriteRuleFormulas((formula, sheet) => moveReferences(formula, sheet.name, name, map));
1112
+ }
1113
+ mustIndex(ref = this.sheet) {
1114
+ const index = this.indexOf(ref);
1115
+ if (index === -1) throw new RangeError(`[@dou-ui/sheet] there is no sheet "${ref}"`);
1116
+ return index;
1117
+ }
1118
+ keyOf(index) {
1119
+ return sheetKey(this.book.sheets[index]);
1120
+ }
1121
+ cellAt(at) {
1122
+ if (typeof at === "string") {
1123
+ const { sheet, ref } = splitSheet(at);
1124
+ const point = parseCell(ref);
1125
+ if (!point) throw new RangeError(`[@dou-ui/sheet] "${at}" isn't a cell`);
1126
+ return {
1127
+ index: this.mustIndex(sheet),
1128
+ ...point
1129
+ };
1130
+ }
1131
+ return {
1132
+ index: this.mustIndex(at.sheet),
1133
+ row: at.row,
1134
+ col: at.col
1135
+ };
1136
+ }
1137
+ rangeAt(at) {
1138
+ let sheet;
1139
+ let bounds;
1140
+ if (typeof at === "string") {
1141
+ const split = splitSheet(at);
1142
+ sheet = split.sheet;
1143
+ const range = parseRange(split.ref);
1144
+ if (!range) throw new RangeError(`[@dou-ui/sheet] "${at}" isn't a range`);
1145
+ bounds = boundsOf(range);
1146
+ } else {
1147
+ sheet = at.sheet;
1148
+ bounds = "start" in at ? boundsOf(at) : {
1149
+ top: at.top,
1150
+ left: at.left,
1151
+ bottom: at.bottom,
1152
+ right: at.right
1153
+ };
1154
+ }
1155
+ return {
1156
+ index: this.mustIndex(sheet),
1157
+ bounds
1158
+ };
1159
+ }
1160
+ /** A range kept inside its sheet, so whole columns and rows end at its edge. */
1161
+ rangeIn(at) {
1162
+ const { index, bounds } = this.rangeAt(at);
1163
+ const sheet = this.book.sheets[index];
1164
+ return {
1165
+ index,
1166
+ bounds: {
1167
+ top: Math.max(0, bounds.top),
1168
+ left: Math.max(0, bounds.left),
1169
+ bottom: Math.min(rowCountOf(sheet) - 1, bounds.bottom),
1170
+ right: Math.min(colCountOf(sheet) - 1, bounds.right)
1171
+ }
1172
+ };
1173
+ }
1174
+ /** Applies a step, returning the step that undoes it, or `null` when nothing changes. */
1175
+ apply(step) {
1176
+ if (step.type === "setStyle") {
1177
+ const old = this.book.styles?.[step.name];
1178
+ if (old === step.style) return null;
1179
+ const book = this.ownBook();
1180
+ const styles = this.own.styles ? book.styles : { ...book.styles };
1181
+ this.own.styles = true;
1182
+ if (step.style) styles[step.name] = step.style;
1183
+ else delete styles[step.name];
1184
+ book.styles = styles;
1185
+ return {
1186
+ type: "setStyle",
1187
+ name: step.name,
1188
+ style: old
1189
+ };
1190
+ }
1191
+ if (step.type === "setType") {
1192
+ const old = this.book.types?.[step.name];
1193
+ if (old === step.setup) return null;
1194
+ const book = this.ownBook();
1195
+ const types = this.own.types ? book.types : { ...book.types };
1196
+ this.own.types = true;
1197
+ if (step.setup) types[step.name] = step.setup;
1198
+ else delete types[step.name];
1199
+ book.types = types;
1200
+ return {
1201
+ type: "setType",
1202
+ name: step.name,
1203
+ setup: old
1204
+ };
1205
+ }
1206
+ if (step.type === "updateBook") {
1207
+ const book = this.book;
1208
+ const old = {};
1209
+ let changed = false;
1210
+ for (const [key, value] of Object.entries(step.patch)) {
1211
+ old[key] = book[key];
1212
+ if (book[key] !== value) changed = true;
1213
+ }
1214
+ if (!changed) return null;
1215
+ const own = this.ownBook();
1216
+ for (const [key, value] of Object.entries(step.patch)) if (value === void 0) delete own[key];
1217
+ else own[key] = value;
1218
+ return {
1219
+ type: "updateBook",
1220
+ patch: old
1221
+ };
1222
+ }
1223
+ if (step.type === "insertSheet") {
1224
+ const sheets = this.ownList();
1225
+ const index = Math.max(0, Math.min(sheets.length, step.index));
1226
+ sheets.splice(index, 0, step.data);
1227
+ return {
1228
+ type: "removeSheet",
1229
+ sheet: sheetKey(step.data)
1230
+ };
1231
+ }
1232
+ const index = this.mustIndex(step.sheet);
1233
+ const sheet = this.book.sheets[index];
1234
+ switch (step.type) {
1235
+ case "removeSheet":
1236
+ this.ownList().splice(index, 1);
1237
+ this.ownSheets.delete(sheet);
1238
+ this.ownRows.delete(sheet);
1239
+ return {
1240
+ type: "insertSheet",
1241
+ index,
1242
+ data: sheet
1243
+ };
1244
+ case "moveSheet": {
1245
+ const sheets = this.book.sheets;
1246
+ const to = Math.max(0, Math.min(sheets.length - 1, step.index));
1247
+ if (to === index) return null;
1248
+ const list = this.ownList();
1249
+ list.splice(index, 1);
1250
+ list.splice(to, 0, sheet);
1251
+ return {
1252
+ type: "moveSheet",
1253
+ sheet: step.sheet,
1254
+ index
1255
+ };
1256
+ }
1257
+ case "setCell": {
1258
+ const { row, col } = step;
1259
+ const cell = step.cell ?? void 0;
1260
+ const old = sheet.cells[row]?.[col] ?? void 0;
1261
+ if (old === cell || old == null && cell == null) return null;
1262
+ this.row(index, row)[col] = cell;
1263
+ return {
1264
+ ...step,
1265
+ cell: old
1266
+ };
1267
+ }
1268
+ case "setSize": {
1269
+ const key = step.axis === "row" ? "rowSizes" : "colSizes";
1270
+ const sizes = sheet[key];
1271
+ const old = sizes?.[step.index];
1272
+ if (old === step.size) return null;
1273
+ const next = { ...sizes };
1274
+ if (step.size == null) delete next[step.index];
1275
+ else next[step.index] = step.size;
1276
+ this.ownSheet(index)[key] = next;
1277
+ return {
1278
+ ...step,
1279
+ size: old
1280
+ };
1281
+ }
1282
+ case "updateSheet": {
1283
+ const old = {};
1284
+ let changed = false;
1285
+ for (const key of Object.keys(step.patch)) {
1286
+ old[key] = sheet[key];
1287
+ if (sheet[key] !== step.patch[key]) changed = true;
1288
+ }
1289
+ if (!changed) return null;
1290
+ const own = this.ownSheet(index);
1291
+ for (const [key, value] of Object.entries(step.patch)) if (value === void 0) delete own[key];
1292
+ else own[key] = value;
1293
+ return {
1294
+ type: "updateSheet",
1295
+ sheet: this.keyOf(index),
1296
+ patch: old
1297
+ };
1298
+ }
1299
+ case "insertLines": return this.insertAt(index, step);
1300
+ case "removeLines": return this.removeAt(index, step);
1301
+ case "moveLines": return this.moveAt(index, step);
1302
+ }
1303
+ }
1304
+ insertAt(index, step) {
1305
+ const { axis, count } = step;
1306
+ const at = step.index;
1307
+ const own = this.ownSheet(index);
1308
+ const fill = step.cells;
1309
+ if (axis === "row") {
1310
+ if (at < own.cells.length || fill?.some((line) => line?.length)) {
1311
+ while (own.cells.length < at) own.cells.push(null);
1312
+ own.cells.splice(at, 0, ...Array.from({ length: count }, (_, i) => fill?.[i] ?? null));
1313
+ }
1314
+ this.ownRows.delete(own);
1315
+ } else {
1316
+ const length = Math.max(own.cells.length, fill?.length ?? 0);
1317
+ for (let r = 0; r < length; r++) {
1318
+ const line = own.cells[r];
1319
+ const part = fill?.[r];
1320
+ if (!part?.length && (!line || line.length <= at)) continue;
1321
+ const next = this.row(index, r);
1322
+ while (next.length < at) next.push(void 0);
1323
+ next.splice(at, 0, ...Array.from({ length: count }, (_, i) => part?.[i] ?? void 0));
1324
+ }
1325
+ }
1326
+ shiftSizes(own, axis, (n) => n >= at ? n + count : n, at, step.sizes);
1327
+ return {
1328
+ type: "removeLines",
1329
+ sheet: step.sheet,
1330
+ axis,
1331
+ index: at,
1332
+ count
1333
+ };
1334
+ }
1335
+ removeAt(index, step) {
1336
+ const { axis, count } = step;
1337
+ const at = step.index;
1338
+ const own = this.ownSheet(index);
1339
+ let cells;
1340
+ if (axis === "row") {
1341
+ if (at < own.cells.length) cells = own.cells.splice(at, count).map((line) => line ?? null);
1342
+ this.ownRows.delete(own);
1343
+ } else {
1344
+ for (let r = 0; r < own.cells.length; r++) {
1345
+ const line = own.cells[r];
1346
+ if (!line || line.length <= at) continue;
1347
+ const removed = this.row(index, r).splice(at, count);
1348
+ if (removed.some((cell) => cell != null)) (cells ??= [])[r] = removed;
1349
+ }
1350
+ if (cells) for (let r = 0; r < cells.length; r++) cells[r] ??= null;
1351
+ }
1352
+ const old = own[axis === "row" ? "rowSizes" : "colSizes"];
1353
+ let sizes;
1354
+ if (old) {
1355
+ for (let i = 0; i < count; i++) if (old[at + i] != null) (sizes ??= Array(count).fill(null))[i] = old[at + i];
1356
+ }
1357
+ shiftSizes(own, axis, (n) => n < at ? n : n >= at + count ? n - count : -1);
1358
+ const inverse = {
1359
+ type: "insertLines",
1360
+ sheet: step.sheet,
1361
+ axis,
1362
+ index: at,
1363
+ count
1364
+ };
1365
+ if (cells) inverse.cells = cells;
1366
+ if (sizes) inverse.sizes = sizes;
1367
+ return inverse;
1368
+ }
1369
+ moveAt(index, step) {
1370
+ const { axis, from, count, to } = step;
1371
+ const own = this.ownSheet(index);
1372
+ const place = movedIndex(from, count, to);
1373
+ const reorder = (list, empty) => {
1374
+ const end = Math.max(from + count, to);
1375
+ if (list.length <= Math.min(from, to)) return list;
1376
+ const out = list.slice();
1377
+ while (out.length < end) out.push(empty);
1378
+ for (let i = Math.min(from, to); i < end; i++) out[place(i)] = list[i] ?? empty;
1379
+ while (out.length && out[out.length - 1] == null) out.pop();
1380
+ return out;
1381
+ };
1382
+ if (axis === "row") {
1383
+ own.cells = reorder(own.cells, null);
1384
+ this.ownRows.delete(own);
1385
+ } else for (let r = 0; r < own.cells.length; r++) {
1386
+ const line = own.cells[r];
1387
+ if (!line || line.length <= Math.min(from, to)) continue;
1388
+ const next = reorder(line, void 0);
1389
+ if (next !== line) own.cells[r] = next;
1390
+ }
1391
+ shiftSizes(own, axis, place);
1392
+ const back = to > from ? to - count : to;
1393
+ return {
1394
+ type: "moveLines",
1395
+ sheet: step.sheet,
1396
+ axis,
1397
+ from: back,
1398
+ count,
1399
+ to: to > from ? from : from + count
1400
+ };
1401
+ }
1402
+ ownBook() {
1403
+ if (!this.own.book) {
1404
+ this.book = { ...this.book };
1405
+ this.own.book = true;
1406
+ }
1407
+ return this.book;
1408
+ }
1409
+ /** The list of sheets as an array this transaction may change. */
1410
+ ownList() {
1411
+ const book = this.ownBook();
1412
+ if (!this.own.sheets) {
1413
+ book.sheets = book.sheets.slice();
1414
+ this.own.sheets = true;
1415
+ }
1416
+ return book.sheets;
1417
+ }
1418
+ ownSheet(index) {
1419
+ const book = this.ownBook();
1420
+ this.ownList();
1421
+ let sheet = book.sheets[index];
1422
+ if (!this.ownSheets.has(sheet)) {
1423
+ sheet = book.sheets[index] = {
1424
+ ...sheet,
1425
+ cells: sheet.cells.slice()
1426
+ };
1427
+ this.ownSheets.add(sheet);
1428
+ }
1429
+ return sheet;
1430
+ }
1431
+ /** The row as an array this transaction may change. */
1432
+ row(sheet, row) {
1433
+ const own = this.ownSheet(sheet);
1434
+ let rows = this.ownRows.get(own);
1435
+ if (!rows) this.ownRows.set(own, rows = /* @__PURE__ */ new Set());
1436
+ if (!rows.has(row)) {
1437
+ own.cells[row] = own.cells[row]?.slice() ?? [];
1438
+ rows.add(row);
1439
+ }
1440
+ return own.cells[row];
1441
+ }
1442
+ };
1443
+ var rangeText = (b) => formatRange({
1444
+ start: {
1445
+ row: b.top,
1446
+ col: b.left
1447
+ },
1448
+ end: {
1449
+ row: b.bottom,
1450
+ col: b.right
1451
+ }
1452
+ });
1453
+ /** Whether `x` lies inside `y`. */
1454
+ var inside = (x, y) => x.top >= y.top && x.bottom <= y.bottom && x.left >= y.left && x.right <= y.right;
1455
+ /** Rows or columns written as a number, `'5:7'` or `'B:C'`, as a first line and a count. */
1456
+ function linesOf(axis, at, count) {
1457
+ if (typeof at === "number") return {
1458
+ from: at,
1459
+ count: count ?? 1
1460
+ };
1461
+ if (typeof at === "object") return at;
1462
+ const [a = "", b = a, extra] = at.replaceAll("$", "").trim().split(":");
1463
+ const line = (text) => axis === "row" ? /^[1-9]\d*$/.test(text) ? Number(text) - 1 : -1 : columnIndex(text);
1464
+ const first = line(a);
1465
+ const last = line(b);
1466
+ if (extra != null || first < 0 || last < 0) throw new RangeError(`[@dou-ui/sheet] "${at}" isn't ${axis === "row" ? "rows" : "columns"}`);
1467
+ return {
1468
+ from: Math.min(first, last),
1469
+ count: count ?? Math.abs(last - first) + 1
1470
+ };
1471
+ }
1472
+ /**
1473
+ * A reference map for a change to rows or columns: `span` gives a range's
1474
+ * new first and last line on that axis, or `null` when they are gone.
1475
+ * Whole rows are not touched by a change to columns, and so on.
1476
+ */
1477
+ function lineMap(axis, span) {
1478
+ return (b) => {
1479
+ const [a, z] = axis === "row" ? [b.top, b.bottom] : [b.left, b.right];
1480
+ if (a === 0 && z === Infinity) return b;
1481
+ const next = span(a, z === Infinity ? a : z);
1482
+ if (!next) return null;
1483
+ const last = z === Infinity ? Infinity : next[1];
1484
+ return axis === "row" ? {
1485
+ ...b,
1486
+ top: next[0],
1487
+ bottom: last
1488
+ } : {
1489
+ ...b,
1490
+ left: next[0],
1491
+ right: last
1492
+ };
1493
+ };
1494
+ }
1495
+ /** Where line `i` ends up when `count` lines from `from` move to before `to`. */
1496
+ function movedIndex(from, count, to) {
1497
+ return (i) => {
1498
+ if (i >= from && i < from + count) return to > from ? i - from + to - count : i - from + to;
1499
+ if (to > from && i >= from + count && i < to) return i - count;
1500
+ if (to < from && i >= to && i < from) return i + count;
1501
+ return i;
1502
+ };
1503
+ }
1504
+ /** Renumbers a sheet's changed sizes; a number below 0 drops the size. */
1505
+ function shiftSizes(sheet, axis, to, insertAt, inserted) {
1506
+ const key = axis === "row" ? "rowSizes" : "colSizes";
1507
+ const old = sheet[key];
1508
+ if (!old && !inserted?.some((size) => size != null)) return;
1509
+ const next = {};
1510
+ for (const [n, size] of Object.entries(old ?? {})) {
1511
+ const moved = to(Number(n));
1512
+ if (moved >= 0) next[moved] = size;
1513
+ }
1514
+ inserted?.forEach((size, i) => {
1515
+ if (size != null) next[insertAt + i] = size;
1516
+ });
1517
+ sheet[key] = next;
1518
+ }
1519
+ /** The frozen count and line count after lines are added or removed. */
1520
+ function linesPatch(sheet, axis, frozen, change) {
1521
+ const patch = {};
1522
+ const countKey = axis === "row" ? "rowCount" : "colCount";
1523
+ const freezeKey = axis === "row" ? "rows" : "cols";
1524
+ const count = sheet[countKey];
1525
+ if (count != null) patch[countKey] = Math.max(1, count + change);
1526
+ const freeze = sheet.freeze?.[freezeKey];
1527
+ if (freeze) {
1528
+ const next = frozen(freeze);
1529
+ if (next !== freeze) patch.freeze = {
1530
+ ...sheet.freeze,
1531
+ [freezeKey]: next
1532
+ };
1533
+ }
1534
+ return patch;
1535
+ }
1536
+ /** Applies steps to a workbook, as one transaction. */
1537
+ function applySheetSteps(doc, steps) {
1538
+ const tr = new SheetTransaction(doc);
1539
+ for (const step of steps) tr.step(step);
1540
+ return tr;
1541
+ }
1542
+ var keyOf = (step) => {
1543
+ switch (step.type) {
1544
+ case "setCell": return `c${step.sheet}:${step.row}:${step.col}`;
1545
+ case "setSize": return `s${step.sheet}:${step.axis}:${step.index}`;
1546
+ case "setStyle": return `y${step.name}`;
1547
+ case "setType": return `t${step.name}`;
1548
+ default: return null;
1549
+ }
1550
+ };
1551
+ /**
1552
+ * The same changes in fewer steps: of several steps writing the same cell,
1553
+ * size or style, keeps only the last, which is the one that counts.
1554
+ */
1555
+ function compactSheetSteps(steps) {
1556
+ const out = [];
1557
+ const at = /* @__PURE__ */ new Map();
1558
+ for (const step of steps) {
1559
+ const key = keyOf(step);
1560
+ if (key == null) {
1561
+ at.clear();
1562
+ out.push(step);
1563
+ continue;
1564
+ }
1565
+ const index = at.get(key);
1566
+ if (index == null) {
1567
+ at.set(key, out.length);
1568
+ out.push(step);
1569
+ } else out[index] = step;
1570
+ }
1571
+ return out;
1572
+ }
1573
+ var warnedUncancellable = false;
1574
+ /** Says once that cancelling a change kept out of the history does nothing. */
1575
+ function warnUncancellable() {
1576
+ if (process.env.NODE_ENV === "production" || warnedUncancellable) return;
1577
+ warnedUncancellable = true;
1578
+ console.warn("[@dou-ui/sheet] a change kept out of the history (undo, redo or another person's) can't be cancelled; it goes ahead");
1579
+ }
1580
+ export { SheetTransaction, applySheetSteps, compactSheetSteps, movedIndex, sheetKey };
1581
+
1582
+ //# sourceMappingURL=transaction.js.map