@trebco/treb 36.1.4 → 37.0.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 (552) hide show
  1. package/api-config.json +1 -1
  2. package/build/package.json +119 -0
  3. package/build/treb-base-types/src/api_types.d.ts +11 -0
  4. package/build/treb-base-types/src/api_types.js +22 -0
  5. package/build/treb-base-types/src/api_types.js.map +1 -0
  6. package/build/treb-base-types/src/area-utils.d.ts +9 -0
  7. package/build/treb-base-types/src/area-utils.js +50 -0
  8. package/build/treb-base-types/src/area-utils.js.map +1 -0
  9. package/build/treb-base-types/src/area.d.ts +182 -0
  10. package/build/treb-base-types/src/area.js +715 -0
  11. package/build/treb-base-types/src/area.js.map +1 -0
  12. package/build/treb-base-types/src/basic_types.d.ts +20 -0
  13. package/build/treb-base-types/src/basic_types.js +22 -0
  14. package/build/treb-base-types/src/basic_types.js.map +1 -0
  15. package/build/treb-base-types/src/cell.d.ts +167 -0
  16. package/build/treb-base-types/src/cell.js +432 -0
  17. package/build/treb-base-types/src/cell.js.map +1 -0
  18. package/build/treb-base-types/src/cells.d.ts +251 -0
  19. package/build/treb-base-types/src/cells.js +1136 -0
  20. package/build/treb-base-types/src/cells.js.map +1 -0
  21. package/build/treb-base-types/src/color.d.ts +35 -0
  22. package/build/treb-base-types/src/color.js +162 -0
  23. package/build/treb-base-types/src/color.js.map +1 -0
  24. package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
  25. package/build/treb-base-types/src/dom-utilities.js +144 -0
  26. package/build/treb-base-types/src/dom-utilities.js.map +1 -0
  27. package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
  28. package/build/treb-base-types/src/evaluate-options.js +22 -0
  29. package/build/treb-base-types/src/evaluate-options.js.map +1 -0
  30. package/build/treb-base-types/src/font-stack.d.ts +37 -0
  31. package/build/treb-base-types/src/font-stack.js +93 -0
  32. package/build/treb-base-types/src/font-stack.js.map +1 -0
  33. package/build/treb-base-types/src/gradient.d.ts +18 -0
  34. package/build/treb-base-types/src/gradient.js +86 -0
  35. package/build/treb-base-types/src/gradient.js.map +1 -0
  36. package/build/treb-base-types/src/import.d.ts +48 -0
  37. package/build/treb-base-types/src/import.js +22 -0
  38. package/build/treb-base-types/src/import.js.map +1 -0
  39. package/build/treb-base-types/src/index-standalone.d.ts +6 -0
  40. package/build/treb-base-types/src/index-standalone.js +27 -0
  41. package/build/treb-base-types/src/index-standalone.js.map +1 -0
  42. package/build/treb-base-types/src/index.d.ts +22 -0
  43. package/build/treb-base-types/src/index.js +45 -0
  44. package/build/treb-base-types/src/index.js.map +1 -0
  45. package/build/treb-base-types/src/layout.d.ts +22 -0
  46. package/build/treb-base-types/src/layout.js +22 -0
  47. package/build/treb-base-types/src/layout.js.map +1 -0
  48. package/build/treb-base-types/src/localization.d.ts +37 -0
  49. package/build/treb-base-types/src/localization.js +157 -0
  50. package/build/treb-base-types/src/localization.js.map +1 -0
  51. package/build/treb-base-types/src/rectangle.d.ts +51 -0
  52. package/build/treb-base-types/src/rectangle.js +123 -0
  53. package/build/treb-base-types/src/rectangle.js.map +1 -0
  54. package/build/treb-base-types/src/render_text.d.ts +34 -0
  55. package/build/treb-base-types/src/render_text.js +22 -0
  56. package/build/treb-base-types/src/render_text.js.map +1 -0
  57. package/build/treb-base-types/src/style.d.ts +214 -0
  58. package/build/treb-base-types/src/style.js +373 -0
  59. package/build/treb-base-types/src/style.js.map +1 -0
  60. package/build/treb-base-types/src/table.d.ts +58 -0
  61. package/build/treb-base-types/src/table.js +27 -0
  62. package/build/treb-base-types/src/table.js.map +1 -0
  63. package/build/treb-base-types/src/text_part.d.ts +26 -0
  64. package/build/treb-base-types/src/text_part.js +47 -0
  65. package/build/treb-base-types/src/text_part.js.map +1 -0
  66. package/build/treb-base-types/src/theme.d.ts +120 -0
  67. package/build/treb-base-types/src/theme.js +460 -0
  68. package/build/treb-base-types/src/theme.js.map +1 -0
  69. package/build/treb-base-types/src/union.d.ts +73 -0
  70. package/build/treb-base-types/src/union.js +61 -0
  71. package/build/treb-base-types/src/union.js.map +1 -0
  72. package/build/treb-base-types/src/value-type.d.ts +86 -0
  73. package/build/treb-base-types/src/value-type.js +168 -0
  74. package/build/treb-base-types/src/value-type.js.map +1 -0
  75. package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
  76. package/build/treb-base-types/src/worker-proxy.js +221 -0
  77. package/build/treb-base-types/src/worker-proxy.js.map +1 -0
  78. package/build/treb-calculator/src/calculator.d.ts +249 -0
  79. package/build/treb-calculator/src/calculator.js +2755 -0
  80. package/build/treb-calculator/src/calculator.js.map +1 -0
  81. package/build/treb-calculator/src/complex-math.d.ts +75 -0
  82. package/build/treb-calculator/src/complex-math.js +559 -0
  83. package/build/treb-calculator/src/complex-math.js.map +1 -0
  84. package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
  85. package/build/treb-calculator/src/dag/array-vertex.js +156 -0
  86. package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
  87. package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
  88. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
  89. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
  90. package/build/treb-calculator/src/dag/graph.d.ts +134 -0
  91. package/build/treb-calculator/src/dag/graph.js +842 -0
  92. package/build/treb-calculator/src/dag/graph.js.map +1 -0
  93. package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
  94. package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
  95. package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
  96. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
  97. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
  98. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
  99. package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
  100. package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
  101. package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
  102. package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
  103. package/build/treb-calculator/src/dag/vertex.js +274 -0
  104. package/build/treb-calculator/src/dag/vertex.js.map +1 -0
  105. package/build/treb-calculator/src/descriptors.d.ts +189 -0
  106. package/build/treb-calculator/src/descriptors.js +22 -0
  107. package/build/treb-calculator/src/descriptors.js.map +1 -0
  108. package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
  109. package/build/treb-calculator/src/expression-calculator.js +1033 -0
  110. package/build/treb-calculator/src/expression-calculator.js.map +1 -0
  111. package/build/treb-calculator/src/function-error.d.ts +35 -0
  112. package/build/treb-calculator/src/function-error.js +85 -0
  113. package/build/treb-calculator/src/function-error.js.map +1 -0
  114. package/build/treb-calculator/src/function-library.d.ts +22 -0
  115. package/build/treb-calculator/src/function-library.js +96 -0
  116. package/build/treb-calculator/src/function-library.js.map +1 -0
  117. package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
  118. package/build/treb-calculator/src/functions/base-functions.js +2611 -0
  119. package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
  120. package/build/treb-calculator/src/functions/beta.d.ts +17 -0
  121. package/build/treb-calculator/src/functions/beta.js +201 -0
  122. package/build/treb-calculator/src/functions/beta.js.map +1 -0
  123. package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
  124. package/build/treb-calculator/src/functions/checkbox.js +128 -0
  125. package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
  126. package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
  127. package/build/treb-calculator/src/functions/complex-functions.js +217 -0
  128. package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
  129. package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
  130. package/build/treb-calculator/src/functions/date-utils.js +59 -0
  131. package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
  132. package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
  133. package/build/treb-calculator/src/functions/finance-functions.js +547 -0
  134. package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
  135. package/build/treb-calculator/src/functions/fp.d.ts +2 -0
  136. package/build/treb-calculator/src/functions/fp.js +463 -0
  137. package/build/treb-calculator/src/functions/fp.js.map +1 -0
  138. package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
  139. package/build/treb-calculator/src/functions/function-utilities.js +36 -0
  140. package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
  141. package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
  142. package/build/treb-calculator/src/functions/gamma.js +142 -0
  143. package/build/treb-calculator/src/functions/gamma.js.map +1 -0
  144. package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
  145. package/build/treb-calculator/src/functions/information-functions.js +71 -0
  146. package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
  147. package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
  148. package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
  149. package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
  150. package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
  151. package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
  152. package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
  153. package/build/treb-calculator/src/functions/normal.d.ts +2 -0
  154. package/build/treb-calculator/src/functions/normal.js +32 -0
  155. package/build/treb-calculator/src/functions/normal.js.map +1 -0
  156. package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
  157. package/build/treb-calculator/src/functions/regex-functions.js +188 -0
  158. package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
  159. package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
  160. package/build/treb-calculator/src/functions/sparkline.js +264 -0
  161. package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
  162. package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
  163. package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
  164. package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
  165. package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
  166. package/build/treb-calculator/src/functions/students-t.js +64 -0
  167. package/build/treb-calculator/src/functions/students-t.js.map +1 -0
  168. package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
  169. package/build/treb-calculator/src/functions/text-functions.js +320 -0
  170. package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
  171. package/build/treb-calculator/src/index.d.ts +2 -0
  172. package/build/treb-calculator/src/index.js +22 -0
  173. package/build/treb-calculator/src/index.js.map +1 -0
  174. package/build/treb-calculator/src/notifier-types.d.ts +26 -0
  175. package/build/treb-calculator/src/notifier-types.js +22 -0
  176. package/build/treb-calculator/src/notifier-types.js.map +1 -0
  177. package/build/treb-calculator/src/primitives.d.ts +15 -0
  178. package/build/treb-calculator/src/primitives.js +398 -0
  179. package/build/treb-calculator/src/primitives.js.map +1 -0
  180. package/build/treb-calculator/src/utilities.d.ts +68 -0
  181. package/build/treb-calculator/src/utilities.js +324 -0
  182. package/build/treb-calculator/src/utilities.js.map +1 -0
  183. package/build/treb-charts/src/chart-functions.d.ts +8 -0
  184. package/build/treb-charts/src/chart-functions.js +209 -0
  185. package/build/treb-charts/src/chart-functions.js.map +1 -0
  186. package/build/treb-charts/src/chart-types.d.ts +233 -0
  187. package/build/treb-charts/src/chart-types.js +57 -0
  188. package/build/treb-charts/src/chart-types.js.map +1 -0
  189. package/build/treb-charts/src/chart-utils.d.ts +106 -0
  190. package/build/treb-charts/src/chart-utils.js +1060 -0
  191. package/build/treb-charts/src/chart-utils.js.map +1 -0
  192. package/build/treb-charts/src/chart.d.ts +23 -0
  193. package/build/treb-charts/src/chart.js +94 -0
  194. package/build/treb-charts/src/chart.js.map +1 -0
  195. package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
  196. package/build/treb-charts/src/default-chart-renderer.js +533 -0
  197. package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
  198. package/build/treb-charts/src/index.d.ts +5 -0
  199. package/build/treb-charts/src/index.js +24 -0
  200. package/build/treb-charts/src/index.js.map +1 -0
  201. package/build/treb-charts/src/main.d.ts +1 -0
  202. package/build/treb-charts/src/main.js +34 -0
  203. package/build/treb-charts/src/main.js.map +1 -0
  204. package/build/treb-charts/src/quicksort.d.ts +1 -0
  205. package/build/treb-charts/src/quicksort.js +49 -0
  206. package/build/treb-charts/src/quicksort.js.map +1 -0
  207. package/build/treb-charts/src/rectangle.d.ts +18 -0
  208. package/build/treb-charts/src/rectangle.js +41 -0
  209. package/build/treb-charts/src/rectangle.js.map +1 -0
  210. package/build/treb-charts/src/renderer-type.d.ts +24 -0
  211. package/build/treb-charts/src/renderer-type.js +22 -0
  212. package/build/treb-charts/src/renderer-type.js.map +1 -0
  213. package/build/treb-charts/src/renderer.d.ts +127 -0
  214. package/build/treb-charts/src/renderer.js +1518 -0
  215. package/build/treb-charts/src/renderer.js.map +1 -0
  216. package/build/treb-charts/src/util.d.ts +18 -0
  217. package/build/treb-charts/src/util.js +71 -0
  218. package/build/treb-charts/src/util.js.map +1 -0
  219. package/build/treb-data-model/src/annotation.d.ts +167 -0
  220. package/build/treb-data-model/src/annotation.js +120 -0
  221. package/build/treb-data-model/src/annotation.js.map +1 -0
  222. package/build/treb-data-model/src/conditional_format.d.ts +155 -0
  223. package/build/treb-data-model/src/conditional_format.js +62 -0
  224. package/build/treb-data-model/src/conditional_format.js.map +1 -0
  225. package/build/treb-data-model/src/data-validation.d.ts +28 -0
  226. package/build/treb-data-model/src/data-validation.js +22 -0
  227. package/build/treb-data-model/src/data-validation.js.map +1 -0
  228. package/build/treb-data-model/src/data_model.d.ts +173 -0
  229. package/build/treb-data-model/src/data_model.js +637 -0
  230. package/build/treb-data-model/src/data_model.js.map +1 -0
  231. package/build/treb-data-model/src/index.d.ts +13 -0
  232. package/build/treb-data-model/src/index.js +28 -0
  233. package/build/treb-data-model/src/index.js.map +1 -0
  234. package/build/treb-data-model/src/language-model.d.ts +22 -0
  235. package/build/treb-data-model/src/language-model.js +22 -0
  236. package/build/treb-data-model/src/language-model.js.map +1 -0
  237. package/build/treb-data-model/src/named.d.ts +124 -0
  238. package/build/treb-data-model/src/named.js +372 -0
  239. package/build/treb-data-model/src/named.js.map +1 -0
  240. package/build/treb-data-model/src/serialize_options.d.ts +49 -0
  241. package/build/treb-data-model/src/serialize_options.js +22 -0
  242. package/build/treb-data-model/src/serialize_options.js.map +1 -0
  243. package/build/treb-data-model/src/sheet.d.ts +499 -0
  244. package/build/treb-data-model/src/sheet.js +2904 -0
  245. package/build/treb-data-model/src/sheet.js.map +1 -0
  246. package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
  247. package/build/treb-data-model/src/sheet_collection.js +112 -0
  248. package/build/treb-data-model/src/sheet_collection.js.map +1 -0
  249. package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
  250. package/build/treb-data-model/src/sheet_selection.js +39 -0
  251. package/build/treb-data-model/src/sheet_selection.js.map +1 -0
  252. package/build/treb-data-model/src/sheet_types.d.ts +104 -0
  253. package/build/treb-data-model/src/sheet_types.js +22 -0
  254. package/build/treb-data-model/src/sheet_types.js.map +1 -0
  255. package/build/treb-data-model/src/types.d.ts +59 -0
  256. package/build/treb-data-model/src/types.js +22 -0
  257. package/build/treb-data-model/src/types.js.map +1 -0
  258. package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
  259. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
  260. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
  261. package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
  262. package/build/treb-embed/src/custom-element/treb-global.js +64 -0
  263. package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
  264. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
  265. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
  266. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
  267. package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
  268. package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
  269. package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
  270. package/build/treb-embed/src/index.d.ts +12 -0
  271. package/build/treb-embed/src/index.js +34 -0
  272. package/build/treb-embed/src/index.js.map +1 -0
  273. package/build/treb-embed/src/options.d.ts +266 -0
  274. package/build/treb-embed/src/options.js +56 -0
  275. package/build/treb-embed/src/options.js.map +1 -0
  276. package/build/treb-embed/src/plugin.d.ts +9 -0
  277. package/build/treb-embed/src/plugin.js +22 -0
  278. package/build/treb-embed/src/plugin.js.map +1 -0
  279. package/build/treb-embed/src/progress-dialog.d.ts +49 -0
  280. package/build/treb-embed/src/progress-dialog.js +178 -0
  281. package/build/treb-embed/src/progress-dialog.js.map +1 -0
  282. package/build/treb-embed/src/selection-state.d.ts +15 -0
  283. package/build/treb-embed/src/selection-state.js +22 -0
  284. package/build/treb-embed/src/selection-state.js.map +1 -0
  285. package/build/treb-embed/src/spinner.d.ts +8 -0
  286. package/build/treb-embed/src/spinner.js +40 -0
  287. package/build/treb-embed/src/spinner.js.map +1 -0
  288. package/build/treb-embed/src/toolbar-message.d.ts +72 -0
  289. package/build/treb-embed/src/toolbar-message.js +22 -0
  290. package/build/treb-embed/src/toolbar-message.js.map +1 -0
  291. package/build/treb-embed/src/types.d.ts +185 -0
  292. package/build/treb-embed/src/types.js +45 -0
  293. package/build/treb-embed/src/types.js.map +1 -0
  294. package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
  295. package/build/treb-export/src/address-type.d.ts +34 -0
  296. package/build/treb-export/src/address-type.js +53 -0
  297. package/build/treb-export/src/address-type.js.map +1 -0
  298. package/build/treb-export/src/base-template.d.ts +1 -0
  299. package/build/treb-export/src/base-template.js +22 -0
  300. package/build/treb-export/src/base-template.js.map +1 -0
  301. package/build/treb-export/src/column-width.d.ts +2 -0
  302. package/build/treb-export/src/column-width.js +80 -0
  303. package/build/treb-export/src/column-width.js.map +1 -0
  304. package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
  305. package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
  306. package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
  307. package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
  308. package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
  309. package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
  310. package/build/treb-export/src/drawing/chart.d.ts +26 -0
  311. package/build/treb-export/src/drawing/chart.js +247 -0
  312. package/build/treb-export/src/drawing/chart.js.map +1 -0
  313. package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
  314. package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
  315. package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
  316. package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
  317. package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
  318. package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
  319. package/build/treb-export/src/drawing/drawing.d.ts +49 -0
  320. package/build/treb-export/src/drawing/drawing.js +193 -0
  321. package/build/treb-export/src/drawing/drawing.js.map +1 -0
  322. package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
  323. package/build/treb-export/src/drawing/embedded-image.js +54 -0
  324. package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
  325. package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
  326. package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
  327. package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
  328. package/build/treb-export/src/export.d.ts +72 -0
  329. package/build/treb-export/src/export.js +2039 -0
  330. package/build/treb-export/src/export.js.map +1 -0
  331. package/build/treb-export/src/import-export-messages.d.ts +31 -0
  332. package/build/treb-export/src/import-export-messages.js +22 -0
  333. package/build/treb-export/src/import-export-messages.js.map +1 -0
  334. package/build/treb-export/src/import.d.ts +33 -0
  335. package/build/treb-export/src/import.js +1258 -0
  336. package/build/treb-export/src/import.js.map +1 -0
  337. package/build/treb-export/src/index.worker.d.ts +1 -0
  338. package/build/treb-export/src/index.worker.js +93 -0
  339. package/build/treb-export/src/index.worker.js.map +1 -0
  340. package/build/treb-export/src/metadata.d.ts +51 -0
  341. package/build/treb-export/src/metadata.js +153 -0
  342. package/build/treb-export/src/metadata.js.map +1 -0
  343. package/build/treb-export/src/ooxml.d.ts +7 -0
  344. package/build/treb-export/src/ooxml.js +41 -0
  345. package/build/treb-export/src/ooxml.js.map +1 -0
  346. package/build/treb-export/src/relationship.d.ts +8 -0
  347. package/build/treb-export/src/relationship.js +27 -0
  348. package/build/treb-export/src/relationship.js.map +1 -0
  349. package/build/treb-export/src/shared-strings.d.ts +11 -0
  350. package/build/treb-export/src/shared-strings.js +105 -0
  351. package/build/treb-export/src/shared-strings.js.map +1 -0
  352. package/build/treb-export/src/template-2.d.ts +1 -0
  353. package/build/treb-export/src/template-2.js +22 -0
  354. package/build/treb-export/src/template-2.js.map +1 -0
  355. package/build/treb-export/src/unescape_xml.d.ts +1 -0
  356. package/build/treb-export/src/unescape_xml.js +61 -0
  357. package/build/treb-export/src/unescape_xml.js.map +1 -0
  358. package/build/treb-export/src/workbook-sheet.d.ts +75 -0
  359. package/build/treb-export/src/workbook-sheet.js +128 -0
  360. package/build/treb-export/src/workbook-sheet.js.map +1 -0
  361. package/build/treb-export/src/workbook-style.d.ts +110 -0
  362. package/build/treb-export/src/workbook-style.js +1134 -0
  363. package/build/treb-export/src/workbook-style.js.map +1 -0
  364. package/build/treb-export/src/workbook-theme.d.ts +13 -0
  365. package/build/treb-export/src/workbook-theme.js +85 -0
  366. package/build/treb-export/src/workbook-theme.js.map +1 -0
  367. package/build/treb-export/src/workbook.d.ts +123 -0
  368. package/build/treb-export/src/workbook.js +644 -0
  369. package/build/treb-export/src/workbook.js.map +1 -0
  370. package/build/treb-export/src/xml-test.d.ts +9 -0
  371. package/build/treb-export/src/xml-test.js +52 -0
  372. package/build/treb-export/src/xml-test.js.map +1 -0
  373. package/build/treb-export/src/xml-utils.d.ts +76 -0
  374. package/build/treb-export/src/xml-utils.js +223 -0
  375. package/build/treb-export/src/xml-utils.js.map +1 -0
  376. package/build/treb-export/src/zip-wrapper.d.ts +22 -0
  377. package/build/treb-export/src/zip-wrapper.js +93 -0
  378. package/build/treb-export/src/zip-wrapper.js.map +1 -0
  379. package/build/treb-format/src/format.d.ts +130 -0
  380. package/build/treb-format/src/format.js +805 -0
  381. package/build/treb-format/src/format.js.map +1 -0
  382. package/build/treb-format/src/format_cache.d.ts +55 -0
  383. package/build/treb-format/src/format_cache.js +166 -0
  384. package/build/treb-format/src/format_cache.js.map +1 -0
  385. package/build/treb-format/src/format_parser.d.ts +70 -0
  386. package/build/treb-format/src/format_parser.js +618 -0
  387. package/build/treb-format/src/format_parser.js.map +1 -0
  388. package/build/treb-format/src/index.d.ts +4 -0
  389. package/build/treb-format/src/index.js +25 -0
  390. package/build/treb-format/src/index.js.map +1 -0
  391. package/build/treb-format/src/number_format_section.d.ts +58 -0
  392. package/build/treb-format/src/number_format_section.js +78 -0
  393. package/build/treb-format/src/number_format_section.js.map +1 -0
  394. package/build/treb-format/src/value_parser.d.ts +48 -0
  395. package/build/treb-format/src/value_parser.js +244 -0
  396. package/build/treb-format/src/value_parser.js.map +1 -0
  397. package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
  398. package/build/treb-grid/src/editors/autocomplete.js +316 -0
  399. package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
  400. package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
  401. package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
  402. package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
  403. package/build/treb-grid/src/editors/editor.d.ts +214 -0
  404. package/build/treb-grid/src/editors/editor.js +879 -0
  405. package/build/treb-grid/src/editors/editor.js.map +1 -0
  406. package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
  407. package/build/treb-grid/src/editors/external_editor.js +118 -0
  408. package/build/treb-grid/src/editors/external_editor.js.map +1 -0
  409. package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
  410. package/build/treb-grid/src/editors/formula_bar.js +444 -0
  411. package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
  412. package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
  413. package/build/treb-grid/src/editors/overlay_editor.js +353 -0
  414. package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
  415. package/build/treb-grid/src/index.d.ts +12 -0
  416. package/build/treb-grid/src/index.js +28 -0
  417. package/build/treb-grid/src/index.js.map +1 -0
  418. package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
  419. package/build/treb-grid/src/layout/base_layout.js +2050 -0
  420. package/build/treb-grid/src/layout/base_layout.js.map +1 -0
  421. package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
  422. package/build/treb-grid/src/layout/grid_layout.js +235 -0
  423. package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
  424. package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
  425. package/build/treb-grid/src/layout/mock-layout.js +37 -0
  426. package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
  427. package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
  428. package/build/treb-grid/src/render/selection-renderer.js +315 -0
  429. package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
  430. package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
  431. package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
  432. package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
  433. package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
  434. package/build/treb-grid/src/render/svg_selection_block.js +106 -0
  435. package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
  436. package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
  437. package/build/treb-grid/src/render/tile_renderer.js +1609 -0
  438. package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
  439. package/build/treb-grid/src/types/border_constants.d.ts +9 -0
  440. package/build/treb-grid/src/types/border_constants.js +34 -0
  441. package/build/treb-grid/src/types/border_constants.js.map +1 -0
  442. package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
  443. package/build/treb-grid/src/types/clipboard_data.js +22 -0
  444. package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
  445. package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
  446. package/build/treb-grid/src/types/clipboard_data2.js +22 -0
  447. package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
  448. package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
  449. package/build/treb-grid/src/types/drag_mask.js +78 -0
  450. package/build/treb-grid/src/types/drag_mask.js.map +1 -0
  451. package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
  452. package/build/treb-grid/src/types/external_editor_config.js +22 -0
  453. package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
  454. package/build/treb-grid/src/types/grid.d.ts +806 -0
  455. package/build/treb-grid/src/types/grid.js +6410 -0
  456. package/build/treb-grid/src/types/grid.js.map +1 -0
  457. package/build/treb-grid/src/types/grid_base.d.ts +442 -0
  458. package/build/treb-grid/src/types/grid_base.js +3523 -0
  459. package/build/treb-grid/src/types/grid_base.js.map +1 -0
  460. package/build/treb-grid/src/types/grid_command.d.ts +408 -0
  461. package/build/treb-grid/src/types/grid_command.js +75 -0
  462. package/build/treb-grid/src/types/grid_command.js.map +1 -0
  463. package/build/treb-grid/src/types/grid_events.d.ts +93 -0
  464. package/build/treb-grid/src/types/grid_events.js +36 -0
  465. package/build/treb-grid/src/types/grid_events.js.map +1 -0
  466. package/build/treb-grid/src/types/grid_options.d.ts +50 -0
  467. package/build/treb-grid/src/types/grid_options.js +34 -0
  468. package/build/treb-grid/src/types/grid_options.js.map +1 -0
  469. package/build/treb-grid/src/types/scale-control.d.ts +21 -0
  470. package/build/treb-grid/src/types/scale-control.js +148 -0
  471. package/build/treb-grid/src/types/scale-control.js.map +1 -0
  472. package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
  473. package/build/treb-grid/src/types/set_range_options.js +22 -0
  474. package/build/treb-grid/src/types/set_range_options.js.map +1 -0
  475. package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
  476. package/build/treb-grid/src/types/tab_bar.js +426 -0
  477. package/build/treb-grid/src/types/tab_bar.js.map +1 -0
  478. package/build/treb-grid/src/types/tile.d.ts +29 -0
  479. package/build/treb-grid/src/types/tile.js +22 -0
  480. package/build/treb-grid/src/types/tile.js.map +1 -0
  481. package/build/treb-grid/src/types/update_flags.d.ts +48 -0
  482. package/build/treb-grid/src/types/update_flags.js +22 -0
  483. package/build/treb-grid/src/types/update_flags.js.map +1 -0
  484. package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
  485. package/build/treb-grid/src/util/fontmetrics.js +82 -0
  486. package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
  487. package/build/treb-grid/src/util/ua.d.ts +33 -0
  488. package/build/treb-grid/src/util/ua.js +86 -0
  489. package/build/treb-grid/src/util/ua.js.map +1 -0
  490. package/build/treb-parser/src/csv-parser.d.ts +13 -0
  491. package/build/treb-parser/src/csv-parser.js +107 -0
  492. package/build/treb-parser/src/csv-parser.js.map +1 -0
  493. package/build/treb-parser/src/index.d.ts +4 -0
  494. package/build/treb-parser/src/index.js +25 -0
  495. package/build/treb-parser/src/index.js.map +1 -0
  496. package/build/treb-parser/src/md-parser.d.ts +97 -0
  497. package/build/treb-parser/src/md-parser.js +403 -0
  498. package/build/treb-parser/src/md-parser.js.map +1 -0
  499. package/build/treb-parser/src/parser-types.d.ts +345 -0
  500. package/build/treb-parser/src/parser-types.js +53 -0
  501. package/build/treb-parser/src/parser-types.js.map +1 -0
  502. package/build/treb-parser/src/parser.d.ts +422 -0
  503. package/build/treb-parser/src/parser.js +2418 -0
  504. package/build/treb-parser/src/parser.js.map +1 -0
  505. package/build/treb-utils/src/event_source.d.ts +34 -0
  506. package/build/treb-utils/src/event_source.js +110 -0
  507. package/build/treb-utils/src/event_source.js.map +1 -0
  508. package/build/treb-utils/src/ievent_source.d.ts +9 -0
  509. package/build/treb-utils/src/ievent_source.js +22 -0
  510. package/build/treb-utils/src/ievent_source.js.map +1 -0
  511. package/build/treb-utils/src/index.d.ts +6 -0
  512. package/build/treb-utils/src/index.js +30 -0
  513. package/build/treb-utils/src/index.js.map +1 -0
  514. package/build/treb-utils/src/measurement.d.ts +42 -0
  515. package/build/treb-utils/src/measurement.js +145 -0
  516. package/build/treb-utils/src/measurement.js.map +1 -0
  517. package/build/treb-utils/src/scale.d.ts +16 -0
  518. package/build/treb-utils/src/scale.js +106 -0
  519. package/build/treb-utils/src/scale.js.map +1 -0
  520. package/build/treb-utils/src/serialize_html.d.ts +5 -0
  521. package/build/treb-utils/src/serialize_html.js +128 -0
  522. package/build/treb-utils/src/serialize_html.js.map +1 -0
  523. package/build/treb-utils/src/validate_uri.d.ts +20 -0
  524. package/build/treb-utils/src/validate_uri.js +55 -0
  525. package/build/treb-utils/src/validate_uri.js.map +1 -0
  526. package/dist/{chunk-Z4XFMZ2X.mjs → chunk-E35ONJUS.mjs} +1 -1
  527. package/dist/treb-export-worker.mjs +2 -2
  528. package/dist/treb-spreadsheet.mjs +4 -4
  529. package/dist/treb.d.ts +1 -1
  530. package/esbuild-composite.mjs +5 -1
  531. package/package.json +67 -3
  532. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
  533. package/treb-embed/src/embedded-spreadsheet.ts +1 -1
  534. package/treb-grid/src/types/grid_options.ts +1 -1
  535. package/tsproject.json +1 -2
  536. package/dist/chunk-43DLP2OX.mjs +0 -11
  537. package/dist/chunk-4CKS56PE.mjs +0 -11
  538. package/dist/chunk-75PARUQE.mjs +0 -11
  539. package/dist/chunk-7QD63AZS.mjs +0 -24601
  540. package/dist/chunk-A55ARVRD.mjs +0 -11
  541. package/dist/chunk-DESAKYW4.mjs +0 -11
  542. package/dist/chunk-EQ2R5W6P.mjs +0 -24565
  543. package/dist/chunk-IYJU2J6D.mjs +0 -24601
  544. package/dist/chunk-KSJFPGXT.mjs +0 -11
  545. package/dist/chunk-MQK4DNXI.mjs +0 -11
  546. package/dist/chunk-ORQFKLXM.mjs +0 -24601
  547. package/dist/chunk-SFDNNDHY.mjs +0 -11
  548. package/dist/chunk-T47DX5MI.mjs +0 -11
  549. package/dist/chunk-T6ILBVEX.mjs +0 -11
  550. package/dist/chunk-TPRCDYYG.mjs +0 -11
  551. package/dist/chunk-YAHNOOHO.mjs +0 -11
  552. package/dist/chunk-YLCFKX2G.mjs +0 -24601
@@ -0,0 +1,3523 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2026 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+ /**
22
+ * grid base is a superclass for grid that takes over all (most) of the
23
+ * data operations, leaving UI operations (painting and interacting, plus
24
+ * layout) in the grid subclass.
25
+ *
26
+ * this is part of an effort to support running outside of the browser,
27
+ * but still using the command log to handle deltas.
28
+ *
29
+ * this turns out to be a little like the (old) layout where we had modern
30
+ * and legacy layouts -- a lot of stuff can be reused, but a lot can't.
31
+ *
32
+ * calling this "grid" doesn't really make sense anymore, but we're not in
33
+ * a hurry to change it either.
34
+ *
35
+ */
36
+ import { EventSource } from 'treb-utils';
37
+ import { IllegalSheetNameRegex, ParseCSV, DecimalMarkType } from 'treb-parser';
38
+ import { Area, IsCellAddress, ValueType, DefaultTableSortOptions } from 'treb-base-types';
39
+ import { Sheet } from 'treb-data-model';
40
+ import { AutocompleteMatcher } from '../editors/autocomplete_matcher';
41
+ import { NumberFormat, ValueParser } from 'treb-format';
42
+ import { ErrorCode } from './grid_events';
43
+ import { DefaultGridOptions } from './grid_options';
44
+ import { BorderConstants } from './border_constants';
45
+ import { CommandKey } from './grid_command';
46
+ // this is an assert, bascially, for completeness. we could probably
47
+ // resolve the eslint issue (there's no type option) but not sure it's
48
+ // useful to do so
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
+ const AssertNever = (value) => {
51
+ console.error('invalid case');
52
+ };
53
+ export class GridBase {
54
+ // --- public members --------------------------------------------------------
55
+ /** events */
56
+ grid_events = new EventSource();
57
+ /** for recording */
58
+ command_log = new EventSource();
59
+ model;
60
+ view;
61
+ // --- public accessors ------------------------------------------------------
62
+ get active_sheet() {
63
+ return this.view.active_sheet;
64
+ }
65
+ set active_sheet(sheet) {
66
+ this.view.active_sheet = sheet;
67
+ }
68
+ /** access the view index, if needed */
69
+ get view_index() {
70
+ return this.view.view_index;
71
+ }
72
+ // --- protected members -----------------------------------------------------
73
+ /**
74
+ * switching to a stack, in case batching is nested. we don't need
75
+ * actual data so (atm) just count the depth.
76
+ */
77
+ batch = 0; // false;
78
+ /**
79
+ * if any batch method along the way requests a paint update, we
80
+ * want to toll it until the last batch call is complete, but we don't
81
+ * want to lose it. just remember to reset. [FIXME: isn't tolling this
82
+ * paint implicit, since it's async? ...]
83
+ */
84
+ batch_paint = false;
85
+ batch_events = [];
86
+ /**
87
+ * single instance of AC. editors (function bar, ICE) have references.
88
+ * this is in base, instead of subclass, because we use it to check
89
+ * for valid names.
90
+ */
91
+ autocomplete_matcher = new AutocompleteMatcher();
92
+ /**
93
+ * flags/state (used for some recordkeeping -- not super important)
94
+ */
95
+ flags = {};
96
+ /** */
97
+ options;
98
+ /**
99
+ * spreadsheet language parser. used to pull out address
100
+ * references from functions, for highlighting
101
+ *
102
+ * ...
103
+ *
104
+ * it's used for lots of stuff now, in addition to highlighting.
105
+ * copy/paste with translation; csv; defines; and some other stuff.
106
+ * still would like to share w/ parent though, if possible.
107
+ *
108
+ *
109
+ * FIXME: need a way to share/pass parser flags
110
+ * UPDATE: sharing parser w/ owner (embedded sheet)
111
+ */
112
+ parser;
113
+ // --- constructor -----------------------------------------------------------
114
+ constructor(options = {}, model) {
115
+ this.model = model;
116
+ this.view = {
117
+ active_sheet: this.model.sheets.list[0],
118
+ view_index: this.model.view_count++,
119
+ };
120
+ // shared parser
121
+ this.parser = model.parser;
122
+ // apply default options, meaning that you need to explicitly set/unset
123
+ // in order to change behavior. FIXME: this is ok for flat structure, but
124
+ // anything more complicated will need a nested merge
125
+ this.options = { ...DefaultGridOptions, ...options };
126
+ }
127
+ // --- API methods -----------------------------------------------------------
128
+ RemoveConditionalFormat(options) {
129
+ this.ExecCommand({
130
+ key: CommandKey.RemoveConditionalFormat,
131
+ ...options,
132
+ });
133
+ }
134
+ AddConditionalFormat(format) {
135
+ this.ExecCommand({
136
+ key: CommandKey.AddConditionalFormat,
137
+ format,
138
+ });
139
+ }
140
+ /** remove a table. doesn't remove any data, just removes the overlay. */
141
+ RemoveTable(table) {
142
+ this.ExecCommand({
143
+ key: CommandKey.RemoveTable,
144
+ table,
145
+ });
146
+ }
147
+ /**
148
+ * create a table in the given area. the area cannot contain any
149
+ * merge cells, arrays, or be part of another table. if you add a table
150
+ * with a totals row, we don't insert a new row -- allocate enough space
151
+ * when you create it.
152
+ *
153
+ * @param area - the total area for the table, including headers and totals
154
+ * @param totals - set true to include a totals row. tables have different
155
+ * formatting and slightly different behavior when there's a totals row.
156
+ */
157
+ InsertTable(area, totals = true, sortable = undefined, theme) {
158
+ // we should validate here, so that we can throw.
159
+ if (!area.start.sheet_id) {
160
+ area.start.sheet_id = this.active_sheet.id;
161
+ }
162
+ const sheet = this.FindSheet(area);
163
+ for (let row = area.start.row; row <= area.end.row; row++) {
164
+ for (let column = area.start.column; column <= area.end.column; column++) {
165
+ const cell = sheet.cells.GetCell({ row, column }, false);
166
+ if (cell && (cell.area || cell.merge_area || cell.table)) {
167
+ // throw new Error('invalid area for table');
168
+ this.Error(ErrorCode.invalid_area_for_table);
169
+ return;
170
+ }
171
+ }
172
+ }
173
+ this.ExecCommand({
174
+ key: CommandKey.InsertTable,
175
+ area: JSON.parse(JSON.stringify(area)),
176
+ totals,
177
+ sortable,
178
+ theme,
179
+ });
180
+ }
181
+ /**
182
+ * activate sheet, by name or index number
183
+ * @param sheet number (index into the array) or string (name)
184
+ */
185
+ ActivateSheet(sheet, user) {
186
+ const index = (typeof sheet === 'number') ? sheet : undefined;
187
+ const name = (typeof sheet === 'string') ? sheet : undefined;
188
+ this.ExecCommand({
189
+ key: CommandKey.ActivateSheet,
190
+ index,
191
+ name,
192
+ user,
193
+ });
194
+ }
195
+ /**
196
+ * activate sheet, by ID
197
+ */
198
+ ActivateSheetID(id) {
199
+ this.ExecCommand({
200
+ key: CommandKey.ActivateSheet,
201
+ id,
202
+ });
203
+ }
204
+ /**
205
+ * duplicate sheet by index or (omitting index) the current active sheet
206
+ */
207
+ DuplicateSheet(index, name, insert_before) {
208
+ const command = {
209
+ key: CommandKey.DuplicateSheet,
210
+ new_name: name,
211
+ insert_before,
212
+ };
213
+ if (typeof index === 'undefined') {
214
+ command.id = this.active_sheet.id;
215
+ }
216
+ else {
217
+ command.index = index;
218
+ }
219
+ this.ExecCommand(command);
220
+ }
221
+ AddSheet(name) {
222
+ this.ExecCommand({
223
+ key: CommandKey.AddSheet,
224
+ name,
225
+ show: true,
226
+ });
227
+ }
228
+ /**
229
+ * delete sheet, by index or (omitting index) the current active sheet
230
+ */
231
+ DeleteSheet(index) {
232
+ if (typeof index === 'undefined') {
233
+ if (!this.model.sheets.list.some((sheet, i) => {
234
+ if (sheet === this.active_sheet) {
235
+ index = i;
236
+ return true;
237
+ }
238
+ return false;
239
+ })) {
240
+ throw new Error('invalid index');
241
+ }
242
+ }
243
+ this.ExecCommand({
244
+ key: CommandKey.DeleteSheet,
245
+ index,
246
+ });
247
+ }
248
+ /** insert sheet at the given index (or current index) */
249
+ InsertSheet(index, name) {
250
+ if (typeof index === 'undefined') {
251
+ if (!this.model.sheets.list.some((sheet, i) => {
252
+ if (sheet === this.active_sheet) {
253
+ index = i + 1;
254
+ return true;
255
+ }
256
+ return false;
257
+ })) {
258
+ throw new Error('invalid index');
259
+ }
260
+ }
261
+ this.ExecCommand({
262
+ key: CommandKey.AddSheet,
263
+ insert_index: index,
264
+ name,
265
+ show: true,
266
+ });
267
+ }
268
+ DeleteSheetID(id) {
269
+ this.ExecCommand({
270
+ key: CommandKey.DeleteSheet,
271
+ id,
272
+ });
273
+ }
274
+ /**
275
+ * clear sheet, reset all data
276
+ */
277
+ Reset() {
278
+ this.ExecCommand({ key: CommandKey.Reset });
279
+ }
280
+ /**
281
+ * set hyperlink, like set note
282
+ */
283
+ SetLink(address, reference) {
284
+ /*
285
+ if (!address) {
286
+ if (this.primary_selection.empty) return;
287
+ address = this.primary_selection.target;
288
+ }
289
+ */
290
+ this.ExecCommand({
291
+ key: CommandKey.SetLink,
292
+ area: address,
293
+ reference,
294
+ });
295
+ }
296
+ ShowAll() {
297
+ // obviously there are better ways to do this, but this
298
+ // will use the execcommand system and _should_ only fire
299
+ // a single event (FIXME: check)
300
+ const commands = [];
301
+ for (let index = 0; index < this.model.sheets.length; index++) {
302
+ commands.push({
303
+ key: CommandKey.ShowSheet,
304
+ index,
305
+ show: true,
306
+ });
307
+ }
308
+ this.ExecCommand(commands);
309
+ }
310
+ ShowSheet(index = 0, show = true) {
311
+ const command = {
312
+ key: CommandKey.ShowSheet,
313
+ show,
314
+ };
315
+ if (typeof index === 'string') {
316
+ command.name = index;
317
+ }
318
+ else {
319
+ command.index = index;
320
+ }
321
+ this.ExecCommand(command);
322
+ }
323
+ /**
324
+ * sort table. column is absolute.
325
+ */
326
+ SortTable(table, options = {}) {
327
+ //
328
+ // table typically has an actual area, while we want a plain
329
+ // object in the command queue for serialization purposes. not
330
+ // sure how we wound up with this situation, it's problematic.
331
+ //
332
+ this.ExecCommand({
333
+ key: CommandKey.SortTable,
334
+ table: JSON.parse(JSON.stringify(table)),
335
+ ...DefaultTableSortOptions,
336
+ ...options,
337
+ });
338
+ }
339
+ /** return freeze area */
340
+ GetFreeze() {
341
+ return { ...this.active_sheet.freeze };
342
+ }
343
+ /**
344
+ * insert rows(s) at some specific point
345
+ */
346
+ InsertRows(before_row = 0, count = 1) {
347
+ this.ExecCommand({
348
+ key: CommandKey.InsertRows,
349
+ before_row,
350
+ count,
351
+ });
352
+ }
353
+ /**
354
+ * return the table (if any) at the given address
355
+ */
356
+ GetTableReference(address) {
357
+ const sheet = this.model.sheets.Find(address.sheet_id || this.active_sheet.id);
358
+ return sheet?.CellData(address).table || undefined;
359
+ }
360
+ /**
361
+ * reset sheet, set data from CSV
362
+ *
363
+ * FIXME: this is problematic, because it runs around the exec command
364
+ * system. however it doesn't seem like a good candidate for a separate
365
+ * command. it should maybe move to the import class? (...)
366
+ *
367
+ * one problem with that is that import is really, really heavy (jszip).
368
+ * it seems wasteful to require all that just to import csv.
369
+ */
370
+ FromCSV(text) {
371
+ // CSV assumes dot-decimal, correct? if we want to use the
372
+ // parser we will have to check (and set/reset) the separator
373
+ this.parser.Save();
374
+ this.parser.SetLocaleSettings(DecimalMarkType.Period);
375
+ /*
376
+ const toggle_separator = this.parser.decimal_mark === DecimalMarkType.Comma;
377
+
378
+ if (toggle_separator)
379
+ {
380
+ // swap
381
+ this.parser.argument_separator = ArgumentSeparatorType.Comma;
382
+ this.parser.decimal_mark = DecimalMarkType.Period;
383
+ }
384
+ */
385
+ const records = ParseCSV(text);
386
+ const arr = records.map((record) => record.map((field) => {
387
+ if (field) {
388
+ const tmp = this.parser.Parse(field);
389
+ if (tmp.expression?.type === 'complex') {
390
+ return tmp.expression;
391
+ }
392
+ }
393
+ return ValueParser.TryParse(field).value;
394
+ }));
395
+ /*
396
+ if (toggle_separator) {
397
+ // reset
398
+ this.parser.argument_separator = ArgumentSeparatorType.Semicolon;
399
+ this.parser.decimal_mark = DecimalMarkType.Comma;
400
+ }
401
+ */
402
+ this.parser.Restore();
403
+ const end = {
404
+ row: Math.max(0, arr.length - 1),
405
+ column: arr.reduce((max, row) => Math.max(max, Math.max(0, row.length - 1)), 0),
406
+ };
407
+ // NOTE: SetRange here does not need to be translated, because
408
+ // we're not expecting spreadsheet functions in the CSV. CSV should
409
+ // be data only. Famous last words.
410
+ this.ExecCommand([
411
+ { key: CommandKey.Reset },
412
+ {
413
+ key: CommandKey.SetRange,
414
+ area: { start: { row: 0, column: 0 }, end },
415
+ value: arr,
416
+ },
417
+ // we took this out because the data may require a layout update
418
+ // (rebuilding tiles); in that case, this will be duplicative. maybe
419
+ // should use setTimeout or some sort of queue...
420
+ // { key: CommandKey.ResizeColumns }, // auto
421
+ ]);
422
+ }
423
+ /**
424
+ * insert column(s) at some specific point
425
+ */
426
+ InsertColumns(before_column = 0, count = 1) {
427
+ this.ExecCommand({
428
+ key: CommandKey.InsertColumns,
429
+ before_column,
430
+ count,
431
+ });
432
+ }
433
+ /** move sheet (X) before sheet (Y) */
434
+ ReorderSheet(index, move_before) {
435
+ this.ExecCommand({
436
+ key: CommandKey.ReorderSheet,
437
+ index,
438
+ move_before,
439
+ });
440
+ }
441
+ /**
442
+ * rename active sheet
443
+ */
444
+ RenameSheet(sheet, name) {
445
+ this.ExecCommand({
446
+ key: CommandKey.RenameSheet,
447
+ new_name: name,
448
+ id: sheet.id,
449
+ });
450
+ }
451
+ /**
452
+ * freeze rows or columns. set to 0 (or call with no arguments) to un-freeze.
453
+ *
454
+ * highglight is shown by default, but we can hide it(mostly for document load)
455
+ */
456
+ Freeze(rows = 0, columns = 0, highlight_transition = true) {
457
+ this.ExecCommand({
458
+ key: CommandKey.Freeze,
459
+ rows,
460
+ columns,
461
+ highlight_transition,
462
+ });
463
+ }
464
+ /**
465
+ * API method
466
+ */
467
+ SetRowHeight(row, height, shrink = true) {
468
+ this.ExecCommand({
469
+ key: CommandKey.ResizeRows,
470
+ row,
471
+ height,
472
+ shrink,
473
+ });
474
+ }
475
+ /**
476
+ * API method
477
+ *
478
+ * @param column - column, columns, or undefined means all columns
479
+ * @param width - target width, or undefined means auto-size
480
+ * @param allow_shrinking - for auto-size, allow shrinking. defaults to true.
481
+ * set false to disallow shrinking.
482
+ */
483
+ SetColumnWidth(column, width, allow_shrinking) {
484
+ this.ExecCommand({
485
+ key: CommandKey.ResizeColumns,
486
+ column,
487
+ width,
488
+ allow_shrinking,
489
+ });
490
+ }
491
+ /**
492
+ * filter table. what this means is "show the rows that match the filter
493
+ * and hide the other rows". it doesn't actually change data, but it does
494
+ * show/hide rows which (now) has some data effects.
495
+ *
496
+ * note that we don't pass the filter command through the command queue.
497
+ * it uses a callback, so that would not work. rather we filter first,
498
+ * then send hide/show row commands through the command queue. that will
499
+ * propagate updates.
500
+ */
501
+ FilterTable(table, column, filter) {
502
+ const command = [];
503
+ if (!table.area.start.sheet_id) {
504
+ throw new Error('invalid table area');
505
+ }
506
+ const sheet = this.model.sheets.Find(table.area.start.sheet_id);
507
+ if (!sheet) {
508
+ throw new Error('invalid table sheet');
509
+ }
510
+ const show_rows = [];
511
+ const hide_rows = [];
512
+ const end = table.totals_row ? table.area.end.row - 1 : table.area.end.row;
513
+ column += table.area.start.column;
514
+ for (let row = table.area.start.row + 1; row <= end; row++) {
515
+ const cell = sheet.CellData({ row, column });
516
+ const show = filter(cell);
517
+ const current = sheet.GetRowHeight(row);
518
+ if (show && !current) {
519
+ show_rows.push(row);
520
+ }
521
+ else if (!show && current) {
522
+ hide_rows.push(row);
523
+ }
524
+ }
525
+ if (show_rows) {
526
+ command.push({
527
+ key: CommandKey.ResizeRows,
528
+ sheet_id: sheet.id,
529
+ row: show_rows,
530
+ height: sheet.default_row_height,
531
+ });
532
+ }
533
+ if (hide_rows) {
534
+ command.push({
535
+ key: CommandKey.ResizeRows,
536
+ sheet_id: sheet.id,
537
+ row: hide_rows,
538
+ height: 0,
539
+ });
540
+ }
541
+ if (command.length) {
542
+ this.ExecCommand(command);
543
+ }
544
+ }
545
+ /**
546
+ * UpdateSheets means "set these as the sheets, drop any old stuff". there's
547
+ * an implicit reset (in fact we may do that twice in some cases).
548
+ *
549
+ * this is non-UI; specialization should handle the UI part
550
+ */
551
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
552
+ UpdateSheets(data, render = false, activate_sheet) {
553
+ Sheet.Reset(); // reset ID generation
554
+ const sheets = data.map((sheet) => Sheet.FromJSON(sheet, this.model.theme_style_properties));
555
+ // ensure we have a sheets[0] so we can set active
556
+ if (sheets.length === 0) {
557
+ sheets.push(Sheet.Blank(this.model.theme_style_properties));
558
+ }
559
+ // now assign sheets
560
+ this.model.sheets.Assign(sheets);
561
+ this.ResetMetadata(); // FIXME: shouldn't we just set metadata from the file?
562
+ // set active
563
+ this.active_sheet = sheets[0];
564
+ // possibly set an active sheet on load (shortcut)
565
+ // could we not use a command for this?
566
+ if (activate_sheet) {
567
+ const sheet = this.model.sheets.Find(activate_sheet);
568
+ if (sheet) {
569
+ this.active_sheet = sheet;
570
+ }
571
+ }
572
+ // NOTE: we're not handling annotations here. do we need to? (...)
573
+ }
574
+ /**
575
+ * set functions for AC matcher. should be called by calculator on init,
576
+ * or when any functions are added/removed.
577
+ *
578
+ * FIXME: we should use this to normalize function names, on insert and
579
+ * on paste (if we're doing that).
580
+ *
581
+ * FIXME: are named expressions included here? (this function predates
582
+ * named expressions).
583
+ *
584
+ *
585
+ * this moved to grid base because we use the list to check for conflicts
586
+ * when setting names.
587
+ *
588
+ */
589
+ SetAutocompleteFunctions(functions) {
590
+ const expressions = [];
591
+ for (const entry of this.model.named.list) {
592
+ expressions.push({
593
+ name: entry.name,
594
+ named: true,
595
+ type: 'token',
596
+ });
597
+ }
598
+ /*
599
+ for (const name of this.model.named_expressions.keys()) {
600
+ expressions.push({
601
+ name,
602
+ named: true,
603
+ type: 'token',
604
+ });
605
+ }
606
+ */
607
+ const consolidated = functions.slice(0).concat(expressions);
608
+ /*
609
+ const consolidated = functions.slice(0).concat(
610
+ this.model.named_ranges.List().map((named_range) => {
611
+ return {
612
+ name: named_range.name,
613
+ named: true,
614
+ type: 'token'
615
+ };
616
+ }),
617
+ expressions,
618
+ );
619
+ */
620
+ this.autocomplete_matcher.SetFunctions(consolidated);
621
+ }
622
+ ResetMetadata() {
623
+ this.model.document_name = undefined;
624
+ this.model.user_data = undefined;
625
+ }
626
+ // --- protected methods -----------------------------------------------------
627
+ /**
628
+ * see ResizeRowsInternal
629
+ */
630
+ ResizeColumnsInternal(command) {
631
+ const sheet = command.sheet_id ? this.FindSheet(command.sheet_id) : this.active_sheet;
632
+ // normalize
633
+ let column = command.column;
634
+ if (typeof column === 'undefined') {
635
+ column = [];
636
+ for (let i = 0; i < sheet.columns; i++)
637
+ column.push(i);
638
+ }
639
+ if (typeof column === 'number')
640
+ column = [column];
641
+ if (command.width) {
642
+ for (const entry of column) {
643
+ sheet.SetColumnWidth(entry, command.width);
644
+ }
645
+ }
646
+ else {
647
+ console.error('auto size not supported');
648
+ }
649
+ }
650
+ RemoveAnnotationInternal(command) {
651
+ for (let i = 0; i < command.sheet.annotations.length; i++) {
652
+ if (command.annotation === command.sheet.annotations[i]) {
653
+ command.sheet.annotations.splice(i, 1);
654
+ // subclass only // this.layout.RemoveAnnotation(annotation);
655
+ // do we still need this message? not sure
656
+ this.grid_events.Publish({
657
+ type: 'annotation',
658
+ annotation: command.annotation,
659
+ event: 'delete',
660
+ });
661
+ return;
662
+ }
663
+ }
664
+ }
665
+ /** placeholder */
666
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
667
+ CreateAnnotationInternal(command) {
668
+ }
669
+ /**
670
+ * resize rows. this supports auto size, but that will fail in !ui grid,
671
+ * because it uses HTML. also non-ui doesn't really need to worry about
672
+ * scale... we should split.
673
+ */
674
+ ResizeRowsInternal(command) {
675
+ // we're guaranteed this now, we should have a way to represent that...
676
+ const sheet = command.sheet_id ? this.FindSheet(command.sheet_id) : this.active_sheet;
677
+ // normalize rows -> array. undefined means all rows.
678
+ let row = command.row;
679
+ if (typeof row === 'undefined') {
680
+ row = [];
681
+ for (let i = 0; i < sheet.rows; i++)
682
+ row.push(i);
683
+ }
684
+ if (typeof row === 'number')
685
+ row = [row];
686
+ // I guess this was intended to prevent auto-size, but what about 0?
687
+ if (command.height) {
688
+ for (const entry of row) {
689
+ sheet.SetRowHeight(entry, command.height);
690
+ }
691
+ }
692
+ else {
693
+ console.error('auto size not supported');
694
+ }
695
+ return undefined;
696
+ }
697
+ ResetInternal() {
698
+ Sheet.Reset();
699
+ this.UpdateSheets([], true);
700
+ this.model.named.Reset();
701
+ this.model.macro_functions.clear(); // = {};
702
+ this.model.tables.clear();
703
+ }
704
+ /**
705
+ * check if we can paste into the target area(s). this will
706
+ * return false if the areas contain locked cells, or part of
707
+ * an array or merge but not the whole array or merge.
708
+ *
709
+ * @param areas
710
+ * @returns
711
+ */
712
+ ValidatePasteAreas(areas) {
713
+ for (const area of areas) {
714
+ let sheet = this.active_sheet;
715
+ if (area.start.sheet_id && area.start.sheet_id !== sheet.id) {
716
+ sheet = this.model.sheets.Find(area.start.sheet_id);
717
+ }
718
+ if (!sheet) {
719
+ return false;
720
+ }
721
+ // let valid = true;
722
+ for (const cell of sheet.cells.Iterate(area)) {
723
+ if (cell.style?.locked) {
724
+ console.info('invalid: locked cells');
725
+ return false;
726
+ }
727
+ if (cell.merge_area) {
728
+ if (!area.Contains(cell.merge_area.start) || !area.Contains(cell.merge_area.end)) {
729
+ console.info('invalid: merge area');
730
+ return false;
731
+ }
732
+ }
733
+ if (cell.area) {
734
+ if (!area.Contains(cell.area.start) || !area.Contains(cell.area.end)) {
735
+ console.info('invalid: array');
736
+ return false;
737
+ }
738
+ }
739
+ }
740
+ /*
741
+ sheet.cells.Apply2(area, cell => {
742
+ if (cell.style?.locked) {
743
+ console.info('invalid: locked cells');
744
+ valid = false;
745
+ }
746
+ if (cell.merge_area) {
747
+ if (!area.Contains(cell.merge_area.start) || !area.Contains(cell.merge_area.end)) {
748
+ console.info('invalid: merge area');
749
+ valid = false;
750
+ }
751
+ }
752
+ if (cell.area) {
753
+ if (!area.Contains(cell.area.start) || !area.Contains(cell.area.end)) {
754
+ console.info('invalid: array');
755
+ valid = false;
756
+ }
757
+ }
758
+
759
+ return valid;
760
+ });
761
+ */
762
+ // if (!valid) {
763
+ // return false;
764
+ // }
765
+ }
766
+ return true;
767
+ }
768
+ SetValidationInternal(command) {
769
+ const sheet = this.FindSheet(command.area);
770
+ if (!sheet) {
771
+ throw new Error('invalid sheet in set validation');
772
+ }
773
+ const target = { start: command.area.start, end: command.area.end };
774
+ if (command.range) {
775
+ sheet.AddValidation({
776
+ type: 'range',
777
+ error: !!command.error,
778
+ area: command.range,
779
+ target: [target],
780
+ });
781
+ /*
782
+ cell.validation = {
783
+ type: ValidationType.Range,
784
+ area: command.range,
785
+ error: !!command.error,
786
+ };
787
+ */
788
+ }
789
+ else if (command.list) {
790
+ sheet.AddValidation({
791
+ type: 'list',
792
+ error: !!command.error,
793
+ list: JSON.parse(JSON.stringify(command.list)),
794
+ target: [target],
795
+ });
796
+ /*
797
+ cell.validation = {
798
+ type: ValidationType.List,
799
+ list: JSON.parse(JSON.stringify(command.list)),
800
+ error: !!command.error,
801
+ }
802
+ */
803
+ }
804
+ else {
805
+ // cell.validation = undefined;
806
+ sheet.RemoveValidations(target);
807
+ }
808
+ }
809
+ /**
810
+ * get values from a range of data
811
+ * @param area
812
+ */
813
+ GetValidationRange(area) {
814
+ let list;
815
+ const sheet = this.FindSheet(area);
816
+ if (sheet) {
817
+ list = [];
818
+ // clamp to actual area to avoid screwing up sheet
819
+ // FIXME: what does that cause [problem with selections], why, and fix it
820
+ area = sheet.RealArea(new Area(area.start, area.end), true);
821
+ for (let row = area.start.row; row <= area.end.row; row++) {
822
+ for (let column = area.start.column; column <= area.end.column; column++) {
823
+ const cell = sheet.CellData({ row, column });
824
+ if (cell && cell.formatted) {
825
+ if (typeof cell.formatted === 'string') {
826
+ list.push(cell.formatted);
827
+ }
828
+ else {
829
+ list.push(NumberFormat.FormatPartsAsText(cell.formatted));
830
+ }
831
+ }
832
+ }
833
+ }
834
+ }
835
+ return list;
836
+ }
837
+ /**
838
+ * @returns true if we need a recalc, because references have broken.
839
+ */
840
+ DeleteSheetInternal(command) {
841
+ let is_active = false;
842
+ let index = -1;
843
+ let target_name = '';
844
+ let requires_recalc = false;
845
+ // remove from array. check if this is the active sheet
846
+ const named_sheet = command.name ? command.name.toLowerCase() : '';
847
+ const sheets = this.model.sheets.list.filter((sheet, i) => {
848
+ if (i === command.index || sheet.id === command.id || sheet.name.toLowerCase() === named_sheet) {
849
+ is_active = (sheet === this.active_sheet);
850
+ this.model.named.RemoveRangesForSheet(sheet.id);
851
+ target_name = sheet.name;
852
+ index = i;
853
+ return false;
854
+ }
855
+ return true;
856
+ });
857
+ // NOTE: we might want to remove references to this sheet. see
858
+ // how we patch references in insert columns/rows functions.
859
+ // actually note the logic we need is already in the rename sheet
860
+ // function; we just need to split it out from actually renaming the
861
+ // sheet, then we can use it
862
+ if (target_name) {
863
+ const count = this.RenameSheetReferences(sheets, target_name, '#REF');
864
+ if (count > 0) {
865
+ requires_recalc = true;
866
+ }
867
+ }
868
+ // empty? create new, activate
869
+ // UPDATE: we also need to create if all remaining sheets are hidden
870
+ if (!sheets.length) {
871
+ sheets.push(Sheet.Blank(this.model.theme_style_properties));
872
+ index = 0;
873
+ }
874
+ else if (sheets.every(test => !test.visible)) {
875
+ // why insert at 0 here? shouldn't it still be last,
876
+ // even if all the others are empty?
877
+ sheets.unshift(Sheet.Blank(this.model.theme_style_properties));
878
+ index = 0;
879
+ }
880
+ else {
881
+ if (index >= sheets.length) {
882
+ index = 0;
883
+ }
884
+ while (!sheets[index].visible) {
885
+ index++;
886
+ }
887
+ }
888
+ // this.model.sheets = sheets;
889
+ this.model.sheets.Assign(sheets);
890
+ // need to activate a new sheet? use the next one (now in the slot
891
+ // we just removed). this will roll over properly if we're at the end.
892
+ // UPDATE: we need to make sure that the target is not hidden, or we
893
+ // can't activate it
894
+ if (is_active) {
895
+ // console.info('activate @', index);
896
+ this.ActivateSheetInternal({ key: CommandKey.ActivateSheet, index });
897
+ }
898
+ return requires_recalc;
899
+ }
900
+ /**
901
+ * rename a sheet. this requires changing any formulae that refer to the
902
+ * old name to refer to the new name. if there are any references by ID
903
+ * those don't have to change.
904
+ *
905
+ * FIXME: can we do this using the dependency graph? (...)
906
+ */
907
+ RenameSheetInternal(target, name) {
908
+ // validate name... ?
909
+ if (!name || IllegalSheetNameRegex.test(name)) {
910
+ throw new Error('invalid sheet name');
911
+ }
912
+ // also can't have two sheets with the same name
913
+ const compare = name.toLowerCase();
914
+ for (const sheet of this.model.sheets.list) {
915
+ if (sheet !== target && sheet.name.toLowerCase() === compare) {
916
+ throw new Error('sheet name already exists');
917
+ }
918
+ }
919
+ // function will LC the name
920
+ // const old_name = target.name.toLowerCase();
921
+ const old_name = target.name;
922
+ target.name = name;
923
+ // need to update indexes
924
+ this.model.sheets.Assign(this.model.sheets.list);
925
+ this.RenameSheetReferences(this.model.sheets.list, old_name, name);
926
+ }
927
+ /**
928
+ *
929
+ */
930
+ SortTableInternal(command) {
931
+ if (!command.table.area.start.sheet_id) {
932
+ throw new Error('table has invalid area');
933
+ }
934
+ const sheet = this.model.sheets.Find(command.table.area.start.sheet_id);
935
+ if (!sheet) {
936
+ throw new Error('invalid sheet in table area');
937
+ }
938
+ // I guess we're sorting on calculated value? seems weird.
939
+ // NOTE: only sort hidden rows... what to do with !hidden rows? do they
940
+ // get sorted anyway? [A: no, leave them as-is]
941
+ const ranked = [];
942
+ // get a list of visible table rows. that will be our insert map at the end
943
+ const visible = [];
944
+ let end = command.table.area.end.row;
945
+ if (command.table.totals_row) {
946
+ end--;
947
+ }
948
+ // for auto-sort
949
+ let text_count = 0;
950
+ let number_count = 0;
951
+ for (let row = command.table.area.start.row + 1; row <= end; row++) {
952
+ const height = sheet.GetRowHeight(row);
953
+ if (height) {
954
+ visible.push(row);
955
+ }
956
+ else {
957
+ continue;
958
+ }
959
+ const row_data = {
960
+ row,
961
+ number: 0,
962
+ text: '',
963
+ type: ValueType.undefined,
964
+ data: [],
965
+ };
966
+ for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
967
+ const cd = sheet.CellData({ row, column });
968
+ // sort column is relative to table
969
+ if (column === command.column + command.table.area.start.column) {
970
+ const check_type = cd.calculated_type || cd.type;
971
+ if (check_type === ValueType.string) {
972
+ text_count++;
973
+ }
974
+ else if (check_type === ValueType.number) {
975
+ number_count++;
976
+ }
977
+ // we can precalculate the type for sorting
978
+ const value = cd.calculated_type ? cd.calculated : cd.value;
979
+ row_data.text = value?.toString() || '';
980
+ row_data.number = Number(value) || 0;
981
+ row_data.type = cd.calculated_type || cd.type;
982
+ }
983
+ row_data.data.push({
984
+ address: { row, column },
985
+ data: cd.value,
986
+ type: cd.type,
987
+ style: cd.style,
988
+ });
989
+ }
990
+ ranked.push(row_data);
991
+ }
992
+ // auto sort - default to text, unless we see more numbers
993
+ let sort_type = command.type;
994
+ if (sort_type === 'auto') {
995
+ if (number_count > text_count) {
996
+ sort_type = 'numeric';
997
+ }
998
+ else {
999
+ sort_type = 'text';
1000
+ }
1001
+ }
1002
+ // console.info(visible, ranked);
1003
+ // rank
1004
+ const invert = command.asc ? 1 : -1;
1005
+ switch (sort_type) {
1006
+ case 'numeric':
1007
+ ranked.sort((a, b) => {
1008
+ if (a.type === ValueType.undefined) {
1009
+ return ((b.type === ValueType.undefined) ? 0 : 1);
1010
+ }
1011
+ if (b.type === ValueType.undefined) {
1012
+ return -1;
1013
+ }
1014
+ return (a.number - b.number) * invert;
1015
+ });
1016
+ break;
1017
+ case 'text':
1018
+ default:
1019
+ ranked.sort((a, b) => {
1020
+ if (a.type === ValueType.undefined) {
1021
+ return ((b.type === ValueType.undefined) ? 0 : 1);
1022
+ }
1023
+ if (b.type === ValueType.undefined) {
1024
+ return -1;
1025
+ }
1026
+ return a.text.localeCompare(b.text) * invert;
1027
+ });
1028
+ break;
1029
+ }
1030
+ // now apply the sort
1031
+ const insert = { row: command.table.area.start.row + 1, column: command.table.area.start.column };
1032
+ for (let i = 0; i < visible.length; i++) {
1033
+ insert.row = visible[i];
1034
+ const entry = ranked[i];
1035
+ insert.column = command.table.area.start.column; // reset
1036
+ for (const cell of entry.data) {
1037
+ if (cell.type === ValueType.formula) {
1038
+ let data = cell.data;
1039
+ const offsets = { columns: 0, rows: insert.row - entry.row };
1040
+ const parse_result = this.parser.Parse(data);
1041
+ if (parse_result.expression) {
1042
+ data = '=' + this.parser.Render(parse_result.expression, { offset: offsets, missing: '' });
1043
+ }
1044
+ sheet.SetCellValue(insert, data);
1045
+ }
1046
+ else {
1047
+ sheet.SetCellValue(insert, cell.data);
1048
+ }
1049
+ sheet.UpdateCellStyle(insert, cell.style || {}, false);
1050
+ insert.column++; // step
1051
+ }
1052
+ }
1053
+ // keep reference. we don't have the actual table, we have a copy.
1054
+ // this is done because the command queue might be broadcast, so
1055
+ // references won't work.
1056
+ const ref = this.model.tables.get(command.table.name.toLowerCase());
1057
+ if (ref) {
1058
+ ref.sort = {
1059
+ type: command.type,
1060
+ asc: !!command.asc,
1061
+ column: command.column,
1062
+ };
1063
+ }
1064
+ // flush style in rows that don't change, to force repainting. this
1065
+ // has to do with how table styles are overlaid on other styles; it's
1066
+ // not optimal at the moment.
1067
+ {
1068
+ let row = command.table.area.start.row;
1069
+ for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
1070
+ sheet.cells.data[row][column]?.FlushStyle();
1071
+ }
1072
+ if (command.table.totals_row) {
1073
+ row = command.table.area.end.row;
1074
+ for (let column = command.table.area.start.column; column <= command.table.area.end.column; column++) {
1075
+ sheet.cells.data[row][column]?.FlushStyle();
1076
+ }
1077
+ }
1078
+ }
1079
+ // console.info(ordered);
1080
+ return new Area(command.table.area.start, command.table.area.end);
1081
+ }
1082
+ /**
1083
+ * update all columns of a table (collect column names). this
1084
+ * method rebuilds all columns; that's probably unecessary in
1085
+ * many cases, but we'll start here and we can drill down later.
1086
+ *
1087
+ * we do two things here: we normalize column header values, and
1088
+ * we collect them for table headers.
1089
+ *
1090
+ * @param table
1091
+ */
1092
+ UpdateTableColumns(table) {
1093
+ if (!table.area.start.sheet_id) {
1094
+ throw new Error('invalid area in table');
1095
+ }
1096
+ const sheet = this.model.sheets.Find(table.area.start.sheet_id);
1097
+ if (!sheet) {
1098
+ throw new Error('invalid sheet in table');
1099
+ }
1100
+ // this can get called when a document is loaded, we might
1101
+ // not have column names when we start. but if we do, we will
1102
+ // need to keep the old ones so we can check deltas.
1103
+ const current_columns = table.columns?.slice(0) || undefined;
1104
+ const columns = [];
1105
+ const row = table.area.start.row;
1106
+ const count = table.area.end.column - table.area.start.column + 1;
1107
+ let column = table.area.start.column;
1108
+ for (let i = 0; i < count; i++, column++) {
1109
+ const header = sheet.CellData({ row, column });
1110
+ let value = '';
1111
+ if (header.type !== ValueType.string) {
1112
+ if (typeof header.formatted !== 'undefined') {
1113
+ value = (header.formatted).toString();
1114
+ }
1115
+ else if (typeof header.calculated !== 'undefined') {
1116
+ value = (header.calculated).toString();
1117
+ }
1118
+ else if (typeof header.value !== 'undefined') {
1119
+ value = (header.value).toString();
1120
+ }
1121
+ header.Set(value, ValueType.string);
1122
+ }
1123
+ else {
1124
+ value = header.value || '';
1125
+ }
1126
+ if (!value) {
1127
+ value = `Column${i + 1}`;
1128
+ }
1129
+ let proposed = value;
1130
+ let success = false;
1131
+ let index = 1;
1132
+ while (!success) {
1133
+ success = true;
1134
+ inner_loop: for (const check of columns) {
1135
+ if (check.toLowerCase() === proposed.toLowerCase()) {
1136
+ success = false;
1137
+ proposed = `${value}${++index}`;
1138
+ break inner_loop;
1139
+ }
1140
+ }
1141
+ }
1142
+ header.Set(proposed, ValueType.string);
1143
+ columns.push(proposed.toLowerCase());
1144
+ }
1145
+ // TODO: this is good, and works, but we are going to have to
1146
+ // look for structured references and update them if the column
1147
+ // names change.
1148
+ if (current_columns) {
1149
+ // if we are inserting/removing columns, we're probably
1150
+ // not changing names at the same time. on remove, some
1151
+ // references will break, but that's to be expected. on
1152
+ // insert, new columns will get added but we don't have
1153
+ // to change references.
1154
+ if (current_columns.length === columns.length) {
1155
+ const update = new Map();
1156
+ for (let i = 0; i < current_columns.length; i++) {
1157
+ const compare = current_columns[i].toLowerCase();
1158
+ if (compare !== columns[i]) {
1159
+ update.set(compare, columns[i]); // add old -> new
1160
+ }
1161
+ }
1162
+ if (update.size) {
1163
+ // OK, we need to update. we're iterating cells, then
1164
+ // updates, so we don't accidentally oscillate if we have
1165
+ // columns that swap names. going through once should
1166
+ // ensure that doesn't happen.
1167
+ const table_name = table.name.toLowerCase();
1168
+ for (const sheet of this.model.sheets.list) {
1169
+ // there's an additional complication: we support anonymous
1170
+ // tables, if the cell is in the table. so we also have to
1171
+ // know the address. so we can't use the IterateAll method.
1172
+ // duh, no we don't. if the cell is in the table it will have
1173
+ // a reference.
1174
+ for (const cell of sheet.cells.Iterate()) {
1175
+ if (cell.ValueIsFormula()) {
1176
+ let updated_formula = false;
1177
+ const parse_result = this.parser.Parse(cell.value);
1178
+ if (parse_result.expression) {
1179
+ this.parser.Walk(parse_result.expression, (unit) => {
1180
+ if (unit.type === 'structured-reference') {
1181
+ if (unit.table.toLowerCase() === table_name ||
1182
+ (!unit.table && cell.table === table)) {
1183
+ // we may need to rewrite...
1184
+ for (const [key, value] of update.entries()) {
1185
+ if (unit.column.toLowerCase() === key) {
1186
+ // ok we need to update
1187
+ unit.column = value;
1188
+ updated_formula = true;
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ return true;
1194
+ });
1195
+ if (updated_formula) {
1196
+ console.info('updating value');
1197
+ cell.value = '=' + this.parser.Render(parse_result.expression, {
1198
+ missing: '',
1199
+ });
1200
+ }
1201
+ }
1202
+ }
1203
+ }
1204
+ /*
1205
+ sheet.cells.IterateAll(cell => {
1206
+ if (cell.ValueIsFormula()) {
1207
+ let updated_formula = false;
1208
+ const parse_result = this.parser.Parse(cell.value);
1209
+ if (parse_result.expression) {
1210
+
1211
+ this.parser.Walk(parse_result.expression, (unit) => {
1212
+ if (unit.type === 'structured-reference') {
1213
+
1214
+ if (unit.table.toLowerCase() === table_name ||
1215
+ (!unit.table && cell.table === table)) {
1216
+
1217
+ // we may need to rewrite...
1218
+ for (const [key, value] of update.entries()) {
1219
+ if (unit.column.toLowerCase() === key) {
1220
+
1221
+ // ok we need to update
1222
+ unit.column = value;
1223
+ updated_formula = true;
1224
+
1225
+ }
1226
+ }
1227
+
1228
+ }
1229
+ }
1230
+ return true;
1231
+ });
1232
+ if (updated_formula) {
1233
+ console.info('updating value');
1234
+ cell.value = '=' + this.parser.Render(parse_result.expression, {
1235
+ missing: '',
1236
+ });
1237
+ }
1238
+ }
1239
+ }
1240
+ });
1241
+ */
1242
+ }
1243
+ }
1244
+ }
1245
+ }
1246
+ table.columns = columns;
1247
+ return {
1248
+ start: {
1249
+ ...table.area.start,
1250
+ }, end: {
1251
+ row: table.area.start.row,
1252
+ column: table.area.end.column,
1253
+ }
1254
+ };
1255
+ }
1256
+ /**
1257
+ * set range, via command. returns affected area.
1258
+ *
1259
+ * Adding a flags parameter (in/out) to support indicating
1260
+ * that we need to update layout.
1261
+ */
1262
+ SetRangeInternal(command, flags = {}) {
1263
+ // NOTE: apparently if we call SetRange with a single target
1264
+ // and the array flag set, it gets translated to an area. which
1265
+ // is OK, I guess, but there may be an unecessary branch in here.
1266
+ const area = IsCellAddress(command.area)
1267
+ ? new Area(command.area)
1268
+ : new Area(command.area.start, command.area.end);
1269
+ const sheet = this.FindSheet(area);
1270
+ if (!area.start.sheet_id) {
1271
+ area.start.sheet_id = sheet.id;
1272
+ }
1273
+ if (!area.entire_row && !area.entire_column && (area.end.row >= sheet.rows
1274
+ || area.end.column >= sheet.columns)) {
1275
+ // we have to call this because the 'set area' method calls RealArea
1276
+ sheet.cells.EnsureCell(area.end);
1277
+ // should we send a structure event here? we may be increasing the
1278
+ // size, in which case we should send the event. even though no addresses
1279
+ // change, there are new cells.
1280
+ if (sheet === this.active_sheet) {
1281
+ flags.layout = true;
1282
+ }
1283
+ }
1284
+ // originally we called sheet methods here, but all the sheet
1285
+ // does is call methods on the cells object -- we can shortcut.
1286
+ // is that a good idea? (...)
1287
+ // at a minimum we can consolidate...
1288
+ if (IsCellAddress(command.area)) {
1289
+ // FIXME: should throw if we try to set part of an array
1290
+ const cell = sheet.CellData(command.area);
1291
+ if (cell.area && (cell.area.rows > 1 || cell.area.columns > 1)) {
1292
+ this.Error(ErrorCode.array);
1293
+ return;
1294
+ }
1295
+ // single cell
1296
+ // UPDATE: could be array
1297
+ // type is value|value[][], pull out first value. at some point
1298
+ // we may have supported value[], or maybe they were passed in
1299
+ // accidentally, but check regardless.
1300
+ // FIXME: no, that should throw (or otherwise error) (or fix the data?).
1301
+ // we can't handle errors all the way down the call stack.
1302
+ let value = Array.isArray(command.value) ?
1303
+ Array.isArray(command.value[0]) ? command.value[0][0] : command.value[0] : command.value;
1304
+ // translate R1C1. in this case, we translate relative to the
1305
+ // target address, irrspective of the array flag. this is the
1306
+ // easiest case?
1307
+ // NOTE: as noted above (top of function), if a single cell target
1308
+ // is set with the array flag, it may fall into the next branch. not
1309
+ // sure that makes much of a difference.
1310
+ if (command.r1c1) {
1311
+ value = this.TranslateR1C1(command.area, value);
1312
+ }
1313
+ if (command.array) {
1314
+ // what is the case for this? not saying it doesn't happen, just
1315
+ // when is it useful?
1316
+ // A: there is the case in Excel where there are different semantics
1317
+ // for array calculation; something we mentioned in one of the kb
1318
+ // articles, something about array functions... [FIXME: ref?]
1319
+ sheet.SetArrayValue(area, value);
1320
+ }
1321
+ else {
1322
+ sheet.SetCellValue(command.area, value);
1323
+ }
1324
+ return area;
1325
+ }
1326
+ else {
1327
+ // there are a couple of options here, from the methods that
1328
+ // have accumulated in Sheet.
1329
+ // SetArrayValue -- set data as an array
1330
+ // SetAreaValues -- set values from data one-to-one
1331
+ // SetAreaValue -- single value repeated in range
1332
+ // FIXME: clean this up!
1333
+ if (command.array) {
1334
+ let value = Array.isArray(command.value) ?
1335
+ Array.isArray(command.value[0]) ? command.value[0][0] : command.value[0] : command.value;
1336
+ if (command.r1c1) {
1337
+ value = this.TranslateR1C1(area.start, value);
1338
+ }
1339
+ sheet.SetArrayValue(area, value);
1340
+ }
1341
+ else {
1342
+ // in this case, either value is a single value or it's a 2D array;
1343
+ // and area is a range of unknown size. we do a 1-1 map from area
1344
+ // member to data member. if the data is not the same shape, it just
1345
+ // results in empty cells (if area is larger) or dropped data (if value
1346
+ // is larger).
1347
+ // so for the purposes of R1C1, we have to run the same loop that
1348
+ // happens internally in the Cells.SetArea routine. but I definitely
1349
+ // don't want R1C1 to get all the way in there.
1350
+ // FIXME/TODO: we're doing this the naive way for now. it could be
1351
+ // optimized in several ways.
1352
+ if (command.r1c1) {
1353
+ if (Array.isArray(command.value)) {
1354
+ // loop on DATA, since that's what we care about here. we can
1355
+ // expand data, since it won't spill in the next call (spill is
1356
+ // handled earlier in the call stack).
1357
+ for (let r = 0; r < command.value.length && r < area.rows; r++) {
1358
+ if (!command.value[r]) {
1359
+ command.value[r] = [];
1360
+ }
1361
+ const row = command.value[r];
1362
+ for (let c = 0; c < row.length && c < area.columns; c++) {
1363
+ const target = { ...area.start, row: area.start.row + r, column: area.start.column + c };
1364
+ row[c] = this.TranslateR1C1(target, row[c]);
1365
+ }
1366
+ }
1367
+ }
1368
+ else {
1369
+ // only have to do this for strings
1370
+ if (typeof command.value === 'string' && command.value[0] === '=') {
1371
+ // we need to rebuild the value so it is an array, so that
1372
+ // relative addresses will be relative to the cell.
1373
+ const value = [];
1374
+ for (let r = 0; r < area.rows; r++) {
1375
+ const row = [];
1376
+ for (let c = 0; c < area.columns; c++) {
1377
+ const target = { ...area.start, row: area.start.row + r, column: area.start.column + c };
1378
+ row.push(this.TranslateR1C1(target, command.value));
1379
+ }
1380
+ value.push(row);
1381
+ }
1382
+ command.value = value;
1383
+ }
1384
+ }
1385
+ }
1386
+ sheet.SetAreaValues2(area, command.value);
1387
+ }
1388
+ return area;
1389
+ }
1390
+ }
1391
+ /**
1392
+ * basic implementation does not handle any UI, painting, or layout.
1393
+ */
1394
+ ActivateSheetInternal(command) {
1395
+ const candidate = this.ResolveSheet(command) || this.model.sheets.list[0];
1396
+ if (this.active_sheet === candidate && !command.force) {
1397
+ return;
1398
+ }
1399
+ if (!candidate.visible) {
1400
+ throw new Error('cannot activate hidden sheet');
1401
+ }
1402
+ // hold this for the event (later)
1403
+ const deactivate = this.active_sheet;
1404
+ // select target
1405
+ this.active_sheet = candidate;
1406
+ /*
1407
+ // scrub, then add any sheet annotations. note the caller will
1408
+ // still have to inflate these or do whatever step is necessary to
1409
+ // render.
1410
+
1411
+ const annotations = this.active_sheet.annotations;
1412
+ for (const element of annotations) {
1413
+ this.AddAnnotation(element, true);
1414
+ }
1415
+ */
1416
+ this.grid_events.Publish({
1417
+ type: 'sheet-change',
1418
+ deactivate,
1419
+ activate: this.active_sheet,
1420
+ });
1421
+ }
1422
+ ShowSheetInternal(command) {
1423
+ const sheet = this.ResolveSheet(command);
1424
+ // invalid
1425
+ if (!sheet) {
1426
+ return;
1427
+ }
1428
+ // not changed
1429
+ if (sheet.visible === command.show) {
1430
+ return;
1431
+ }
1432
+ // make sure at least one will be visible after the operation
1433
+ if (!command.show) {
1434
+ let count = 0;
1435
+ for (const test of this.model.sheets.list) {
1436
+ if (!sheet.visible || test === sheet) {
1437
+ count++;
1438
+ }
1439
+ }
1440
+ if (count >= this.model.sheets.length) {
1441
+ throw new Error('can\'t hide all sheets');
1442
+ }
1443
+ }
1444
+ // ok, set
1445
+ sheet.visible = command.show;
1446
+ // is this current?
1447
+ if (sheet === this.active_sheet) {
1448
+ // this needs to check the visibility field, or else we'll throw
1449
+ // when we call the activate method. given the above check we know
1450
+ // that there's at least one visible sheet.
1451
+ const list = this.model.sheets.list;
1452
+ // first find the _next_ visible sheet...
1453
+ for (let i = 0; i < list.length; i++) {
1454
+ if (list[i] === this.active_sheet) {
1455
+ for (let j = i + 1; j < list.length; j++) {
1456
+ if (list[j].visible) {
1457
+ this.ActivateSheetInternal({
1458
+ key: CommandKey.ActivateSheet,
1459
+ index: j,
1460
+ });
1461
+ return;
1462
+ }
1463
+ }
1464
+ // if we got here, then we need to start again from the beginning
1465
+ for (let j = 0; j < list.length; j++) {
1466
+ if (list[j].visible) {
1467
+ this.ActivateSheetInternal({
1468
+ key: CommandKey.ActivateSheet,
1469
+ index: j,
1470
+ });
1471
+ return;
1472
+ }
1473
+ }
1474
+ // should not be possible
1475
+ throw new Error('no visible sheet');
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+ /**
1481
+ * normalize commands. for co-editing support we need to ensure that
1482
+ * commands properly have sheet IDs in areas/addresses (and explicit
1483
+ * fields in some cases).
1484
+ *
1485
+ * at the same time we're editing the commands a little bit to make
1486
+ * them a little more consistent (within reason).
1487
+ *
1488
+ * @param commands
1489
+ */
1490
+ NormalizeCommands(commands) {
1491
+ if (!Array.isArray(commands)) {
1492
+ commands = [commands];
1493
+ }
1494
+ const id = this.active_sheet.id;
1495
+ for (const command of commands) {
1496
+ switch (command.key) {
1497
+ // nothing
1498
+ case CommandKey.Null:
1499
+ case CommandKey.TabColor:
1500
+ case CommandKey.ShowHeaders:
1501
+ case CommandKey.ShowSheet:
1502
+ case CommandKey.AddSheet:
1503
+ case CommandKey.DuplicateSheet:
1504
+ case CommandKey.DeleteSheet:
1505
+ case CommandKey.ActivateSheet:
1506
+ case CommandKey.RenameSheet:
1507
+ case CommandKey.ReorderSheet:
1508
+ case CommandKey.Reset:
1509
+ case CommandKey.CreateAnnotation:
1510
+ case CommandKey.RemoveAnnotation:
1511
+ break;
1512
+ /*
1513
+ // both
1514
+ case CommandKey.Clear:
1515
+ if (command.area) {
1516
+ if (!command.area.start.sheet_id) {
1517
+ command.area.start.sheet_id = id;
1518
+ }
1519
+ }
1520
+ else {
1521
+ if (!command.sheet_id) {
1522
+ command.sheet_id = id;
1523
+ }
1524
+ }
1525
+ break;
1526
+ */
1527
+ // special cases
1528
+ case CommandKey.SortTable:
1529
+ case CommandKey.RemoveTable:
1530
+ if (!command.table.area.start.sheet_id) {
1531
+ command.table.area.start.sheet_id = id;
1532
+ }
1533
+ break;
1534
+ case CommandKey.AddConditionalFormat:
1535
+ if (!command.format.area.start.sheet_id) {
1536
+ command.format.area.start.sheet_id = id;
1537
+ }
1538
+ break;
1539
+ // field
1540
+ case CommandKey.ResizeRows:
1541
+ case CommandKey.ResizeColumns:
1542
+ case CommandKey.InsertColumns:
1543
+ case CommandKey.InsertRows:
1544
+ case CommandKey.Freeze:
1545
+ if (!command.sheet_id) {
1546
+ command.sheet_id = id;
1547
+ }
1548
+ break;
1549
+ // area: Area|Address (may be optional)
1550
+ case CommandKey.Clear:
1551
+ case CommandKey.SetNote:
1552
+ case CommandKey.SetLink:
1553
+ case CommandKey.Indent:
1554
+ case CommandKey.UpdateBorders:
1555
+ case CommandKey.MergeCells:
1556
+ case CommandKey.UnmergeCells:
1557
+ case CommandKey.DataValidation:
1558
+ case CommandKey.SetRange:
1559
+ case CommandKey.UpdateStyle:
1560
+ case CommandKey.SetName:
1561
+ case CommandKey.Select:
1562
+ case CommandKey.InsertTable:
1563
+ // note that remove conditional format could have a format
1564
+ // object (with an area) instead of an area. but in that case,
1565
+ // the format object must match an existing format, so it would
1566
+ // have to have a proper area. there's no case where we would
1567
+ // want to add it. so we only handle the area case.
1568
+ // eslint-disable-next-line no-fallthrough
1569
+ case CommandKey.RemoveConditionalFormat:
1570
+ if (command.area) {
1571
+ if (IsCellAddress(command.area)) {
1572
+ if (!command.area.sheet_id) {
1573
+ command.area.sheet_id = id;
1574
+ }
1575
+ }
1576
+ else {
1577
+ if (!command.area.start.sheet_id) {
1578
+ command.area.start.sheet_id = id;
1579
+ }
1580
+ }
1581
+ }
1582
+ break;
1583
+ default:
1584
+ // this is intended to check that we've handled all cases. if you
1585
+ // add additional commands and they're not handled, this should
1586
+ // raise a ts error.
1587
+ AssertNever(command);
1588
+ }
1589
+ }
1590
+ return commands;
1591
+ }
1592
+ TabColor(sheet, color) {
1593
+ this.ExecCommand({
1594
+ key: CommandKey.TabColor,
1595
+ sheet,
1596
+ color,
1597
+ });
1598
+ }
1599
+ /**
1600
+ * add sheet. data only.
1601
+ */
1602
+ AddSheetInternal(name = Sheet.default_sheet_name, insert_index = -1) {
1603
+ if (!this.options.add_tab) {
1604
+ console.warn('add tab option not set or false');
1605
+ return;
1606
+ }
1607
+ // validate name...
1608
+ while (this.model.sheets.list.some((test) => test.name === name)) {
1609
+ const match = name.match(/^(.*?)(\d+)$/);
1610
+ if (match) {
1611
+ name = match[1] + (Number(match[2]) + 1);
1612
+ }
1613
+ else {
1614
+ name = name + '2';
1615
+ }
1616
+ }
1617
+ // FIXME: structure event
1618
+ const sheet = Sheet.Blank(this.model.theme_style_properties, name);
1619
+ if (insert_index >= 0) {
1620
+ this.model.sheets.Splice(insert_index, 0, sheet);
1621
+ }
1622
+ else {
1623
+ this.model.sheets.Add(sheet);
1624
+ }
1625
+ // moved to ExecCmomand
1626
+ // if (this.tab_bar) { this.tab_bar.Update(); }
1627
+ return sheet.id;
1628
+ }
1629
+ /**
1630
+ * resolve sheet in a command that uses the SheetSelection interface;
1631
+ * that allows sheet selection by name, id or index.
1632
+ */
1633
+ ResolveSheet(command) {
1634
+ // NOTE: since you are using typeof here to check for undefined,
1635
+ // it seems like it would be efficient to use typeof to check
1636
+ // the actual type; hence merging "index" and "name" might be
1637
+ // more efficient than checking each one separately.
1638
+ if (typeof command.index !== 'undefined') {
1639
+ return this.model.sheets.list[command.index];
1640
+ }
1641
+ if (typeof command.name !== 'undefined') {
1642
+ return this.model.sheets.Find(command.name);
1643
+ }
1644
+ if (command.id) {
1645
+ return this.model.sheets.Find(command.id);
1646
+ }
1647
+ return undefined;
1648
+ }
1649
+ /**
1650
+ * find sheet matching sheet_id in area.start, or active sheet
1651
+ *
1652
+ * FIXME: should return undefined on !match
1653
+ * FIXME: should be in model, which should be a class
1654
+ */
1655
+ FindSheet(identifier) {
1656
+ if (identifier === undefined) {
1657
+ return this.active_sheet;
1658
+ }
1659
+ const id = typeof identifier === 'number' ? identifier : IsCellAddress(identifier) ? identifier.sheet_id : identifier.start.sheet_id;
1660
+ if (!id || id === this.active_sheet.id) {
1661
+ return this.active_sheet;
1662
+ }
1663
+ const sheet = this.model.sheets.Find(id);
1664
+ if (sheet) {
1665
+ return sheet;
1666
+ }
1667
+ /*
1668
+ for (const test of this.model.sheets) {
1669
+ if (test.id === id) {
1670
+ return test;
1671
+ }
1672
+ }
1673
+ */
1674
+ // FIXME: should return undefined here
1675
+ return this.active_sheet;
1676
+ }
1677
+ /**
1678
+ * this function now works for both rows and columns, and can handle
1679
+ * sheets other than the active sheet. it does assume that you only ever
1680
+ * add rows/columns on the active sheet, but since that's all parameterized
1681
+ * you could get it to work either way.
1682
+ *
1683
+ * in fact we should change the names of those parameters so it's a little
1684
+ * more generic.
1685
+ */
1686
+ PatchFormulasInternal(source, before_row, row_count, before_column, column_count, target_sheet_name, is_target) {
1687
+ const parsed = this.parser.Parse(source || '');
1688
+ let modified = false;
1689
+ // the sheet test is different for active sheet/non-active sheet.
1690
+ // on the active sheet, check for no name OR name === active sheet name.
1691
+ // on other sheets, check for name AND name === active sheet name.
1692
+ if (parsed.expression) {
1693
+ this.parser.Walk(parsed.expression, (element) => {
1694
+ if (element.type === 'range' || element.type === 'address') {
1695
+ // we can test if we need to modify a range or an address, but the
1696
+ // second address in a range can't be tested properly. so the solution
1697
+ // here is to just capture the addresses that need to be modified
1698
+ // from the range, and then not recurse (we should never get here
1699
+ // as an address in a range).
1700
+ const addresses = [];
1701
+ if (element.type === 'range') {
1702
+ // there's a problem: this breaks because the inner test fails when
1703
+ // this is TRUE... we may need to modify
1704
+ // recurse if (1) explicit name match; or (2) no name AND we are on the active sheet
1705
+ // return ((element.start.sheet && element.start.sheet.toLowerCase() === active_sheet_name) || (!element.start.sheet && active_sheet));
1706
+ if ((element.start.sheet && element.start.sheet.toLowerCase() === target_sheet_name) || (!element.start.sheet && is_target)) {
1707
+ addresses.push(element.start, element.end);
1708
+ }
1709
+ }
1710
+ else if (element.type === 'address') {
1711
+ if ((element.sheet && element.sheet.toLowerCase() === target_sheet_name) || (!element.sheet && is_target)) {
1712
+ addresses.push(element);
1713
+ }
1714
+ }
1715
+ // could switch the tests around? (referring to the count
1716
+ // tests, which switch on operation)
1717
+ for (const address of addresses) {
1718
+ if (row_count && address.row >= before_row) {
1719
+ if (row_count < 0 && address.row + row_count < before_row) {
1720
+ address.column = address.row = -1;
1721
+ }
1722
+ else {
1723
+ address.row += row_count;
1724
+ }
1725
+ modified = true;
1726
+ }
1727
+ if (column_count && address.column >= before_column) {
1728
+ if (column_count < 0 && address.column + column_count < before_column) {
1729
+ address.column = address.row = -1; // set as invalid (-1)
1730
+ }
1731
+ else {
1732
+ address.column += column_count;
1733
+ }
1734
+ modified = true;
1735
+ }
1736
+ }
1737
+ return false; // always explicit
1738
+ }
1739
+ return true; // recurse for everything else
1740
+ });
1741
+ if (modified) {
1742
+ return '=' + this.parser.Render(parsed.expression, { missing: '' });
1743
+ }
1744
+ }
1745
+ return undefined;
1746
+ }
1747
+ PatchExpressionSheetName(expression, old_name, name) {
1748
+ let modified = false;
1749
+ const parsed = this.parser.Parse(expression || '');
1750
+ if (parsed.expression) {
1751
+ this.parser.Walk(parsed.expression, (element) => {
1752
+ if (element.type === 'address') {
1753
+ if (element.sheet && element.sheet.toLowerCase() === old_name) {
1754
+ element.sheet = name;
1755
+ modified = true;
1756
+ }
1757
+ }
1758
+ return true; // continue walk
1759
+ });
1760
+ if (modified) {
1761
+ return '=' + this.parser.Render(parsed.expression, { missing: '' });
1762
+ }
1763
+ }
1764
+ return undefined;
1765
+ }
1766
+ /**
1767
+ * splitting this logic into a new function so we can reuse it
1768
+ * for invalidating broken references. generally we'll call this
1769
+ * on all sheets, but I wanted to leave the option open.
1770
+ *
1771
+ * @returns count of changes made. it's useful for the delete routine,
1772
+ * so we can force a recalc.
1773
+ */
1774
+ RenameSheetReferences(sheets, old_name, name) {
1775
+ let changes = 0;
1776
+ old_name = old_name.toLowerCase();
1777
+ for (const sheet of sheets) {
1778
+ // cells
1779
+ for (const cell of sheet.cells.Iterate()) {
1780
+ if (cell.ValueIsFormula()) {
1781
+ const updated = this.PatchExpressionSheetName(cell.value || '', old_name, name);
1782
+ if (updated) {
1783
+ cell.value = updated;
1784
+ changes++;
1785
+ }
1786
+ }
1787
+ }
1788
+ /*
1789
+ sheet.cells.IterateAll((cell: Cell) => {
1790
+ if (cell.ValueIsFormula()) {
1791
+ const updated = this.PatchExpressionSheetName(cell.value||'', old_name, name);
1792
+ if (updated) {
1793
+ cell.value = updated;
1794
+ changes++;
1795
+ }
1796
+ }
1797
+ });
1798
+ */
1799
+ // conditionals
1800
+ if (sheet.conditional_formats?.length) {
1801
+ for (const format of sheet.conditional_formats) {
1802
+ switch (format.type) {
1803
+ case 'cell-match':
1804
+ case 'expression':
1805
+ {
1806
+ const updated = this.PatchExpressionSheetName(format.expression, old_name, name);
1807
+ if (updated) {
1808
+ format.expression = updated;
1809
+ changes++;
1810
+ }
1811
+ }
1812
+ break;
1813
+ }
1814
+ }
1815
+ }
1816
+ // annotations
1817
+ for (const annotation of sheet.annotations) {
1818
+ if (annotation.data.formula) {
1819
+ const updated = this.PatchExpressionSheetName(annotation.data.formula, old_name, name);
1820
+ if (updated) {
1821
+ annotation.data.formula = updated;
1822
+ changes++;
1823
+ }
1824
+ }
1825
+ }
1826
+ }
1827
+ for (const element of this.model.connected_elements.values()) {
1828
+ if (element.formula) {
1829
+ const updated = this.PatchExpressionSheetName(element.formula, old_name, name);
1830
+ if (updated) {
1831
+ element.formula = updated;
1832
+ changes++;
1833
+ }
1834
+ }
1835
+ }
1836
+ return changes;
1837
+ }
1838
+ /**
1839
+ * these are all addative except for "none", which removes all borders.
1840
+ *
1841
+ * we no longer put borders into two cells at once (hurrah!). however
1842
+ * we still need to do some maintenance on the mirror cells -- because
1843
+ * if you apply a border to cell A1, then that should take precedence
1844
+ * over any border previously applied to cell A2.
1845
+ *
1846
+ * FIXME: is that right? perhaps we should just leave whatever the user
1847
+ * did -- with the exception of clearing, which should always mirror.
1848
+ *
1849
+ *
1850
+ * UPDATE: modifying function for use with ExecCommand. runs the style
1851
+ * updates and returns the affected area.
1852
+ *
1853
+ */
1854
+ ApplyBordersInternal(command) {
1855
+ const borders = command.borders;
1856
+ const width = (command.borders === BorderConstants.None)
1857
+ ? 0 : command.width;
1858
+ const area = new Area(command.area.start, command.area.end);
1859
+ const sheet = this.FindSheet(area);
1860
+ area.start.sheet_id = sheet.id; // ensure
1861
+ /*
1862
+ let sheet = this.active_sheet;
1863
+ if (command.area.start.sheet_id && command.area.start.sheet_id !== this.active_sheet.id) {
1864
+ for (const compare of this.model.sheets) {
1865
+ if (compare.id === command.area.start.sheet_id) {
1866
+ sheet = compare;
1867
+ break;
1868
+ }
1869
+ }
1870
+ }
1871
+ */
1872
+ const top = { border_top: width };
1873
+ const bottom = { border_bottom: width };
1874
+ const left = { border_left: width };
1875
+ const right = { border_right: width };
1876
+ const clear_top = { border_top: 0, border_top_fill: {} };
1877
+ const clear_bottom = { border_bottom: 0, border_bottom_fill: {} };
1878
+ const clear_left = { border_left: 0, border_left_fill: {} };
1879
+ const clear_right = { border_right: 0, border_right_fill: {} };
1880
+ // default to "none", which means "default"
1881
+ //if (!command.color) {
1882
+ // command.color = 'none';
1883
+ //}
1884
+ //if (typeof command.color !== 'undefined') {
1885
+ if (command.color) {
1886
+ // this is now an object so we need to clone it (might be faster to JSON->JSON)
1887
+ top.border_top_fill = { ...command.color };
1888
+ bottom.border_bottom_fill = { ...command.color };
1889
+ left.border_left_fill = { ...command.color };
1890
+ right.border_right_fill = { ...command.color };
1891
+ }
1892
+ else {
1893
+ // otherwise we should be sure to clear any color
1894
+ top.border_top_fill = {};
1895
+ bottom.border_bottom_fill = {};
1896
+ left.border_left_fill = {};
1897
+ right.border_right_fill = {};
1898
+ }
1899
+ // inside all/none
1900
+ if (borders === BorderConstants.None || borders === BorderConstants.All) {
1901
+ sheet.UpdateAreaStyle(area, {
1902
+ ...top, ...bottom, ...left, ...right,
1903
+ }, true);
1904
+ }
1905
+ // top
1906
+ if (borders === BorderConstants.Top || borders === BorderConstants.Outside) {
1907
+ if (!area.entire_column) {
1908
+ sheet.UpdateAreaStyle(area.top, { ...top }, true);
1909
+ }
1910
+ }
1911
+ // mirror top (CLEAR)
1912
+ if (borders === BorderConstants.None || borders === BorderConstants.All ||
1913
+ borders === BorderConstants.Outside || borders === BorderConstants.Top) {
1914
+ if (!area.entire_column) {
1915
+ if (area.start.row) {
1916
+ sheet.UpdateAreaStyle(new Area({ row: area.start.row - 1, column: area.start.column }, { row: area.start.row - 1, column: area.end.column }), { ...clear_bottom }, true);
1917
+ }
1918
+ }
1919
+ }
1920
+ // bottom
1921
+ if (borders === BorderConstants.Bottom || borders === BorderConstants.Outside) {
1922
+ if (!area.entire_column) {
1923
+ sheet.UpdateAreaStyle(area.bottom, { ...bottom }, true);
1924
+ }
1925
+ }
1926
+ // mirror bottom (CLEAR)
1927
+ if (borders === BorderConstants.None || borders === BorderConstants.All ||
1928
+ borders === BorderConstants.Outside || borders === BorderConstants.Bottom) {
1929
+ if (!area.entire_column) {
1930
+ sheet.UpdateAreaStyle(new Area({ row: area.end.row + 1, column: area.start.column }, { row: area.end.row + 1, column: area.end.column }), { ...clear_top }, true);
1931
+ }
1932
+ }
1933
+ // left
1934
+ if (borders === BorderConstants.Left || borders === BorderConstants.Outside) {
1935
+ if (!area.entire_row) {
1936
+ sheet.UpdateAreaStyle(area.left, { ...left }, true);
1937
+ }
1938
+ }
1939
+ // mirror left (CLEAR)
1940
+ if (borders === BorderConstants.None || borders === BorderConstants.All ||
1941
+ borders === BorderConstants.Outside || borders === BorderConstants.Left) {
1942
+ if (!area.entire_row) {
1943
+ if (area.start.column) {
1944
+ sheet.UpdateAreaStyle(new Area({ row: area.start.row, column: area.start.column - 1 }, { row: area.end.row, column: area.start.column - 1 }), { ...clear_right }, true);
1945
+ }
1946
+ }
1947
+ }
1948
+ // right
1949
+ if (borders === BorderConstants.Right || borders === BorderConstants.Outside) {
1950
+ if (!area.entire_row) {
1951
+ sheet.UpdateAreaStyle(area.right, { ...right }, true);
1952
+ }
1953
+ }
1954
+ // mirror right (CLEAR)
1955
+ if (borders === BorderConstants.None || borders === BorderConstants.All ||
1956
+ borders === BorderConstants.Outside || borders === BorderConstants.Right) {
1957
+ if (!area.entire_row) {
1958
+ sheet.UpdateAreaStyle(new Area({ row: area.start.row, column: area.end.column + 1 }, { row: area.end.row, column: area.end.column + 1 }), { ...clear_left }, true);
1959
+ }
1960
+ }
1961
+ /*
1962
+ // why is there not an expand method on area? (FIXME)
1963
+
1964
+ this.DelayedRender(false, new Area({
1965
+ row: Math.max(0, area.start.row - 1),
1966
+ column: Math.max(0, area.start.column - 1),
1967
+ }, {
1968
+ row: area.end.row + 1,
1969
+ column: area.end.column + 1,
1970
+ }));
1971
+
1972
+ // NOTE: we don't have to route through the sheet. we are the only client
1973
+ // (we republish). we can just publish directly.
1974
+
1975
+ this.grid_events.Publish({ type: 'style', area });
1976
+ */
1977
+ return Area.Bleed(area);
1978
+ /*
1979
+ return new Area(
1980
+ {
1981
+ row: Math.max(0, area.start.row - 1),
1982
+ column: Math.max(0, area.start.column - 1),
1983
+ }, {
1984
+ row: area.end.row + 1,
1985
+ column: area.end.column + 1,
1986
+ },
1987
+ );
1988
+ */
1989
+ }
1990
+ TranslateR1C1(address, value) {
1991
+ let transformed = false;
1992
+ const cached = this.parser.flags.r1c1;
1993
+ this.parser.flags.r1c1 = true; // set
1994
+ if (typeof value === 'string' && value[0] === '=') {
1995
+ const result = this.parser.Parse(value);
1996
+ if (result.expression) {
1997
+ this.parser.Walk(result.expression, unit => {
1998
+ if (unit.type === 'address' && unit.r1c1) {
1999
+ transformed = true;
2000
+ // translate...
2001
+ if (unit.offset_column) {
2002
+ unit.absolute_column = false;
2003
+ unit.column = unit.column + address.column;
2004
+ }
2005
+ else {
2006
+ unit.absolute_column = true;
2007
+ }
2008
+ if (unit.offset_row) {
2009
+ unit.absolute_row = false;
2010
+ unit.row = unit.row + address.row;
2011
+ }
2012
+ else {
2013
+ unit.absolute_row = true;
2014
+ }
2015
+ }
2016
+ return true;
2017
+ });
2018
+ if (transformed) {
2019
+ /*
2020
+ if (!this.flags.warned_r1c1) {
2021
+
2022
+ // 1-time warning
2023
+
2024
+ this.flags.warned_r1c1 = true;
2025
+ console.warn('NOTE: R1C1 support is experimental. the semantics may change in the future.');
2026
+ }
2027
+ */
2028
+ value = '=' + this.parser.Render(result.expression, { missing: '' });
2029
+ }
2030
+ }
2031
+ }
2032
+ this.parser.flags.r1c1 = cached; // reset
2033
+ return value;
2034
+ }
2035
+ ClearAreaInternal(area) {
2036
+ // updated to use sheet ID. not sure why this was still using
2037
+ // active sheet without checking ID.
2038
+ let sheet;
2039
+ if (area.start.sheet_id) {
2040
+ sheet = this.model.sheets.Find(area.start.sheet_id);
2041
+ }
2042
+ else {
2043
+ sheet = this.active_sheet;
2044
+ }
2045
+ if (!sheet) {
2046
+ console.warn(`can't resolve sheet in ClearAreaInternal`);
2047
+ return;
2048
+ }
2049
+ // let error = false;
2050
+ area = sheet.RealArea(area); // collapse
2051
+ for (const cell of sheet.cells.Iterate(area)) {
2052
+ if (cell.area && !area.ContainsArea(cell.area)) {
2053
+ this.Error(ErrorCode.array);
2054
+ return;
2055
+ }
2056
+ }
2057
+ /*
2058
+ sheet.cells.Apply(area, (cell) => {
2059
+ if (cell.area && !area.ContainsArea(cell.area)) {
2060
+ // throw new Error('can\'t change part of an array');
2061
+ error = true;
2062
+ }
2063
+ });
2064
+ */
2065
+ // if the area completely encloses a table, delete the table
2066
+ const table_keys = this.model.tables.keys();
2067
+ for (const key of table_keys) {
2068
+ const table = this.model.tables.get(key);
2069
+ if (table && table.area.start.sheet_id === sheet.id) {
2070
+ const table_area = new Area(table.area.start, table.area.end);
2071
+ if (area.ContainsArea(table_area)) {
2072
+ for (let row = table_area.start.row; row <= table_area.end.row; row++) {
2073
+ for (let column = table_area.start.column; column <= table.area.end.column; column++) {
2074
+ const cell = sheet.cells.GetCell({ row, column }, false);
2075
+ if (cell) {
2076
+ cell.table = undefined;
2077
+ }
2078
+ }
2079
+ }
2080
+ this.model.tables.delete(key);
2081
+ }
2082
+ }
2083
+ }
2084
+ /*
2085
+ if (error) {
2086
+ this.Error(ErrorCode.array); // `You can't change part of an array.`
2087
+ }
2088
+ else {
2089
+ sheet.ClearArea(area);
2090
+ }
2091
+ */
2092
+ sheet.ClearArea(area);
2093
+ }
2094
+ /**
2095
+ * send an error message. subscriber can figure out how to communicate it
2096
+ * to users.
2097
+ *
2098
+ * dropping strings, now we only allow error constants (via enum)
2099
+ *
2100
+ * @param message
2101
+ */
2102
+ Error(message) {
2103
+ /*
2104
+ console.info('Error', message);
2105
+ if (typeof message === 'string') {
2106
+ this.grid_events.Publish({
2107
+ type: 'error',
2108
+ message,
2109
+ });
2110
+ }
2111
+ else {
2112
+ this.grid_events.Publish({
2113
+ type: 'error',
2114
+ code: message,
2115
+ });
2116
+ }
2117
+ */
2118
+ this.grid_events.Publish({
2119
+ type: 'error',
2120
+ code: message,
2121
+ });
2122
+ }
2123
+ /**
2124
+ * this breaks (or doesn't work) if the add_tab option is false; that's
2125
+ * fine, although we might want to make a distinction between UI add-tab
2126
+ * and API add-tab. And allow it from the API.
2127
+ *
2128
+ * @param command
2129
+ * @returns
2130
+ */
2131
+ DuplicateSheetInternal(command) {
2132
+ if (!this.options.add_tab) {
2133
+ console.warn('add tab option not set or false');
2134
+ return;
2135
+ }
2136
+ const source = this.ResolveSheet(command);
2137
+ const next_id = this.model.sheets.list.reduce((id, sheet) => Math.max(id, sheet.id), 0) + 1;
2138
+ let insert_index = -1;
2139
+ for (let i = 0; i < this.model.sheets.length; i++) {
2140
+ if (this.model.sheets.list[i] === source) {
2141
+ insert_index = i + 1;
2142
+ }
2143
+ }
2144
+ if (!source || insert_index < 0) {
2145
+ throw new Error('source sheet not found');
2146
+ }
2147
+ // explicit insert index
2148
+ if (typeof command.insert_before === 'number') {
2149
+ insert_index = command.insert_before;
2150
+ }
2151
+ else if (typeof command.insert_before === 'string') {
2152
+ const lc = command.insert_before.toLowerCase();
2153
+ for (let i = 0; i < this.model.sheets.length; i++) {
2154
+ if (this.model.sheets.list[i].name.toLowerCase() === lc) {
2155
+ insert_index = i;
2156
+ break;
2157
+ }
2158
+ }
2159
+ }
2160
+ const options = {
2161
+ rendered_values: true,
2162
+ };
2163
+ const clone = Sheet.FromJSON(source.toJSON(options), this.model.theme_style_properties);
2164
+ let name = command.new_name || source.name;
2165
+ while (this.model.sheets.list.some((test) => test.name === name)) {
2166
+ const match = name.match(/^(.*?)(\d+)$/);
2167
+ if (match) {
2168
+ name = match[1] + (Number(match[2]) + 1);
2169
+ }
2170
+ else {
2171
+ name = name + '2';
2172
+ }
2173
+ }
2174
+ clone.name = name;
2175
+ clone.id = next_id;
2176
+ // console.info('CLONE', clone.id, clone);
2177
+ this.model.sheets.Splice(insert_index, 0, clone);
2178
+ // if (this.tab_bar) { this.tab_bar.Update(); }
2179
+ return clone.id;
2180
+ }
2181
+ /**
2182
+ * this is the callback method for the command-log select command
2183
+ * (which is not widely used). it does nothing. the specialization
2184
+ * should do something.
2185
+ *
2186
+ * @param command
2187
+ */
2188
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2189
+ SelectInternal(command) {
2190
+ // does nothing
2191
+ }
2192
+ FreezeInternal(command) {
2193
+ const sheet = this.FindSheet(command.sheet_id || this.active_sheet.id);
2194
+ sheet.freeze.rows = command.rows;
2195
+ sheet.freeze.columns = command.columns;
2196
+ }
2197
+ /**
2198
+ * patch an expression for insert/delete row/column operations.
2199
+ * FIXME: should move, maybe to parser? (...)
2200
+ *
2201
+ * NOTE: did I write this twice? we only need one. check which one is better.
2202
+ * @see PatchFormulasInternal
2203
+ *
2204
+ * @returns the updated expression, or `undefined` if no changes were made.
2205
+ */
2206
+ PatchExpression(expression, options) {
2207
+ let count = 0;
2208
+ const parse_result = this.parser.Parse(expression);
2209
+ if (parse_result.expression) {
2210
+ this.parser.Walk(parse_result.expression, unit => {
2211
+ if (unit.type === 'range') {
2212
+ if (!unit.start.sheet || (unit.start.sheet.toLowerCase() === options.sheet.name.toLowerCase())) {
2213
+ const updated = Area.PatchArea(unit, options);
2214
+ if (updated) {
2215
+ unit.start.row = updated.start.row;
2216
+ unit.start.column = updated.start.column;
2217
+ unit.end.row = updated.end.row;
2218
+ unit.end.column = updated.end.column;
2219
+ }
2220
+ else {
2221
+ // FIXME: maybe have options for this? we don't really have a
2222
+ // good way to replace nodes atm
2223
+ unit.start.row = unit.end.row = unit.start.column = unit.end.column = -1;
2224
+ }
2225
+ }
2226
+ count++;
2227
+ return false;
2228
+ }
2229
+ else if (unit.type === 'address') {
2230
+ const updated = Area.PatchArea({ start: unit, end: unit }, options);
2231
+ if (updated) {
2232
+ unit.row = updated.start.row;
2233
+ unit.column = updated.start.column;
2234
+ }
2235
+ else {
2236
+ // see above
2237
+ unit.row = unit.column = -1;
2238
+ }
2239
+ count++;
2240
+ return false;
2241
+ }
2242
+ return true;
2243
+ });
2244
+ if (count) {
2245
+ const rendered = this.parser.Render(parse_result.expression, {
2246
+ missing: '',
2247
+ });
2248
+ // console.info("FROM", expression, "TO", rendered);
2249
+ return rendered;
2250
+ }
2251
+ }
2252
+ return undefined;
2253
+ }
2254
+ /**
2255
+ * patch sheet conditionals for insert/delete row/column operations.
2256
+ * some of them may be deleted.
2257
+ *
2258
+ * UPDATE: using this routine to also patch data validations
2259
+ */
2260
+ PatchConditionalsAndValidations(options) {
2261
+ if (options.sheet.data_validation.length) {
2262
+ const delete_list = new Set();
2263
+ for (const validation of options.sheet.data_validation) {
2264
+ const targets = [];
2265
+ for (const area of validation.target) {
2266
+ const updated = Area.PatchArea(area, options);
2267
+ if (updated) {
2268
+ targets.push(updated);
2269
+ }
2270
+ }
2271
+ if (targets.length > 0) {
2272
+ validation.target = targets;
2273
+ // format the range, if necessary
2274
+ if (validation.type === 'range') {
2275
+ if (validation.area.start.sheet_id === options.sheet.id) {
2276
+ const updated = Area.PatchArea(validation.area, options);
2277
+ if (updated) {
2278
+ validation.area = updated;
2279
+ }
2280
+ else {
2281
+ delete_list.add(validation);
2282
+ }
2283
+ }
2284
+ }
2285
+ }
2286
+ else {
2287
+ delete_list.add(validation);
2288
+ }
2289
+ }
2290
+ if (delete_list.size) {
2291
+ options.sheet.data_validation = options.sheet.data_validation.filter(test => !delete_list.has(test));
2292
+ }
2293
+ }
2294
+ if (options.sheet.conditional_formats?.length) {
2295
+ const delete_list = new Set();
2296
+ for (const format of options.sheet.conditional_formats) {
2297
+ // first adjust the format area
2298
+ const updated = Area.PatchArea(format.area, options);
2299
+ if (updated) {
2300
+ format.area = JSON.parse(JSON.stringify(updated));
2301
+ }
2302
+ else {
2303
+ delete_list.add(format);
2304
+ continue; // don't bother with formula
2305
+ }
2306
+ // next update the formula, if necessary. what do we do if the
2307
+ // area has disappeared? should be a #REF error, not sure we
2308
+ // can encode that properly
2309
+ switch (format.type) {
2310
+ case 'expression':
2311
+ case 'cell-match':
2312
+ {
2313
+ const updated = this.PatchExpression(format.expression, options);
2314
+ if (updated) {
2315
+ format.expression = updated;
2316
+ }
2317
+ }
2318
+ break;
2319
+ }
2320
+ }
2321
+ if (delete_list.size) {
2322
+ options.sheet.conditional_formats = options.sheet.conditional_formats.filter(test => !delete_list.has(test));
2323
+ }
2324
+ }
2325
+ }
2326
+ /**
2327
+ * FIXME: should be API method
2328
+ * FIXME: need to handle annotations that are address-based
2329
+ *
2330
+ * @see InsertColumns for inline comments
2331
+ */
2332
+ InsertRowsInternal(command) {
2333
+ const target_sheet = this.FindSheet(command.sheet_id);
2334
+ if (command.count === Infinity) {
2335
+ command.count = 1; // ?
2336
+ }
2337
+ else if (command.count === -Infinity) {
2338
+ command.count = -target_sheet.rows; // delete all
2339
+ }
2340
+ if (!target_sheet.InsertRows(command.before_row, command.count)) {
2341
+ // this.Error(`You can't change part of an array.`);
2342
+ this.Error(ErrorCode.array);
2343
+ return { error: true };
2344
+ }
2345
+ // conditionals
2346
+ this.PatchConditionalsAndValidations({
2347
+ sheet: target_sheet,
2348
+ before_column: 0,
2349
+ column_count: 0,
2350
+ before_row: command.before_row,
2351
+ row_count: command.count
2352
+ });
2353
+ // connected elements
2354
+ for (const external of this.model.connected_elements.values()) {
2355
+ if (external.formula) {
2356
+ const modified = this.PatchFormulasInternal(external.formula, command.before_row, command.count, 0, 0, target_sheet.name.toLowerCase(), false);
2357
+ if (modified) {
2358
+ external.formula = modified;
2359
+ }
2360
+ }
2361
+ }
2362
+ // see InsertColumnsInternal re: tables. rows are less complicated,
2363
+ // except that if you delete the header row we want to remove the
2364
+ // table entirely.
2365
+ const tables = Array.from(this.model.tables.values());
2366
+ for (const table of tables) {
2367
+ if (table.area.start.sheet_id === command.sheet_id) {
2368
+ if (command.count > 0) {
2369
+ if (command.before_row <= table.area.start.row) {
2370
+ // shift the table down
2371
+ //console.info("shift table down");
2372
+ table.area.start.row += command.count;
2373
+ table.area.end.row += command.count;
2374
+ }
2375
+ else if (command.before_row <= table.area.end.row) {
2376
+ // insert rows. we need to add references to
2377
+ // cells that have been inserted.
2378
+ // console.info("insert table rows");
2379
+ table.area.end.row += command.count;
2380
+ for (let row = table.area.start.row; row <= table.area.end.row; row++) {
2381
+ for (let column = table.area.start.column; column <= table.area.end.column; column++) {
2382
+ const cell = target_sheet.CellData({ row, column });
2383
+ cell.table = table;
2384
+ }
2385
+ }
2386
+ }
2387
+ }
2388
+ else {
2389
+ if (command.before_row <= table.area.start.row) {
2390
+ if (command.before_row - command.count <= table.area.start.row) {
2391
+ // shift table up
2392
+ table.area.start.row += command.count;
2393
+ table.area.end.row += command.count;
2394
+ }
2395
+ else if (command.before_row - command.count >= table.area.end.row) {
2396
+ // remove the entire table
2397
+ this.model.tables.delete(table.name.toLowerCase());
2398
+ }
2399
+ else {
2400
+ // assuming this will remove the header row, drop the table
2401
+ // altogether. the alternative is to just not let you remove
2402
+ // this row. but that should be handled before you get here;
2403
+ // if you get here, and you want to delete the row, then the
2404
+ // table will go.
2405
+ this.model.tables.delete(table.name.toLowerCase());
2406
+ for (let row = command.before_row; row <= table.area.end.row; row++) {
2407
+ for (let column = table.area.start.column; column <= table.area.end.column; column++) {
2408
+ const cell = target_sheet.CellData({ row, column });
2409
+ if (cell.table === table) {
2410
+ cell.table = undefined;
2411
+ }
2412
+ }
2413
+ }
2414
+ }
2415
+ }
2416
+ else if (command.before_row <= table.area.end.row) {
2417
+ // remove table rows from the end. cap.
2418
+ // we may be removing the totals row -- in that case, update the table to reflect.
2419
+ if (command.before_row - command.count > table.area.end.row) {
2420
+ table.totals_row = false;
2421
+ }
2422
+ table.area.end.row = Math.max(0, table.area.end.row + command.count, command.before_row - 1);
2423
+ }
2424
+ }
2425
+ }
2426
+ }
2427
+ this.model.named.PatchNamedRanges(target_sheet.id, 0, 0, command.before_row, command.count);
2428
+ const target_sheet_name = target_sheet.name.toLowerCase();
2429
+ for (const sheet of this.model.sheets.list) {
2430
+ const is_target = sheet === target_sheet;
2431
+ for (const cell of sheet.cells.Iterate()) {
2432
+ if (cell.ValueIsFormula()) {
2433
+ const modified = this.PatchFormulasInternal(cell.value || '', command.before_row, command.count, 0, 0, target_sheet_name, is_target);
2434
+ if (modified) {
2435
+ cell.value = modified;
2436
+ }
2437
+ }
2438
+ }
2439
+ /*
2440
+ sheet.cells.IterateAll((cell: Cell) => {
2441
+ if (cell.ValueIsFormula()) {
2442
+ const modified = this.PatchFormulasInternal(cell.value || '',
2443
+ command.before_row, command.count, 0, 0,
2444
+ target_sheet_name, is_target);
2445
+ if (modified) {
2446
+ cell.value = modified;
2447
+ }
2448
+ }
2449
+ });
2450
+ */
2451
+ for (const annotation of sheet.annotations) {
2452
+ if (annotation.data.formula) {
2453
+ const modified = this.PatchFormulasInternal(annotation.data.formula || '', command.before_row, command.count, 0, 0, target_sheet_name, is_target);
2454
+ if (modified) {
2455
+ annotation.data.formula = modified;
2456
+ }
2457
+ }
2458
+ }
2459
+ }
2460
+ // annotations
2461
+ const update_annotations_list = [];
2462
+ const resize_annotations_list = [];
2463
+ const delete_annotations_list = [];
2464
+ if (command.count > 0) { // insert
2465
+ const first = command.before_row;
2466
+ for (const annotation of target_sheet.annotations) {
2467
+ if (annotation.data.layout) {
2468
+ const [start, end, endy] = [
2469
+ annotation.data.layout.tl.address.row,
2470
+ annotation.data.layout.br.address.row,
2471
+ annotation.data.layout.br.offset.y,
2472
+ ];
2473
+ if (first <= start) {
2474
+ // start case 1: starts above the annotation (including exactly at the top)
2475
+ // shift
2476
+ annotation.data.layout.tl.address.row += command.count;
2477
+ annotation.data.layout.br.address.row += command.count;
2478
+ }
2479
+ else if (first < end || first === end && endy > 0) {
2480
+ // start case 2: starts in the annotation, omitting the first row
2481
+ annotation.data.layout.br.address.row += command.count;
2482
+ // size changing
2483
+ resize_annotations_list.push(annotation);
2484
+ }
2485
+ else {
2486
+ // do nothing
2487
+ continue;
2488
+ }
2489
+ update_annotations_list.push(annotation);
2490
+ }
2491
+ }
2492
+ }
2493
+ else if (command.count < 0) { // delete
2494
+ // first and last column deleted
2495
+ const first = command.before_row;
2496
+ const last = command.before_row - command.count - 1;
2497
+ for (const annotation of target_sheet.annotations) {
2498
+ if (annotation.data.layout) {
2499
+ // start and end row of the annotation. recall that in
2500
+ // this layout, the annotation may extend into the (first,last)
2501
+ // row but not beyond it. the offset is _within_ the row.
2502
+ const [start, end, endy] = [
2503
+ annotation.data.layout.tl.address.row,
2504
+ annotation.data.layout.br.address.row,
2505
+ annotation.data.layout.br.offset.y,
2506
+ ];
2507
+ if (first <= start) {
2508
+ // start case 1: starts above the annotation (including exactly at the top)
2509
+ if (last < start) {
2510
+ // end case 1: ends before the annotation
2511
+ // shift
2512
+ annotation.data.layout.tl.address.row += command.count;
2513
+ annotation.data.layout.br.address.row += command.count;
2514
+ }
2515
+ else if (last < end - 1 || (last === end - 1 && endy > 0)) {
2516
+ // end case 2: ends before the end of the annotation
2517
+ // shift + cut
2518
+ annotation.data.layout.tl.address.row = first;
2519
+ annotation.data.layout.tl.offset.y = 0;
2520
+ annotation.data.layout.br.address.row += command.count;
2521
+ // size changing
2522
+ resize_annotations_list.push(annotation);
2523
+ }
2524
+ else {
2525
+ // end case 3: ends after the annotation
2526
+ // drop the annotation
2527
+ delete_annotations_list.push(annotation);
2528
+ continue;
2529
+ }
2530
+ }
2531
+ else if (first < end || first === end && endy > 0) {
2532
+ // start case 2: starts in the annotation, omitting the first row
2533
+ if (last < end - 1 || (last === end - 1 && endy > 0)) {
2534
+ // end case 2: ends before the end of the annotation
2535
+ // shorten
2536
+ annotation.data.layout.br.address.row += command.count;
2537
+ // size changing
2538
+ resize_annotations_list.push(annotation);
2539
+ }
2540
+ else {
2541
+ // end case 3: ends after the annotation
2542
+ // clip
2543
+ annotation.data.layout.br.address.row = first;
2544
+ annotation.data.layout.br.offset.y = 0;
2545
+ // size changing
2546
+ resize_annotations_list.push(annotation);
2547
+ }
2548
+ }
2549
+ else {
2550
+ // start case 3: starts after the annotation
2551
+ // do nothing
2552
+ continue;
2553
+ }
2554
+ update_annotations_list.push(annotation);
2555
+ }
2556
+ }
2557
+ }
2558
+ for (const annotation of delete_annotations_list) {
2559
+ target_sheet.annotations = target_sheet.annotations.filter(test => test !== annotation);
2560
+ }
2561
+ return {
2562
+ update_annotations_list,
2563
+ resize_annotations_list,
2564
+ delete_annotations_list,
2565
+ };
2566
+ }
2567
+ /**
2568
+ *
2569
+ */
2570
+ InsertColumnsInternal(command) {
2571
+ const target_sheet = this.FindSheet(command.sheet_id);
2572
+ // it seems like we never get an insert infinity. not sure why,
2573
+ // but the UI is blocking that. we should handle it anyway jic
2574
+ if (command.count === Infinity) {
2575
+ command.count = 1; // ?
2576
+ }
2577
+ else if (command.count === -Infinity) {
2578
+ command.count = -target_sheet.columns; // delete all
2579
+ }
2580
+ // FIXME: we need to get this error out earlier. before this call,
2581
+ // in the call that generates the insert event. otherwise if we
2582
+ // have remotes, everyone will see the error -- we only want the
2583
+ // actual actor to see the error.
2584
+ if (!target_sheet.InsertColumns(command.before_column, command.count)) {
2585
+ // this.Error(`You can't change part of an array.`);
2586
+ this.Error(ErrorCode.array);
2587
+ return { error: true };
2588
+ }
2589
+ // conditionals
2590
+ this.PatchConditionalsAndValidations({
2591
+ sheet: target_sheet,
2592
+ before_column: command.before_column,
2593
+ column_count: command.count,
2594
+ before_row: 0,
2595
+ row_count: 0
2596
+ });
2597
+ // connected elements
2598
+ for (const element of this.model.connected_elements.values()) {
2599
+ if (element.formula) {
2600
+ const modified = this.PatchFormulasInternal(element.formula, 0, 0, command.before_column, command.count, target_sheet.name.toLowerCase(), false);
2601
+ if (modified) {
2602
+ element.formula = modified;
2603
+ }
2604
+ }
2605
+ }
2606
+ // patch tables. we removed this from the sheet routine entirely,
2607
+ // we need to rebuild any affected tables now.
2608
+ // NOTE: we may drop tables, so we can't use a live iterator. or
2609
+ // is the iterator precomputed? not sure. let's flatten immediately jic.
2610
+ const tables = Array.from(this.model.tables.values());
2611
+ for (const table of tables) {
2612
+ if (table.area.start.sheet_id === command.sheet_id) {
2613
+ if (command.count > 0) {
2614
+ if (command.before_column <= table.area.start.column) {
2615
+ // shift the table to the right. update the table reference,
2616
+ // we can skip updating headers as the columns haven't changed.
2617
+ // console.info("shift table right");
2618
+ table.area.start.column += command.count;
2619
+ table.area.end.column += command.count;
2620
+ }
2621
+ else if (command.before_column <= table.area.end.column) {
2622
+ // insert columns -- we need to add references to new
2623
+ // cells, and update headers.
2624
+ // console.info("insert table columns");
2625
+ table.area.end.column += command.count;
2626
+ for (let row = table.area.start.row; row <= table.area.end.row; row++) {
2627
+ for (let column = table.area.start.column; column <= table.area.end.column; column++) {
2628
+ const cell = target_sheet.CellData({ row, column });
2629
+ cell.table = table;
2630
+ }
2631
+ }
2632
+ this.UpdateTableColumns(table);
2633
+ }
2634
+ }
2635
+ else {
2636
+ if (command.before_column <= table.area.start.column) {
2637
+ if (command.before_column - command.count <= table.area.start.column) {
2638
+ // shift table left. update the table reference, we can skip headers.
2639
+ // console.info("shift table left");
2640
+ table.area.start.column += command.count;
2641
+ table.area.end.column += command.count;
2642
+ }
2643
+ else if (command.before_column - command.count >= table.area.end.column) {
2644
+ // remove entire table. cells are already removed, we can just
2645
+ // drop the table from the model.
2646
+ // console.info("remove table");
2647
+ this.model.tables.delete(table.name.toLowerCase());
2648
+ }
2649
+ else {
2650
+ // shift to the left, then remove table columns. cells are
2651
+ // already removed, so we don't need to touch cells; just
2652
+ // update the reference and column headers.
2653
+ // console.info("remove table columns (1)");
2654
+ table.area.start.column = command.before_column;
2655
+ table.area.end.column += command.count;
2656
+ this.UpdateTableColumns(table);
2657
+ }
2658
+ }
2659
+ else if (command.before_column <= table.area.end.column) {
2660
+ // remove table columns. as above. cap.
2661
+ // console.info("remove table columns (2)");
2662
+ table.area.end.column = Math.max(0, table.area.end.column + command.count, command.before_column - 1);
2663
+ this.UpdateTableColumns(table);
2664
+ }
2665
+ }
2666
+ }
2667
+ }
2668
+ this.model.named.PatchNamedRanges(target_sheet.id, command.before_column, command.count, 0, 0);
2669
+ // FIXME: we need an event here?
2670
+ // A: caller sends a "structure" event after this call. that doesn't include
2671
+ // affected areas, though. need to think about whether structure event
2672
+ // triggers a recalc (probably should). we could track whether we've made
2673
+ // any modifications (and maybe also whether we now have any invalid
2674
+ // references)
2675
+ // patch all sheets
2676
+ // you know we have a calculator that has backward-and-forward references.
2677
+ // we could theoretically ask the calculator what needs to be changed.
2678
+ //
2679
+ // for the most part, we try to maintain separation between the display
2680
+ // (this) and the calculator. we could ask, but this isn't terrible and
2681
+ // helps maintain that separation.
2682
+ const target_sheet_name = target_sheet.name.toLowerCase();
2683
+ for (const sheet of this.model.sheets.list) {
2684
+ const is_target = sheet === target_sheet;
2685
+ for (const cell of sheet.cells.Iterate()) {
2686
+ if (cell.ValueIsFormula()) {
2687
+ const modified = this.PatchFormulasInternal(cell.value || '', 0, 0, command.before_column, command.count, target_sheet_name, is_target);
2688
+ if (modified) {
2689
+ cell.value = modified;
2690
+ }
2691
+ }
2692
+ }
2693
+ /*
2694
+ sheet.cells.IterateAll((cell: Cell) => {
2695
+ if (cell.ValueIsFormula()) {
2696
+ const modified = this.PatchFormulasInternal(cell.value || '', 0, 0,
2697
+ command.before_column, command.count,
2698
+ target_sheet_name, is_target);
2699
+ if (modified) {
2700
+ cell.value = modified;
2701
+ }
2702
+ }
2703
+ });
2704
+ */
2705
+ for (const annotation of sheet.annotations) {
2706
+ if (annotation.data.formula) {
2707
+ const modified = this.PatchFormulasInternal(annotation.data.formula, 0, 0, command.before_column, command.count, target_sheet_name, is_target);
2708
+ if (modified) {
2709
+ annotation.data.formula = modified;
2710
+ }
2711
+ }
2712
+ }
2713
+ }
2714
+ // annotations
2715
+ const update_annotations_list = [];
2716
+ const resize_annotations_list = [];
2717
+ const delete_annotations_list = [];
2718
+ if (command.count > 0) { // insert
2719
+ const first = command.before_column;
2720
+ for (const annotation of target_sheet.annotations) {
2721
+ if (annotation.data.layout) {
2722
+ const [start, end, endx] = [
2723
+ annotation.data.layout.tl.address.column,
2724
+ annotation.data.layout.br.address.column,
2725
+ annotation.data.layout.br.offset.x,
2726
+ ];
2727
+ if (first <= start) {
2728
+ // start case 1: starts to the left of the annotation (including exactly at the left)
2729
+ // shift
2730
+ annotation.data.layout.tl.address.column += command.count;
2731
+ annotation.data.layout.br.address.column += command.count;
2732
+ }
2733
+ else if (first < end || first === end && endx > 0) {
2734
+ // start case 2: starts in the annotation, omitting the first column
2735
+ annotation.data.layout.br.address.column += command.count;
2736
+ // size changing
2737
+ resize_annotations_list.push(annotation);
2738
+ }
2739
+ else {
2740
+ // do nothing
2741
+ continue;
2742
+ }
2743
+ update_annotations_list.push(annotation);
2744
+ }
2745
+ }
2746
+ }
2747
+ else if (command.count < 0) { // delete
2748
+ // first and last column deleted
2749
+ const first = command.before_column;
2750
+ const last = command.before_column - command.count - 1;
2751
+ for (const annotation of target_sheet.annotations) {
2752
+ if (annotation.data.layout) {
2753
+ // start and end column of the annotation. recall that in
2754
+ // this layout, the annotation may extend into the (first,last)
2755
+ // column but not beyond it. the offset is _within_ the column.
2756
+ const [start, end, endx] = [
2757
+ annotation.data.layout.tl.address.column,
2758
+ annotation.data.layout.br.address.column,
2759
+ annotation.data.layout.br.offset.x,
2760
+ ];
2761
+ if (first <= start) {
2762
+ // start case 1: starts to the left of the annotation (including exactly at the left)
2763
+ if (last < start) {
2764
+ // end case 1: ends before the annotation
2765
+ // shift
2766
+ annotation.data.layout.tl.address.column += command.count;
2767
+ annotation.data.layout.br.address.column += command.count;
2768
+ }
2769
+ else if (last < end - 1 || (last === end - 1 && endx > 0)) {
2770
+ // end case 2: ends before the end of the annotation
2771
+ // shift + cut
2772
+ annotation.data.layout.tl.address.column = first;
2773
+ annotation.data.layout.tl.offset.x = 0;
2774
+ annotation.data.layout.br.address.column += command.count;
2775
+ // size changing
2776
+ resize_annotations_list.push(annotation);
2777
+ }
2778
+ else {
2779
+ // end case 3: ends after the annotation
2780
+ // drop the annotation
2781
+ delete_annotations_list.push(annotation);
2782
+ continue;
2783
+ }
2784
+ }
2785
+ else if (first < end || first === end && endx > 0) {
2786
+ // start case 2: starts in the annotation, omitting the first column
2787
+ if (last < end - 1 || (last === end - 1 && endx > 0)) {
2788
+ // end case 2: ends before the end of the annotation
2789
+ // shorten
2790
+ annotation.data.layout.br.address.column += command.count;
2791
+ // size changing
2792
+ resize_annotations_list.push(annotation);
2793
+ }
2794
+ else {
2795
+ // end case 3: ends after the annotation
2796
+ // clip
2797
+ annotation.data.layout.br.address.column = first;
2798
+ annotation.data.layout.br.offset.x = 0;
2799
+ // size changing
2800
+ resize_annotations_list.push(annotation);
2801
+ }
2802
+ }
2803
+ else {
2804
+ // start case 3: starts after the annotation
2805
+ // do nothing
2806
+ continue;
2807
+ }
2808
+ update_annotations_list.push(annotation);
2809
+ }
2810
+ }
2811
+ }
2812
+ for (const annotation of delete_annotations_list) {
2813
+ target_sheet.annotations = target_sheet.annotations.filter(test => test !== annotation);
2814
+ }
2815
+ return {
2816
+ update_annotations_list,
2817
+ resize_annotations_list,
2818
+ delete_annotations_list,
2819
+ };
2820
+ }
2821
+ //////////////////////////////////////////////////////////////////////////////
2822
+ /**
2823
+ * pass all data/style/structure operations through a command mechanism.
2824
+ * this method should optimally act as a dispatcher, so try to minimize
2825
+ * inline code in favor of method calls.
2826
+ *
2827
+ * [NOTE: don't go crazy with that, some simple operations can be inlined]
2828
+ *
2829
+ * NOTE: working on coediting. we will need to handle different sheets.
2830
+ * going to work one command at a time...
2831
+ *
2832
+ * @param queue -- push on the command log. this is default true so it
2833
+ * doesn't change existing behavior, but you can turn it off if the message
2834
+ * comes from a remote queue.
2835
+ *
2836
+ */
2837
+ ExecCommand(commands, queue = true) {
2838
+ // FIXME: support ephemeral commands (...)
2839
+ // data and style events were triggered by the areas being set.
2840
+ // we are not necessarily setting them for offsheet changes, so
2841
+ // we need an explicit flag. this should be logically OR'ed with
2842
+ // the area existing (for purposes of sending an event).
2843
+ // all flags/areas moved to this struct
2844
+ const flags = {
2845
+ pending: [],
2846
+ };
2847
+ const events = [];
2848
+ // should we normalize always, or only if we're queueing?
2849
+ // it seems like it's useful here, then we can be a little
2850
+ // sloppier in the actual handlers. after normalizing, any
2851
+ // command that has an address/area (or sheet ID parameter)
2852
+ // will have an explicit sheet ID.
2853
+ commands = this.NormalizeCommands(commands);
2854
+ // FIXME: we should queue later, so we can remove any commands
2855
+ // that fail... throw errors, and so on
2856
+ if (queue) {
2857
+ this.command_log.Publish({ command: commands, timestamp: new Date().getTime() });
2858
+ }
2859
+ for (const command of commands) {
2860
+ // console.log(CommandKey[command.key], JSON.stringify(command));
2861
+ switch (command.key) {
2862
+ case CommandKey.Reset:
2863
+ // not sure how well this fits in with the command queue. it
2864
+ // doesn't look like it sends any events, so what's the point?
2865
+ // just to get a command log event?
2866
+ // the problem is that load doesn't run through the queue, so
2867
+ // even if you did a reset -> load we'd just get the reset part.
2868
+ // ...
2869
+ // OK, actually this is used in the CSV import routine. we need
2870
+ // to support it until we get rid of that (it needs to move).
2871
+ this.ResetInternal();
2872
+ break;
2873
+ case CommandKey.Clear:
2874
+ if (command.area) {
2875
+ const area = new Area(command.area.start, command.area.end);
2876
+ this.ClearAreaInternal(area);
2877
+ flags.data_area = Area.Join(area, flags.data_area);
2878
+ flags.formula = true;
2879
+ }
2880
+ break;
2881
+ case CommandKey.Select:
2882
+ // nobody (except one routine) is using commands for selection.
2883
+ // not sure why or why not, or if that's a problem. (it's definitely
2884
+ // a problem if we are recording the log for playback)
2885
+ // ATM the base class is just going to do nothing.
2886
+ this.SelectInternal(command);
2887
+ break;
2888
+ case CommandKey.Freeze:
2889
+ // COEDITING: ok
2890
+ this.FreezeInternal(command);
2891
+ // is the event necessary here? not sure. we were sending it as a
2892
+ // side effect, so it was added here in case there was some reason
2893
+ // it was necessary. at a minimum, it should not require a rebuild
2894
+ // because no addresses change. (although we leave it in case someone
2895
+ // else sets it).)
2896
+ flags.structure_event = true;
2897
+ break;
2898
+ case CommandKey.AddConditionalFormat:
2899
+ {
2900
+ const sheet = this.FindSheet(command.format.area);
2901
+ sheet.conditional_formats.push(command.format);
2902
+ sheet.Invalidate(new Area(command.format.area.start, command.format.area.end));
2903
+ if (sheet === this.active_sheet) {
2904
+ // flags.style_area = Area.Join(command.format.area, flags.style_area);
2905
+ flags.render_area = Area.Join(command.format.area, flags.render_area);
2906
+ }
2907
+ else {
2908
+ // flags.style_event = true;
2909
+ }
2910
+ flags.structure_event = true;
2911
+ flags.conditional_formatting_event = true;
2912
+ }
2913
+ break;
2914
+ case CommandKey.RemoveConditionalFormat:
2915
+ {
2916
+ let sheet;
2917
+ let count = 0;
2918
+ if (command.format) {
2919
+ // we're removing by object equivalence, not strict equality.
2920
+ // this is in case we're switching contexts and the objects
2921
+ // are not strictly equal. may be unecessary. do we need to
2922
+ // normalize in some way? (...)
2923
+ const format = JSON.stringify(command.format);
2924
+ sheet = this.FindSheet(command.format.area);
2925
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
2926
+ // if (test === command.format) {
2927
+ if (JSON.stringify(test) === format) {
2928
+ count++;
2929
+ flags.render_area = Area.Join(test.area, flags.render_area);
2930
+ return false;
2931
+ }
2932
+ return true;
2933
+ });
2934
+ }
2935
+ else if (command.area) {
2936
+ const area = new Area(command.area.start, command.area.end);
2937
+ sheet = this.FindSheet(command.area);
2938
+ sheet.conditional_formats = sheet.conditional_formats.filter(test => {
2939
+ const compare = new Area(test.area.start, test.area.end);
2940
+ if (compare.Intersects(area)) {
2941
+ count++;
2942
+ flags.render_area = Area.Join(compare, flags.render_area);
2943
+ return false;
2944
+ }
2945
+ return true;
2946
+ });
2947
+ }
2948
+ if (sheet && count) {
2949
+ sheet.FlushConditionalFormats();
2950
+ flags.structure_event = true;
2951
+ // this will flush leaf vertices. but it's expensive because
2952
+ // it's rebuilding the whole graph. we could maybe reduce a
2953
+ // bit... the question is what's worse: rebuilding the graph
2954
+ // or leaving orphans for a while?
2955
+ // flags.structure_rebuild_required = true;
2956
+ flags.conditional_formatting_event = true;
2957
+ }
2958
+ }
2959
+ break;
2960
+ case CommandKey.InsertTable:
2961
+ // the most important thing here is validating that we can
2962
+ // create the table in the target area.
2963
+ {
2964
+ const sheet = this.FindSheet(command.area);
2965
+ const area = new Area(command.area.start, command.area.end);
2966
+ // validate first
2967
+ let valid = true;
2968
+ validation_loop: for (let row = area.start.row; row <= area.end.row; row++) {
2969
+ for (let column = area.start.column; column <= area.end.column; column++) {
2970
+ const cell = sheet.cells.GetCell({ row, column }, false);
2971
+ if (cell && (cell.area || cell.merge_area || cell.table)) {
2972
+ valid = false;
2973
+ break validation_loop;
2974
+ }
2975
+ }
2976
+ }
2977
+ if (valid) {
2978
+ // we need a name for the table. needs to be unique.
2979
+ let index = this.model.tables.size + 1;
2980
+ let name = '';
2981
+ for (;;) {
2982
+ name = `Table${index++}`;
2983
+ if (!this.model.tables.has(name.toLowerCase())) {
2984
+ break;
2985
+ }
2986
+ }
2987
+ const table = {
2988
+ area: command.area,
2989
+ name,
2990
+ sortable: command.sortable, // defaults to true if !present
2991
+ theme: command.theme,
2992
+ };
2993
+ if (command.totals) {
2994
+ table.totals_row = true;
2995
+ }
2996
+ this.model.tables.set(name.toLowerCase(), table);
2997
+ for (let row = area.start.row; row <= area.end.row; row++) {
2998
+ for (let column = area.start.column; column <= area.end.column; column++) {
2999
+ const cell = sheet.cells.GetCell({ row, column }, true);
3000
+ cell.table = table;
3001
+ }
3002
+ }
3003
+ this.UpdateTableColumns(table);
3004
+ // force rerendering, we don't need to flush the values
3005
+ sheet.Invalidate(new Area(table.area.start, table.area.end));
3006
+ if (sheet === this.active_sheet) {
3007
+ flags.style_area = Area.Join(area, flags.style_area);
3008
+ flags.render_area = Area.Join(area, flags.render_area);
3009
+ }
3010
+ else {
3011
+ flags.style_event = true;
3012
+ }
3013
+ }
3014
+ }
3015
+ break;
3016
+ case CommandKey.RemoveTable:
3017
+ // this is pretty easy, we can do it inline
3018
+ {
3019
+ const sheet = this.FindSheet(command.table.area);
3020
+ const area = new Area(command.table.area.start, command.table.area.end);
3021
+ for (let row = area.start.row; row <= area.end.row; row++) {
3022
+ for (let column = area.start.column; column <= area.end.column; column++) {
3023
+ const cell = sheet.cells.GetCell({ row, column }, false);
3024
+ if (cell) {
3025
+ cell.table = undefined;
3026
+ }
3027
+ }
3028
+ }
3029
+ // drop from model
3030
+ // console.info('deleting...', command.table.name);
3031
+ this.model.tables.delete(command.table.name.toLowerCase());
3032
+ // tables use nonstandard styling, we need to invalidate the sheet.
3033
+ // for edges invalidate an extra cell around the table
3034
+ const invalid = sheet.RealArea(area.Clone().Shift(-1, -1).Resize(area.rows + 2, area.columns + 2));
3035
+ sheet.Invalidate(invalid);
3036
+ if (sheet === this.active_sheet) {
3037
+ flags.style_area = Area.Join(area, flags.style_area);
3038
+ flags.render_area = Area.Join(area, flags.render_area);
3039
+ }
3040
+ else {
3041
+ flags.style_event = true;
3042
+ }
3043
+ }
3044
+ break;
3045
+ case CommandKey.MergeCells:
3046
+ {
3047
+ // COEDITING: ok
3048
+ const sheet = this.FindSheet(command.area);
3049
+ sheet.MergeCells(new Area(command.area.start, command.area.end));
3050
+ // sheet publishes a data event here, too. probably a good
3051
+ // idea because references to the secondary (non-head) merge
3052
+ // cells will break.
3053
+ flags.structure_event = true;
3054
+ flags.structure_rebuild_required = true;
3055
+ if (sheet === this.active_sheet) {
3056
+ flags.data_area = Area.Join(command.area, flags.data_area);
3057
+ flags.render_area = Area.Join(command.area, flags.render_area);
3058
+ }
3059
+ else {
3060
+ flags.data_event = true;
3061
+ // this.pending_layout_update.add(sheet.id);
3062
+ if (!flags.pending) {
3063
+ flags.pending = [];
3064
+ }
3065
+ flags.pending.push(sheet.id);
3066
+ }
3067
+ }
3068
+ break;
3069
+ case CommandKey.UnmergeCells:
3070
+ {
3071
+ // COEDITING: ok
3072
+ // the sheet unmerge routine requires a single, contiguous merge area.
3073
+ // we want to support multiple unmerges at the same time, though,
3074
+ // so let's check for multiple. create a list.
3075
+ // FIXME: use a set
3076
+ const sheet = this.FindSheet(command.area);
3077
+ const list = {};
3078
+ const area = new Area(command.area.start, command.area.end);
3079
+ for (const cell of sheet.cells.Iterate(area, false)) {
3080
+ if (cell.merge_area) {
3081
+ const label = Area.CellAddressToLabel(cell.merge_area.start) + ':'
3082
+ + Area.CellAddressToLabel(cell.merge_area.end);
3083
+ list[label] = cell.merge_area;
3084
+ }
3085
+ }
3086
+ /*
3087
+ sheet.cells.Apply(area, (cell: Cell) => {
3088
+ if (cell.merge_area) {
3089
+ const label = Area.CellAddressToLabel(cell.merge_area.start) + ':'
3090
+ + Area.CellAddressToLabel(cell.merge_area.end);
3091
+ list[label] = cell.merge_area;
3092
+ }
3093
+ }, false);
3094
+ */
3095
+ const keys = Object.keys(list);
3096
+ for (let i = 0; i < keys.length; i++) {
3097
+ sheet.UnmergeCells(list[keys[i]]);
3098
+ }
3099
+ // see above
3100
+ if (sheet === this.active_sheet) {
3101
+ flags.render_area = Area.Join(command.area, flags.render_area);
3102
+ flags.data_area = Area.Join(command.area, flags.data_area);
3103
+ }
3104
+ else {
3105
+ flags.data_event = true;
3106
+ // this.pending_layout_update.add(sheet.id);
3107
+ if (!flags.pending) {
3108
+ flags.pending = [];
3109
+ }
3110
+ flags.pending.push(sheet.id);
3111
+ }
3112
+ flags.structure_event = true;
3113
+ flags.structure_rebuild_required = true;
3114
+ }
3115
+ break;
3116
+ case CommandKey.Indent:
3117
+ {
3118
+ let area;
3119
+ const sheet = this.FindSheet(command.area);
3120
+ if (IsCellAddress(command.area)) {
3121
+ area = new Area(command.area);
3122
+ const style = sheet.GetCellStyle(command.area, true);
3123
+ sheet.UpdateCellStyle(command.area, {
3124
+ indent: Math.max(0, (style.indent || 0) + command.delta),
3125
+ }, true);
3126
+ }
3127
+ else {
3128
+ area = new Area(command.area.start, command.area.end);
3129
+ for (const address of area) {
3130
+ const style = sheet.GetCellStyle(address, true);
3131
+ sheet.UpdateCellStyle(address, {
3132
+ indent: Math.max(0, (style.indent || 0) + command.delta),
3133
+ }, true);
3134
+ }
3135
+ }
3136
+ if (sheet === this.active_sheet) {
3137
+ flags.style_area = Area.Join(area, flags.style_area);
3138
+ flags.render_area = Area.Join(area, flags.render_area);
3139
+ }
3140
+ else {
3141
+ flags.style_event = true;
3142
+ }
3143
+ }
3144
+ break;
3145
+ case CommandKey.UpdateStyle:
3146
+ {
3147
+ // COEDITING: handles sheet ID properly
3148
+ // to account for our background bleeding up/left, when applying
3149
+ // style changes we may need to render one additional row/column.
3150
+ let area;
3151
+ const sheet = this.FindSheet(command.area);
3152
+ if (IsCellAddress(command.area)) {
3153
+ area = new Area(command.area);
3154
+ sheet.UpdateCellStyle(command.area, command.style, !!command.delta);
3155
+ }
3156
+ else {
3157
+ area = new Area(command.area.start, command.area.end);
3158
+ sheet.UpdateAreaStyle(area, command.style, !!command.delta);
3159
+ }
3160
+ if (sheet === this.active_sheet) {
3161
+ flags.style_area = Area.Join(area, flags.style_area);
3162
+ // we can limit bleed handling to cases where it's necessary...
3163
+ // if we really wanted to optimize we could call invalidate on .left, .top, &c
3164
+ if (!command.delta
3165
+ || command.style.fill
3166
+ || command.style.border_top
3167
+ || command.style.border_left
3168
+ || command.style.border_right
3169
+ || command.style.border_bottom) {
3170
+ area = Area.Bleed(area); // bleed by 1 to account for borders/background
3171
+ this.active_sheet.Invalidate(area);
3172
+ }
3173
+ flags.render_area = Area.Join(area, flags.render_area);
3174
+ }
3175
+ else {
3176
+ flags.style_event = true;
3177
+ }
3178
+ }
3179
+ break;
3180
+ case CommandKey.DataValidation:
3181
+ // COEDITING: ok
3182
+ this.SetValidationInternal(command);
3183
+ if (!command.area.start.sheet_id || command.area.start.sheet_id === this.active_sheet.id) {
3184
+ flags.render_area = Area.Join(new Area(command.area.start, command.area.end), flags.render_area);
3185
+ }
3186
+ break;
3187
+ case CommandKey.SetName:
3188
+ // it seems like we're allowing overwriting names if those
3189
+ // names exist as expressions or named ranges. however we
3190
+ // should not allow overriding a built-in function name (or
3191
+ // a macro function name?)
3192
+ // FOR THE TIME BEING we're going to add that restriction to
3193
+ // the calling function, which (atm) is the only way to get here.
3194
+ {
3195
+ const ac_token = { type: 'token', named: true, name: command.name, scope: command.scope };
3196
+ if (command.area || command.expression) {
3197
+ if (command.area) {
3198
+ this.model.named.SetNamedRange(command.name, new Area(command.area.start, command.area.end), command.scope);
3199
+ }
3200
+ else if (command.expression) {
3201
+ this.model.named.SetNamedExpression(command.name, command.expression, command.scope);
3202
+ }
3203
+ this.autocomplete_matcher.AddFunctions(ac_token);
3204
+ }
3205
+ else {
3206
+ this.model.named.ClearName(command.name, command.scope);
3207
+ this.autocomplete_matcher.RemoveFunctions(ac_token);
3208
+ }
3209
+ flags.structure_event = true;
3210
+ flags.structure_rebuild_required = true;
3211
+ }
3212
+ break;
3213
+ case CommandKey.UpdateBorders:
3214
+ {
3215
+ // COEDITING: ok
3216
+ // UPDATE: actually had a problem with Area.Bleed dropping the
3217
+ // sheet ID. fixed.
3218
+ const area = this.ApplyBordersInternal(command);
3219
+ if (area.start.sheet_id === this.active_sheet.id) {
3220
+ flags.render_area = Area.Join(area, flags.render_area);
3221
+ flags.style_area = Area.Join(area, flags.style_area);
3222
+ }
3223
+ else {
3224
+ flags.style_event = true;
3225
+ }
3226
+ }
3227
+ break;
3228
+ case CommandKey.ShowSheet:
3229
+ // COEDITING: we probably don't want this to pass through
3230
+ // when coediting, but it won't break anything. you can filter.
3231
+ this.ShowSheetInternal(command);
3232
+ flags.sheets = true; // repaint tab bar
3233
+ flags.structure_event = true;
3234
+ break;
3235
+ case CommandKey.TabColor:
3236
+ command.sheet.tab_color = command.color;
3237
+ // NOTE: the flag.sheets originally did not update tab colors,
3238
+ // which were cached. we could create a new flag for that, or
3239
+ // we could just refresh the colors. since there aren't that
3240
+ // many of them, it's probably OK to just refresh the colors any
3241
+ // time this flag is set. if that becomes a perf issue in the
3242
+ // future we could add a new flag.
3243
+ flags.sheets = true; // repaint tab bar
3244
+ flags.structure_event = true;
3245
+ break;
3246
+ case CommandKey.ReorderSheet:
3247
+ {
3248
+ // COEDITING: seems OK, irrespective of active sheet
3249
+ const sheets = [];
3250
+ const target = this.model.sheets.list[command.index];
3251
+ for (let i = 0; i < this.model.sheets.length; i++) {
3252
+ if (i !== command.index) {
3253
+ if (i === command.move_before) {
3254
+ sheets.push(target);
3255
+ }
3256
+ sheets.push(this.model.sheets.list[i]);
3257
+ }
3258
+ }
3259
+ if (command.move_before >= this.model.sheets.length) {
3260
+ sheets.push(target);
3261
+ }
3262
+ // this.model.sheets = sheets;
3263
+ this.model.sheets.Assign(sheets);
3264
+ flags.sheets = true;
3265
+ flags.structure_event = true;
3266
+ }
3267
+ break;
3268
+ case CommandKey.RenameSheet:
3269
+ {
3270
+ // COEDITING: seems OK, irrespective of active sheet
3271
+ const sheet = this.ResolveSheet(command);
3272
+ if (sheet) {
3273
+ this.RenameSheetInternal(sheet, command.new_name);
3274
+ flags.sheets = true;
3275
+ flags.structure_event = true;
3276
+ }
3277
+ }
3278
+ break;
3279
+ case CommandKey.RemoveAnnotation:
3280
+ this.RemoveAnnotationInternal(command);
3281
+ flags.structure_event = true;
3282
+ flags.structure_rebuild_required = true;
3283
+ flags.annotation_event = true;
3284
+ break;
3285
+ case CommandKey.CreateAnnotation:
3286
+ this.CreateAnnotationInternal(command);
3287
+ flags.structure_event = true;
3288
+ flags.structure_rebuild_required = true;
3289
+ flags.annotation_event = true;
3290
+ break;
3291
+ case CommandKey.ResizeRows:
3292
+ // moving this to a method so we can specialize: non-UI grid
3293
+ // should not support autosize (it can't)
3294
+ // this may impact the SUBTOTAL function. which is dumb, but
3295
+ // there you go. so treat this as a data event for rows that
3296
+ // change visibility one way or the other.
3297
+ // COEDITING: ok
3298
+ {
3299
+ const area = this.ResizeRowsInternal(command);
3300
+ if (area) {
3301
+ if (area.start.sheet_id === this.active_sheet.id) {
3302
+ const real_area = this.active_sheet.RealArea(new Area(area.start, area.end));
3303
+ flags.render_area = Area.Join(real_area, flags.render_area);
3304
+ flags.data_area = Area.Join(real_area, flags.data_area);
3305
+ flags.data_event = true;
3306
+ }
3307
+ else {
3308
+ flags.data_event = true;
3309
+ if (!flags.pending) {
3310
+ flags.pending = [];
3311
+ }
3312
+ if (area.start.sheet_id) {
3313
+ flags.pending.push(area.start.sheet_id);
3314
+ }
3315
+ }
3316
+ }
3317
+ flags.structure_event = true;
3318
+ }
3319
+ break;
3320
+ case CommandKey.ResizeColumns:
3321
+ this.ResizeColumnsInternal(command);
3322
+ flags.structure_event = true;
3323
+ break;
3324
+ case CommandKey.ShowHeaders:
3325
+ // FIXME: now that we don't support 2-level headers (or anything
3326
+ // other than 1-level headers), headers should be managed by/move into
3327
+ // the grid class.
3328
+ this.active_sheet.SetHeaderSize(command.show ? undefined : 1, command.show ? undefined : 1);
3329
+ this.flags.layout = true;
3330
+ this.flags.repaint = true;
3331
+ break;
3332
+ case CommandKey.InsertRows:
3333
+ // COEDITING: annotations are broken
3334
+ this.InsertRowsInternal(command);
3335
+ flags.structure_event = true;
3336
+ flags.structure_rebuild_required = true;
3337
+ break;
3338
+ case CommandKey.InsertColumns:
3339
+ // COEDITING: annotations are broken
3340
+ this.InsertColumnsInternal(command);
3341
+ flags.structure_event = true;
3342
+ flags.structure_rebuild_required = true;
3343
+ break;
3344
+ case CommandKey.SetLink:
3345
+ case CommandKey.SetNote:
3346
+ {
3347
+ // COEDITING: ok
3348
+ // note and link are basically the same, although there's a
3349
+ // method for setting note (not sure why)
3350
+ const sheet = this.FindSheet(command.area);
3351
+ let cell = sheet.cells.GetCell(command.area, true);
3352
+ if (cell) {
3353
+ let area;
3354
+ if (cell.merge_area) {
3355
+ area = new Area(cell.merge_area.start);
3356
+ cell = sheet.cells.GetCell(cell.merge_area.start, true);
3357
+ }
3358
+ else {
3359
+ area = new Area(command.area);
3360
+ }
3361
+ if (command.key === CommandKey.SetNote) {
3362
+ cell.SetNote(command.note);
3363
+ }
3364
+ else {
3365
+ cell.hyperlink = command.reference || undefined;
3366
+ cell.render_clean = [];
3367
+ }
3368
+ if (sheet === this.active_sheet) {
3369
+ // this isn't necessary because it's what the render area does
3370
+ // this.DelayedRender(false, area);
3371
+ // treat this as style, because it affects painting but
3372
+ // does not require calculation.
3373
+ flags.style_area = Area.Join(area, flags.style_area);
3374
+ flags.render_area = Area.Join(area, flags.render_area);
3375
+ }
3376
+ else {
3377
+ flags.style_event = true;
3378
+ }
3379
+ }
3380
+ }
3381
+ break;
3382
+ case CommandKey.SortTable:
3383
+ {
3384
+ // console.info(command.table.area.spreadsheet_label);
3385
+ const area = this.SortTableInternal(command);
3386
+ if (area && area.start.sheet_id === this.active_sheet.id) {
3387
+ flags.data_area = Area.Join(area, flags.data_area);
3388
+ // normally we don't paint, we wait for the calculator to resolve
3389
+ if (this.options.repaint_on_cell_change) {
3390
+ flags.render_area = Area.Join(area, flags.render_area);
3391
+ }
3392
+ }
3393
+ else {
3394
+ flags.data_event = true;
3395
+ }
3396
+ }
3397
+ break;
3398
+ case CommandKey.SetRange:
3399
+ {
3400
+ // COEDITING: handles sheet ID properly
3401
+ // FIXME: areas should check sheet
3402
+ // area could be undefined if there's an error
3403
+ // (try to change part of an array)
3404
+ const area = this.SetRangeInternal(command, flags);
3405
+ if (area) {
3406
+ const sheet = this.model.sheets.Find(area.start.sheet_id || this.active_sheet.id);
3407
+ const tables = sheet?.TablesFromArea(area, true) || [];
3408
+ for (const table of tables) {
3409
+ this.UpdateTableColumns(table);
3410
+ }
3411
+ }
3412
+ if (area && area.start.sheet_id === this.active_sheet.id) {
3413
+ flags.data_area = Area.Join(area, flags.data_area);
3414
+ // normally we don't paint, we wait for the calculator to resolve
3415
+ if (this.options.repaint_on_cell_change) {
3416
+ flags.render_area = Area.Join(area, flags.render_area);
3417
+ }
3418
+ }
3419
+ else {
3420
+ flags.data_event = true;
3421
+ }
3422
+ }
3423
+ break;
3424
+ case CommandKey.DeleteSheet:
3425
+ // COEDITING: looks fine
3426
+ this.DeleteSheetInternal(command);
3427
+ flags.sheets = true;
3428
+ flags.structure_event = true;
3429
+ flags.structure_rebuild_required = true;
3430
+ break;
3431
+ case CommandKey.DuplicateSheet:
3432
+ // FIXME: what happens to named ranges? we don't have sheet-local names...
3433
+ this.DuplicateSheetInternal(command);
3434
+ flags.sheets = true;
3435
+ flags.structure_event = true;
3436
+ flags.structure_rebuild_required = true;
3437
+ break;
3438
+ case CommandKey.AddSheet:
3439
+ // COEDITING: this won't break, but it shouldn't change the
3440
+ // active sheet if this is a remote command. is there a way
3441
+ // to know? we can guess implicitly from the queue parameter,
3442
+ // but it would be better to be explicit.
3443
+ {
3444
+ const id = this.AddSheetInternal(command.name, command.insert_index); // default name
3445
+ if (typeof id === 'number' && command.show) {
3446
+ this.ActivateSheetInternal({
3447
+ key: CommandKey.ActivateSheet,
3448
+ id,
3449
+ });
3450
+ }
3451
+ flags.structure_event = true;
3452
+ flags.sheets = true;
3453
+ flags.structure = true;
3454
+ }
3455
+ break;
3456
+ case CommandKey.ActivateSheet:
3457
+ this.ActivateSheetInternal(command);
3458
+ break;
3459
+ default:
3460
+ console.warn(`unhandled command: ${CommandKey[command.key]} (${command.key})`);
3461
+ }
3462
+ }
3463
+ // consolidate events and merge areas
3464
+ let data_event;
3465
+ let style_event;
3466
+ if (flags.data_area) {
3467
+ if (!flags.data_area.start.sheet_id) {
3468
+ flags.data_area.SetSheetID(this.active_sheet.id);
3469
+ }
3470
+ // events.push({ type: 'data', area: flags.data_area });
3471
+ data_event = { type: 'data', area: flags.data_area };
3472
+ }
3473
+ else if (flags.data_event) {
3474
+ // events.push({ type: 'data' });
3475
+ data_event = { type: 'data' };
3476
+ }
3477
+ if (flags.style_area) {
3478
+ if (!flags.style_area.start.sheet_id) {
3479
+ flags.style_area.SetSheetID(this.active_sheet.id);
3480
+ }
3481
+ // events.push({ type: 'style', area: flags.style_area });
3482
+ style_event = { type: 'style', area: flags.style_area };
3483
+ }
3484
+ else if (flags.style_event) {
3485
+ // events.push({ type: 'style' });
3486
+ style_event = { type: 'style' };
3487
+ }
3488
+ if (data_event && style_event) {
3489
+ events.push({
3490
+ type: 'composite',
3491
+ data_area: data_event.area,
3492
+ style_area: style_event.area,
3493
+ });
3494
+ }
3495
+ else {
3496
+ if (data_event) {
3497
+ events.push(data_event);
3498
+ }
3499
+ if (style_event) {
3500
+ events.push(style_event);
3501
+ }
3502
+ }
3503
+ if (flags.structure_event) {
3504
+ events.push({
3505
+ type: 'structure',
3506
+ rebuild_required: flags.structure_rebuild_required,
3507
+ conditional_format: flags.conditional_formatting_event,
3508
+ update_annotations: flags.annotation_event,
3509
+ });
3510
+ }
3511
+ if (this.batch > 0) {
3512
+ this.batch_events.push(...events);
3513
+ }
3514
+ else {
3515
+ this.grid_events.Publish(events);
3516
+ //if (flags.render_area) {
3517
+ // this.DelayedRender(false, flags.render_area);
3518
+ //}
3519
+ }
3520
+ return flags;
3521
+ }
3522
+ }
3523
+ //# sourceMappingURL=grid_base.js.map