@tekus/design-system 5.38.1 → 5.40.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.
- package/assets/readme-images/tk-data-list-anatomy.png +0 -0
- package/assets/readme-images/tk-data-list-grid.png +0 -0
- package/assets/readme-images/tk-data-list-list.png +0 -0
- package/fesm2022/tekus-design-system-components-counter.mjs +494 -0
- package/fesm2022/tekus-design-system-components-counter.mjs.map +1 -0
- package/fesm2022/tekus-design-system-components-data-list.mjs +185 -0
- package/fesm2022/tekus-design-system-components-data-list.mjs.map +1 -0
- package/fesm2022/tekus-design-system-components-icon.mjs +14 -0
- package/fesm2022/tekus-design-system-components-icon.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-stat.mjs +193 -0
- package/fesm2022/tekus-design-system-components-stat.mjs.map +1 -0
- package/fesm2022/tekus-design-system-components-table.mjs +34 -2
- package/fesm2022/tekus-design-system-components-table.mjs.map +1 -1
- package/package.json +13 -1
- package/types/tekus-design-system-components-counter.d.ts +286 -0
- package/types/tekus-design-system-components-data-list.d.ts +193 -0
- package/types/tekus-design-system-components-stat.d.ts +161 -0
- package/types/tekus-design-system-components-table.d.ts +35 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, inject, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { AvatarComponent } from '@tekus/design-system/components/avatar';
|
|
6
|
+
import { ButtonComponent } from '@tekus/design-system/components/button';
|
|
7
|
+
import { TagComponent } from '@tekus/design-system/components/tag';
|
|
8
|
+
import { Breakpoints } from '@tekus/design-system/core/types';
|
|
9
|
+
|
|
10
|
+
/** Rendered in place of a value that is `null`, `undefined` or a blank string. */
|
|
11
|
+
const DATA_LIST_EMPTY_PLACEHOLDER = '--';
|
|
12
|
+
/** Fallback severity for `'tag'` values, matching the design. */
|
|
13
|
+
const DEFAULT_TAG_SEVERITY = 'primary';
|
|
14
|
+
/**
|
|
15
|
+
* @component DataListComponent
|
|
16
|
+
* @description
|
|
17
|
+
* Displays a collection of read-only term→value pairs, where each value is a typed
|
|
18
|
+
* piece of data (text, link, tag or number). It is the pair-oriented sibling of
|
|
19
|
+
* `tk-table`: both render governed, typed values, but the data list presents them as
|
|
20
|
+
* label→value pairs instead of rows and columns. Use it for detail panels, record
|
|
21
|
+
* summaries and entity overviews.
|
|
22
|
+
*
|
|
23
|
+
* This component supports:
|
|
24
|
+
* - `items`: the pairs to render. See {@link TkDataListItem}.
|
|
25
|
+
* - `layout`: `'list'` (one column, dividers, term left / value right) or `'grid'`
|
|
26
|
+
* (responsive multi-column, no dividers, term above value).
|
|
27
|
+
* - `emptyPlaceholder`: the text rendered in place of an empty value.
|
|
28
|
+
*
|
|
29
|
+
* The consumer only chooses the layout. Everything that follows from it — item
|
|
30
|
+
* orientation, dividers and the responsive column count — is owned by the component.
|
|
31
|
+
* To vary the layout across breakpoints (e.g. list on mobile, grid on desktop), switch
|
|
32
|
+
* the `layout` input at your own breakpoint.
|
|
33
|
+
*
|
|
34
|
+
* Renders as a description list (`<dl>`) so assistive technology announces each entry
|
|
35
|
+
* as a term→value pair. Interactivity lives inside the value, never on the item — the
|
|
36
|
+
* item itself is neither focusable nor clickable.
|
|
37
|
+
*
|
|
38
|
+
* @usage
|
|
39
|
+
* ### Basic Usage
|
|
40
|
+
* ```html
|
|
41
|
+
* <tk-data-list [items]="details" layout="list"></tk-data-list>
|
|
42
|
+
* ```
|
|
43
|
+
* ```ts
|
|
44
|
+
* details: TkDataListItem[] = [
|
|
45
|
+
* { label: 'Owner', value: 'Ana Bermúdez' },
|
|
46
|
+
* { label: 'Status', value: 'Active', type: 'tag' },
|
|
47
|
+
* { label: 'Devices', value: 2322, type: 'number' },
|
|
48
|
+
* { label: 'Contract', value: 'Open', type: 'link', handler: () => this.openContract() },
|
|
49
|
+
* ];
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
class DataListComponent {
|
|
53
|
+
constructor() {
|
|
54
|
+
/**
|
|
55
|
+
* The term→value pairs to render, in order.
|
|
56
|
+
* @default []
|
|
57
|
+
*/
|
|
58
|
+
this.items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
59
|
+
/**
|
|
60
|
+
* How the pairs are arranged.
|
|
61
|
+
* @default 'list'
|
|
62
|
+
*/
|
|
63
|
+
this.layout = input('list', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
64
|
+
/**
|
|
65
|
+
* The text rendered in place of a value that is `null`, `undefined` or blank.
|
|
66
|
+
*
|
|
67
|
+
* Exposed as an input so it can be localised (`'N/A'`, `'Sin dato'`, …). The
|
|
68
|
+
* default is the system-wide placeholder.
|
|
69
|
+
* @default '--'
|
|
70
|
+
*/
|
|
71
|
+
this.emptyPlaceholder = input(DATA_LIST_EMPTY_PLACEHOLDER, ...(ngDevMode ? [{ debugName: "emptyPlaceholder" }] : /* istanbul ignore next */ []));
|
|
72
|
+
this.breakpointObserver = inject(BreakpointObserver);
|
|
73
|
+
/**
|
|
74
|
+
* Reactive viewport state across every breakpoint the design system defines.
|
|
75
|
+
*
|
|
76
|
+
* Grid mode sizes off the viewport rather than the component's own width, matching
|
|
77
|
+
* `tk-grid-container`. A consequence worth knowing: a data list inside a narrow
|
|
78
|
+
* drawer still reports the viewport's column count.
|
|
79
|
+
*/
|
|
80
|
+
this.screenChanges = toSignal(this.breakpointObserver.observe([
|
|
81
|
+
Breakpoints.mobileSmall,
|
|
82
|
+
Breakpoints.mobile,
|
|
83
|
+
Breakpoints.mobileLarge,
|
|
84
|
+
Breakpoints.tabletVertical,
|
|
85
|
+
Breakpoints.tabletHorizontal,
|
|
86
|
+
Breakpoints.desktopSmall,
|
|
87
|
+
Breakpoints.desktop,
|
|
88
|
+
Breakpoints.desktopLarge,
|
|
89
|
+
]));
|
|
90
|
+
/** Whether the grid layout is active. */
|
|
91
|
+
this.isGrid = computed(() => this.layout() === 'grid', ...(ngDevMode ? [{ debugName: "isGrid" }] : /* istanbul ignore next */ []));
|
|
92
|
+
/**
|
|
93
|
+
* The number of grid columns for the current viewport.
|
|
94
|
+
*
|
|
95
|
+
* Mobile 1 · Tablet 2 · Small 2 · Normal 3 · Extra large 5. Falls back to a single
|
|
96
|
+
* column when the observer has not reported yet, so the first paint is mobile-first
|
|
97
|
+
* rather than over-wide.
|
|
98
|
+
*/
|
|
99
|
+
this.gridColumnCount = computed(() => {
|
|
100
|
+
const breakpoints = this.screenChanges()?.breakpoints;
|
|
101
|
+
if (!breakpoints) {
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
// Widest first. Every Breakpoints entry is currently bounded on both sides except
|
|
105
|
+
// desktopLarge, so at most one matches at a time and the order is not strictly
|
|
106
|
+
// required — but BreakpointObserver reports every matching query, so descending
|
|
107
|
+
// order keeps this correct even if an upper bound is ever dropped upstream.
|
|
108
|
+
if (breakpoints[Breakpoints.desktopLarge]) {
|
|
109
|
+
return 5;
|
|
110
|
+
}
|
|
111
|
+
if (breakpoints[Breakpoints.desktop]) {
|
|
112
|
+
return 3;
|
|
113
|
+
}
|
|
114
|
+
if (breakpoints[Breakpoints.desktopSmall] ||
|
|
115
|
+
breakpoints[Breakpoints.tabletHorizontal] ||
|
|
116
|
+
breakpoints[Breakpoints.tabletVertical]) {
|
|
117
|
+
return 2;
|
|
118
|
+
}
|
|
119
|
+
return 1;
|
|
120
|
+
}, ...(ngDevMode ? [{ debugName: "gridColumnCount" }] : /* istanbul ignore next */ []));
|
|
121
|
+
/**
|
|
122
|
+
* The `grid-template-columns` value bound on the `<dl>`, or `null` in list layout
|
|
123
|
+
* so the property is left off the element entirely.
|
|
124
|
+
*
|
|
125
|
+
* The track count is capped at the number of items: a data list usually holds fewer
|
|
126
|
+
* pairs than the viewport allows columns, and emitting the full count would leave
|
|
127
|
+
* empty tracks that squeeze every value into a fraction of the available width
|
|
128
|
+
* (e.g. three items over five tracks). Never drops below one, so an empty list still
|
|
129
|
+
* produces a valid value.
|
|
130
|
+
*/
|
|
131
|
+
this.gridTemplateColumns = computed(() => {
|
|
132
|
+
if (!this.isGrid()) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const columns = Math.min(this.gridColumnCount(), Math.max(this.items().length, 1));
|
|
136
|
+
return `repeat(${columns}, minmax(0, 1fr))`;
|
|
137
|
+
}, ...(ngDevMode ? [{ debugName: "gridTemplateColumns" }] : /* istanbul ignore next */ []));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Whether a value should render as the `--` placeholder.
|
|
141
|
+
*
|
|
142
|
+
* Applied before the type is considered, so the rule holds uniformly across every
|
|
143
|
+
* type. `0` and `false`-ish numbers are real values and are not treated as empty.
|
|
144
|
+
*/
|
|
145
|
+
isEmpty(value) {
|
|
146
|
+
return value === null || value === undefined || (typeof value === 'string' && value.trim() === '');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The effective value type, defaulting to `'text'`.
|
|
150
|
+
*
|
|
151
|
+
* A `'link'` with no `handler` has nothing to invoke, so it resolves to `'text'`: it
|
|
152
|
+
* renders as plain data instead of as a button that does nothing. That keeps a dead
|
|
153
|
+
* control out of the accessibility tree and the tab order entirely, which `disabled`
|
|
154
|
+
* would not do — and `disabled` would also claim the action merely is unavailable
|
|
155
|
+
* right now, which is not the case.
|
|
156
|
+
*/
|
|
157
|
+
resolveType(item) {
|
|
158
|
+
const type = item.type ?? 'text';
|
|
159
|
+
return type === 'link' && !item.handler ? 'text' : type;
|
|
160
|
+
}
|
|
161
|
+
/** The effective tag severity, defaulting to the one used in the design. */
|
|
162
|
+
resolveTagSeverity(item) {
|
|
163
|
+
return item.tagSeverity ?? DEFAULT_TAG_SEVERITY;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The value as a string, for the child components that require one
|
|
167
|
+
* (`tk-tag.value` and `tk-button.label` are both typed `string`).
|
|
168
|
+
*/
|
|
169
|
+
asText(value) {
|
|
170
|
+
return this.isEmpty(value) ? '' : String(value);
|
|
171
|
+
}
|
|
172
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DataListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
173
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DataListComponent, isStandalone: true, selector: "tk-data-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, emptyPlaceholder: { classPropertyName: "emptyPlaceholder", publicName: "emptyPlaceholder", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n A description list, so assistive tech announces each entry as a term\u2192value pair.\n Each pair is wrapped in a <div> \u2014 valid inside <dl>, and it lets the list and grid\n layouts share identical markup, differing only in CSS.\n-->\n<dl\n class=\"tk-data-list\"\n [class.tk-data-list--list]=\"!isGrid()\"\n [class.tk-data-list--grid]=\"isGrid()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\">\n @for (item of items(); track $index) {\n <div class=\"tk-data-list__item\">\n <dt class=\"tk-data-list__term\">\n @if (item.leadingIcon) {\n <tk-avatar variant=\"icon\" severity=\"secondary\" size=\"m\" [icon]=\"item.leadingIcon\" aria-hidden=\"true\" />\n }\n\n <span class=\"tk-data-list__text\">\n <span class=\"tk-data-list__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"tk-data-list__description\">{{ item.description }}</span>\n }\n </span>\n </dt>\n\n <dd\n class=\"tk-data-list__value\"\n [class.tk-data-list__value--indented]=\"isGrid() && !!item.leadingIcon\">\n <!--\n The empty check runs before the type switch, so the `--` placeholder rule\n applies uniformly to every value type.\n -->\n @if (isEmpty(item.value)) {\n <span class=\"tk-data-list__placeholder\">{{ emptyPlaceholder() }}</span>\n } @else {\n @switch (resolveType(item)) {\n @case ('tag') {\n <tk-tag [value]=\"asText(item.value)\" [severity]=\"resolveTagSeverity(item)\" />\n }\n @case ('link') {\n <tk-button [label]=\"asText(item.value)\" [link]=\"true\" (clicked)=\"item.handler?.()\" />\n }\n @default {\n <!-- 'text' and 'number' render identically; number formatting is not implemented yet. -->\n <span class=\"tk-data-list__value-text\">{{ item.value }}</span>\n }\n }\n }\n </dd>\n </div>\n }\n</dl>\n", styles: [":host{display:block}.tk-data-list{margin:0;font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-data-list--list{display:flex;flex-direction:column;width:100%}.tk-data-list--list .tk-data-list__item{display:flex;align-items:center;justify-content:space-between;padding:var(--tk-spacing-padding-m, 1rem) var(--tk-spacing-padding-xs, .25rem)}.tk-data-list--list .tk-data-list__item:not(:last-child){border-bottom:1px solid var(--tk-color-border-default, #cecdcd)}.tk-data-list--grid{display:grid;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-data-list--grid .tk-data-list__item{display:flex;flex-direction:column;align-items:flex-start;padding:var(--tk-spacing-padding-s, .5rem) var(--tk-spacing-padding-xs, .25rem)}.tk-data-list__term{display:flex;align-items:center;gap:var(--tk-spacing-gap-xs, .25rem);min-width:0}.tk-data-list__text{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:var(--tk-spacing-gap-xs, .25rem);min-width:0}.tk-data-list__label{color:var(--tk-color-text-default, #191a1b);font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-m, 1rem)}.tk-data-list__description{color:var(--tk-color-text-subtle, #5d5d5e);font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1rem}.tk-data-list__value{display:flex;align-items:center;margin:0;min-width:0}.tk-data-list__value--indented{padding-left:calc(var(--tk-size-base-250, 2.5rem) + var(--tk-spacing-gap-xs, .25rem))}.tk-data-list__value-text,.tk-data-list__placeholder{color:var(--tk-color-text-default, #191a1b);font-weight:var(--tk-font-weight-600, 600);font-size:var(--tk-font-size-paragraph-m, 1rem)}\n"], dependencies: [{ kind: "component", type: AvatarComponent, selector: "tk-avatar", inputs: ["variant", "severity", "size", "value", "icon", "image", "alt", "ariaLabel"] }, { kind: "component", type: ButtonComponent, selector: "tk-button", inputs: ["label", "disabled", "type", "severity", "variant", "link", "icon", "iconPosition", "tooltipText", "full", "ariaLabel", "size"], outputs: ["clicked"] }, { kind: "component", type: TagComponent, selector: "tk-tag", inputs: ["value", "severity", "truncationLimit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
174
|
+
}
|
|
175
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DataListComponent, decorators: [{
|
|
176
|
+
type: Component,
|
|
177
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'tk-data-list', imports: [AvatarComponent, ButtonComponent, TagComponent], template: "<!--\n A description list, so assistive tech announces each entry as a term\u2192value pair.\n Each pair is wrapped in a <div> \u2014 valid inside <dl>, and it lets the list and grid\n layouts share identical markup, differing only in CSS.\n-->\n<dl\n class=\"tk-data-list\"\n [class.tk-data-list--list]=\"!isGrid()\"\n [class.tk-data-list--grid]=\"isGrid()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\">\n @for (item of items(); track $index) {\n <div class=\"tk-data-list__item\">\n <dt class=\"tk-data-list__term\">\n @if (item.leadingIcon) {\n <tk-avatar variant=\"icon\" severity=\"secondary\" size=\"m\" [icon]=\"item.leadingIcon\" aria-hidden=\"true\" />\n }\n\n <span class=\"tk-data-list__text\">\n <span class=\"tk-data-list__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"tk-data-list__description\">{{ item.description }}</span>\n }\n </span>\n </dt>\n\n <dd\n class=\"tk-data-list__value\"\n [class.tk-data-list__value--indented]=\"isGrid() && !!item.leadingIcon\">\n <!--\n The empty check runs before the type switch, so the `--` placeholder rule\n applies uniformly to every value type.\n -->\n @if (isEmpty(item.value)) {\n <span class=\"tk-data-list__placeholder\">{{ emptyPlaceholder() }}</span>\n } @else {\n @switch (resolveType(item)) {\n @case ('tag') {\n <tk-tag [value]=\"asText(item.value)\" [severity]=\"resolveTagSeverity(item)\" />\n }\n @case ('link') {\n <tk-button [label]=\"asText(item.value)\" [link]=\"true\" (clicked)=\"item.handler?.()\" />\n }\n @default {\n <!-- 'text' and 'number' render identically; number formatting is not implemented yet. -->\n <span class=\"tk-data-list__value-text\">{{ item.value }}</span>\n }\n }\n }\n </dd>\n </div>\n }\n</dl>\n", styles: [":host{display:block}.tk-data-list{margin:0;font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-data-list--list{display:flex;flex-direction:column;width:100%}.tk-data-list--list .tk-data-list__item{display:flex;align-items:center;justify-content:space-between;padding:var(--tk-spacing-padding-m, 1rem) var(--tk-spacing-padding-xs, .25rem)}.tk-data-list--list .tk-data-list__item:not(:last-child){border-bottom:1px solid var(--tk-color-border-default, #cecdcd)}.tk-data-list--grid{display:grid;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-data-list--grid .tk-data-list__item{display:flex;flex-direction:column;align-items:flex-start;padding:var(--tk-spacing-padding-s, .5rem) var(--tk-spacing-padding-xs, .25rem)}.tk-data-list__term{display:flex;align-items:center;gap:var(--tk-spacing-gap-xs, .25rem);min-width:0}.tk-data-list__text{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:var(--tk-spacing-gap-xs, .25rem);min-width:0}.tk-data-list__label{color:var(--tk-color-text-default, #191a1b);font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-m, 1rem)}.tk-data-list__description{color:var(--tk-color-text-subtle, #5d5d5e);font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1rem}.tk-data-list__value{display:flex;align-items:center;margin:0;min-width:0}.tk-data-list__value--indented{padding-left:calc(var(--tk-size-base-250, 2.5rem) + var(--tk-spacing-gap-xs, .25rem))}.tk-data-list__value-text,.tk-data-list__placeholder{color:var(--tk-color-text-default, #191a1b);font-weight:var(--tk-font-weight-600, 600);font-size:var(--tk-font-size-paragraph-m, 1rem)}\n"] }]
|
|
178
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], emptyPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyPlaceholder", required: false }] }] } });
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Generated bundle index. Do not edit.
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
export { DATA_LIST_EMPTY_PLACEHOLDER, DataListComponent };
|
|
185
|
+
//# sourceMappingURL=tekus-design-system-components-data-list.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tekus-design-system-components-data-list.mjs","sources":["../../../projects/design-system/components/data-list/src/data-list.component.ts","../../../projects/design-system/components/data-list/src/data-list.component.html","../../../projects/design-system/components/data-list/tekus-design-system-components-data-list.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { AvatarComponent } from '@tekus/design-system/components/avatar';\nimport { ButtonComponent } from '@tekus/design-system/components/button';\nimport { TagComponent, TagSeverity } from '@tekus/design-system/components/tag';\nimport { Breakpoints } from '@tekus/design-system/core/types';\nimport { TkDataListItem, TkDataListLayout, TkDataListValueType } from './data-list.types';\n\n/** Rendered in place of a value that is `null`, `undefined` or a blank string. */\nexport const DATA_LIST_EMPTY_PLACEHOLDER = '--';\n\n/** Fallback severity for `'tag'` values, matching the design. */\nconst DEFAULT_TAG_SEVERITY: TagSeverity = 'primary';\n\n/**\n * @component DataListComponent\n * @description\n * Displays a collection of read-only term→value pairs, where each value is a typed\n * piece of data (text, link, tag or number). It is the pair-oriented sibling of\n * `tk-table`: both render governed, typed values, but the data list presents them as\n * label→value pairs instead of rows and columns. Use it for detail panels, record\n * summaries and entity overviews.\n *\n * This component supports:\n * - `items`: the pairs to render. See {@link TkDataListItem}.\n * - `layout`: `'list'` (one column, dividers, term left / value right) or `'grid'`\n * (responsive multi-column, no dividers, term above value).\n * - `emptyPlaceholder`: the text rendered in place of an empty value.\n *\n * The consumer only chooses the layout. Everything that follows from it — item\n * orientation, dividers and the responsive column count — is owned by the component.\n * To vary the layout across breakpoints (e.g. list on mobile, grid on desktop), switch\n * the `layout` input at your own breakpoint.\n *\n * Renders as a description list (`<dl>`) so assistive technology announces each entry\n * as a term→value pair. Interactivity lives inside the value, never on the item — the\n * item itself is neither focusable nor clickable.\n *\n * @usage\n * ### Basic Usage\n * ```html\n * <tk-data-list [items]=\"details\" layout=\"list\"></tk-data-list>\n * ```\n * ```ts\n * details: TkDataListItem[] = [\n * { label: 'Owner', value: 'Ana Bermúdez' },\n * { label: 'Status', value: 'Active', type: 'tag' },\n * { label: 'Devices', value: 2322, type: 'number' },\n * { label: 'Contract', value: 'Open', type: 'link', handler: () => this.openContract() },\n * ];\n * ```\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'tk-data-list',\n imports: [AvatarComponent, ButtonComponent, TagComponent],\n templateUrl: './data-list.component.html',\n styleUrl: './data-list.component.scss',\n})\nexport class DataListComponent {\n /**\n * The term→value pairs to render, in order.\n * @default []\n */\n items = input<TkDataListItem[]>([]);\n\n /**\n * How the pairs are arranged.\n * @default 'list'\n */\n layout = input<TkDataListLayout>('list');\n\n /**\n * The text rendered in place of a value that is `null`, `undefined` or blank.\n *\n * Exposed as an input so it can be localised (`'N/A'`, `'Sin dato'`, …). The\n * default is the system-wide placeholder.\n * @default '--'\n */\n emptyPlaceholder = input<string>(DATA_LIST_EMPTY_PLACEHOLDER);\n\n private readonly breakpointObserver = inject(BreakpointObserver);\n\n /**\n * Reactive viewport state across every breakpoint the design system defines.\n *\n * Grid mode sizes off the viewport rather than the component's own width, matching\n * `tk-grid-container`. A consequence worth knowing: a data list inside a narrow\n * drawer still reports the viewport's column count.\n */\n readonly screenChanges = toSignal(\n this.breakpointObserver.observe([\n Breakpoints.mobileSmall,\n Breakpoints.mobile,\n Breakpoints.mobileLarge,\n Breakpoints.tabletVertical,\n Breakpoints.tabletHorizontal,\n Breakpoints.desktopSmall,\n Breakpoints.desktop,\n Breakpoints.desktopLarge,\n ])\n );\n\n /** Whether the grid layout is active. */\n readonly isGrid = computed(() => this.layout() === 'grid');\n\n /**\n * The number of grid columns for the current viewport.\n *\n * Mobile 1 · Tablet 2 · Small 2 · Normal 3 · Extra large 5. Falls back to a single\n * column when the observer has not reported yet, so the first paint is mobile-first\n * rather than over-wide.\n */\n readonly gridColumnCount = computed(() => {\n const breakpoints = this.screenChanges()?.breakpoints;\n if (!breakpoints) {\n return 1;\n }\n\n // Widest first. Every Breakpoints entry is currently bounded on both sides except\n // desktopLarge, so at most one matches at a time and the order is not strictly\n // required — but BreakpointObserver reports every matching query, so descending\n // order keeps this correct even if an upper bound is ever dropped upstream.\n if (breakpoints[Breakpoints.desktopLarge]) {\n return 5;\n }\n if (breakpoints[Breakpoints.desktop]) {\n return 3;\n }\n if (\n breakpoints[Breakpoints.desktopSmall] ||\n breakpoints[Breakpoints.tabletHorizontal] ||\n breakpoints[Breakpoints.tabletVertical]\n ) {\n return 2;\n }\n\n return 1;\n });\n\n /**\n * The `grid-template-columns` value bound on the `<dl>`, or `null` in list layout\n * so the property is left off the element entirely.\n *\n * The track count is capped at the number of items: a data list usually holds fewer\n * pairs than the viewport allows columns, and emitting the full count would leave\n * empty tracks that squeeze every value into a fraction of the available width\n * (e.g. three items over five tracks). Never drops below one, so an empty list still\n * produces a valid value.\n */\n readonly gridTemplateColumns = computed(() => {\n if (!this.isGrid()) {\n return null;\n }\n\n const columns = Math.min(this.gridColumnCount(), Math.max(this.items().length, 1));\n return `repeat(${columns}, minmax(0, 1fr))`;\n });\n\n /**\n * Whether a value should render as the `--` placeholder.\n *\n * Applied before the type is considered, so the rule holds uniformly across every\n * type. `0` and `false`-ish numbers are real values and are not treated as empty.\n */\n isEmpty(value: TkDataListItem['value']): boolean {\n return value === null || value === undefined || (typeof value === 'string' && value.trim() === '');\n }\n\n /**\n * The effective value type, defaulting to `'text'`.\n *\n * A `'link'` with no `handler` has nothing to invoke, so it resolves to `'text'`: it\n * renders as plain data instead of as a button that does nothing. That keeps a dead\n * control out of the accessibility tree and the tab order entirely, which `disabled`\n * would not do — and `disabled` would also claim the action merely is unavailable\n * right now, which is not the case.\n */\n resolveType(item: TkDataListItem): TkDataListValueType {\n const type = item.type ?? 'text';\n return type === 'link' && !item.handler ? 'text' : type;\n }\n\n /** The effective tag severity, defaulting to the one used in the design. */\n resolveTagSeverity(item: TkDataListItem): TagSeverity {\n return item.tagSeverity ?? DEFAULT_TAG_SEVERITY;\n }\n\n /**\n * The value as a string, for the child components that require one\n * (`tk-tag.value` and `tk-button.label` are both typed `string`).\n */\n asText(value: TkDataListItem['value']): string {\n return this.isEmpty(value) ? '' : String(value);\n }\n}\n","<!--\n A description list, so assistive tech announces each entry as a term→value pair.\n Each pair is wrapped in a <div> — valid inside <dl>, and it lets the list and grid\n layouts share identical markup, differing only in CSS.\n-->\n<dl\n class=\"tk-data-list\"\n [class.tk-data-list--list]=\"!isGrid()\"\n [class.tk-data-list--grid]=\"isGrid()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\">\n @for (item of items(); track $index) {\n <div class=\"tk-data-list__item\">\n <dt class=\"tk-data-list__term\">\n @if (item.leadingIcon) {\n <tk-avatar variant=\"icon\" severity=\"secondary\" size=\"m\" [icon]=\"item.leadingIcon\" aria-hidden=\"true\" />\n }\n\n <span class=\"tk-data-list__text\">\n <span class=\"tk-data-list__label\">{{ item.label }}</span>\n @if (item.description) {\n <span class=\"tk-data-list__description\">{{ item.description }}</span>\n }\n </span>\n </dt>\n\n <dd\n class=\"tk-data-list__value\"\n [class.tk-data-list__value--indented]=\"isGrid() && !!item.leadingIcon\">\n <!--\n The empty check runs before the type switch, so the `--` placeholder rule\n applies uniformly to every value type.\n -->\n @if (isEmpty(item.value)) {\n <span class=\"tk-data-list__placeholder\">{{ emptyPlaceholder() }}</span>\n } @else {\n @switch (resolveType(item)) {\n @case ('tag') {\n <tk-tag [value]=\"asText(item.value)\" [severity]=\"resolveTagSeverity(item)\" />\n }\n @case ('link') {\n <tk-button [label]=\"asText(item.value)\" [link]=\"true\" (clicked)=\"item.handler?.()\" />\n }\n @default {\n <!-- 'text' and 'number' render identically; number formatting is not implemented yet. -->\n <span class=\"tk-data-list__value-text\">{{ item.value }}</span>\n }\n }\n }\n </dd>\n </div>\n }\n</dl>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -668,6 +668,19 @@ const plusIcon = {
|
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
670
|
|
|
671
|
+
const minusIcon = {
|
|
672
|
+
'minus': {
|
|
673
|
+
name: 'faMinus',
|
|
674
|
+
tags: ['basic'],
|
|
675
|
+
source: 'Font awesome',
|
|
676
|
+
styles: {
|
|
677
|
+
light: import('@fortawesome/pro-light-svg-icons').then(m => m.faMinus),
|
|
678
|
+
regular: import('@fortawesome/pro-regular-svg-icons').then(m => m.faMinus),
|
|
679
|
+
solid: import('@fortawesome/pro-solid-svg-icons').then(m => m.faMinus)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
671
684
|
const linkIcon = {
|
|
672
685
|
'link': {
|
|
673
686
|
name: 'faLink',
|
|
@@ -1150,6 +1163,7 @@ const IconCatalog = {
|
|
|
1150
1163
|
...locationIcon,
|
|
1151
1164
|
...layerIcon,
|
|
1152
1165
|
...plusIcon,
|
|
1166
|
+
...minusIcon,
|
|
1153
1167
|
...linkIcon,
|
|
1154
1168
|
...checkIcon,
|
|
1155
1169
|
...xMarkIcon,
|