@simple-reporting/base 1.0.13 → 1.0.15

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 (44) hide show
  1. package/dev/package.json +14 -14
  2. package/dev/src/assets/scss/editor.scss +45 -0
  3. package/dev/src/assets/scss/general.scss +1 -0
  4. package/dev/tsconfig.json +1 -1
  5. package/dev/vite.config.ts +1 -1
  6. package/livingdocs/040.Media/010.table/table/download.vue +494 -0
  7. package/livingdocs/040.Media/010.table/table/responsive.vue +104 -0
  8. package/livingdocs/040.Media/010.table/table.html +2 -2
  9. package/livingdocs/040.Media/010.table/table.vue +26 -0
  10. package/livingdocs/040.Media/030.video/ld-conf.json +1 -1
  11. package/livingdocs/100.Misc/010.anchor/anchor.html +3 -6
  12. package/livingdocs/100.Misc/010.anchor/scss/editor.scss +1 -12
  13. package/livingdocs/110.PDF/010.pdf-pagebreak/pdf-pagebreak.html +2 -2
  14. package/livingdocs/110.PDF/010.pdf-pagebreak/scss/editor.scss +0 -8
  15. package/livingdocs/110.PDF/030.pdf-publication-title/pdf-publication-title.html +2 -5
  16. package/livingdocs/110.PDF/030.pdf-publication-title/scss/editor.scss +0 -11
  17. package/livingdocs/110.PDF/040.pdf-chapter-title/pdf-chapter-title.html +2 -5
  18. package/livingdocs/110.PDF/040.pdf-chapter-title/scss/editor.scss +0 -5
  19. package/package.json +7 -7
  20. package/plugins/viteSrlPlugin.d.ts +2 -0
  21. package/{srl/plugins/viteSrlPlugin.ts → plugins/viteSrlPlugin.js} +114 -86
  22. package/scripts/prepare.d.ts +1 -1
  23. package/scripts/prepare.js +3 -58
  24. package/scripts/vue/components.d.ts +1 -0
  25. package/scripts/vue/components.js +94 -0
  26. package/srl/components/Srl/Article/{DialogButton.vue → Dialog/Button.vue} +1 -1
  27. package/srl/components/Srl/Menu/Item/Content.vue +7 -14
  28. package/srl/components/Srl/Menu/Item.vue +5 -7
  29. package/srl/components/Srl/{Menu/List.vue → Menu.vue} +2 -3
  30. package/srl/components/Srl/Note/Accordion/Content.vue +1 -1
  31. package/srl/components/Srl/Page/Dialog.vue +1 -1
  32. package/srl/plugins/asyncLdComponent.ts +2 -2
  33. package/srl/plugins/asyncSrlComponents.ts +2 -0
  34. package/srl/plugins/initProject.ts +1 -1
  35. package/srl/plugins/vueSrlPlugin.ts +3 -20
  36. package/srl/tsconfig.srl.json +89 -0
  37. package/srl/types/components.d.ts +3 -14
  38. package/srl/types/global.d.ts +7 -0
  39. package/srl/types/nswow.d.ts +14 -14
  40. /package/srl/components/{Srl/Page/App.vue → App.vue} +0 -0
  41. /package/srl/components/Srl/Menu/Item/Content/{IconAfter.vue → Icon/After.vue} +0 -0
  42. /package/srl/components/Srl/Menu/Item/Content/{IconBefore.vue → Icon/Before.vue} +0 -0
  43. /package/srl/components/Srl/Menu/Item/Content/{ImageAfter.vue → Image/After.vue} +0 -0
  44. /package/srl/components/Srl/Menu/Item/Content/{ImageBefore.vue → Image/Before.vue} +0 -0
package/dev/package.json CHANGED
@@ -19,9 +19,9 @@
19
19
  "postinstall": "srl prepare"
20
20
  },
21
21
  "dependencies": {
22
- "@simple-reporting/base": "^1.0.13",
23
- "axios": "^1.9.0",
24
- "chalk": "^5.4.1",
22
+ "@simple-reporting/base": "^1.0.15",
23
+ "axios": "^1.12.2",
24
+ "chalk": "^5.6.2",
25
25
  "exceljs": "^4.4.0",
26
26
  "file-saver": "^2.0.5",
27
27
  "highcharts": "^11.4.8",
@@ -32,33 +32,33 @@
32
32
  "standard-scss": "github:mms-neidhartschoen/nsfd_standard-scss",
33
33
  "vite-plugin-pwa": "^0.21.2",
34
34
  "vue": "^3.5.13",
35
- "vue-i18n": "^11.1.5",
35
+ "vue-i18n": "^11.1.12",
36
36
  "vue-router": "^4.5.1",
37
37
  "vue3-runtime-template": "^1.0.2",
38
38
  "xlsx": "^0.18.5"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@kne/color-palette": "^0.1.0",
42
- "@rushstack/eslint-patch": "^1.11.0",
42
+ "@rushstack/eslint-patch": "^1.12.0",
43
43
  "@tsconfig/node22": "^22.0.2",
44
44
  "@types/jsdom": "^21.1.7",
45
- "@types/node": "^22.15.29",
45
+ "@types/node": "^22.18.4",
46
46
  "@vitejs/plugin-vue": "^5.2.4",
47
47
  "@vitest/eslint-plugin": "1.1.20",
48
48
  "@vue/eslint-config-prettier": "^10.2.0",
49
- "@vue/eslint-config-typescript": "^14.5.0",
49
+ "@vue/eslint-config-typescript": "^14.6.0",
50
50
  "@vue/test-utils": "^2.4.6",
51
51
  "@vue/tsconfig": "^0.7.0",
52
- "eslint": "^9.28.0",
52
+ "eslint": "^9.35.0",
53
53
  "eslint-plugin-vue": "^9.33.0",
54
54
  "jsdom": "^25.0.1",
55
55
  "npm-run-all2": "^7.0.2",
56
- "prettier": "^3.5.3",
57
- "sass": "^1.89.1",
56
+ "prettier": "^3.6.2",
57
+ "sass": "^1.92.1",
58
58
  "typescript": "~5.6.3",
59
- "vite": "^6.3.5",
60
- "vite-plugin-vue-devtools": "^7.7.6",
61
- "vitest": "^2.1.9",
62
- "vue-tsc": "^2.2.10"
59
+ "vite": "^6.3.6",
60
+ "vite-plugin-vue-devtools": "^7.7.7",
61
+ "vitest": "^3.2.4",
62
+ "vue-tsc": "^2.2.12"
63
63
  }
64
64
  }
@@ -1,5 +1,6 @@
1
1
  @use 'srl';
2
2
  @use 'web';
3
+ @use "placeholders";
3
4
 
4
5
  $editor-color-srl: srl.colors-shade-200();
5
6
  $editor-background-color-srl: srl.colors-primary-1000();
@@ -37,4 +38,48 @@ span.ns-key-figure {
37
38
  // Set width of placeholder image to 100%
38
39
  img[src^="data:image/svg+xml"] {
39
40
  width: 100%
41
+ }
42
+
43
+ .srl-editor-component {
44
+ @include srl.spacer-padding-block(200);
45
+ @extend %srl-grid-base;
46
+
47
+ &.srl-pdf-pagebreak,
48
+ &.srl-anchor {
49
+ border-top: 1px solid #{srl.colors-primary-1000()};
50
+ border-bottom: 1px solid #{srl.colors-primary-1000()};
51
+ }
52
+
53
+ &.srl-pdf-publication-title,
54
+ &.srl-pdf-chapter-title {
55
+ border-bottom: 1px solid #{srl.colors-primary-1000()};
56
+ }
57
+
58
+ .doc-editable.doc-no-placeholder:before {
59
+ display: none !important;
60
+ }
61
+ }
62
+
63
+ .srl-editor-component__text {
64
+ @extend %srl-regular-width;
65
+ display: flex;
66
+ flex-direction: column;
67
+
68
+ &:after {
69
+ order: -1;
70
+ display: block;
71
+ @include srl.typography-editor-label-text();
72
+
73
+ .srl-anchor & {
74
+ content: "Anchor:";
75
+ }
76
+
77
+ .srl-pdf-publication-title & {
78
+ content: "PDF publication title:"
79
+ }
80
+
81
+ .srl-pdf-chapter-title & {
82
+ content: "PDF chapter title:"
83
+ }
84
+ }
40
85
  }
@@ -13,6 +13,7 @@ body {
13
13
  */
14
14
  img {
15
15
  max-width: 100%;
16
+ width: 100%;
16
17
  height: auto;
17
18
  display: block;
18
19
  }
package/dev/tsconfig.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "path": "./tsconfig.node.json"
6
6
  },
7
7
  {
8
- "path": "./tsconfig.app.json"
8
+ "path": "./.srl/tsconfig.srl.json"
9
9
  },
10
10
  {
11
11
  "path": "./tsconfig.vitest.json"
@@ -1,7 +1,7 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
3
  import VueDevTools from 'vite-plugin-vue-devtools'
4
- import viteSrlPlugin from './.srl/plugins/viteSrlPlugin.ts'
4
+ import viteSrlPlugin from '@simple-reporting/base/plugins/viteSrlPlugin.js'
5
5
  import { fileURLToPath, URL } from 'node:url'
6
6
 
7
7
  // https://vitejs.dev/config/
@@ -0,0 +1,494 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * This Vue component script enables downloading an HTML table as an Excel file.
4
+ * It uses the `exceljs` library to convert the table into an Excel format and trigger the download.
5
+ *
6
+ * ## Props:
7
+ * - `titleSelector` (string, optional): CSS selector for the table titles. Default: `.srl-title-h1__text, .srl-page-title__overline, .srl-page-title__headline`.
8
+ * - `font` (string, optional): Font used in the Excel file. Default: `Arial`.
9
+ * - `defaultSheetName` (string, optional): Default name of the Excel worksheet. Default: `Table 1`.
10
+ * - `defaultFileName` (string, optional): Default name of the downloaded file. Default: `Table`.
11
+ * - `spacerRowHeight` (number, optional): Height of spacer rows in the Excel file. Default: `25`.
12
+ * - `formatText` (Function, optional): Function to format cell content. Default: Trims text and returns `null` if empty.
13
+ *
14
+ * ## Types:
15
+ * - `FormatText`: Type for the `formatText` function.
16
+ * - `ElementStyles`: Style properties of an HTML element.
17
+ * - `TextData`: Text data with styles.
18
+ * - `CellData`: Data and styles of a table cell.
19
+ * - `CellStyles`: Styles of a table cell.
20
+ * - `TableIdElement`: HTML div element with a `data-tableid` attribute.
21
+ * - `TableMatrix`: Matrix representing the table structure.
22
+ *
23
+ * ## Functions:
24
+ * - `downloadTable()`: Initiates the download of the table as an Excel file.
25
+ * - `parseColumns(worksheet: Worksheet)`: Parses the column widths of the table and sets them in the worksheet.
26
+ * - `parseTitles()`: Extracts the table titles and their styles.
27
+ * - `writeTitles(worksheet: Worksheet, titles: TextData[])`: Writes the titles to the worksheet.
28
+ * - `parseTable()`: Converts the HTML table into a matrix of cells.
29
+ * - `writeTable(worksheet: Worksheet, matrix: TableMatrix)`: Writes the table matrix to the worksheet.
30
+ * - `mergeCells(worksheet: Worksheet, matrix: TableMatrix, excelRows: Row[])`: Merges cells with `rowSpan` or `colSpan`.
31
+ * - `parseFootNotes()`: Extracts footnotes from the table.
32
+ * - `writeFootNotes(worksheet: Worksheet, footNotes: TextData[])`: Writes the footnotes to the worksheet.
33
+ * - `triggerDownload(fileName: string, buffer: ArrayBuffer)`: Triggers the download of the Excel file.
34
+ * - `writeSpacerRow(worksheet: Worksheet)`: Adds a spacer row to the worksheet.
35
+ * - `parseElementStyles(element: HTMLElement)`: Parses the styles of an HTML element.
36
+ * - `rgbToArgb(rgb: string, alpha: number)`: Converts an RGB color to ARGB format.
37
+ * - `getStyles(tableCell: HTMLTableCellElement)`: Extracts the styles of a table cell.
38
+ *
39
+ * ## Usage:
40
+ * This component is used to export an HTML table to an Excel file.
41
+ * It can be integrated into a Vue application and provides a button to trigger the export.
42
+ */
43
+ import { ref, watch } from 'vue'
44
+ import { Workbook } from 'exceljs'
45
+ import type { Worksheet, Row } from 'exceljs/index.d.ts'
46
+
47
+ const props = withDefaults(defineProps<{
48
+ component?: HTMLDivElement
49
+ tableSelector?: string
50
+ titleSelector?: string
51
+ buttonLabel?: string
52
+ buttonTitle?: string
53
+ buttonDescription?: string
54
+ font?: string
55
+ defaultSheetName?: string
56
+ defaultFileName?: string
57
+ spacerRowHeight?: number
58
+ formatText?: FormatText
59
+ }>(),{
60
+ tableSelector: 'table',
61
+ titleSelector: '.srl-title-h1__text, .srl-page-title__overline, .srl-page-title__headline',
62
+ buttonLabel: 'Download',
63
+ buttonDescription: 'Download table as Excel file',
64
+ font: 'Arial',
65
+ defaultSheetName: 'Table 1',
66
+ defaultFileName: 'Table',
67
+ spacerRowHeight: 25,
68
+ formatText: (cell: HTMLTableCellElement) => {
69
+ return cell.innerText !== '' ? cell.innerText.trim() : null
70
+ }
71
+ })
72
+
73
+ type FormatText = (cell: HTMLTableCellElement) => string | null
74
+
75
+ type ElementStyles = {
76
+ name: string,
77
+ size: number,
78
+ color: string,
79
+ bold: boolean
80
+ }
81
+
82
+ interface TextData extends ElementStyles {
83
+ value: string
84
+ }
85
+
86
+ type CellData = {
87
+ value: string | null,
88
+ styles: CellStyles,
89
+ colSpan: number,
90
+ rowSpan: number
91
+ }
92
+
93
+ type CellStyles = {
94
+ alignment: {
95
+ horizontal: string,
96
+ vertical: string,
97
+ wrapText: boolean
98
+ },
99
+ border: {
100
+ top?: { style: string, color: { argb: string } },
101
+ right?: { style: string, color: { argb: string } },
102
+ bottom?: { style: string, color: { argb: string } },
103
+ left?: { style: string, color: { argb: string } }
104
+ },
105
+ font: {
106
+ name: string,
107
+ size: number,
108
+ color: { argb: string },
109
+ bold: boolean
110
+ },
111
+ fill: {
112
+ type?: string,
113
+ pattern?: string,
114
+ fgColor?: { argb: string }
115
+ }
116
+ }
117
+
118
+ type TableIdElement = HTMLDivElement & {
119
+ dataset: {
120
+ tableid: string
121
+ }
122
+ }
123
+
124
+ type TableMatrix = Array<Array<CellData>>
125
+
126
+ const rootElement = ref<HTMLDivElement>()
127
+ const table = ref<HTMLTableElement | null | undefined>(null)
128
+
129
+ watch(
130
+ props,
131
+ to => {
132
+ !to.component || init(to.component)
133
+ }
134
+ )
135
+
136
+ function init(componentEl: HTMLDivElement | undefined) {
137
+ if (!componentEl) return
138
+ rootElement.value = componentEl
139
+ table.value = componentEl.querySelector(props.tableSelector) as HTMLTableElement
140
+ }
141
+
142
+ function getTable(): Promise<HTMLTableElement | null | undefined> {
143
+ return new Promise((resolve, reject) => {
144
+ let attempts = 0;
145
+ const checkTable = () => {
146
+ const table = rootElement.value?.querySelector<HTMLTableElement>('table');
147
+ if (table) {
148
+ resolve(table);
149
+ } else if (attempts >= 30) {
150
+ reject(new Error('Table not found within the maximum number of attempts.'));
151
+ } else {
152
+ attempts++;
153
+ setTimeout(checkTable, 10);
154
+ }
155
+ };
156
+
157
+ checkTable();
158
+ })
159
+ }
160
+
161
+ async function downloadTable() {
162
+ if (!table.value) return
163
+
164
+ const matrix = parseTable()
165
+ const titles = parseTitles()
166
+ const footNotes = parseFootNotes()
167
+ const fileName = titles.map(t => t.value)
168
+
169
+ const headTop: HTMLTableCellElement | null | undefined = rootElement.value?.querySelector('.head_top')
170
+ if (headTop && headTop.textContent) {
171
+ fileName.push(headTop.textContent.trim())
172
+ }
173
+
174
+ const tableIdElement = rootElement.value?.querySelector('[data-tableid]') as TableIdElement | null
175
+
176
+ const workbook = new Workbook()
177
+ const worksheet = workbook.addWorksheet( tableIdElement?.dataset.tableid || props.defaultSheetName )
178
+
179
+ parseColumns(worksheet)
180
+
181
+ writeTitles(worksheet, titles)
182
+ mergeCells(worksheet, matrix, writeTable(worksheet, matrix))
183
+ writeFootNotes(worksheet, footNotes)
184
+
185
+ triggerDownload(
186
+ fileName.length ? fileName.join(' - ').replaceAll(' ', '_') : props.defaultFileName,
187
+ await workbook.xlsx.writeBuffer()
188
+ )
189
+ }
190
+
191
+ function parseColumns(worksheet: Worksheet): void {
192
+ worksheet.columns = Array.from(table.value?.querySelectorAll('col') ?? [])
193
+ .map(col => {
194
+ return {
195
+ width: col.offsetWidth / 7
196
+ }
197
+ })
198
+ }
199
+
200
+ function parseTitles(): TextData[] {
201
+ const titleElements: HTMLElement[] = Array.from(document.querySelectorAll(props.titleSelector)) as HTMLElement[]
202
+ const tableTitle: TextData[] = []
203
+ titleElements.forEach( element => {
204
+ if (!element || !element.textContent) return
205
+ const styles = parseElementStyles(element)
206
+ tableTitle.push(Object.assign({
207
+ value: element.textContent.trim(),
208
+ }, styles))
209
+ })
210
+ return tableTitle
211
+ }
212
+
213
+ function writeTitles( worksheet: Worksheet, titles: TextData[] ): void {
214
+ if (titles.length === 0) return
215
+
216
+ titles.forEach( title => {
217
+ const row = worksheet.addRow([title.value])
218
+ const cell = row.getCell(1)
219
+ cell.font = {
220
+ name: title.name,
221
+ size: title.size,
222
+ color: { argb: title.color },
223
+ bold: title.bold
224
+ }
225
+ cell.alignment = {
226
+ wrapText: true
227
+ }
228
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
229
+ })
230
+
231
+ //writeSpacerRow(worksheet)
232
+ }
233
+
234
+ function parseTable(): TableMatrix {
235
+ if (!table.value) return []
236
+
237
+ const matrix: TableMatrix = []
238
+
239
+ const tableRows = Array.from(table.value.querySelectorAll('tr'))
240
+
241
+ tableRows?.forEach( tableRowElem => {
242
+ const row: CellData[] = []
243
+ const tableCells: HTMLTableCellElement[] = Array.from(tableRowElem.querySelectorAll('th, td'))
244
+
245
+ tableCells.forEach( tableCellElem => {
246
+
247
+ const styles = getStyles(tableCellElem)
248
+
249
+ row.push({
250
+ value: props.formatText(tableCellElem),
251
+ styles: styles,
252
+ colSpan: tableCellElem.colSpan,
253
+ rowSpan: tableCellElem.rowSpan,
254
+ })
255
+
256
+ if (tableCellElem.colSpan > 1) {
257
+ for (let i = 1; i < tableCellElem.colSpan; i++) {
258
+ row.push({
259
+ value: null,
260
+ styles: styles,
261
+ colSpan: 1,
262
+ rowSpan: 1
263
+ })
264
+ }
265
+ }
266
+ })
267
+
268
+ matrix.push(row)
269
+ })
270
+
271
+ matrix.forEach( (row, rowIndex) => {
272
+ row.forEach( (cell) => {
273
+ const cellIndex = row.findIndex(c => c === cell)
274
+ if (cell.rowSpan > 1) {
275
+ for (let i = 1; i < cell.rowSpan; i++) {
276
+ const nextRow = matrix[rowIndex + i]
277
+ if (nextRow) {
278
+ for (let i = 0; i < cell.colSpan; i++) {
279
+ nextRow.splice(cellIndex, 0, {
280
+ value: null,
281
+ styles: cell.styles,
282
+ colSpan: 1,
283
+ rowSpan: 1
284
+ })
285
+ }
286
+ }
287
+ }
288
+ }
289
+ })
290
+ })
291
+
292
+ return matrix
293
+ }
294
+
295
+ function writeTable( worksheet: Worksheet, matrix: TableMatrix ): Row[] {
296
+ const excelRows: Row[] = []
297
+ matrix.forEach( row => {
298
+ const excelRow = worksheet.addRow(row.map(cell => cell.value))
299
+ row.forEach( (cell, index) => {
300
+ const excelCell = excelRow.getCell(index + 1)
301
+
302
+ excelCell.alignment = cell.styles.alignment
303
+ excelCell.font = cell.styles.font
304
+ cell.styles.fill ? excelCell.fill = cell.styles.fill : null
305
+ excelCell.border = cell.styles.border
306
+ })
307
+ excelRows.push(excelRow)
308
+ })
309
+ return excelRows
310
+ }
311
+
312
+ function mergeCells( worksheet: Worksheet, matrix: TableMatrix, excelRows: Row[] ): void {
313
+ matrix.forEach( (row, rowIndex) => {
314
+ row.forEach( (cell, cellIndex) => {
315
+ if (cell.rowSpan > 1 || cell.colSpan > 1) {
316
+ const excelRow = excelRows[rowIndex]
317
+ const startCell = cellIndex + 1
318
+ const endCell = cellIndex + cell.colSpan
319
+ const endRow = excelRow.number + ( cell.rowSpan - 1 )
320
+ worksheet.mergeCells(excelRow.number, startCell, endRow, endCell)
321
+ }
322
+ })
323
+ })
324
+ }
325
+
326
+ function parseFootNotes(): TextData[] {
327
+ const footNoteData: TextData[] = []
328
+ const footNotes = Array.from(rootElement.value?.querySelectorAll('.srl-footnote') ?? []) as HTMLDivElement[]
329
+
330
+ footNotes.forEach(footNote => {
331
+ const key: HTMLSpanElement | null = footNote.querySelector('.srl-footnote__key')
332
+ const text: HTMLSpanElement | null = footNote.querySelector('.srl-footnote__text')
333
+
334
+ if (text && text.textContent) {
335
+ const row: string[] = []
336
+ if (key && key.textContent) {
337
+ row.push(key.textContent.trim())
338
+ }
339
+ row.push(text.textContent.trim())
340
+ const styles = parseElementStyles(text)
341
+ footNoteData.push(Object.assign({
342
+ value: row.join(' '),
343
+ }, styles))
344
+ }
345
+ })
346
+ return footNoteData
347
+ }
348
+
349
+ function writeFootNotes( worksheet: Worksheet, footNotes: TextData[] ): void {
350
+ if (footNotes.length === 0) return
351
+
352
+ writeSpacerRow(worksheet)
353
+
354
+ footNotes.forEach(footNote => {
355
+ const row = worksheet.addRow([footNote.value])
356
+ const cell = row.getCell(1)
357
+ cell.font = {
358
+ name: footNote.name,
359
+ size: footNote.size,
360
+ color: { argb: footNote.color },
361
+ bold: footNote.bold
362
+ }
363
+ cell.alignment = {
364
+ horizontal: 'left',
365
+ vertical: 'top',
366
+ wrapText: true
367
+ }
368
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
369
+ })
370
+ }
371
+
372
+ function triggerDownload( fileName: string, buffer: ArrayBuffer ): void {
373
+ const blob = new Blob([buffer], {
374
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
375
+ })
376
+ const link = document.createElement('a')
377
+ link.href = URL.createObjectURL(blob)
378
+ link.download = fileName + '.xlsx'
379
+ link.click()
380
+ link.remove()
381
+ URL.revokeObjectURL(link.href)
382
+ }
383
+
384
+ function writeSpacerRow(worksheet: Worksheet): void {
385
+ const row = worksheet.addRow([])
386
+ row.height = props.spacerRowHeight
387
+ worksheet.mergeCells(row.number, 1, row.number, worksheet.columns.length)
388
+ }
389
+
390
+ function parseElementStyles(element: HTMLElement): ElementStyles | null {
391
+ const styles = window.getComputedStyle(element)
392
+ return {
393
+ name: props.font,
394
+ size: parseInt(styles.fontSize) * 0.75,
395
+ color: rgbToArgb(styles.color),
396
+ bold: parseInt(styles.fontWeight) > 400
397
+ }
398
+ }
399
+
400
+ function rgbToArgb(rgb: string, alpha: number = 1): string {
401
+ const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/)
402
+ if (!match) {
403
+ throw new Error("Dies ist keine gültige RGB- oder RGBA-Farbe")
404
+ }
405
+
406
+ const r = parseInt(match[1])
407
+ const g = parseInt(match[2])
408
+ const b = parseInt(match[3])
409
+ const a = match[4] !== undefined ? Math.round(parseFloat(match[4]) * 255) : Math.round(alpha * 255)
410
+
411
+ const aHex = a.toString(16).padStart(2, '0')
412
+ const rHex = r.toString(16).padStart(2, '0')
413
+ const gHex = g.toString(16).padStart(2, '0')
414
+ const bHex = b.toString(16).padStart(2, '0')
415
+
416
+ return `${aHex}${rHex}${gHex}${bHex}`
417
+ }
418
+
419
+ function getStyles( tableCell: HTMLTableCellElement ): CellStyles {
420
+ const styles: CellStyles = {
421
+ alignment: {
422
+ horizontal: window.getComputedStyle(tableCell).textAlign,
423
+ vertical: window.getComputedStyle(tableCell).verticalAlign,
424
+ wrapText: true
425
+ },
426
+ border: {},
427
+ font: {
428
+ name: 'Arial',
429
+ size: parseInt(window.getComputedStyle(tableCell).fontSize) * 0.75,
430
+ color: { argb: rgbToArgb(window.getComputedStyle(tableCell).color) },
431
+ bold: parseInt(window.getComputedStyle(tableCell).fontWeight) > 400
432
+ },
433
+ }
434
+
435
+ const brdTopWidth = parseInt(window.getComputedStyle(tableCell).borderTopWidth)
436
+ if (brdTopWidth) {
437
+ styles.border.top = {
438
+ style: brdTopWidth < 3 ? 'thin' : brdTopWidth < 6 ? 'medium' : 'thick',
439
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderTopColor)}
440
+ }
441
+ }
442
+
443
+ const brdRightWidth = parseInt(window.getComputedStyle(tableCell).borderRightWidth)
444
+ if (brdRightWidth) {
445
+ styles.border.right = {
446
+ style: brdRightWidth < 3 ? 'thin' : brdRightWidth < 6 ? 'medium' : 'thick',
447
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderRightColor)}
448
+ }
449
+ }
450
+
451
+ const brdBottomWidth = parseInt(window.getComputedStyle(tableCell).borderBottomWidth)
452
+ if (brdBottomWidth) {
453
+ styles.border.bottom = {
454
+ style: brdBottomWidth < 3 ? 'thin' : brdBottomWidth < 6 ? 'medium' : 'thick',
455
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderBottomColor)}
456
+ }
457
+ }
458
+
459
+ const brdLeftWidth = parseInt(window.getComputedStyle(tableCell).borderLeftWidth)
460
+ if (brdLeftWidth) {
461
+ styles.border.left = {
462
+ style: brdLeftWidth < 3 ? 'thin' : brdLeftWidth < 6 ? 'medium' : 'thick',
463
+ color: {argb: rgbToArgb(window.getComputedStyle(tableCell).borderLeftColor)}
464
+ }
465
+ }
466
+
467
+ const bgColor = rgbToArgb(window.getComputedStyle(tableCell).backgroundColor)
468
+ if (bgColor.substring(0,2) !== '00') {
469
+ styles.fill = {
470
+ type: 'pattern',
471
+ pattern: 'solid',
472
+ fgColor: {argb: bgColor}
473
+ }
474
+ }
475
+
476
+ return styles
477
+ }
478
+
479
+ </script>
480
+
481
+ <template>
482
+ <button
483
+ v-if="table"
484
+ type="button"
485
+ :title="$te('table.download.title') ? $t('table.download.title') : props.buttonTitle ?? props.buttonLabel"
486
+ :aria-label="$te('table.download.label') ? $t('table.download.label') : props.buttonLabel"
487
+ :aria-description="$te('table.download.description') ? $t('table.download.description') : props.buttonDescription"
488
+ class="srl-button"
489
+ @click="downloadTable"
490
+ >
491
+ <i class="srl-icon srl-icon-download" aria-hidden="true"></i>
492
+ <span v-text="$te('table.download.label') ? $t('table.download.label') : props.buttonLabel" />
493
+ </button>
494
+ </template>