@kanso-protocol/table 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.
@@ -0,0 +1,367 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Input, Directive, EventEmitter, ContentChildren, Output, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgTemplateOutlet } from '@angular/common';
4
+ import { KpCheckboxComponent } from '@kanso-protocol/checkbox';
5
+
6
+ /** `<ng-template kpTableCell="columnId" let-row let-i="index">` — custom cell renderer per column. */
7
+ class KpTableCellDirective {
8
+ template;
9
+ columnId;
10
+ constructor(template) {
11
+ this.template = template;
12
+ }
13
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableCellDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
14
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: KpTableCellDirective, isStandalone: true, selector: "ng-template[kpTableCell]", inputs: { columnId: ["kpTableCell", "columnId"] }, ngImport: i0 });
15
+ }
16
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableCellDirective, decorators: [{
17
+ type: Directive,
18
+ args: [{ selector: 'ng-template[kpTableCell]', standalone: true }]
19
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { columnId: [{
20
+ type: Input,
21
+ args: ['kpTableCell']
22
+ }] } });
23
+ /** `<ng-template kpTableHeader="columnId">` — custom header renderer per column. */
24
+ class KpTableHeaderDirective {
25
+ template;
26
+ columnId;
27
+ constructor(template) {
28
+ this.template = template;
29
+ }
30
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableHeaderDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
31
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: KpTableHeaderDirective, isStandalone: true, selector: "ng-template[kpTableHeader]", inputs: { columnId: ["kpTableHeader", "columnId"] }, ngImport: i0 });
32
+ }
33
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableHeaderDirective, decorators: [{
34
+ type: Directive,
35
+ args: [{ selector: 'ng-template[kpTableHeader]', standalone: true }]
36
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { columnId: [{
37
+ type: Input,
38
+ args: ['kpTableHeader']
39
+ }] } });
40
+ /**
41
+ * Kanso Protocol — Table
42
+ *
43
+ * Data-driven table with a columns schema, sortable headers, optional
44
+ * row selection, striped / bordered variants, and template-per-column
45
+ * cell rendering via `<ng-template kpTableCell="id">`.
46
+ *
47
+ * @example
48
+ * <kp-table [columns]="cols" [data]="rows" [selectable]="true" [(selected)]="picked">
49
+ * <ng-template kpTableCell="status" let-row>
50
+ * <kp-badge [color]="row.active ? 'success' : 'neutral'">
51
+ * {{ row.active ? 'Active' : 'Inactive' }}
52
+ * </kp-badge>
53
+ * </ng-template>
54
+ * </kp-table>
55
+ */
56
+ class KpTableComponent {
57
+ size = 'md';
58
+ columns = [];
59
+ data = [];
60
+ striped = false;
61
+ bordered = false;
62
+ selectable = false;
63
+ selected = [];
64
+ sort = null;
65
+ emptyMessage = 'No data';
66
+ /** Override how rows are tracked in the `@for` loop (default: index). */
67
+ trackBy = null;
68
+ selectedChange = new EventEmitter();
69
+ sortChange = new EventEmitter();
70
+ rowClick = new EventEmitter();
71
+ cellTemplates;
72
+ headerTemplates;
73
+ get hostClasses() {
74
+ const c = ['kp-table', `kp-table--${this.size}`];
75
+ if (this.striped)
76
+ c.push('kp-table--striped');
77
+ if (this.bordered)
78
+ c.push('kp-table--bordered');
79
+ if (this.selectable)
80
+ c.push('kp-table--selectable');
81
+ return c.join(' ');
82
+ }
83
+ cellAlignClass(col) {
84
+ return col.align === 'right' ? 'kp-table__cell--right'
85
+ : col.align === 'center' ? 'kp-table__cell--center'
86
+ : '';
87
+ }
88
+ columnCount() {
89
+ return this.columns.length + (this.selectable ? 1 : 0);
90
+ }
91
+ cellTemplate(columnId) {
92
+ return this.cellTemplates?.find((d) => d.columnId === columnId)?.template ?? null;
93
+ }
94
+ headerTemplate(columnId) {
95
+ return this.headerTemplates?.find((d) => d.columnId === columnId)?.template ?? null;
96
+ }
97
+ trackRow(row, index) {
98
+ return this.trackBy ? this.trackBy(row, index) : index;
99
+ }
100
+ isSelected(row) { return this.selected.includes(row); }
101
+ allSelected() { return this.data.length > 0 && this.selected.length === this.data.length; }
102
+ someSelected() { return this.selected.length > 0; }
103
+ toggleRow(row) {
104
+ const next = this.isSelected(row)
105
+ ? this.selected.filter((r) => r !== row)
106
+ : [...this.selected, row];
107
+ this.selected = next;
108
+ this.selectedChange.emit(next);
109
+ }
110
+ toggleAll(checked) {
111
+ const next = checked ? [...this.data] : [];
112
+ this.selected = next;
113
+ this.selectedChange.emit(next);
114
+ }
115
+ onRowClick(row, _event) {
116
+ this.rowClick.emit(row);
117
+ if (this.selectable)
118
+ this.toggleRow(row);
119
+ }
120
+ onHeaderClick(col) {
121
+ if (!col.sortable)
122
+ return;
123
+ let next;
124
+ if (!this.sort || this.sort.columnId !== col.id) {
125
+ next = { columnId: col.id, direction: 'asc' };
126
+ }
127
+ else if (this.sort.direction === 'asc') {
128
+ next = { columnId: col.id, direction: 'desc' };
129
+ }
130
+ else {
131
+ next = null; // clear sort on third click
132
+ }
133
+ this.sort = next;
134
+ this.sortChange.emit(next);
135
+ }
136
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
137
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: KpTableComponent, isStandalone: true, selector: "kp-table", inputs: { size: "size", columns: "columns", data: "data", striped: "striped", bordered: "bordered", selectable: "selectable", selected: "selected", sort: "sort", emptyMessage: "emptyMessage", trackBy: "trackBy" }, outputs: { selectedChange: "selectedChange", sortChange: "sortChange", rowClick: "rowClick" }, host: { properties: { "class": "hostClasses" } }, queries: [{ propertyName: "cellTemplates", predicate: KpTableCellDirective }, { propertyName: "headerTemplates", predicate: KpTableHeaderDirective }], ngImport: i0, template: `
138
+ <table class="kp-table__el">
139
+ <thead class="kp-table__head">
140
+ <tr class="kp-table__head-row">
141
+ @if (selectable) {
142
+ <th class="kp-table__cell kp-table__cell--checkbox" scope="col">
143
+ <kp-checkbox
144
+ size="sm"
145
+ [hasLabel]="false"
146
+ [checked]="allSelected()"
147
+ [indeterminate]="someSelected() && !allSelected()"
148
+ (checkedChange)="toggleAll($event)"
149
+ />
150
+ </th>
151
+ }
152
+ @for (col of columns; track col.id) {
153
+ <th
154
+ class="kp-table__cell kp-table__cell--header"
155
+ [class]="cellAlignClass(col)"
156
+ [style.width]="col.width ?? null"
157
+ scope="col"
158
+ >
159
+ <button
160
+ type="button"
161
+ class="kp-table__header-button"
162
+ [class.kp-table__header-button--sortable]="col.sortable"
163
+ [class.kp-table__header-button--active]="col.sortable && sort?.columnId === col.id"
164
+ [attr.aria-sort]="col.sortable ? (sort?.columnId === col.id ? (sort?.direction === 'asc' ? 'ascending' : 'descending') : 'none') : null"
165
+ [disabled]="!col.sortable"
166
+ (click)="onHeaderClick(col)"
167
+ >
168
+ @if (headerTemplate(col.id); as tpl) {
169
+ <ng-container *ngTemplateOutlet="tpl"></ng-container>
170
+ } @else {
171
+ {{ col.label }}
172
+ }
173
+ @if (col.sortable) {
174
+ <span class="kp-table__sort-icon" aria-hidden="true">
175
+ @if (sort?.columnId === col.id) {
176
+ @if (sort!.direction === 'asc') {
177
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M3 7l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
178
+ } @else {
179
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
180
+ }
181
+ } @else {
182
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M4 4.5l2-2 2 2M4 7.5l2 2 2-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
183
+ }
184
+ </span>
185
+ }
186
+ </button>
187
+ </th>
188
+ }
189
+ </tr>
190
+ </thead>
191
+
192
+ <tbody class="kp-table__body">
193
+ @for (row of data; track trackRow(row, $index); let i = $index) {
194
+ <tr
195
+ class="kp-table__row"
196
+ [class.kp-table__row--selected]="isSelected(row)"
197
+ (click)="onRowClick(row, $event)"
198
+ >
199
+ @if (selectable) {
200
+ <td class="kp-table__cell kp-table__cell--checkbox" (click)="$event.stopPropagation()">
201
+ <kp-checkbox
202
+ size="sm"
203
+ [hasLabel]="false"
204
+ [checked]="isSelected(row)"
205
+ (checkedChange)="toggleRow(row)"
206
+ />
207
+ </td>
208
+ }
209
+ @for (col of columns; track col.id) {
210
+ <td class="kp-table__cell" [class]="cellAlignClass(col)">
211
+ @if (cellTemplate(col.id); as tpl) {
212
+ <ng-container *ngTemplateOutlet="tpl; context: { $implicit: row, index: i, column: col }"></ng-container>
213
+ } @else {
214
+ {{ col.accessor ? col.accessor(row) : '' }}
215
+ }
216
+ </td>
217
+ }
218
+ </tr>
219
+ }
220
+
221
+ @if (data.length === 0) {
222
+ <tr class="kp-table__row kp-table__row--empty">
223
+ <td class="kp-table__cell kp-table__cell--empty" [attr.colspan]="columnCount()">
224
+ {{ emptyMessage }}
225
+ </td>
226
+ </tr>
227
+ }
228
+ </tbody>
229
+ </table>
230
+ `, isInline: true, styles: [":host{display:block;width:100%;font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);--kp-table-row-h: 48px;--kp-table-pad-x: 16px;--kp-table-fs: 14px;--kp-table-lh: 20px}:host(.kp-table--sm){--kp-table-row-h: 40px;--kp-table-pad-x: 12px;--kp-table-fs: 13px;--kp-table-lh: 18px}:host(.kp-table--md){--kp-table-row-h: 48px;--kp-table-pad-x: 16px;--kp-table-fs: 14px;--kp-table-lh: 20px}:host(.kp-table--lg){--kp-table-row-h: 56px;--kp-table-pad-x: 20px;--kp-table-fs: 15px;--kp-table-lh: 22px}:host(.kp-table--bordered) .kp-table__el{border:1px solid var(--kp-color-table-border, var(--kp-color-gray-200));border-radius:12px;overflow:hidden}.kp-table__el{width:100%;border-collapse:collapse;font-size:var(--kp-table-fs);line-height:var(--kp-table-lh);color:var(--kp-color-table-row-fg, var(--kp-color-gray-900))}.kp-table__head-row{background:var(--kp-color-table-header-bg, var(--kp-color-gray-50));border-bottom:1px solid var(--kp-color-table-border, var(--kp-color-gray-200))}.kp-table__cell{padding:0 var(--kp-table-pad-x);height:var(--kp-table-row-h);vertical-align:middle;text-align:start;color:inherit}.kp-table__cell--center{text-align:center}.kp-table__cell--right{text-align:end}.kp-table__cell--checkbox{width:44px;padding-inline-end:0}.kp-table__cell--header{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.04em;color:var(--kp-color-table-header-fg, var(--kp-color-gray-700));white-space:nowrap}.kp-table__header-button{all:unset;display:inline-flex;align-items:center;gap:6px;width:100%;color:inherit}.kp-table__header-button--sortable{cursor:pointer}.kp-table__header-button--sortable:hover{color:var(--kp-color-table-row-fg, var(--kp-color-gray-900))}.kp-table__header-button--active{color:var(--kp-color-table-header-sort-active, var(--kp-color-blue-600))}.kp-table__cell--right .kp-table__header-button{justify-content:flex-end}.kp-table__cell--center .kp-table__header-button{justify-content:center}.kp-table__sort-icon{display:inline-flex;color:var(--kp-color-table-header-sort-icon, var(--kp-color-gray-500))}.kp-table__header-button--active .kp-table__sort-icon{color:var(--kp-color-table-header-sort-active, var(--kp-color-blue-600))}.kp-table__row{background:var(--kp-color-table-row-bg-rest, var(--kp-color-white));border-bottom:1px solid var(--kp-color-table-border-soft, var(--kp-color-gray-100));transition:background var(--kp-motion-duration-fast) ease}.kp-table__row:hover:not(.kp-table__row--selected):not(.kp-table__row--empty){background:var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50))}:host(.kp-table--striped) .kp-table__row:nth-child(2n):not(.kp-table__row--selected){background:var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50))}.kp-table__row--selected{background:var(--kp-color-table-row-bg-selected, var(--kp-color-blue-50))!important}.kp-table__row:last-child{border-bottom:0}:host(.kp-table--selectable) .kp-table__row:not(.kp-table__row--empty){cursor:pointer}.kp-table__cell--empty{text-align:center;color:var(--kp-color-table-header-fg, var(--kp-color-gray-500));padding:32px 16px;font-size:13px}.kp-table__cell--checkbox kp-checkbox{display:inline-flex}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: KpCheckboxComponent, selector: "kp-checkbox", inputs: ["size", "color", "checked", "indeterminate", "disabled", "forceState", "hasLabel"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
231
+ }
232
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpTableComponent, decorators: [{
233
+ type: Component,
234
+ args: [{ selector: 'kp-table', imports: [NgTemplateOutlet, KpCheckboxComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class]': 'hostClasses' }, template: `
235
+ <table class="kp-table__el">
236
+ <thead class="kp-table__head">
237
+ <tr class="kp-table__head-row">
238
+ @if (selectable) {
239
+ <th class="kp-table__cell kp-table__cell--checkbox" scope="col">
240
+ <kp-checkbox
241
+ size="sm"
242
+ [hasLabel]="false"
243
+ [checked]="allSelected()"
244
+ [indeterminate]="someSelected() && !allSelected()"
245
+ (checkedChange)="toggleAll($event)"
246
+ />
247
+ </th>
248
+ }
249
+ @for (col of columns; track col.id) {
250
+ <th
251
+ class="kp-table__cell kp-table__cell--header"
252
+ [class]="cellAlignClass(col)"
253
+ [style.width]="col.width ?? null"
254
+ scope="col"
255
+ >
256
+ <button
257
+ type="button"
258
+ class="kp-table__header-button"
259
+ [class.kp-table__header-button--sortable]="col.sortable"
260
+ [class.kp-table__header-button--active]="col.sortable && sort?.columnId === col.id"
261
+ [attr.aria-sort]="col.sortable ? (sort?.columnId === col.id ? (sort?.direction === 'asc' ? 'ascending' : 'descending') : 'none') : null"
262
+ [disabled]="!col.sortable"
263
+ (click)="onHeaderClick(col)"
264
+ >
265
+ @if (headerTemplate(col.id); as tpl) {
266
+ <ng-container *ngTemplateOutlet="tpl"></ng-container>
267
+ } @else {
268
+ {{ col.label }}
269
+ }
270
+ @if (col.sortable) {
271
+ <span class="kp-table__sort-icon" aria-hidden="true">
272
+ @if (sort?.columnId === col.id) {
273
+ @if (sort!.direction === 'asc') {
274
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M3 7l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
275
+ } @else {
276
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M3 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
277
+ }
278
+ } @else {
279
+ <svg viewBox="0 0 12 12" fill="none" width="10" height="10"><path d="M4 4.5l2-2 2 2M4 7.5l2 2 2-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
280
+ }
281
+ </span>
282
+ }
283
+ </button>
284
+ </th>
285
+ }
286
+ </tr>
287
+ </thead>
288
+
289
+ <tbody class="kp-table__body">
290
+ @for (row of data; track trackRow(row, $index); let i = $index) {
291
+ <tr
292
+ class="kp-table__row"
293
+ [class.kp-table__row--selected]="isSelected(row)"
294
+ (click)="onRowClick(row, $event)"
295
+ >
296
+ @if (selectable) {
297
+ <td class="kp-table__cell kp-table__cell--checkbox" (click)="$event.stopPropagation()">
298
+ <kp-checkbox
299
+ size="sm"
300
+ [hasLabel]="false"
301
+ [checked]="isSelected(row)"
302
+ (checkedChange)="toggleRow(row)"
303
+ />
304
+ </td>
305
+ }
306
+ @for (col of columns; track col.id) {
307
+ <td class="kp-table__cell" [class]="cellAlignClass(col)">
308
+ @if (cellTemplate(col.id); as tpl) {
309
+ <ng-container *ngTemplateOutlet="tpl; context: { $implicit: row, index: i, column: col }"></ng-container>
310
+ } @else {
311
+ {{ col.accessor ? col.accessor(row) : '' }}
312
+ }
313
+ </td>
314
+ }
315
+ </tr>
316
+ }
317
+
318
+ @if (data.length === 0) {
319
+ <tr class="kp-table__row kp-table__row--empty">
320
+ <td class="kp-table__cell kp-table__cell--empty" [attr.colspan]="columnCount()">
321
+ {{ emptyMessage }}
322
+ </td>
323
+ </tr>
324
+ }
325
+ </tbody>
326
+ </table>
327
+ `, styles: [":host{display:block;width:100%;font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);--kp-table-row-h: 48px;--kp-table-pad-x: 16px;--kp-table-fs: 14px;--kp-table-lh: 20px}:host(.kp-table--sm){--kp-table-row-h: 40px;--kp-table-pad-x: 12px;--kp-table-fs: 13px;--kp-table-lh: 18px}:host(.kp-table--md){--kp-table-row-h: 48px;--kp-table-pad-x: 16px;--kp-table-fs: 14px;--kp-table-lh: 20px}:host(.kp-table--lg){--kp-table-row-h: 56px;--kp-table-pad-x: 20px;--kp-table-fs: 15px;--kp-table-lh: 22px}:host(.kp-table--bordered) .kp-table__el{border:1px solid var(--kp-color-table-border, var(--kp-color-gray-200));border-radius:12px;overflow:hidden}.kp-table__el{width:100%;border-collapse:collapse;font-size:var(--kp-table-fs);line-height:var(--kp-table-lh);color:var(--kp-color-table-row-fg, var(--kp-color-gray-900))}.kp-table__head-row{background:var(--kp-color-table-header-bg, var(--kp-color-gray-50));border-bottom:1px solid var(--kp-color-table-border, var(--kp-color-gray-200))}.kp-table__cell{padding:0 var(--kp-table-pad-x);height:var(--kp-table-row-h);vertical-align:middle;text-align:start;color:inherit}.kp-table__cell--center{text-align:center}.kp-table__cell--right{text-align:end}.kp-table__cell--checkbox{width:44px;padding-inline-end:0}.kp-table__cell--header{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.04em;color:var(--kp-color-table-header-fg, var(--kp-color-gray-700));white-space:nowrap}.kp-table__header-button{all:unset;display:inline-flex;align-items:center;gap:6px;width:100%;color:inherit}.kp-table__header-button--sortable{cursor:pointer}.kp-table__header-button--sortable:hover{color:var(--kp-color-table-row-fg, var(--kp-color-gray-900))}.kp-table__header-button--active{color:var(--kp-color-table-header-sort-active, var(--kp-color-blue-600))}.kp-table__cell--right .kp-table__header-button{justify-content:flex-end}.kp-table__cell--center .kp-table__header-button{justify-content:center}.kp-table__sort-icon{display:inline-flex;color:var(--kp-color-table-header-sort-icon, var(--kp-color-gray-500))}.kp-table__header-button--active .kp-table__sort-icon{color:var(--kp-color-table-header-sort-active, var(--kp-color-blue-600))}.kp-table__row{background:var(--kp-color-table-row-bg-rest, var(--kp-color-white));border-bottom:1px solid var(--kp-color-table-border-soft, var(--kp-color-gray-100));transition:background var(--kp-motion-duration-fast) ease}.kp-table__row:hover:not(.kp-table__row--selected):not(.kp-table__row--empty){background:var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50))}:host(.kp-table--striped) .kp-table__row:nth-child(2n):not(.kp-table__row--selected){background:var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50))}.kp-table__row--selected{background:var(--kp-color-table-row-bg-selected, var(--kp-color-blue-50))!important}.kp-table__row:last-child{border-bottom:0}:host(.kp-table--selectable) .kp-table__row:not(.kp-table__row--empty){cursor:pointer}.kp-table__cell--empty{text-align:center;color:var(--kp-color-table-header-fg, var(--kp-color-gray-500));padding:32px 16px;font-size:13px}.kp-table__cell--checkbox kp-checkbox{display:inline-flex}\n"] }]
328
+ }], propDecorators: { size: [{
329
+ type: Input
330
+ }], columns: [{
331
+ type: Input
332
+ }], data: [{
333
+ type: Input
334
+ }], striped: [{
335
+ type: Input
336
+ }], bordered: [{
337
+ type: Input
338
+ }], selectable: [{
339
+ type: Input
340
+ }], selected: [{
341
+ type: Input
342
+ }], sort: [{
343
+ type: Input
344
+ }], emptyMessage: [{
345
+ type: Input
346
+ }], trackBy: [{
347
+ type: Input
348
+ }], selectedChange: [{
349
+ type: Output
350
+ }], sortChange: [{
351
+ type: Output
352
+ }], rowClick: [{
353
+ type: Output
354
+ }], cellTemplates: [{
355
+ type: ContentChildren,
356
+ args: [KpTableCellDirective]
357
+ }], headerTemplates: [{
358
+ type: ContentChildren,
359
+ args: [KpTableHeaderDirective]
360
+ }] } });
361
+
362
+ /**
363
+ * Generated bundle index. Do not edit.
364
+ */
365
+
366
+ export { KpTableCellDirective, KpTableComponent, KpTableHeaderDirective };
367
+ //# sourceMappingURL=kanso-protocol-table.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanso-protocol-table.mjs","sources":["../../../../../packages/components/table/src/table.component.ts","../../../../../packages/components/table/src/kanso-protocol-table.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ContentChildren,\n Directive,\n EventEmitter,\n Input,\n Output,\n QueryList,\n TemplateRef,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { KpCheckboxComponent } from '@kanso-protocol/checkbox';\n\nexport type KpTableSize = 'sm' | 'md' | 'lg';\nexport type KpTableSortDirection = 'asc' | 'desc';\n\nexport interface KpTableColumn<T = unknown> {\n /** Stable id matched against `<ng-template kpTableCell=\"id\">` projections. */\n id: string;\n /** Header text. */\n label: string;\n /** Cell alignment. */\n align?: 'left' | 'center' | 'right';\n /** Enable sorting for this column. */\n sortable?: boolean;\n /** Flex-basis for the column (CSS length). Defaults to `auto` (content-sized). */\n width?: string;\n /** Fallback accessor when no `kpTableCell` template is provided. */\n accessor?: (row: T) => unknown;\n}\n\nexport interface KpTableSort {\n columnId: string;\n direction: KpTableSortDirection;\n}\n\n/** `<ng-template kpTableCell=\"columnId\" let-row let-i=\"index\">` — custom cell renderer per column. */\n@Directive({ selector: 'ng-template[kpTableCell]', standalone: true })\nexport class KpTableCellDirective {\n @Input('kpTableCell') columnId!: string;\n constructor(public readonly template: TemplateRef<unknown>) {}\n}\n\n/** `<ng-template kpTableHeader=\"columnId\">` — custom header renderer per column. */\n@Directive({ selector: 'ng-template[kpTableHeader]', standalone: true })\nexport class KpTableHeaderDirective {\n @Input('kpTableHeader') columnId!: string;\n constructor(public readonly template: TemplateRef<unknown>) {}\n}\n\n/**\n * Kanso Protocol — Table\n *\n * Data-driven table with a columns schema, sortable headers, optional\n * row selection, striped / bordered variants, and template-per-column\n * cell rendering via `<ng-template kpTableCell=\"id\">`.\n *\n * @example\n * <kp-table [columns]=\"cols\" [data]=\"rows\" [selectable]=\"true\" [(selected)]=\"picked\">\n * <ng-template kpTableCell=\"status\" let-row>\n * <kp-badge [color]=\"row.active ? 'success' : 'neutral'\">\n * {{ row.active ? 'Active' : 'Inactive' }}\n * </kp-badge>\n * </ng-template>\n * </kp-table>\n */\n@Component({\n selector: 'kp-table',\n imports: [NgTemplateOutlet, KpCheckboxComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { '[class]': 'hostClasses' },\n template: `\n <table class=\"kp-table__el\">\n <thead class=\"kp-table__head\">\n <tr class=\"kp-table__head-row\">\n @if (selectable) {\n <th class=\"kp-table__cell kp-table__cell--checkbox\" scope=\"col\">\n <kp-checkbox\n size=\"sm\"\n [hasLabel]=\"false\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected() && !allSelected()\"\n (checkedChange)=\"toggleAll($event)\"\n />\n </th>\n }\n @for (col of columns; track col.id) {\n <th\n class=\"kp-table__cell kp-table__cell--header\"\n [class]=\"cellAlignClass(col)\"\n [style.width]=\"col.width ?? null\"\n scope=\"col\"\n >\n <button\n type=\"button\"\n class=\"kp-table__header-button\"\n [class.kp-table__header-button--sortable]=\"col.sortable\"\n [class.kp-table__header-button--active]=\"col.sortable && sort?.columnId === col.id\"\n [attr.aria-sort]=\"col.sortable ? (sort?.columnId === col.id ? (sort?.direction === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n [disabled]=\"!col.sortable\"\n (click)=\"onHeaderClick(col)\"\n >\n @if (headerTemplate(col.id); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl\"></ng-container>\n } @else {\n {{ col.label }}\n }\n @if (col.sortable) {\n <span class=\"kp-table__sort-icon\" aria-hidden=\"true\">\n @if (sort?.columnId === col.id) {\n @if (sort!.direction === 'asc') {\n <svg viewBox=\"0 0 12 12\" fill=\"none\" width=\"10\" height=\"10\"><path d=\"M3 7l3-3 3 3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n } @else {\n <svg viewBox=\"0 0 12 12\" fill=\"none\" width=\"10\" height=\"10\"><path d=\"M3 5l3 3 3-3\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n }\n } @else {\n <svg viewBox=\"0 0 12 12\" fill=\"none\" width=\"10\" height=\"10\"><path d=\"M4 4.5l2-2 2 2M4 7.5l2 2 2-2\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n }\n </span>\n }\n </button>\n </th>\n }\n </tr>\n </thead>\n\n <tbody class=\"kp-table__body\">\n @for (row of data; track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kp-table__row\"\n [class.kp-table__row--selected]=\"isSelected(row)\"\n (click)=\"onRowClick(row, $event)\"\n >\n @if (selectable) {\n <td class=\"kp-table__cell kp-table__cell--checkbox\" (click)=\"$event.stopPropagation()\">\n <kp-checkbox\n size=\"sm\"\n [hasLabel]=\"false\"\n [checked]=\"isSelected(row)\"\n (checkedChange)=\"toggleRow(row)\"\n />\n </td>\n }\n @for (col of columns; track col.id) {\n <td class=\"kp-table__cell\" [class]=\"cellAlignClass(col)\">\n @if (cellTemplate(col.id); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: row, index: i, column: col }\"></ng-container>\n } @else {\n {{ col.accessor ? col.accessor(row) : '' }}\n }\n </td>\n }\n </tr>\n }\n\n @if (data.length === 0) {\n <tr class=\"kp-table__row kp-table__row--empty\">\n <td class=\"kp-table__cell kp-table__cell--empty\" [attr.colspan]=\"columnCount()\">\n {{ emptyMessage }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n --kp-table-row-h: 48px;\n --kp-table-pad-x: 16px;\n --kp-table-fs: 14px;\n --kp-table-lh: 20px;\n }\n :host(.kp-table--sm) { --kp-table-row-h: 40px; --kp-table-pad-x: 12px; --kp-table-fs: 13px; --kp-table-lh: 18px; }\n :host(.kp-table--md) { --kp-table-row-h: 48px; --kp-table-pad-x: 16px; --kp-table-fs: 14px; --kp-table-lh: 20px; }\n :host(.kp-table--lg) { --kp-table-row-h: 56px; --kp-table-pad-x: 20px; --kp-table-fs: 15px; --kp-table-lh: 22px; }\n\n :host(.kp-table--bordered) .kp-table__el {\n border: 1px solid var(--kp-color-table-border, var(--kp-color-gray-200));\n border-radius: 12px;\n overflow: hidden;\n }\n\n .kp-table__el {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--kp-table-fs);\n line-height: var(--kp-table-lh);\n color: var(--kp-color-table-row-fg, var(--kp-color-gray-900));\n }\n\n .kp-table__head-row {\n background: var(--kp-color-table-header-bg, var(--kp-color-gray-50));\n border-bottom: 1px solid var(--kp-color-table-border, var(--kp-color-gray-200));\n }\n\n .kp-table__cell {\n padding: 0 var(--kp-table-pad-x);\n height: var(--kp-table-row-h);\n vertical-align: middle;\n text-align: start;\n color: inherit;\n }\n .kp-table__cell--center { text-align: center; }\n .kp-table__cell--right { text-align: end; }\n .kp-table__cell--checkbox { width: 44px; padding-inline-end: 0; }\n\n .kp-table__cell--header {\n font-size: 11px;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--kp-color-table-header-fg, var(--kp-color-gray-700));\n white-space: nowrap;\n }\n\n .kp-table__header-button {\n all: unset;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n color: inherit;\n }\n .kp-table__header-button--sortable { cursor: pointer; }\n .kp-table__header-button--sortable:hover { color: var(--kp-color-table-row-fg, var(--kp-color-gray-900)); }\n .kp-table__header-button--active { color: var(--kp-color-table-header-sort-active, var(--kp-color-blue-600)); }\n .kp-table__cell--right .kp-table__header-button { justify-content: flex-end; }\n .kp-table__cell--center .kp-table__header-button { justify-content: center; }\n\n .kp-table__sort-icon {\n display: inline-flex;\n color: var(--kp-color-table-header-sort-icon, var(--kp-color-gray-500));\n }\n .kp-table__header-button--active .kp-table__sort-icon {\n color: var(--kp-color-table-header-sort-active, var(--kp-color-blue-600));\n }\n\n .kp-table__row {\n background: var(--kp-color-table-row-bg-rest, var(--kp-color-white));\n border-bottom: 1px solid var(--kp-color-table-border-soft, var(--kp-color-gray-100));\n transition: background var(--kp-motion-duration-fast) ease;\n }\n .kp-table__row:hover:not(.kp-table__row--selected):not(.kp-table__row--empty) {\n background: var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50));\n }\n :host(.kp-table--striped) .kp-table__row:nth-child(even):not(.kp-table__row--selected) {\n background: var(--kp-color-table-row-bg-hover, var(--kp-color-gray-50));\n }\n .kp-table__row--selected {\n background: var(--kp-color-table-row-bg-selected, var(--kp-color-blue-50)) !important;\n }\n .kp-table__row:last-child { border-bottom: 0; }\n :host(.kp-table--selectable) .kp-table__row:not(.kp-table__row--empty) { cursor: pointer; }\n\n .kp-table__cell--empty {\n text-align: center;\n color: var(--kp-color-table-header-fg, var(--kp-color-gray-500));\n padding: 32px 16px;\n font-size: 13px;\n }\n\n /* Checkbox cell centers the KpCheckbox inside a fixed column width. */\n .kp-table__cell--checkbox kp-checkbox { display: inline-flex; }\n `],\n})\nexport class KpTableComponent<T = unknown> {\n @Input() size: KpTableSize = 'md';\n @Input() columns: KpTableColumn<T>[] = [];\n @Input() data: T[] = [];\n @Input() striped = false;\n @Input() bordered = false;\n @Input() selectable = false;\n @Input() selected: T[] = [];\n @Input() sort: KpTableSort | null = null;\n @Input() emptyMessage = 'No data';\n /** Override how rows are tracked in the `@for` loop (default: index). */\n @Input() trackBy: ((row: T, index: number) => unknown) | null = null;\n\n @Output() readonly selectedChange = new EventEmitter<T[]>();\n @Output() readonly sortChange = new EventEmitter<KpTableSort | null>();\n @Output() readonly rowClick = new EventEmitter<T>();\n\n @ContentChildren(KpTableCellDirective) cellTemplates!: QueryList<KpTableCellDirective>;\n @ContentChildren(KpTableHeaderDirective) headerTemplates!: QueryList<KpTableHeaderDirective>;\n\n get hostClasses(): string {\n const c = ['kp-table', `kp-table--${this.size}`];\n if (this.striped) c.push('kp-table--striped');\n if (this.bordered) c.push('kp-table--bordered');\n if (this.selectable) c.push('kp-table--selectable');\n return c.join(' ');\n }\n\n cellAlignClass(col: KpTableColumn<T>): string {\n return col.align === 'right' ? 'kp-table__cell--right'\n : col.align === 'center' ? 'kp-table__cell--center'\n : '';\n }\n\n columnCount(): number {\n return this.columns.length + (this.selectable ? 1 : 0);\n }\n\n cellTemplate(columnId: string): TemplateRef<unknown> | null {\n return this.cellTemplates?.find((d) => d.columnId === columnId)?.template ?? null;\n }\n headerTemplate(columnId: string): TemplateRef<unknown> | null {\n return this.headerTemplates?.find((d) => d.columnId === columnId)?.template ?? null;\n }\n\n trackRow(row: T, index: number): unknown {\n return this.trackBy ? this.trackBy(row, index) : index;\n }\n\n isSelected(row: T): boolean { return this.selected.includes(row); }\n allSelected(): boolean { return this.data.length > 0 && this.selected.length === this.data.length; }\n someSelected(): boolean { return this.selected.length > 0; }\n\n toggleRow(row: T): void {\n const next = this.isSelected(row)\n ? this.selected.filter((r) => r !== row)\n : [...this.selected, row];\n this.selected = next;\n this.selectedChange.emit(next);\n }\n\n toggleAll(checked: boolean): void {\n const next = checked ? [...this.data] : [];\n this.selected = next;\n this.selectedChange.emit(next);\n }\n\n onRowClick(row: T, _event: MouseEvent): void {\n this.rowClick.emit(row);\n if (this.selectable) this.toggleRow(row);\n }\n\n onHeaderClick(col: KpTableColumn<T>): void {\n if (!col.sortable) return;\n let next: KpTableSort | null;\n if (!this.sort || this.sort.columnId !== col.id) {\n next = { columnId: col.id, direction: 'asc' };\n } else if (this.sort.direction === 'asc') {\n next = { columnId: col.id, direction: 'desc' };\n } else {\n next = null; // clear sort on third click\n }\n this.sort = next;\n this.sortChange.emit(next);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kanso-protocol/table",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "peerDependencies": {
6
+ "@angular/core": "^18.0.0",
7
+ "@angular/common": "^18.0.0",
8
+ "@kanso-protocol/core": "^0.0.1",
9
+ "@kanso-protocol/checkbox": ">=0.1.0"
10
+ },
11
+ "description": "Kanso Protocol — table (component).",
12
+ "author": "GregNBlack",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/GregNBlack/kanso-protocol.git",
16
+ "directory": "packages/components/table"
17
+ },
18
+ "homepage": "https://gregnblack.github.io/kanso-protocol/?path=/docs/components-table--docs",
19
+ "bugs": "https://github.com/GregNBlack/kanso-protocol/issues",
20
+ "keywords": [
21
+ "design-system",
22
+ "angular",
23
+ "kanso",
24
+ "table"
25
+ ],
26
+ "sideEffects": false,
27
+ "module": "fesm2022/kanso-protocol-table.mjs",
28
+ "typings": "types/kanso-protocol-table.d.ts",
29
+ "exports": {
30
+ "./package.json": {
31
+ "default": "./package.json"
32
+ },
33
+ ".": {
34
+ "types": "./types/kanso-protocol-table.d.ts",
35
+ "default": "./fesm2022/kanso-protocol-table.mjs"
36
+ }
37
+ },
38
+ "type": "module",
39
+ "dependencies": {
40
+ "tslib": "^2.3.0"
41
+ }
42
+ }
@@ -0,0 +1,91 @@
1
+ import * as i0 from '@angular/core';
2
+ import { TemplateRef, EventEmitter, QueryList } from '@angular/core';
3
+
4
+ type KpTableSize = 'sm' | 'md' | 'lg';
5
+ type KpTableSortDirection = 'asc' | 'desc';
6
+ interface KpTableColumn<T = unknown> {
7
+ /** Stable id matched against `<ng-template kpTableCell="id">` projections. */
8
+ id: string;
9
+ /** Header text. */
10
+ label: string;
11
+ /** Cell alignment. */
12
+ align?: 'left' | 'center' | 'right';
13
+ /** Enable sorting for this column. */
14
+ sortable?: boolean;
15
+ /** Flex-basis for the column (CSS length). Defaults to `auto` (content-sized). */
16
+ width?: string;
17
+ /** Fallback accessor when no `kpTableCell` template is provided. */
18
+ accessor?: (row: T) => unknown;
19
+ }
20
+ interface KpTableSort {
21
+ columnId: string;
22
+ direction: KpTableSortDirection;
23
+ }
24
+ /** `<ng-template kpTableCell="columnId" let-row let-i="index">` — custom cell renderer per column. */
25
+ declare class KpTableCellDirective {
26
+ readonly template: TemplateRef<unknown>;
27
+ columnId: string;
28
+ constructor(template: TemplateRef<unknown>);
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<KpTableCellDirective, never>;
30
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KpTableCellDirective, "ng-template[kpTableCell]", never, { "columnId": { "alias": "kpTableCell"; "required": false; }; }, {}, never, never, true, never>;
31
+ }
32
+ /** `<ng-template kpTableHeader="columnId">` — custom header renderer per column. */
33
+ declare class KpTableHeaderDirective {
34
+ readonly template: TemplateRef<unknown>;
35
+ columnId: string;
36
+ constructor(template: TemplateRef<unknown>);
37
+ static ɵfac: i0.ɵɵFactoryDeclaration<KpTableHeaderDirective, never>;
38
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KpTableHeaderDirective, "ng-template[kpTableHeader]", never, { "columnId": { "alias": "kpTableHeader"; "required": false; }; }, {}, never, never, true, never>;
39
+ }
40
+ /**
41
+ * Kanso Protocol — Table
42
+ *
43
+ * Data-driven table with a columns schema, sortable headers, optional
44
+ * row selection, striped / bordered variants, and template-per-column
45
+ * cell rendering via `<ng-template kpTableCell="id">`.
46
+ *
47
+ * @example
48
+ * <kp-table [columns]="cols" [data]="rows" [selectable]="true" [(selected)]="picked">
49
+ * <ng-template kpTableCell="status" let-row>
50
+ * <kp-badge [color]="row.active ? 'success' : 'neutral'">
51
+ * {{ row.active ? 'Active' : 'Inactive' }}
52
+ * </kp-badge>
53
+ * </ng-template>
54
+ * </kp-table>
55
+ */
56
+ declare class KpTableComponent<T = unknown> {
57
+ size: KpTableSize;
58
+ columns: KpTableColumn<T>[];
59
+ data: T[];
60
+ striped: boolean;
61
+ bordered: boolean;
62
+ selectable: boolean;
63
+ selected: T[];
64
+ sort: KpTableSort | null;
65
+ emptyMessage: string;
66
+ /** Override how rows are tracked in the `@for` loop (default: index). */
67
+ trackBy: ((row: T, index: number) => unknown) | null;
68
+ readonly selectedChange: EventEmitter<T[]>;
69
+ readonly sortChange: EventEmitter<KpTableSort>;
70
+ readonly rowClick: EventEmitter<T>;
71
+ cellTemplates: QueryList<KpTableCellDirective>;
72
+ headerTemplates: QueryList<KpTableHeaderDirective>;
73
+ get hostClasses(): string;
74
+ cellAlignClass(col: KpTableColumn<T>): string;
75
+ columnCount(): number;
76
+ cellTemplate(columnId: string): TemplateRef<unknown> | null;
77
+ headerTemplate(columnId: string): TemplateRef<unknown> | null;
78
+ trackRow(row: T, index: number): unknown;
79
+ isSelected(row: T): boolean;
80
+ allSelected(): boolean;
81
+ someSelected(): boolean;
82
+ toggleRow(row: T): void;
83
+ toggleAll(checked: boolean): void;
84
+ onRowClick(row: T, _event: MouseEvent): void;
85
+ onHeaderClick(col: KpTableColumn<T>): void;
86
+ static ɵfac: i0.ɵɵFactoryDeclaration<KpTableComponent<any>, never>;
87
+ static ɵcmp: i0.ɵɵComponentDeclaration<KpTableComponent<any>, "kp-table", never, { "size": { "alias": "size"; "required": false; }; "columns": { "alias": "columns"; "required": false; }; "data": { "alias": "data"; "required": false; }; "striped": { "alias": "striped"; "required": false; }; "bordered": { "alias": "bordered"; "required": false; }; "selectable": { "alias": "selectable"; "required": false; }; "selected": { "alias": "selected"; "required": false; }; "sort": { "alias": "sort"; "required": false; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; }; "trackBy": { "alias": "trackBy"; "required": false; }; }, { "selectedChange": "selectedChange"; "sortChange": "sortChange"; "rowClick": "rowClick"; }, ["cellTemplates", "headerTemplates"], never, true, never>;
88
+ }
89
+
90
+ export { KpTableCellDirective, KpTableComponent, KpTableHeaderDirective };
91
+ export type { KpTableColumn, KpTableSize, KpTableSort, KpTableSortDirection };