@trebco/treb 37.0.0 → 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 (528) hide show
  1. package/build/package.json +119 -0
  2. package/build/treb-base-types/src/api_types.d.ts +11 -0
  3. package/build/treb-base-types/src/api_types.js +22 -0
  4. package/build/treb-base-types/src/api_types.js.map +1 -0
  5. package/build/treb-base-types/src/area-utils.d.ts +9 -0
  6. package/build/treb-base-types/src/area-utils.js +50 -0
  7. package/build/treb-base-types/src/area-utils.js.map +1 -0
  8. package/build/treb-base-types/src/area.d.ts +182 -0
  9. package/build/treb-base-types/src/area.js +715 -0
  10. package/build/treb-base-types/src/area.js.map +1 -0
  11. package/build/treb-base-types/src/basic_types.d.ts +20 -0
  12. package/build/treb-base-types/src/basic_types.js +22 -0
  13. package/build/treb-base-types/src/basic_types.js.map +1 -0
  14. package/build/treb-base-types/src/cell.d.ts +167 -0
  15. package/build/treb-base-types/src/cell.js +432 -0
  16. package/build/treb-base-types/src/cell.js.map +1 -0
  17. package/build/treb-base-types/src/cells.d.ts +251 -0
  18. package/build/treb-base-types/src/cells.js +1136 -0
  19. package/build/treb-base-types/src/cells.js.map +1 -0
  20. package/build/treb-base-types/src/color.d.ts +35 -0
  21. package/build/treb-base-types/src/color.js +162 -0
  22. package/build/treb-base-types/src/color.js.map +1 -0
  23. package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
  24. package/build/treb-base-types/src/dom-utilities.js +144 -0
  25. package/build/treb-base-types/src/dom-utilities.js.map +1 -0
  26. package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
  27. package/build/treb-base-types/src/evaluate-options.js +22 -0
  28. package/build/treb-base-types/src/evaluate-options.js.map +1 -0
  29. package/build/treb-base-types/src/font-stack.d.ts +37 -0
  30. package/build/treb-base-types/src/font-stack.js +93 -0
  31. package/build/treb-base-types/src/font-stack.js.map +1 -0
  32. package/build/treb-base-types/src/gradient.d.ts +18 -0
  33. package/build/treb-base-types/src/gradient.js +86 -0
  34. package/build/treb-base-types/src/gradient.js.map +1 -0
  35. package/build/treb-base-types/src/import.d.ts +48 -0
  36. package/build/treb-base-types/src/import.js +22 -0
  37. package/build/treb-base-types/src/import.js.map +1 -0
  38. package/build/treb-base-types/src/index-standalone.d.ts +6 -0
  39. package/build/treb-base-types/src/index-standalone.js +27 -0
  40. package/build/treb-base-types/src/index-standalone.js.map +1 -0
  41. package/build/treb-base-types/src/index.d.ts +22 -0
  42. package/build/treb-base-types/src/index.js +45 -0
  43. package/build/treb-base-types/src/index.js.map +1 -0
  44. package/build/treb-base-types/src/layout.d.ts +22 -0
  45. package/build/treb-base-types/src/layout.js +22 -0
  46. package/build/treb-base-types/src/layout.js.map +1 -0
  47. package/build/treb-base-types/src/localization.d.ts +37 -0
  48. package/build/treb-base-types/src/localization.js +157 -0
  49. package/build/treb-base-types/src/localization.js.map +1 -0
  50. package/build/treb-base-types/src/rectangle.d.ts +51 -0
  51. package/build/treb-base-types/src/rectangle.js +123 -0
  52. package/build/treb-base-types/src/rectangle.js.map +1 -0
  53. package/build/treb-base-types/src/render_text.d.ts +34 -0
  54. package/build/treb-base-types/src/render_text.js +22 -0
  55. package/build/treb-base-types/src/render_text.js.map +1 -0
  56. package/build/treb-base-types/src/style.d.ts +214 -0
  57. package/build/treb-base-types/src/style.js +373 -0
  58. package/build/treb-base-types/src/style.js.map +1 -0
  59. package/build/treb-base-types/src/table.d.ts +58 -0
  60. package/build/treb-base-types/src/table.js +27 -0
  61. package/build/treb-base-types/src/table.js.map +1 -0
  62. package/build/treb-base-types/src/text_part.d.ts +26 -0
  63. package/build/treb-base-types/src/text_part.js +47 -0
  64. package/build/treb-base-types/src/text_part.js.map +1 -0
  65. package/build/treb-base-types/src/theme.d.ts +120 -0
  66. package/build/treb-base-types/src/theme.js +460 -0
  67. package/build/treb-base-types/src/theme.js.map +1 -0
  68. package/build/treb-base-types/src/union.d.ts +73 -0
  69. package/build/treb-base-types/src/union.js +61 -0
  70. package/build/treb-base-types/src/union.js.map +1 -0
  71. package/build/treb-base-types/src/value-type.d.ts +86 -0
  72. package/build/treb-base-types/src/value-type.js +168 -0
  73. package/build/treb-base-types/src/value-type.js.map +1 -0
  74. package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
  75. package/build/treb-base-types/src/worker-proxy.js +221 -0
  76. package/build/treb-base-types/src/worker-proxy.js.map +1 -0
  77. package/build/treb-calculator/src/calculator.d.ts +249 -0
  78. package/build/treb-calculator/src/calculator.js +2755 -0
  79. package/build/treb-calculator/src/calculator.js.map +1 -0
  80. package/build/treb-calculator/src/complex-math.d.ts +75 -0
  81. package/build/treb-calculator/src/complex-math.js +559 -0
  82. package/build/treb-calculator/src/complex-math.js.map +1 -0
  83. package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
  84. package/build/treb-calculator/src/dag/array-vertex.js +156 -0
  85. package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
  86. package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
  87. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
  88. package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
  89. package/build/treb-calculator/src/dag/graph.d.ts +134 -0
  90. package/build/treb-calculator/src/dag/graph.js +842 -0
  91. package/build/treb-calculator/src/dag/graph.js.map +1 -0
  92. package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
  93. package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
  94. package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
  95. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
  96. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
  97. package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
  98. package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
  99. package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
  100. package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
  101. package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
  102. package/build/treb-calculator/src/dag/vertex.js +274 -0
  103. package/build/treb-calculator/src/dag/vertex.js.map +1 -0
  104. package/build/treb-calculator/src/descriptors.d.ts +189 -0
  105. package/build/treb-calculator/src/descriptors.js +22 -0
  106. package/build/treb-calculator/src/descriptors.js.map +1 -0
  107. package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
  108. package/build/treb-calculator/src/expression-calculator.js +1033 -0
  109. package/build/treb-calculator/src/expression-calculator.js.map +1 -0
  110. package/build/treb-calculator/src/function-error.d.ts +35 -0
  111. package/build/treb-calculator/src/function-error.js +85 -0
  112. package/build/treb-calculator/src/function-error.js.map +1 -0
  113. package/build/treb-calculator/src/function-library.d.ts +22 -0
  114. package/build/treb-calculator/src/function-library.js +96 -0
  115. package/build/treb-calculator/src/function-library.js.map +1 -0
  116. package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
  117. package/build/treb-calculator/src/functions/base-functions.js +2611 -0
  118. package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
  119. package/build/treb-calculator/src/functions/beta.d.ts +17 -0
  120. package/build/treb-calculator/src/functions/beta.js +201 -0
  121. package/build/treb-calculator/src/functions/beta.js.map +1 -0
  122. package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
  123. package/build/treb-calculator/src/functions/checkbox.js +128 -0
  124. package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
  125. package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
  126. package/build/treb-calculator/src/functions/complex-functions.js +217 -0
  127. package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
  128. package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
  129. package/build/treb-calculator/src/functions/date-utils.js +59 -0
  130. package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
  131. package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
  132. package/build/treb-calculator/src/functions/finance-functions.js +547 -0
  133. package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
  134. package/build/treb-calculator/src/functions/fp.d.ts +2 -0
  135. package/build/treb-calculator/src/functions/fp.js +463 -0
  136. package/build/treb-calculator/src/functions/fp.js.map +1 -0
  137. package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
  138. package/build/treb-calculator/src/functions/function-utilities.js +36 -0
  139. package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
  140. package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
  141. package/build/treb-calculator/src/functions/gamma.js +142 -0
  142. package/build/treb-calculator/src/functions/gamma.js.map +1 -0
  143. package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
  144. package/build/treb-calculator/src/functions/information-functions.js +71 -0
  145. package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
  146. package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
  147. package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
  148. package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
  149. package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
  150. package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
  151. package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
  152. package/build/treb-calculator/src/functions/normal.d.ts +2 -0
  153. package/build/treb-calculator/src/functions/normal.js +32 -0
  154. package/build/treb-calculator/src/functions/normal.js.map +1 -0
  155. package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
  156. package/build/treb-calculator/src/functions/regex-functions.js +188 -0
  157. package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
  158. package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
  159. package/build/treb-calculator/src/functions/sparkline.js +264 -0
  160. package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
  161. package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
  162. package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
  163. package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
  164. package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
  165. package/build/treb-calculator/src/functions/students-t.js +64 -0
  166. package/build/treb-calculator/src/functions/students-t.js.map +1 -0
  167. package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
  168. package/build/treb-calculator/src/functions/text-functions.js +320 -0
  169. package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
  170. package/build/treb-calculator/src/index.d.ts +2 -0
  171. package/build/treb-calculator/src/index.js +22 -0
  172. package/build/treb-calculator/src/index.js.map +1 -0
  173. package/build/treb-calculator/src/notifier-types.d.ts +26 -0
  174. package/build/treb-calculator/src/notifier-types.js +22 -0
  175. package/build/treb-calculator/src/notifier-types.js.map +1 -0
  176. package/build/treb-calculator/src/primitives.d.ts +15 -0
  177. package/build/treb-calculator/src/primitives.js +398 -0
  178. package/build/treb-calculator/src/primitives.js.map +1 -0
  179. package/build/treb-calculator/src/utilities.d.ts +68 -0
  180. package/build/treb-calculator/src/utilities.js +324 -0
  181. package/build/treb-calculator/src/utilities.js.map +1 -0
  182. package/build/treb-charts/src/chart-functions.d.ts +8 -0
  183. package/build/treb-charts/src/chart-functions.js +209 -0
  184. package/build/treb-charts/src/chart-functions.js.map +1 -0
  185. package/build/treb-charts/src/chart-types.d.ts +233 -0
  186. package/build/treb-charts/src/chart-types.js +57 -0
  187. package/build/treb-charts/src/chart-types.js.map +1 -0
  188. package/build/treb-charts/src/chart-utils.d.ts +106 -0
  189. package/build/treb-charts/src/chart-utils.js +1060 -0
  190. package/build/treb-charts/src/chart-utils.js.map +1 -0
  191. package/build/treb-charts/src/chart.d.ts +23 -0
  192. package/build/treb-charts/src/chart.js +94 -0
  193. package/build/treb-charts/src/chart.js.map +1 -0
  194. package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
  195. package/build/treb-charts/src/default-chart-renderer.js +533 -0
  196. package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
  197. package/build/treb-charts/src/index.d.ts +5 -0
  198. package/build/treb-charts/src/index.js +24 -0
  199. package/build/treb-charts/src/index.js.map +1 -0
  200. package/build/treb-charts/src/main.d.ts +1 -0
  201. package/build/treb-charts/src/main.js +34 -0
  202. package/build/treb-charts/src/main.js.map +1 -0
  203. package/build/treb-charts/src/quicksort.d.ts +1 -0
  204. package/build/treb-charts/src/quicksort.js +49 -0
  205. package/build/treb-charts/src/quicksort.js.map +1 -0
  206. package/build/treb-charts/src/rectangle.d.ts +18 -0
  207. package/build/treb-charts/src/rectangle.js +41 -0
  208. package/build/treb-charts/src/rectangle.js.map +1 -0
  209. package/build/treb-charts/src/renderer-type.d.ts +24 -0
  210. package/build/treb-charts/src/renderer-type.js +22 -0
  211. package/build/treb-charts/src/renderer-type.js.map +1 -0
  212. package/build/treb-charts/src/renderer.d.ts +127 -0
  213. package/build/treb-charts/src/renderer.js +1518 -0
  214. package/build/treb-charts/src/renderer.js.map +1 -0
  215. package/build/treb-charts/src/util.d.ts +18 -0
  216. package/build/treb-charts/src/util.js +71 -0
  217. package/build/treb-charts/src/util.js.map +1 -0
  218. package/build/treb-data-model/src/annotation.d.ts +167 -0
  219. package/build/treb-data-model/src/annotation.js +120 -0
  220. package/build/treb-data-model/src/annotation.js.map +1 -0
  221. package/build/treb-data-model/src/conditional_format.d.ts +155 -0
  222. package/build/treb-data-model/src/conditional_format.js +62 -0
  223. package/build/treb-data-model/src/conditional_format.js.map +1 -0
  224. package/build/treb-data-model/src/data-validation.d.ts +28 -0
  225. package/build/treb-data-model/src/data-validation.js +22 -0
  226. package/build/treb-data-model/src/data-validation.js.map +1 -0
  227. package/build/treb-data-model/src/data_model.d.ts +173 -0
  228. package/build/treb-data-model/src/data_model.js +637 -0
  229. package/build/treb-data-model/src/data_model.js.map +1 -0
  230. package/build/treb-data-model/src/index.d.ts +13 -0
  231. package/build/treb-data-model/src/index.js +28 -0
  232. package/build/treb-data-model/src/index.js.map +1 -0
  233. package/build/treb-data-model/src/language-model.d.ts +22 -0
  234. package/build/treb-data-model/src/language-model.js +22 -0
  235. package/build/treb-data-model/src/language-model.js.map +1 -0
  236. package/build/treb-data-model/src/named.d.ts +124 -0
  237. package/build/treb-data-model/src/named.js +372 -0
  238. package/build/treb-data-model/src/named.js.map +1 -0
  239. package/build/treb-data-model/src/serialize_options.d.ts +49 -0
  240. package/build/treb-data-model/src/serialize_options.js +22 -0
  241. package/build/treb-data-model/src/serialize_options.js.map +1 -0
  242. package/build/treb-data-model/src/sheet.d.ts +499 -0
  243. package/build/treb-data-model/src/sheet.js +2904 -0
  244. package/build/treb-data-model/src/sheet.js.map +1 -0
  245. package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
  246. package/build/treb-data-model/src/sheet_collection.js +112 -0
  247. package/build/treb-data-model/src/sheet_collection.js.map +1 -0
  248. package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
  249. package/build/treb-data-model/src/sheet_selection.js +39 -0
  250. package/build/treb-data-model/src/sheet_selection.js.map +1 -0
  251. package/build/treb-data-model/src/sheet_types.d.ts +104 -0
  252. package/build/treb-data-model/src/sheet_types.js +22 -0
  253. package/build/treb-data-model/src/sheet_types.js.map +1 -0
  254. package/build/treb-data-model/src/types.d.ts +59 -0
  255. package/build/treb-data-model/src/types.js +22 -0
  256. package/build/treb-data-model/src/types.js.map +1 -0
  257. package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
  258. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
  259. package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
  260. package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
  261. package/build/treb-embed/src/custom-element/treb-global.js +64 -0
  262. package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
  263. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
  264. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
  265. package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
  266. package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
  267. package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
  268. package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
  269. package/build/treb-embed/src/index.d.ts +12 -0
  270. package/build/treb-embed/src/index.js +34 -0
  271. package/build/treb-embed/src/index.js.map +1 -0
  272. package/build/treb-embed/src/options.d.ts +266 -0
  273. package/build/treb-embed/src/options.js +56 -0
  274. package/build/treb-embed/src/options.js.map +1 -0
  275. package/build/treb-embed/src/plugin.d.ts +9 -0
  276. package/build/treb-embed/src/plugin.js +22 -0
  277. package/build/treb-embed/src/plugin.js.map +1 -0
  278. package/build/treb-embed/src/progress-dialog.d.ts +49 -0
  279. package/build/treb-embed/src/progress-dialog.js +178 -0
  280. package/build/treb-embed/src/progress-dialog.js.map +1 -0
  281. package/build/treb-embed/src/selection-state.d.ts +15 -0
  282. package/build/treb-embed/src/selection-state.js +22 -0
  283. package/build/treb-embed/src/selection-state.js.map +1 -0
  284. package/build/treb-embed/src/spinner.d.ts +8 -0
  285. package/build/treb-embed/src/spinner.js +40 -0
  286. package/build/treb-embed/src/spinner.js.map +1 -0
  287. package/build/treb-embed/src/toolbar-message.d.ts +72 -0
  288. package/build/treb-embed/src/toolbar-message.js +22 -0
  289. package/build/treb-embed/src/toolbar-message.js.map +1 -0
  290. package/build/treb-embed/src/types.d.ts +185 -0
  291. package/build/treb-embed/src/types.js +45 -0
  292. package/build/treb-embed/src/types.js.map +1 -0
  293. package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
  294. package/build/treb-export/src/address-type.d.ts +34 -0
  295. package/build/treb-export/src/address-type.js +53 -0
  296. package/build/treb-export/src/address-type.js.map +1 -0
  297. package/build/treb-export/src/base-template.d.ts +1 -0
  298. package/build/treb-export/src/base-template.js +22 -0
  299. package/build/treb-export/src/base-template.js.map +1 -0
  300. package/build/treb-export/src/column-width.d.ts +2 -0
  301. package/build/treb-export/src/column-width.js +80 -0
  302. package/build/treb-export/src/column-width.js.map +1 -0
  303. package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
  304. package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
  305. package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
  306. package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
  307. package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
  308. package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
  309. package/build/treb-export/src/drawing/chart.d.ts +26 -0
  310. package/build/treb-export/src/drawing/chart.js +247 -0
  311. package/build/treb-export/src/drawing/chart.js.map +1 -0
  312. package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
  313. package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
  314. package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
  315. package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
  316. package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
  317. package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
  318. package/build/treb-export/src/drawing/drawing.d.ts +49 -0
  319. package/build/treb-export/src/drawing/drawing.js +193 -0
  320. package/build/treb-export/src/drawing/drawing.js.map +1 -0
  321. package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
  322. package/build/treb-export/src/drawing/embedded-image.js +54 -0
  323. package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
  324. package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
  325. package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
  326. package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
  327. package/build/treb-export/src/export.d.ts +72 -0
  328. package/build/treb-export/src/export.js +2039 -0
  329. package/build/treb-export/src/export.js.map +1 -0
  330. package/build/treb-export/src/import-export-messages.d.ts +31 -0
  331. package/build/treb-export/src/import-export-messages.js +22 -0
  332. package/build/treb-export/src/import-export-messages.js.map +1 -0
  333. package/build/treb-export/src/import.d.ts +33 -0
  334. package/build/treb-export/src/import.js +1258 -0
  335. package/build/treb-export/src/import.js.map +1 -0
  336. package/build/treb-export/src/index.worker.d.ts +1 -0
  337. package/build/treb-export/src/index.worker.js +93 -0
  338. package/build/treb-export/src/index.worker.js.map +1 -0
  339. package/build/treb-export/src/metadata.d.ts +51 -0
  340. package/build/treb-export/src/metadata.js +153 -0
  341. package/build/treb-export/src/metadata.js.map +1 -0
  342. package/build/treb-export/src/ooxml.d.ts +7 -0
  343. package/build/treb-export/src/ooxml.js +41 -0
  344. package/build/treb-export/src/ooxml.js.map +1 -0
  345. package/build/treb-export/src/relationship.d.ts +8 -0
  346. package/build/treb-export/src/relationship.js +27 -0
  347. package/build/treb-export/src/relationship.js.map +1 -0
  348. package/build/treb-export/src/shared-strings.d.ts +11 -0
  349. package/build/treb-export/src/shared-strings.js +105 -0
  350. package/build/treb-export/src/shared-strings.js.map +1 -0
  351. package/build/treb-export/src/template-2.d.ts +1 -0
  352. package/build/treb-export/src/template-2.js +22 -0
  353. package/build/treb-export/src/template-2.js.map +1 -0
  354. package/build/treb-export/src/unescape_xml.d.ts +1 -0
  355. package/build/treb-export/src/unescape_xml.js +61 -0
  356. package/build/treb-export/src/unescape_xml.js.map +1 -0
  357. package/build/treb-export/src/workbook-sheet.d.ts +75 -0
  358. package/build/treb-export/src/workbook-sheet.js +128 -0
  359. package/build/treb-export/src/workbook-sheet.js.map +1 -0
  360. package/build/treb-export/src/workbook-style.d.ts +110 -0
  361. package/build/treb-export/src/workbook-style.js +1134 -0
  362. package/build/treb-export/src/workbook-style.js.map +1 -0
  363. package/build/treb-export/src/workbook-theme.d.ts +13 -0
  364. package/build/treb-export/src/workbook-theme.js +85 -0
  365. package/build/treb-export/src/workbook-theme.js.map +1 -0
  366. package/build/treb-export/src/workbook.d.ts +123 -0
  367. package/build/treb-export/src/workbook.js +644 -0
  368. package/build/treb-export/src/workbook.js.map +1 -0
  369. package/build/treb-export/src/xml-test.d.ts +9 -0
  370. package/build/treb-export/src/xml-test.js +52 -0
  371. package/build/treb-export/src/xml-test.js.map +1 -0
  372. package/build/treb-export/src/xml-utils.d.ts +76 -0
  373. package/build/treb-export/src/xml-utils.js +223 -0
  374. package/build/treb-export/src/xml-utils.js.map +1 -0
  375. package/build/treb-export/src/zip-wrapper.d.ts +22 -0
  376. package/build/treb-export/src/zip-wrapper.js +93 -0
  377. package/build/treb-export/src/zip-wrapper.js.map +1 -0
  378. package/build/treb-format/src/format.d.ts +130 -0
  379. package/build/treb-format/src/format.js +805 -0
  380. package/build/treb-format/src/format.js.map +1 -0
  381. package/build/treb-format/src/format_cache.d.ts +55 -0
  382. package/build/treb-format/src/format_cache.js +166 -0
  383. package/build/treb-format/src/format_cache.js.map +1 -0
  384. package/build/treb-format/src/format_parser.d.ts +70 -0
  385. package/build/treb-format/src/format_parser.js +618 -0
  386. package/build/treb-format/src/format_parser.js.map +1 -0
  387. package/build/treb-format/src/index.d.ts +4 -0
  388. package/build/treb-format/src/index.js +25 -0
  389. package/build/treb-format/src/index.js.map +1 -0
  390. package/build/treb-format/src/number_format_section.d.ts +58 -0
  391. package/build/treb-format/src/number_format_section.js +78 -0
  392. package/build/treb-format/src/number_format_section.js.map +1 -0
  393. package/build/treb-format/src/value_parser.d.ts +48 -0
  394. package/build/treb-format/src/value_parser.js +244 -0
  395. package/build/treb-format/src/value_parser.js.map +1 -0
  396. package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
  397. package/build/treb-grid/src/editors/autocomplete.js +316 -0
  398. package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
  399. package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
  400. package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
  401. package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
  402. package/build/treb-grid/src/editors/editor.d.ts +214 -0
  403. package/build/treb-grid/src/editors/editor.js +879 -0
  404. package/build/treb-grid/src/editors/editor.js.map +1 -0
  405. package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
  406. package/build/treb-grid/src/editors/external_editor.js +118 -0
  407. package/build/treb-grid/src/editors/external_editor.js.map +1 -0
  408. package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
  409. package/build/treb-grid/src/editors/formula_bar.js +444 -0
  410. package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
  411. package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
  412. package/build/treb-grid/src/editors/overlay_editor.js +353 -0
  413. package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
  414. package/build/treb-grid/src/index.d.ts +12 -0
  415. package/build/treb-grid/src/index.js +28 -0
  416. package/build/treb-grid/src/index.js.map +1 -0
  417. package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
  418. package/build/treb-grid/src/layout/base_layout.js +2050 -0
  419. package/build/treb-grid/src/layout/base_layout.js.map +1 -0
  420. package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
  421. package/build/treb-grid/src/layout/grid_layout.js +235 -0
  422. package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
  423. package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
  424. package/build/treb-grid/src/layout/mock-layout.js +37 -0
  425. package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
  426. package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
  427. package/build/treb-grid/src/render/selection-renderer.js +315 -0
  428. package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
  429. package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
  430. package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
  431. package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
  432. package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
  433. package/build/treb-grid/src/render/svg_selection_block.js +106 -0
  434. package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
  435. package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
  436. package/build/treb-grid/src/render/tile_renderer.js +1609 -0
  437. package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
  438. package/build/treb-grid/src/types/border_constants.d.ts +9 -0
  439. package/build/treb-grid/src/types/border_constants.js +34 -0
  440. package/build/treb-grid/src/types/border_constants.js.map +1 -0
  441. package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
  442. package/build/treb-grid/src/types/clipboard_data.js +22 -0
  443. package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
  444. package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
  445. package/build/treb-grid/src/types/clipboard_data2.js +22 -0
  446. package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
  447. package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
  448. package/build/treb-grid/src/types/drag_mask.js +78 -0
  449. package/build/treb-grid/src/types/drag_mask.js.map +1 -0
  450. package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
  451. package/build/treb-grid/src/types/external_editor_config.js +22 -0
  452. package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
  453. package/build/treb-grid/src/types/grid.d.ts +806 -0
  454. package/build/treb-grid/src/types/grid.js +6410 -0
  455. package/build/treb-grid/src/types/grid.js.map +1 -0
  456. package/build/treb-grid/src/types/grid_base.d.ts +442 -0
  457. package/build/treb-grid/src/types/grid_base.js +3523 -0
  458. package/build/treb-grid/src/types/grid_base.js.map +1 -0
  459. package/build/treb-grid/src/types/grid_command.d.ts +408 -0
  460. package/build/treb-grid/src/types/grid_command.js +75 -0
  461. package/build/treb-grid/src/types/grid_command.js.map +1 -0
  462. package/build/treb-grid/src/types/grid_events.d.ts +93 -0
  463. package/build/treb-grid/src/types/grid_events.js +36 -0
  464. package/build/treb-grid/src/types/grid_events.js.map +1 -0
  465. package/build/treb-grid/src/types/grid_options.d.ts +50 -0
  466. package/build/treb-grid/src/types/grid_options.js +34 -0
  467. package/build/treb-grid/src/types/grid_options.js.map +1 -0
  468. package/build/treb-grid/src/types/scale-control.d.ts +21 -0
  469. package/build/treb-grid/src/types/scale-control.js +148 -0
  470. package/build/treb-grid/src/types/scale-control.js.map +1 -0
  471. package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
  472. package/build/treb-grid/src/types/set_range_options.js +22 -0
  473. package/build/treb-grid/src/types/set_range_options.js.map +1 -0
  474. package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
  475. package/build/treb-grid/src/types/tab_bar.js +426 -0
  476. package/build/treb-grid/src/types/tab_bar.js.map +1 -0
  477. package/build/treb-grid/src/types/tile.d.ts +29 -0
  478. package/build/treb-grid/src/types/tile.js +22 -0
  479. package/build/treb-grid/src/types/tile.js.map +1 -0
  480. package/build/treb-grid/src/types/update_flags.d.ts +48 -0
  481. package/build/treb-grid/src/types/update_flags.js +22 -0
  482. package/build/treb-grid/src/types/update_flags.js.map +1 -0
  483. package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
  484. package/build/treb-grid/src/util/fontmetrics.js +82 -0
  485. package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
  486. package/build/treb-grid/src/util/ua.d.ts +33 -0
  487. package/build/treb-grid/src/util/ua.js +86 -0
  488. package/build/treb-grid/src/util/ua.js.map +1 -0
  489. package/build/treb-parser/src/csv-parser.d.ts +13 -0
  490. package/build/treb-parser/src/csv-parser.js +107 -0
  491. package/build/treb-parser/src/csv-parser.js.map +1 -0
  492. package/build/treb-parser/src/index.d.ts +4 -0
  493. package/build/treb-parser/src/index.js +25 -0
  494. package/build/treb-parser/src/index.js.map +1 -0
  495. package/build/treb-parser/src/md-parser.d.ts +97 -0
  496. package/build/treb-parser/src/md-parser.js +403 -0
  497. package/build/treb-parser/src/md-parser.js.map +1 -0
  498. package/build/treb-parser/src/parser-types.d.ts +345 -0
  499. package/build/treb-parser/src/parser-types.js +53 -0
  500. package/build/treb-parser/src/parser-types.js.map +1 -0
  501. package/build/treb-parser/src/parser.d.ts +422 -0
  502. package/build/treb-parser/src/parser.js +2418 -0
  503. package/build/treb-parser/src/parser.js.map +1 -0
  504. package/build/treb-utils/src/event_source.d.ts +34 -0
  505. package/build/treb-utils/src/event_source.js +110 -0
  506. package/build/treb-utils/src/event_source.js.map +1 -0
  507. package/build/treb-utils/src/ievent_source.d.ts +9 -0
  508. package/build/treb-utils/src/ievent_source.js +22 -0
  509. package/build/treb-utils/src/ievent_source.js.map +1 -0
  510. package/build/treb-utils/src/index.d.ts +6 -0
  511. package/build/treb-utils/src/index.js +30 -0
  512. package/build/treb-utils/src/index.js.map +1 -0
  513. package/build/treb-utils/src/measurement.d.ts +42 -0
  514. package/build/treb-utils/src/measurement.js +145 -0
  515. package/build/treb-utils/src/measurement.js.map +1 -0
  516. package/build/treb-utils/src/scale.d.ts +16 -0
  517. package/build/treb-utils/src/scale.js +106 -0
  518. package/build/treb-utils/src/scale.js.map +1 -0
  519. package/build/treb-utils/src/serialize_html.d.ts +5 -0
  520. package/build/treb-utils/src/serialize_html.js +128 -0
  521. package/build/treb-utils/src/serialize_html.js.map +1 -0
  522. package/build/treb-utils/src/validate_uri.d.ts +20 -0
  523. package/build/treb-utils/src/validate_uri.js +55 -0
  524. package/build/treb-utils/src/validate_uri.js.map +1 -0
  525. package/dist/{chunk-XD5PEZBZ.mjs → chunk-E35ONJUS.mjs} +1 -1
  526. package/dist/treb-export-worker.mjs +2 -2
  527. package/dist/treb-spreadsheet.mjs +4 -4
  528. package/package.json +1 -1
@@ -0,0 +1,2904 @@
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
+ // --- treb imports -----------------------------------------------------------
22
+ import { ValueType, Cells, Style, Area, IsFlatDataArray, IsNestedRowArray, IsCellAddress, DOMContext, IsHTMLColor, IsThemeColor } from 'treb-base-types';
23
+ import { NumberFormatCache } from 'treb-format';
24
+ import { Measurement, ValidateURI } from 'treb-utils';
25
+ import { Get as GetFonrMetrics } from 'treb-grid/src/util/fontmetrics';
26
+ import { CreateSelection } from './sheet_selection';
27
+ import { Annotation } from './annotation';
28
+ // --- constants --------------------------------------------------------------
29
+ const DEFAULT_COLUMN_WIDTH = 100;
30
+ // const DEFAULT_ROW_HEIGHT = 26; // not used because it's based on font (theoretically)
31
+ const DEFAULT_ROW_HEADER_WIDTH = 60;
32
+ export class Sheet {
33
+ // --- static members -------------------------------------------------------
34
+ static base_id = 100;
35
+ static default_sheet_name = 'Sheet1';
36
+ // FIXME: use the external measurement object (from utils)
37
+ // private static measurement_canvas?: HTMLCanvasElement;
38
+ /**
39
+ * adding verbose flag so we can figure out who is publishing
40
+ * (and stop -- part of the ExecCommand switchover)
41
+ */
42
+ // public static readonly sheet_events = new EventSource<SheetEvent>(true, 'sheet-events');
43
+ // --- instance members -----------------------------------------------------
44
+ /**
45
+ * in the old model, we had a concept of "default" style properties. we then
46
+ * used that object for theming: we would set default properties when the theme
47
+ * changed.
48
+ *
49
+ * the problem is that if there are multiple instances on a single page, with
50
+ * different themes, they would clash.
51
+ *
52
+ * so the new concept is to have a default property set per instance, managed
53
+ * by the grid instance. any sheets that are loaded in/created by grid will
54
+ * get a reference to that property set, and grid can update it as desired.
55
+ *
56
+ * because it's a reference, it should be constant.
57
+ * FIXME: move to model...
58
+ */
59
+ default_style_properties;
60
+ /* moved from grid */
61
+ annotations = [];
62
+ // moved from layout
63
+ freeze = {
64
+ rows: 0,
65
+ columns: 0,
66
+ };
67
+ /** testing */
68
+ // public scale = 1.0;
69
+ visible = true;
70
+ /** standard width (FIXME: static?) */
71
+ default_column_width = 100;
72
+ /** standard height (FIXME: static?) */
73
+ default_row_height = 25;
74
+ /** cells data */
75
+ cells = new Cells();
76
+ /**
77
+ * selection. moved to sheet to preserve selections in multiple sheets.
78
+ * this instance should just be used to populate the actual selection,
79
+ * not used as a reference.
80
+ */
81
+ selection = CreateSelection();
82
+ /**
83
+ * cache scroll offset for flipping between sheets. should this be
84
+ * persisted? (...)
85
+ */
86
+ scroll_offset = { x: 0, y: 0 };
87
+ /**
88
+ * named ranges: name -> area
89
+ * FIXME: this needs to move to an outer container, otherwise we
90
+ * may get conflicts w/ multiple sheets. unless we want to allow that...
91
+ */
92
+ // public named_ranges = new NamedRangeCollection();
93
+ name = Sheet.default_sheet_name;
94
+ tab_color;
95
+ background_image;
96
+ _image = undefined;
97
+ /**
98
+ * set this flag when we need to update conditional formats even
99
+ * if they are not dirty (generally when one is deleted)
100
+ */
101
+ flush_conditional_formats = false;
102
+ get image() {
103
+ return this._image;
104
+ }
105
+ /**
106
+ * @internal
107
+ */
108
+ conditional_formats = [];
109
+ /**
110
+ * @internal
111
+ */
112
+ data_validation = [];
113
+ /**
114
+ * @internal
115
+ *
116
+ * testing, not serialized atm
117
+ */
118
+ outline;
119
+ /** internal ID */
120
+ // tslint:disable-next-line: variable-name
121
+ id_;
122
+ // tslint:disable-next-line:variable-name
123
+ // private row_height_: number[] = [];
124
+ row_height_map = new Map();
125
+ // tslint:disable-next-line:variable-name
126
+ column_width_ = [];
127
+ /**
128
+ * optionally, custom row headers (instead of 1...2...3...)
129
+ * FIXME: should maybe be a function instead?
130
+ * FIXME: why is this any type? just sloppiness?
131
+ */
132
+ row_headers = [];
133
+ /**
134
+ * optionally, custom column headers (instead of A...B...C...)
135
+ * FIXME: should maybe be a function instead?
136
+ * FIXME: why is this any type? just sloppiness?
137
+ */
138
+ column_headers = [];
139
+ /** size of header */
140
+ row_header_width = 100;
141
+ /** size of header */
142
+ column_header_height = 25;
143
+ // we cache composite styles so we don't wind up with objects
144
+ // for every cell, when all we need is a single reference.
145
+ style_map = [];
146
+ // we use json for comparison. it should be faster than the alternative
147
+ // (even if that doesn't make sense).
148
+ style_json_map = [];
149
+ // style now uses overlays, but we want to precalculate the
150
+ // overlaid values. we need to hold on to the originals, in
151
+ // the event something changes, so we can redo the calculation.
152
+ // there's a default at the bottom that gets applied to everything.
153
+ // (in Style). above that, we have the sheet style
154
+ sheet_style = {};
155
+ // then individual (applied) row and column styles (indexed by row/column)
156
+ row_styles = {};
157
+ column_styles = {};
158
+ /*
159
+ we used to have "alternate row" styles. it's clumsy, but it is a nice
160
+ effect. we will add that back via a "pattern". not sure how the UI would
161
+ work for this, but programatically it works.
162
+
163
+ just rows atm, not columns.
164
+ */
165
+ row_pattern = [];
166
+ // and finally any cell-specific styles. [FIXME: this is sparse]
167
+ // [why FIXME? sparse is OK in js]
168
+ cell_style = [];
169
+ /**
170
+ * applied conditional formats are stored them in this array;
171
+ * they will be stacked on top of cell style when rendering.
172
+ * conditional formats have top priority. [FIXME: what about tables?]
173
+ */
174
+ conditional_format_cache = [];
175
+ /**
176
+ * this is a list of cells we formatted on the last pass, so we can
177
+ * compare when applying conditional formats .
178
+ *
179
+ * update: using areas
180
+ */
181
+ conditional_format_checklist = [];
182
+ // --- accessors ------------------------------------------------------------
183
+ // public get column_header_count() { return this.column_header_count_; }
184
+ get header_offset() {
185
+ return { x: this.row_header_width, y: this.column_header_height };
186
+ }
187
+ /** accessor: now just a wrapper for the call on cells */
188
+ get rows() { return this.cells.rows; }
189
+ /** accessor: now just a wrapper for the call on cells */
190
+ get columns() { return this.cells.columns; }
191
+ get id() { return this.id_; }
192
+ set id(id) {
193
+ this.id_ = id;
194
+ if (this.id >= Sheet.base_id) {
195
+ Sheet.base_id = this.id + 1;
196
+ }
197
+ }
198
+ /**
199
+ * constructor is now protected. use a factory method (Blank or FromJSON).
200
+ */
201
+ constructor(theme_style_properties) {
202
+ this.default_style_properties = theme_style_properties;
203
+ // FIXME: the below should be called in a separate 'init' method
204
+ // that can be called after we change styles (since it will measure)
205
+ this.default_column_width = DEFAULT_COLUMN_WIDTH;
206
+ this.row_header_width = DEFAULT_ROW_HEADER_WIDTH;
207
+ // this.UpdateDefaultRowHeight();
208
+ this.id_ = Sheet.base_id++;
209
+ }
210
+ // --- class methods --------------------------------------------------------
211
+ static Reset() {
212
+ this.base_id = 100;
213
+ }
214
+ /**
215
+ * factory method creates a new sheet
216
+ */
217
+ static Blank(style_defaults, name, rows = 30, columns = 20, theme) {
218
+ const sheet = new Sheet(style_defaults);
219
+ if (theme) {
220
+ sheet.UpdateDefaultRowHeight(theme);
221
+ }
222
+ if (name) {
223
+ sheet.name = name;
224
+ }
225
+ rows = Math.max(rows, 1);
226
+ columns = Math.max(columns, 1);
227
+ sheet.cells.EnsureCell({ row: rows - 1, column: columns - 1 });
228
+ return sheet;
229
+ }
230
+ /**
231
+ * update old-style alignment constants to the new symbolic values.
232
+ * updates in place.
233
+ */
234
+ static UpdateStyle(properties) {
235
+ if (typeof properties.horizontal_align === 'number') {
236
+ const members = [
237
+ '', // Style.HorizontalAlign.None,
238
+ 'left', // Style.HorizontalAlign.Left,
239
+ 'center', // Style.HorizontalAlign.Center,
240
+ 'right', // Style.HorizontalAlign.Right,
241
+ ];
242
+ properties.horizontal_align = members[properties.horizontal_align] || undefined;
243
+ }
244
+ if (typeof properties.vertical_align === 'number') {
245
+ const members = [
246
+ '', // Style.VerticalAlign.None,
247
+ 'top', // Style.VerticalAlign.Top,
248
+ 'bottom', // Style.VerticalAlign.Bottom,
249
+ 'middle', // Style.VerticalAlign.Middle,
250
+ ];
251
+ properties.vertical_align = members[properties.vertical_align] || undefined;
252
+ }
253
+ }
254
+ /**
255
+ * deserialize json representation. returns new instance or updates
256
+ * passed instance.
257
+ *
258
+ * FIXME: why not make this an instance method, always call on new instance?
259
+ *
260
+ * @param hints UpdateHints supports partial deserialization/replacement
261
+ * if we know there are only minor changes (as part of undo/redo, probably)
262
+ */
263
+ static FromJSON(json, style_defaults, sheet) {
264
+ const source = (typeof json === 'string') ?
265
+ JSON.parse(json) : json;
266
+ const unflatten_numeric_array = (target, data) => {
267
+ Object.keys(data).forEach((key) => {
268
+ const index = Number(key) || 0;
269
+ target[index] = data[key];
270
+ });
271
+ };
272
+ if (!sheet) {
273
+ sheet = new Sheet(style_defaults);
274
+ }
275
+ if (source.default_column_width) {
276
+ sheet.default_column_width = source.default_column_width;
277
+ }
278
+ if (source.default_row_height) {
279
+ sheet.default_row_height = source.default_row_height;
280
+ }
281
+ if (source.conditional_formats) {
282
+ sheet.conditional_formats = source.conditional_formats;
283
+ }
284
+ sheet.data_validation = (source.data_validations || []).map(validation => ({
285
+ ...validation,
286
+ target: (validation.target || []).map(target => new Area(target.start, target.end)),
287
+ }));
288
+ // persist ID, name
289
+ if (source.id) {
290
+ sheet.id = source.id;
291
+ }
292
+ if (source.name) {
293
+ sheet.name = source.name;
294
+ }
295
+ if (source.tab_color) {
296
+ sheet.tab_color = source.tab_color;
297
+ }
298
+ if (source.background_image) {
299
+ sheet.background_image = source.background_image;
300
+ }
301
+ // FIXME: this should only be done on load (and possibly paste).
302
+ // we don't need to do it on every parse, which also happens on
303
+ // undo and some other things.
304
+ const patch_style = (style) => {
305
+ // this part is for back compat with older color schemes, it
306
+ // could theoretically come out if we don't care (or maybe have a tool)
307
+ // UPDATE for updated font properties
308
+ const ref = style;
309
+ this.UpdateStyle(ref);
310
+ if (ref.font_size_value || ref.font_size_unit) {
311
+ ref.font_size = {
312
+ unit: ref.font_size_unit || 'pt',
313
+ value: ref.font_size_value || 10,
314
+ };
315
+ ref.font_size_unit = undefined;
316
+ ref.font_size_value = undefined;
317
+ }
318
+ if (ref.font_bold) {
319
+ ref.bold = true;
320
+ ref.font_bold = undefined;
321
+ }
322
+ if (ref.font_italic) {
323
+ ref.italic = true;
324
+ ref.font_italic = undefined;
325
+ }
326
+ if (ref.font_underline) {
327
+ ref.underline = true;
328
+ ref.font_underline = undefined;
329
+ }
330
+ if (ref.font_strike) {
331
+ ref.strike = true;
332
+ ref.font_strike = undefined;
333
+ }
334
+ if (ref.text_color) {
335
+ if (ref.text_color !== 'none') {
336
+ ref.text = { text: ref.text_color };
337
+ }
338
+ ref.text_color = undefined; // will get cleared, eventually
339
+ }
340
+ if (ref.background) {
341
+ if (ref.background !== 'none') {
342
+ ref.fill = { text: ref.background };
343
+ }
344
+ ref.background = undefined; // ibid
345
+ }
346
+ if (ref.border_top_color) {
347
+ if (ref.border_top_color !== 'none') {
348
+ ref.border_top_fill = { text: ref.border_top_color };
349
+ }
350
+ ref.border_top_color = undefined;
351
+ }
352
+ if (ref.border_left_color) {
353
+ if (ref.border_left_color !== 'none') {
354
+ ref.border_left_fill = { text: ref.border_left_color };
355
+ }
356
+ ref.border_left_color = undefined;
357
+ }
358
+ if (ref.border_bottom_color) {
359
+ if (ref.border_bottom_color !== 'none') {
360
+ ref.border_bottom_fill = { text: ref.border_bottom_color };
361
+ }
362
+ ref.border_bottom_color = undefined;
363
+ }
364
+ if (ref.border_right_color) {
365
+ if (ref.border_right_color !== 'none') {
366
+ ref.border_right_fill = { text: ref.border_right_color };
367
+ }
368
+ ref.border_right_color = undefined;
369
+ }
370
+ };
371
+ // use the new name, if available; fall back to the old name, and because
372
+ // that's now optional, add a default.
373
+ const cell_style_refs = source.styles || source.cell_style_refs || [];
374
+ /*
375
+ const cell_style_refs = source.cell_style_refs;
376
+ */
377
+ for (const entry of cell_style_refs) {
378
+ patch_style(entry);
379
+ }
380
+ // styles (part 1) -- moved up in case we use inlined style refs
381
+ // so this is converting "ref" (number) to "style" (properties)...
382
+ // in the same object. why do we do this here, and early?
383
+ sheet.cell_style = [];
384
+ if (cell_style_refs) {
385
+ (source.cell_styles || []).forEach((cell_style) => {
386
+ if (typeof cell_style.ref === 'number') {
387
+ cell_style.style =
388
+ JSON.parse(JSON.stringify(cell_style_refs[cell_style.ref])); // clone
389
+ }
390
+ });
391
+ }
392
+ // data: cells (moved after style)
393
+ sheet.cells.FromJSON(source.data);
394
+ if (source.rows)
395
+ sheet.cells.EnsureRow(source.rows - 1);
396
+ if (source.columns)
397
+ sheet.cells.EnsureColumn(source.columns - 1);
398
+ // new style stuff
399
+ // different handling for nested, flat, but we only have to
400
+ // check once because data is either nested or it isn't.
401
+ if (source.data) {
402
+ if (IsFlatDataArray(source.data)) {
403
+ for (const entry of source.data) {
404
+ if (entry.style_ref) {
405
+ if (!sheet.cell_style[entry.column])
406
+ sheet.cell_style[entry.column] = [];
407
+ sheet.cell_style[entry.column][entry.row] = // entry.style;
408
+ JSON.parse(JSON.stringify(cell_style_refs[entry.style_ref])); // clone
409
+ }
410
+ }
411
+ }
412
+ else {
413
+ if (IsNestedRowArray(source.data)) {
414
+ for (const block of source.data) {
415
+ const row = block.row;
416
+ for (const entry of block.cells) {
417
+ const column = entry.column;
418
+ if (entry.style_ref) {
419
+ if (!sheet.cell_style[column])
420
+ sheet.cell_style[column] = [];
421
+ sheet.cell_style[column][row] = // entry.style;
422
+ JSON.parse(JSON.stringify(cell_style_refs[entry.style_ref])); // clone
423
+ }
424
+ }
425
+ }
426
+ }
427
+ else {
428
+ for (const block of source.data) {
429
+ const column = block.column;
430
+ for (const entry of block.cells) {
431
+ const row = entry.row;
432
+ if (entry.style_ref) {
433
+ if (!sheet.cell_style[column])
434
+ sheet.cell_style[column] = [];
435
+ sheet.cell_style[column][row] = // entry.style;
436
+ JSON.parse(JSON.stringify(cell_style_refs[entry.style_ref])); // clone
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+ // freeze
444
+ sheet.freeze.rows = 0;
445
+ sheet.freeze.columns = 0;
446
+ if (source.freeze) {
447
+ sheet.freeze.rows = source.freeze.rows || 0;
448
+ sheet.freeze.columns = source.freeze.columns || 0;
449
+ }
450
+ // scroll, optionally
451
+ sheet.scroll_offset = source.scroll ? { ...source.scroll } : { x: 0, y: 0 };
452
+ // wrap up styles
453
+ for (const cell_style of (source.cell_styles || [])) {
454
+ if (cell_style.style) {
455
+ if (!sheet.cell_style[cell_style.column])
456
+ sheet.cell_style[cell_style.column] = [];
457
+ sheet.cell_style[cell_style.column][cell_style.row] = cell_style.style;
458
+ // update for blocks
459
+ // these are styles, not references... not sure why we translated
460
+ // (above) but if so, we probably need to clone
461
+ if (cell_style.rows) {
462
+ for (let r = 1; r < cell_style.rows; r++) {
463
+ sheet.cell_style[cell_style.column][cell_style.row + r] =
464
+ JSON.parse(JSON.stringify(cell_style.style));
465
+ }
466
+ }
467
+ }
468
+ }
469
+ sheet.sheet_style = source.sheet_style || {};
470
+ // sheet.row_styles = source.row_style;
471
+ // sheet.column_styles = source.column_style;
472
+ // these are NOT arrays atm. that might be a problem (might not). I think
473
+ // this was accidental. when running, we don't care, because empty array
474
+ // indexes don't consume memory (AFAIK). when serializing, we do care, but
475
+ // how we serialize shouldn't impact how we operate at runtime.
476
+ // it breaks when we do patching (below), although we could just fix
477
+ // patching. also TODO: merge patching with the map routine.
478
+ sheet.column_styles = {};
479
+ sheet.row_styles = {};
480
+ const MapStyles = (source_list, target_list) => {
481
+ for (const key of Object.keys(source_list)) {
482
+ const index = Number(key);
483
+ const value = source_list[index];
484
+ if (typeof value === 'number') {
485
+ const properties = cell_style_refs[value];
486
+ if (properties) {
487
+ target_list[index] = JSON.parse(JSON.stringify(properties)); // clone jic
488
+ patch_style(target_list[index]);
489
+ }
490
+ }
491
+ else if (value) {
492
+ target_list[index] = value;
493
+ patch_style(target_list[index]);
494
+ }
495
+ }
496
+ };
497
+ MapStyles(source.row_style, sheet.row_styles);
498
+ MapStyles(source.column_style, sheet.column_styles);
499
+ /*
500
+ for (const key of Object.keys(source.column_style)) {
501
+ const index = Number(key);
502
+ const value = source.column_style[index];
503
+ if (typeof value === 'number') {
504
+ const properties = cell_style_refs[value];
505
+ if (properties) {
506
+ sheet.column_styles[index] = JSON.parse(JSON.stringify(properties)); // clone jic
507
+ }
508
+ }
509
+ else {
510
+ sheet.column_styles[index] = value;
511
+ }
512
+ }
513
+ */
514
+ sheet.row_pattern = source.row_pattern || [];
515
+ // patch other styles
516
+ patch_style(sheet.sheet_style || {});
517
+ for (const entry of sheet.row_pattern) {
518
+ patch_style(entry);
519
+ }
520
+ /*
521
+ for (const key of Object.keys(sheet.column_styles)) {
522
+ patch_style(sheet.column_styles[key as any]);
523
+ }
524
+
525
+ for (const key of Object.keys(sheet.row_styles)) {
526
+ patch_style(sheet.row_styles[key as any]);
527
+ }
528
+ */
529
+ // ok
530
+ sheet.row_height_map = new Map();
531
+ if (source.row_height) {
532
+ for (const [key, value] of Object.entries(source.row_height)) {
533
+ sheet.row_height_map.set(Number(key), value);
534
+ }
535
+ }
536
+ if (sheet.row_height_map.size > 0) {
537
+ const max = Math.max(...sheet.row_height_map.keys());
538
+ sheet.cells.EnsureRow(max);
539
+ }
540
+ sheet.column_width_ = [];
541
+ unflatten_numeric_array(sheet.column_width_, source.column_width || {});
542
+ if (sheet.column_width_.length) {
543
+ sheet.cells.EnsureColumn(sheet.column_width_.length - 1);
544
+ }
545
+ // NOTE: we're padding out rows/columns here to be under annotations,
546
+ // otherwise the pruning may have removed them. it would probably be
547
+ // preferable to not prune them... that shouldn't add much extra data
548
+ // because it would just be the number.
549
+ // FIXME
550
+ sheet.annotations = (source.annotations || []).map((entry) => new Annotation(entry));
551
+ if (source.selection) {
552
+ // copy to ensure there's no link to random object
553
+ sheet.selection = JSON.parse(JSON.stringify(source.selection));
554
+ }
555
+ sheet.visible = true; // default
556
+ if (typeof source.visible !== 'undefined') {
557
+ sheet.visible = !!source.visible;
558
+ }
559
+ return sheet;
560
+ }
561
+ /** add a data validation. */
562
+ AddValidation(validation) {
563
+ this.data_validation.push({
564
+ ...validation,
565
+ target: (validation.target || []).map(target => new Area(target.start, target.end)), // ensure class instance
566
+ });
567
+ }
568
+ /**
569
+ * remove validations from area. must be an exact match (FIXME).
570
+ * if there are multiple areas, only remove the matching area.
571
+ */
572
+ RemoveValidations(area) {
573
+ const check = new Area(area.start, area.end);
574
+ this.data_validation = this.data_validation.filter(validation => {
575
+ validation.target = validation.target.filter(compare => !check.Equals2(compare));
576
+ return validation.target.length > 0;
577
+ });
578
+ }
579
+ /** return data validation(s) that apply to a given address */
580
+ GetValidation(address) {
581
+ // switch to imperative
582
+ const list = [];
583
+ for (const entry of this.data_validation) {
584
+ for (const area of entry.target) {
585
+ if (area.Contains(address)) {
586
+ list.push(entry);
587
+ break;
588
+ }
589
+ }
590
+ }
591
+ return list;
592
+ }
593
+ Activate(DOM) {
594
+ // load background image, if set
595
+ if (this.background_image) {
596
+ const resource = ValidateURI(this.background_image);
597
+ if (resource) {
598
+ this._image = DOM.Create('img');
599
+ this._image.src = resource;
600
+ }
601
+ // this._image = image_store.Get(this.background_image);
602
+ }
603
+ }
604
+ // --- public methods -------------------------------------------------------
605
+ MergeCells(area) {
606
+ // FIXME: it's an error if this area includes some
607
+ // (but not all) of another merge area.
608
+ // ...
609
+ // assuming we're good to go...
610
+ area = area.Clone();
611
+ // so this needs the address, in order to test if it's the head;
612
+ // but we know the head will always be the first one tested (correct?)
613
+ const cells = [...this.cells.Iterate(area, true)];
614
+ for (const [index, cell] of cells.entries()) {
615
+ cell.merge_area = area;
616
+ cell.render_clean = [];
617
+ if (index) {
618
+ cell.Reset();
619
+ }
620
+ }
621
+ /*
622
+ for (const {column, row, cell} of this.cells.IterateArea(area, true)) {
623
+ cell.merge_area = area;
624
+ cell.render_clean = [];
625
+
626
+ // clear data in !head
627
+ if (column !== area.start.column || row !== area.start.row) cell.Reset();
628
+ }
629
+ */
630
+ /*
631
+ this.cells.Apply(area, (cell, c, r) => {
632
+ cell.merge_area = area;
633
+ cell.render_clean = [];
634
+
635
+ // clear data in !head
636
+ if (c !== area.start.column || r !== area.start.row) cell.Reset();
637
+ }, true);
638
+ */
639
+ }
640
+ UnmergeCells(area) {
641
+ // this _must_ be the full merge area. to get it, just get
642
+ // the merge property from a particular cell or cells.
643
+ // let's check:
644
+ for (const cell of this.cells.Iterate(area, false)) {
645
+ if (!cell.merge_area || !area.Equals(cell.merge_area)) {
646
+ console.warn('area mismatch');
647
+ return;
648
+ }
649
+ }
650
+ /*
651
+ let match = true;
652
+
653
+ this.cells.Apply(area, (cell) => {
654
+ match = match && !!cell.merge_area && area.Equals(cell.merge_area);
655
+ }, false);
656
+
657
+ if (!match) {
658
+ console.warn('area mismatch');
659
+ return;
660
+ }
661
+ */
662
+ for (const cell of this.cells.Iterate(area, false)) {
663
+ cell.merge_area = undefined;
664
+ cell.render_clean = [];
665
+ }
666
+ /*
667
+ this.cells.Apply(area, (cell) => {
668
+ cell.merge_area = undefined;
669
+ cell.render_clean = [];
670
+ }, false);
671
+ */
672
+ }
673
+ /**
674
+ * FIXME: this is called in the ctor, which made sense when sheets
675
+ * were more ephemeral. now that we update a single instance, rather
676
+ * than create new instances, we lose this behavior. we should call
677
+ * this when we change sheet style.
678
+ *
679
+ * actually this should just move to theme, no? as long as sheet has
680
+ * a reference to theme. for headless instances that would just use
681
+ * theme defaults, which should be appropriate.
682
+ *
683
+ * I guess the original idea was that sheet style might be used to
684
+ * base row height on sheet font size? not sure if that's how it actually
685
+ * plays out, since this is only called in the ctor (or equivalent) or when
686
+ * theme is updated (but not when sheet style is updated). might need some
687
+ * thought.
688
+ *
689
+ */
690
+ UpdateDefaultRowHeight(theme, scale = 1) {
691
+ // this guard is here because this is called by sheet directly, so maybe
692
+ // in headless context? would make more sense to just not call it
693
+ if (typeof window !== 'undefined') {
694
+ const composite = Style.Composite([this.default_style_properties, this.sheet_style]);
695
+ const font_info = Style.CompositeFont(theme.grid_cell_font_size, composite, scale, theme);
696
+ const metrics = GetFonrMetrics(font_info.font, font_info.variants);
697
+ const height = metrics.height * 1.25; // ??
698
+ // const measurement = Measurement.MeasureText(Style.Font2(composite, 1, theme).font, 'M');
699
+ // const height = Math.round(measurement.height * 1.4);
700
+ // console.info({height, default: this.default_row_height});
701
+ if (this.default_row_height < height) {
702
+ this.default_row_height = height;
703
+ }
704
+ }
705
+ }
706
+ /**
707
+ * deprecated (or give me a reason to keep it)
708
+ * KEEP IT: just maintain flexibility, it has very low cost
709
+ */
710
+ SetRowHeaders(headers) {
711
+ this.row_headers = headers.map(value => value === undefined ? '' : value.toString());
712
+ if (this.row_headers) {
713
+ this.cells.EnsureRow(this.row_headers.length - 1);
714
+ }
715
+ }
716
+ /**
717
+ * deprecated (or give me a reason to keep it)
718
+ * KEEP IT: just maintain flexibility, it has very low cost
719
+ */
720
+ SetColumnHeaders(headers) {
721
+ this.column_headers = headers.map(value => value === undefined ? '' : value.toString());
722
+ if (headers) {
723
+ this.cells.EnsureColumn(headers.length - 1);
724
+ }
725
+ }
726
+ /**
727
+ * deprecated
728
+ * KEEP IT: just maintain flexibility, it has very low cost
729
+ */
730
+ RowHeader(row) {
731
+ if (this.row_headers) {
732
+ if (this.row_headers.length > row)
733
+ return this.row_headers[row];
734
+ return '';
735
+ }
736
+ return row + 1;
737
+ }
738
+ /**
739
+ * deprecated
740
+ * KEEP IT: just maintain flexibility, it has very low cost
741
+ * (we did drop the multiple rows, though)
742
+ */
743
+ ColumnHeader(column) {
744
+ let s = '';
745
+ if (this.column_headers) {
746
+ if (this.column_headers.length > column)
747
+ return this.column_headers[column];
748
+ return '';
749
+ }
750
+ for (;;) {
751
+ const c = column % 26;
752
+ s = String.fromCharCode(65 + c) + s;
753
+ column = Math.floor(column / 26);
754
+ if (column)
755
+ column--;
756
+ else
757
+ break;
758
+ }
759
+ return s;
760
+ }
761
+ GetRowHeight(row) {
762
+ const height = this.row_height_map.get(row);
763
+ if (typeof height === 'undefined')
764
+ return this.default_row_height;
765
+ return height;
766
+ }
767
+ SetRowHeight(row, height) {
768
+ this.row_height_map.set(row, height);
769
+ this.cells.EnsureRow(row);
770
+ return height;
771
+ }
772
+ GetColumnWidth(column) {
773
+ const width = this.column_width_[column];
774
+ if (typeof width === 'undefined')
775
+ return this.default_column_width;
776
+ return width;
777
+ }
778
+ SetColumnWidth(column, width) {
779
+ this.column_width_[column] = width;
780
+ this.cells.EnsureColumn(column);
781
+ return width;
782
+ }
783
+ /**
784
+ * returns set of properties in B that differ from A. returns
785
+ * property values from B.
786
+ *
787
+ * this is the function I could never get to work inline for
788
+ * CellStyle -- not sure why it works better with a generic
789
+ * function (although the partial here is new, so maybe it's that?)
790
+ *
791
+ * seems to be related to
792
+ * https://github.com/microsoft/TypeScript/pull/30769
793
+ *
794
+ */
795
+ Delta(A, B) {
796
+ const result = {};
797
+ // keys that are in either object. this will result in some
798
+ // duplication, probably not too bad. could precompute array? (...)
799
+ // you could do that using a composite object, but would be wasteful.
800
+ // would look good in typescript but generate extra javascript. might
801
+ // still be faster, though? (...)
802
+ const keys = [...Object.keys(A), ...Object.keys(B)];
803
+ // FIXME: should check if B[key] is undefined, in which case you don't
804
+ // want it? (...) that seems appropriate, but since the method we are
805
+ // replacing did not do that, I'm hesitant to do it now
806
+ for (const key of keys) {
807
+ const a = A[key];
808
+ const b = B[key];
809
+ // we are not checking for arrays, that's not a consideration atm
810
+ if (typeof a === 'object' && typeof b === 'object') {
811
+ // is this faster than checking properties?
812
+ // especially if we know the list?
813
+ if (JSON.stringify(a) !== JSON.stringify(b)) {
814
+ result[key] = b;
815
+ }
816
+ }
817
+ else if (a !== b) {
818
+ result[key] = b;
819
+ }
820
+ //if (A[key] !== B[key]) {
821
+ // result[key] = B[key];
822
+ //}
823
+ }
824
+ return result;
825
+ }
826
+ /**
827
+ * updates cell styles. flushes cached style.
828
+ *
829
+ * @param delta merge with existing properties (we will win conflicts)
830
+ * @param inline this is part of another operation, don't do any undo/state updates
831
+ */
832
+ UpdateCellStyle(address, properties, delta = true) {
833
+ // so what this is doing is constructing two merge stacks: one including
834
+ // the cell style, and one without. any deltas among the two are the cell
835
+ // style. the aim here is to remove properties that would be duplicative
836
+ // because they stack, so if the base sheet has color=red, there is no
837
+ // reason to apply that to the cell as well.
838
+ const { row, column } = address;
839
+ if (!this.cell_style[column])
840
+ this.cell_style[column] = [];
841
+ const underlying = this.CompositeStyleForCell(address, false, false, undefined, false);
842
+ const merged = Style.Composite([
843
+ this.default_style_properties,
844
+ underlying,
845
+ Style.Merge(this.cell_style[column][row] || {}, properties, delta),
846
+ ]);
847
+ const composite = this.Delta(underlying, merged);
848
+ /*
849
+ // this is type "any" because of the assignment, below, which fails
850
+ // otherwise. however this could be done with spread assignments? (...)
851
+ // A: no, it's not merging them, it is looking for deltas.
852
+ // ...but, what if you filtered? (...) [A] how?
853
+
854
+ // I think the only way to do it with types would be to use delete, which
855
+ // somehow seems wasteful and slow (although I have not validated that)
856
+
857
+ const composite: any = {};
858
+
859
+ // find properties that are different, those will be the cell style.
860
+
861
+ for (const key of Object.keys(merged) as Style.PropertyKeys[]) {
862
+ if (merged[key] !== underlying[key]) {
863
+ composite[key] = merged[key];
864
+ }
865
+ }
866
+ for (const key of Object.keys(underlying) as Style.PropertyKeys[]) {
867
+ if (merged[key] !== underlying[key]) {
868
+ composite[key] = merged[key];
869
+ }
870
+ }
871
+ */
872
+ this.cell_style[column][row] = composite; // merged;
873
+ // targeted flush
874
+ // this.CellData(address).FlushStyle();
875
+ this.BleedFlush({ start: address, end: address });
876
+ }
877
+ /**
878
+ * invalidate sets the "render dirty" flag on cells, whether there
879
+ * is any change or not. we are currently using it to force rendering
880
+ * when border/background changes, and we need to handle bleed into
881
+ * neighboring cells.
882
+ */
883
+ Invalidate(area) {
884
+ // this.cells.Apply(this.RealArea(area), cell => cell.render_clean = []);
885
+ for (const cell of this.cells.Iterate(this.RealArea(area), false)) {
886
+ cell.render_clean = [];
887
+ }
888
+ }
889
+ /**
890
+ *
891
+ * @param area
892
+ * @param style
893
+ * @param delta
894
+ * @param render LEGACY PARAMETER NOT USED
895
+ */
896
+ UpdateAreaStyle(area, style = {}, delta = true) {
897
+ if (!area)
898
+ return;
899
+ if (area.entire_sheet) {
900
+ this.UpdateSheetStyle(style, delta);
901
+ }
902
+ else if (area.entire_column) {
903
+ for (let column = area.start.column; column <= area.end.column; column++) {
904
+ this.UpdateColumnStyle(column, style, delta);
905
+ }
906
+ }
907
+ else if (area.entire_row) {
908
+ for (let row = area.start.row; row <= area.end.row; row++) {
909
+ this.UpdateRowStyle(row, style, delta);
910
+ }
911
+ }
912
+ else {
913
+ // area.Array().forEach((address) => this.UpdateCellStyle(address, style, delta));
914
+ for (const address of area) {
915
+ this.UpdateCellStyle(address, style, delta);
916
+ }
917
+ }
918
+ }
919
+ /**
920
+ * checks if the given cell has been assigned a specific style, either for
921
+ * the cell itself, or for row and column.
922
+ */
923
+ HasCellStyle(address) {
924
+ return !!((this.cell_style[address.column] && this.cell_style[address.column][address.row])
925
+ || this.row_styles[address.row]
926
+ || this.column_styles[address.column]
927
+ || this.row_pattern.length);
928
+ }
929
+ /**
930
+ * returns the next non-hidden column. so if you are column C (2) and columns
931
+ * D, E, and F are hidden, then it will return 6 (G).
932
+ */
933
+ NextVisibleColumn(column) {
934
+ for (++column; this.column_width_[column] === 0; column++) { /* */ }
935
+ return column;
936
+ }
937
+ /**
938
+ * @see NextVisibleColumn
939
+ * because this one goes left, it may return -1 meaning you are at the left edge
940
+ */
941
+ PreviousVisibleColumn(column) {
942
+ for (--column; column >= 0 && this.column_width_[column] === 0; column--) { /* */ }
943
+ return column;
944
+ }
945
+ /**
946
+ * @see NextVisibleColumn
947
+ */
948
+ NextVisibleRow(row) {
949
+ for (++row; this.GetRowHeight(row) === 0; row++) { /* */ }
950
+ return row;
951
+ }
952
+ /**
953
+ * @see PreviousVisibleColumn
954
+ */
955
+ PreviousVisibleRow(row) {
956
+ for (--row; row >= 0 && this.GetRowHeight(row) === 0; row--) { /* */ }
957
+ return row;
958
+ }
959
+ /**
960
+ * if this cell is part of a table, get row information -- is this
961
+ * an alternate row, is it the header, is it the last (visible) row
962
+ *
963
+ * @param table
964
+ * @param row
965
+ * @returns
966
+ */
967
+ TableRow(table, row) {
968
+ const result = {
969
+ alternate: false,
970
+ header: (row === table.area.start.row),
971
+ last: false,
972
+ totals: (table.totals_row && row === table.area.end.row),
973
+ };
974
+ // can short circuit here
975
+ if (result.header || result.totals) {
976
+ return result;
977
+ }
978
+ // oh these loops are a problem if the table is very large. need to
979
+ // address, maybe we can cache? not sure. as a hint, we have a list
980
+ // of non-default row heights.
981
+ // how we handle last row depends on totals. if we have a totals
982
+ // row, and it's visible, we don't need to do the "last row" thing.
983
+ const totals_visible = (table.totals_row && (this.GetRowHeight(table.area.end.row) > 0));
984
+ // this one is probably ok
985
+ if (!totals_visible) {
986
+ let last = table.area.end.row;
987
+ for (; last >= table.area.start.row; last--) {
988
+ if (this.GetRowHeight(last)) {
989
+ result.last = (last === row);
990
+ break;
991
+ }
992
+ }
993
+ }
994
+ // this is an improvement if the table is mostly not hidden. but
995
+ // if the table is mostly hidden we'll run into the same problem
996
+ // again. not sure the most effective way to address this. we probably
997
+ // need to cache this information somewhere.
998
+ // ACTUALLY this does not work (at least not the way you think it
999
+ // does). row_height_ is a sparse array so iterating will still
1000
+ // check every skipped index.
1001
+ // OK now it's a map
1002
+ const start = table.area.start.row + 1; // (table.headers ? 1 : 0);
1003
+ let delta = row - start;
1004
+ for (const [index, height] of this.row_height_map.entries()) {
1005
+ if (index < start) {
1006
+ continue;
1007
+ }
1008
+ if (index > table.area.end.row) {
1009
+ break;
1010
+ }
1011
+ if (!height) {
1012
+ delta--;
1013
+ }
1014
+ }
1015
+ result.alternate = (delta % 2 === 1);
1016
+ // this one looks bad
1017
+ /*
1018
+ let start = table.area.start.row + 1 ; // (table.headers ? 1 : 0);
1019
+
1020
+ for ( ; start <= table.area.end.row; start++ ) {
1021
+ if (!this.GetRowHeight(start)) {
1022
+ continue;
1023
+ }
1024
+
1025
+ result.alternate = !result.alternate;
1026
+ if (start === row) {
1027
+ break;
1028
+ }
1029
+ }
1030
+ */
1031
+ return result;
1032
+ }
1033
+ /**
1034
+ * returns style properties for cells surrounding this cell,
1035
+ * mapped like a number pad:
1036
+ *
1037
+ * +---+---+---+
1038
+ * | 7 | 8 | 9 |
1039
+ * +---+---+---+
1040
+ * | 4 | X | 6 |
1041
+ * +---+---+---+
1042
+ * | 1 | 2 | 3 |
1043
+ * +---+---+---+
1044
+ *
1045
+ * presuming you already have X (5). this is called by renderer, we
1046
+ * move it here so we can inline the next/previous loops.
1047
+ *
1048
+ */
1049
+ SurroundingStyle(address, table) {
1050
+ const map = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
1051
+ // FIXME: what about merges? (...)
1052
+ let column_right = address.column + 1;
1053
+ let column_left = address.column - 1;
1054
+ let row_below = address.row + 1;
1055
+ let row_above = address.row - 1;
1056
+ for (; this.column_width_[column_right] === 0; column_right++) { /* */ }
1057
+ for (; this.GetRowHeight(row_below) === 0; row_below++) { /* */ }
1058
+ for (; column_left >= 0 && this.column_width_[column_left] === 0; column_left--) { /* */ }
1059
+ for (; row_above >= 0 && this.GetRowHeight(row_above) === 0; row_above--) { /* */ }
1060
+ if (column_left >= 0 && row_above >= 0) {
1061
+ map[7] = this.CellStyleData({ row: row_above, column: column_left }, table) || {};
1062
+ }
1063
+ if (column_left >= 0) {
1064
+ map[4] = this.CellStyleData({ row: address.row, column: column_left }, table) || {};
1065
+ map[1] = this.CellStyleData({ row: row_below, column: column_left }, table) || {};
1066
+ }
1067
+ if (row_above >= 0) {
1068
+ map[8] = this.CellStyleData({ row: row_above, column: address.column }, table) || {};
1069
+ map[9] = this.CellStyleData({ row: row_above, column: column_right }, table) || {};
1070
+ }
1071
+ map[6] = this.CellStyleData({ row: address.row, column: column_right }, table) || {};
1072
+ map[2] = this.CellStyleData({ row: row_below, column: address.column }, table) || {};
1073
+ map[3] = this.CellStyleData({ row: row_below, column: column_right }, table) || {};
1074
+ return map;
1075
+ }
1076
+ /**
1077
+ * get style only. as noted in the comment to `CellData` there used to be
1078
+ * no case where this was useful without calculated value as well; but we
1079
+ * now have a case: fixing borders by checking neighboring cells. (testing).
1080
+ *
1081
+ * switching from null to undefined as "missing" type
1082
+ *
1083
+ * UPDATE: this is a convenient place to do table formatting. table
1084
+ * formatting is complicated because it's variable; it depends on row
1085
+ * visibility so we can't cache it. this is a good spot because we're
1086
+ * already calling this function when doing border rendering; we can call
1087
+ * it separately, if necessary, when rendering cells.
1088
+ *
1089
+ * table formats are applied on top of cell formats, after compositing,
1090
+ * and we don't preserve the style.
1091
+ *
1092
+ */
1093
+ CellStyleData(address, default_table_theme) {
1094
+ // don't create if it doesn't exist
1095
+ const cell = this.cells.GetCell(address);
1096
+ if (!cell) {
1097
+ return undefined;
1098
+ }
1099
+ // composite style if necessary
1100
+ if (!cell.style) {
1101
+ const index = this.GetStyleIndex(this.CompositeStyleForCell(address));
1102
+ cell.style = this.style_map[index];
1103
+ }
1104
+ if (cell.table) {
1105
+ const table_theme = cell.table.theme || default_table_theme;
1106
+ if (table_theme) {
1107
+ let style = JSON.parse(JSON.stringify(cell.style));
1108
+ const data = this.TableRow(cell.table, address.row);
1109
+ if (data.header) {
1110
+ if (table_theme.header) {
1111
+ style = Style.Composite([style, table_theme.header]);
1112
+ }
1113
+ }
1114
+ else if (data.totals) {
1115
+ // like headers, totals is outside of the alternating rows thing
1116
+ if (table_theme.total) {
1117
+ style = Style.Composite([style, table_theme.total]);
1118
+ }
1119
+ }
1120
+ else {
1121
+ if (data.alternate) {
1122
+ if (table_theme.odd) {
1123
+ style = Style.Composite([style, table_theme.odd]);
1124
+ }
1125
+ }
1126
+ else {
1127
+ if (table_theme.even) {
1128
+ style = Style.Composite([style, table_theme.even]);
1129
+ }
1130
+ }
1131
+ }
1132
+ /*
1133
+ if (data.last) {
1134
+ if (table_styles.footer) {
1135
+ style = Style.Composite([style, table_styles.footer]);
1136
+ }
1137
+ }
1138
+ */
1139
+ return style;
1140
+ }
1141
+ }
1142
+ return cell.style;
1143
+ }
1144
+ /**
1145
+ * accessor to get cell style without row pattern -- for cut/copy
1146
+ * @param address
1147
+ */
1148
+ GetCopyStyle(address) {
1149
+ return this.CompositeStyleForCell(address, true, false, undefined, false);
1150
+ }
1151
+ /**
1152
+ * wrapper for getting all relevant render data.
1153
+ * TODO: merge in "FormattedValue". restructure data so we don't have
1154
+ * two caches (formatted and calculated).
1155
+ *
1156
+ * NOTE: we removed "GetCellStyle" in favor of this function. the rationale
1157
+ * is that there are no reasonable cases where someone looks up the style
1158
+ * without that being a next step to (or in reasonable proximity to)
1159
+ * rendering. so it's reasonable to call this function even if it's in
1160
+ * advance of rendering.
1161
+ *
1162
+ * NOTE: that applies to the "GetCellFormula" and "GetCellValue" functions
1163
+ * as well -- so remove those too.
1164
+ *
1165
+ * NOTE: actually GetCellFormula resolves array formulae, so maybe not --
1166
+ * or the caller needs to check.
1167
+ *
1168
+ */
1169
+ CellData(address) {
1170
+ const cell = this.cells.EnsureCell(address);
1171
+ // if cell has rendered type (i.e. not undefined), then it has
1172
+ // complete render data and we can return it as-is.
1173
+ if (cell.rendered_type)
1174
+ return cell;
1175
+ // otherwise we need to render it. if we have a calculated value, use that.
1176
+ let type;
1177
+ let value;
1178
+ if (cell.calculated_type) {
1179
+ value = cell.calculated;
1180
+ type = cell.calculated_type;
1181
+ }
1182
+ else {
1183
+ value = cell.value;
1184
+ type = cell.type;
1185
+ }
1186
+ // do we have style for this cell? if not, we need to composite it.
1187
+ if (!cell.style) {
1188
+ const index = this.GetStyleIndex(this.CompositeStyleForCell(address));
1189
+ cell.style = this.style_map[index];
1190
+ }
1191
+ // why is this done here? shouldn't it be done by/in the renderer?
1192
+ if (!type || value === null || typeof value === 'undefined') {
1193
+ cell.formatted = '';
1194
+ cell.rendered_type = ValueType.string;
1195
+ }
1196
+ else if (type === ValueType.number) {
1197
+ // IE11. not sure of the effect of this.
1198
+ if (isNaN(value)) {
1199
+ cell.formatted = // Style.Format(cell.style, value); // formats NaN
1200
+ (typeof cell.style.nan === 'undefined') ? 'NaN' : cell.style.nan;
1201
+ }
1202
+ else {
1203
+ cell.formatted = // Style.Format(cell.style, value);
1204
+ this.FormatNumber(value, cell.style.number_format);
1205
+ }
1206
+ cell.rendered_type = ValueType.number;
1207
+ }
1208
+ else if (type === ValueType.error) {
1209
+ cell.formatted = '#' + (value || 'ERR?');
1210
+ cell.rendered_type = ValueType.error;
1211
+ }
1212
+ else if (type === ValueType.boolean) {
1213
+ cell.formatted = value.toString().toUpperCase(); // implicit locale?
1214
+ cell.rendered_type = ValueType.boolean;
1215
+ }
1216
+ else if (type === ValueType.formula && cell.calculated === undefined) {
1217
+ cell.formatted = '';
1218
+ cell.rendered_type = ValueType.string;
1219
+ }
1220
+ else if (type === ValueType.complex) {
1221
+ // formatting complex value (note for searching)
1222
+ // here testing "mathematical italic small i", "𝑖", U+1D456
1223
+ //
1224
+ // I'm not sure this is a good idea, the character might not be available
1225
+ // in a particular font (not sure if those are auto-filled or what)
1226
+ //
1227
+ // what we _should_ do is have a formatting flag (in text part) to
1228
+ // indicate italic, and then render a regular lower-case i in italic.
1229
+ // that also means that if you copy it as text, it's still just a regular
1230
+ // i and not a high-value unicode character. which is helpful.
1231
+ // OK we tried that and it looked like crap. I would like to go back
1232
+ // to using "𝑖" but I'm not sure... maybe a flag>
1233
+ // NOTE: all that moved to NumberFormat
1234
+ const complex = value;
1235
+ if (isNaN(complex.real) || isNaN(complex.imaginary)) {
1236
+ // render nan for nan values
1237
+ cell.formatted = // Style.Format(cell.style, value); // formats NaN
1238
+ (typeof cell.style.nan === 'undefined') ? 'NaN' : cell.style.nan;
1239
+ }
1240
+ else {
1241
+ const format = NumberFormatCache.Get(cell.style.number_format || '', true);
1242
+ cell.formatted = format.FormatComplex(complex);
1243
+ }
1244
+ cell.rendered_type = ValueType.complex;
1245
+ }
1246
+ else if (type === ValueType.dimensioned_quantity) {
1247
+ // is this really what we want? NaN mm? or can we just do NaN?
1248
+ // the reason for the question is that we want to move formatting
1249
+ // of DQ into format, in order that we can do logic on the formatting
1250
+ // side. but that won't work if we're short-circuiting here
1251
+ // actually I guess it's immaterial, NaN mm is effectively === to NaN ft
1252
+ if (isNaN(value.value)) {
1253
+ cell.formatted = // Style.Format(cell.style, value); // formats NaN
1254
+ (typeof cell.style.nan === 'undefined') ? 'NaN' : cell.style.nan;
1255
+ cell.formatted += (` ` + value.unit);
1256
+ }
1257
+ else {
1258
+ const format = NumberFormatCache.Get(cell.style.number_format || '', true);
1259
+ cell.formatted = // Style.Format(cell.style, value);
1260
+ // this.FormatNumber((value as DimensionedQuantity).value, cell.style.number_format);
1261
+ // this.FormatNumber(value, cell.style.number_format);
1262
+ format.FormatDimensionedQuantity(value);
1263
+ }
1264
+ cell.rendered_type = ValueType.dimensioned_quantity; // who cares about rendered_type? (...)
1265
+ }
1266
+ else if (type === ValueType.function) {
1267
+ /*
1268
+ // FIXME: lock down this type (function)
1269
+
1270
+ if ((cell.calculated as any)?.alt) {
1271
+ cell.formatted = (cell.calculated as any).alt.toString();
1272
+ cell.rendered_type = ValueType.string;
1273
+ }
1274
+ else
1275
+ */
1276
+ {
1277
+ cell.formatted = '𝑓()'; // FIXME
1278
+ cell.rendered_type = ValueType.string;
1279
+ }
1280
+ }
1281
+ else {
1282
+ // why is this being treated as a number? (...)
1283
+ // A: it's not, number format has a text section. defaults
1284
+ // to @ (just show the text), but could be different
1285
+ cell.formatted = this.FormatNumber(value, cell.style.number_format);
1286
+ cell.rendered_type = ValueType.string;
1287
+ }
1288
+ // now we can return it
1289
+ return cell;
1290
+ }
1291
+ /**
1292
+ * format number using passed format; gets the actual format object
1293
+ * and calls method. returns a string or array of text parts
1294
+ * (@see treb-format).
1295
+ */
1296
+ FormatNumber(value, format = '') {
1297
+ const formatted = NumberFormatCache.Get(format).FormatParts(value);
1298
+ if (!formatted.length)
1299
+ return '';
1300
+ if (formatted.length === 1 && !formatted[0].flag) {
1301
+ return formatted[0].text || '';
1302
+ }
1303
+ return formatted;
1304
+ }
1305
+ // no references... removing
1306
+ //public ColumnHeaderHeight(): number {
1307
+ // return this.column_header_height || this.default_row_height_x;
1308
+ //}
1309
+ /**
1310
+ * the only place this is called is in a method that shows/hides headers;
1311
+ * it sets the size either to 1 (hidden) or undefined, which uses the
1312
+ * defaults here. that suggests we should have a show/hide method instead.
1313
+ *
1314
+ * @param row_header_width
1315
+ * @param column_header_height
1316
+ */
1317
+ SetHeaderSize(row_header_width = DEFAULT_ROW_HEADER_WIDTH, column_header_height = this.default_row_height) {
1318
+ this.row_header_width = row_header_width;
1319
+ this.column_header_height = column_header_height;
1320
+ }
1321
+ /* *
1322
+ * resize row to match character hight, taking into
1323
+ * account multi-line values.
1324
+ *
1325
+ * UPDATE: since the only caller calls with inline = true, removing
1326
+ * parameter, test, and extra behavior.
1327
+ * /
1328
+ public AutoSizeRow(row: number, theme?: Theme, allow_shrink = true, scale = 1): void {
1329
+
1330
+ let height = this.default_row_height;
1331
+ const padding = 9; // 9?
1332
+
1333
+ for (let column = 0; column < this.cells.columns; column++) {
1334
+
1335
+ const cell = this.CellData({ row, column });
1336
+ const style = JSON.parse(JSON.stringify(cell.style));
1337
+ let text = cell.formatted || '';
1338
+
1339
+ if (typeof text !== 'string') {
1340
+ text = text.map((part) => part.text).join('');
1341
+ }
1342
+
1343
+ if (style && text && text.length) {
1344
+ const lines = text.split(/\n/);
1345
+
1346
+ if (style.font_size) {
1347
+ if (style.font_size.unit === 'em') {
1348
+ const base = theme?.grid_cell_font_size || { unit: 'pt', value: 10 };
1349
+ style.font_size.unit = base.unit;
1350
+ style.font_size.value *= base.value;
1351
+ }
1352
+ }
1353
+ else {
1354
+ style.font_size = theme?.grid_cell_font_size || { unit: 'pt', value: 10 };
1355
+ }
1356
+
1357
+ let target = style.font_size.value;
1358
+ if (style.font_size.unit === 'px') {
1359
+ target = Math.round((style.font_size.value||16) * 300 / 4) / 100;
1360
+ }
1361
+
1362
+ const font_height = // Math.round(this.StyleFontSize(style, default_properties) * 1.5); // it's a start, we still need to measure properly
1363
+ Math.round(target * 1.5 * scale);
1364
+ height = Math.max(height, ((font_height || 10) + padding) * lines.length);
1365
+ }
1366
+ }
1367
+
1368
+ if (!allow_shrink) {
1369
+ const test = this.GetRowHeight(row);
1370
+ if (test >= height) { return; }
1371
+ }
1372
+
1373
+ this.SetRowHeight(row, height);
1374
+
1375
+ }
1376
+ */
1377
+ /** returns the style properties for a given style index */
1378
+ GetStyle(index) {
1379
+ return this.style_map[index];
1380
+ }
1381
+ /* *
1382
+ * if the cell is in an array, returns the array as an Area.
1383
+ * if not, returns falsy (null or undefined).
1384
+ *
1385
+ * FIXME: is this used? seems like the caller could do this
1386
+ * calculation.
1387
+ *
1388
+ * Answer was no, so removed
1389
+ * /
1390
+ public ContainingArray(address: ICellAddress): Area | undefined {
1391
+ const cell = this.cells.GetCell(address);
1392
+ if (cell) return cell.area;
1393
+ return undefined;
1394
+ }
1395
+ */
1396
+ /**
1397
+ *
1398
+ * @param before_row insert before
1399
+ * @param count number to insert
1400
+ */
1401
+ InsertRows(before_row = 0, count = 1) {
1402
+ // this needs to be shared between sheet/cells and the
1403
+ // outside spreadsheet logic. we should not be fixing references,
1404
+ // for example, because we don't have the graph.
1405
+ // we should definitely fix merge heads. also array heads.
1406
+ // also: you cannot insert rows that would break arrays.
1407
+ // if the new row(s) are inside of a merged cell, that cell
1408
+ // consumes the new row(s).
1409
+ // validate we won't break arrays. a new row would break an
1410
+ // array if before_row is in an array and (before_row-1) is
1411
+ // in the same array.
1412
+ if (before_row) {
1413
+ for (let column = 0; column < this.cells.columns; column++) {
1414
+ const cell1 = this.cells.GetCell({ row: before_row - 1, column }, false);
1415
+ if (cell1 && cell1.area) {
1416
+ const cell2 = this.cells.GetCell({ row: before_row, column }, false);
1417
+ if (cell2 && cell2.area && cell2.area.Equals(cell1.area)) {
1418
+ return false; // failed
1419
+ }
1420
+ }
1421
+ }
1422
+ }
1423
+ // this.named_ranges.PatchNamedRanges(0, 0, before_row, count);
1424
+ // ok we can insert...
1425
+ if (count < 0) {
1426
+ this.cells.DeleteRows(before_row, -count);
1427
+ }
1428
+ else {
1429
+ this.cells.InsertRows(before_row, count);
1430
+ }
1431
+ // now we have to fix arrays and merge heads. these lists will keep
1432
+ // track of the _new_ starting address.
1433
+ const merge_heads = {};
1434
+ const array_heads = {};
1435
+ // const table_heads: Record<string, Table> = {};
1436
+ // now grab arrays and merge heads that are below the new rows
1437
+ // this should include merges that span the new range
1438
+ for (let row = before_row; row < this.cells.rows; row++) {
1439
+ for (let column = 0; column < this.cells.columns; column++) {
1440
+ const cell = this.cells.GetCell({ row, column }, false);
1441
+ if (cell) {
1442
+ /*
1443
+ if (cell.table) {
1444
+ const label = new Area(cell.table.area.start, cell.table.area.end).spreadsheet_label;
1445
+ if (!table_heads[label]) {
1446
+ table_heads[label] = cell.table;
1447
+ }
1448
+ }
1449
+ */
1450
+ if (cell.area && !array_heads[cell.area.spreadsheet_label]) {
1451
+ array_heads[cell.area.spreadsheet_label] = cell.area;
1452
+ }
1453
+ if (cell.merge_area && !merge_heads[cell.merge_area.spreadsheet_label]) {
1454
+ merge_heads[cell.merge_area.spreadsheet_label] = cell.merge_area;
1455
+ }
1456
+ }
1457
+ }
1458
+ }
1459
+ // console.info("IR arrays", array_heads);
1460
+ // console.info("IR merges", merge_heads);
1461
+ for (const key of Object.keys(array_heads)) {
1462
+ const head = array_heads[key];
1463
+ const patched = new Area({ row: head.start.row + count, column: head.start.column }, { row: head.end.row + count, column: head.end.column });
1464
+ for (const address of patched) {
1465
+ const cell = this.cells.GetCell(address, true);
1466
+ cell.area = patched;
1467
+ }
1468
+ /*
1469
+ patched.Iterate((address) => {
1470
+ const cell = this.cells.GetCell(address, true);
1471
+ cell.area = patched;
1472
+ });
1473
+ */
1474
+ }
1475
+ /*
1476
+ for (const key of Object.keys(table_heads)) {
1477
+ const table = table_heads[key];
1478
+
1479
+ const patched_start = { ...table.area.start };
1480
+ if (table.area.start.row >= before_row) patched_start.row += count;
1481
+ const patched = new Area(
1482
+ patched_start,
1483
+ { row: table.area.end.row + count, column: table.area.end.column });
1484
+
1485
+ table.area = { start: patched.start, end: patched.end };
1486
+
1487
+ // we don't need to reset table for cells that already have it,
1488
+ // but we do need to add it to new rows. could simplify. FIXME
1489
+
1490
+ patched.Iterate((address) => {
1491
+ const cell = this.cells.GetCell(address, true);
1492
+ cell.table = table;
1493
+ });
1494
+ }
1495
+ */
1496
+ for (const key of Object.keys(merge_heads)) {
1497
+ const head = merge_heads[key];
1498
+ const patched_start = { row: head.start.row, column: head.start.column };
1499
+ if (head.start.row >= before_row)
1500
+ patched_start.row += count;
1501
+ const patched = new Area(patched_start, { row: head.end.row + count, column: head.end.column });
1502
+ for (const address of patched) {
1503
+ const cell = this.cells.GetCell(address, true);
1504
+ cell.merge_area = patched;
1505
+ }
1506
+ /*
1507
+ patched.Iterate((address) => {
1508
+ const cell = this.cells.GetCell(address, true);
1509
+ cell.merge_area = patched;
1510
+ });
1511
+ */
1512
+ }
1513
+ // row styles
1514
+ const row_keys = Object.keys(this.row_styles);
1515
+ const new_row_style = {};
1516
+ row_keys.forEach((key) => {
1517
+ const index = Number(key);
1518
+ if (index < before_row)
1519
+ new_row_style[index] = this.row_styles[index];
1520
+ else if (count < 0 && index < before_row - count) { /* ? */ }
1521
+ else
1522
+ new_row_style[index + count] = this.row_styles[index];
1523
+ });
1524
+ this.row_styles = new_row_style;
1525
+ // cell styles
1526
+ let args = [];
1527
+ if (count < 0) {
1528
+ args = [before_row, -count];
1529
+ }
1530
+ else {
1531
+ args = [before_row, 0];
1532
+ for (let i = 0; i < count; i++)
1533
+ args.push(undefined);
1534
+ }
1535
+ // console.info('m5.1');
1536
+ this.cell_style.forEach((column) => {
1537
+ if (column && column.length >= before_row) {
1538
+ // eslint-disable-next-line prefer-spread
1539
+ column.splice.apply(column, args);
1540
+ }
1541
+ });
1542
+ // console.info('m6');
1543
+ // row heights
1544
+ // row heights is now a map, so this has to change...
1545
+ // eslint-disable-next-line prefer-spread
1546
+ // this.row_height_.splice.apply(this.row_height_, args as [number, number, number]);
1547
+ const tmp = new Map();
1548
+ if (count > 0) {
1549
+ for (const [row, height] of this.row_height_map) {
1550
+ if (row >= before_row) {
1551
+ tmp.set(row + count, height);
1552
+ }
1553
+ else {
1554
+ tmp.set(row, height);
1555
+ }
1556
+ }
1557
+ }
1558
+ else if (count < 0) {
1559
+ for (const [row, height] of this.row_height_map) {
1560
+ if (row >= before_row) {
1561
+ if (row >= before_row - count) {
1562
+ tmp.set(row + count, height);
1563
+ }
1564
+ }
1565
+ else {
1566
+ tmp.set(row, height);
1567
+ }
1568
+ }
1569
+ }
1570
+ this.row_height_map = tmp;
1571
+ // invalidate style cache
1572
+ this.FlushCellStyles();
1573
+ // console.info('m7');
1574
+ return true;
1575
+ }
1576
+ /**
1577
+ * see InsertRow for details
1578
+ */
1579
+ InsertColumns(before_column = 0, count = 1) {
1580
+ // check for array breaks
1581
+ if (before_column) {
1582
+ for (let row = 0; row < this.cells.rows; row++) {
1583
+ const cell1 = this.cells.GetCell({ row, column: before_column - 1 }, false);
1584
+ if (cell1 && cell1.area) {
1585
+ const cell2 = this.cells.GetCell({ row, column: before_column }, false);
1586
+ if (cell2 && cell2.area && cell2.area.Equals(cell1.area))
1587
+ return false; // failed
1588
+ }
1589
+ }
1590
+ }
1591
+ // this.named_ranges.PatchNamedRanges(before_column, count, 0, 0);
1592
+ // ok we can insert...
1593
+ if (count < 0) {
1594
+ this.cells.DeleteColumns(before_column, -count);
1595
+ }
1596
+ else {
1597
+ this.cells.InsertColumns(before_column, count);
1598
+ }
1599
+ // now we have to fix arrays and merge heads. these lists will keep
1600
+ // track of the _new_ starting address.
1601
+ // NOTE: tables are handled by the grid routine. for a time we were
1602
+ // doing that here but it's easier to unify on the grid size, since
1603
+ // we may need to update column headers or remove the model reference.
1604
+ const merge_heads = {};
1605
+ const array_heads = {};
1606
+ // now grab arrays and merge heads that are below the new rows
1607
+ // this should include merges that span the new range
1608
+ for (let column = before_column; column < this.cells.columns; column++) {
1609
+ for (let row = 0; row < this.cells.rows; row++) {
1610
+ const cell = this.cells.GetCell({ row, column }, false);
1611
+ if (cell) {
1612
+ if (cell.area && !array_heads[cell.area.spreadsheet_label]) {
1613
+ array_heads[cell.area.spreadsheet_label] = cell.area;
1614
+ }
1615
+ if (cell.merge_area && !merge_heads[cell.merge_area.spreadsheet_label]) {
1616
+ merge_heads[cell.merge_area.spreadsheet_label] = cell.merge_area;
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+ for (const key of Object.keys(array_heads)) {
1622
+ const head = array_heads[key];
1623
+ const patched = new Area({ row: head.start.row, column: head.start.column + count }, { row: head.end.row, column: head.end.column + count });
1624
+ for (const address of patched) {
1625
+ const cell = this.cells.GetCell(address, true);
1626
+ cell.area = patched;
1627
+ }
1628
+ /*
1629
+ patched.Iterate((address) => {
1630
+ const cell = this.cells.GetCell(address, true);
1631
+ cell.area = patched;
1632
+ });
1633
+ */
1634
+ }
1635
+ for (const key of Object.keys(merge_heads)) {
1636
+ const head = merge_heads[key];
1637
+ const patched_start = { row: head.start.row, column: head.start.column };
1638
+ if (head.start.column >= before_column)
1639
+ patched_start.column += count;
1640
+ const patched = new Area(patched_start, { row: head.end.row, column: head.end.column + count });
1641
+ for (const address of patched) {
1642
+ const cell = this.cells.GetCell(address, true);
1643
+ cell.merge_area = patched;
1644
+ }
1645
+ /*
1646
+ patched.Iterate((address) => {
1647
+ const cell = this.cells.GetCell(address, true);
1648
+ cell.merge_area = patched;
1649
+ });
1650
+ */
1651
+ }
1652
+ // column styles
1653
+ const column_keys = Object.keys(this.column_styles);
1654
+ const new_column_style = {};
1655
+ column_keys.forEach((key) => {
1656
+ const index = Number(key);
1657
+ if (index < before_column)
1658
+ new_column_style[index] = this.column_styles[index];
1659
+ else if (count < 0 && index < before_column - count) { /* ? */ }
1660
+ else
1661
+ new_column_style[index + count] = this.column_styles[index];
1662
+ });
1663
+ this.column_styles = new_column_style;
1664
+ // cell styles
1665
+ let args = [];
1666
+ if (count < 0) {
1667
+ args = [before_column, -count];
1668
+ }
1669
+ else {
1670
+ args = [before_column, 0];
1671
+ for (let i = 0; i < count; i++)
1672
+ args.push(undefined);
1673
+ }
1674
+ // eslint-disable-next-line prefer-spread
1675
+ this.cell_style.splice.apply(this.cell_style, args);
1676
+ // row heights
1677
+ // eslint-disable-next-line prefer-spread
1678
+ this.column_width_.splice.apply(this.column_width_, args);
1679
+ // invalidate style cache
1680
+ this.FlushCellStyles();
1681
+ return true;
1682
+ }
1683
+ /** clear cells in area */
1684
+ ClearArea(area) {
1685
+ // this is not allowed if any of the cells are in
1686
+ // an array, and the array does not match the passed
1687
+ // array.
1688
+ // ...
1689
+ // assuming it's ok, :
1690
+ // area = this.RealArea(area);
1691
+ // this.cells.Apply(area, (cell) => cell.Reset());
1692
+ for (const cell of this.cells.Iterate(this.RealArea(area), false)) {
1693
+ cell.Reset();
1694
+ }
1695
+ }
1696
+ // ATM we have 4 methods to set value/values. we need a distinction for
1697
+ // arrays, but that could be a parameter. the single-value/multi-value
1698
+ // area functions could probably be consolidated, also the single-cell-
1699
+ // single-value function... you need logic either on the outside or the
1700
+ // inside, put that logic where it makes the most sense.
1701
+ // also some of this could be moved to the Cells class... if for no
1702
+ // other reason than to remove the iteration overhead
1703
+ SetAreaValues2(area, values) {
1704
+ // we don't want to limit this to the existing area, we only
1705
+ // want to remove infinities (if set). it's possible to expand
1706
+ // the grid here (maybe -- check option?)
1707
+ // actually, realarea already does exactly that -- which is not
1708
+ // what I thought. we may need a new, different method to clip.
1709
+ area = this.RealArea(area);
1710
+ this.cells.SetArea(area, values);
1711
+ }
1712
+ /**
1713
+ * set the area as an array formula, based in the top-left cell
1714
+ */
1715
+ SetArrayValue(area, value) {
1716
+ area = this.RealArea(area);
1717
+ // this.cells.Apply(area, (element) => element.SetArray(area), true);
1718
+ for (const cell of this.cells.Iterate(area, true)) {
1719
+ cell.SetArray(area);
1720
+ }
1721
+ const cell = this.cells.GetCell(area.start, true);
1722
+ cell.SetArrayHead(area, value);
1723
+ }
1724
+ /**
1725
+ * set a single value in a single cell
1726
+ */
1727
+ SetCellValue(address, value) {
1728
+ const cell = this.cells.GetCell(address, true);
1729
+ cell.Set(value);
1730
+ }
1731
+ /**
1732
+ * FIXME: does not need to be in sheet
1733
+ *
1734
+ * @param headers_only - only return tables if the cell is in the
1735
+ * header (first) row. useful if you only want to worry about headers.
1736
+ */
1737
+ TablesFromArea(area, headers_only = false) {
1738
+ if (IsCellAddress(area)) {
1739
+ const cell = this.cells.GetCell(area, false);
1740
+ if (cell?.table) {
1741
+ if (!headers_only || (area.row === cell.table.area.start.row)) {
1742
+ return [cell.table];
1743
+ }
1744
+ }
1745
+ return [];
1746
+ }
1747
+ const set = new Set();
1748
+ for (let row = area.start.row; row <= area.end.row; row++) {
1749
+ for (let column = area.start.column; column <= area.end.column; column++) {
1750
+ const cell = this.cells.GetCell({ row, column }, false);
1751
+ if (cell?.table && !set.has(cell.table)) {
1752
+ if (!headers_only || (row === cell.table.area.start.row)) {
1753
+ set.add(cell.table);
1754
+ }
1755
+ }
1756
+ }
1757
+ }
1758
+ return Array.from(set.values());
1759
+ }
1760
+ /**
1761
+ * returns the area bounding actual content
1762
+ * (i.e. flattening "entire row/column/sheet")
1763
+ *
1764
+ * FIXME: this does not clamp to actual cells... why not?
1765
+ * FIXME: so now we are (optionally) clamping end; should clamp start, too
1766
+ *
1767
+ * @param clamp -- new parameter will optionally clamp to actual sheet size
1768
+ */
1769
+ RealArea(area, clamp = false) {
1770
+ const start = area.start; // this is a copy
1771
+ const end = area.end; // ditto
1772
+ if (area.entire_row) {
1773
+ start.column = 0;
1774
+ start.absolute_column = false;
1775
+ end.column = this.cells.columns - 1;
1776
+ end.absolute_column = false;
1777
+ }
1778
+ if (area.entire_column) {
1779
+ start.row = 0;
1780
+ start.absolute_row = false;
1781
+ end.row = this.cells.rows - 1;
1782
+ end.absolute_row = false;
1783
+ }
1784
+ if (clamp) {
1785
+ if (end.row >= this.rows) {
1786
+ end.row = this.rows - 1;
1787
+ end.absolute_row = false;
1788
+ }
1789
+ if (end.column >= this.columns) {
1790
+ end.column = this.columns - 1;
1791
+ end.absolute_column = false;
1792
+ }
1793
+ }
1794
+ return new Area(start, end);
1795
+ }
1796
+ /**
1797
+ * this is a new GetCellStyle function, used for external access
1798
+ * to style (for API access). there was an old GetCellStyle function
1799
+ * for rendering, but that's been removed (control+F for info).
1800
+ *
1801
+ * Q: does this include conditional formatting? (...)
1802
+ */
1803
+ GetCellStyle(area, apply_theme = false) {
1804
+ if (IsCellAddress(area)) {
1805
+ return this.CompositeStyleForCell(area, true, false, apply_theme);
1806
+ }
1807
+ // the contract says this should return an array, not a single value.
1808
+ //
1809
+ // I can fix it, but will anyone break? (...) check the indent buttons
1810
+ // (update: looks OK)
1811
+ //
1812
+ if (area.start.row === area.end.row && area.start.column === area.end.column) {
1813
+ return [[this.CompositeStyleForCell(area.start, true, false, apply_theme)]];
1814
+ }
1815
+ const result = [];
1816
+ for (let r = area.start.row; r <= area.end.row; r++) {
1817
+ const row = [];
1818
+ for (let c = area.start.column; c <= area.end.column; c++) {
1819
+ row.push(this.CompositeStyleForCell({ row: r, column: c }, true, false, apply_theme));
1820
+ }
1821
+ result.push(row);
1822
+ }
1823
+ return result;
1824
+ }
1825
+ ///
1826
+ FormattedCellValue(address) {
1827
+ const cell = this.CellData(address);
1828
+ if (!cell) {
1829
+ return undefined;
1830
+ }
1831
+ if (typeof cell.formatted === 'string')
1832
+ return cell.formatted;
1833
+ if (cell.formatted) {
1834
+ return cell.formatted.map(part => {
1835
+ switch (part.flag) {
1836
+ case 1:
1837
+ return ' ';
1838
+ case 2:
1839
+ return ' '; // ??
1840
+ default:
1841
+ return part.text;
1842
+ }
1843
+ }).join('');
1844
+ }
1845
+ return cell.value;
1846
+ }
1847
+ GetFormattedRange(from, to = from) {
1848
+ if (from.row === to.row && from.column === to.column) {
1849
+ return this.FormattedCellValue(from);
1850
+ }
1851
+ const result = [];
1852
+ // grab rows
1853
+ for (let row = from.row; row <= to.row; row++) {
1854
+ const target = [];
1855
+ for (let column = from.column; column <= to.column; column++) {
1856
+ target.push(this.FormattedCellValue({ row, column }));
1857
+ }
1858
+ result.push(target);
1859
+ }
1860
+ return result;
1861
+ }
1862
+ /**
1863
+ * get all styles used in the sheet. this is used to populate color
1864
+ * and number format lists in the toolbar. we used to just serialize
1865
+ * the document and use that, but that's absurdly wasteful. for this
1866
+ * application we don't even need composites.
1867
+ *
1868
+ * although, this is a bit dangerous because you could (in theory)
1869
+ * modify the results in place. so maybe we should either duplicate or
1870
+ * just return the requested data...
1871
+ */
1872
+ NumberFormatsAndColors(color_map, number_format_map) {
1873
+ const parse = (style) => {
1874
+ if (style.number_format) {
1875
+ number_format_map[style.number_format] = 1;
1876
+ }
1877
+ if (IsHTMLColor(style.text)) {
1878
+ color_map[style.text.text] = 1;
1879
+ }
1880
+ if (IsHTMLColor(style.fill)) {
1881
+ color_map[style.fill.text] = 1;
1882
+ }
1883
+ //if (style.background && style.background !== 'none') {
1884
+ // color_map[style.background] = 1;
1885
+ //}
1886
+ if (IsHTMLColor(style.border_top_fill)) {
1887
+ color_map[style.border_top_fill.text] = 1;
1888
+ }
1889
+ if (IsHTMLColor(style.border_left_fill)) {
1890
+ color_map[style.border_left_fill.text] = 1;
1891
+ }
1892
+ if (IsHTMLColor(style.border_right_fill)) {
1893
+ color_map[style.border_right_fill.text] = 1;
1894
+ }
1895
+ if (IsHTMLColor(style.border_bottom_fill)) {
1896
+ color_map[style.border_bottom_fill.text] = 1;
1897
+ }
1898
+ };
1899
+ parse(this.sheet_style);
1900
+ for (const key in this.row_styles) {
1901
+ parse(this.row_styles[key]);
1902
+ }
1903
+ for (const key in this.column_styles) {
1904
+ parse(this.column_styles[key]);
1905
+ }
1906
+ for (const style of this.row_pattern) {
1907
+ parse(style);
1908
+ }
1909
+ for (const row of this.cell_style) {
1910
+ if (row) {
1911
+ for (const style of row) {
1912
+ if (style) {
1913
+ parse(style);
1914
+ }
1915
+ }
1916
+ }
1917
+ }
1918
+ }
1919
+ CompressCellStyles(data) {
1920
+ // we can almost certainly compress the cell style map (above) if there
1921
+ // are consistent areas. not sure what the optimal algorithms for this
1922
+ // are, but there are probably some out there. let's start naively and
1923
+ // see what we can get.
1924
+ // I think the real issue is imports from XLSX; we're getting a lot
1925
+ // of individual cell styles where there should probably be R/C styles.
1926
+ // actually we might be working against ourselves here if we are
1927
+ // removing populated cells from this array: because in that case we'll
1928
+ // get fewer contiguous blocks. perhaps we should have a "lookaround"
1929
+ // in the original array? (...)
1930
+ // OTOH this can never be _worse_ than the old method, and I don't think
1931
+ // it costs much more. so we'll stick with this for the time being, see
1932
+ // if we can further optimize later.
1933
+ // (note: tried passing the original array, and checking for overlap,
1934
+ // but ultimately savings was minimal. not worth it)
1935
+ const list = [];
1936
+ for (let c = 0; c < data.length; c++) {
1937
+ const column = data[c];
1938
+ if (column) {
1939
+ for (let r = 0; r < column.length; r++) {
1940
+ const style = column[r];
1941
+ if (style) {
1942
+ let k = r + 1;
1943
+ for (; k < column.length; k++) {
1944
+ if (column[k] !== style) {
1945
+ break;
1946
+ }
1947
+ }
1948
+ if (k > r + 1) {
1949
+ list.push({ row: r, column: c, ref: style, rows: k - r });
1950
+ }
1951
+ else {
1952
+ list.push({ row: r, column: c, ref: style });
1953
+ }
1954
+ r = k - 1;
1955
+ }
1956
+ }
1957
+ }
1958
+ }
1959
+ return list;
1960
+ }
1961
+ /**
1962
+ * generates serializable object. given the new data semantics this
1963
+ * has to change a bit. here is what we are storing:
1964
+ *
1965
+ * all style data (sheet, row/column, alternate and cell)
1966
+ * raw value for cell
1967
+ * array head for arrays
1968
+ * row height and column width arrays
1969
+ *
1970
+ * because we have sparse arrays, we convert them to flat objects first.
1971
+ */
1972
+ toJSON(options = {}) {
1973
+ // flatten height/width arrays
1974
+ const flatten_numeric_array = (arr, default_value) => {
1975
+ const obj = {};
1976
+ for (let i = 0; i < arr.length; i++) {
1977
+ if ((typeof arr[i] !== 'undefined') && arr[i] !== default_value)
1978
+ obj[i] = arr[i];
1979
+ }
1980
+ if (Object.keys(obj).length)
1981
+ return obj;
1982
+ return undefined;
1983
+ };
1984
+ // flatten cell styles, which is a sparse array
1985
+ // UPDATE: ref table
1986
+ // NOTE: we originally did this (I think) because it's possible for a
1987
+ // cell to have a style but have no other data, and therefore not be
1988
+ // represented. but we should be able to store the data in the cell object
1989
+ // if we have it...
1990
+ let cell_style_refs = [{}]; // include an empty entry at zero
1991
+ const cell_style_map = {};
1992
+ const cell_reference_map = [];
1993
+ // (1) create a map of cells -> references, and build the reference
1994
+ // table at the same time. preserve indexes? (...)
1995
+ // it would be nice if we could use some sort of numeric test, rather
1996
+ // than leaving empty indexes as undefined -- that requires a type test
1997
+ // (to avoid zeros).
1998
+ const empty_json = JSON.stringify({});
1999
+ // actually we could just offset the index by 1... (see above)
2000
+ for (let c = 0; c < this.cell_style.length; c++) {
2001
+ const column = this.cell_style[c];
2002
+ if (column) {
2003
+ cell_reference_map[c] = [];
2004
+ for (let r = 0; r < column.length; r++) {
2005
+ if (column[r]) {
2006
+ const style_as_json = Style.Serialize(column[r]); // JSON.stringify(column[r]);
2007
+ if (style_as_json !== empty_json) {
2008
+ let reference_index = cell_style_map[style_as_json];
2009
+ if (typeof reference_index !== 'number') {
2010
+ cell_style_map[style_as_json] = reference_index = cell_style_refs.length;
2011
+ cell_style_refs.push(column[r]);
2012
+ }
2013
+ cell_reference_map[c][r] = reference_index;
2014
+ }
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+ // it might be more efficient to store cell styles separately from
2020
+ // cell data, as we might be able to compress it. it looks more like
2021
+ // an indexed image, and we likely don't have that many styles.
2022
+ /**
2023
+ * this assumes that "empty" style is at index 0
2024
+ */
2025
+ const StyleToRef = (style) => {
2026
+ const style_as_json = Style.Serialize(style); // JSON.stringify(style);
2027
+ if (style_as_json === empty_json) {
2028
+ return 0;
2029
+ }
2030
+ let reference_index = cell_style_map[style_as_json];
2031
+ if (typeof reference_index !== 'number') {
2032
+ cell_style_map[style_as_json] = reference_index = cell_style_refs.length;
2033
+ cell_style_refs.push(style);
2034
+ }
2035
+ return reference_index;
2036
+ };
2037
+ // ensure we're not linked
2038
+ cell_style_refs = JSON.parse(JSON.stringify(cell_style_refs));
2039
+ // same here (note broken naming)
2040
+ const sheet_style = JSON.parse(JSON.stringify(this.sheet_style));
2041
+ const row_pattern = JSON.parse(JSON.stringify(this.row_pattern));
2042
+ // row and column styles are Record<number, props> and not arrays.
2043
+ // I think they should probably be arrays. it's not critical but
2044
+ // using records (objects) converts keys to strings, which is sloppy.
2045
+ // const column_style: Array<number|CellStyle> = [];
2046
+ // const row_style: Array<number|CellStyle> = [];
2047
+ const column_style = {};
2048
+ const row_style = {};
2049
+ for (const key of Object.keys(this.column_styles)) {
2050
+ const index = Number(key);
2051
+ const style = this.column_styles[index];
2052
+ if (style) {
2053
+ const reference = StyleToRef(style);
2054
+ if (reference) {
2055
+ column_style[index] = reference;
2056
+ }
2057
+ }
2058
+ }
2059
+ if (this.row_pattern && this.row_pattern.length && options.apply_row_pattern) {
2060
+ let count = this.rows + 1;
2061
+ for (const key of Object.keys(this.row_styles)) {
2062
+ const index = Number(key);
2063
+ if (!isNaN(index) && index >= count) {
2064
+ count = index + 1;
2065
+ }
2066
+ }
2067
+ for (let i = 0; i < count; i++) {
2068
+ const pattern = this.row_pattern[i % this.row_pattern.length];
2069
+ const style = this.row_styles[i] || {};
2070
+ const composite = Style.Composite([pattern, style]);
2071
+ const reference = StyleToRef(composite);
2072
+ if (reference) {
2073
+ row_style[i] = reference;
2074
+ }
2075
+ }
2076
+ }
2077
+ else {
2078
+ for (const key of Object.keys(this.row_styles)) {
2079
+ const index = Number(key);
2080
+ const style = this.row_styles[index];
2081
+ if (style) {
2082
+ const reference = StyleToRef(style);
2083
+ if (reference) {
2084
+ row_style[index] = reference;
2085
+ }
2086
+ }
2087
+ }
2088
+ }
2089
+ /*
2090
+ const translate_border_color = (color: string | undefined, default_color: string | undefined): string | undefined => {
2091
+ if (typeof color !== 'undefined' && color !== 'none') {
2092
+ if (color === default_color) {
2093
+ return undefined;
2094
+ }
2095
+ else {
2096
+ return Measurement.MeasureColorARGB(color);
2097
+ }
2098
+ }
2099
+ return undefined;
2100
+ }
2101
+ */
2102
+ const translate_border_fill = (color = {}, default_color = {}) => {
2103
+ const result = {
2104
+ ...default_color,
2105
+ ...color,
2106
+ };
2107
+ if (IsHTMLColor(result)) {
2108
+ result.text = Measurement.MeasureColorARGB(result.text);
2109
+ return result;
2110
+ }
2111
+ else if (IsThemeColor(result)) {
2112
+ return result;
2113
+ }
2114
+ return undefined;
2115
+ };
2116
+ // translate, if necessary
2117
+ if (options.export_colors) {
2118
+ const style_list = [];
2119
+ for (const group of [
2120
+ //row_style, column_style, // these are moved -> csr (which should be renamed)
2121
+ cell_style_refs, [sheet_style], row_pattern
2122
+ ]) {
2123
+ if (Array.isArray(group)) {
2124
+ for (const entry of group)
2125
+ style_list.push(entry);
2126
+ }
2127
+ else {
2128
+ for (const key of Object.keys(group))
2129
+ style_list.push(group[key]);
2130
+ }
2131
+ }
2132
+ for (const style of style_list) {
2133
+ // don't set "undefined" overrides. also, was this broken
2134
+ // wrt all the defaults from top? probably
2135
+ let fill = translate_border_fill(style.border_top_fill, Style.DefaultProperties.border_top_fill);
2136
+ if (fill !== undefined) {
2137
+ style.border_top_fill = fill;
2138
+ }
2139
+ fill = translate_border_fill(style.border_left_fill, Style.DefaultProperties.border_left_fill);
2140
+ if (fill !== undefined) {
2141
+ style.border_left_fill = fill;
2142
+ }
2143
+ fill = translate_border_fill(style.border_right_fill, Style.DefaultProperties.border_right_fill);
2144
+ if (fill !== undefined) {
2145
+ style.border_right_fill = fill;
2146
+ }
2147
+ fill = translate_border_fill(style.border_bottom_fill, Style.DefaultProperties.border_bottom_fill);
2148
+ if (fill !== undefined) {
2149
+ style.border_bottom_fill = fill;
2150
+ }
2151
+ if (IsHTMLColor(style.fill)) {
2152
+ style.fill.text = Measurement.MeasureColorARGB(style.fill.text);
2153
+ }
2154
+ //if (typeof style.background !== 'undefined' && style.background !== 'none') {
2155
+ // style.background = Measurement.MeasureColorARGB(style.background);
2156
+ //}
2157
+ if (IsHTMLColor(style.text)) {
2158
+ style.text.text = Measurement.MeasureColorARGB(style.text.text);
2159
+ }
2160
+ }
2161
+ }
2162
+ // FIXME: flatten row/column styles too
2163
+ // flatten data -- also remove unecessary fields (FIXME: you might
2164
+ // keep rendered data, so it doesn't have to do work on initial render?)
2165
+ const serialization_options = {
2166
+ calculated_value: !!options.rendered_values,
2167
+ preserve_type: !!options.preserve_type,
2168
+ expand_arrays: !!options.expand_arrays,
2169
+ decorated_cells: !!options.decorated_cells,
2170
+ nested: true,
2171
+ cell_style_refs: cell_reference_map,
2172
+ tables: !!options.tables,
2173
+ };
2174
+ // the rows/columns we export can be shrunk to the actual used area,
2175
+ // subject to serialization option.
2176
+ const serialized_data = this.cells.toJSON(serialization_options);
2177
+ const data = serialized_data.data;
2178
+ let { rows, columns } = serialized_data;
2179
+ if (!options.shrink) {
2180
+ rows = this.rows;
2181
+ columns = this.columns;
2182
+ }
2183
+ else {
2184
+ // pad by 1 (2?)
2185
+ rows += 2;
2186
+ columns += 1;
2187
+ }
2188
+ // push out for annotations
2189
+ for (const annotation of this.annotations) {
2190
+ if (!annotation.data.extent) {
2191
+ this.CalculateAnnotationExtent(annotation);
2192
+ }
2193
+ if (annotation.data.extent) {
2194
+ rows = Math.max(rows, annotation.data.extent.row + 1);
2195
+ columns = Math.max(columns, annotation.data.extent.column + 1);
2196
+ }
2197
+ }
2198
+ // (3) (style) for anything that hasn't been consumed, create a
2199
+ // cell style map. FIXME: optional [?]
2200
+ /*
2201
+ const cell_styles: Array<{ row: number; column: number; ref: number }> = [];
2202
+
2203
+ for (let c = 0; c < cell_reference_map.length; c++) {
2204
+ const column = cell_reference_map[c];
2205
+ if (column) {
2206
+ for (let r = 0; r < column.length; r++) {
2207
+ if (column[r]) {
2208
+ cell_styles.push({ row: r, column: c, ref: column[r] });
2209
+ }
2210
+ }
2211
+ }
2212
+ }
2213
+
2214
+ const CS2 = this.CompressCellStyles(cell_reference_map);
2215
+ console.info({cs1: JSON.stringify(cell_styles), cs2: JSON.stringify(CS2)});
2216
+ */
2217
+ // using blocks. this is our naive method. we could do (at minimum)
2218
+ // testing row-dominant vs column-dominant and see which is better;
2219
+ // but that kind of thing adds time, so it should be optional.
2220
+ const cell_styles = this.CompressCellStyles(cell_reference_map);
2221
+ // if we serialize this when it has Area values (instead of IArea) it
2222
+ // will export incorrectly. is that an issue anywhere else? (...)
2223
+ const conditional_formats = this.conditional_formats.length ?
2224
+ JSON.parse(JSON.stringify(this.conditional_formats.map(format => ({ ...format, internal: undefined })))) :
2225
+ undefined;
2226
+ // yes, here. we should have a serialized type so we know to convert. TODO
2227
+ const data_validations = this.data_validation.length ? JSON.parse(JSON.stringify(this.data_validation)) : undefined;
2228
+ const row_height = {};
2229
+ for (const [key, value] of this.row_height_map.entries()) {
2230
+ row_height[key] = value;
2231
+ }
2232
+ const result = {
2233
+ // not used atm, but in the event we need to gate
2234
+ // or swap importers on versions in the future
2235
+ // FIXME: drop, in favor of container versioning. there's no point
2236
+ // in this submodule versioning (is there? ...)
2237
+ // version: (ModuleInfo as any).version,
2238
+ id: this.id,
2239
+ name: this.name,
2240
+ tab_color: this.tab_color,
2241
+ data,
2242
+ sheet_style,
2243
+ rows,
2244
+ columns,
2245
+ cell_styles,
2246
+ styles: cell_style_refs,
2247
+ row_style,
2248
+ column_style,
2249
+ conditional_formats,
2250
+ data_validations,
2251
+ row_pattern: row_pattern.length ? row_pattern : undefined,
2252
+ // why are these serialized? (...) export!
2253
+ default_row_height: this.default_row_height,
2254
+ default_column_width: this.default_column_width,
2255
+ row_height, // : flatten_numeric_array(this.row_height_, this.default_row_height),
2256
+ column_width: flatten_numeric_array(this.column_width_, this.default_column_width),
2257
+ selection: JSON.parse(JSON.stringify(this.selection)),
2258
+ annotations: JSON.parse(JSON.stringify(this.annotations)),
2259
+ };
2260
+ // omit default (true)
2261
+ if (!this.visible) {
2262
+ result.visible = this.visible;
2263
+ }
2264
+ if (this.scroll_offset.x || this.scroll_offset.y) {
2265
+ result.scroll = this.scroll_offset;
2266
+ }
2267
+ if (this.background_image) {
2268
+ result.background_image = this.background_image;
2269
+ }
2270
+ // moved to outer container (data model)
2271
+ /*
2272
+ // omit if empty
2273
+
2274
+ if (this.named_ranges.Count()) {
2275
+ result.named_ranges = JSON.parse(JSON.stringify(this.named_ranges.Map()));
2276
+ }
2277
+ */
2278
+ // only put in freeze if used
2279
+ if (this.freeze.rows || this.freeze.columns) {
2280
+ result.freeze = this.freeze;
2281
+ }
2282
+ return result;
2283
+ }
2284
+ /*
2285
+ * export values and calcualted values; as for csv export (which is what it's for) * /
2286
+ public ExportValueData(transpose = false, dates_as_strings = false, export_functions = false): CellValue[][] {
2287
+
2288
+ const arr: CellValue[][] = [];
2289
+ const data = this.cells.data;
2290
+
2291
+ if (transpose) {
2292
+ const rowcount = data[0].length; // assuming it's a rectangle
2293
+ for (let r = 0; r < rowcount; r++) {
2294
+ const row: CellValue[] = [];
2295
+ for (const column of data) {
2296
+ const ref = column[r];
2297
+ let value: CellValue;
2298
+ if (!export_functions && typeof ref.calculated !== 'undefined') value = ref.calculated;
2299
+ else if (typeof ref.value === 'undefined') value = '';
2300
+ else value = ref.value;
2301
+
2302
+ if (dates_as_strings && ref.style && typeof value === 'number') {
2303
+ const format = NumberFormatCache.Get(ref.style.number_format || '');
2304
+ if (format.date_format) value = format.Format(value);
2305
+ }
2306
+
2307
+ // if (dates_as_strings && ref.style && ref.style.date && typeof value === 'number') {
2308
+ // value = Style.Format(ref.style, value);
2309
+ // }
2310
+ row.push(value);
2311
+ }
2312
+ arr.push(row);
2313
+ }
2314
+ }
2315
+ else {
2316
+ for (const column_ref of data) {
2317
+ const column: CellValue[] = [];
2318
+ for (const ref of column_ref) {
2319
+ let value: CellValue;
2320
+ if (!export_functions && typeof ref.calculated !== 'undefined') value = ref.calculated;
2321
+ else if (typeof ref.value === 'undefined') value = '';
2322
+ else value = ref.value;
2323
+
2324
+ if (dates_as_strings && ref.style && typeof value === 'number') {
2325
+ const format = NumberFormatCache.Get(ref.style.number_format || '');
2326
+ if (format.date_format) value = format.Format(value);
2327
+ }
2328
+
2329
+ // if (dates_as_strings && ref.style && ref.style.date && typeof value === 'number') {
2330
+ // value = Style.Format(ref.style, value);
2331
+ // }
2332
+ column.push(value);
2333
+ }
2334
+ arr.push(column);
2335
+ }
2336
+ }
2337
+
2338
+ return arr;
2339
+ }
2340
+ */
2341
+ /** flushes ALL rendered styles and caches. made public for theme API */
2342
+ FlushCellStyles() {
2343
+ this.style_map = [];
2344
+ this.style_json_map = [];
2345
+ this.cells.FlushCellStyles();
2346
+ }
2347
+ ImportData(data) {
2348
+ const styles = data.styles;
2349
+ if (data.outline) {
2350
+ this.outline = data.outline;
2351
+ }
2352
+ // adding sheet style...
2353
+ // 0 is implicitly just a general style
2354
+ const sheet_style = data.sheet_style;
2355
+ if (sheet_style) {
2356
+ this.UpdateAreaStyle(new Area({ row: Infinity, column: Infinity }, { row: Infinity, column: Infinity }), styles[sheet_style]);
2357
+ }
2358
+ // and column styles...
2359
+ const column_styles = data.column_styles;
2360
+ if (column_styles) {
2361
+ for (let i = 0; i < column_styles.length; i++) {
2362
+ // 0 is implicitly just a general style
2363
+ if (column_styles[i]) {
2364
+ this.UpdateAreaStyle(new Area({ row: Infinity, column: i }, { row: Infinity, column: i }), styles[column_styles[i]]);
2365
+ }
2366
+ }
2367
+ }
2368
+ // and row styles...
2369
+ if (data.row_styles) {
2370
+ for (const [row, style] of data.row_styles.entries()) {
2371
+ if (style) {
2372
+ this.UpdateAreaStyle(new Area({ row, column: Infinity }), styles[style]);
2373
+ }
2374
+ }
2375
+ }
2376
+ // this.cells.FromJSON(cell_data);
2377
+ this.cells.FromJSON(data.cells);
2378
+ if (data.name) {
2379
+ this.name = data.name || ''; // wtf is this?
2380
+ }
2381
+ // patching from import
2382
+ for (const cell of this.cells.Iterate()) {
2383
+ if (cell.spill) {
2384
+ if (!cell.spill.start.sheet_id) {
2385
+ cell.spill.SetSheetID(this.id);
2386
+ }
2387
+ }
2388
+ }
2389
+ if (data.tab_color) {
2390
+ this.tab_color = data.tab_color;
2391
+ }
2392
+ // 0 is implicitly just a general style
2393
+ const cs = this.cell_style;
2394
+ for (const info of data.cells) {
2395
+ if (info.style_ref) {
2396
+ if (!cs[info.column])
2397
+ cs[info.column] = [];
2398
+ cs[info.column][info.row] = styles[info.style_ref];
2399
+ }
2400
+ }
2401
+ for (let i = 0; i < data.column_widths.length; i++) {
2402
+ if (typeof data.column_widths[i] !== 'undefined') {
2403
+ // OK this is unscaled, we are setting unscaled from source data
2404
+ this.SetColumnWidth(i, data.column_widths[i]);
2405
+ }
2406
+ }
2407
+ for (let i = 0; i < data.row_heights.length; i++) {
2408
+ if (typeof data.row_heights[i] !== 'undefined') {
2409
+ // OK this is unscaled, we are setting unscaled from source data
2410
+ this.SetRowHeight(i, data.row_heights[i]);
2411
+ }
2412
+ }
2413
+ for (const annotation of data.annotations || []) {
2414
+ this.annotations.push(new Annotation(annotation));
2415
+ }
2416
+ for (const format of data.conditional_formats || []) {
2417
+ this.conditional_formats.push(format);
2418
+ }
2419
+ for (const validation of data.data_validations || []) {
2420
+ this.AddValidation(validation);
2421
+ }
2422
+ if (data.hidden) {
2423
+ this.visible = false;
2424
+ }
2425
+ }
2426
+ // --- protected ------------------------------------------------------------
2427
+ /**
2428
+ * figure out the last row/column of the annotation. this
2429
+ * might set it to 0/0 if there's no rect, just make sure
2430
+ * that it gets cleared on layout changes.
2431
+ */
2432
+ CalculateAnnotationExtent(annotation) {
2433
+ // this is much easier with layout, but we are leaving the old
2434
+ // coude to support older files -- OTOH, the layout will be created
2435
+ // at some point, we just need to make sure that happens before this
2436
+ // is called
2437
+ if (annotation.data.layout) {
2438
+ annotation.data.extent = { ...annotation.data.layout.br.address };
2439
+ return;
2440
+ }
2441
+ // 1000 here is just sanity check, it might be larger
2442
+ const sanity = 1000;
2443
+ annotation.data.extent = { row: 0, column: 0 };
2444
+ let right = annotation.rect?.right;
2445
+ if (right && this.default_column_width) { // also sanity check
2446
+ for (let i = 0; right >= 0 && i < sanity; i++) {
2447
+ right -= this.GetColumnWidth(i); // FIXME: check // it's ok, rect is scaled to unit
2448
+ if (right < 0) {
2449
+ annotation.data.extent.column = i;
2450
+ break;
2451
+ }
2452
+ }
2453
+ }
2454
+ let bottom = annotation.rect?.bottom;
2455
+ if (bottom && this.default_row_height) {
2456
+ for (let i = 0; bottom >= 0 && i < sanity; i++) {
2457
+ bottom -= this.GetRowHeight(i); // FIXME: check // it's ok, rect is scaled to unit
2458
+ if (bottom < 0) {
2459
+ annotation.data.extent.row = i;
2460
+ break;
2461
+ }
2462
+ }
2463
+ }
2464
+ }
2465
+ /* *
2466
+ * when checking style properties, check falsy but not '' or 0
2467
+ * (also strict equivalence)
2468
+ * /
2469
+ protected StyleEquals(a: any, b: any): boolean {
2470
+ return a === b ||
2471
+ ((a === false || a === null || a === undefined)
2472
+ && (b === false || b === null || b === undefined));
2473
+ }
2474
+ */
2475
+ /*
2476
+ protected Serialize() {
2477
+ return JSON.stringify(this);
2478
+ }
2479
+ */
2480
+ /*
2481
+ protected Deserialize(data: SerializedSheet) {
2482
+ Sheet.FromJSON(data, this.default_style_properties, this);
2483
+
2484
+ // some overlap here... consolidate? actually, doesn't
2485
+ // fromJSON call flush styles? [A: sometimes...]
2486
+
2487
+ this.cells.FlushCachedValues();
2488
+ this.FlushCellStyles();
2489
+ }
2490
+ */
2491
+ // --- private methods ------------------------------------------------------
2492
+ /**
2493
+ * update style properties. merge by default.
2494
+ *
2495
+ * this method will reverse-override properties, meaning if you have set (for
2496
+ * example) a cell style to bold, then you set the whole sheet to unbold, we
2497
+ * expect that the unbold style will control. instead of explicitly setting
2498
+ * the cell style, we go up the chain and remove any matching properties.
2499
+ */
2500
+ UpdateSheetStyle(properties, delta = true) {
2501
+ this.sheet_style = Style.Merge(this.sheet_style, properties, delta);
2502
+ // reverse-override...
2503
+ // const keys = Object.keys(properties);
2504
+ const keys = Object.keys(properties);
2505
+ // const keys = Object.keys(this.sheet_style) as Style.PropertyKeys[];
2506
+ for (const style_column of this.cell_style) {
2507
+ if (style_column) {
2508
+ for (const style_ref of style_column) {
2509
+ if (style_ref) {
2510
+ keys.forEach((key) => delete style_ref[key]);
2511
+ }
2512
+ }
2513
+ }
2514
+ }
2515
+ for (const index of Object.keys(this.row_styles)) {
2516
+ keys.forEach((key) => delete this.row_styles[index][key]);
2517
+ }
2518
+ for (const index of Object.keys(this.column_styles)) {
2519
+ keys.forEach((key) => delete this.column_styles[index][key]);
2520
+ }
2521
+ // FIXME: ROW PATTERN
2522
+ this.FlushCellStyles(); // not targeted
2523
+ }
2524
+ /**
2525
+ * updates row properties. reverse-overrides cells (@see UpdateSheetStyle).
2526
+ *
2527
+ * we also need to ensure that the desired effect takes hold, meaning if
2528
+ * there's an overriding column property (columns have priority), we will
2529
+ * need to update the cell property to match the desired output.
2530
+ */
2531
+ UpdateRowStyle(row, properties, delta = true) {
2532
+ this.row_styles[row] = Style.Merge(this.row_styles[row] || {}, properties, delta);
2533
+ // reverse-override... remove matching properties from cells in this row
2534
+ // (we can do this in-place)
2535
+ // const keys = Object.keys(properties);
2536
+ const keys = Object.keys(properties);
2537
+ // const keys = Object.keys(this.row_styles[row]) as Style.PropertyKeys[];
2538
+ for (const column of this.cell_style) {
2539
+ if (column && column[row]) {
2540
+ // FIXME: we don't want to delete. reverse-add.
2541
+ keys.forEach((key) => delete column[row][key]);
2542
+ }
2543
+ }
2544
+ /*
2545
+
2546
+ //
2547
+ // seems to be related to
2548
+ // https://github.com/microsoft/TypeScript/pull/30769
2549
+ //
2550
+ // not clear why the behavior should be different, but
2551
+ //
2552
+ // "indexed access with generics now works differently inside & outside a function."
2553
+ //
2554
+
2555
+ const FilteredAssign = <T>(test: T, source: T, target: T, keys: Array<keyof T>): void => {
2556
+ for (const key of keys) {
2557
+ if (test[key] !== undefined) {
2558
+ target[key] = source[key];
2559
+ }
2560
+ }
2561
+ };
2562
+ */
2563
+ // if there's a column style, it will override the row
2564
+ // style; so we need to set a cell style to compensate.
2565
+ // "override" because a reserved word in ts 4.3.2, possibly accidentally?
2566
+ // or possibly it was already a reserved word, and was handled incorrectly?
2567
+ // not sure. stop using it.
2568
+ //
2569
+ // Actually just by the by, if it does work as described in
2570
+ //
2571
+ // https://github.com/microsoft/TypeScript/issues/2000
2572
+ //
2573
+ // then we should start using it where appropriate, because it is good.
2574
+ // just don't use it here as a variable name.
2575
+ for (let i = 0; i < this.cells.columns; i++) {
2576
+ if (this.column_styles[i]) {
2577
+ const column_style = this.column_styles[i];
2578
+ const overrides = this.cell_style[i] ? this.cell_style[i][row] || {} : {};
2579
+ for (const key of keys) {
2580
+ if (typeof column_style[key] !== 'undefined') {
2581
+ // what's the correct pattern (if any) for this? these
2582
+ // are the same type so the type of the indexed value should
2583
+ // be equivalent... no? maybe there is no correct way
2584
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2585
+ overrides[key] = properties[key];
2586
+ }
2587
+ }
2588
+ if (Object.keys(overrides).length) {
2589
+ if (!this.cell_style[i])
2590
+ this.cell_style[i] = [];
2591
+ this.cell_style[i][row] = JSON.parse(JSON.stringify(overrides));
2592
+ }
2593
+ }
2594
+ }
2595
+ // FIXME: ROW PATTERN
2596
+ // this.cells.Apply(this.RealArea(Area.FromRow(row)), (cell) => cell.FlushStyle());
2597
+ for (const cell of this.cells.Iterate(this.RealArea(Area.FromRow(row)))) {
2598
+ cell.FlushStyle();
2599
+ }
2600
+ }
2601
+ /**
2602
+ * updates column properties. reverse-overrides cells (@see UpdateSheetStyle).
2603
+ */
2604
+ UpdateColumnStyle(column, properties, delta = true) {
2605
+ this.column_styles[column] = Style.Merge(this.column_styles[column] || {}, properties, delta);
2606
+ // returning to this function after a long time. so what this is doing
2607
+ // is removing unecessary properties from style objects higher in the
2608
+ // style chain, if those properties are overridden. note that this doesn't
2609
+ // seem to prune now-empty styles, which it probably should...
2610
+ // in essence, we have a containing style object
2611
+ // { a: 1, c: 2 }
2612
+ //
2613
+ // then we iterate all cells in the column, and if there are any
2614
+ // matching properties they're deleted; so if a cell has
2615
+ // { a: 0, b: 1 }
2616
+ //
2617
+ // we drop the a property, so it becomes
2618
+ // { b: 1 }
2619
+ //
2620
+ // note you can drop and re-create the cell style object, because the cell's
2621
+ // reference is actually to a separate object (composited with the stack),
2622
+ // and the reference is cleared so the composite will be rebuilt when it's
2623
+ // needed next.
2624
+ // NOTE this was broken anyway; it wasn't taking the merge into account...
2625
+ // ALTHOUGH that breaks "remove-color" operations. I think the old way
2626
+ // took into account that the styles would be relatively in sync already.
2627
+ // reverse-override... I think we only need to override _cell_ values.
2628
+ const keys = Object.keys(properties);
2629
+ // const keys = Object.keys(this.column_styles[column]) as Style.PropertyKeys[];
2630
+ if (this.cell_style[column]) {
2631
+ for (const ref of this.cell_style[column]) {
2632
+ if (ref) {
2633
+ // FIXME: we don't want to delete. reverse-add.
2634
+ keys.forEach((key) => delete ref[key]);
2635
+ }
2636
+ }
2637
+ }
2638
+ // this.cells.Apply(this.RealArea(Area.FromColumn(column)), (cell) => cell.FlushStyle());
2639
+ for (const cell of this.cells.Iterate(this.RealArea(Area.FromColumn(column)))) {
2640
+ cell.FlushStyle();
2641
+ }
2642
+ // FIXME: ROW PATTERN
2643
+ }
2644
+ BleedFlush(area) {
2645
+ const rows = [Math.max(0, area.start.row - 1), area.end.row + 1];
2646
+ const cols = [Math.max(0, area.start.column - 1), area.end.column + 1];
2647
+ for (let row = rows[0]; row <= rows[1]; row++) {
2648
+ for (let column = cols[0]; column <= cols[1]; column++) {
2649
+ // const cell = this.cells.EnsureCell({row, column});
2650
+ this.cells.GetCell({ row, column }, false)?.FlushStyle();
2651
+ }
2652
+ }
2653
+ }
2654
+ FlushConditionalFormats() {
2655
+ this.flush_conditional_formats = true;
2656
+ }
2657
+ /**
2658
+ * this version combines flushing the cache with building it, using
2659
+ * the application flag in the format objects.
2660
+ *
2661
+ * this function was set up to support comparing the two lists and
2662
+ * only flushing style if necessary; but that turns out to be so
2663
+ * much additional work that I'm not sure it's preferable to just
2664
+ * repaint. need to test.
2665
+ *
2666
+ * ...we're also probably looping unecessarily. since we're using
2667
+ * those leaf nodes we can probably check if the state changed, and
2668
+ * it not, skip the loop pass. I think we'd need to identify or map
2669
+ * the applications though (meaning use a stack that matches the list
2670
+ * of formats). or you could even recheck everything if one of them
2671
+ * changed, you'd still probably save a lot in cases where nothing
2672
+ * changed.
2673
+ *
2674
+ */
2675
+ ApplyConditionalFormats() {
2676
+ // we're not doing any pruning at the moment, so this is doing
2677
+ // a lot of unecessary looping -- we could start with one big
2678
+ // global check
2679
+ // ...we need to account for the case where a format is removed,
2680
+ // in that case we will need to update. flag?
2681
+ let updated = this.flush_conditional_formats; // maybe required
2682
+ for (const format of this.conditional_formats) {
2683
+ if (format.internal?.vertex?.updated) {
2684
+ updated = true;
2685
+ break;
2686
+ }
2687
+ }
2688
+ if (!updated) {
2689
+ // console.info('no updates');
2690
+ // that should save 90% of the calculation, we'll still do
2691
+ // unecessary work but it's a step in the right direction.
2692
+ // note that this flag doesn't necessarily indicate anything
2693
+ // has changed -- it will get set if you do a global recalc,
2694
+ // because that marks everything as dirty. still a good step
2695
+ // though.
2696
+ return;
2697
+ }
2698
+ this.flush_conditional_formats = false; // unset
2699
+ const temp = [];
2700
+ const checklist = [...this.conditional_format_checklist];
2701
+ this.conditional_format_checklist = []; // flush
2702
+ for (const format of this.conditional_formats) {
2703
+ if (format.internal?.vertex?.updated) {
2704
+ format.internal.vertex.updated = false;
2705
+ }
2706
+ // NOTE: if you go backwards, then you can short-circuit if a format
2707
+ // is already set. except then if you want to support "stop" rules,
2708
+ // that won't work.
2709
+ //
2710
+ // although you might still want to go backwards as it's easier to
2711
+ // apply stop rules in reverse (why? because if you are going backwards,
2712
+ // you can just drop everything on the stack when you see a
2713
+ // stop rule. if you go forwards, you need some sort of indicator
2714
+ // or flag).
2715
+ // there's more to this, because there are rules that apply to areas,
2716
+ // which might stop, and there's priority. so we probably need those
2717
+ // flags eventually.
2718
+ const area = JSON.parse(JSON.stringify(format.area));
2719
+ if (area.start.row === null || area.end.row === null) {
2720
+ area.start.row = 0;
2721
+ area.end.row = this.cells.rows - 1;
2722
+ }
2723
+ if (area.start.column === null || area.end.column === null) {
2724
+ area.start.column = 0;
2725
+ area.end.column = this.cells.columns - 1;
2726
+ }
2727
+ const result = format.internal?.vertex?.result;
2728
+ if (format.type === 'gradient') {
2729
+ if (result && format.internal?.gradient) {
2730
+ const property = format.property ?? 'fill';
2731
+ if (result.type === ValueType.array) {
2732
+ for (let row = area.start.row; row <= area.end.row; row++) {
2733
+ for (let column = area.start.column; column <= area.end.column; column++) {
2734
+ const value = result.value[column - area.start.column][row - area.start.row];
2735
+ if (value.type === ValueType.number) {
2736
+ if (!temp[row]) {
2737
+ temp[row] = [];
2738
+ }
2739
+ if (!temp[row][column]) {
2740
+ temp[row][column] = [];
2741
+ }
2742
+ const color = format.internal.gradient.Interpolate(value.value);
2743
+ temp[row][column].push({ [property]: color });
2744
+ }
2745
+ }
2746
+ }
2747
+ }
2748
+ else if (result.type === ValueType.number) {
2749
+ const color = format.internal.gradient.Interpolate(result.value);
2750
+ for (let row = area.start.row; row <= area.end.row; row++) {
2751
+ if (!temp[row]) {
2752
+ temp[row] = [];
2753
+ }
2754
+ for (let column = area.start.column; column <= area.end.column; column++) {
2755
+ if (!temp[row][column]) {
2756
+ temp[row][column] = [];
2757
+ }
2758
+ temp[row][column].push({ [property]: color });
2759
+ }
2760
+ }
2761
+ }
2762
+ checklist.push(area);
2763
+ this.conditional_format_checklist.push(area);
2764
+ }
2765
+ }
2766
+ else if (format.type === 'data-bar') {
2767
+ if (result) {
2768
+ if (result.type === ValueType.array) {
2769
+ for (let row = area.start.row; row <= area.end.row; row++) {
2770
+ for (let column = area.start.column; column <= area.end.column; column++) {
2771
+ const value = result.value[column - area.start.column][row - area.start.row];
2772
+ if (value.type === ValueType.array) {
2773
+ const [pct, zero] = value.value[0];
2774
+ if (pct.type === ValueType.number && zero.type === ValueType.number) {
2775
+ if (!temp[row]) {
2776
+ temp[row] = [];
2777
+ }
2778
+ if (!temp[row][column]) {
2779
+ temp[row][column] = [];
2780
+ }
2781
+ // const color = format.internal.gradient.Interpolate(value.value);
2782
+ // temp[row][column].push({ [property]: color});
2783
+ temp[row][column].push({
2784
+ databar: {
2785
+ value: pct.value,
2786
+ zero: zero.value,
2787
+ fill: format.fill,
2788
+ negative: format.negative,
2789
+ hide_values: format.hide_values,
2790
+ }
2791
+ });
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+ }
2797
+ checklist.push(area);
2798
+ this.conditional_format_checklist.push(area);
2799
+ }
2800
+ }
2801
+ else {
2802
+ // handle types expression, cell-match and duplicate-values
2803
+ if (result) {
2804
+ if (result.type === ValueType.array) {
2805
+ for (let row = area.start.row; row <= area.end.row; row++) {
2806
+ for (let column = area.start.column; column <= area.end.column; column++) {
2807
+ const value = result.value[column - area.start.column][row - area.start.row];
2808
+ if (value && (value.type === ValueType.boolean || value.type === ValueType.number) && !!value.value) {
2809
+ if (!temp[row]) {
2810
+ temp[row] = [];
2811
+ }
2812
+ if (!temp[row][column]) {
2813
+ temp[row][column] = [];
2814
+ }
2815
+ temp[row][column].push(format.style);
2816
+ }
2817
+ }
2818
+ }
2819
+ }
2820
+ else {
2821
+ if (result.type === ValueType.boolean || result.type === ValueType.number) {
2822
+ if (result.value) {
2823
+ for (let row = area.start.row; row <= area.end.row; row++) {
2824
+ if (!temp[row]) {
2825
+ temp[row] = [];
2826
+ }
2827
+ for (let column = area.start.column; column <= area.end.column; column++) {
2828
+ if (!temp[row][column]) {
2829
+ temp[row][column] = [];
2830
+ }
2831
+ temp[row][column].push(format.style);
2832
+ }
2833
+ }
2834
+ }
2835
+ }
2836
+ }
2837
+ checklist.push(area);
2838
+ this.conditional_format_checklist.push(area);
2839
+ }
2840
+ }
2841
+ }
2842
+ for (const area of checklist) {
2843
+ this.BleedFlush(area);
2844
+ }
2845
+ this.conditional_format_cache = temp;
2846
+ }
2847
+ ConditionalFormatForCell(address) {
2848
+ if (this.conditional_format_cache[address.row]) {
2849
+ return this.conditional_format_cache[address.row][address.column] || [];
2850
+ }
2851
+ return [];
2852
+ }
2853
+ /**
2854
+ * generates the composite style for the given cell. this
2855
+ * should only be used to generate a cache of styles (Q: really? PERF?)
2856
+ *
2857
+ * the "apply_cell_style" parameter is used for testing when pruning. we
2858
+ * want to check what happens if the cell style is not applied; if nothing
2859
+ * happens, then we can drop the cell style (or the property in the style).
2860
+ */
2861
+ CompositeStyleForCell(address, apply_cell_style = true, apply_row_pattern = true, apply_default = true, apply_conditional = true) {
2862
+ const { row, column } = address;
2863
+ const stack = [];
2864
+ if (apply_default) {
2865
+ stack.push(this.default_style_properties);
2866
+ }
2867
+ stack.push(this.sheet_style);
2868
+ if (apply_row_pattern && this.row_pattern.length) {
2869
+ stack.push(this.row_pattern[row % this.row_pattern.length]);
2870
+ }
2871
+ if (this.row_styles[row]) {
2872
+ stack.push(this.row_styles[row]);
2873
+ }
2874
+ if (this.column_styles[column]) {
2875
+ stack.push(this.column_styles[column]);
2876
+ }
2877
+ if (apply_cell_style
2878
+ && this.cell_style[column]
2879
+ && this.cell_style[column][row]) {
2880
+ stack.push(this.cell_style[column][row]);
2881
+ }
2882
+ if (apply_conditional) {
2883
+ stack.push(...this.ConditionalFormatForCell(address));
2884
+ }
2885
+ return Style.Composite(stack);
2886
+ }
2887
+ /**
2888
+ * can we use the rendered JSON as a key, instead?
2889
+ */
2890
+ GetStyleIndex(style) {
2891
+ const json = JSON.stringify(style);
2892
+ for (let i = 0; i < this.style_json_map.length; i++) {
2893
+ if (json === this.style_json_map[i])
2894
+ return i; // match
2895
+ }
2896
+ // ok we need to add it to the list. make sure to add a copy,
2897
+ // and add json to the json index.
2898
+ const new_index = this.style_map.length;
2899
+ this.style_map.push(JSON.parse(json));
2900
+ this.style_json_map.push(json);
2901
+ return new_index;
2902
+ }
2903
+ }
2904
+ //# sourceMappingURL=sheet.js.map