angular-my-report 0.1.0 → 0.1.1

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 (88) hide show
  1. package/README.md +124 -40
  2. package/fesm2022/angular-my-report.mjs +7931 -0
  3. package/fesm2022/angular-my-report.mjs.map +1 -0
  4. package/index.d.ts +5 -0
  5. package/lib/engine/barcode.d.ts +17 -0
  6. package/lib/engine/chart.d.ts +26 -0
  7. package/lib/engine/chartLayout.d.ts +45 -0
  8. package/lib/engine/crosstab.d.ts +22 -0
  9. package/lib/engine/dataSources.d.ts +35 -0
  10. package/lib/engine/expressions.d.ts +46 -0
  11. package/lib/engine/measure.d.ts +7 -0
  12. package/lib/engine/parameters.d.ts +15 -0
  13. package/lib/engine/qr.d.ts +8 -0
  14. package/lib/engine/renderer.d.ts +43 -0
  15. package/lib/engine/table.d.ts +22 -0
  16. package/lib/model/defaults.d.ts +31 -0
  17. package/lib/model/factories.d.ts +31 -0
  18. package/lib/model/types.d.ts +244 -0
  19. package/lib/ui/css.d.ts +2 -0
  20. package/lib/ui/designer/banded-table-dialog.component.d.ts +27 -0
  21. package/lib/ui/designer/canvas.component.d.ts +41 -0
  22. package/lib/ui/designer/data-source-dialog.component.d.ts +37 -0
  23. package/lib/ui/designer/element-box.component.d.ts +32 -0
  24. package/lib/ui/designer/graphics.components.d.ts +36 -0
  25. package/lib/ui/designer/left-panel.component.d.ts +37 -0
  26. package/lib/ui/designer/safe-image.pipe.d.ts +13 -0
  27. package/lib/ui/designer/table-view.component.d.ts +19 -0
  28. package/lib/ui/designer.component.d.ts +59 -0
  29. package/lib/ui/designer.store.d.ts +82 -0
  30. package/lib/ui/inputs.d.ts +8 -0
  31. package/lib/ui/properties/band-props.component.d.ts +15 -0
  32. package/lib/ui/properties/element-props.component.d.ts +46 -0
  33. package/lib/ui/properties/expr-input.component.d.ts +43 -0
  34. package/lib/ui/properties/graphics-props.component.d.ts +52 -0
  35. package/lib/ui/properties/image-picker.component.d.ts +15 -0
  36. package/lib/ui/properties/properties-panel.component.d.ts +13 -0
  37. package/lib/ui/properties/report-extras.component.d.ts +40 -0
  38. package/lib/ui/properties/report-props.component.d.ts +34 -0
  39. package/lib/ui/properties/table-props.component.d.ts +31 -0
  40. package/lib/ui/viewer/page-view.component.d.ts +17 -0
  41. package/lib/ui/viewer/render.d.ts +43 -0
  42. package/lib/ui/viewer/viewer.component.d.ts +99 -0
  43. package/lib/ui/viewer.component.d.ts +40 -0
  44. package/package.json +16 -5
  45. package/{src/public-api.ts → public-api.d.ts} +0 -5
  46. package/ng-package.json +0 -7
  47. package/src/lib/engine/barcode.ts +0 -118
  48. package/src/lib/engine/chart.ts +0 -85
  49. package/src/lib/engine/chartLayout.ts +0 -265
  50. package/src/lib/engine/crosstab.ts +0 -96
  51. package/src/lib/engine/dataSources.ts +0 -204
  52. package/src/lib/engine/expressions.ts +0 -734
  53. package/src/lib/engine/measure.ts +0 -62
  54. package/src/lib/engine/parameters.ts +0 -96
  55. package/src/lib/engine/qr.ts +0 -26
  56. package/src/lib/engine/renderer.ts +0 -324
  57. package/src/lib/engine/table.ts +0 -58
  58. package/src/lib/model/defaults.ts +0 -249
  59. package/src/lib/model/factories.ts +0 -324
  60. package/src/lib/model/types.ts +0 -281
  61. package/src/lib/ui/css.ts +0 -12
  62. package/src/lib/ui/designer/banded-table-dialog.component.ts +0 -127
  63. package/src/lib/ui/designer/canvas.component.ts +0 -281
  64. package/src/lib/ui/designer/data-source-dialog.component.ts +0 -201
  65. package/src/lib/ui/designer/element-box.component.ts +0 -138
  66. package/src/lib/ui/designer/graphics.components.ts +0 -140
  67. package/src/lib/ui/designer/left-panel.component.ts +0 -189
  68. package/src/lib/ui/designer/safe-image.pipe.ts +0 -16
  69. package/src/lib/ui/designer/table-view.component.ts +0 -120
  70. package/src/lib/ui/designer.component.ts +0 -296
  71. package/src/lib/ui/designer.store.ts +0 -230
  72. package/src/lib/ui/inputs.ts +0 -34
  73. package/src/lib/ui/myreport.css +0 -267
  74. package/src/lib/ui/properties/band-props.component.ts +0 -86
  75. package/src/lib/ui/properties/element-props.component.ts +0 -326
  76. package/src/lib/ui/properties/expr-input.component.ts +0 -191
  77. package/src/lib/ui/properties/graphics-props.component.ts +0 -260
  78. package/src/lib/ui/properties/image-picker.component.ts +0 -62
  79. package/src/lib/ui/properties/properties-panel.component.ts +0 -37
  80. package/src/lib/ui/properties/report-extras.component.ts +0 -198
  81. package/src/lib/ui/properties/report-props.component.ts +0 -211
  82. package/src/lib/ui/properties/table-props.component.ts +0 -197
  83. package/src/lib/ui/viewer/page-view.component.ts +0 -73
  84. package/src/lib/ui/viewer/render.ts +0 -161
  85. package/src/lib/ui/viewer/viewer.component.ts +0 -555
  86. package/src/lib/ui/viewer.component.ts +0 -107
  87. package/tsconfig.lib.json +0 -12
  88. package/tsconfig.lib.prod.json +0 -5
@@ -1,140 +0,0 @@
1
- import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
- import type { ReportElement } from '../../model/types';
3
- import type { Row } from '../../engine/expressions';
4
- import { layoutChart } from '../../engine/chartLayout';
5
- import { encodeBarcode } from '../../engine/barcode';
6
- import { encodeQr } from '../../engine/qr';
7
-
8
- /** Chart element: SVG drawn from the engine's layout primitives. Marks carry a hover tooltip. */
9
- @Component({
10
- selector: 'rpt-chart-view',
11
- standalone: true,
12
- changeDetection: ChangeDetectionStrategy.OnPush,
13
- host: { style: 'position:absolute;inset:0;display:block' },
14
- template: `
15
- <svg [attr.width]="el().w" [attr.height]="el().h" [attr.viewBox]="'0 0 ' + el().w + ' ' + el().h" [attr.font-family]="el().style.fontFamily" style="display:block;overflow:visible">
16
- @for (p of prims(); track $index) {
17
- @switch (p.t) {
18
- @case ('path') {
19
- <path [attr.d]="p.d" [attr.fill]="p.fill" [attr.stroke]="p.stroke ?? null" [attr.stroke-width]="p.sw ?? null" [attr.fill-opacity]="p.opacity ?? null">
20
- @if (p.title) { <title>{{ p.title }}</title> }
21
- </path>
22
- }
23
- @case ('line') {
24
- <line [attr.x1]="p.x1" [attr.y1]="p.y1" [attr.x2]="p.x2" [attr.y2]="p.y2" [attr.stroke]="p.stroke" [attr.stroke-width]="p.sw" shape-rendering="crispEdges" />
25
- }
26
- @case ('poly') {
27
- <polyline [attr.points]="p.points" fill="none" [attr.stroke]="p.stroke" [attr.stroke-width]="p.sw" stroke-linejoin="round" stroke-linecap="round" />
28
- }
29
- @case ('circle') {
30
- <circle [attr.cx]="p.cx" [attr.cy]="p.cy" [attr.r]="p.r" [attr.fill]="p.fill" [attr.stroke]="p.stroke" [attr.stroke-width]="p.sw">
31
- @if (p.title) { <title>{{ p.title }}</title> }
32
- </circle>
33
- }
34
- @case ('text') {
35
- <text [attr.x]="p.x" [attr.y]="p.y" [attr.text-anchor]="p.anchor" [attr.font-size]="p.size" [attr.font-weight]="p.weight" [attr.fill]="p.fill">{{ p.text }}</text>
36
- }
37
- }
38
- }
39
- </svg>
40
- `,
41
- })
42
- export class ChartViewComponent {
43
- readonly el = input.required<ReportElement>();
44
- readonly rows = input<Row[]>([]);
45
- readonly prims = computed(() => {
46
- const el = this.el();
47
- return el.chart ? layoutChart(el.chart, this.rows(), el.w, el.h, el.style.fontSize, el.style.color) : [];
48
- });
49
- }
50
-
51
- /** Barcode element (Code 128 / EAN-13): bars as one crisp SVG path, optional human-readable text. */
52
- @Component({
53
- selector: 'rpt-barcode-view',
54
- standalone: true,
55
- changeDetection: ChangeDetectionStrategy.OnPush,
56
- host: { style: 'position:absolute;inset:0;display:block' },
57
- template: `
58
- <svg [attr.width]="el().w" [attr.height]="el().h" style="display:block">
59
- @if (model(); as m) {
60
- @if (m.error) {
61
- <text [attr.x]="el().w / 2" [attr.y]="el().h / 2" text-anchor="middle" font-size="11" fill="#c4372f">{{ m.error }}</text>
62
- } @else {
63
- <path [attr.d]="m.d" [attr.fill]="color()" shape-rendering="crispEdges" />
64
- @if (m.showText) {
65
- <text [attr.x]="el().w / 2" [attr.y]="el().h - 2" text-anchor="middle" [attr.font-size]="el().style.fontSize" [attr.font-family]="el().style.fontFamily" [attr.fill]="el().style.color">{{ m.text }}</text>
66
- }
67
- }
68
- }
69
- </svg>
70
- `,
71
- })
72
- export class BarcodeViewComponent {
73
- readonly el = input.required<ReportElement>();
74
- readonly value = input('');
75
- readonly color = computed(() => this.el().barcode?.color || '#000000');
76
- readonly model = computed(() => {
77
- const el = this.el();
78
- const spec = el.barcode ?? { symbology: 'code128' as const, showText: true, color: '#000' };
79
- const res = encodeBarcode(spec.symbology, this.value());
80
- if (res.error) return { error: res.error, d: '', text: '', showText: false };
81
- const quiet = 10; // quiet zone in modules, each side
82
- const total = res.modules.length + quiet * 2;
83
- const mw = el.w / total;
84
- const textH = spec.showText ? el.style.fontSize + 4 : 0;
85
- const barH = Math.max(4, el.h - textH);
86
- let d = '';
87
- let i = 0;
88
- while (i < res.modules.length) {
89
- if (res.modules[i] !== '1') {
90
- i++;
91
- continue;
92
- }
93
- let j = i;
94
- while (j < res.modules.length && res.modules[j] === '1') j++;
95
- const x = +((quiet + i) * mw).toFixed(3);
96
- const bw = +((j - i) * mw).toFixed(3);
97
- d += `M${x},0h${bw}v${barH}h${-bw}Z`;
98
- i = j;
99
- }
100
- return { error: '', d, text: res.text, showText: spec.showText };
101
- });
102
- }
103
-
104
- /** QR code element: dark modules as one SVG path, centred, with a 4-module quiet zone. */
105
- @Component({
106
- selector: 'rpt-qr-view',
107
- standalone: true,
108
- changeDetection: ChangeDetectionStrategy.OnPush,
109
- host: { style: 'position:absolute;inset:0;display:block' },
110
- template: `
111
- <svg [attr.width]="el().w" [attr.height]="el().h" style="display:block">
112
- @if (model(); as m) {
113
- @if (m.error) {
114
- <text [attr.x]="el().w / 2" [attr.y]="el().h / 2" text-anchor="middle" font-size="11" fill="#c4372f">{{ m.error }}</text>
115
- } @else {
116
- <path [attr.d]="m.d" [attr.fill]="el().qr?.color || '#000000'" shape-rendering="crispEdges" />
117
- }
118
- }
119
- </svg>
120
- `,
121
- })
122
- export class QrViewComponent {
123
- readonly el = input.required<ReportElement>();
124
- readonly value = input('');
125
- readonly model = computed(() => {
126
- const el = this.el();
127
- const res = encodeQr(this.value(), el.qr?.errorCorrection ?? 'M');
128
- if (res.error) return { error: res.error, d: '' };
129
- const quiet = 4;
130
- const cell = Math.min(el.w, el.h) / (res.size + quiet * 2);
131
- const ox = (el.w - cell * res.size) / 2;
132
- const oy = (el.h - cell * res.size) / 2;
133
- const c3 = +cell.toFixed(3);
134
- let d = '';
135
- for (let r = 0; r < res.size; r++)
136
- for (let c = 0; c < res.size; c++)
137
- if (res.modules[r][c]) d += `M${+(ox + c * cell).toFixed(3)},${+(oy + r * cell).toFixed(3)}h${c3}v${c3}h${-c3}Z`;
138
- return { error: '', d };
139
- });
140
- }
@@ -1,189 +0,0 @@
1
- import { ChangeDetectionStrategy, Component, computed, inject, output } from '@angular/core';
2
- import { contentWidth } from '../../model/defaults';
3
- import { DesignerStore } from '../designer.store';
4
- import { DRAG_MIME, elementFromPayload, type DragPayload } from './canvas.component';
5
-
6
- const TOOLS: { kind: 'text' | 'line' | 'rect' | 'image'; label: string; icon: string }[] = [
7
- { kind: 'text', label: 'Label', icon: 'A' },
8
- { kind: 'line', label: 'Line', icon: '―' },
9
- { kind: 'rect', label: 'Rectangle', icon: '▭' },
10
- { kind: 'image', label: 'Image', icon: '🖼' },
11
- ];
12
-
13
- type DataToolKind = 'table' | 'chart' | 'crosstab' | 'barcode' | 'qrcode';
14
- const DATA_TOOLS: { kind: DataToolKind; label: string; icon: string; hint: string }[] = [
15
- { kind: 'chart', label: 'Chart', icon: '📊', hint: 'Bar, line, area, pie or doughnut chart over the rows of its band' },
16
- { kind: 'crosstab', label: 'Cross table', icon: '⊞', hint: 'Pivot: rows × columns of a value, with totals' },
17
- { kind: 'barcode', label: 'Barcode', icon: '║', hint: 'Code 128 or EAN-13; value can be a field like {OrderID}' },
18
- { kind: 'qrcode', label: 'QR code', icon: '▣', hint: 'QR code; value can contain fields' },
19
- ];
20
-
21
- const SPECIALS: { label: string; template: string }[] = [
22
- { label: 'Page N of M', template: 'Page {PageNumber} of {TotalPages}' },
23
- { label: 'Page number', template: '{PageNumber}' },
24
- { label: 'Current date/time', template: '{Now}' },
25
- { label: 'Row number', template: '{RowNumber}' },
26
- { label: 'Report name', template: '{ReportName}' },
27
- { label: 'Record count', template: '{count()}' },
28
- ];
29
-
30
- const TYPE_ICON: Record<string, string> = { number: '#', string: 'Ab', date: '📅', boolean: '✓', object: '{}' };
31
-
32
- @Component({
33
- selector: 'rpt-left-panel',
34
- standalone: true,
35
- changeDetection: ChangeDetectionStrategy.OnPush,
36
- template: `
37
- <aside class="panel left-panel">
38
- <section>
39
- <h3>Toolbox</h3>
40
- <div class="toolbox">
41
- <div
42
- class="tool"
43
- draggable="true"
44
- title="Self-contained table: lists the rows of its band (all rows in Report Header/Footer, group rows in group bands)"
45
- (dragstart)="dragStart($event, dataPayload('table'))"
46
- (click)="add(dataPayload('table'))"
47
- >
48
- <span class="tool-icon">▦</span>Table
49
- </div>
50
- <div class="tool" title="Column titles in the header band, one field per column in Detail" (click)="openBandedTable.emit()">
51
- <span class="tool-icon">☰</span>Header/Detail
52
- </div>
53
- @for (t of dataTools; track t.kind) {
54
- <div class="tool" draggable="true" [title]="t.hint" (dragstart)="dragStart($event, dataPayload(t.kind))" (click)="add(dataPayload(t.kind))">
55
- <span class="tool-icon">{{ t.icon }}</span>{{ t.label }}
56
- </div>
57
- }
58
- @for (t of tools; track t.kind) {
59
- <div class="tool" draggable="true" (dragstart)="dragStart($event, { kind: t.kind })" (click)="add({ kind: t.kind })">
60
- <span class="tool-icon">{{ t.icon }}</span>{{ t.label }}
61
- </div>
62
- }
63
- </div>
64
- </section>
65
-
66
- <section class="grow">
67
- <h3>
68
- Data
69
- <button class="link" (click)="openDataSource.emit()">Configure…</button>
70
- </h3>
71
- <div class="ds-summary">
72
- {{ ds().type === 'odata' ? 'OData' : 'JSON' }}:
73
- <span class="muted">{{ dsLabel() }}</span>
74
- @if (ds().entitySet) {
75
- <span class="muted"> / {{ ds().entitySet }}</span>
76
- }
77
- </div>
78
- <div class="ds-status {{ data().status }}">
79
- @switch (data().status) {
80
- @case ('loading') { Loading… }
81
- @case ('ready') { {{ data().rows.length }} rows, {{ data().fields.length }} fields }
82
- @case ('error') { {{ data().error }} }
83
- @default { No data source configured }
84
- }
85
- @if (data().status !== 'idle') {
86
- <button class="link" (click)="store.reloadData()">Refresh</button>
87
- }
88
- </div>
89
- <div class="field-list">
90
- @for (f of data().fields; track f.name) {
91
- <div
92
- class="field-item"
93
- draggable="true"
94
- title="Drag onto a band or table (double-click adds to selected band). Numbers dropped in footers become sum()."
95
- (dragstart)="dragStart($event, { kind: 'field', field: f.name, type: f.type })"
96
- (dblclick)="add({ kind: 'field', field: f.name, type: f.type })"
97
- >
98
- <span class="field-type">{{ typeIcon[f.type] }}</span>{{ f.name }}
99
- </div>
100
- }
101
- </div>
102
- @if (data().docFields?.length) {
103
- <h4>Document fields</h4>
104
- <div class="field-list">
105
- @for (f of data().docFields; track f.name) {
106
- <div
107
- class="field-item"
108
- draggable="true"
109
- title="A value of the whole document (not per row), e.g. a receipt number or shop name"
110
- (dragstart)="dragStart($event, { kind: 'field', field: f.name, type: f.type })"
111
- (dblclick)="add({ kind: 'field', field: f.name, type: f.type })"
112
- >
113
- <span class="field-type">{{ typeIcon[f.type] }}</span>{{ f.name.slice(5) }}
114
- </div>
115
- }
116
- </div>
117
- }
118
- @if (params().length) {
119
- <h4>Parameters</h4>
120
- <div class="field-list">
121
- @for (p of params(); track p.name) {
122
- <div
123
- class="field-item special"
124
- draggable="true"
125
- (dragstart)="dragStart($event, { kind: 'field', field: '', template: '{@' + p.name + '}' })"
126
- (dblclick)="add({ kind: 'field', field: '', template: '{@' + p.name + '}' })"
127
- >
128
- <span class="field-type">&#64;</span>{{ p.label || p.name }}
129
- </div>
130
- }
131
- </div>
132
- }
133
- <h4>Special fields</h4>
134
- <div class="field-list">
135
- @for (s of specials; track s.label) {
136
- <div
137
- class="field-item special"
138
- draggable="true"
139
- (dragstart)="dragStart($event, { kind: 'field', field: '', template: s.template })"
140
- (dblclick)="add({ kind: 'field', field: '', template: s.template })"
141
- >
142
- <span class="field-type">ƒ</span>{{ s.label }}
143
- </div>
144
- }
145
- </div>
146
- </section>
147
- </aside>
148
- `,
149
- })
150
- export class LeftPanelComponent {
151
- protected readonly store = inject(DesignerStore);
152
- readonly openDataSource = output<void>();
153
- readonly openBandedTable = output<void>();
154
- protected readonly dataTools = DATA_TOOLS;
155
-
156
- protected readonly tools = TOOLS;
157
- protected readonly specials = SPECIALS;
158
- protected readonly typeIcon = TYPE_ICON;
159
- protected readonly data = this.store.data;
160
- protected readonly params = computed(() => (this.store.report().parameters ?? []).filter((p) => p.name));
161
- protected readonly ds = computed(() => this.store.report().dataSource);
162
- protected readonly dsLabel = computed(() => {
163
- const ds = this.ds();
164
- return ds.type === 'json' && ds.inline.trim() ? '(pasted JSON)' : ds.url || '(not set)';
165
- });
166
- /** Toolbox items that are pre-configured from the current data fields. */
167
- protected dataPayload(kind: DataToolKind): DragPayload {
168
- return { kind, fields: this.data().fields.filter((f) => f.type !== 'object') };
169
- }
170
-
171
- protected dragStart(e: DragEvent, payload: DragPayload): void {
172
- e.dataTransfer?.setData(DRAG_MIME, JSON.stringify(payload));
173
- if (e.dataTransfer) e.dataTransfer.effectAllowed = 'copy';
174
- }
175
-
176
- /** Click / double-click: add to the selected band (or Detail) at the top-left. */
177
- protected add(payload: DragPayload): void {
178
- const report = this.store.report();
179
- const band =
180
- report.bands.find((b) => b.id === this.store.selection().bandId) ?? report.bands.find((b) => b.type === 'detail')!;
181
- const el = elementFromPayload(payload, band, 0, 0, contentWidth(report.page));
182
- this.store.update((r) => {
183
- const b = r.bands.find((bb) => bb.id === band.id)!;
184
- b.elements.push(el);
185
- b.height = Math.max(b.height, el.h);
186
- });
187
- this.store.select({ elementIds: [el.id], bandId: band.id });
188
- }
189
- }
@@ -1,16 +0,0 @@
1
- import { Pipe, PipeTransform, inject } from '@angular/core';
2
- import { DomSanitizer, type SafeUrl } from '@angular/platform-browser';
3
-
4
- /**
5
- * Image source for <img [src]>. Angular's sanitizer rejects some data: images (e.g. SVG); uploads
6
- * are embedded as data:image/… URLs, so those are trusted here. An <img> never runs scripts.
7
- */
8
- @Pipe({ name: 'safeImage', standalone: true })
9
- export class SafeImagePipe implements PipeTransform {
10
- private readonly sanitizer = inject(DomSanitizer);
11
-
12
- transform(url: string | null | undefined): SafeUrl | string {
13
- if (!url) return '';
14
- return /^data:image\//i.test(url) ? this.sanitizer.bypassSecurityTrustUrl(url) : url;
15
- }
16
- }
@@ -1,120 +0,0 @@
1
- import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
2
- import type { ReportElement } from '../../model/types';
3
- import { columnWidths, hasGrid, type TableRenderData } from '../../engine/table';
4
- import { LINE_HEIGHT } from '../../engine/measure';
5
- import { css } from '../css';
6
-
7
- export type { TableRenderData };
8
-
9
- /** Data for drawing a table in the designer: the column templates themselves. */
10
- export function designTableData(el: ReportElement): TableRenderData {
11
- const t = el.table!;
12
- return {
13
- header: t.showHeader ? t.columns.map((c) => c.header) : null,
14
- headerHeight: t.headerHeight,
15
- rows: [{ cells: t.columns.map((c) => c.text), height: t.rowHeight, index: 0 }],
16
- footer: t.showFooter ? t.columns.map((c) => c.footer) : null,
17
- footerHeight: t.rowHeight,
18
- };
19
- }
20
-
21
- const JUSTIFY = { left: 'flex-start', center: 'center', right: 'flex-end' } as const;
22
- const ALIGN_ITEMS = { top: 'flex-start', middle: 'center', bottom: 'flex-end' } as const;
23
-
24
- /** Draws the inside of a table element (fills its parent box). Shared by designer and preview. */
25
- @Component({
26
- selector: 'rpt-table-view',
27
- standalone: true,
28
- changeDetection: ChangeDetectionStrategy.OnPush,
29
- host: { '[style]': 'hostStyle()' },
30
- template: `
31
- @if (data().header; as header) {
32
- <div [style]="headerRowStyle()">
33
- @for (h of header; track $index) {
34
- <div [style]="cellStyles()[$index]"><span style="width:100%">{{ h }}</span></div>
35
- }
36
- </div>
37
- }
38
- @for (r of data().rows; track r.index) {
39
- <div [style]="rowStyle(r.index, r.height)">
40
- @for (c of r.cells; track $index) {
41
- <div [style]="cellStyles()[$index]"><span style="width:100%">{{ c }}</span></div>
42
- }
43
- </div>
44
- }
45
- @if (data().footer; as footer) {
46
- <div [style]="footerRowStyle()">
47
- @for (f of footer; track $index) {
48
- <div [style]="cellStyles()[$index]"><span style="width:100%">{{ f }}</span></div>
49
- }
50
- </div>
51
- }
52
- `,
53
- })
54
- export class TableViewComponent {
55
- readonly el = input.required<ReportElement>();
56
- readonly data = input.required<TableRenderData>();
57
-
58
- private readonly line = computed(() => {
59
- const t = this.el().table!;
60
- return hasGrid(t) ? `1px solid ${t.gridColor}` : undefined;
61
- });
62
-
63
- readonly hostStyle = computed(() => {
64
- const s = this.el().style;
65
- return css({
66
- position: 'absolute',
67
- inset: 0,
68
- display: 'block',
69
- borderTop: this.line(),
70
- borderLeft: this.line(),
71
- fontFamily: s.fontFamily,
72
- fontSize: s.fontSize,
73
- fontWeight: s.bold ? 700 : 400,
74
- fontStyle: s.italic ? 'italic' : 'normal',
75
- color: s.color,
76
- overflow: 'hidden',
77
- });
78
- });
79
-
80
- readonly cellStyles = computed(() => {
81
- const el = this.el();
82
- const t = el.table!;
83
- const widths = columnWidths(el);
84
- return t.columns.map((c, i) =>
85
- css({
86
- width: widths[i],
87
- flex: 'none',
88
- display: 'flex',
89
- justifyContent: JUSTIFY[c.align],
90
- alignItems: ALIGN_ITEMS[el.style.vAlign],
91
- textAlign: c.align,
92
- padding: el.style.padding,
93
- borderRight: this.line(),
94
- borderBottom: this.line(),
95
- overflow: 'hidden',
96
- whiteSpace: 'pre-wrap',
97
- wordBreak: 'break-word',
98
- lineHeight: LINE_HEIGHT,
99
- boxSizing: 'border-box',
100
- }),
101
- );
102
- });
103
-
104
- readonly headerRowStyle = computed(() => {
105
- const t = this.el().table!;
106
- return css({ display: 'flex', height: this.data().headerHeight, background: t.headerBackground, color: t.headerColor, fontWeight: 700 });
107
- });
108
-
109
- readonly footerRowStyle = computed(() => {
110
- const t = this.el().table!;
111
- const border = hasGrid(t) ? t.gridColor : this.el().style.color;
112
- return css({ display: 'flex', height: this.data().footerHeight, fontWeight: 700, borderTop: `2px solid ${border}`, boxSizing: 'border-box' });
113
- });
114
-
115
- rowStyle(index: number, height: number): string {
116
- const t = this.el().table!;
117
- const bg = index % 2 === 1 && t.stripeBackground !== 'transparent' ? t.stripeBackground : this.el().style.background;
118
- return css({ display: 'flex', height, background: bg });
119
- }
120
- }