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.
Files changed (387) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/THIRD_PARTY_NOTICES.md +49 -0
  4. package/dist/assets/audio-engine/audio_engine.mjs +2 -0
  5. package/dist/assets/audio-engine/audio_engine.wasm +0 -0
  6. package/dist/assets/pdfjs/pdf.worker.min.mjs +21 -0
  7. package/dist/host/index.d.ts +133 -0
  8. package/dist/host/index.js +10 -0
  9. package/dist/i18n/catalog.en.d.ts +1 -0
  10. package/dist/i18n/catalog.en.js +411 -0
  11. package/dist/i18n/catalog.ko.d.ts +1 -0
  12. package/dist/i18n/catalog.ko.js +68 -0
  13. package/dist/i18n/index.d.ts +18 -0
  14. package/dist/i18n/index.js +41 -0
  15. package/dist/licenses/emscripten.txt +101 -0
  16. package/dist/licenses/kissfft.txt +28 -0
  17. package/dist/licenses/pdfjs-dist.txt +177 -0
  18. package/dist/parsers/a2l/index.d.ts +1 -0
  19. package/dist/parsers/a2l/index.js +1 -0
  20. package/dist/parsers/archive/index.d.ts +22 -0
  21. package/dist/parsers/archive/index.js +46 -0
  22. package/dist/parsers/archive/model.d.ts +29 -0
  23. package/dist/parsers/archive/model.js +1 -0
  24. package/dist/parsers/archive/worker.d.ts +21 -0
  25. package/dist/parsers/archive/worker.js +1 -0
  26. package/dist/parsers/arxml/index.d.ts +1 -0
  27. package/dist/parsers/arxml/index.js +1 -0
  28. package/dist/parsers/asc/index.d.ts +1 -0
  29. package/dist/parsers/asc/index.js +1 -0
  30. package/dist/parsers/audio/index.d.ts +9 -0
  31. package/dist/parsers/audio/index.js +46 -0
  32. package/dist/parsers/automotive/index.d.ts +33 -0
  33. package/dist/parsers/automotive/index.js +973 -0
  34. package/dist/parsers/blf/index.d.ts +1 -0
  35. package/dist/parsers/blf/index.js +1 -0
  36. package/dist/parsers/csv/delimiter.d.ts +31 -0
  37. package/dist/parsers/csv/delimiter.js +151 -0
  38. package/dist/parsers/csv/index.d.ts +30 -0
  39. package/dist/parsers/csv/index.js +108 -0
  40. package/dist/parsers/csv/parseText.d.ts +20 -0
  41. package/dist/parsers/csv/parseText.js +108 -0
  42. package/dist/parsers/csv/sort.d.ts +26 -0
  43. package/dist/parsers/csv/sort.js +224 -0
  44. package/dist/parsers/csv/statistics.d.ts +49 -0
  45. package/dist/parsers/csv/statistics.js +150 -0
  46. package/dist/parsers/dbc/index.d.ts +56 -0
  47. package/dist/parsers/dbc/index.js +94 -0
  48. package/dist/parsers/doc-binary/index.d.ts +35 -0
  49. package/dist/parsers/doc-binary/index.js +267 -0
  50. package/dist/parsers/doc-binary/vscode-parser.d.ts +142 -0
  51. package/dist/parsers/doc-binary/vscode-parser.js +3747 -0
  52. package/dist/parsers/excel/index.d.ts +17 -0
  53. package/dist/parsers/excel/index.js +406 -0
  54. package/dist/parsers/excel/model.d.ts +80 -0
  55. package/dist/parsers/excel/model.js +48 -0
  56. package/dist/parsers/excel/serialize.d.ts +3 -0
  57. package/dist/parsers/excel/serialize.js +78 -0
  58. package/dist/parsers/hdf5/index.d.ts +75 -0
  59. package/dist/parsers/hdf5/index.js +658 -0
  60. package/dist/parsers/hdf5/node.d.ts +21 -0
  61. package/dist/parsers/hdf5/node.js +95 -0
  62. package/dist/parsers/json/index.d.ts +15 -0
  63. package/dist/parsers/json/index.js +102 -0
  64. package/dist/parsers/json/model.d.ts +42 -0
  65. package/dist/parsers/json/model.js +140 -0
  66. package/dist/parsers/json/parse.d.ts +22 -0
  67. package/dist/parsers/json/parse.js +279 -0
  68. package/dist/parsers/json/tokenizer.d.ts +17 -0
  69. package/dist/parsers/json/tokenizer.js +175 -0
  70. package/dist/parsers/jsonl/index.d.ts +14 -0
  71. package/dist/parsers/jsonl/index.js +18 -0
  72. package/dist/parsers/jsonl/model.d.ts +23 -0
  73. package/dist/parsers/jsonl/model.js +1 -0
  74. package/dist/parsers/jsonl/parse.d.ts +7 -0
  75. package/dist/parsers/jsonl/parse.js +34 -0
  76. package/dist/parsers/markdown/index.d.ts +37 -0
  77. package/dist/parsers/markdown/index.js +59 -0
  78. package/dist/parsers/mat/index.d.ts +31 -0
  79. package/dist/parsers/mat/index.js +426 -0
  80. package/dist/parsers/mermaid/index.d.ts +5 -0
  81. package/dist/parsers/mermaid/index.js +3 -0
  82. package/dist/parsers/mf4/index.d.ts +1 -0
  83. package/dist/parsers/mf4/index.js +1 -0
  84. package/dist/parsers/parquet/index.d.ts +21 -0
  85. package/dist/parsers/parquet/index.js +68 -0
  86. package/dist/parsers/pcap/index.d.ts +1 -0
  87. package/dist/parsers/pcap/index.js +1 -0
  88. package/dist/parsers/pcapng/index.d.ts +1 -0
  89. package/dist/parsers/pcapng/index.js +1 -0
  90. package/dist/parsers/plantuml/index.d.ts +5 -0
  91. package/dist/parsers/plantuml/index.js +4 -0
  92. package/dist/parsers/ppt-binary/index.d.ts +5 -0
  93. package/dist/parsers/ppt-binary/index.js +62 -0
  94. package/dist/parsers/ppt-binary/pptBinaryBuildContext.d.ts +12 -0
  95. package/dist/parsers/ppt-binary/pptBinaryBuildContext.js +40 -0
  96. package/dist/parsers/ppt-binary/pptBinaryContainer.d.ts +4 -0
  97. package/dist/parsers/ppt-binary/pptBinaryContainer.js +174 -0
  98. package/dist/parsers/ppt-binary/pptBinaryTypes.d.ts +125 -0
  99. package/dist/parsers/ppt-binary/pptBinaryTypes.js +1 -0
  100. package/dist/parsers/ppt-binary/pptSlideLayouts.d.ts +9 -0
  101. package/dist/parsers/ppt-binary/pptSlideLayouts.js +510 -0
  102. package/dist/parsers/ppt-binary/vscode-parser.d.ts +116 -0
  103. package/dist/parsers/ppt-binary/vscode-parser.js +2853 -0
  104. package/dist/parsers/ppt-binary/vscode.d.ts +4 -0
  105. package/dist/parsers/ppt-binary/vscode.js +12 -0
  106. package/dist/parsers/pptx/index.d.ts +15 -0
  107. package/dist/parsers/pptx/index.js +88 -0
  108. package/dist/parsers/pptx/vscode-parser.d.ts +167 -0
  109. package/dist/parsers/pptx/vscode-parser.js +1503 -0
  110. package/dist/parsers/pptx/vscode.d.ts +4 -0
  111. package/dist/parsers/pptx/vscode.js +18 -0
  112. package/dist/parsers/pptx/zip-reader.d.ts +3 -0
  113. package/dist/parsers/pptx/zip-reader.js +20 -0
  114. package/dist/parsers/proto/index.d.ts +90 -0
  115. package/dist/parsers/proto/index.js +197 -0
  116. package/dist/parsers/psd/index.d.ts +16 -0
  117. package/dist/parsers/psd/index.js +10 -0
  118. package/dist/parsers/shapefile/dbf.d.ts +23 -0
  119. package/dist/parsers/shapefile/dbf.js +112 -0
  120. package/dist/parsers/shapefile/index.d.ts +44 -0
  121. package/dist/parsers/shapefile/index.js +119 -0
  122. package/dist/parsers/shapefile/reproject.d.ts +10 -0
  123. package/dist/parsers/shapefile/reproject.js +41 -0
  124. package/dist/parsers/slide-model.d.ts +106 -0
  125. package/dist/parsers/slide-model.js +1 -0
  126. package/dist/parsers/toml/index.d.ts +15 -0
  127. package/dist/parsers/toml/index.js +18 -0
  128. package/dist/parsers/toml/model.d.ts +15 -0
  129. package/dist/parsers/toml/model.js +1 -0
  130. package/dist/parsers/toml/parse.d.ts +16 -0
  131. package/dist/parsers/toml/parse.js +266 -0
  132. package/dist/parsers/types.d.ts +103 -0
  133. package/dist/parsers/types.js +106 -0
  134. package/dist/parsers/video/index.d.ts +6 -0
  135. package/dist/parsers/video/index.js +2 -0
  136. package/dist/parsers/yaml/index.d.ts +16 -0
  137. package/dist/parsers/yaml/index.js +58 -0
  138. package/dist/parsers/yaml/model.d.ts +22 -0
  139. package/dist/parsers/yaml/model.js +1 -0
  140. package/dist/parsers/yaml/self-loading.d.ts +3 -0
  141. package/dist/parsers/yaml/self-loading.js +19 -0
  142. package/dist/parsers/zip-scan.d.ts +8 -0
  143. package/dist/parsers/zip-scan.js +80 -0
  144. package/dist/registry/container.d.ts +17 -0
  145. package/dist/registry/container.js +49 -0
  146. package/dist/registry/index.d.ts +133 -0
  147. package/dist/registry/index.js +348 -0
  148. package/dist/registry/probe.d.ts +3 -0
  149. package/dist/registry/probe.js +177 -0
  150. package/dist/registry/sniff.d.ts +17 -0
  151. package/dist/registry/sniff.js +57 -0
  152. package/dist/styles/archive.css +10 -0
  153. package/dist/styles/audio.css +66 -0
  154. package/dist/styles/automotive.css +1 -0
  155. package/dist/styles/csv.css +262 -0
  156. package/dist/styles/excel.css +212 -0
  157. package/dist/styles/fallback.css +48 -0
  158. package/dist/styles/hdf5.css +3 -0
  159. package/dist/styles/hwp.css +12 -0
  160. package/dist/styles/image.css +115 -0
  161. package/dist/styles/json.css +342 -0
  162. package/dist/styles/jsonl.css +1 -0
  163. package/dist/styles/markdown.css +10 -0
  164. package/dist/styles/mat.css +2 -0
  165. package/dist/styles/mermaid.css +1 -0
  166. package/dist/styles/parquet.css +2 -0
  167. package/dist/styles/pdf.css +551 -0
  168. package/dist/styles/plantuml.css +1 -0
  169. package/dist/styles/ppt.css +1 -0
  170. package/dist/styles/proto.css +2 -0
  171. package/dist/styles/psd.css +1 -0
  172. package/dist/styles/shapefile.css +25 -0
  173. package/dist/styles/toml.css +1 -0
  174. package/dist/styles/video.css +74 -0
  175. package/dist/styles/word.css +685 -0
  176. package/dist/styles/yaml.css +1 -0
  177. package/dist/viewers/a2l/index.d.ts +15 -0
  178. package/dist/viewers/a2l/index.js +5 -0
  179. package/dist/viewers/archive/archive-preview-decoder.d.ts +4 -0
  180. package/dist/viewers/archive/archive-preview-decoder.js +223 -0
  181. package/dist/viewers/archive/controller.d.ts +24 -0
  182. package/dist/viewers/archive/controller.js +18 -0
  183. package/dist/viewers/archive/index.d.ts +25 -0
  184. package/dist/viewers/archive/index.js +230 -0
  185. package/dist/viewers/archive/preview.d.ts +2 -0
  186. package/dist/viewers/archive/preview.js +2 -0
  187. package/dist/viewers/archive/styles.d.ts +1 -0
  188. package/dist/viewers/archive/styles.js +11 -0
  189. package/dist/viewers/arxml/index.d.ts +15 -0
  190. package/dist/viewers/arxml/index.js +5 -0
  191. package/dist/viewers/asc/index.d.ts +15 -0
  192. package/dist/viewers/asc/index.js +5 -0
  193. package/dist/viewers/audio/controller.d.ts +33 -0
  194. package/dist/viewers/audio/controller.js +49 -0
  195. package/dist/viewers/audio/engine.d.ts +44 -0
  196. package/dist/viewers/audio/engine.js +108 -0
  197. package/dist/viewers/audio/index.d.ts +92 -0
  198. package/dist/viewers/audio/index.js +448 -0
  199. package/dist/viewers/audio/self-loading.d.ts +7 -0
  200. package/dist/viewers/audio/self-loading.js +42 -0
  201. package/dist/viewers/audio/styles.d.ts +1 -0
  202. package/dist/viewers/audio/styles.js +73 -0
  203. package/dist/viewers/automotive/index.d.ts +8 -0
  204. package/dist/viewers/automotive/index.js +152 -0
  205. package/dist/viewers/automotive/styles.d.ts +1 -0
  206. package/dist/viewers/automotive/styles.js +1 -0
  207. package/dist/viewers/avro/index.d.ts +15 -0
  208. package/dist/viewers/avro/index.js +5 -0
  209. package/dist/viewers/bag/index.d.ts +15 -0
  210. package/dist/viewers/bag/index.js +5 -0
  211. package/dist/viewers/blf/index.d.ts +15 -0
  212. package/dist/viewers/blf/index.js +5 -0
  213. package/dist/viewers/csv/controller.d.ts +126 -0
  214. package/dist/viewers/csv/controller.js +598 -0
  215. package/dist/viewers/csv/index.d.ts +48 -0
  216. package/dist/viewers/csv/index.js +846 -0
  217. package/dist/viewers/csv/styles.d.ts +1 -0
  218. package/dist/viewers/csv/styles.js +267 -0
  219. package/dist/viewers/db3/index.d.ts +15 -0
  220. package/dist/viewers/db3/index.js +5 -0
  221. package/dist/viewers/dbc/index.d.ts +15 -0
  222. package/dist/viewers/dbc/index.js +5 -0
  223. package/dist/viewers/diagram.d.ts +14 -0
  224. package/dist/viewers/diagram.js +95 -0
  225. package/dist/viewers/excel/controller.d.ts +108 -0
  226. package/dist/viewers/excel/controller.js +548 -0
  227. package/dist/viewers/excel/grid.d.ts +28 -0
  228. package/dist/viewers/excel/grid.js +99 -0
  229. package/dist/viewers/excel/index.d.ts +37 -0
  230. package/dist/viewers/excel/index.js +620 -0
  231. package/dist/viewers/excel/self-loading.d.ts +6 -0
  232. package/dist/viewers/excel/self-loading.js +13 -0
  233. package/dist/viewers/excel/styles.d.ts +1 -0
  234. package/dist/viewers/excel/styles.js +216 -0
  235. package/dist/viewers/fallback/index.d.ts +17 -0
  236. package/dist/viewers/fallback/index.js +130 -0
  237. package/dist/viewers/fallback/styles.d.ts +1 -0
  238. package/dist/viewers/fallback/styles.js +49 -0
  239. package/dist/viewers/hdf5/index.d.ts +19 -0
  240. package/dist/viewers/hdf5/index.js +170 -0
  241. package/dist/viewers/hdf5/styles.d.ts +1 -0
  242. package/dist/viewers/hdf5/styles.js +4 -0
  243. package/dist/viewers/hwp/controller.d.ts +19 -0
  244. package/dist/viewers/hwp/controller.js +22 -0
  245. package/dist/viewers/hwp/index.d.ts +39 -0
  246. package/dist/viewers/hwp/index.js +271 -0
  247. package/dist/viewers/hwp/self-loading.d.ts +2 -0
  248. package/dist/viewers/hwp/self-loading.js +6 -0
  249. package/dist/viewers/hwp/styles.d.ts +1 -0
  250. package/dist/viewers/hwp/styles.js +13 -0
  251. package/dist/viewers/image/controller.d.ts +210 -0
  252. package/dist/viewers/image/controller.js +336 -0
  253. package/dist/viewers/image/decode.d.ts +54 -0
  254. package/dist/viewers/image/decode.js +150 -0
  255. package/dist/viewers/image/index.d.ts +38 -0
  256. package/dist/viewers/image/index.js +749 -0
  257. package/dist/viewers/image/limits.d.ts +18 -0
  258. package/dist/viewers/image/limits.js +20 -0
  259. package/dist/viewers/image/styles.d.ts +1 -0
  260. package/dist/viewers/image/styles.js +119 -0
  261. package/dist/viewers/json/controller.d.ts +89 -0
  262. package/dist/viewers/json/controller.js +411 -0
  263. package/dist/viewers/json/index.d.ts +25 -0
  264. package/dist/viewers/json/index.js +503 -0
  265. package/dist/viewers/json/styles.d.ts +1 -0
  266. package/dist/viewers/json/styles.js +346 -0
  267. package/dist/viewers/json/transforms.d.ts +27 -0
  268. package/dist/viewers/json/transforms.js +253 -0
  269. package/dist/viewers/jsonl/controller.d.ts +48 -0
  270. package/dist/viewers/jsonl/controller.js +136 -0
  271. package/dist/viewers/jsonl/index.d.ts +22 -0
  272. package/dist/viewers/jsonl/index.js +184 -0
  273. package/dist/viewers/jsonl/styles.d.ts +1 -0
  274. package/dist/viewers/jsonl/styles.js +1 -0
  275. package/dist/viewers/markdown/controller.d.ts +50 -0
  276. package/dist/viewers/markdown/controller.js +62 -0
  277. package/dist/viewers/markdown/index.d.ts +64 -0
  278. package/dist/viewers/markdown/index.js +425 -0
  279. package/dist/viewers/markdown/math.d.ts +21 -0
  280. package/dist/viewers/markdown/math.js +100 -0
  281. package/dist/viewers/markdown/self-loading.d.ts +5 -0
  282. package/dist/viewers/markdown/self-loading.js +48 -0
  283. package/dist/viewers/markdown/styles.d.ts +1 -0
  284. package/dist/viewers/markdown/styles.js +11 -0
  285. package/dist/viewers/mat/index.d.ts +19 -0
  286. package/dist/viewers/mat/index.js +113 -0
  287. package/dist/viewers/mat/styles.d.ts +1 -0
  288. package/dist/viewers/mat/styles.js +3 -0
  289. package/dist/viewers/media.d.ts +9 -0
  290. package/dist/viewers/media.js +77 -0
  291. package/dist/viewers/mermaid/index.d.ts +17 -0
  292. package/dist/viewers/mermaid/index.js +6 -0
  293. package/dist/viewers/mermaid/self-loading.d.ts +2 -0
  294. package/dist/viewers/mermaid/self-loading.js +2 -0
  295. package/dist/viewers/mf4/index.d.ts +15 -0
  296. package/dist/viewers/mf4/index.js +5 -0
  297. package/dist/viewers/parquet/controller.d.ts +37 -0
  298. package/dist/viewers/parquet/controller.js +26 -0
  299. package/dist/viewers/parquet/index.d.ts +23 -0
  300. package/dist/viewers/parquet/index.js +134 -0
  301. package/dist/viewers/parquet/styles.d.ts +1 -0
  302. package/dist/viewers/parquet/styles.js +3 -0
  303. package/dist/viewers/pcap/index.d.ts +15 -0
  304. package/dist/viewers/pcap/index.js +5 -0
  305. package/dist/viewers/pcapng/index.d.ts +15 -0
  306. package/dist/viewers/pcapng/index.js +5 -0
  307. package/dist/viewers/pdf/controller.d.ts +120 -0
  308. package/dist/viewers/pdf/controller.js +191 -0
  309. package/dist/viewers/pdf/editing.d.ts +26 -0
  310. package/dist/viewers/pdf/editing.js +265 -0
  311. package/dist/viewers/pdf/index.d.ts +88 -0
  312. package/dist/viewers/pdf/index.js +1400 -0
  313. package/dist/viewers/pdf/self-loading.d.ts +6 -0
  314. package/dist/viewers/pdf/self-loading.js +14 -0
  315. package/dist/viewers/pdf/styles.d.ts +1 -0
  316. package/dist/viewers/pdf/styles.js +555 -0
  317. package/dist/viewers/plantuml/index.d.ts +17 -0
  318. package/dist/viewers/plantuml/index.js +6 -0
  319. package/dist/viewers/plantuml/self-loading.d.ts +2 -0
  320. package/dist/viewers/plantuml/self-loading.js +2 -0
  321. package/dist/viewers/ppt/chart.d.ts +8 -0
  322. package/dist/viewers/ppt/chart.js +83 -0
  323. package/dist/viewers/ppt/controller.d.ts +34 -0
  324. package/dist/viewers/ppt/controller.js +27 -0
  325. package/dist/viewers/ppt/index.d.ts +33 -0
  326. package/dist/viewers/ppt/index.js +101 -0
  327. package/dist/viewers/ppt/render.d.ts +5 -0
  328. package/dist/viewers/ppt/render.js +144 -0
  329. package/dist/viewers/ppt/self-loading.d.ts +9 -0
  330. package/dist/viewers/ppt/self-loading.js +4 -0
  331. package/dist/viewers/ppt/styles.d.ts +1 -0
  332. package/dist/viewers/ppt/styles.js +1 -0
  333. package/dist/viewers/proto/index.d.ts +17 -0
  334. package/dist/viewers/proto/index.js +170 -0
  335. package/dist/viewers/proto/styles.d.ts +1 -0
  336. package/dist/viewers/proto/styles.js +2 -0
  337. package/dist/viewers/psd/index.d.ts +27 -0
  338. package/dist/viewers/psd/index.js +39 -0
  339. package/dist/viewers/psd/self-loading.d.ts +10 -0
  340. package/dist/viewers/psd/self-loading.js +20 -0
  341. package/dist/viewers/psd/styles.d.ts +1 -0
  342. package/dist/viewers/psd/styles.js +1 -0
  343. package/dist/viewers/reqif/index.d.ts +15 -0
  344. package/dist/viewers/reqif/index.js +5 -0
  345. package/dist/viewers/shapefile/index.d.ts +35 -0
  346. package/dist/viewers/shapefile/index.js +336 -0
  347. package/dist/viewers/shapefile/self-loading.d.ts +7 -0
  348. package/dist/viewers/shapefile/self-loading.js +32 -0
  349. package/dist/viewers/shapefile/styles.d.ts +1 -0
  350. package/dist/viewers/shapefile/styles.js +28 -0
  351. package/dist/viewers/stp/index.d.ts +15 -0
  352. package/dist/viewers/stp/index.js +5 -0
  353. package/dist/viewers/structured-styles.d.ts +1 -0
  354. package/dist/viewers/structured-styles.js +1 -0
  355. package/dist/viewers/structured.d.ts +36 -0
  356. package/dist/viewers/structured.js +138 -0
  357. package/dist/viewers/toml/controller.d.ts +38 -0
  358. package/dist/viewers/toml/controller.js +61 -0
  359. package/dist/viewers/toml/index.d.ts +18 -0
  360. package/dist/viewers/toml/index.js +34 -0
  361. package/dist/viewers/types.d.ts +36 -0
  362. package/dist/viewers/types.js +9 -0
  363. package/dist/viewers/video/controller.d.ts +29 -0
  364. package/dist/viewers/video/controller.js +54 -0
  365. package/dist/viewers/video/index.d.ts +18 -0
  366. package/dist/viewers/video/index.js +305 -0
  367. package/dist/viewers/video/styles.d.ts +1 -0
  368. package/dist/viewers/video/styles.js +79 -0
  369. package/dist/viewers/word/controller.d.ts +19 -0
  370. package/dist/viewers/word/controller.js +22 -0
  371. package/dist/viewers/word/docx-preprocess.d.ts +63 -0
  372. package/dist/viewers/word/docx-preprocess.js +136 -0
  373. package/dist/viewers/word/index.d.ts +32 -0
  374. package/dist/viewers/word/index.js +187 -0
  375. package/dist/viewers/word/normalize-docx.d.ts +2 -0
  376. package/dist/viewers/word/normalize-docx.js +50 -0
  377. package/dist/viewers/word/paginate.d.ts +1 -0
  378. package/dist/viewers/word/paginate.js +1035 -0
  379. package/dist/viewers/word/self-loading.d.ts +2 -0
  380. package/dist/viewers/word/self-loading.js +7 -0
  381. package/dist/viewers/word/styles.d.ts +1 -0
  382. package/dist/viewers/word/styles.js +686 -0
  383. package/dist/viewers/yaml/controller.d.ts +42 -0
  384. package/dist/viewers/yaml/controller.js +41 -0
  385. package/dist/viewers/yaml/index.d.ts +21 -0
  386. package/dist/viewers/yaml/index.js +20 -0
  387. package/package.json +375 -0
@@ -0,0 +1,846 @@
1
+ // CSV viewer — DOM renderer consuming CsvController (DESIGN.md §3-②).
2
+ //
3
+ // CSP rules: no eval, no inline handlers, no innerHTML — all rendering goes
4
+ // through createElement/textContent. Mount is async and renders inside a
5
+ // shadow root by default (§6 reverse-contamination contract).
6
+ //
7
+ // Optional services: `clipboard`. Degraded mode (core contract, ADR 24):
8
+ // without it the copy buttons render disabled with an explanatory tooltip —
9
+ // never hidden.
10
+ import { MountAbortedError, VIEWER_ROOT_CLASS } from '../types.js';
11
+ import { formatPercent, formatStatNumber, DELIMITER_PRIORITY } from '../../parsers/csv/index.js';
12
+ import { utf8ByteLength } from '../../parsers/types.js';
13
+ import { createCsvController, CSV_MIN_SEARCH_LENGTH } from './controller.js';
14
+ import { csvViewerCss } from './styles.js';
15
+ export { createCsvController, DEFAULT_PAGE_SIZE } from './controller.js';
16
+ export { csvViewerCss } from './styles.js';
17
+ /** Viewer metadata — single source for the registry codegen (DESIGN.md §7). */
18
+ export const CSV_VIEWER_META = {
19
+ id: 'csv',
20
+ displayNameKey: 'csv.tableView',
21
+ extensions: ['csv', 'tsv'],
22
+ priority: 10,
23
+ requiredServices: [],
24
+ optionalServices: ['clipboard', 'save', 'writeback'],
25
+ inputOwnership: 'borrows'
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 function exportFileName(sourceName, delimiter) {
30
+ const stem = sourceName.replace(/\.[^./\\]+$/, '') || sourceName;
31
+ return `${stem}-export${delimiter === '\t' ? '.tsv' : '.csv'}`;
32
+ }
33
+ /** Suggested filename for Save As. The host's FileSaveService presents the
34
+ * destination picker; this only supplies its initial filename. */
35
+ export function saveAsFileName(sourceName, delimiter) {
36
+ const stem = sourceName.replace(/\.[^./\\]+$/, '') || sourceName;
37
+ return `${stem}${delimiter === '\t' ? '.tsv' : '.csv'}`;
38
+ }
39
+ /** Clipboard copy guard (docs/viewers/csv.md §6): larger payloads are refused. */
40
+ export const COPY_PAYLOAD_LIMIT_BYTES = 1024 * 1024;
41
+ /** Cells at least this long (or containing a newline) get a full-content tooltip. */
42
+ export const CELL_TOOLTIP_MIN_CHARS = 50;
43
+ const MIN_COLUMN_WIDTH = 40;
44
+ const FALLBACK_COLUMN_WIDTH = 120;
45
+ /** Header single-click sort is deferred briefly so a double-click can win and
46
+ * open the rename editor instead (docs §6). */
47
+ export const SORT_CLICK_DELAY_MS = 250;
48
+ /** Raw-view typing settles this long before the text is reparsed into the
49
+ * document (docs §6 — the edit only marks dirty; saving stays explicit). */
50
+ export const RAW_EDIT_DEBOUNCE_MS = 400;
51
+ const DELIMITER_LABEL_KEYS = {
52
+ ',': 'csv.delimiter.comma',
53
+ ';': 'csv.delimiter.semicolon',
54
+ '\t': 'csv.delimiter.tab',
55
+ '|': 'csv.delimiter.pipe'
56
+ };
57
+ export async function mountCsvViewer(input, container, ctx, options = {}) {
58
+ if (options.signal?.aborted)
59
+ throw new MountAbortedError();
60
+ const controllerOptions = {
61
+ fileName: input.fileName
62
+ };
63
+ if (options.limits !== undefined)
64
+ controllerOptions.limits = options.limits;
65
+ if (options.pageSize !== undefined)
66
+ controllerOptions.pageSize = options.pageSize;
67
+ const controller = createCsvController(input.data, controllerOptions);
68
+ const isolation = options.styleIsolation ?? 'shadow';
69
+ let root;
70
+ if (isolation === 'shadow' && typeof container.attachShadow === 'function') {
71
+ root = container.shadowRoot ?? container.attachShadow({ mode: 'open' });
72
+ const style = document.createElement('style');
73
+ style.textContent = csvViewerCss;
74
+ root.appendChild(style);
75
+ }
76
+ else {
77
+ container.classList.add(VIEWER_ROOT_CLASS, 'omni-viewer--csv');
78
+ root = container;
79
+ }
80
+ const t = ctx.i18n.t.bind(ctx.i18n);
81
+ const disposers = [];
82
+ let toastTimer = null;
83
+ /** Session-scoped column widths (px), per data column; null entry = auto.
84
+ * Non-null array means the table runs in fixed layout (docs §6). */
85
+ let columnWidths = null;
86
+ let activeResizeCleanup = null;
87
+ let pendingSortTimer = null;
88
+ let contextMenuCleanup = null;
89
+ let rawTextarea = null;
90
+ let rawEditTimer = null;
91
+ const el = (tag, className, text) => {
92
+ const node = document.createElement(tag);
93
+ if (className)
94
+ node.className = className;
95
+ if (text !== undefined)
96
+ node.textContent = text;
97
+ return node;
98
+ };
99
+ const on = (target, type, handler) => {
100
+ target.addEventListener(type, handler);
101
+ disposers.push(() => target.removeEventListener(type, handler));
102
+ };
103
+ // --- static frame -------------------------------------------------------
104
+ const frame = el('div', 'omni-csv');
105
+ frame.style.position = 'relative';
106
+ const toolbar = el('div', 'omni-csv__toolbar');
107
+ const delimiterLabel = el('label', undefined, t('csv.delimiter'));
108
+ const delimiterSelect = el('select');
109
+ // "Auto detect" re-runs detection; the select value always shows the
110
+ // delimiter currently in effect, so choosing auto snaps back to the
111
+ // detected one (docs/viewers/csv.md §2).
112
+ const autoOpt = el('option', undefined, t('csv.delimiter.auto'));
113
+ autoOpt.value = 'auto';
114
+ delimiterSelect.appendChild(autoOpt);
115
+ for (const d of DELIMITER_PRIORITY) {
116
+ const opt = el('option', undefined, t(DELIMITER_LABEL_KEYS[d]));
117
+ opt.value = d;
118
+ delimiterSelect.appendChild(opt);
119
+ }
120
+ delimiterLabel.appendChild(delimiterSelect);
121
+ on(delimiterSelect, 'change', () => {
122
+ const value = delimiterSelect.value;
123
+ controller.dispatch({
124
+ type: 'set-delimiter',
125
+ delimiter: value === 'auto' ? 'auto' : value
126
+ });
127
+ });
128
+ const searchInput = el('input', 'omni-csv__search');
129
+ searchInput.type = 'search';
130
+ searchInput.minLength = CSV_MIN_SEARCH_LENGTH;
131
+ searchInput.placeholder = t('csv.search');
132
+ searchInput.setAttribute('aria-label', t('csv.search'));
133
+ on(searchInput, 'input', () => {
134
+ controller.dispatch({ type: 'set-search', search: searchInput.value });
135
+ });
136
+ const rowNumToggle = el('button', undefined, t('csv.rowNumbers'));
137
+ rowNumToggle.type = 'button';
138
+ on(rowNumToggle, 'click', () => controller.dispatch({ type: 'toggle-row-numbers' }));
139
+ const pageSizeLabel = el('label', undefined, t('csv.pageSize'));
140
+ const pageSizeSelect = el('select');
141
+ for (const size of [100, 200, 500, 1000]) {
142
+ const opt = el('option', undefined, String(size));
143
+ opt.value = String(size);
144
+ pageSizeSelect.appendChild(opt);
145
+ }
146
+ pageSizeLabel.appendChild(pageSizeSelect);
147
+ on(pageSizeSelect, 'change', () => {
148
+ controller.dispatch({
149
+ type: 'set-page-size',
150
+ pageSize: Number(pageSizeSelect.value)
151
+ });
152
+ });
153
+ const viewToggle = el('button', undefined, t('csv.rawView'));
154
+ viewToggle.type = 'button';
155
+ on(viewToggle, 'click', () => {
156
+ controller.dispatch({
157
+ type: 'set-view-mode',
158
+ mode: controller.state.viewMode === 'table' ? 'raw' : 'table'
159
+ });
160
+ });
161
+ const statsToggle = el('button', undefined, t('csv.statistics'));
162
+ statsToggle.type = 'button';
163
+ on(statsToggle, 'click', () => controller.dispatch({ type: 'toggle-stats' }));
164
+ const copyTsvBtn = el('button', undefined, t('csv.copyTsv'));
165
+ copyTsvBtn.type = 'button';
166
+ const copyJsonBtn = el('button', undefined, t('csv.copyJson'));
167
+ copyJsonBtn.type = 'button';
168
+ const clipboard = ctx.clipboard;
169
+ if (clipboard) {
170
+ on(copyTsvBtn, 'click', () => void copy(clipboard, controller.toTsv()));
171
+ on(copyJsonBtn, 'click', () => void copy(clipboard, controller.toJson()));
172
+ }
173
+ else {
174
+ // Degraded mode: disabled with reason, identical on every platform.
175
+ for (const btn of [copyTsvBtn, copyJsonBtn]) {
176
+ btn.disabled = true;
177
+ btn.title = t('common.noClipboard');
178
+ }
179
+ }
180
+ const undoBtn = el('button', undefined, '↶');
181
+ undoBtn.type = 'button';
182
+ undoBtn.setAttribute('aria-label', t('csv.undo'));
183
+ on(undoBtn, 'click', () => controller.dispatch({ type: 'undo' }));
184
+ const redoBtn = el('button', undefined, '↷');
185
+ redoBtn.type = 'button';
186
+ redoBtn.setAttribute('aria-label', t('csv.redo'));
187
+ on(redoBtn, 'click', () => controller.dispatch({ type: 'redo' }));
188
+ const addRowBtn = el('button', undefined, t('csv.addRow'));
189
+ addRowBtn.type = 'button';
190
+ on(addRowBtn, 'click', () => controller.dispatch({ type: 'insert-row', afterRowId: 'end' }));
191
+ const addColumnBtn = el('button', undefined, t('csv.addColumn'));
192
+ addColumnBtn.type = 'button';
193
+ on(addColumnBtn, 'click', () => controller.dispatch({
194
+ type: 'insert-column',
195
+ columnIndex: controller.state.columnCount
196
+ }));
197
+ const saveBtn = el('button', undefined, t('csv.save'));
198
+ saveBtn.type = 'button';
199
+ const writeback = ctx.writeback;
200
+ if (writeback) {
201
+ on(saveBtn, 'click', () => void saveDocument(writeback));
202
+ }
203
+ else {
204
+ // Degraded mode (docs §6 결정 1): editing works, saving points to export.
205
+ saveBtn.disabled = true;
206
+ saveBtn.title = t('common.noWriteback');
207
+ }
208
+ const saveAsBtn = el('button', undefined, t('csv.saveAs'));
209
+ saveAsBtn.type = 'button';
210
+ const save = ctx.save;
211
+ if (save) {
212
+ on(saveAsBtn, 'click', () => void saveAsDocument(save));
213
+ }
214
+ else {
215
+ saveAsBtn.disabled = true;
216
+ saveAsBtn.title = t('common.noFileSave');
217
+ }
218
+ const dirtyBadge = el('span', 'omni-csv__dirty');
219
+ async function saveDocument(service) {
220
+ try {
221
+ // Save covers the whole document in original order — never the
222
+ // filtered/sorted display result (docs §6).
223
+ const bytes = new TextEncoder().encode(controller.toDocumentCsv());
224
+ await service.write(bytes);
225
+ controller.markSaved();
226
+ showToast(t('common.savedToOriginal'));
227
+ }
228
+ catch (error) {
229
+ ctx.logger.log('error', `csv save failed: ${String(error)}`);
230
+ showToast(t('common.saveFailed'));
231
+ }
232
+ }
233
+ /** Save As delegates the destination picker to the host adapter. Unlike
234
+ * export, it always writes the whole edited document in document order. */
235
+ async function saveAsDocument(service) {
236
+ const state = controller.state;
237
+ const name = saveAsFileName(input.fileName, state.delimiter);
238
+ const mime = state.delimiter === '\t' ? 'text/tab-separated-values' : 'text/csv';
239
+ try {
240
+ const bytes = new TextEncoder().encode(controller.toDocumentCsv());
241
+ await service.saveFile(name, bytes, mime);
242
+ controller.markSaved();
243
+ showToast(t('common.saved', { name }));
244
+ }
245
+ catch (error) {
246
+ ctx.logger.log('error', `csv save-as failed: ${String(error)}`);
247
+ showToast(t('common.saveFailed'));
248
+ }
249
+ }
250
+ // Undo/redo shortcuts while editing (shadow-root local, disposed with us).
251
+ on(frame, 'keydown', (e) => {
252
+ if (!(e.ctrlKey || e.metaKey) || e.key.toLowerCase() !== 'z')
253
+ return;
254
+ e.preventDefault();
255
+ controller.dispatch({ type: e.shiftKey ? 'redo' : 'undo' });
256
+ });
257
+ const exportBtn = el('button', undefined, t('csv.exportFile'));
258
+ exportBtn.type = 'button';
259
+ exportBtn.title = t('csv.exportFile.hint');
260
+ exportBtn.setAttribute('aria-label', t('csv.exportFile.hint'));
261
+ if (save) {
262
+ on(exportBtn, 'click', () => void exportFile(save));
263
+ }
264
+ else {
265
+ // Degraded mode (ADR 24): disabled with reason, never hidden.
266
+ exportBtn.disabled = true;
267
+ exportBtn.title = t('common.noFileSave');
268
+ }
269
+ async function exportFile(service) {
270
+ const state = controller.state;
271
+ const name = exportFileName(input.fileName, state.delimiter);
272
+ const mime = state.delimiter === '\t' ? 'text/tab-separated-values' : 'text/csv';
273
+ try {
274
+ const bytes = new TextEncoder().encode(controller.toCsv());
275
+ await service.saveFile(name, bytes, mime);
276
+ showToast(t('common.saved', { name }));
277
+ }
278
+ catch (error) {
279
+ ctx.logger.log('error', `csv export failed: ${String(error)}`);
280
+ }
281
+ }
282
+ async function copy(service, payload) {
283
+ const payloadBytes = utf8ByteLength(payload);
284
+ if (payloadBytes > COPY_PAYLOAD_LIMIT_BYTES) {
285
+ showToast(t('common.copyTooLarge', { size: payloadBytes }));
286
+ return;
287
+ }
288
+ try {
289
+ await service.writeText(payload);
290
+ showToast(t('common.copied'));
291
+ }
292
+ catch (error) {
293
+ ctx.logger.log('error', `csv copy failed: ${String(error)}`);
294
+ }
295
+ }
296
+ const meta = el('span', 'omni-csv__meta');
297
+ toolbar.append(delimiterLabel, searchInput, viewToggle, statsToggle, rowNumToggle, pageSizeLabel, undoBtn, redoBtn, addRowBtn, addColumnBtn, saveBtn, saveAsBtn, copyTsvBtn, copyJsonBtn, exportBtn, el('span', 'omni-csv__spacer'), dirtyBadge, meta);
298
+ const diagnosticsBar = el('div', 'omni-csv__diagnostics');
299
+ const body = el('div', 'omni-csv__body');
300
+ const footer = el('div', 'omni-csv__footer');
301
+ const prevBtn = el('button', undefined, '‹');
302
+ prevBtn.type = 'button';
303
+ prevBtn.setAttribute('aria-label', t('common.prevPage'));
304
+ const nextBtn = el('button', undefined, '›');
305
+ nextBtn.type = 'button';
306
+ nextBtn.setAttribute('aria-label', t('common.nextPage'));
307
+ const pageInfo = el('span', 'omni-csv__meta');
308
+ on(prevBtn, 'click', () => controller.dispatch({ type: 'prev-page' }));
309
+ on(nextBtn, 'click', () => controller.dispatch({ type: 'next-page' }));
310
+ footer.append(prevBtn, pageInfo, nextBtn);
311
+ frame.append(toolbar, diagnosticsBar, body, footer);
312
+ root.appendChild(frame);
313
+ function showToast(message) {
314
+ const existing = frame.querySelector('.omni-csv__toast');
315
+ existing?.remove();
316
+ const toast = el('div', 'omni-csv__toast', message);
317
+ frame.appendChild(toast);
318
+ if (toastTimer)
319
+ clearTimeout(toastTimer);
320
+ toastTimer = setTimeout(() => toast.remove(), 1500);
321
+ }
322
+ function closeContextMenu() {
323
+ contextMenuCleanup?.();
324
+ contextMenuCleanup = null;
325
+ }
326
+ /** Right-click cell menu: row insert/delete on body rows, column
327
+ * insert/delete wherever a data column was hit — headers included
328
+ * (docs §6). Either half is omitted when its target is absent
329
+ * (header cell → no row ops, row-number cell → no column ops). */
330
+ function openCellContextMenu(event, rowId, columnIndex) {
331
+ closeContextMenu();
332
+ const menu = el('div', 'omni-csv__menu');
333
+ menu.setAttribute('role', 'menu');
334
+ const item = (label, action) => {
335
+ const btn = el('button', undefined, label);
336
+ btn.type = 'button';
337
+ btn.setAttribute('role', 'menuitem');
338
+ btn.addEventListener('click', () => {
339
+ closeContextMenu();
340
+ action();
341
+ });
342
+ menu.appendChild(btn);
343
+ };
344
+ if (rowId !== null) {
345
+ item(t('csv.insertRowBelow'), () => controller.dispatch({ type: 'insert-row', afterRowId: rowId }));
346
+ item(t('csv.deleteRow'), () => controller.dispatch({ type: 'delete-row', rowId }));
347
+ }
348
+ if (columnIndex !== null) {
349
+ const atColumn = columnIndex;
350
+ item(t('csv.insertColumnLeft'), () => controller.dispatch({ type: 'insert-column', columnIndex: atColumn }));
351
+ item(t('csv.insertColumnRight'), () => controller.dispatch({ type: 'insert-column', columnIndex: atColumn + 1 }));
352
+ // The controller refuses to delete the last column; don't offer it.
353
+ if (controller.state.columnCount > 1) {
354
+ item(t('csv.deleteColumn'), () => controller.dispatch({ type: 'delete-column', columnIndex: atColumn }));
355
+ }
356
+ }
357
+ if (menu.childElementCount === 0)
358
+ return;
359
+ const frameRect = frame.getBoundingClientRect();
360
+ menu.style.left = `${event.clientX - frameRect.left}px`;
361
+ menu.style.top = `${event.clientY - frameRect.top}px`;
362
+ frame.appendChild(menu);
363
+ const onOutside = (e) => {
364
+ // Events crossing the shadow boundary have their `target` retargeted
365
+ // to the host, so `menu.contains(e.target)` is unreliable here.
366
+ // `composedPath()` keeps the real nodes traversed through the shadow.
367
+ if (e.composedPath().includes(menu))
368
+ return;
369
+ closeContextMenu();
370
+ };
371
+ const onKey = (e) => {
372
+ if (e.key === 'Escape')
373
+ closeContextMenu();
374
+ };
375
+ window.addEventListener('pointerdown', onOutside, true);
376
+ window.addEventListener('keydown', onKey, true);
377
+ contextMenuCleanup = () => {
378
+ window.removeEventListener('pointerdown', onOutside, true);
379
+ window.removeEventListener('keydown', onKey, true);
380
+ menu.remove();
381
+ };
382
+ }
383
+ const focusedInRoot = () => root instanceof ShadowRoot ? root.activeElement : document.activeElement;
384
+ /** Commit pending raw-view typing into the document (debounce flush). */
385
+ function commitRawEdit() {
386
+ if (rawEditTimer) {
387
+ clearTimeout(rawEditTimer);
388
+ rawEditTimer = null;
389
+ }
390
+ if (rawTextarea && rawTextarea.parentNode === body) {
391
+ controller.dispatch({ type: 'replace-document', text: rawTextarea.value });
392
+ }
393
+ }
394
+ function ensureRawTextarea() {
395
+ if (rawTextarea)
396
+ return rawTextarea;
397
+ const ta = el('textarea', 'omni-csv__raw omni-csv__raw-edit');
398
+ ta.spellcheck = false;
399
+ ta.setAttribute('aria-label', t('csv.rawView'));
400
+ // Keep typing out of the sort/undo shortcuts; the browser's own
401
+ // undo/redo applies inside the textarea.
402
+ ta.addEventListener('keydown', (e) => e.stopPropagation());
403
+ ta.addEventListener('input', () => {
404
+ if (rawEditTimer)
405
+ clearTimeout(rawEditTimer);
406
+ rawEditTimer = setTimeout(commitRawEdit, RAW_EDIT_DEBOUNCE_MS);
407
+ });
408
+ // Leaving the editor (e.g. clicking Save or Table) commits first, so
409
+ // the action that took the focus sees the edited document.
410
+ ta.addEventListener('blur', () => {
411
+ if (rawEditTimer)
412
+ commitRawEdit();
413
+ });
414
+ rawTextarea = ta;
415
+ return ta;
416
+ }
417
+ /** Swap a cell's content for a text input; Enter/blur commits, Esc cancels. */
418
+ function startInlineEdit(cell, current, commit) {
419
+ const inputEl = document.createElement('input');
420
+ inputEl.type = 'text';
421
+ inputEl.className = 'omni-csv__cell-input';
422
+ inputEl.value = current;
423
+ let done = false;
424
+ const finish = (commitValue) => {
425
+ if (done)
426
+ return;
427
+ done = true;
428
+ if (commitValue && inputEl.value !== current) {
429
+ commit(inputEl.value); // dispatch triggers a re-render
430
+ }
431
+ else {
432
+ render(controller.state); // restore the original cell
433
+ }
434
+ };
435
+ inputEl.addEventListener('keydown', (e) => {
436
+ // Keep typing out of the sort/undo shortcuts.
437
+ e.stopPropagation();
438
+ if (e.key === 'Enter') {
439
+ e.preventDefault();
440
+ finish(true);
441
+ }
442
+ else if (e.key === 'Escape') {
443
+ e.preventDefault();
444
+ finish(false);
445
+ }
446
+ });
447
+ inputEl.addEventListener('blur', () => finish(true));
448
+ // Editing input must not bubble a click back into sort/edit handlers.
449
+ inputEl.addEventListener('click', (e) => e.stopPropagation());
450
+ cell.replaceChildren(inputEl);
451
+ inputEl.focus();
452
+ if (typeof inputEl.select === 'function')
453
+ inputEl.select();
454
+ }
455
+ // --- state-driven rendering --------------------------------------------
456
+ function render(state) {
457
+ delimiterSelect.value = state.delimiter;
458
+ // Delimiter switching reparses and would discard edits (docs §6).
459
+ delimiterSelect.disabled = state.dirty;
460
+ if (searchInput.value !== state.search)
461
+ searchInput.value = state.search;
462
+ pageSizeSelect.value = String(state.pageSize);
463
+ rowNumToggle.setAttribute('aria-pressed', String(state.showRowNumbers));
464
+ // Editing controls surface once editing has started (docs §6) — pure
465
+ // readers never see them. "Add row" also shows for empty documents.
466
+ // "Add column" is the fallback while there is no data column target
467
+ // from which the column context menu could be opened.
468
+ const editingActive = state.dirty || state.canUndo || state.canRedo;
469
+ for (const btn of [undoBtn, redoBtn, saveBtn]) {
470
+ btn.style.display = editingActive ? '' : 'none';
471
+ }
472
+ addRowBtn.style.display =
473
+ editingActive || (state.rowCount === 0 && state.status === 'ok') ? '' : 'none';
474
+ addColumnBtn.style.display =
475
+ state.columnCount === 0 && state.status === 'ok' ? '' : 'none';
476
+ undoBtn.disabled = !state.canUndo;
477
+ redoBtn.disabled = !state.canRedo;
478
+ if (ctx.writeback)
479
+ saveBtn.disabled = !state.dirty;
480
+ dirtyBadge.textContent = state.dirty ? t('common.unsaved') : '';
481
+ viewToggle.textContent =
482
+ state.viewMode === 'table' ? t('csv.rawView') : t('csv.tableView');
483
+ viewToggle.setAttribute('aria-pressed', String(state.viewMode === 'raw'));
484
+ statsToggle.setAttribute('aria-pressed', String(state.statsVisible));
485
+ const hasActiveSearch = state.search.length >= CSV_MIN_SEARCH_LENGTH;
486
+ const rowSummary = hasActiveSearch
487
+ ? t('common.rowsMatched', {
488
+ matched: state.matchedRowCount,
489
+ total: state.rowCount
490
+ })
491
+ : t('common.rows', { count: state.rowCount });
492
+ meta.textContent = `${rowSummary} · ${t('common.columns', {
493
+ count: state.columnCount
494
+ })}`;
495
+ diagnosticsBar.replaceChildren();
496
+ const hasNotices = state.diagnostics.length > 0 || state.failure !== null;
497
+ diagnosticsBar.style.display = hasNotices ? '' : 'none';
498
+ if (state.failure) {
499
+ // Failed parses must surface their reason — never a bare empty
500
+ // table (docs/viewers/csv.md §2).
501
+ diagnosticsBar.appendChild(el('div', 'omni-csv__diag-error', t(state.failure.messageKey, state.failure.args)));
502
+ }
503
+ for (const diag of state.diagnostics) {
504
+ diagnosticsBar.appendChild(el('div', undefined, t(diag.messageKey, diag.args)));
505
+ }
506
+ const rawEditable = state.viewMode === 'raw' && state.status === 'ok';
507
+ if (rawEditable && rawTextarea && rawTextarea.parentNode === body) {
508
+ // The raw editor is already mounted — this render came from its own
509
+ // debounced commit or a toolbar action. Keep the node so focus and
510
+ // caret survive; refresh its content only when the change came from
511
+ // elsewhere (undo/redo, table edits), never mid-typing.
512
+ if (focusedInRoot() !== rawTextarea) {
513
+ rawTextarea.value = controller.toRawText();
514
+ }
515
+ }
516
+ else {
517
+ body.replaceChildren();
518
+ if (state.viewMode === 'raw') {
519
+ if (rawEditable) {
520
+ const ta = ensureRawTextarea();
521
+ ta.value = controller.toRawText();
522
+ body.appendChild(ta);
523
+ }
524
+ else {
525
+ // partial/failed documents stay a read-only source view
526
+ // (docs §6 — a truncated document must not be editable).
527
+ body.appendChild(el('pre', 'omni-csv__raw', controller.rawText));
528
+ }
529
+ }
530
+ else if (state.columnCount === 0) {
531
+ body.appendChild(el('div', 'omni-csv__empty', t('csv.empty')));
532
+ }
533
+ else if (state.rowCount > 0 &&
534
+ hasActiveSearch &&
535
+ state.matchedRowCount === 0) {
536
+ body.appendChild(el('div', 'omni-csv__empty', t('csv.noMatches')));
537
+ }
538
+ else {
539
+ const table = renderTable(state);
540
+ body.appendChild(table);
541
+ // A header-only document is still editable: keep the headers visible
542
+ // so rename and column context-menu actions remain reachable.
543
+ if (state.rowCount === 0) {
544
+ body.appendChild(el('div', 'omni-csv__empty', t('csv.empty')));
545
+ }
546
+ if (state.statsVisible) {
547
+ // The stats row sticks right below the header row; its offset
548
+ // is the measured header height (0 in JSDOM — harmless).
549
+ const headRow = table.querySelector('thead tr');
550
+ const headHeight = headRow?.offsetHeight ?? 0;
551
+ table.style.setProperty('--omni-csv-head-h', `${headHeight}px`);
552
+ }
553
+ }
554
+ }
555
+ const showPager = state.pageCount > 1 && state.viewMode === 'table';
556
+ footer.style.display = showPager ? '' : 'none';
557
+ if (showPager) {
558
+ pageInfo.textContent = t('common.page', {
559
+ page: state.page + 1,
560
+ pages: state.pageCount
561
+ });
562
+ prevBtn.disabled = state.page === 0;
563
+ nextBtn.disabled = state.page >= state.pageCount - 1;
564
+ }
565
+ }
566
+ function renderTable(state) {
567
+ const table = el('table', 'omni-csv__table');
568
+ // Session column widths survive re-renders; a column-count change
569
+ // (delimiter switch) invalidates them.
570
+ if (columnWidths && columnWidths.length !== state.columnCount) {
571
+ columnWidths = null;
572
+ }
573
+ const editable = state.status === 'ok';
574
+ const colgroup = el('colgroup');
575
+ if (state.showRowNumbers)
576
+ colgroup.appendChild(el('col'));
577
+ const dataCols = [];
578
+ for (let c = 0; c < state.columnCount; c++) {
579
+ const col = el('col');
580
+ const width = columnWidths?.[c];
581
+ if (width != null)
582
+ col.style.width = `${width}px`;
583
+ dataCols.push(col);
584
+ colgroup.appendChild(col);
585
+ }
586
+ table.appendChild(colgroup);
587
+ if (columnWidths)
588
+ applyFixedLayout();
589
+ const dataThs = [];
590
+ /** Keep the table exactly as wide as its frozen data columns. The
591
+ * resize interaction then transfers width between two adjacent
592
+ * columns while the total table width stays constant. */
593
+ function applyFixedLayout() {
594
+ if (!columnWidths)
595
+ return;
596
+ const width = columnWidths.reduce((total, columnWidth) => total + (columnWidth ?? 0), 0);
597
+ table.style.tableLayout = 'fixed';
598
+ table.style.width = `${width}px`;
599
+ table.style.minWidth = '0';
600
+ }
601
+ function freezeWidths() {
602
+ if (columnWidths)
603
+ return;
604
+ columnWidths = dataThs.map((th) => Math.max(th.offsetWidth || FALLBACK_COLUMN_WIDTH, MIN_COLUMN_WIDTH));
605
+ columnWidths.forEach((w, i) => {
606
+ const col = dataCols[i];
607
+ if (col && w != null)
608
+ col.style.width = `${w}px`;
609
+ });
610
+ applyFixedLayout();
611
+ }
612
+ function startResize(event, columnIndex) {
613
+ event.preventDefault();
614
+ event.stopPropagation();
615
+ activeResizeCleanup?.();
616
+ freezeWidths();
617
+ const adjacentColumnIndex = columnIndex + 1;
618
+ // A handle sits on the right edge of its header, so it divides
619
+ // this column and the next one. There is no adjacent column on
620
+ // the far right edge to trade width with.
621
+ if (!columnWidths || adjacentColumnIndex >= columnWidths.length)
622
+ return;
623
+ const frozenWidths = columnWidths;
624
+ const startX = event.clientX;
625
+ const startWidth = frozenWidths[columnIndex] ?? FALLBACK_COLUMN_WIDTH;
626
+ const startAdjacentWidth = frozenWidths[adjacentColumnIndex] ?? FALLBACK_COLUMN_WIDTH;
627
+ const onMove = (ev) => {
628
+ const requestedDelta = Math.round(ev.clientX - startX);
629
+ const delta = Math.min(Math.max(requestedDelta, MIN_COLUMN_WIDTH - startWidth), startAdjacentWidth - MIN_COLUMN_WIDTH);
630
+ frozenWidths[columnIndex] = startWidth + delta;
631
+ frozenWidths[adjacentColumnIndex] = startAdjacentWidth - delta;
632
+ const col = dataCols[columnIndex];
633
+ const adjacentCol = dataCols[adjacentColumnIndex];
634
+ if (col)
635
+ col.style.width = `${frozenWidths[columnIndex]}px`;
636
+ if (adjacentCol) {
637
+ adjacentCol.style.width = `${frozenWidths[adjacentColumnIndex]}px`;
638
+ }
639
+ };
640
+ const onUp = () => activeResizeCleanup?.();
641
+ window.addEventListener('pointermove', onMove);
642
+ window.addEventListener('pointerup', onUp);
643
+ activeResizeCleanup = () => {
644
+ window.removeEventListener('pointermove', onMove);
645
+ window.removeEventListener('pointerup', onUp);
646
+ activeResizeCleanup = null;
647
+ };
648
+ }
649
+ const thead = el('thead');
650
+ const headRow = el('tr');
651
+ if (state.showRowNumbers) {
652
+ // Current document row numbers (1-based) — not sortable.
653
+ const numTh = el('th', 'omni-csv__rownum', '#');
654
+ numTh.setAttribute('aria-label', t('csv.rowNumbers'));
655
+ headRow.appendChild(numTh);
656
+ }
657
+ state.headers.forEach((header, columnIndex) => {
658
+ const th = el('th');
659
+ th.appendChild(el('span', undefined, header));
660
+ const hint = editable ? t('csv.header.hint') : t('csv.sort.hint');
661
+ th.title =
662
+ header.length > CELL_TOOLTIP_MIN_CHARS ? `${header}\n${hint}` : hint;
663
+ th.setAttribute('role', 'columnheader');
664
+ dataThs.push(th);
665
+ const handle = el('span', 'omni-csv__resize-handle');
666
+ handle.title = t('csv.resizeColumn');
667
+ handle.addEventListener('pointerdown', (e) => startResize(e, columnIndex));
668
+ // A click that follows a resize drag must not toggle the sort.
669
+ handle.addEventListener('click', (e) => e.stopPropagation());
670
+ handle.addEventListener('dblclick', (e) => {
671
+ e.stopPropagation();
672
+ if (!columnWidths)
673
+ return;
674
+ columnWidths[columnIndex] = null;
675
+ if (columnWidths.every((w) => w == null))
676
+ columnWidths = null;
677
+ render(controller.state);
678
+ });
679
+ th.appendChild(handle);
680
+ if (state.sort.columnIndex === columnIndex && state.sort.direction) {
681
+ th.setAttribute('aria-sort', state.sort.direction === 'asc' ? 'ascending' : 'descending');
682
+ th.appendChild(el('span', 'omni-csv__sort-indicator', state.sort.direction === 'asc' ? '▲' : '▼'));
683
+ }
684
+ // Per-render elements: listeners are collected with the nodes.
685
+ // Single click sorts (deferred so a double-click can win); double
686
+ // click renames the header (docs §6).
687
+ th.addEventListener('click', () => {
688
+ if (th.querySelector('input'))
689
+ return;
690
+ if (pendingSortTimer)
691
+ clearTimeout(pendingSortTimer);
692
+ pendingSortTimer = setTimeout(() => {
693
+ pendingSortTimer = null;
694
+ controller.dispatch({ type: 'sort-column', columnIndex });
695
+ }, SORT_CLICK_DELAY_MS);
696
+ });
697
+ th.addEventListener('dblclick', () => {
698
+ if (pendingSortTimer) {
699
+ clearTimeout(pendingSortTimer);
700
+ pendingSortTimer = null;
701
+ }
702
+ if (th.querySelector('input'))
703
+ return;
704
+ if (!editable) {
705
+ showToast(t('csv.editUnavailable'));
706
+ return;
707
+ }
708
+ startInlineEdit(th, controller.state.headers[columnIndex] ?? '', (value) => controller.dispatch({ type: 'edit-header', columnIndex, value }));
709
+ });
710
+ if (editable) {
711
+ th.addEventListener('contextmenu', (e) => {
712
+ e.preventDefault();
713
+ openCellContextMenu(e, null, columnIndex);
714
+ });
715
+ }
716
+ headRow.appendChild(th);
717
+ });
718
+ thead.appendChild(headRow);
719
+ if (state.statsVisible) {
720
+ thead.appendChild(renderStatsRow(state));
721
+ }
722
+ table.appendChild(thead);
723
+ const tbody = el('tbody');
724
+ for (const rowId of state.visibleRowIds) {
725
+ const tr = el('tr');
726
+ if (editable) {
727
+ // Row and column ops live in a right-click menu (docs §6) —
728
+ // no permanent ops column cluttering the table.
729
+ tr.addEventListener('contextmenu', (e) => {
730
+ e.preventDefault();
731
+ const cell = e.target?.closest?.('td');
732
+ // Hitting the row-number gutter (or the tr itself in
733
+ // synthetic events) offers row ops only.
734
+ const columnIndex = cell && !cell.classList.contains('omni-csv__rownum')
735
+ ? cell.cellIndex - (state.showRowNumbers ? 1 : 0)
736
+ : null;
737
+ openCellContextMenu(e, rowId, columnIndex);
738
+ });
739
+ }
740
+ if (state.showRowNumbers) {
741
+ tr.appendChild(el('td', 'omni-csv__rownum', String(controller.getRowNumber(rowId))));
742
+ }
743
+ const cells = controller.getRowById(rowId);
744
+ for (let c = 0; c < state.columnCount; c++) {
745
+ const columnIndex = c;
746
+ const text = cells[c] ?? '';
747
+ const td = el('td', undefined, text);
748
+ // Truncated long cells reveal their full content on hover.
749
+ if (text.length > CELL_TOOLTIP_MIN_CHARS || text.includes('\n')) {
750
+ td.title = text;
751
+ }
752
+ td.addEventListener('dblclick', () => {
753
+ if (td.querySelector('input'))
754
+ return;
755
+ if (!editable) {
756
+ showToast(t('csv.editUnavailable'));
757
+ return;
758
+ }
759
+ startInlineEdit(td, controller.getRowById(rowId)[columnIndex] ?? '', (value) => controller.dispatch({
760
+ type: 'edit-cell',
761
+ rowId,
762
+ columnIndex,
763
+ value
764
+ }));
765
+ });
766
+ tr.appendChild(td);
767
+ }
768
+ tbody.appendChild(tr);
769
+ }
770
+ table.appendChild(tbody);
771
+ return table;
772
+ }
773
+ /** Second sticky thead row: per-column compact stats, details in the
774
+ * tooltip. Stats cover the whole document regardless of the search
775
+ * filter (docs §2). */
776
+ function renderStatsRow(state) {
777
+ const stats = controller.getStatistics();
778
+ const row = el('tr', 'omni-csv__stats-row');
779
+ if (state.showRowNumbers) {
780
+ row.appendChild(el('th', 'omni-csv__rownum', ''));
781
+ }
782
+ for (let c = 0; c < state.columnCount; c++) {
783
+ const stat = stats[c];
784
+ const cell = el('th');
785
+ if (!stat) {
786
+ row.appendChild(cell);
787
+ continue;
788
+ }
789
+ const parts = [t('csv.stats.compact.count', { count: stat.count })];
790
+ if (stat.nullCount > 0) {
791
+ parts.push(t('csv.stats.compact.nulls', {
792
+ percent: formatPercent(stat.nullPercent)
793
+ }));
794
+ }
795
+ if (stat.numeric) {
796
+ parts.push(t('csv.stats.compact.numeric', {
797
+ mean: formatStatNumber(stat.numeric.mean),
798
+ min: formatStatNumber(stat.numeric.min),
799
+ max: formatStatNumber(stat.numeric.max)
800
+ }));
801
+ }
802
+ cell.textContent = parts.join(' · ');
803
+ const details = [
804
+ `${t('csv.stats.count')} ${stat.count} / ${stat.total}`,
805
+ `${t('csv.stats.nulls')} ${stat.nullCount} (${formatPercent(stat.nullPercent)})`
806
+ ];
807
+ if (stat.numeric) {
808
+ details.push(`${t('csv.stats.mean')} ${formatStatNumber(stat.numeric.mean)}`, `${t('csv.stats.min')} ${formatStatNumber(stat.numeric.min)}`, `${t('csv.stats.max')} ${formatStatNumber(stat.numeric.max)}`);
809
+ }
810
+ cell.title = details.join(' · ');
811
+ row.appendChild(cell);
812
+ }
813
+ return row;
814
+ }
815
+ const unsubscribe = controller.subscribe(render);
816
+ render(controller.state);
817
+ if (options.signal?.aborted) {
818
+ cleanup();
819
+ throw new MountAbortedError();
820
+ }
821
+ function cleanup() {
822
+ unsubscribe();
823
+ for (const dispose of disposers)
824
+ dispose();
825
+ disposers.length = 0;
826
+ activeResizeCleanup?.();
827
+ closeContextMenu();
828
+ if (pendingSortTimer)
829
+ clearTimeout(pendingSortTimer);
830
+ if (rawEditTimer)
831
+ clearTimeout(rawEditTimer);
832
+ if (toastTimer)
833
+ clearTimeout(toastTimer);
834
+ if (root instanceof ShadowRoot) {
835
+ root.replaceChildren();
836
+ }
837
+ else {
838
+ root.replaceChildren();
839
+ root.classList.remove(VIEWER_ROOT_CLASS, 'omni-viewer--csv');
840
+ }
841
+ }
842
+ return {
843
+ dispose: cleanup,
844
+ isDirty: () => controller.state.dirty
845
+ };
846
+ }