tin-spa 20.14.57 → 20.14.71
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/fesm2022/tin-spa-excelRenderer-DAREFsYF.mjs +100 -0
- package/fesm2022/tin-spa-excelRenderer-DAREFsYF.mjs.map +1 -0
- package/fesm2022/tin-spa-wordRenderer-C1H1DiAh.mjs +34 -0
- package/fesm2022/tin-spa-wordRenderer-C1H1DiAh.mjs.map +1 -0
- package/fesm2022/tin-spa.mjs +12695 -9334
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +588 -64
- package/package.json +5 -2
- package/src/leaflet-core.css +665 -0
- package/src/tin-styles.css +129 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// The Excel (.xlsx) renderer, a PLAIN ASYNC FUNCTION for the same reason wordRenderer.ts is one: anything
|
|
2
|
+
// listed in tin-spa.module.ts `declarations` is a static import and lands in every consumer's INITIAL
|
|
3
|
+
// bundle. Reached only through `await import('./renderers/excelRenderer')`, this file and its library are
|
|
4
|
+
// split into their own chunk and cost a user who never opens a spreadsheet nothing.
|
|
5
|
+
//
|
|
6
|
+
// 🔴 WHY exceljs AND NOT SheetJS/xlsx (Arbiter ruling, 2026-08-19). `xlsx` on npm is frozen at 0.18.5 —
|
|
7
|
+
// SheetJS left the registry in 2023 and every patched release since lives only on cdn.sheetjs.com. 0.18.5
|
|
8
|
+
// carries two HIGH advisories with NO FIX AVAILABLE (GHSA-4r6h-8v6p-xvw6 prototype pollution CVSS 7.8, and
|
|
9
|
+
// a ReDoS), and the bytes this parser eats are USER-UPLOADED files. Pointing `dependencies` at the SheetJS
|
|
10
|
+
// CDN tarball instead would reproduce the 2026-08-14/16 delivery failure — resolves on this machine, ENOENT
|
|
11
|
+
// (or a network failure) on every cold cache and in CI. exceljs is on the ordinary registry, is maintained,
|
|
12
|
+
// and its only advisory is a MODERATE one inherited from `uuid`, which sits in its write path, not this
|
|
13
|
+
// read path. Do not "simplify" this back to xlsx.
|
|
14
|
+
//
|
|
15
|
+
// This file converts bytes to a plain data model only. The viewer owns the DOM and the design — nothing here
|
|
16
|
+
// produces HTML, so there is no sanitisation question on this path at all (unlike Word).
|
|
17
|
+
// A viewer, not an editor. Beyond a few hundred rows the browser is laying out tens of thousands of cells
|
|
18
|
+
// for a person who is scrolling to check one figure, and the honest answer is "download it".
|
|
19
|
+
const MAX_ROWS = 500;
|
|
20
|
+
const MAX_COLUMNS = 60;
|
|
21
|
+
async function renderExcel(blob) {
|
|
22
|
+
// 🔴 THE BROWSER BUNDLE BY PATH, for the same measured reason wordRenderer.ts imports mammoth by path:
|
|
23
|
+
// exceljs's `index.d.ts` is typed against Node and imports the `stream` module, which fails the tin-spa
|
|
24
|
+
// library build with TS2307 (measured 2026-08-19). `dist/exceljs.min.js` is exactly what the package's own
|
|
25
|
+
// `browser` field points at, has no declaration file, and keeps the cost inside this one file rather than
|
|
26
|
+
// forcing `@types/node` or `skipLibCheck` on every project in ng-space. The UMD shape means the namespace
|
|
27
|
+
// may arrive under `default`.
|
|
28
|
+
const module = await import('exceljs/dist/exceljs.min.js');
|
|
29
|
+
const ExcelJS = module?.Workbook ? module : (module?.default ?? module);
|
|
30
|
+
const workbook = new ExcelJS.Workbook();
|
|
31
|
+
await workbook.xlsx.load(await blob.arrayBuffer());
|
|
32
|
+
const sheets = [];
|
|
33
|
+
workbook.eachSheet((worksheet) => {
|
|
34
|
+
const rows = [];
|
|
35
|
+
let columns = 0;
|
|
36
|
+
let truncated = false;
|
|
37
|
+
// includeEmpty keeps a blank row's PLACE — collapsing them silently shifts every figure below it up a
|
|
38
|
+
// line, which is a wrong spreadsheet rendered as if it were right.
|
|
39
|
+
worksheet.eachRow({ includeEmpty: true }, (row) => {
|
|
40
|
+
if (rows.length >= MAX_ROWS) {
|
|
41
|
+
truncated = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const cells = [];
|
|
45
|
+
const width = Math.min(row.cellCount ?? 0, MAX_COLUMNS);
|
|
46
|
+
if ((row.cellCount ?? 0) > MAX_COLUMNS)
|
|
47
|
+
truncated = true;
|
|
48
|
+
for (let column = 1; column <= width; column++) {
|
|
49
|
+
const value = row.getCell(column)?.value;
|
|
50
|
+
cells.push({ text: cellText(value), numeric: isNumeric(value) });
|
|
51
|
+
}
|
|
52
|
+
columns = Math.max(columns, cells.length);
|
|
53
|
+
rows.push(cells);
|
|
54
|
+
});
|
|
55
|
+
// Pad every row out to the widest, so the grid is a true rectangle.
|
|
56
|
+
rows.forEach(cells => { while (cells.length < columns)
|
|
57
|
+
cells.push({ text: '', numeric: false }); });
|
|
58
|
+
// Trim trailing all-blank rows, which xlsx files accumulate in quantity.
|
|
59
|
+
while (rows.length > 0 && rows[rows.length - 1].every(cell => cell.text === ''))
|
|
60
|
+
rows.pop();
|
|
61
|
+
sheets.push({ name: worksheet.name ?? 'Sheet', rows: rows, columns: columns, truncated: truncated });
|
|
62
|
+
});
|
|
63
|
+
return { sheets: sheets, hasContent: sheets.some(sheet => sheet.rows.length > 0) };
|
|
64
|
+
}
|
|
65
|
+
// exceljs cell values are a union, not a string: a Date, a { formula, result }, a { richText: [] }, a
|
|
66
|
+
// { text, hyperlink }, or an { error }. Exported so a spec can pin every branch — this is the one function
|
|
67
|
+
// here that can quietly render "[object Object]" across a whole column.
|
|
68
|
+
function cellText(value) {
|
|
69
|
+
if (value === null || value === undefined)
|
|
70
|
+
return '';
|
|
71
|
+
if (value instanceof Date)
|
|
72
|
+
return value.toLocaleDateString();
|
|
73
|
+
if (typeof value === 'object') {
|
|
74
|
+
if (Array.isArray(value.richText))
|
|
75
|
+
return value.richText.map((run) => run?.text ?? '').join('');
|
|
76
|
+
// A formula cell shows its RESULT, which is what a spreadsheet shows. Falling back to the formula text
|
|
77
|
+
// is better than a blank when the file carries no cached result.
|
|
78
|
+
if ('result' in value)
|
|
79
|
+
return value.result === null || value.result === undefined ? '' : cellText(value.result);
|
|
80
|
+
if ('text' in value)
|
|
81
|
+
return String(value.text);
|
|
82
|
+
if ('error' in value)
|
|
83
|
+
return String(value.error);
|
|
84
|
+
if ('hyperlink' in value)
|
|
85
|
+
return String(value.hyperlink);
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
return String(value);
|
|
89
|
+
}
|
|
90
|
+
// Exported alongside cellText for the same reason.
|
|
91
|
+
function isNumeric(value) {
|
|
92
|
+
if (typeof value === 'number')
|
|
93
|
+
return true;
|
|
94
|
+
if (value && typeof value === 'object' && 'result' in value)
|
|
95
|
+
return typeof value.result === 'number';
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { cellText, isNumeric, renderExcel };
|
|
100
|
+
//# sourceMappingURL=tin-spa-excelRenderer-DAREFsYF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tin-spa-excelRenderer-DAREFsYF.mjs","sources":["../../../projects/tin-spa/src/lib/components/document-viewer/renderers/excelRenderer.ts"],"sourcesContent":["// The Excel (.xlsx) renderer, a PLAIN ASYNC FUNCTION for the same reason wordRenderer.ts is one: anything\n// listed in tin-spa.module.ts `declarations` is a static import and lands in every consumer's INITIAL\n// bundle. Reached only through `await import('./renderers/excelRenderer')`, this file and its library are\n// split into their own chunk and cost a user who never opens a spreadsheet nothing.\n//\n// 🔴 WHY exceljs AND NOT SheetJS/xlsx (Arbiter ruling, 2026-08-19). `xlsx` on npm is frozen at 0.18.5 —\n// SheetJS left the registry in 2023 and every patched release since lives only on cdn.sheetjs.com. 0.18.5\n// carries two HIGH advisories with NO FIX AVAILABLE (GHSA-4r6h-8v6p-xvw6 prototype pollution CVSS 7.8, and\n// a ReDoS), and the bytes this parser eats are USER-UPLOADED files. Pointing `dependencies` at the SheetJS\n// CDN tarball instead would reproduce the 2026-08-14/16 delivery failure — resolves on this machine, ENOENT\n// (or a network failure) on every cold cache and in CI. exceljs is on the ordinary registry, is maintained,\n// and its only advisory is a MODERATE one inherited from `uuid`, which sits in its write path, not this\n// read path. Do not \"simplify\" this back to xlsx.\n//\n// This file converts bytes to a plain data model only. The viewer owns the DOM and the design — nothing here\n// produces HTML, so there is no sanitisation question on this path at all (unlike Word).\n\nexport interface ExcelCell {\n text: string;\n\n // Right-aligns in the grid, the way a spreadsheet does. Decided from the SOURCE value's type, never by\n // re-parsing the formatted text — \"1-2\" would otherwise read as a number in some locales.\n numeric: boolean;\n}\n\nexport interface ExcelSheet {\n name: string;\n rows: ExcelCell[][];\n\n // The widest row, so every row can be padded to a rectangle. A ragged grid renders as a broken table.\n columns: number;\n\n // True when the sheet was cut off at the row/column caps below. Surfaced in the UI — a silently truncated\n // spreadsheet is the silent-failure shape this codebase keeps producing.\n truncated: boolean;\n}\n\nexport interface ExcelRenderResult {\n sheets: ExcelSheet[];\n hasContent: boolean;\n}\n\n// A viewer, not an editor. Beyond a few hundred rows the browser is laying out tens of thousands of cells\n// for a person who is scrolling to check one figure, and the honest answer is \"download it\".\nconst MAX_ROWS = 500;\nconst MAX_COLUMNS = 60;\n\nexport async function renderExcel(blob: Blob): Promise<ExcelRenderResult> {\n // 🔴 THE BROWSER BUNDLE BY PATH, for the same measured reason wordRenderer.ts imports mammoth by path:\n // exceljs's `index.d.ts` is typed against Node and imports the `stream` module, which fails the tin-spa\n // library build with TS2307 (measured 2026-08-19). `dist/exceljs.min.js` is exactly what the package's own\n // `browser` field points at, has no declaration file, and keeps the cost inside this one file rather than\n // forcing `@types/node` or `skipLibCheck` on every project in ng-space. The UMD shape means the namespace\n // may arrive under `default`.\n const module: any = await import('exceljs/dist/exceljs.min.js');\n const ExcelJS = module?.Workbook ? module : (module?.default ?? module);\n\n const workbook = new ExcelJS.Workbook();\n await workbook.xlsx.load(await blob.arrayBuffer());\n\n const sheets: ExcelSheet[] = [];\n\n workbook.eachSheet((worksheet: any) => {\n const rows: ExcelCell[][] = [];\n let columns = 0;\n let truncated = false;\n\n // includeEmpty keeps a blank row's PLACE — collapsing them silently shifts every figure below it up a\n // line, which is a wrong spreadsheet rendered as if it were right.\n worksheet.eachRow({ includeEmpty: true }, (row: any) => {\n if (rows.length >= MAX_ROWS) { truncated = true; return; }\n\n const cells: ExcelCell[] = [];\n const width = Math.min(row.cellCount ?? 0, MAX_COLUMNS);\n if ((row.cellCount ?? 0) > MAX_COLUMNS) truncated = true;\n\n for (let column = 1; column <= width; column++) {\n const value = row.getCell(column)?.value;\n cells.push({ text: cellText(value), numeric: isNumeric(value) });\n }\n\n columns = Math.max(columns, cells.length);\n rows.push(cells);\n });\n\n // Pad every row out to the widest, so the grid is a true rectangle.\n rows.forEach(cells => { while (cells.length < columns) cells.push({ text: '', numeric: false }); });\n\n // Trim trailing all-blank rows, which xlsx files accumulate in quantity.\n while (rows.length > 0 && rows[rows.length - 1].every(cell => cell.text === '')) rows.pop();\n\n sheets.push({ name: worksheet.name ?? 'Sheet', rows: rows, columns: columns, truncated: truncated });\n });\n\n return { sheets: sheets, hasContent: sheets.some(sheet => sheet.rows.length > 0) };\n}\n\n// exceljs cell values are a union, not a string: a Date, a { formula, result }, a { richText: [] }, a\n// { text, hyperlink }, or an { error }. Exported so a spec can pin every branch — this is the one function\n// here that can quietly render \"[object Object]\" across a whole column.\nexport function cellText(value: any): string {\n if (value === null || value === undefined) return '';\n if (value instanceof Date) return value.toLocaleDateString();\n if (typeof value === 'object') {\n if (Array.isArray(value.richText)) return value.richText.map((run: any) => run?.text ?? '').join('');\n // A formula cell shows its RESULT, which is what a spreadsheet shows. Falling back to the formula text\n // is better than a blank when the file carries no cached result.\n if ('result' in value) return value.result === null || value.result === undefined ? '' : cellText(value.result);\n if ('text' in value) return String(value.text);\n if ('error' in value) return String(value.error);\n if ('hyperlink' in value) return String(value.hyperlink);\n return '';\n }\n return String(value);\n}\n\n// Exported alongside cellText for the same reason.\nexport function isNumeric(value: any): boolean {\n if (typeof value === 'number') return true;\n if (value && typeof value === 'object' && 'result' in value) return typeof value.result === 'number';\n return false;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The Word (.docx) renderer, as a PLAIN ASYNC FUNCTION rather than an Angular component.
|
|
2
|
+
//
|
|
3
|
+
// 🔴 WHY NOT A COMPONENT. The whole point of this file is that mammoth must never reach a consumer's INITIAL
|
|
4
|
+
// bundle — the exact regression recorded at tin-spa.module.ts:42, where ngx-doc-viewer dragged mammoth into
|
|
5
|
+
// every consumer app. A non-standalone Angular component has to be listed in tin-spa.module.ts's
|
|
6
|
+
// `declarations`, which is a STATIC import, which puts the component AND everything it imports back in the
|
|
7
|
+
// initial bundle. Declaring it defeats the requirement. A function that is reached only through
|
|
8
|
+
// `await import('./renderers/wordRenderer')` is split into its own chunk by the bundler, and mammoth is
|
|
9
|
+
// split with it. The viewer owns the DOM and the design; this file owns only the conversion.
|
|
10
|
+
//
|
|
11
|
+
// 🔴 THE OUTPUT IS UNTRUSTED HTML. It is derived from a file a user uploaded. It is NOT sanitized here — the
|
|
12
|
+
// caller runs it through DomSanitizer.sanitize(SecurityContext.HTML, ...) before it ever reaches a binding.
|
|
13
|
+
// Sanitizing in this file would need a DI-provided sanitizer and would make the module Angular-dependent for
|
|
14
|
+
// no gain; the contract is stated on renderWord's return type instead.
|
|
15
|
+
async function renderWord(blob) {
|
|
16
|
+
// 🔴 THE PREBUILT BROWSER BUNDLE BY PATH, NOT the bare 'mammoth' specifier, and this is not a style
|
|
17
|
+
// preference. mammoth's `lib/index.d.ts` is typed against Node — it names `Buffer` in four places — so the
|
|
18
|
+
// bare specifier fails the tin-spa library build with four TS2591 "Cannot find name 'Buffer'" errors
|
|
19
|
+
// (measured 2026-08-19). The alternatives were adding `@types/node` to a browser library, or turning on
|
|
20
|
+
// `skipLibCheck` in the SHARED root tsconfig, which would weaken type-checking for every project in
|
|
21
|
+
// ng-space to fix one import. `mammoth.browser.js` is the package's own documented web build, carries no
|
|
22
|
+
// declaration file, and under `strict: false` an untyped import is accepted — so the cost stays inside
|
|
23
|
+
// this file, which is why the result is typed as WordRenderResult right here.
|
|
24
|
+
const mammoth = await import('mammoth/mammoth.browser');
|
|
25
|
+
const convert = mammoth.convertToHtml ?? mammoth.default?.convertToHtml;
|
|
26
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
27
|
+
const result = await convert({ arrayBuffer: arrayBuffer });
|
|
28
|
+
const html = result?.value ?? '';
|
|
29
|
+
const warnings = (result?.messages ?? []).map((m) => m?.message ?? String(m)).filter((m) => !!m);
|
|
30
|
+
return { html: html, warnings: warnings, hasContent: html.trim().length > 0 };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { renderWord };
|
|
34
|
+
//# sourceMappingURL=tin-spa-wordRenderer-C1H1DiAh.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tin-spa-wordRenderer-C1H1DiAh.mjs","sources":["../../../projects/tin-spa/src/lib/components/document-viewer/renderers/wordRenderer.ts"],"sourcesContent":["// The Word (.docx) renderer, as a PLAIN ASYNC FUNCTION rather than an Angular component.\n//\n// 🔴 WHY NOT A COMPONENT. The whole point of this file is that mammoth must never reach a consumer's INITIAL\n// bundle — the exact regression recorded at tin-spa.module.ts:42, where ngx-doc-viewer dragged mammoth into\n// every consumer app. A non-standalone Angular component has to be listed in tin-spa.module.ts's\n// `declarations`, which is a STATIC import, which puts the component AND everything it imports back in the\n// initial bundle. Declaring it defeats the requirement. A function that is reached only through\n// `await import('./renderers/wordRenderer')` is split into its own chunk by the bundler, and mammoth is\n// split with it. The viewer owns the DOM and the design; this file owns only the conversion.\n//\n// 🔴 THE OUTPUT IS UNTRUSTED HTML. It is derived from a file a user uploaded. It is NOT sanitized here — the\n// caller runs it through DomSanitizer.sanitize(SecurityContext.HTML, ...) before it ever reaches a binding.\n// Sanitizing in this file would need a DI-provided sanitizer and would make the module Angular-dependent for\n// no gain; the contract is stated on renderWord's return type instead.\n\n// The result of converting one .docx. `html` is UNSANITIZED — see above.\nexport interface WordRenderResult {\n html: string;\n\n // mammoth's own conversion warnings (unsupported styles, dropped elements). Surfaced so a document that\n // renders only partially says so, rather than quietly losing content — the silent-failure shape this\n // codebase keeps producing.\n warnings: string[];\n\n // False when the document converted to nothing at all, e.g. a .docx whose body is a single image mammoth\n // could not inline. The viewer shows its honest fallback panel rather than a blank white sheet.\n hasContent: boolean;\n}\n\nexport async function renderWord(blob: Blob): Promise<WordRenderResult> {\n // 🔴 THE PREBUILT BROWSER BUNDLE BY PATH, NOT the bare 'mammoth' specifier, and this is not a style\n // preference. mammoth's `lib/index.d.ts` is typed against Node — it names `Buffer` in four places — so the\n // bare specifier fails the tin-spa library build with four TS2591 \"Cannot find name 'Buffer'\" errors\n // (measured 2026-08-19). The alternatives were adding `@types/node` to a browser library, or turning on\n // `skipLibCheck` in the SHARED root tsconfig, which would weaken type-checking for every project in\n // ng-space to fix one import. `mammoth.browser.js` is the package's own documented web build, carries no\n // declaration file, and under `strict: false` an untyped import is accepted — so the cost stays inside\n // this file, which is why the result is typed as WordRenderResult right here.\n const mammoth: any = await import('mammoth/mammoth.browser');\n const convert = mammoth.convertToHtml ?? mammoth.default?.convertToHtml;\n\n const arrayBuffer = await blob.arrayBuffer();\n const result = await convert({ arrayBuffer: arrayBuffer });\n\n const html: string = result?.value ?? '';\n const warnings: string[] = (result?.messages ?? []).map((m: any) => m?.message ?? String(m)).filter((m: string) => !!m);\n\n return { html: html, warnings: warnings, hasContent: html.trim().length > 0 };\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAgBO,eAAe,UAAU,CAAC,IAAU,EAAA;;;;;;;;;AASzC,IAAA,MAAM,OAAO,GAAQ,MAAM,OAAO,yBAAyB,CAAC;IAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,OAAO,EAAE,aAAa;AAEvE,IAAA,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE;IAC5C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;AAE1D,IAAA,MAAM,IAAI,GAAW,MAAM,EAAE,KAAK,IAAI,EAAE;AACxC,IAAA,MAAM,QAAQ,GAAa,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAM,KAAK,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAS,KAAK,CAAC,CAAC,CAAC,CAAC;IAEvH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE;AAC/E;;;;"}
|