@beaconsoftware/b-report-table 1.0.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 (56) hide show
  1. package/dist/b-report-table-body.d.ts +16 -0
  2. package/dist/b-report-table-body.d.ts.map +1 -0
  3. package/dist/b-report-table-body.js +28 -0
  4. package/dist/b-report-table-body.js.map +1 -0
  5. package/dist/b-report-table-cell.d.ts +23 -0
  6. package/dist/b-report-table-cell.d.ts.map +1 -0
  7. package/dist/b-report-table-cell.js +103 -0
  8. package/dist/b-report-table-cell.js.map +1 -0
  9. package/dist/b-report-table-footer-cell.d.ts +25 -0
  10. package/dist/b-report-table-footer-cell.d.ts.map +1 -0
  11. package/dist/b-report-table-footer-cell.js +125 -0
  12. package/dist/b-report-table-footer-cell.js.map +1 -0
  13. package/dist/b-report-table-footer-row.d.ts +16 -0
  14. package/dist/b-report-table-footer-row.d.ts.map +1 -0
  15. package/dist/b-report-table-footer-row.js +28 -0
  16. package/dist/b-report-table-footer-row.js.map +1 -0
  17. package/dist/b-report-table-footer.d.ts +20 -0
  18. package/dist/b-report-table-footer.d.ts.map +1 -0
  19. package/dist/b-report-table-footer.js +38 -0
  20. package/dist/b-report-table-footer.js.map +1 -0
  21. package/dist/b-report-table-header-cell.d.ts +30 -0
  22. package/dist/b-report-table-header-cell.d.ts.map +1 -0
  23. package/dist/b-report-table-header-cell.js +121 -0
  24. package/dist/b-report-table-header-cell.js.map +1 -0
  25. package/dist/b-report-table-header-row.d.ts +16 -0
  26. package/dist/b-report-table-header-row.d.ts.map +1 -0
  27. package/dist/b-report-table-header-row.js +28 -0
  28. package/dist/b-report-table-header-row.js.map +1 -0
  29. package/dist/b-report-table-header.d.ts +25 -0
  30. package/dist/b-report-table-header.d.ts.map +1 -0
  31. package/dist/b-report-table-header.js +114 -0
  32. package/dist/b-report-table-header.js.map +1 -0
  33. package/dist/b-report-table-row.d.ts +16 -0
  34. package/dist/b-report-table-row.d.ts.map +1 -0
  35. package/dist/b-report-table-row.js +32 -0
  36. package/dist/b-report-table-row.js.map +1 -0
  37. package/dist/b-report-table.d.ts +32 -0
  38. package/dist/b-report-table.d.ts.map +1 -0
  39. package/dist/b-report-table.js +74 -0
  40. package/dist/b-report-table.js.map +1 -0
  41. package/dist/index.d.ts +12 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +11 -0
  44. package/dist/index.js.map +1 -0
  45. package/package.json +41 -0
  46. package/src/b-report-table-body.ts +26 -0
  47. package/src/b-report-table-cell.ts +109 -0
  48. package/src/b-report-table-footer-cell.ts +131 -0
  49. package/src/b-report-table-footer-row.ts +26 -0
  50. package/src/b-report-table-footer.ts +32 -0
  51. package/src/b-report-table-header-cell.ts +128 -0
  52. package/src/b-report-table-header-row.ts +26 -0
  53. package/src/b-report-table-header.ts +125 -0
  54. package/src/b-report-table-row.ts +30 -0
  55. package/src/b-report-table.ts +80 -0
  56. package/src/index.ts +12 -0
@@ -0,0 +1,131 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+ import type { ColumnStickyConfig } from './b-report-table.js';
4
+
5
+ /**
6
+ * A cell within a table footer row.
7
+ * Inherits sticky behavior from corresponding header columns.
8
+ *
9
+ * @slot - Default slot for cell content
10
+ */
11
+ @customElement('b-report-table-footer-cell')
12
+ export class BReportTableFooterCell extends LitElement {
13
+ static styles = css`
14
+ :host {
15
+ display: table-cell;
16
+ padding: 12px 16px;
17
+ font-weight: 600;
18
+ background: #f8fafc;
19
+ border-top: 2px solid #e2e8f0;
20
+ }
21
+
22
+ /* Vertical sticky (when parent footer has sticky attribute) */
23
+ :host([data-sticky-bottom]) {
24
+ position: sticky;
25
+ bottom: 0;
26
+ z-index: 2;
27
+ }
28
+
29
+ /* Horizontal sticky columns */
30
+ :host([data-sticky='left']),
31
+ :host([data-sticky='right']) {
32
+ position: sticky;
33
+ z-index: 2;
34
+ background: #f1f5f9;
35
+ }
36
+
37
+ /* Both vertical and horizontal sticky */
38
+ :host([data-sticky-bottom][data-sticky='left']),
39
+ :host([data-sticky-bottom][data-sticky='right']) {
40
+ z-index: 4;
41
+ }
42
+
43
+ :host([data-sticky='left']) {
44
+ left: var(--sticky-offset, 0px);
45
+ box-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ :host([data-sticky='right']) {
49
+ right: var(--sticky-offset, 0px);
50
+ box-shadow: -2px 0 4px -2px rgba(0, 0, 0, 0.1);
51
+ }
52
+ `;
53
+
54
+
55
+ connectedCallback(): void {
56
+ super.connectedCallback();
57
+ const table = this._findTable();
58
+ if (table) {
59
+ table.addEventListener('column-config-change', this._handleConfigChange);
60
+ // Check if configs are already available
61
+ this._applyInitialConfig();
62
+ }
63
+ // Check if parent footer has sticky attribute
64
+ this._updateVerticalSticky();
65
+ }
66
+
67
+ private _updateVerticalSticky(): void {
68
+ const footer = this.closest('b-report-table-footer');
69
+ if (footer?.hasAttribute('sticky')) {
70
+ this.setAttribute('data-sticky-bottom', '');
71
+ } else {
72
+ this.removeAttribute('data-sticky-bottom');
73
+ }
74
+ }
75
+
76
+ disconnectedCallback(): void {
77
+ super.disconnectedCallback();
78
+ this._findTable()?.removeEventListener(
79
+ 'column-config-change',
80
+ this._handleConfigChange
81
+ );
82
+ }
83
+
84
+ private _findTable(): HTMLElement | null {
85
+ return this.closest('b-report-table');
86
+ }
87
+
88
+ private _applyInitialConfig(): void {
89
+ const table = this._findTable() as any;
90
+ if (table?.columnConfigs) {
91
+ this._applyConfig(table.columnConfigs);
92
+ }
93
+ }
94
+
95
+ private _handleConfigChange = (event: Event): void => {
96
+ const customEvent = event as CustomEvent<{ configs: ColumnStickyConfig[] }>;
97
+ this._applyConfig(customEvent.detail.configs);
98
+ };
99
+
100
+ private _applyConfig(configs: ColumnStickyConfig[]): void {
101
+ const index = this._getColumnIndex();
102
+ const config = configs.find((c) => c.index === index);
103
+
104
+ if (config) {
105
+ if (config.sticky) {
106
+ this.setAttribute('data-sticky', config.sticky);
107
+ this.style.setProperty('--sticky-offset', `${config.offset}px`);
108
+ } else {
109
+ this.removeAttribute('data-sticky');
110
+ this.style.removeProperty('--sticky-offset');
111
+ }
112
+ }
113
+ }
114
+
115
+ private _getColumnIndex(): number {
116
+ const row = this.parentElement;
117
+ if (!row) return -1;
118
+ const cells = Array.from(row.querySelectorAll('b-report-table-footer-cell'));
119
+ return cells.indexOf(this);
120
+ }
121
+
122
+ render() {
123
+ return html`<slot></slot>`;
124
+ }
125
+ }
126
+
127
+ declare global {
128
+ interface HTMLElementTagNameMap {
129
+ 'b-report-table-footer-cell': BReportTableFooterCell;
130
+ }
131
+ }
@@ -0,0 +1,26 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A row within the table footer.
6
+ *
7
+ * @slot - Default slot for footer cells (b-report-table-footer-cell)
8
+ */
9
+ @customElement('b-report-table-footer-row')
10
+ export class BReportTableFooterRow extends LitElement {
11
+ static styles = css`
12
+ :host {
13
+ display: table-row;
14
+ }
15
+ `;
16
+
17
+ render() {
18
+ return html`<slot></slot>`;
19
+ }
20
+ }
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ 'b-report-table-footer-row': BReportTableFooterRow;
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * Table footer component that wraps footer rows.
6
+ *
7
+ * @slot - Default slot for footer rows (b-report-table-footer-row)
8
+ */
9
+ @customElement('b-report-table-footer')
10
+ export class BReportTableFooter extends LitElement {
11
+ static styles = css`
12
+ :host {
13
+ display: table-footer-group;
14
+ }
15
+ `;
16
+
17
+ /**
18
+ * Whether the footer should be sticky (fixed at the bottom when scrolling).
19
+ */
20
+ @property({ type: Boolean, reflect: true })
21
+ sticky = false;
22
+
23
+ render() {
24
+ return html`<slot></slot>`;
25
+ }
26
+ }
27
+
28
+ declare global {
29
+ interface HTMLElementTagNameMap {
30
+ 'b-report-table-footer': BReportTableFooter;
31
+ }
32
+ }
@@ -0,0 +1,128 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import type { ColumnStickyConfig } from './b-report-table.js';
4
+
5
+ /**
6
+ * A header cell within a table header row.
7
+ *
8
+ * @slot - Default slot for cell content
9
+ */
10
+ @customElement('b-report-table-header-cell')
11
+ export class BReportTableHeaderCell extends LitElement {
12
+ static styles = css`
13
+ :host {
14
+ display: table-cell;
15
+ padding: 12px 16px;
16
+ text-align: left;
17
+ font-weight: 600;
18
+ background: #f8fafc;
19
+ border-bottom: 2px solid #e2e8f0;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ /* Vertical sticky (when parent header has sticky attribute) */
24
+ :host([data-sticky-top]) {
25
+ position: sticky;
26
+ top: 0;
27
+ z-index: 2;
28
+ }
29
+
30
+ /* Horizontal sticky columns */
31
+ :host([sticky='left']),
32
+ :host([sticky='right']) {
33
+ position: sticky;
34
+ z-index: 2;
35
+ background: #f1f5f9;
36
+ }
37
+
38
+ /* Both vertical and horizontal sticky */
39
+ :host([data-sticky-top][sticky='left']),
40
+ :host([data-sticky-top][sticky='right']) {
41
+ z-index: 4;
42
+ }
43
+
44
+ :host([sticky='left']) {
45
+ left: var(--sticky-offset, 0px);
46
+ box-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.1);
47
+ }
48
+
49
+ :host([sticky='right']) {
50
+ right: var(--sticky-offset, 0px);
51
+ box-shadow: -2px 0 4px -2px rgba(0, 0, 0, 0.1);
52
+ }
53
+ `;
54
+
55
+ /**
56
+ * Determines if this column should be sticky.
57
+ * - "left": Column sticks to the left side
58
+ * - "right": Column sticks to the right side
59
+ * - null/undefined: Column is not sticky
60
+ */
61
+ @property({ type: String, reflect: true })
62
+ sticky: 'left' | 'right' | null = null;
63
+
64
+ @state()
65
+ private _offset = 0;
66
+
67
+ connectedCallback(): void {
68
+ super.connectedCallback();
69
+ this._findTable()?.addEventListener(
70
+ 'column-config-change',
71
+ this._handleConfigChange
72
+ );
73
+ // Check if parent header has sticky attribute
74
+ this._updateVerticalSticky();
75
+ }
76
+
77
+ private _updateVerticalSticky(): void {
78
+ const header = this.closest('b-report-table-header');
79
+ if (header?.hasAttribute('sticky')) {
80
+ this.setAttribute('data-sticky-top', '');
81
+ } else {
82
+ this.removeAttribute('data-sticky-top');
83
+ }
84
+ }
85
+
86
+ disconnectedCallback(): void {
87
+ super.disconnectedCallback();
88
+ this._findTable()?.removeEventListener(
89
+ 'column-config-change',
90
+ this._handleConfigChange
91
+ );
92
+ }
93
+
94
+ private _findTable(): HTMLElement | null {
95
+ return this.closest('b-report-table');
96
+ }
97
+
98
+ private _handleConfigChange = (event: Event): void => {
99
+ const customEvent = event as CustomEvent<{ configs: ColumnStickyConfig[] }>;
100
+ const configs = customEvent.detail.configs;
101
+ const index = this._getColumnIndex();
102
+
103
+ const config = configs.find((c) => c.index === index);
104
+ if (config && config.sticky) {
105
+ this._offset = config.offset;
106
+ this.style.setProperty('--sticky-offset', `${this._offset}px`);
107
+ }
108
+ };
109
+
110
+ private _getColumnIndex(): number {
111
+ const row = this.parentElement;
112
+ if (!row) return -1;
113
+ const cells = Array.from(
114
+ row.querySelectorAll('b-report-table-header-cell')
115
+ );
116
+ return cells.indexOf(this);
117
+ }
118
+
119
+ render() {
120
+ return html`<slot></slot>`;
121
+ }
122
+ }
123
+
124
+ declare global {
125
+ interface HTMLElementTagNameMap {
126
+ 'b-report-table-header-cell': BReportTableHeaderCell;
127
+ }
128
+ }
@@ -0,0 +1,26 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A row within the table header.
6
+ *
7
+ * @slot - Default slot for header cells (b-report-table-header-cell)
8
+ */
9
+ @customElement('b-report-table-header-row')
10
+ export class BReportTableHeaderRow extends LitElement {
11
+ static styles = css`
12
+ :host {
13
+ display: table-row;
14
+ }
15
+ `;
16
+
17
+ render() {
18
+ return html`<slot></slot>`;
19
+ }
20
+ }
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ 'b-report-table-header-row': BReportTableHeaderRow;
25
+ }
26
+ }
@@ -0,0 +1,125 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import type { BReportTable, ColumnStickyConfig } from './b-report-table.js';
4
+ import type { BReportTableHeaderCell } from './b-report-table-header-cell.js';
5
+
6
+ /**
7
+ * Table header component that wraps header rows.
8
+ *
9
+ * @slot - Default slot for header rows (b-report-table-header-row)
10
+ */
11
+ @customElement('b-report-table-header')
12
+ export class BReportTableHeader extends LitElement {
13
+ static styles = css`
14
+ :host {
15
+ display: table-header-group;
16
+ }
17
+ `;
18
+
19
+ /**
20
+ * Whether the header should be sticky (fixed at the top when scrolling).
21
+ */
22
+ @property({ type: Boolean, reflect: true })
23
+ sticky = false;
24
+
25
+ private _resizeObserver?: ResizeObserver;
26
+
27
+ connectedCallback(): void {
28
+ super.connectedCallback();
29
+ this._setupResizeObserver();
30
+ // Delay initial calculation to ensure all children are rendered
31
+ requestAnimationFrame(() => {
32
+ this._calculateColumnConfigs();
33
+ });
34
+ }
35
+
36
+ disconnectedCallback(): void {
37
+ super.disconnectedCallback();
38
+ this._resizeObserver?.disconnect();
39
+ }
40
+
41
+ private _setupResizeObserver(): void {
42
+ this._resizeObserver = new ResizeObserver(() => {
43
+ this._calculateColumnConfigs();
44
+ });
45
+ this._resizeObserver.observe(this);
46
+ }
47
+
48
+ private _calculateColumnConfigs(): void {
49
+ const table = this.closest('b-report-table') as BReportTable | null;
50
+ if (!table) return;
51
+
52
+ // Find the first header row to get column configurations
53
+ const firstRow = this.querySelector('b-report-table-header-row');
54
+ if (!firstRow) return;
55
+
56
+ const cells = Array.from(
57
+ firstRow.querySelectorAll('b-report-table-header-cell')
58
+ ) as BReportTableHeaderCell[];
59
+
60
+ const configs: ColumnStickyConfig[] = [];
61
+ let leftOffset = 0;
62
+ let rightOffset = 0;
63
+
64
+ // First pass: collect all cells and their sticky values
65
+ const cellData = cells.map((cell, index) => ({
66
+ index,
67
+ sticky: cell.sticky as 'left' | 'right' | null,
68
+ width: cell.offsetWidth || 100,
69
+ }));
70
+
71
+ // Calculate left sticky offsets
72
+ for (const data of cellData) {
73
+ if (data.sticky === 'left') {
74
+ configs.push({
75
+ index: data.index,
76
+ sticky: 'left',
77
+ width: data.width,
78
+ offset: leftOffset,
79
+ });
80
+ leftOffset += data.width;
81
+ }
82
+ }
83
+
84
+ // Calculate right sticky offsets (from right to left)
85
+ for (let i = cellData.length - 1; i >= 0; i--) {
86
+ const data = cellData[i];
87
+ if (data.sticky === 'right') {
88
+ configs.push({
89
+ index: data.index,
90
+ sticky: 'right',
91
+ width: data.width,
92
+ offset: rightOffset,
93
+ });
94
+ rightOffset += data.width;
95
+ }
96
+ }
97
+
98
+ // Add non-sticky columns
99
+ for (const data of cellData) {
100
+ if (data.sticky !== 'left' && data.sticky !== 'right') {
101
+ configs.push({
102
+ index: data.index,
103
+ sticky: null,
104
+ width: data.width,
105
+ offset: 0,
106
+ });
107
+ }
108
+ }
109
+
110
+ // Sort by index
111
+ configs.sort((a, b) => a.index - b.index);
112
+
113
+ table.updateColumnConfigs(configs);
114
+ }
115
+
116
+ render() {
117
+ return html`<slot></slot>`;
118
+ }
119
+ }
120
+
121
+ declare global {
122
+ interface HTMLElementTagNameMap {
123
+ 'b-report-table-header': BReportTableHeader;
124
+ }
125
+ }
@@ -0,0 +1,30 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+
4
+ /**
5
+ * A row within the table body.
6
+ *
7
+ * @slot - Default slot for cells (b-report-table-cell)
8
+ */
9
+ @customElement('b-report-table-row')
10
+ export class BReportTableRow extends LitElement {
11
+ static styles = css`
12
+ :host {
13
+ display: table-row;
14
+ }
15
+
16
+ :host(:hover) {
17
+ background: #f8fafc;
18
+ }
19
+ `;
20
+
21
+ render() {
22
+ return html`<slot></slot>`;
23
+ }
24
+ }
25
+
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ 'b-report-table-row': BReportTableRow;
29
+ }
30
+ }
@@ -0,0 +1,80 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, state } from 'lit/decorators.js';
3
+
4
+ export interface ColumnStickyConfig {
5
+ index: number;
6
+ sticky: 'left' | 'right' | null;
7
+ width: number;
8
+ offset: number;
9
+ }
10
+
11
+ /**
12
+ * A report table component with support for sticky headers, footers, and columns.
13
+ *
14
+ * @slot header - The table header (b-report-table-header)
15
+ * @slot - Default slot for table body (b-report-table-body)
16
+ * @slot footer - The table footer (b-report-table-footer)
17
+ */
18
+ @customElement('b-report-table')
19
+ export class BReportTable extends LitElement {
20
+ static styles = css`
21
+ :host {
22
+ display: block;
23
+ }
24
+
25
+ table {
26
+ width: 100%;
27
+ border-collapse: separate;
28
+ border-spacing: 0;
29
+ font-family: system-ui, -apple-system, sans-serif;
30
+ font-size: 14px;
31
+ }
32
+
33
+ ::slotted(*) {
34
+ display: table-row-group;
35
+ }
36
+ `;
37
+
38
+ @state()
39
+ private _columnConfigs: ColumnStickyConfig[] = [];
40
+
41
+ get columnConfigs(): ColumnStickyConfig[] {
42
+ return this._columnConfigs;
43
+ }
44
+
45
+ /**
46
+ * Updates the sticky column configuration based on header cells.
47
+ * This is called by the header component when it's connected or updated.
48
+ */
49
+ updateColumnConfigs(configs: ColumnStickyConfig[]): void {
50
+ this._columnConfigs = configs;
51
+ this._notifyChildren();
52
+ }
53
+
54
+ private _notifyChildren(): void {
55
+ // Dispatch event to notify all children of column config changes
56
+ this.dispatchEvent(
57
+ new CustomEvent('column-config-change', {
58
+ detail: { configs: this._columnConfigs },
59
+ bubbles: true,
60
+ composed: true,
61
+ })
62
+ );
63
+ }
64
+
65
+ render() {
66
+ return html`
67
+ <table role="table">
68
+ <slot name="header"></slot>
69
+ <slot></slot>
70
+ <slot name="footer"></slot>
71
+ </table>
72
+ `;
73
+ }
74
+ }
75
+
76
+ declare global {
77
+ interface HTMLElementTagNameMap {
78
+ 'b-report-table': BReportTable;
79
+ }
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export { BReportTable } from './b-report-table.js';
2
+ export { BReportTableHeader } from './b-report-table-header.js';
3
+ export { BReportTableHeaderRow } from './b-report-table-header-row.js';
4
+ export { BReportTableHeaderCell } from './b-report-table-header-cell.js';
5
+ export { BReportTableBody } from './b-report-table-body.js';
6
+ export { BReportTableRow } from './b-report-table-row.js';
7
+ export { BReportTableCell } from './b-report-table-cell.js';
8
+ export { BReportTableFooter } from './b-report-table-footer.js';
9
+ export { BReportTableFooterRow } from './b-report-table-footer-row.js';
10
+ export { BReportTableFooterCell } from './b-report-table-footer-cell.js';
11
+
12
+ export type { ColumnStickyConfig } from './b-report-table.js';