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,598 @@
|
|
|
1
|
+
// CsvController — the behavior-controller layer for the CSV viewer
|
|
2
|
+
// (DESIGN.md §3-②). Pure state machine: no DOM, no host services. The DOM
|
|
3
|
+
// renderer is one consumer; web (Vue) can consume it directly to share
|
|
4
|
+
// interaction semantics. Conformance kit item 3 (action sequence → normalized
|
|
5
|
+
// state) runs against this.
|
|
6
|
+
//
|
|
7
|
+
// Rows are identified by immutable RowIds (not array indexes) so edit-mode
|
|
8
|
+
// insert/delete cannot corrupt sort, search, or row numbering
|
|
9
|
+
// (docs/viewers/csv.md §6 편집과 저장).
|
|
10
|
+
import { parseCsv, applySort, detectColumnType, nextSortState, computeStatistics, serializeRowsToTsv, serializeRowsToJson, serializeRowsToCsv } from '../../parsers/csv/index.js';
|
|
11
|
+
export const DEFAULT_PAGE_SIZE = 500;
|
|
12
|
+
export const UNDO_STACK_LIMIT = 200;
|
|
13
|
+
/** Avoid a full-table scan for each very broad single-character query. */
|
|
14
|
+
export const CSV_MIN_SEARCH_LENGTH = 2;
|
|
15
|
+
export function createCsvController(source, options = {}) {
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
const text = typeof source === 'string'
|
|
18
|
+
? source
|
|
19
|
+
: new TextDecoder('utf-8', { fatal: false }).decode(source);
|
|
20
|
+
let document = emptyDocument();
|
|
21
|
+
let parse;
|
|
22
|
+
let headers = [];
|
|
23
|
+
/** Live column count — column insert/delete moves it off the parsed value. */
|
|
24
|
+
let columnCount = 0;
|
|
25
|
+
let rowOrder = [];
|
|
26
|
+
let byId = new Map();
|
|
27
|
+
let orderPos = new Map();
|
|
28
|
+
let nextRowId = 0;
|
|
29
|
+
let sort = { columnIndex: null, direction: null };
|
|
30
|
+
let sortedColumnType = null;
|
|
31
|
+
/** Document rows in sorted display order (row references). */
|
|
32
|
+
let sortedRows = [];
|
|
33
|
+
/** sortedRows filtered by the active search — always a distinct array. */
|
|
34
|
+
let displayRows = [];
|
|
35
|
+
let search = '';
|
|
36
|
+
let showRowNumbers = false;
|
|
37
|
+
let pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
38
|
+
let page = 0;
|
|
39
|
+
let viewMode = 'table';
|
|
40
|
+
let statsVisible = false;
|
|
41
|
+
let statsCache = null;
|
|
42
|
+
const undoStack = [];
|
|
43
|
+
let redoStack = [];
|
|
44
|
+
let savedDepth = 0;
|
|
45
|
+
let savedUnreachable = false;
|
|
46
|
+
/** True once the stack overflowed — an empty undo stack then no longer
|
|
47
|
+
* proves the document equals the original source text. */
|
|
48
|
+
let opsDropped = false;
|
|
49
|
+
function isDirty() {
|
|
50
|
+
return savedUnreachable || undoStack.length !== savedDepth;
|
|
51
|
+
}
|
|
52
|
+
function rebuildOrderPos() {
|
|
53
|
+
orderPos = new Map();
|
|
54
|
+
rowOrder.forEach((row, i) => orderPos.set(row.id, i));
|
|
55
|
+
}
|
|
56
|
+
function rowMatches(row, term) {
|
|
57
|
+
for (const cell of row.cells) {
|
|
58
|
+
if (cell.toLowerCase().includes(term))
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function hasActiveSearch() {
|
|
64
|
+
return search.length >= CSV_MIN_SEARCH_LENGTH;
|
|
65
|
+
}
|
|
66
|
+
/** Search filters over the sorted display order (docs §6). */
|
|
67
|
+
function refilter() {
|
|
68
|
+
if (!hasActiveSearch()) {
|
|
69
|
+
displayRows = sortedRows.slice();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const term = search.toLowerCase();
|
|
73
|
+
displayRows = sortedRows.filter((r) => rowMatches(r, term));
|
|
74
|
+
}
|
|
75
|
+
function resort() {
|
|
76
|
+
if (sort.columnIndex === null || !sort.direction) {
|
|
77
|
+
sortedRows = rowOrder.slice();
|
|
78
|
+
sortedColumnType = null;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const col = sort.columnIndex;
|
|
82
|
+
sortedColumnType = detectColumnType(rowOrder.map((r) => r.cells[col] ?? ''));
|
|
83
|
+
const positions = applySort(identity(rowOrder.length), sort, (rowIndex, columnIndex) => rowOrder[rowIndex]?.cells[columnIndex] ?? '', sortedColumnType);
|
|
84
|
+
sortedRows = positions.map((p) => rowOrder[p]);
|
|
85
|
+
}
|
|
86
|
+
refilter();
|
|
87
|
+
}
|
|
88
|
+
function reparse(delimiter) {
|
|
89
|
+
const parseOptions = {};
|
|
90
|
+
if (delimiter !== undefined)
|
|
91
|
+
parseOptions.delimiter = delimiter;
|
|
92
|
+
if (options.fileName !== undefined)
|
|
93
|
+
parseOptions.fileName = options.fileName;
|
|
94
|
+
if (options.limits !== undefined)
|
|
95
|
+
parseOptions.limits = options.limits;
|
|
96
|
+
parse = parseCsv(text, parseOptions).result;
|
|
97
|
+
document = parse.status === 'failed' ? emptyDocument() : parse.document;
|
|
98
|
+
headers = [...document.headers];
|
|
99
|
+
columnCount = document.columnCount;
|
|
100
|
+
nextRowId = 0;
|
|
101
|
+
rowOrder = document.rows.map((cells) => ({ id: nextRowId++, cells: [...cells] }));
|
|
102
|
+
byId = new Map(rowOrder.map((r) => [r.id, r]));
|
|
103
|
+
rebuildOrderPos();
|
|
104
|
+
sort = { columnIndex: null, direction: null };
|
|
105
|
+
sortedColumnType = null;
|
|
106
|
+
// The search term survives a reparse and reapplies to the new document.
|
|
107
|
+
resort();
|
|
108
|
+
page = 0;
|
|
109
|
+
statsCache = null;
|
|
110
|
+
undoStack.length = 0;
|
|
111
|
+
redoStack = [];
|
|
112
|
+
savedDepth = 0;
|
|
113
|
+
savedUnreachable = false;
|
|
114
|
+
opsDropped = false;
|
|
115
|
+
}
|
|
116
|
+
// ------------------------------------------------------------- edit ops
|
|
117
|
+
function addRowAt(row, positions) {
|
|
118
|
+
rowOrder.splice(clamp(positions.order, 0, rowOrder.length), 0, row);
|
|
119
|
+
sortedRows.splice(clamp(positions.sorted, 0, sortedRows.length), 0, row);
|
|
120
|
+
// display === -1 records "was filtered out" — don't resurface it.
|
|
121
|
+
if (positions.display >= 0) {
|
|
122
|
+
displayRows.splice(clamp(positions.display, 0, displayRows.length), 0, row);
|
|
123
|
+
}
|
|
124
|
+
byId.set(row.id, row);
|
|
125
|
+
rebuildOrderPos();
|
|
126
|
+
}
|
|
127
|
+
function removeRow(row) {
|
|
128
|
+
const removeFrom = (arr) => {
|
|
129
|
+
const i = arr.indexOf(row);
|
|
130
|
+
if (i >= 0)
|
|
131
|
+
arr.splice(i, 1);
|
|
132
|
+
};
|
|
133
|
+
removeFrom(rowOrder);
|
|
134
|
+
removeFrom(sortedRows);
|
|
135
|
+
removeFrom(displayRows);
|
|
136
|
+
byId.delete(row.id);
|
|
137
|
+
rebuildOrderPos();
|
|
138
|
+
}
|
|
139
|
+
// Column ops mutate the shared row objects, so rowOrder / sortedRows /
|
|
140
|
+
// displayRows all see the change without re-deriving. Display order is
|
|
141
|
+
// deliberately untouched (docs §6: edits never reshuffle the view) — the
|
|
142
|
+
// sort column index just shifts with the columns, and deleting the sorted
|
|
143
|
+
// column clears the sort marker while keeping the current order on screen.
|
|
144
|
+
function insertColumnAt(columnIndex, header, cells) {
|
|
145
|
+
headers.splice(columnIndex, 0, header);
|
|
146
|
+
for (const row of rowOrder) {
|
|
147
|
+
row.cells.splice(columnIndex, 0, cells?.get(row.id) ?? '');
|
|
148
|
+
}
|
|
149
|
+
columnCount++;
|
|
150
|
+
if (sort.columnIndex !== null && sort.columnIndex >= columnIndex) {
|
|
151
|
+
sort = { ...sort, columnIndex: sort.columnIndex + 1 };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function removeColumnAt(columnIndex) {
|
|
155
|
+
headers.splice(columnIndex, 1);
|
|
156
|
+
for (const row of rowOrder) {
|
|
157
|
+
row.cells.splice(columnIndex, 1);
|
|
158
|
+
}
|
|
159
|
+
columnCount--;
|
|
160
|
+
if (sort.columnIndex !== null) {
|
|
161
|
+
if (sort.columnIndex === columnIndex) {
|
|
162
|
+
sort = { columnIndex: null, direction: null };
|
|
163
|
+
sortedColumnType = null;
|
|
164
|
+
}
|
|
165
|
+
else if (sort.columnIndex > columnIndex) {
|
|
166
|
+
sort = { ...sort, columnIndex: sort.columnIndex - 1 };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function applyOp(op, direction) {
|
|
171
|
+
switch (op.kind) {
|
|
172
|
+
case 'cell': {
|
|
173
|
+
const row = byId.get(op.rowId);
|
|
174
|
+
if (row) {
|
|
175
|
+
row.cells[op.columnIndex] =
|
|
176
|
+
direction === 'forward' ? op.next : op.prev;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'header': {
|
|
181
|
+
headers[op.columnIndex] = direction === 'forward' ? op.next : op.prev;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case 'insert': {
|
|
185
|
+
if (direction === 'forward')
|
|
186
|
+
addRowAt(op.row, op.positions);
|
|
187
|
+
else
|
|
188
|
+
removeRow(op.row);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'delete': {
|
|
192
|
+
if (direction === 'forward')
|
|
193
|
+
removeRow(op.row);
|
|
194
|
+
else
|
|
195
|
+
addRowAt(op.row, op.positions);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case 'insert-col': {
|
|
199
|
+
if (direction === 'forward')
|
|
200
|
+
insertColumnAt(op.columnIndex, op.header);
|
|
201
|
+
else
|
|
202
|
+
removeColumnAt(op.columnIndex);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case 'delete-col': {
|
|
206
|
+
if (direction === 'forward')
|
|
207
|
+
removeColumnAt(op.columnIndex);
|
|
208
|
+
else
|
|
209
|
+
insertColumnAt(op.columnIndex, op.header, op.cells);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case 'document': {
|
|
213
|
+
const target = direction === 'forward' ? op.next : op.prev;
|
|
214
|
+
headers = target.headers;
|
|
215
|
+
rowOrder = target.rows.slice();
|
|
216
|
+
byId = new Map(rowOrder.map((r) => [r.id, r]));
|
|
217
|
+
rebuildOrderPos();
|
|
218
|
+
columnCount = target.columnCount;
|
|
219
|
+
parse = target.parse;
|
|
220
|
+
document = target.document;
|
|
221
|
+
// The sorted column may not exist in the swapped-in document.
|
|
222
|
+
if (sort.columnIndex !== null && sort.columnIndex >= columnCount) {
|
|
223
|
+
sort = { columnIndex: null, direction: null };
|
|
224
|
+
sortedColumnType = null;
|
|
225
|
+
}
|
|
226
|
+
resort();
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
statsCache = null;
|
|
231
|
+
}
|
|
232
|
+
function pushOp(op) {
|
|
233
|
+
applyOp(op, 'forward');
|
|
234
|
+
if (redoStack.length > 0) {
|
|
235
|
+
// The saved point may live in the discarded redo branch.
|
|
236
|
+
if (savedDepth > undoStack.length)
|
|
237
|
+
savedUnreachable = true;
|
|
238
|
+
redoStack = [];
|
|
239
|
+
}
|
|
240
|
+
undoStack.push(op);
|
|
241
|
+
if (undoStack.length > UNDO_STACK_LIMIT) {
|
|
242
|
+
undoStack.shift();
|
|
243
|
+
opsDropped = true;
|
|
244
|
+
if (savedDepth > 0)
|
|
245
|
+
savedDepth--;
|
|
246
|
+
else
|
|
247
|
+
savedUnreachable = true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function documentCsv() {
|
|
251
|
+
return serializeRowsToCsv(rowOrder.map((r) => r.cells), headers, document.delimiter);
|
|
252
|
+
}
|
|
253
|
+
/** Verbatim source while provably unedited (original quoting and line
|
|
254
|
+
* endings preserved); normalized serialization once edits exist. */
|
|
255
|
+
function toRawText() {
|
|
256
|
+
return undoStack.length === 0 && !opsDropped ? text : documentCsv();
|
|
257
|
+
}
|
|
258
|
+
// -------------------------------------------------------------- state
|
|
259
|
+
function pageCount() {
|
|
260
|
+
return Math.max(1, Math.ceil(displayRows.length / pageSize));
|
|
261
|
+
}
|
|
262
|
+
function buildState() {
|
|
263
|
+
const start = page * pageSize;
|
|
264
|
+
return {
|
|
265
|
+
status: parse.status,
|
|
266
|
+
failure: parse.status === 'failed' ? parse.failure : null,
|
|
267
|
+
diagnostics: parse.diagnostics,
|
|
268
|
+
headers: [...headers],
|
|
269
|
+
columnCount,
|
|
270
|
+
rowCount: rowOrder.length,
|
|
271
|
+
delimiter: document.delimiter,
|
|
272
|
+
detectionSource: document.detection.source,
|
|
273
|
+
sort,
|
|
274
|
+
sortedColumnType,
|
|
275
|
+
search,
|
|
276
|
+
matchedRowCount: displayRows.length,
|
|
277
|
+
showRowNumbers,
|
|
278
|
+
dirty: isDirty(),
|
|
279
|
+
canUndo: undoStack.length > 0,
|
|
280
|
+
canRedo: redoStack.length > 0,
|
|
281
|
+
page,
|
|
282
|
+
pageCount: pageCount(),
|
|
283
|
+
pageSize,
|
|
284
|
+
viewMode,
|
|
285
|
+
statsVisible,
|
|
286
|
+
visibleRowIds: displayRows.slice(start, start + pageSize).map((r) => r.id)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
let state;
|
|
290
|
+
function commit() {
|
|
291
|
+
state = buildState();
|
|
292
|
+
for (const listener of listeners)
|
|
293
|
+
listener(state);
|
|
294
|
+
}
|
|
295
|
+
reparse(undefined);
|
|
296
|
+
state = buildState();
|
|
297
|
+
function dispatch(action) {
|
|
298
|
+
switch (action.type) {
|
|
299
|
+
case 'sort-column': {
|
|
300
|
+
if (action.columnIndex < 0 || action.columnIndex >= columnCount) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
sort = nextSortState(sort, action.columnIndex);
|
|
304
|
+
resort();
|
|
305
|
+
page = 0;
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'set-delimiter': {
|
|
309
|
+
// Reparsing would discard edits (docs §6 dirty guard).
|
|
310
|
+
if (isDirty())
|
|
311
|
+
return;
|
|
312
|
+
reparse(action.delimiter === 'auto' ? undefined : action.delimiter);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'set-search': {
|
|
316
|
+
if (action.search === search)
|
|
317
|
+
return;
|
|
318
|
+
search = action.search;
|
|
319
|
+
refilter();
|
|
320
|
+
page = 0;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case 'set-page-size': {
|
|
324
|
+
if (!Number.isInteger(action.pageSize) || action.pageSize <= 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (action.pageSize === pageSize)
|
|
328
|
+
return;
|
|
329
|
+
const firstVisiblePosition = page * pageSize;
|
|
330
|
+
pageSize = action.pageSize;
|
|
331
|
+
page = Math.floor(firstVisiblePosition / pageSize);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 'toggle-row-numbers': {
|
|
335
|
+
showRowNumbers = !showRowNumbers;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case 'set-page': {
|
|
339
|
+
page = clamp(action.page, 0, pageCount() - 1);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case 'next-page': {
|
|
343
|
+
page = clamp(page + 1, 0, pageCount() - 1);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case 'prev-page': {
|
|
347
|
+
page = clamp(page - 1, 0, pageCount() - 1);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'set-view-mode': {
|
|
351
|
+
viewMode = action.mode;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case 'toggle-stats': {
|
|
355
|
+
statsVisible = !statsVisible;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'edit-cell': {
|
|
359
|
+
if (parse.status !== 'ok')
|
|
360
|
+
return; // docs §6 결정 2
|
|
361
|
+
const row = byId.get(action.rowId);
|
|
362
|
+
if (!row)
|
|
363
|
+
return;
|
|
364
|
+
if (action.columnIndex < 0 || action.columnIndex >= columnCount) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const prev = row.cells[action.columnIndex] ?? '';
|
|
368
|
+
if (prev === action.value)
|
|
369
|
+
return;
|
|
370
|
+
pushOp({
|
|
371
|
+
kind: 'cell',
|
|
372
|
+
rowId: action.rowId,
|
|
373
|
+
columnIndex: action.columnIndex,
|
|
374
|
+
prev,
|
|
375
|
+
next: action.value
|
|
376
|
+
});
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case 'edit-header': {
|
|
380
|
+
if (parse.status !== 'ok')
|
|
381
|
+
return;
|
|
382
|
+
if (action.columnIndex < 0 || action.columnIndex >= headers.length) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const prev = headers[action.columnIndex] ?? '';
|
|
386
|
+
if (prev === action.value)
|
|
387
|
+
return;
|
|
388
|
+
pushOp({
|
|
389
|
+
kind: 'header',
|
|
390
|
+
columnIndex: action.columnIndex,
|
|
391
|
+
prev,
|
|
392
|
+
next: action.value
|
|
393
|
+
});
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'insert-row': {
|
|
397
|
+
if (parse.status !== 'ok')
|
|
398
|
+
return;
|
|
399
|
+
const row = {
|
|
400
|
+
id: nextRowId++,
|
|
401
|
+
cells: Array.from({ length: columnCount }, () => '')
|
|
402
|
+
};
|
|
403
|
+
let positions;
|
|
404
|
+
if (action.afterRowId === 'end') {
|
|
405
|
+
positions = {
|
|
406
|
+
order: rowOrder.length,
|
|
407
|
+
sorted: sortedRows.length,
|
|
408
|
+
display: displayRows.length
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
const after = byId.get(action.afterRowId);
|
|
413
|
+
if (!after)
|
|
414
|
+
return;
|
|
415
|
+
// The new row shows right below the clicked one regardless
|
|
416
|
+
// of sort/filter — an invisible new row cannot be edited.
|
|
417
|
+
positions = {
|
|
418
|
+
order: (orderPos.get(after.id) ?? rowOrder.length - 1) + 1,
|
|
419
|
+
sorted: sortedRows.indexOf(after) + 1 || sortedRows.length,
|
|
420
|
+
display: displayRows.indexOf(after) + 1 || displayRows.length
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
pushOp({ kind: 'insert', row, positions });
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'delete-row': {
|
|
427
|
+
if (parse.status !== 'ok')
|
|
428
|
+
return;
|
|
429
|
+
const row = byId.get(action.rowId);
|
|
430
|
+
if (!row)
|
|
431
|
+
return;
|
|
432
|
+
pushOp({
|
|
433
|
+
kind: 'delete',
|
|
434
|
+
row,
|
|
435
|
+
positions: {
|
|
436
|
+
order: orderPos.get(row.id) ?? 0,
|
|
437
|
+
sorted: sortedRows.indexOf(row),
|
|
438
|
+
display: displayRows.indexOf(row)
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case 'insert-column': {
|
|
444
|
+
if (parse.status !== 'ok')
|
|
445
|
+
return;
|
|
446
|
+
if (action.columnIndex < 0 || action.columnIndex > columnCount) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
pushOp({
|
|
450
|
+
kind: 'insert-col',
|
|
451
|
+
columnIndex: action.columnIndex,
|
|
452
|
+
header: `Column${columnCount + 1}`
|
|
453
|
+
});
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case 'delete-column': {
|
|
457
|
+
if (parse.status !== 'ok')
|
|
458
|
+
return;
|
|
459
|
+
// Deleting the last column would leave an unrepresentable
|
|
460
|
+
// zero-column document.
|
|
461
|
+
if (columnCount <= 1)
|
|
462
|
+
return;
|
|
463
|
+
if (action.columnIndex < 0 || action.columnIndex >= columnCount) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
pushOp({
|
|
467
|
+
kind: 'delete-col',
|
|
468
|
+
columnIndex: action.columnIndex,
|
|
469
|
+
header: headers[action.columnIndex] ?? '',
|
|
470
|
+
cells: new Map(rowOrder.map((r) => [r.id, r.cells[action.columnIndex] ?? '']))
|
|
471
|
+
});
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case 'replace-document': {
|
|
475
|
+
if (parse.status !== 'ok')
|
|
476
|
+
return;
|
|
477
|
+
if (action.text === toRawText())
|
|
478
|
+
return;
|
|
479
|
+
// The active delimiter is forced so mid-edit text never flips
|
|
480
|
+
// the document to a surprise delimiter; switching stays with
|
|
481
|
+
// the delimiter select (and its dirty guard).
|
|
482
|
+
const parseOptions = { delimiter: document.delimiter };
|
|
483
|
+
if (options.fileName !== undefined)
|
|
484
|
+
parseOptions.fileName = options.fileName;
|
|
485
|
+
if (options.limits !== undefined)
|
|
486
|
+
parseOptions.limits = options.limits;
|
|
487
|
+
const next = parseCsv(action.text, parseOptions).result;
|
|
488
|
+
// A swap that cannot represent the full text (partial/failed,
|
|
489
|
+
// e.g. over the row limit) is refused; the good document stays.
|
|
490
|
+
// Emptied text parses 'ok' and legitimately empties the document.
|
|
491
|
+
if (next.status !== 'ok')
|
|
492
|
+
return;
|
|
493
|
+
const nextRows = next.document.rows.map((cells) => ({
|
|
494
|
+
id: nextRowId++,
|
|
495
|
+
cells: [...cells]
|
|
496
|
+
}));
|
|
497
|
+
pushOp({
|
|
498
|
+
kind: 'document',
|
|
499
|
+
prev: {
|
|
500
|
+
headers,
|
|
501
|
+
rows: rowOrder.slice(),
|
|
502
|
+
columnCount,
|
|
503
|
+
parse,
|
|
504
|
+
document
|
|
505
|
+
},
|
|
506
|
+
next: {
|
|
507
|
+
headers: [...next.document.headers],
|
|
508
|
+
rows: nextRows,
|
|
509
|
+
columnCount: next.document.columnCount,
|
|
510
|
+
parse: next,
|
|
511
|
+
document: next.document
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
page = 0;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
case 'undo': {
|
|
518
|
+
const op = undoStack.pop();
|
|
519
|
+
if (!op)
|
|
520
|
+
return;
|
|
521
|
+
applyOp(op, 'backward');
|
|
522
|
+
redoStack.push(op);
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
case 'redo': {
|
|
526
|
+
const op = redoStack.pop();
|
|
527
|
+
if (!op)
|
|
528
|
+
return;
|
|
529
|
+
applyOp(op, 'forward');
|
|
530
|
+
undoStack.push(op);
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
commit();
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
get state() {
|
|
538
|
+
return state;
|
|
539
|
+
},
|
|
540
|
+
dispatch,
|
|
541
|
+
subscribe(listener) {
|
|
542
|
+
listeners.add(listener);
|
|
543
|
+
return () => listeners.delete(listener);
|
|
544
|
+
},
|
|
545
|
+
getRowById(rowId) {
|
|
546
|
+
return byId.get(rowId)?.cells ?? [];
|
|
547
|
+
},
|
|
548
|
+
getRowNumber(rowId) {
|
|
549
|
+
return (orderPos.get(rowId) ?? -1) + 1;
|
|
550
|
+
},
|
|
551
|
+
getStatistics() {
|
|
552
|
+
if (!statsCache) {
|
|
553
|
+
statsCache = computeStatistics(rowOrder.map((r) => r.cells), headers);
|
|
554
|
+
}
|
|
555
|
+
return statsCache;
|
|
556
|
+
},
|
|
557
|
+
toTsv() {
|
|
558
|
+
// Copy targets the current display result: sorted + search-filtered
|
|
559
|
+
// (docs §6). sourceRowsLoaded below stays the unfiltered total.
|
|
560
|
+
return serializeRowsToTsv(displayRows.map((r) => r.cells), headers);
|
|
561
|
+
},
|
|
562
|
+
toJson() {
|
|
563
|
+
return serializeRowsToJson(displayRows.map((r) => r.cells), headers, {
|
|
564
|
+
scope: 'loaded-results',
|
|
565
|
+
sourceRowsLoaded: rowOrder.length,
|
|
566
|
+
sourceFullyScanned: parse.status === 'ok'
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
toCsv() {
|
|
570
|
+
return serializeRowsToCsv(displayRows.map((r) => r.cells), headers, document.delimiter);
|
|
571
|
+
},
|
|
572
|
+
toDocumentCsv() {
|
|
573
|
+
return documentCsv();
|
|
574
|
+
},
|
|
575
|
+
markSaved() {
|
|
576
|
+
savedDepth = undoStack.length;
|
|
577
|
+
savedUnreachable = false;
|
|
578
|
+
commit();
|
|
579
|
+
},
|
|
580
|
+
rawText: text,
|
|
581
|
+
toRawText
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function emptyDocument() {
|
|
585
|
+
return {
|
|
586
|
+
headers: [],
|
|
587
|
+
rows: [],
|
|
588
|
+
columnCount: 0,
|
|
589
|
+
delimiter: ',',
|
|
590
|
+
detection: { delimiter: ',', confidence: 0, source: 'auto' }
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function identity(n) {
|
|
594
|
+
return Array.from({ length: n }, (_, i) => i);
|
|
595
|
+
}
|
|
596
|
+
function clamp(v, min, max) {
|
|
597
|
+
return Math.min(Math.max(v, min), max);
|
|
598
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ClipboardService, FileSaveService, FileWritebackService, HostContext } from '../../host/index.js';
|
|
2
|
+
import { type MountOptions, type ViewerHandle, type ViewerInput } from '../types.js';
|
|
3
|
+
import { type ResourceLimits } from '../../parsers/types.js';
|
|
4
|
+
export { createCsvController, DEFAULT_PAGE_SIZE } from './controller.js';
|
|
5
|
+
export type { CsvController, CsvViewState, CsvAction, CsvControllerOptions } from './controller.js';
|
|
6
|
+
export { csvViewerCss } from './styles.js';
|
|
7
|
+
/** Viewer metadata — single source for the registry codegen (DESIGN.md §7). */
|
|
8
|
+
export declare const CSV_VIEWER_META: {
|
|
9
|
+
id: string;
|
|
10
|
+
displayNameKey: string;
|
|
11
|
+
extensions: string[];
|
|
12
|
+
priority: number;
|
|
13
|
+
requiredServices: readonly [];
|
|
14
|
+
optionalServices: readonly ["clipboard", "save", "writeback"];
|
|
15
|
+
inputOwnership: "borrows";
|
|
16
|
+
};
|
|
17
|
+
export type CsvViewerContext = HostContext & {
|
|
18
|
+
clipboard?: ClipboardService;
|
|
19
|
+
save?: FileSaveService;
|
|
20
|
+
writeback?: FileWritebackService;
|
|
21
|
+
};
|
|
22
|
+
/** CSV mount handle: dirty inspection lets hosts guard against re-renders
|
|
23
|
+
* that would discard unsaved edits (docs §6). */
|
|
24
|
+
export interface CsvViewerHandle extends ViewerHandle {
|
|
25
|
+
isDirty(): boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Export file name: strip the extension, append -export, pick the extension
|
|
28
|
+
* by the active delimiter (docs/viewers/csv.md §6). */
|
|
29
|
+
export declare function exportFileName(sourceName: string, delimiter: string): string;
|
|
30
|
+
/** Suggested filename for Save As. The host's FileSaveService presents the
|
|
31
|
+
* destination picker; this only supplies its initial filename. */
|
|
32
|
+
export declare function saveAsFileName(sourceName: string, delimiter: string): string;
|
|
33
|
+
export interface CsvMountOptions extends MountOptions {
|
|
34
|
+
/** Parser resource limits (docs/parsers/csv.md); adapters may tighten them. */
|
|
35
|
+
limits?: ResourceLimits;
|
|
36
|
+
pageSize?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Clipboard copy guard (docs/viewers/csv.md §6): larger payloads are refused. */
|
|
39
|
+
export declare const COPY_PAYLOAD_LIMIT_BYTES: number;
|
|
40
|
+
/** Cells at least this long (or containing a newline) get a full-content tooltip. */
|
|
41
|
+
export declare const CELL_TOOLTIP_MIN_CHARS = 50;
|
|
42
|
+
/** Header single-click sort is deferred briefly so a double-click can win and
|
|
43
|
+
* open the rename editor instead (docs §6). */
|
|
44
|
+
export declare const SORT_CLICK_DELAY_MS = 250;
|
|
45
|
+
/** Raw-view typing settles this long before the text is reparsed into the
|
|
46
|
+
* document (docs §6 — the edit only marks dirty; saving stays explicit). */
|
|
47
|
+
export declare const RAW_EDIT_DEBOUNCE_MS = 400;
|
|
48
|
+
export declare function mountCsvViewer(input: ViewerInput, container: HTMLElement, ctx: CsvViewerContext, options?: CsvMountOptions): Promise<CsvViewerHandle>;
|