@trebco/treb 36.1.4 → 37.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api-config.json +1 -1
- package/build/package.json +119 -0
- package/build/treb-base-types/src/api_types.d.ts +11 -0
- package/build/treb-base-types/src/api_types.js +22 -0
- package/build/treb-base-types/src/api_types.js.map +1 -0
- package/build/treb-base-types/src/area-utils.d.ts +9 -0
- package/build/treb-base-types/src/area-utils.js +50 -0
- package/build/treb-base-types/src/area-utils.js.map +1 -0
- package/build/treb-base-types/src/area.d.ts +182 -0
- package/build/treb-base-types/src/area.js +715 -0
- package/build/treb-base-types/src/area.js.map +1 -0
- package/build/treb-base-types/src/basic_types.d.ts +20 -0
- package/build/treb-base-types/src/basic_types.js +22 -0
- package/build/treb-base-types/src/basic_types.js.map +1 -0
- package/build/treb-base-types/src/cell.d.ts +167 -0
- package/build/treb-base-types/src/cell.js +432 -0
- package/build/treb-base-types/src/cell.js.map +1 -0
- package/build/treb-base-types/src/cells.d.ts +251 -0
- package/build/treb-base-types/src/cells.js +1136 -0
- package/build/treb-base-types/src/cells.js.map +1 -0
- package/build/treb-base-types/src/color.d.ts +35 -0
- package/build/treb-base-types/src/color.js +162 -0
- package/build/treb-base-types/src/color.js.map +1 -0
- package/build/treb-base-types/src/dom-utilities.d.ts +70 -0
- package/build/treb-base-types/src/dom-utilities.js +144 -0
- package/build/treb-base-types/src/dom-utilities.js.map +1 -0
- package/build/treb-base-types/src/evaluate-options.d.ts +35 -0
- package/build/treb-base-types/src/evaluate-options.js +22 -0
- package/build/treb-base-types/src/evaluate-options.js.map +1 -0
- package/build/treb-base-types/src/font-stack.d.ts +37 -0
- package/build/treb-base-types/src/font-stack.js +93 -0
- package/build/treb-base-types/src/font-stack.js.map +1 -0
- package/build/treb-base-types/src/gradient.d.ts +18 -0
- package/build/treb-base-types/src/gradient.js +86 -0
- package/build/treb-base-types/src/gradient.js.map +1 -0
- package/build/treb-base-types/src/import.d.ts +48 -0
- package/build/treb-base-types/src/import.js +22 -0
- package/build/treb-base-types/src/import.js.map +1 -0
- package/build/treb-base-types/src/index-standalone.d.ts +6 -0
- package/build/treb-base-types/src/index-standalone.js +27 -0
- package/build/treb-base-types/src/index-standalone.js.map +1 -0
- package/build/treb-base-types/src/index.d.ts +22 -0
- package/build/treb-base-types/src/index.js +45 -0
- package/build/treb-base-types/src/index.js.map +1 -0
- package/build/treb-base-types/src/layout.d.ts +22 -0
- package/build/treb-base-types/src/layout.js +22 -0
- package/build/treb-base-types/src/layout.js.map +1 -0
- package/build/treb-base-types/src/localization.d.ts +37 -0
- package/build/treb-base-types/src/localization.js +157 -0
- package/build/treb-base-types/src/localization.js.map +1 -0
- package/build/treb-base-types/src/rectangle.d.ts +51 -0
- package/build/treb-base-types/src/rectangle.js +123 -0
- package/build/treb-base-types/src/rectangle.js.map +1 -0
- package/build/treb-base-types/src/render_text.d.ts +34 -0
- package/build/treb-base-types/src/render_text.js +22 -0
- package/build/treb-base-types/src/render_text.js.map +1 -0
- package/build/treb-base-types/src/style.d.ts +214 -0
- package/build/treb-base-types/src/style.js +373 -0
- package/build/treb-base-types/src/style.js.map +1 -0
- package/build/treb-base-types/src/table.d.ts +58 -0
- package/build/treb-base-types/src/table.js +27 -0
- package/build/treb-base-types/src/table.js.map +1 -0
- package/build/treb-base-types/src/text_part.d.ts +26 -0
- package/build/treb-base-types/src/text_part.js +47 -0
- package/build/treb-base-types/src/text_part.js.map +1 -0
- package/build/treb-base-types/src/theme.d.ts +120 -0
- package/build/treb-base-types/src/theme.js +460 -0
- package/build/treb-base-types/src/theme.js.map +1 -0
- package/build/treb-base-types/src/union.d.ts +73 -0
- package/build/treb-base-types/src/union.js +61 -0
- package/build/treb-base-types/src/union.js.map +1 -0
- package/build/treb-base-types/src/value-type.d.ts +86 -0
- package/build/treb-base-types/src/value-type.js +168 -0
- package/build/treb-base-types/src/value-type.js.map +1 -0
- package/build/treb-base-types/src/worker-proxy.d.ts +95 -0
- package/build/treb-base-types/src/worker-proxy.js +221 -0
- package/build/treb-base-types/src/worker-proxy.js.map +1 -0
- package/build/treb-calculator/src/calculator.d.ts +249 -0
- package/build/treb-calculator/src/calculator.js +2755 -0
- package/build/treb-calculator/src/calculator.js.map +1 -0
- package/build/treb-calculator/src/complex-math.d.ts +75 -0
- package/build/treb-calculator/src/complex-math.js +559 -0
- package/build/treb-calculator/src/complex-math.js.map +1 -0
- package/build/treb-calculator/src/dag/array-vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/array-vertex.js +156 -0
- package/build/treb-calculator/src/dag/array-vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.d.ts +48 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js +84 -0
- package/build/treb-calculator/src/dag/calculation_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/graph.d.ts +134 -0
- package/build/treb-calculator/src/dag/graph.js +842 -0
- package/build/treb-calculator/src/dag/graph.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.d.ts +58 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js +232 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.d.ts +20 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js +25 -0
- package/build/treb-calculator/src/dag/spreadsheet_vertex_base.js.map +1 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.d.ts +43 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js +81 -0
- package/build/treb-calculator/src/dag/state_leaf_vertex.js.map +1 -0
- package/build/treb-calculator/src/dag/vertex.d.ts +71 -0
- package/build/treb-calculator/src/dag/vertex.js +274 -0
- package/build/treb-calculator/src/dag/vertex.js.map +1 -0
- package/build/treb-calculator/src/descriptors.d.ts +189 -0
- package/build/treb-calculator/src/descriptors.js +22 -0
- package/build/treb-calculator/src/descriptors.js.map +1 -0
- package/build/treb-calculator/src/expression-calculator.d.ts +127 -0
- package/build/treb-calculator/src/expression-calculator.js +1033 -0
- package/build/treb-calculator/src/expression-calculator.js.map +1 -0
- package/build/treb-calculator/src/function-error.d.ts +35 -0
- package/build/treb-calculator/src/function-error.js +85 -0
- package/build/treb-calculator/src/function-error.js.map +1 -0
- package/build/treb-calculator/src/function-library.d.ts +22 -0
- package/build/treb-calculator/src/function-library.js +96 -0
- package/build/treb-calculator/src/function-library.js.map +1 -0
- package/build/treb-calculator/src/functions/base-functions.d.ts +7 -0
- package/build/treb-calculator/src/functions/base-functions.js +2611 -0
- package/build/treb-calculator/src/functions/base-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/beta.d.ts +17 -0
- package/build/treb-calculator/src/functions/beta.js +201 -0
- package/build/treb-calculator/src/functions/beta.js.map +1 -0
- package/build/treb-calculator/src/functions/checkbox.d.ts +3 -0
- package/build/treb-calculator/src/functions/checkbox.js +128 -0
- package/build/treb-calculator/src/functions/checkbox.js.map +1 -0
- package/build/treb-calculator/src/functions/complex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/complex-functions.js +217 -0
- package/build/treb-calculator/src/functions/complex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/date-utils.d.ts +3 -0
- package/build/treb-calculator/src/functions/date-utils.js +59 -0
- package/build/treb-calculator/src/functions/date-utils.js.map +1 -0
- package/build/treb-calculator/src/functions/finance-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/finance-functions.js +547 -0
- package/build/treb-calculator/src/functions/finance-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/fp.d.ts +2 -0
- package/build/treb-calculator/src/functions/fp.js +463 -0
- package/build/treb-calculator/src/functions/fp.js.map +1 -0
- package/build/treb-calculator/src/functions/function-utilities.d.ts +2 -0
- package/build/treb-calculator/src/functions/function-utilities.js +36 -0
- package/build/treb-calculator/src/functions/function-utilities.js.map +1 -0
- package/build/treb-calculator/src/functions/gamma.d.ts +20 -0
- package/build/treb-calculator/src/functions/gamma.js +142 -0
- package/build/treb-calculator/src/functions/gamma.js.map +1 -0
- package/build/treb-calculator/src/functions/information-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/information-functions.js +71 -0
- package/build/treb-calculator/src/functions/information-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/lambda-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/lambda-functions.js +85 -0
- package/build/treb-calculator/src/functions/lambda-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/matrix-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/matrix-functions.js +144 -0
- package/build/treb-calculator/src/functions/matrix-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/normal.d.ts +2 -0
- package/build/treb-calculator/src/functions/normal.js +32 -0
- package/build/treb-calculator/src/functions/normal.js.map +1 -0
- package/build/treb-calculator/src/functions/regex-functions.d.ts +2 -0
- package/build/treb-calculator/src/functions/regex-functions.js +188 -0
- package/build/treb-calculator/src/functions/regex-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/sparkline.d.ts +37 -0
- package/build/treb-calculator/src/functions/sparkline.js +264 -0
- package/build/treb-calculator/src/functions/sparkline.js.map +1 -0
- package/build/treb-calculator/src/functions/statistics-functions.d.ts +6 -0
- package/build/treb-calculator/src/functions/statistics-functions.js +989 -0
- package/build/treb-calculator/src/functions/statistics-functions.js.map +1 -0
- package/build/treb-calculator/src/functions/students-t.d.ts +3 -0
- package/build/treb-calculator/src/functions/students-t.js +64 -0
- package/build/treb-calculator/src/functions/students-t.js.map +1 -0
- package/build/treb-calculator/src/functions/text-functions.d.ts +3 -0
- package/build/treb-calculator/src/functions/text-functions.js +320 -0
- package/build/treb-calculator/src/functions/text-functions.js.map +1 -0
- package/build/treb-calculator/src/index.d.ts +2 -0
- package/build/treb-calculator/src/index.js +22 -0
- package/build/treb-calculator/src/index.js.map +1 -0
- package/build/treb-calculator/src/notifier-types.d.ts +26 -0
- package/build/treb-calculator/src/notifier-types.js +22 -0
- package/build/treb-calculator/src/notifier-types.js.map +1 -0
- package/build/treb-calculator/src/primitives.d.ts +15 -0
- package/build/treb-calculator/src/primitives.js +398 -0
- package/build/treb-calculator/src/primitives.js.map +1 -0
- package/build/treb-calculator/src/utilities.d.ts +68 -0
- package/build/treb-calculator/src/utilities.js +324 -0
- package/build/treb-calculator/src/utilities.js.map +1 -0
- package/build/treb-charts/src/chart-functions.d.ts +8 -0
- package/build/treb-charts/src/chart-functions.js +209 -0
- package/build/treb-charts/src/chart-functions.js.map +1 -0
- package/build/treb-charts/src/chart-types.d.ts +233 -0
- package/build/treb-charts/src/chart-types.js +57 -0
- package/build/treb-charts/src/chart-types.js.map +1 -0
- package/build/treb-charts/src/chart-utils.d.ts +106 -0
- package/build/treb-charts/src/chart-utils.js +1060 -0
- package/build/treb-charts/src/chart-utils.js.map +1 -0
- package/build/treb-charts/src/chart.d.ts +23 -0
- package/build/treb-charts/src/chart.js +94 -0
- package/build/treb-charts/src/chart.js.map +1 -0
- package/build/treb-charts/src/default-chart-renderer.d.ts +16 -0
- package/build/treb-charts/src/default-chart-renderer.js +533 -0
- package/build/treb-charts/src/default-chart-renderer.js.map +1 -0
- package/build/treb-charts/src/index.d.ts +5 -0
- package/build/treb-charts/src/index.js +24 -0
- package/build/treb-charts/src/index.js.map +1 -0
- package/build/treb-charts/src/main.d.ts +1 -0
- package/build/treb-charts/src/main.js +34 -0
- package/build/treb-charts/src/main.js.map +1 -0
- package/build/treb-charts/src/quicksort.d.ts +1 -0
- package/build/treb-charts/src/quicksort.js +49 -0
- package/build/treb-charts/src/quicksort.js.map +1 -0
- package/build/treb-charts/src/rectangle.d.ts +18 -0
- package/build/treb-charts/src/rectangle.js +41 -0
- package/build/treb-charts/src/rectangle.js.map +1 -0
- package/build/treb-charts/src/renderer-type.d.ts +24 -0
- package/build/treb-charts/src/renderer-type.js +22 -0
- package/build/treb-charts/src/renderer-type.js.map +1 -0
- package/build/treb-charts/src/renderer.d.ts +127 -0
- package/build/treb-charts/src/renderer.js +1518 -0
- package/build/treb-charts/src/renderer.js.map +1 -0
- package/build/treb-charts/src/util.d.ts +18 -0
- package/build/treb-charts/src/util.js +71 -0
- package/build/treb-charts/src/util.js.map +1 -0
- package/build/treb-data-model/src/annotation.d.ts +167 -0
- package/build/treb-data-model/src/annotation.js +120 -0
- package/build/treb-data-model/src/annotation.js.map +1 -0
- package/build/treb-data-model/src/conditional_format.d.ts +155 -0
- package/build/treb-data-model/src/conditional_format.js +62 -0
- package/build/treb-data-model/src/conditional_format.js.map +1 -0
- package/build/treb-data-model/src/data-validation.d.ts +28 -0
- package/build/treb-data-model/src/data-validation.js +22 -0
- package/build/treb-data-model/src/data-validation.js.map +1 -0
- package/build/treb-data-model/src/data_model.d.ts +173 -0
- package/build/treb-data-model/src/data_model.js +637 -0
- package/build/treb-data-model/src/data_model.js.map +1 -0
- package/build/treb-data-model/src/index.d.ts +13 -0
- package/build/treb-data-model/src/index.js +28 -0
- package/build/treb-data-model/src/index.js.map +1 -0
- package/build/treb-data-model/src/language-model.d.ts +22 -0
- package/build/treb-data-model/src/language-model.js +22 -0
- package/build/treb-data-model/src/language-model.js.map +1 -0
- package/build/treb-data-model/src/named.d.ts +124 -0
- package/build/treb-data-model/src/named.js +372 -0
- package/build/treb-data-model/src/named.js.map +1 -0
- package/build/treb-data-model/src/serialize_options.d.ts +49 -0
- package/build/treb-data-model/src/serialize_options.js +22 -0
- package/build/treb-data-model/src/serialize_options.js.map +1 -0
- package/build/treb-data-model/src/sheet.d.ts +499 -0
- package/build/treb-data-model/src/sheet.js +2904 -0
- package/build/treb-data-model/src/sheet.js.map +1 -0
- package/build/treb-data-model/src/sheet_collection.d.ts +58 -0
- package/build/treb-data-model/src/sheet_collection.js +112 -0
- package/build/treb-data-model/src/sheet_collection.js.map +1 -0
- package/build/treb-data-model/src/sheet_selection.d.ts +42 -0
- package/build/treb-data-model/src/sheet_selection.js +39 -0
- package/build/treb-data-model/src/sheet_selection.js.map +1 -0
- package/build/treb-data-model/src/sheet_types.d.ts +104 -0
- package/build/treb-data-model/src/sheet_types.js +22 -0
- package/build/treb-data-model/src/sheet_types.js.map +1 -0
- package/build/treb-data-model/src/types.d.ts +59 -0
- package/build/treb-data-model/src/types.js +22 -0
- package/build/treb-data-model/src/types.js.map +1 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.d.ts +75 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js +1144 -0
- package/build/treb-embed/src/custom-element/spreadsheet-constructor.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-global.d.ts +36 -0
- package/build/treb-embed/src/custom-element/treb-global.js +64 -0
- package/build/treb-embed/src/custom-element/treb-global.js.map +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.d.ts +1 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js +61 -0
- package/build/treb-embed/src/custom-element/treb-spreadsheet-element.js.map +1 -0
- package/build/treb-embed/src/embedded-spreadsheet.d.ts +1358 -0
- package/build/treb-embed/src/embedded-spreadsheet.js +5205 -0
- package/build/treb-embed/src/embedded-spreadsheet.js.map +1 -0
- package/build/treb-embed/src/index.d.ts +12 -0
- package/build/treb-embed/src/index.js +34 -0
- package/build/treb-embed/src/index.js.map +1 -0
- package/build/treb-embed/src/options.d.ts +266 -0
- package/build/treb-embed/src/options.js +56 -0
- package/build/treb-embed/src/options.js.map +1 -0
- package/build/treb-embed/src/plugin.d.ts +9 -0
- package/build/treb-embed/src/plugin.js +22 -0
- package/build/treb-embed/src/plugin.js.map +1 -0
- package/build/treb-embed/src/progress-dialog.d.ts +49 -0
- package/build/treb-embed/src/progress-dialog.js +178 -0
- package/build/treb-embed/src/progress-dialog.js.map +1 -0
- package/build/treb-embed/src/selection-state.d.ts +15 -0
- package/build/treb-embed/src/selection-state.js +22 -0
- package/build/treb-embed/src/selection-state.js.map +1 -0
- package/build/treb-embed/src/spinner.d.ts +8 -0
- package/build/treb-embed/src/spinner.js +40 -0
- package/build/treb-embed/src/spinner.js.map +1 -0
- package/build/treb-embed/src/toolbar-message.d.ts +72 -0
- package/build/treb-embed/src/toolbar-message.js +22 -0
- package/build/treb-embed/src/toolbar-message.js.map +1 -0
- package/build/treb-embed/src/types.d.ts +185 -0
- package/build/treb-embed/src/types.js +45 -0
- package/build/treb-embed/src/types.js.map +1 -0
- package/build/treb-embed/tsconfig.tsbuildinfo +1 -0
- package/build/treb-export/src/address-type.d.ts +34 -0
- package/build/treb-export/src/address-type.js +53 -0
- package/build/treb-export/src/address-type.js.map +1 -0
- package/build/treb-export/src/base-template.d.ts +1 -0
- package/build/treb-export/src/base-template.js +22 -0
- package/build/treb-export/src/base-template.js.map +1 -0
- package/build/treb-export/src/column-width.d.ts +2 -0
- package/build/treb-export/src/column-width.js +80 -0
- package/build/treb-export/src/column-width.js.map +1 -0
- package/build/treb-export/src/drawing/bubble-chart-template.d.ts +514 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js +544 -0
- package/build/treb-export/src/drawing/bubble-chart-template.js.map +1 -0
- package/build/treb-export/src/drawing/chart-template-components2.d.ts +365 -0
- package/build/treb-export/src/drawing/chart-template-components2.js +386 -0
- package/build/treb-export/src/drawing/chart-template-components2.js.map +1 -0
- package/build/treb-export/src/drawing/chart.d.ts +26 -0
- package/build/treb-export/src/drawing/chart.js +247 -0
- package/build/treb-export/src/drawing/chart.js.map +1 -0
- package/build/treb-export/src/drawing/column-chart-template2.d.ts +490 -0
- package/build/treb-export/src/drawing/column-chart-template2.js +518 -0
- package/build/treb-export/src/drawing/column-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/donut-chart-template2.d.ts +272 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js +293 -0
- package/build/treb-export/src/drawing/donut-chart-template2.js.map +1 -0
- package/build/treb-export/src/drawing/drawing.d.ts +49 -0
- package/build/treb-export/src/drawing/drawing.js +193 -0
- package/build/treb-export/src/drawing/drawing.js.map +1 -0
- package/build/treb-export/src/drawing/embedded-image.d.ts +12 -0
- package/build/treb-export/src/drawing/embedded-image.js +54 -0
- package/build/treb-export/src/drawing/embedded-image.js.map +1 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.d.ts +520 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js +551 -0
- package/build/treb-export/src/drawing/scatter-chart-template2.js.map +1 -0
- package/build/treb-export/src/export.d.ts +72 -0
- package/build/treb-export/src/export.js +2039 -0
- package/build/treb-export/src/export.js.map +1 -0
- package/build/treb-export/src/import-export-messages.d.ts +31 -0
- package/build/treb-export/src/import-export-messages.js +22 -0
- package/build/treb-export/src/import-export-messages.js.map +1 -0
- package/build/treb-export/src/import.d.ts +33 -0
- package/build/treb-export/src/import.js +1258 -0
- package/build/treb-export/src/import.js.map +1 -0
- package/build/treb-export/src/index.worker.d.ts +1 -0
- package/build/treb-export/src/index.worker.js +93 -0
- package/build/treb-export/src/index.worker.js.map +1 -0
- package/build/treb-export/src/metadata.d.ts +51 -0
- package/build/treb-export/src/metadata.js +153 -0
- package/build/treb-export/src/metadata.js.map +1 -0
- package/build/treb-export/src/ooxml.d.ts +7 -0
- package/build/treb-export/src/ooxml.js +41 -0
- package/build/treb-export/src/ooxml.js.map +1 -0
- package/build/treb-export/src/relationship.d.ts +8 -0
- package/build/treb-export/src/relationship.js +27 -0
- package/build/treb-export/src/relationship.js.map +1 -0
- package/build/treb-export/src/shared-strings.d.ts +11 -0
- package/build/treb-export/src/shared-strings.js +105 -0
- package/build/treb-export/src/shared-strings.js.map +1 -0
- package/build/treb-export/src/template-2.d.ts +1 -0
- package/build/treb-export/src/template-2.js +22 -0
- package/build/treb-export/src/template-2.js.map +1 -0
- package/build/treb-export/src/unescape_xml.d.ts +1 -0
- package/build/treb-export/src/unescape_xml.js +61 -0
- package/build/treb-export/src/unescape_xml.js.map +1 -0
- package/build/treb-export/src/workbook-sheet.d.ts +75 -0
- package/build/treb-export/src/workbook-sheet.js +128 -0
- package/build/treb-export/src/workbook-sheet.js.map +1 -0
- package/build/treb-export/src/workbook-style.d.ts +110 -0
- package/build/treb-export/src/workbook-style.js +1134 -0
- package/build/treb-export/src/workbook-style.js.map +1 -0
- package/build/treb-export/src/workbook-theme.d.ts +13 -0
- package/build/treb-export/src/workbook-theme.js +85 -0
- package/build/treb-export/src/workbook-theme.js.map +1 -0
- package/build/treb-export/src/workbook.d.ts +123 -0
- package/build/treb-export/src/workbook.js +644 -0
- package/build/treb-export/src/workbook.js.map +1 -0
- package/build/treb-export/src/xml-test.d.ts +9 -0
- package/build/treb-export/src/xml-test.js +52 -0
- package/build/treb-export/src/xml-test.js.map +1 -0
- package/build/treb-export/src/xml-utils.d.ts +76 -0
- package/build/treb-export/src/xml-utils.js +223 -0
- package/build/treb-export/src/xml-utils.js.map +1 -0
- package/build/treb-export/src/zip-wrapper.d.ts +22 -0
- package/build/treb-export/src/zip-wrapper.js +93 -0
- package/build/treb-export/src/zip-wrapper.js.map +1 -0
- package/build/treb-format/src/format.d.ts +130 -0
- package/build/treb-format/src/format.js +805 -0
- package/build/treb-format/src/format.js.map +1 -0
- package/build/treb-format/src/format_cache.d.ts +55 -0
- package/build/treb-format/src/format_cache.js +166 -0
- package/build/treb-format/src/format_cache.js.map +1 -0
- package/build/treb-format/src/format_parser.d.ts +70 -0
- package/build/treb-format/src/format_parser.js +618 -0
- package/build/treb-format/src/format_parser.js.map +1 -0
- package/build/treb-format/src/index.d.ts +4 -0
- package/build/treb-format/src/index.js +25 -0
- package/build/treb-format/src/index.js.map +1 -0
- package/build/treb-format/src/number_format_section.d.ts +58 -0
- package/build/treb-format/src/number_format_section.js +78 -0
- package/build/treb-format/src/number_format_section.js.map +1 -0
- package/build/treb-format/src/value_parser.d.ts +48 -0
- package/build/treb-format/src/value_parser.js +244 -0
- package/build/treb-format/src/value_parser.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete.d.ts +39 -0
- package/build/treb-grid/src/editors/autocomplete.js +316 -0
- package/build/treb-grid/src/editors/autocomplete.js.map +1 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.d.ts +74 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js +212 -0
- package/build/treb-grid/src/editors/autocomplete_matcher.js.map +1 -0
- package/build/treb-grid/src/editors/editor.d.ts +214 -0
- package/build/treb-grid/src/editors/editor.js +879 -0
- package/build/treb-grid/src/editors/editor.js.map +1 -0
- package/build/treb-grid/src/editors/external_editor.d.ts +11 -0
- package/build/treb-grid/src/editors/external_editor.js +118 -0
- package/build/treb-grid/src/editors/external_editor.js.map +1 -0
- package/build/treb-grid/src/editors/formula_bar.d.ts +85 -0
- package/build/treb-grid/src/editors/formula_bar.js +444 -0
- package/build/treb-grid/src/editors/formula_bar.js.map +1 -0
- package/build/treb-grid/src/editors/overlay_editor.d.ts +85 -0
- package/build/treb-grid/src/editors/overlay_editor.js +353 -0
- package/build/treb-grid/src/editors/overlay_editor.js.map +1 -0
- package/build/treb-grid/src/index.d.ts +12 -0
- package/build/treb-grid/src/index.js +28 -0
- package/build/treb-grid/src/index.js.map +1 -0
- package/build/treb-grid/src/layout/base_layout.d.ts +346 -0
- package/build/treb-grid/src/layout/base_layout.js +2050 -0
- package/build/treb-grid/src/layout/base_layout.js.map +1 -0
- package/build/treb-grid/src/layout/grid_layout.d.ts +19 -0
- package/build/treb-grid/src/layout/grid_layout.js +235 -0
- package/build/treb-grid/src/layout/grid_layout.js.map +1 -0
- package/build/treb-grid/src/layout/mock-layout.d.ts +10 -0
- package/build/treb-grid/src/layout/mock-layout.js +37 -0
- package/build/treb-grid/src/layout/mock-layout.js.map +1 -0
- package/build/treb-grid/src/render/selection-renderer.d.ts +97 -0
- package/build/treb-grid/src/render/selection-renderer.js +315 -0
- package/build/treb-grid/src/render/selection-renderer.js.map +1 -0
- package/build/treb-grid/src/render/svg_header_overlay.d.ts +20 -0
- package/build/treb-grid/src/render/svg_header_overlay.js +76 -0
- package/build/treb-grid/src/render/svg_header_overlay.js.map +1 -0
- package/build/treb-grid/src/render/svg_selection_block.d.ts +27 -0
- package/build/treb-grid/src/render/svg_selection_block.js +106 -0
- package/build/treb-grid/src/render/svg_selection_block.js.map +1 -0
- package/build/treb-grid/src/render/tile_renderer.d.ts +121 -0
- package/build/treb-grid/src/render/tile_renderer.js +1609 -0
- package/build/treb-grid/src/render/tile_renderer.js.map +1 -0
- package/build/treb-grid/src/types/border_constants.d.ts +9 -0
- package/build/treb-grid/src/types/border_constants.js +34 -0
- package/build/treb-grid/src/types/border_constants.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data.d.ts +11 -0
- package/build/treb-grid/src/types/clipboard_data.js +22 -0
- package/build/treb-grid/src/types/clipboard_data.js.map +1 -0
- package/build/treb-grid/src/types/clipboard_data2.d.ts +46 -0
- package/build/treb-grid/src/types/clipboard_data2.js +22 -0
- package/build/treb-grid/src/types/clipboard_data2.js.map +1 -0
- package/build/treb-grid/src/types/drag_mask.d.ts +10 -0
- package/build/treb-grid/src/types/drag_mask.js +78 -0
- package/build/treb-grid/src/types/drag_mask.js.map +1 -0
- package/build/treb-grid/src/types/external_editor_config.d.ts +33 -0
- package/build/treb-grid/src/types/external_editor_config.js +22 -0
- package/build/treb-grid/src/types/external_editor_config.js.map +1 -0
- package/build/treb-grid/src/types/grid.d.ts +806 -0
- package/build/treb-grid/src/types/grid.js +6410 -0
- package/build/treb-grid/src/types/grid.js.map +1 -0
- package/build/treb-grid/src/types/grid_base.d.ts +442 -0
- package/build/treb-grid/src/types/grid_base.js +3523 -0
- package/build/treb-grid/src/types/grid_base.js.map +1 -0
- package/build/treb-grid/src/types/grid_command.d.ts +408 -0
- package/build/treb-grid/src/types/grid_command.js +75 -0
- package/build/treb-grid/src/types/grid_command.js.map +1 -0
- package/build/treb-grid/src/types/grid_events.d.ts +93 -0
- package/build/treb-grid/src/types/grid_events.js +36 -0
- package/build/treb-grid/src/types/grid_events.js.map +1 -0
- package/build/treb-grid/src/types/grid_options.d.ts +50 -0
- package/build/treb-grid/src/types/grid_options.js +34 -0
- package/build/treb-grid/src/types/grid_options.js.map +1 -0
- package/build/treb-grid/src/types/scale-control.d.ts +21 -0
- package/build/treb-grid/src/types/scale-control.js +148 -0
- package/build/treb-grid/src/types/scale-control.js.map +1 -0
- package/build/treb-grid/src/types/set_range_options.d.ts +24 -0
- package/build/treb-grid/src/types/set_range_options.js +22 -0
- package/build/treb-grid/src/types/set_range_options.js.map +1 -0
- package/build/treb-grid/src/types/tab_bar.d.ts +84 -0
- package/build/treb-grid/src/types/tab_bar.js +426 -0
- package/build/treb-grid/src/types/tab_bar.js.map +1 -0
- package/build/treb-grid/src/types/tile.d.ts +29 -0
- package/build/treb-grid/src/types/tile.js +22 -0
- package/build/treb-grid/src/types/tile.js.map +1 -0
- package/build/treb-grid/src/types/update_flags.d.ts +48 -0
- package/build/treb-grid/src/types/update_flags.js +22 -0
- package/build/treb-grid/src/types/update_flags.js.map +1 -0
- package/build/treb-grid/src/util/fontmetrics.d.ts +21 -0
- package/build/treb-grid/src/util/fontmetrics.js +82 -0
- package/build/treb-grid/src/util/fontmetrics.js.map +1 -0
- package/build/treb-grid/src/util/ua.d.ts +33 -0
- package/build/treb-grid/src/util/ua.js +86 -0
- package/build/treb-grid/src/util/ua.js.map +1 -0
- package/build/treb-parser/src/csv-parser.d.ts +13 -0
- package/build/treb-parser/src/csv-parser.js +107 -0
- package/build/treb-parser/src/csv-parser.js.map +1 -0
- package/build/treb-parser/src/index.d.ts +4 -0
- package/build/treb-parser/src/index.js +25 -0
- package/build/treb-parser/src/index.js.map +1 -0
- package/build/treb-parser/src/md-parser.d.ts +97 -0
- package/build/treb-parser/src/md-parser.js +403 -0
- package/build/treb-parser/src/md-parser.js.map +1 -0
- package/build/treb-parser/src/parser-types.d.ts +345 -0
- package/build/treb-parser/src/parser-types.js +53 -0
- package/build/treb-parser/src/parser-types.js.map +1 -0
- package/build/treb-parser/src/parser.d.ts +422 -0
- package/build/treb-parser/src/parser.js +2418 -0
- package/build/treb-parser/src/parser.js.map +1 -0
- package/build/treb-utils/src/event_source.d.ts +34 -0
- package/build/treb-utils/src/event_source.js +110 -0
- package/build/treb-utils/src/event_source.js.map +1 -0
- package/build/treb-utils/src/ievent_source.d.ts +9 -0
- package/build/treb-utils/src/ievent_source.js +22 -0
- package/build/treb-utils/src/ievent_source.js.map +1 -0
- package/build/treb-utils/src/index.d.ts +6 -0
- package/build/treb-utils/src/index.js +30 -0
- package/build/treb-utils/src/index.js.map +1 -0
- package/build/treb-utils/src/measurement.d.ts +42 -0
- package/build/treb-utils/src/measurement.js +145 -0
- package/build/treb-utils/src/measurement.js.map +1 -0
- package/build/treb-utils/src/scale.d.ts +16 -0
- package/build/treb-utils/src/scale.js +106 -0
- package/build/treb-utils/src/scale.js.map +1 -0
- package/build/treb-utils/src/serialize_html.d.ts +5 -0
- package/build/treb-utils/src/serialize_html.js +128 -0
- package/build/treb-utils/src/serialize_html.js.map +1 -0
- package/build/treb-utils/src/validate_uri.d.ts +20 -0
- package/build/treb-utils/src/validate_uri.js +55 -0
- package/build/treb-utils/src/validate_uri.js.map +1 -0
- package/dist/{chunk-Z4XFMZ2X.mjs → chunk-E35ONJUS.mjs} +1 -1
- package/dist/treb-export-worker.mjs +2 -2
- package/dist/treb-spreadsheet.mjs +4 -4
- package/dist/treb.d.ts +1 -1
- package/esbuild-composite.mjs +5 -1
- package/package.json +67 -3
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +7 -3
- package/treb-embed/src/embedded-spreadsheet.ts +1 -1
- package/treb-grid/src/types/grid_options.ts +1 -1
- package/tsproject.json +1 -2
- package/dist/chunk-43DLP2OX.mjs +0 -11
- package/dist/chunk-4CKS56PE.mjs +0 -11
- package/dist/chunk-75PARUQE.mjs +0 -11
- package/dist/chunk-7QD63AZS.mjs +0 -24601
- package/dist/chunk-A55ARVRD.mjs +0 -11
- package/dist/chunk-DESAKYW4.mjs +0 -11
- package/dist/chunk-EQ2R5W6P.mjs +0 -24565
- package/dist/chunk-IYJU2J6D.mjs +0 -24601
- package/dist/chunk-KSJFPGXT.mjs +0 -11
- package/dist/chunk-MQK4DNXI.mjs +0 -11
- package/dist/chunk-ORQFKLXM.mjs +0 -24601
- package/dist/chunk-SFDNNDHY.mjs +0 -11
- package/dist/chunk-T47DX5MI.mjs +0 -11
- package/dist/chunk-T6ILBVEX.mjs +0 -11
- package/dist/chunk-TPRCDYYG.mjs +0 -11
- package/dist/chunk-YAHNOOHO.mjs +0 -11
- package/dist/chunk-YLCFKX2G.mjs +0 -24601
|
@@ -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
|