omni-viewer-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/dist/assets/audio-engine/audio_engine.mjs +2 -0
- package/dist/assets/audio-engine/audio_engine.wasm +0 -0
- package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
- package/dist/host/index.d.ts +133 -0
- package/dist/host/index.js +10 -0
- package/dist/i18n/catalog.en.d.ts +1 -0
- package/dist/i18n/catalog.en.js +411 -0
- package/dist/i18n/catalog.ko.d.ts +1 -0
- package/dist/i18n/catalog.ko.js +68 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +41 -0
- package/dist/licenses/emscripten.txt +101 -0
- package/dist/licenses/kissfft.txt +28 -0
- package/dist/licenses/pdfjs-dist.txt +177 -0
- package/dist/parsers/a2l/index.d.ts +1 -0
- package/dist/parsers/a2l/index.js +1 -0
- package/dist/parsers/archive/index.d.ts +22 -0
- package/dist/parsers/archive/index.js +46 -0
- package/dist/parsers/archive/model.d.ts +29 -0
- package/dist/parsers/archive/model.js +1 -0
- package/dist/parsers/archive/worker.d.ts +21 -0
- package/dist/parsers/archive/worker.js +1 -0
- package/dist/parsers/arxml/index.d.ts +1 -0
- package/dist/parsers/arxml/index.js +1 -0
- package/dist/parsers/asc/index.d.ts +1 -0
- package/dist/parsers/asc/index.js +1 -0
- package/dist/parsers/audio/index.d.ts +9 -0
- package/dist/parsers/audio/index.js +46 -0
- package/dist/parsers/automotive/index.d.ts +33 -0
- package/dist/parsers/automotive/index.js +973 -0
- package/dist/parsers/blf/index.d.ts +1 -0
- package/dist/parsers/blf/index.js +1 -0
- package/dist/parsers/csv/delimiter.d.ts +31 -0
- package/dist/parsers/csv/delimiter.js +151 -0
- package/dist/parsers/csv/index.d.ts +30 -0
- package/dist/parsers/csv/index.js +108 -0
- package/dist/parsers/csv/parseText.d.ts +20 -0
- package/dist/parsers/csv/parseText.js +108 -0
- package/dist/parsers/csv/sort.d.ts +26 -0
- package/dist/parsers/csv/sort.js +224 -0
- package/dist/parsers/csv/statistics.d.ts +49 -0
- package/dist/parsers/csv/statistics.js +150 -0
- package/dist/parsers/dbc/index.d.ts +56 -0
- package/dist/parsers/dbc/index.js +94 -0
- package/dist/parsers/doc-binary/index.d.ts +35 -0
- package/dist/parsers/doc-binary/index.js +267 -0
- package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
- package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
- package/dist/parsers/excel/index.d.ts +17 -0
- package/dist/parsers/excel/index.js +406 -0
- package/dist/parsers/excel/model.d.ts +80 -0
- package/dist/parsers/excel/model.js +48 -0
- package/dist/parsers/excel/serialize.d.ts +3 -0
- package/dist/parsers/excel/serialize.js +78 -0
- package/dist/parsers/hdf5/index.d.ts +75 -0
- package/dist/parsers/hdf5/index.js +658 -0
- package/dist/parsers/hdf5/node.d.ts +21 -0
- package/dist/parsers/hdf5/node.js +95 -0
- package/dist/parsers/json/index.d.ts +15 -0
- package/dist/parsers/json/index.js +102 -0
- package/dist/parsers/json/model.d.ts +42 -0
- package/dist/parsers/json/model.js +140 -0
- package/dist/parsers/json/parse.d.ts +22 -0
- package/dist/parsers/json/parse.js +279 -0
- package/dist/parsers/json/tokenizer.d.ts +17 -0
- package/dist/parsers/json/tokenizer.js +175 -0
- package/dist/parsers/jsonl/index.d.ts +14 -0
- package/dist/parsers/jsonl/index.js +18 -0
- package/dist/parsers/jsonl/model.d.ts +23 -0
- package/dist/parsers/jsonl/model.js +1 -0
- package/dist/parsers/jsonl/parse.d.ts +7 -0
- package/dist/parsers/jsonl/parse.js +34 -0
- package/dist/parsers/markdown/index.d.ts +37 -0
- package/dist/parsers/markdown/index.js +59 -0
- package/dist/parsers/mat/index.d.ts +31 -0
- package/dist/parsers/mat/index.js +426 -0
- package/dist/parsers/mermaid/index.d.ts +5 -0
- package/dist/parsers/mermaid/index.js +3 -0
- package/dist/parsers/mf4/index.d.ts +1 -0
- package/dist/parsers/mf4/index.js +1 -0
- package/dist/parsers/parquet/index.d.ts +21 -0
- package/dist/parsers/parquet/index.js +68 -0
- package/dist/parsers/pcap/index.d.ts +1 -0
- package/dist/parsers/pcap/index.js +1 -0
- package/dist/parsers/pcapng/index.d.ts +1 -0
- package/dist/parsers/pcapng/index.js +1 -0
- package/dist/parsers/plantuml/index.d.ts +5 -0
- package/dist/parsers/plantuml/index.js +4 -0
- package/dist/parsers/ppt-binary/index.d.ts +5 -0
- package/dist/parsers/ppt-binary/index.js +62 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
- package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
- package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
- package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
- package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
- package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
- package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
- package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
- package/dist/parsers/ppt-binary/vscode.js +12 -0
- package/dist/parsers/pptx/index.d.ts +15 -0
- package/dist/parsers/pptx/index.js +88 -0
- package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
- package/dist/parsers/pptx/vscode-parser.js +1503 -0
- package/dist/parsers/pptx/vscode.d.ts +4 -0
- package/dist/parsers/pptx/vscode.js +18 -0
- package/dist/parsers/pptx/zip-reader.d.ts +3 -0
- package/dist/parsers/pptx/zip-reader.js +20 -0
- package/dist/parsers/proto/index.d.ts +90 -0
- package/dist/parsers/proto/index.js +197 -0
- package/dist/parsers/psd/index.d.ts +16 -0
- package/dist/parsers/psd/index.js +10 -0
- package/dist/parsers/shapefile/dbf.d.ts +23 -0
- package/dist/parsers/shapefile/dbf.js +112 -0
- package/dist/parsers/shapefile/index.d.ts +44 -0
- package/dist/parsers/shapefile/index.js +119 -0
- package/dist/parsers/shapefile/reproject.d.ts +10 -0
- package/dist/parsers/shapefile/reproject.js +41 -0
- package/dist/parsers/slide-model.d.ts +106 -0
- package/dist/parsers/slide-model.js +1 -0
- package/dist/parsers/toml/index.d.ts +15 -0
- package/dist/parsers/toml/index.js +18 -0
- package/dist/parsers/toml/model.d.ts +15 -0
- package/dist/parsers/toml/model.js +1 -0
- package/dist/parsers/toml/parse.d.ts +16 -0
- package/dist/parsers/toml/parse.js +266 -0
- package/dist/parsers/types.d.ts +103 -0
- package/dist/parsers/types.js +106 -0
- package/dist/parsers/video/index.d.ts +6 -0
- package/dist/parsers/video/index.js +2 -0
- package/dist/parsers/yaml/index.d.ts +16 -0
- package/dist/parsers/yaml/index.js +58 -0
- package/dist/parsers/yaml/model.d.ts +22 -0
- package/dist/parsers/yaml/model.js +1 -0
- package/dist/parsers/yaml/self-loading.d.ts +3 -0
- package/dist/parsers/yaml/self-loading.js +19 -0
- package/dist/parsers/zip-scan.d.ts +8 -0
- package/dist/parsers/zip-scan.js +80 -0
- package/dist/registry/container.d.ts +17 -0
- package/dist/registry/container.js +49 -0
- package/dist/registry/index.d.ts +133 -0
- package/dist/registry/index.js +348 -0
- package/dist/registry/probe.d.ts +3 -0
- package/dist/registry/probe.js +177 -0
- package/dist/registry/sniff.d.ts +17 -0
- package/dist/registry/sniff.js +57 -0
- package/dist/styles/archive.css +10 -0
- package/dist/styles/audio.css +66 -0
- package/dist/styles/automotive.css +1 -0
- package/dist/styles/csv.css +262 -0
- package/dist/styles/excel.css +212 -0
- package/dist/styles/fallback.css +48 -0
- package/dist/styles/hdf5.css +3 -0
- package/dist/styles/hwp.css +12 -0
- package/dist/styles/image.css +115 -0
- package/dist/styles/json.css +342 -0
- package/dist/styles/jsonl.css +1 -0
- package/dist/styles/markdown.css +10 -0
- package/dist/styles/mat.css +2 -0
- package/dist/styles/mermaid.css +1 -0
- package/dist/styles/parquet.css +2 -0
- package/dist/styles/pdf.css +551 -0
- package/dist/styles/plantuml.css +1 -0
- package/dist/styles/ppt.css +1 -0
- package/dist/styles/proto.css +2 -0
- package/dist/styles/psd.css +1 -0
- package/dist/styles/shapefile.css +25 -0
- package/dist/styles/toml.css +1 -0
- package/dist/styles/video.css +74 -0
- package/dist/styles/word.css +685 -0
- package/dist/styles/yaml.css +1 -0
- package/dist/viewers/a2l/index.d.ts +15 -0
- package/dist/viewers/a2l/index.js +5 -0
- package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
- package/dist/viewers/archive/archive-preview-decoder.js +223 -0
- package/dist/viewers/archive/controller.d.ts +24 -0
- package/dist/viewers/archive/controller.js +18 -0
- package/dist/viewers/archive/index.d.ts +25 -0
- package/dist/viewers/archive/index.js +230 -0
- package/dist/viewers/archive/preview.d.ts +2 -0
- package/dist/viewers/archive/preview.js +2 -0
- package/dist/viewers/archive/styles.d.ts +1 -0
- package/dist/viewers/archive/styles.js +11 -0
- package/dist/viewers/arxml/index.d.ts +15 -0
- package/dist/viewers/arxml/index.js +5 -0
- package/dist/viewers/asc/index.d.ts +15 -0
- package/dist/viewers/asc/index.js +5 -0
- package/dist/viewers/audio/controller.d.ts +33 -0
- package/dist/viewers/audio/controller.js +49 -0
- package/dist/viewers/audio/engine.d.ts +44 -0
- package/dist/viewers/audio/engine.js +108 -0
- package/dist/viewers/audio/index.d.ts +92 -0
- package/dist/viewers/audio/index.js +448 -0
- package/dist/viewers/audio/self-loading.d.ts +7 -0
- package/dist/viewers/audio/self-loading.js +42 -0
- package/dist/viewers/audio/styles.d.ts +1 -0
- package/dist/viewers/audio/styles.js +73 -0
- package/dist/viewers/automotive/index.d.ts +8 -0
- package/dist/viewers/automotive/index.js +152 -0
- package/dist/viewers/automotive/styles.d.ts +1 -0
- package/dist/viewers/automotive/styles.js +1 -0
- package/dist/viewers/avro/index.d.ts +15 -0
- package/dist/viewers/avro/index.js +5 -0
- package/dist/viewers/bag/index.d.ts +15 -0
- package/dist/viewers/bag/index.js +5 -0
- package/dist/viewers/blf/index.d.ts +15 -0
- package/dist/viewers/blf/index.js +5 -0
- package/dist/viewers/csv/controller.d.ts +126 -0
- package/dist/viewers/csv/controller.js +598 -0
- package/dist/viewers/csv/index.d.ts +48 -0
- package/dist/viewers/csv/index.js +846 -0
- package/dist/viewers/csv/styles.d.ts +1 -0
- package/dist/viewers/csv/styles.js +267 -0
- package/dist/viewers/db3/index.d.ts +15 -0
- package/dist/viewers/db3/index.js +5 -0
- package/dist/viewers/dbc/index.d.ts +15 -0
- package/dist/viewers/dbc/index.js +5 -0
- package/dist/viewers/diagram.d.ts +14 -0
- package/dist/viewers/diagram.js +95 -0
- package/dist/viewers/excel/controller.d.ts +108 -0
- package/dist/viewers/excel/controller.js +548 -0
- package/dist/viewers/excel/grid.d.ts +28 -0
- package/dist/viewers/excel/grid.js +99 -0
- package/dist/viewers/excel/index.d.ts +37 -0
- package/dist/viewers/excel/index.js +620 -0
- package/dist/viewers/excel/self-loading.d.ts +6 -0
- package/dist/viewers/excel/self-loading.js +13 -0
- package/dist/viewers/excel/styles.d.ts +1 -0
- package/dist/viewers/excel/styles.js +216 -0
- package/dist/viewers/fallback/index.d.ts +17 -0
- package/dist/viewers/fallback/index.js +130 -0
- package/dist/viewers/fallback/styles.d.ts +1 -0
- package/dist/viewers/fallback/styles.js +49 -0
- package/dist/viewers/hdf5/index.d.ts +19 -0
- package/dist/viewers/hdf5/index.js +170 -0
- package/dist/viewers/hdf5/styles.d.ts +1 -0
- package/dist/viewers/hdf5/styles.js +4 -0
- package/dist/viewers/hwp/controller.d.ts +19 -0
- package/dist/viewers/hwp/controller.js +22 -0
- package/dist/viewers/hwp/index.d.ts +39 -0
- package/dist/viewers/hwp/index.js +271 -0
- package/dist/viewers/hwp/self-loading.d.ts +2 -0
- package/dist/viewers/hwp/self-loading.js +6 -0
- package/dist/viewers/hwp/styles.d.ts +1 -0
- package/dist/viewers/hwp/styles.js +13 -0
- package/dist/viewers/image/controller.d.ts +210 -0
- package/dist/viewers/image/controller.js +336 -0
- package/dist/viewers/image/decode.d.ts +54 -0
- package/dist/viewers/image/decode.js +150 -0
- package/dist/viewers/image/index.d.ts +38 -0
- package/dist/viewers/image/index.js +749 -0
- package/dist/viewers/image/limits.d.ts +18 -0
- package/dist/viewers/image/limits.js +20 -0
- package/dist/viewers/image/styles.d.ts +1 -0
- package/dist/viewers/image/styles.js +119 -0
- package/dist/viewers/json/controller.d.ts +89 -0
- package/dist/viewers/json/controller.js +411 -0
- package/dist/viewers/json/index.d.ts +25 -0
- package/dist/viewers/json/index.js +503 -0
- package/dist/viewers/json/styles.d.ts +1 -0
- package/dist/viewers/json/styles.js +346 -0
- package/dist/viewers/json/transforms.d.ts +27 -0
- package/dist/viewers/json/transforms.js +253 -0
- package/dist/viewers/jsonl/controller.d.ts +48 -0
- package/dist/viewers/jsonl/controller.js +136 -0
- package/dist/viewers/jsonl/index.d.ts +22 -0
- package/dist/viewers/jsonl/index.js +184 -0
- package/dist/viewers/jsonl/styles.d.ts +1 -0
- package/dist/viewers/jsonl/styles.js +1 -0
- package/dist/viewers/markdown/controller.d.ts +50 -0
- package/dist/viewers/markdown/controller.js +62 -0
- package/dist/viewers/markdown/index.d.ts +64 -0
- package/dist/viewers/markdown/index.js +425 -0
- package/dist/viewers/markdown/math.d.ts +21 -0
- package/dist/viewers/markdown/math.js +100 -0
- package/dist/viewers/markdown/self-loading.d.ts +5 -0
- package/dist/viewers/markdown/self-loading.js +48 -0
- package/dist/viewers/markdown/styles.d.ts +1 -0
- package/dist/viewers/markdown/styles.js +11 -0
- package/dist/viewers/mat/index.d.ts +19 -0
- package/dist/viewers/mat/index.js +113 -0
- package/dist/viewers/mat/styles.d.ts +1 -0
- package/dist/viewers/mat/styles.js +3 -0
- package/dist/viewers/media.d.ts +9 -0
- package/dist/viewers/media.js +77 -0
- package/dist/viewers/mermaid/index.d.ts +17 -0
- package/dist/viewers/mermaid/index.js +6 -0
- package/dist/viewers/mermaid/self-loading.d.ts +2 -0
- package/dist/viewers/mermaid/self-loading.js +2 -0
- package/dist/viewers/mf4/index.d.ts +15 -0
- package/dist/viewers/mf4/index.js +5 -0
- package/dist/viewers/parquet/controller.d.ts +37 -0
- package/dist/viewers/parquet/controller.js +26 -0
- package/dist/viewers/parquet/index.d.ts +23 -0
- package/dist/viewers/parquet/index.js +134 -0
- package/dist/viewers/parquet/styles.d.ts +1 -0
- package/dist/viewers/parquet/styles.js +3 -0
- package/dist/viewers/pcap/index.d.ts +15 -0
- package/dist/viewers/pcap/index.js +5 -0
- package/dist/viewers/pcapng/index.d.ts +15 -0
- package/dist/viewers/pcapng/index.js +5 -0
- package/dist/viewers/pdf/controller.d.ts +120 -0
- package/dist/viewers/pdf/controller.js +191 -0
- package/dist/viewers/pdf/editing.d.ts +26 -0
- package/dist/viewers/pdf/editing.js +265 -0
- package/dist/viewers/pdf/index.d.ts +88 -0
- package/dist/viewers/pdf/index.js +1400 -0
- package/dist/viewers/pdf/self-loading.d.ts +6 -0
- package/dist/viewers/pdf/self-loading.js +14 -0
- package/dist/viewers/pdf/styles.d.ts +1 -0
- package/dist/viewers/pdf/styles.js +555 -0
- package/dist/viewers/plantuml/index.d.ts +17 -0
- package/dist/viewers/plantuml/index.js +6 -0
- package/dist/viewers/plantuml/self-loading.d.ts +2 -0
- package/dist/viewers/plantuml/self-loading.js +2 -0
- package/dist/viewers/ppt/chart.d.ts +8 -0
- package/dist/viewers/ppt/chart.js +83 -0
- package/dist/viewers/ppt/controller.d.ts +34 -0
- package/dist/viewers/ppt/controller.js +27 -0
- package/dist/viewers/ppt/index.d.ts +33 -0
- package/dist/viewers/ppt/index.js +101 -0
- package/dist/viewers/ppt/render.d.ts +5 -0
- package/dist/viewers/ppt/render.js +144 -0
- package/dist/viewers/ppt/self-loading.d.ts +9 -0
- package/dist/viewers/ppt/self-loading.js +4 -0
- package/dist/viewers/ppt/styles.d.ts +1 -0
- package/dist/viewers/ppt/styles.js +1 -0
- package/dist/viewers/proto/index.d.ts +17 -0
- package/dist/viewers/proto/index.js +170 -0
- package/dist/viewers/proto/styles.d.ts +1 -0
- package/dist/viewers/proto/styles.js +2 -0
- package/dist/viewers/psd/index.d.ts +27 -0
- package/dist/viewers/psd/index.js +39 -0
- package/dist/viewers/psd/self-loading.d.ts +10 -0
- package/dist/viewers/psd/self-loading.js +20 -0
- package/dist/viewers/psd/styles.d.ts +1 -0
- package/dist/viewers/psd/styles.js +1 -0
- package/dist/viewers/reqif/index.d.ts +15 -0
- package/dist/viewers/reqif/index.js +5 -0
- package/dist/viewers/shapefile/index.d.ts +35 -0
- package/dist/viewers/shapefile/index.js +336 -0
- package/dist/viewers/shapefile/self-loading.d.ts +7 -0
- package/dist/viewers/shapefile/self-loading.js +32 -0
- package/dist/viewers/shapefile/styles.d.ts +1 -0
- package/dist/viewers/shapefile/styles.js +28 -0
- package/dist/viewers/stp/index.d.ts +15 -0
- package/dist/viewers/stp/index.js +5 -0
- package/dist/viewers/structured-styles.d.ts +1 -0
- package/dist/viewers/structured-styles.js +1 -0
- package/dist/viewers/structured.d.ts +36 -0
- package/dist/viewers/structured.js +138 -0
- package/dist/viewers/toml/controller.d.ts +38 -0
- package/dist/viewers/toml/controller.js +61 -0
- package/dist/viewers/toml/index.d.ts +18 -0
- package/dist/viewers/toml/index.js +34 -0
- package/dist/viewers/types.d.ts +36 -0
- package/dist/viewers/types.js +9 -0
- package/dist/viewers/video/controller.d.ts +29 -0
- package/dist/viewers/video/controller.js +54 -0
- package/dist/viewers/video/index.d.ts +18 -0
- package/dist/viewers/video/index.js +305 -0
- package/dist/viewers/video/styles.d.ts +1 -0
- package/dist/viewers/video/styles.js +79 -0
- package/dist/viewers/word/controller.d.ts +19 -0
- package/dist/viewers/word/controller.js +22 -0
- package/dist/viewers/word/docx-preprocess.d.ts +63 -0
- package/dist/viewers/word/docx-preprocess.js +136 -0
- package/dist/viewers/word/index.d.ts +32 -0
- package/dist/viewers/word/index.js +187 -0
- package/dist/viewers/word/normalize-docx.d.ts +2 -0
- package/dist/viewers/word/normalize-docx.js +50 -0
- package/dist/viewers/word/paginate.d.ts +1 -0
- package/dist/viewers/word/paginate.js +1035 -0
- package/dist/viewers/word/self-loading.d.ts +2 -0
- package/dist/viewers/word/self-loading.js +7 -0
- package/dist/viewers/word/styles.d.ts +1 -0
- package/dist/viewers/word/styles.js +686 -0
- package/dist/viewers/yaml/controller.d.ts +42 -0
- package/dist/viewers/yaml/controller.js +41 -0
- package/dist/viewers/yaml/index.d.ts +21 -0
- package/dist/viewers/yaml/index.js +20 -0
- package/package.json +375 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// ExcelController — the behavior-controller layer for the Excel viewer
|
|
2
|
+
// (DESIGN.md §3-②, docs/viewers/excel.md §3). Pure state machine: no DOM, no
|
|
3
|
+
// host services, no SheetJS. It consumes an already-parsed
|
|
4
|
+
// ParseResult<ExcelWorkbook> and drives sheet switch, sort, search, pagination,
|
|
5
|
+
// raw-view toggle, copy, and CELL EDITING (X-편집, promoted to v1 scope).
|
|
6
|
+
//
|
|
7
|
+
// Editing mirrors the CSV controller engine (src/viewers/csv/controller.ts):
|
|
8
|
+
// immutable RowIds so sort/filter can't misroute an edit, an inverse-op
|
|
9
|
+
// undo/redo stack, and dirty tracking against a saved point. The Excel-specific
|
|
10
|
+
// part is that cells are TYPED (EditableCell {t,v,w}); an edited value's type is
|
|
11
|
+
// inferred (inferEditedCell) so type-preserving export (serializeWorkbook) keeps
|
|
12
|
+
// numbers/dates as numbers/dates rather than text. Edits live in a per-sheet
|
|
13
|
+
// working model so switching sheets preserves them.
|
|
14
|
+
//
|
|
15
|
+
// Sort reuses the CSV deterministic comparators (X2). Save-as export only —
|
|
16
|
+
// no writeback to the original file, no formula recalculation.
|
|
17
|
+
import { applySort, detectColumnType, nextSortState, parseDateKey, serializeRowsToTsv } from '../../parsers/csv/index.js';
|
|
18
|
+
import { buildGridView } from './grid.js';
|
|
19
|
+
export const EXCEL_DEFAULT_PAGE_SIZE = 100;
|
|
20
|
+
export const EXCEL_UNDO_STACK_LIMIT = 200;
|
|
21
|
+
export function createExcelController(result, options = {}) {
|
|
22
|
+
const listeners = new Set();
|
|
23
|
+
const pageSizeDefault = options.pageSize ?? EXCEL_DEFAULT_PAGE_SIZE;
|
|
24
|
+
const editable = result.status === 'ok';
|
|
25
|
+
const workbook = result.status === 'failed'
|
|
26
|
+
? { sheetNames: [], sheets: [], date1904: false }
|
|
27
|
+
: result.document;
|
|
28
|
+
/** Per-sheet mutable working models — created on first load/edit. */
|
|
29
|
+
const working = new Map();
|
|
30
|
+
// Current-sheet runtime view state.
|
|
31
|
+
let sheetIndex = 0;
|
|
32
|
+
let ws = null;
|
|
33
|
+
let sortedRows = [];
|
|
34
|
+
let displayRows = [];
|
|
35
|
+
let sort = { columnIndex: null, direction: null };
|
|
36
|
+
let sortedColumnType = null;
|
|
37
|
+
let search = '';
|
|
38
|
+
let matchedCellCount = 0;
|
|
39
|
+
let pageSize = pageSizeDefault;
|
|
40
|
+
let page = 0;
|
|
41
|
+
let viewMode = 'table';
|
|
42
|
+
let rawCache = null;
|
|
43
|
+
/** Grid views (X3) are derived from the original sheet, cached per sheet. */
|
|
44
|
+
const gridCache = new Map();
|
|
45
|
+
function getWorkingSheet(index) {
|
|
46
|
+
const existing = working.get(index);
|
|
47
|
+
if (existing)
|
|
48
|
+
return existing;
|
|
49
|
+
const sheet = workbook.sheets[index];
|
|
50
|
+
if (!sheet)
|
|
51
|
+
return null;
|
|
52
|
+
const built = deriveWorkingSheet(sheet);
|
|
53
|
+
working.set(index, built);
|
|
54
|
+
return built;
|
|
55
|
+
}
|
|
56
|
+
function hasActiveSearch() {
|
|
57
|
+
return viewMode === 'table' && search.length > 0;
|
|
58
|
+
}
|
|
59
|
+
function rowText(row, c) {
|
|
60
|
+
return row.cells[c]?.w ?? '';
|
|
61
|
+
}
|
|
62
|
+
function refilter() {
|
|
63
|
+
if (!hasActiveSearch()) {
|
|
64
|
+
displayRows = sortedRows.slice();
|
|
65
|
+
matchedCellCount = 0;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const term = search.toLowerCase();
|
|
69
|
+
const filtered = [];
|
|
70
|
+
let cells = 0;
|
|
71
|
+
for (const row of sortedRows) {
|
|
72
|
+
let hit = false;
|
|
73
|
+
for (const cell of row.cells) {
|
|
74
|
+
if (cell.w.toLowerCase().includes(term)) {
|
|
75
|
+
cells++;
|
|
76
|
+
hit = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (hit)
|
|
80
|
+
filtered.push(row);
|
|
81
|
+
}
|
|
82
|
+
displayRows = filtered;
|
|
83
|
+
matchedCellCount = cells;
|
|
84
|
+
}
|
|
85
|
+
function resort() {
|
|
86
|
+
const rows = ws?.rowOrder ?? [];
|
|
87
|
+
if (sort.columnIndex === null || !sort.direction) {
|
|
88
|
+
sortedRows = rows.slice();
|
|
89
|
+
sortedColumnType = null;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const col = sort.columnIndex;
|
|
93
|
+
sortedColumnType = detectColumnType(rows.map((r) => rowText(r, col)));
|
|
94
|
+
const positions = applySort(rows.map((_, i) => i), sort, (rowIndex, columnIndex) => rowText(rows[rowIndex], columnIndex), sortedColumnType);
|
|
95
|
+
sortedRows = positions.map((p) => rows[p]);
|
|
96
|
+
}
|
|
97
|
+
refilter();
|
|
98
|
+
}
|
|
99
|
+
function loadSheet(index) {
|
|
100
|
+
sheetIndex = index;
|
|
101
|
+
ws = getWorkingSheet(index);
|
|
102
|
+
sort = { columnIndex: null, direction: null };
|
|
103
|
+
sortedColumnType = null;
|
|
104
|
+
search = '';
|
|
105
|
+
matchedCellCount = 0;
|
|
106
|
+
page = 0;
|
|
107
|
+
rawCache = null;
|
|
108
|
+
resort();
|
|
109
|
+
}
|
|
110
|
+
function pageCount() {
|
|
111
|
+
return Math.max(1, Math.ceil(displayRows.length / pageSize));
|
|
112
|
+
}
|
|
113
|
+
// --- editing ------------------------------------------------------------
|
|
114
|
+
function isDirty(sheet) {
|
|
115
|
+
return sheet.savedUnreachable || sheet.undoStack.length !== sheet.savedDepth;
|
|
116
|
+
}
|
|
117
|
+
function workbookDirty() {
|
|
118
|
+
for (const sheet of working.values()) {
|
|
119
|
+
if (isDirty(sheet))
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function applyOp(op, direction) {
|
|
125
|
+
if (!ws)
|
|
126
|
+
return;
|
|
127
|
+
switch (op.kind) {
|
|
128
|
+
case 'cell': {
|
|
129
|
+
const row = ws.byId.get(op.rowId);
|
|
130
|
+
if (row)
|
|
131
|
+
row.cells[op.columnIndex] = direction === 'forward' ? op.next : op.prev;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'header': {
|
|
135
|
+
ws.headers[op.columnIndex] = direction === 'forward' ? op.next : op.prev;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'insert': {
|
|
139
|
+
if (direction === 'forward')
|
|
140
|
+
addRowAt(op.row, op.positions);
|
|
141
|
+
else
|
|
142
|
+
removeRow(op.row);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'delete': {
|
|
146
|
+
if (direction === 'forward')
|
|
147
|
+
removeRow(op.row);
|
|
148
|
+
else
|
|
149
|
+
addRowAt(op.row, op.positions);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
rawCache = null;
|
|
154
|
+
}
|
|
155
|
+
function addRowAt(row, positions) {
|
|
156
|
+
if (!ws)
|
|
157
|
+
return;
|
|
158
|
+
ws.rowOrder.splice(clamp(positions.order, 0, ws.rowOrder.length), 0, row);
|
|
159
|
+
sortedRows.splice(clamp(positions.sorted, 0, sortedRows.length), 0, row);
|
|
160
|
+
if (positions.display >= 0) {
|
|
161
|
+
displayRows.splice(clamp(positions.display, 0, displayRows.length), 0, row);
|
|
162
|
+
}
|
|
163
|
+
ws.byId.set(row.id, row);
|
|
164
|
+
}
|
|
165
|
+
function removeRow(row) {
|
|
166
|
+
if (!ws)
|
|
167
|
+
return;
|
|
168
|
+
const drop = (arr) => {
|
|
169
|
+
const i = arr.indexOf(row);
|
|
170
|
+
if (i >= 0)
|
|
171
|
+
arr.splice(i, 1);
|
|
172
|
+
};
|
|
173
|
+
drop(ws.rowOrder);
|
|
174
|
+
drop(sortedRows);
|
|
175
|
+
drop(displayRows);
|
|
176
|
+
ws.byId.delete(row.id);
|
|
177
|
+
}
|
|
178
|
+
function pushOp(op) {
|
|
179
|
+
if (!ws)
|
|
180
|
+
return;
|
|
181
|
+
applyOp(op, 'forward');
|
|
182
|
+
if (ws.redoStack.length > 0) {
|
|
183
|
+
if (ws.savedDepth > ws.undoStack.length)
|
|
184
|
+
ws.savedUnreachable = true;
|
|
185
|
+
ws.redoStack = [];
|
|
186
|
+
}
|
|
187
|
+
ws.undoStack.push(op);
|
|
188
|
+
if (ws.undoStack.length > EXCEL_UNDO_STACK_LIMIT) {
|
|
189
|
+
ws.undoStack.shift();
|
|
190
|
+
if (ws.savedDepth > 0)
|
|
191
|
+
ws.savedDepth--;
|
|
192
|
+
else
|
|
193
|
+
ws.savedUnreachable = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// --- state --------------------------------------------------------------
|
|
197
|
+
function buildState() {
|
|
198
|
+
const start = page * pageSize;
|
|
199
|
+
const pageRows = displayRows.slice(start, start + pageSize);
|
|
200
|
+
return {
|
|
201
|
+
status: result.status,
|
|
202
|
+
failure: result.status === 'failed' ? result.failure : null,
|
|
203
|
+
diagnostics: result.diagnostics,
|
|
204
|
+
sheetNames: [...workbook.sheetNames],
|
|
205
|
+
sheetIndex,
|
|
206
|
+
sheetName: ws?.name ?? '',
|
|
207
|
+
headers: ws ? ws.headers.map((c) => c.w) : [],
|
|
208
|
+
columnCount: ws?.columnCount ?? 0,
|
|
209
|
+
rowCount: ws?.rowOrder.length ?? 0,
|
|
210
|
+
sort,
|
|
211
|
+
sortedColumnType,
|
|
212
|
+
search,
|
|
213
|
+
matchedRowCount: displayRows.length,
|
|
214
|
+
matchedCellCount,
|
|
215
|
+
page,
|
|
216
|
+
pageCount: pageCount(),
|
|
217
|
+
pageSize,
|
|
218
|
+
viewMode,
|
|
219
|
+
visibleRows: pageRows.map((r) => r.cells.map((c) => c.w)),
|
|
220
|
+
visibleRowIds: pageRows.map((r) => r.id),
|
|
221
|
+
editable,
|
|
222
|
+
canUndo: (ws?.undoStack.length ?? 0) > 0,
|
|
223
|
+
canRedo: (ws?.redoStack.length ?? 0) > 0,
|
|
224
|
+
dirty: ws ? isDirty(ws) : false,
|
|
225
|
+
workbookDirty: workbookDirty()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
let state;
|
|
229
|
+
function commit() {
|
|
230
|
+
state = buildState();
|
|
231
|
+
for (const listener of listeners)
|
|
232
|
+
listener(state);
|
|
233
|
+
}
|
|
234
|
+
loadSheet(0);
|
|
235
|
+
state = buildState();
|
|
236
|
+
function dispatch(action) {
|
|
237
|
+
switch (action.type) {
|
|
238
|
+
case 'switch-sheet': {
|
|
239
|
+
if (action.index < 0 ||
|
|
240
|
+
action.index >= workbook.sheets.length ||
|
|
241
|
+
action.index === sheetIndex) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
loadSheet(action.index);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 'sort-column': {
|
|
248
|
+
if (!ws || action.columnIndex < 0 || action.columnIndex >= ws.columnCount)
|
|
249
|
+
return;
|
|
250
|
+
sort = nextSortState(sort, action.columnIndex);
|
|
251
|
+
resort();
|
|
252
|
+
page = 0;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 'set-search': {
|
|
256
|
+
if (action.search === search)
|
|
257
|
+
return;
|
|
258
|
+
search = action.search;
|
|
259
|
+
refilter();
|
|
260
|
+
page = 0;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'set-page-size': {
|
|
264
|
+
if (!Number.isInteger(action.pageSize) || action.pageSize <= 0)
|
|
265
|
+
return;
|
|
266
|
+
if (action.pageSize === pageSize)
|
|
267
|
+
return;
|
|
268
|
+
const firstVisible = page * pageSize;
|
|
269
|
+
pageSize = action.pageSize;
|
|
270
|
+
page = Math.floor(firstVisible / pageSize);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'set-page': {
|
|
274
|
+
page = clamp(action.page, 0, pageCount() - 1);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'next-page': {
|
|
278
|
+
page = clamp(page + 1, 0, pageCount() - 1);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case 'prev-page': {
|
|
282
|
+
page = clamp(page - 1, 0, pageCount() - 1);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'set-view-mode': {
|
|
286
|
+
if (action.mode === viewMode)
|
|
287
|
+
return;
|
|
288
|
+
viewMode = action.mode;
|
|
289
|
+
refilter();
|
|
290
|
+
page = clamp(page, 0, pageCount() - 1);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'edit-cell': {
|
|
294
|
+
if (!editable || !ws)
|
|
295
|
+
return;
|
|
296
|
+
const row = ws.byId.get(action.rowId);
|
|
297
|
+
if (!row || action.columnIndex < 0 || action.columnIndex >= ws.columnCount)
|
|
298
|
+
return;
|
|
299
|
+
const prev = row.cells[action.columnIndex] ?? emptyCell();
|
|
300
|
+
const next = inferEditedCell(action.value);
|
|
301
|
+
if (cellsEqual(prev, next))
|
|
302
|
+
return;
|
|
303
|
+
pushOp({ kind: 'cell', rowId: action.rowId, columnIndex: action.columnIndex, prev, next });
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case 'edit-header': {
|
|
307
|
+
if (!editable || !ws)
|
|
308
|
+
return;
|
|
309
|
+
if (action.columnIndex < 0 || action.columnIndex >= ws.columnCount)
|
|
310
|
+
return;
|
|
311
|
+
const prev = ws.headers[action.columnIndex] ?? emptyCell();
|
|
312
|
+
const next = { t: 'text', v: action.value, w: action.value };
|
|
313
|
+
if (cellsEqual(prev, next))
|
|
314
|
+
return;
|
|
315
|
+
pushOp({ kind: 'header', columnIndex: action.columnIndex, prev, next });
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case 'insert-row': {
|
|
319
|
+
if (!editable || !ws)
|
|
320
|
+
return;
|
|
321
|
+
const row = {
|
|
322
|
+
id: ws.nextRowId++,
|
|
323
|
+
cells: Array.from({ length: ws.columnCount }, emptyCell)
|
|
324
|
+
};
|
|
325
|
+
let positions;
|
|
326
|
+
if (action.afterRowId === 'end') {
|
|
327
|
+
positions = { order: ws.rowOrder.length, sorted: sortedRows.length, display: displayRows.length };
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const after = ws.byId.get(action.afterRowId);
|
|
331
|
+
if (!after)
|
|
332
|
+
return;
|
|
333
|
+
positions = {
|
|
334
|
+
order: ws.rowOrder.indexOf(after) + 1 || ws.rowOrder.length,
|
|
335
|
+
sorted: sortedRows.indexOf(after) + 1 || sortedRows.length,
|
|
336
|
+
display: displayRows.indexOf(after) + 1 || displayRows.length
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
pushOp({ kind: 'insert', row, positions });
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 'delete-row': {
|
|
343
|
+
if (!editable || !ws)
|
|
344
|
+
return;
|
|
345
|
+
const row = ws.byId.get(action.rowId);
|
|
346
|
+
if (!row)
|
|
347
|
+
return;
|
|
348
|
+
pushOp({
|
|
349
|
+
kind: 'delete',
|
|
350
|
+
row,
|
|
351
|
+
positions: {
|
|
352
|
+
order: ws.rowOrder.indexOf(row),
|
|
353
|
+
sorted: sortedRows.indexOf(row),
|
|
354
|
+
display: displayRows.indexOf(row)
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case 'undo': {
|
|
360
|
+
if (!ws)
|
|
361
|
+
return;
|
|
362
|
+
const op = ws.undoStack.pop();
|
|
363
|
+
if (!op)
|
|
364
|
+
return;
|
|
365
|
+
applyOp(op, 'backward');
|
|
366
|
+
ws.redoStack.push(op);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case 'redo': {
|
|
370
|
+
if (!ws)
|
|
371
|
+
return;
|
|
372
|
+
const op = ws.redoStack.pop();
|
|
373
|
+
if (!op)
|
|
374
|
+
return;
|
|
375
|
+
applyOp(op, 'forward');
|
|
376
|
+
ws.undoStack.push(op);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
commit();
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
get state() {
|
|
384
|
+
return state;
|
|
385
|
+
},
|
|
386
|
+
dispatch,
|
|
387
|
+
subscribe(listener) {
|
|
388
|
+
listeners.add(listener);
|
|
389
|
+
return () => listeners.delete(listener);
|
|
390
|
+
},
|
|
391
|
+
getRowById(rowId) {
|
|
392
|
+
return ws?.byId.get(rowId)?.cells.map((c) => c.w) ?? [];
|
|
393
|
+
},
|
|
394
|
+
toTsv() {
|
|
395
|
+
return serializeRowsToTsv(displayRows.map((r) => r.cells.map((c) => c.w)), ws ? ws.headers.map((c) => c.w) : []);
|
|
396
|
+
},
|
|
397
|
+
toJson() {
|
|
398
|
+
const headers = ws ? ws.headers.map((c) => c.w) : [];
|
|
399
|
+
return JSON.stringify({
|
|
400
|
+
format: 'excel-export@1',
|
|
401
|
+
sheetName: ws?.name ?? '',
|
|
402
|
+
headers,
|
|
403
|
+
rows: displayRows.map((r) => r.cells.map((c) => c.w)),
|
|
404
|
+
metadata: {
|
|
405
|
+
totalRows: ws?.rowOrder.length ?? 0,
|
|
406
|
+
filteredRows: displayRows.length,
|
|
407
|
+
columns: headers.length,
|
|
408
|
+
searchActive: hasActiveSearch()
|
|
409
|
+
}
|
|
410
|
+
}, null, 2);
|
|
411
|
+
},
|
|
412
|
+
rawJson() {
|
|
413
|
+
if (rawCache === null) {
|
|
414
|
+
const headers = ws ? ws.headers.map((c) => c.w) : [];
|
|
415
|
+
rawCache = JSON.stringify({
|
|
416
|
+
sheetName: ws?.name ?? '',
|
|
417
|
+
headers,
|
|
418
|
+
rows: (ws?.rowOrder ?? []).map((r) => r.cells.map((c) => c.w)),
|
|
419
|
+
metadata: { totalRows: ws?.rowOrder.length ?? 0, columns: headers.length }
|
|
420
|
+
}, null, 2);
|
|
421
|
+
}
|
|
422
|
+
return rawCache;
|
|
423
|
+
},
|
|
424
|
+
getWorkbook() {
|
|
425
|
+
const sheets = workbook.sheetNames.map((name, index) => {
|
|
426
|
+
const edited = working.get(index);
|
|
427
|
+
if (edited)
|
|
428
|
+
return workingToSheet(edited);
|
|
429
|
+
return workbook.sheets[index] ?? emptySheet(name);
|
|
430
|
+
});
|
|
431
|
+
return { sheetNames: [...workbook.sheetNames], sheets, date1904: workbook.date1904 };
|
|
432
|
+
},
|
|
433
|
+
getGrid() {
|
|
434
|
+
const sheet = workbook.sheets[sheetIndex];
|
|
435
|
+
if (!sheet)
|
|
436
|
+
return null;
|
|
437
|
+
let grid = gridCache.get(sheetIndex);
|
|
438
|
+
if (!grid) {
|
|
439
|
+
grid = buildGridView(sheet);
|
|
440
|
+
gridCache.set(sheetIndex, grid);
|
|
441
|
+
}
|
|
442
|
+
return grid;
|
|
443
|
+
},
|
|
444
|
+
markSaved() {
|
|
445
|
+
for (const sheet of working.values()) {
|
|
446
|
+
sheet.savedDepth = sheet.undoStack.length;
|
|
447
|
+
sheet.savedUnreachable = false;
|
|
448
|
+
}
|
|
449
|
+
commit();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// --- helpers ---------------------------------------------------------------
|
|
454
|
+
function emptyCell() {
|
|
455
|
+
return { t: 'text', v: '', w: '' };
|
|
456
|
+
}
|
|
457
|
+
function cellsEqual(a, b) {
|
|
458
|
+
return a.t === b.t && a.v === b.v && a.w === b.w;
|
|
459
|
+
}
|
|
460
|
+
/** Infer a cell's type from an edited string (docs/viewers/excel.md §3): a
|
|
461
|
+
* finite number is numeric, an ISO-shaped value is a date (parseDateKey —
|
|
462
|
+
* deterministic, ADR 41), everything else is text. */
|
|
463
|
+
function inferEditedCell(value) {
|
|
464
|
+
const trimmed = value.trim();
|
|
465
|
+
if (trimmed !== '' && Number.isFinite(Number(trimmed))) {
|
|
466
|
+
return { t: 'number', v: Number(trimmed), w: value };
|
|
467
|
+
}
|
|
468
|
+
if (parseDateKey(trimmed) !== null) {
|
|
469
|
+
return { t: 'date', v: trimmed, w: value };
|
|
470
|
+
}
|
|
471
|
+
return { t: 'text', v: value, w: value };
|
|
472
|
+
}
|
|
473
|
+
function deriveWorkingSheet(sheet) {
|
|
474
|
+
const range = sheet.usedRange;
|
|
475
|
+
const columnCount = range ? range.c1 - range.c0 + 1 : 0;
|
|
476
|
+
const headers = [];
|
|
477
|
+
const rowOrder = [];
|
|
478
|
+
const byId = new Map();
|
|
479
|
+
if (range) {
|
|
480
|
+
const grid = new Map();
|
|
481
|
+
for (const cell of sheet.cells)
|
|
482
|
+
grid.set(`${cell.r}:${cell.c}`, cell);
|
|
483
|
+
const cellAt = (r, c) => {
|
|
484
|
+
const found = grid.get(`${r}:${c}`);
|
|
485
|
+
return found ? { t: found.t, v: found.v, w: found.w } : emptyCell();
|
|
486
|
+
};
|
|
487
|
+
for (let c = range.c0; c <= range.c1; c++)
|
|
488
|
+
headers.push(cellAt(range.r0, c));
|
|
489
|
+
let nextId = 0;
|
|
490
|
+
for (let r = range.r0 + 1; r <= range.r1; r++) {
|
|
491
|
+
const cells = [];
|
|
492
|
+
for (let c = range.c0; c <= range.c1; c++)
|
|
493
|
+
cells.push(cellAt(r, c));
|
|
494
|
+
const row = { id: nextId++, cells };
|
|
495
|
+
rowOrder.push(row);
|
|
496
|
+
byId.set(row.id, row);
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
name: sheet.name,
|
|
500
|
+
headers,
|
|
501
|
+
rowOrder,
|
|
502
|
+
byId,
|
|
503
|
+
columnCount,
|
|
504
|
+
nextRowId: nextId,
|
|
505
|
+
undoStack: [],
|
|
506
|
+
redoStack: [],
|
|
507
|
+
savedDepth: 0,
|
|
508
|
+
savedUnreachable: false
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
name: sheet.name,
|
|
513
|
+
headers,
|
|
514
|
+
rowOrder,
|
|
515
|
+
byId,
|
|
516
|
+
columnCount,
|
|
517
|
+
nextRowId: 0,
|
|
518
|
+
undoStack: [],
|
|
519
|
+
redoStack: [],
|
|
520
|
+
savedDepth: 0,
|
|
521
|
+
savedUnreachable: false
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/** Rebuild a sparse, typed ExcelSheet from an edited working model (origin
|
|
525
|
+
* normalized to A1). Empty text cells are omitted to keep the model sparse. */
|
|
526
|
+
function workingToSheet(ws) {
|
|
527
|
+
const cells = [];
|
|
528
|
+
const push = (r, c, cell) => {
|
|
529
|
+
if (cell.t === 'text' && cell.v === '' && cell.w === '')
|
|
530
|
+
return;
|
|
531
|
+
cells.push({ r, c, t: cell.t, v: cell.v, w: cell.w });
|
|
532
|
+
};
|
|
533
|
+
ws.headers.forEach((cell, c) => push(0, c, cell));
|
|
534
|
+
ws.rowOrder.forEach((row, ri) => {
|
|
535
|
+
row.cells.forEach((cell, c) => push(ri + 1, c, cell));
|
|
536
|
+
});
|
|
537
|
+
const rowCount = ws.rowOrder.length + (ws.columnCount > 0 ? 1 : 0);
|
|
538
|
+
const usedRange = ws.columnCount > 0 && rowCount > 0
|
|
539
|
+
? { r0: 0, c0: 0, r1: rowCount - 1, c1: ws.columnCount - 1 }
|
|
540
|
+
: null;
|
|
541
|
+
return { name: ws.name, usedRange, cells, rowCount, columnCount: ws.columnCount };
|
|
542
|
+
}
|
|
543
|
+
function emptySheet(name) {
|
|
544
|
+
return { name, usedRange: null, cells: [], rowCount: 0, columnCount: 0 };
|
|
545
|
+
}
|
|
546
|
+
function clamp(v, min, max) {
|
|
547
|
+
return Math.min(Math.max(v, min), max);
|
|
548
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CellStyle, ExcelSheet } from '../../parsers/excel/index.js';
|
|
2
|
+
export interface GridCell {
|
|
3
|
+
r: number;
|
|
4
|
+
c: number;
|
|
5
|
+
rowSpan: number;
|
|
6
|
+
colSpan: number;
|
|
7
|
+
text: string;
|
|
8
|
+
/** CSS property → value (camelCase keys), applied as an inline style. */
|
|
9
|
+
style: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface GridRow {
|
|
12
|
+
heightPx: number;
|
|
13
|
+
cells: GridCell[];
|
|
14
|
+
}
|
|
15
|
+
export interface GridView {
|
|
16
|
+
/** Column widths in px, in used-range order. */
|
|
17
|
+
cols: number[];
|
|
18
|
+
rows: GridRow[];
|
|
19
|
+
/** Rendered row count when the sheet was capped, else null. */
|
|
20
|
+
truncatedRows: number | null;
|
|
21
|
+
}
|
|
22
|
+
/** Cell budget before the grid truncates to keep the DOM bounded (web parity). */
|
|
23
|
+
export declare const GRID_MAX_CELLS = 60000;
|
|
24
|
+
export declare function buildGridView(sheet: ExcelSheet, options?: {
|
|
25
|
+
maxCells?: number;
|
|
26
|
+
}): GridView;
|
|
27
|
+
/** Convert the semantic CellStyle to CSS property→value pairs (camelCase). */
|
|
28
|
+
export declare function styleToCss(style: CellStyle): Record<string, string>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Grid render model (X3) — turns a parsed ExcelSheet (cells + merges + column/
|
|
2
|
+
// row sizes + styles) into a renderable, CSS-ready structure for the WYSIWYG
|
|
3
|
+
// grid view. Pure and DOM-free (ported from omni-viewer-web `buildSheetView`),
|
|
4
|
+
// so both the core DOM renderer and web's Vue renderer can consume it.
|
|
5
|
+
//
|
|
6
|
+
// This is a read-only view of the original parsed sheet — it does not reflect
|
|
7
|
+
// data-mode edits (docs/viewers/excel.md §3, X3 decision).
|
|
8
|
+
const DEFAULT_COL_WIDTH = 100;
|
|
9
|
+
const DEFAULT_ROW_HEIGHT = 24;
|
|
10
|
+
/** Cell budget before the grid truncates to keep the DOM bounded (web parity). */
|
|
11
|
+
export const GRID_MAX_CELLS = 60000;
|
|
12
|
+
export function buildGridView(sheet, options = {}) {
|
|
13
|
+
const range = sheet.usedRange;
|
|
14
|
+
if (!range)
|
|
15
|
+
return { cols: [], rows: [], truncatedRows: null };
|
|
16
|
+
const maxCells = options.maxCells ?? GRID_MAX_CELLS;
|
|
17
|
+
const totalRows = range.r1 - range.r0 + 1;
|
|
18
|
+
const totalCols = range.c1 - range.c0 + 1;
|
|
19
|
+
let lastRow = range.r1;
|
|
20
|
+
let truncatedRows = null;
|
|
21
|
+
if (totalRows * totalCols > maxCells) {
|
|
22
|
+
const maxRows = Math.max(1, Math.floor(maxCells / Math.max(1, totalCols)));
|
|
23
|
+
lastRow = range.r0 + maxRows - 1;
|
|
24
|
+
truncatedRows = maxRows;
|
|
25
|
+
}
|
|
26
|
+
const cellByAddr = new Map();
|
|
27
|
+
for (const cell of sheet.cells)
|
|
28
|
+
cellByAddr.set(`${cell.r}:${cell.c}`, cell);
|
|
29
|
+
// Merges: the top-left cell carries the span; the rest are covered/skipped.
|
|
30
|
+
const mergeStarts = new Map();
|
|
31
|
+
const mergeCovered = new Set();
|
|
32
|
+
for (const m of sheet.merges ?? []) {
|
|
33
|
+
for (let r = m.r0; r <= m.r1; r++) {
|
|
34
|
+
for (let c = m.c0; c <= m.c1; c++) {
|
|
35
|
+
const key = `${r}:${c}`;
|
|
36
|
+
if (r === m.r0 && c === m.c0) {
|
|
37
|
+
mergeStarts.set(key, { rowSpan: m.r1 - m.r0 + 1, colSpan: m.c1 - m.c0 + 1 });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
mergeCovered.add(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const cols = [];
|
|
46
|
+
for (let c = range.c0; c <= range.c1; c++) {
|
|
47
|
+
cols.push(sheet.columnWidthsPx?.[c] ?? DEFAULT_COL_WIDTH);
|
|
48
|
+
}
|
|
49
|
+
const rows = [];
|
|
50
|
+
for (let r = range.r0; r <= lastRow; r++) {
|
|
51
|
+
const cells = [];
|
|
52
|
+
for (let c = range.c0; c <= range.c1; c++) {
|
|
53
|
+
const key = `${r}:${c}`;
|
|
54
|
+
if (mergeCovered.has(key))
|
|
55
|
+
continue;
|
|
56
|
+
const cell = cellByAddr.get(key);
|
|
57
|
+
const span = mergeStarts.get(key);
|
|
58
|
+
cells.push({
|
|
59
|
+
r,
|
|
60
|
+
c,
|
|
61
|
+
rowSpan: span?.rowSpan ?? 1,
|
|
62
|
+
colSpan: span?.colSpan ?? 1,
|
|
63
|
+
text: cell?.w ?? '',
|
|
64
|
+
style: cell?.style ? styleToCss(cell.style) : {}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
rows.push({ heightPx: sheet.rowHeightsPx?.[r] ?? DEFAULT_ROW_HEIGHT, cells });
|
|
68
|
+
}
|
|
69
|
+
return { cols, rows, truncatedRows };
|
|
70
|
+
}
|
|
71
|
+
/** Convert the semantic CellStyle to CSS property→value pairs (camelCase). */
|
|
72
|
+
export function styleToCss(style) {
|
|
73
|
+
const css = {};
|
|
74
|
+
if (style.bold)
|
|
75
|
+
css.fontWeight = '700';
|
|
76
|
+
if (style.italic)
|
|
77
|
+
css.fontStyle = 'italic';
|
|
78
|
+
if (typeof style.fontSizePx === 'number')
|
|
79
|
+
css.fontSize = `${style.fontSizePx}px`;
|
|
80
|
+
if (style.color)
|
|
81
|
+
css.color = style.color;
|
|
82
|
+
if (style.background)
|
|
83
|
+
css.backgroundColor = style.background;
|
|
84
|
+
if (style.align)
|
|
85
|
+
css.textAlign = style.align;
|
|
86
|
+
if (style.valign)
|
|
87
|
+
css.verticalAlign = style.valign;
|
|
88
|
+
if (style.wrap)
|
|
89
|
+
css.whiteSpace = 'pre-wrap';
|
|
90
|
+
if (style.borders?.top)
|
|
91
|
+
css.borderTop = style.borders.top;
|
|
92
|
+
if (style.borders?.right)
|
|
93
|
+
css.borderRight = style.borders.right;
|
|
94
|
+
if (style.borders?.bottom)
|
|
95
|
+
css.borderBottom = style.borders.bottom;
|
|
96
|
+
if (style.borders?.left)
|
|
97
|
+
css.borderLeft = style.borders.left;
|
|
98
|
+
return css;
|
|
99
|
+
}
|