@tekus/design-system 5.38.1 → 5.39.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,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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"}
@@ -0,0 +1,193 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, input, inject, computed, ChangeDetectionStrategy, Component, forwardRef } from '@angular/core';
3
+ import { TagComponent } from '@tekus/design-system/components/tag';
4
+ import { ProgressBarComponent } from '@tekus/design-system/components/progress-bar';
5
+
6
+ /**
7
+ * Provided by `tk-stat-group`, injected optionally by `tk-stat`.
8
+ *
9
+ * This is how a stat discovers that it lives inside a group and must drop its own
10
+ * surface — the group owns the background, so the children must not paint their
11
+ * own. A stat never sets its grouped appearance itself, and a consumer never has
12
+ * to hand it down.
13
+ */
14
+ const TK_STAT_GROUP = new InjectionToken('TK_STAT_GROUP');
15
+
16
+ /**
17
+ * Counter backing the unique id that links a stat's label to its `aria-labelledby`.
18
+ * A module-level counter (rather than a random uuid) keeps the id stable between
19
+ * server and client renders and deterministic in tests.
20
+ */
21
+ let _statUid = 0;
22
+ /**
23
+ * @component StatComponent
24
+ * @description
25
+ * Displays a single highlighted metric: a large dominant value, a small label above
26
+ * it, and optionally a status tag, a description and a progress bar. Used at the top
27
+ * of listings, summaries and dashboards to surface a key figure at a glance.
28
+ *
29
+ * This component supports:
30
+ * - `label` / `value`: the two required parts. The value is the dominant figure.
31
+ * - `severity`: the feedback intent. Colors the value — and nothing else.
32
+ * - `prefix` / `suffix`: small muted affixes flanking the value (e.g. `'$'`, `'/200'`).
33
+ * - `description`: a secondary line below the value.
34
+ * - `tagValue` / `tagSeverity`: a trailing tag in the header, via `tk-tag`.
35
+ * - `progress`: shows a `tk-progress-bar` below the content when set.
36
+ * - `severityLabel`: screen-reader-only text so `severity` is not color-only.
37
+ *
38
+ * There is no `grouped` input: wrapping stats in `tk-stat-group` applies the grouped
39
+ * appearance automatically.
40
+ *
41
+ * @usage
42
+ * ### Basic Usage
43
+ * ```html
44
+ * <tk-stat label="Usuarios mensuales" [value]="500" description="Audiencia estimada" />
45
+ * <tk-stat label="Slots" [value]="25" suffix="/200" [progress]="12" tagValue="Disponible" tagSeverity="success" />
46
+ * ```
47
+ */
48
+ class StatComponent {
49
+ constructor() {
50
+ /** The small title shown above the value. */
51
+ this.label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
52
+ /**
53
+ * The dominant figure. Accepts a number or an already-formatted string
54
+ * (`'1.2K'`, `'99,9 %'`) so the consumer keeps control of formatting and locale.
55
+ */
56
+ this.value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
57
+ /**
58
+ * The feedback intent of the stat, which colors the value.
59
+ * @default 'secondary'
60
+ */
61
+ this.severity = input('secondary', ...(ngDevMode ? [{ debugName: "severity" }] : /* istanbul ignore next */ []));
62
+ /** Small muted text rendered immediately before the value (e.g. `'$'`). */
63
+ this.prefix = input('', ...(ngDevMode ? [{ debugName: "prefix" }] : /* istanbul ignore next */ []));
64
+ /** Small muted text rendered immediately after the value (e.g. `'/200'`, `'/GB'`). */
65
+ this.suffix = input('', ...(ngDevMode ? [{ debugName: "suffix" }] : /* istanbul ignore next */ []));
66
+ /** A secondary line below the value, giving context or the origin of the figure. */
67
+ this.description = input('', ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
68
+ /** The text of the trailing header tag. The tag is only rendered when this is set. */
69
+ this.tagValue = input('', ...(ngDevMode ? [{ debugName: "tagValue" }] : /* istanbul ignore next */ []));
70
+ /**
71
+ * The severity of the trailing header tag.
72
+ * @default 'secondary'
73
+ */
74
+ this.tagSeverity = input('secondary', ...(ngDevMode ? [{ debugName: "tagSeverity" }] : /* istanbul ignore next */ []));
75
+ /**
76
+ * The completion percentage (0-100) shown as a progress bar below the content.
77
+ * Leave `undefined` to omit the bar — `0` is a valid value and renders an empty bar.
78
+ */
79
+ this.progress = input(undefined, ...(ngDevMode ? [{ debugName: "progress" }] : /* istanbul ignore next */ []));
80
+ /**
81
+ * Screen-reader-only text describing what this stat's `severity` means (e.g.
82
+ * `'sobre la cuota'`). Set it whenever `severity` is not `'secondary'`, so the
83
+ * meaning is not carried by color alone. Saying it visibly through `tagValue` or
84
+ * `description` is usually better, since it serves sighted users too.
85
+ */
86
+ this.severityLabel = input('', ...(ngDevMode ? [{ debugName: "severityLabel" }] : /* istanbul ignore next */ []));
87
+ /**
88
+ * The group this stat is projected into, if any. Injected optionally: a standalone
89
+ * stat resolves `null` and keeps its own surface.
90
+ */
91
+ this.group = inject(TK_STAT_GROUP, { optional: true });
92
+ /** Whether this stat is projected inside a `tk-stat-group`. */
93
+ this.grouped = this.group !== null;
94
+ /** Unique DOM id linking the rendered label to the host's `aria-labelledby`. */
95
+ this.labelId = `tk-stat-label-${++_statUid}`;
96
+ /**
97
+ * Whether a progress bar should be rendered. Tests for `null`/`undefined` rather
98
+ * than truthiness so that `progress = 0` still renders an empty bar.
99
+ */
100
+ this.hasProgress = computed(() => this.progress() != null, ...(ngDevMode ? [{ debugName: "hasProgress" }] : /* istanbul ignore next */ []));
101
+ /** Computed host class: the BEM block plus the severity and grouped modifiers. */
102
+ this.hostClass = computed(() => {
103
+ const classes = ['tk-stat', `tk-stat--${this.severity()}`];
104
+ if (this.grouped) {
105
+ classes.push('tk-stat--grouped');
106
+ }
107
+ return classes.join(' ');
108
+ }, ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
109
+ }
110
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: StatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
111
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: StatComponent, isStandalone: true, selector: "tk-stat", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, severity: { classPropertyName: "severity", publicName: "severity", isSignal: true, isRequired: false, transformFunction: null }, prefix: { classPropertyName: "prefix", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, tagValue: { classPropertyName: "tagValue", publicName: "tagValue", isSignal: true, isRequired: false, transformFunction: null }, tagSeverity: { classPropertyName: "tagSeverity", publicName: "tagSeverity", isSignal: true, isRequired: false, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, severityLabel: { classPropertyName: "severityLabel", publicName: "severityLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "class": "hostClass()", "attr.aria-labelledby": "labelId" } }, ngImport: i0, template: "<div class=\"tk-stat__header\">\n <span class=\"tk-stat__label\" [id]=\"labelId\">{{ label() }}</span>\n @if (tagValue()) {\n <tk-tag class=\"tk-stat__tag\" [value]=\"tagValue()\" [severity]=\"tagSeverity()\" />\n }\n</div>\n\n<p class=\"tk-stat__value\">\n @if (prefix()) {\n <span class=\"tk-stat__affix\">{{ prefix() }}</span>\n }\n <span class=\"tk-stat__amount\">{{ value() }}</span>\n @if (suffix()) {\n <span class=\"tk-stat__affix\">{{ suffix() }}</span>\n }\n @if (severityLabel()) {\n <span class=\"tk-stat__sr-only\">{{ severityLabel() }}</span>\n }\n</p>\n\n@if (description()) {\n <p class=\"tk-stat__description\">{{ description() }}</p>\n}\n\n@if (hasProgress()) {\n <tk-progress-bar\n class=\"tk-stat__progress\"\n [value]=\"progress()!\"\n size=\"small\"\n aria-hidden=\"true\" />\n}\n", styles: [":host{display:flex;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:flex-start;gap:var(--tk-spacing-gap-xs, .25rem);min-width:var(--tk-size-base-1000, 10rem);max-width:var(--tk-stat-max-width, var(--tk-size-base-2000, 20rem));padding:var(--tk-spacing-padding-s, .5rem);border-radius:var(--tk-borderRadius-theme-m, var(--tk-borderRadius-m, 1rem));background-color:var(--tk-color-background-soft, #f2f1f1);font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-stat__header{display:flex;align-items:center;justify-content:space-between;gap:var(--tk-spacing-gap-xs, .25rem);width:100%;height:var(--tk-size-base-150, 1.5rem)}.tk-stat__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1.143;color:var(--tk-color-text-default, #191a1b)}.tk-stat__tag{flex-shrink:0}.tk-stat__value{display:flex;align-items:flex-end;max-width:100%;margin:0;white-space:nowrap}.tk-stat__amount{font-weight:var(--tk-font-weight-600, 600);font-size:var(--tk-font-size-headers-xl, 2.5rem);line-height:1}.tk-stat__affix{font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-l, 1.125rem);line-height:1.4;color:var(--tk-color-text-muted, #8a8a8b)}.tk-stat__description{min-width:0;max-width:100%;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-legal-m, .75rem);line-height:1.334;color:var(--tk-color-text-subtle, #5d5d5e)}.tk-stat__progress{width:100%}.tk-stat__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host(.tk-stat--secondary) .tk-stat__amount{color:var(--tk-color-text-default, #191a1b)}:host(.tk-stat--primary) .tk-stat__amount{color:var(--tk-color-primary-default, #16006f)}:host(.tk-stat--success) .tk-stat__amount{color:var(--tk-color-feedback-success-default, #22c55e)}:host(.tk-stat--warn) .tk-stat__amount{color:var(--tk-color-feedback-warn-default, #ffd21b)}:host(.tk-stat--danger) .tk-stat__amount{color:var(--tk-color-feedback-danger-default, #ff6640)}:host(.tk-stat--grouped){background-color:transparent}\n"], dependencies: [{ kind: "component", type: TagComponent, selector: "tk-tag", inputs: ["value", "severity", "truncationLimit"] }, { kind: "component", type: ProgressBarComponent, selector: "tk-progress-bar", inputs: ["value", "showValue", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
112
+ }
113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: StatComponent, decorators: [{
114
+ type: Component,
115
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'tk-stat', imports: [TagComponent, ProgressBarComponent], host: {
116
+ '[class]': 'hostClass()',
117
+ role: 'group',
118
+ '[attr.aria-labelledby]': 'labelId',
119
+ }, template: "<div class=\"tk-stat__header\">\n <span class=\"tk-stat__label\" [id]=\"labelId\">{{ label() }}</span>\n @if (tagValue()) {\n <tk-tag class=\"tk-stat__tag\" [value]=\"tagValue()\" [severity]=\"tagSeverity()\" />\n }\n</div>\n\n<p class=\"tk-stat__value\">\n @if (prefix()) {\n <span class=\"tk-stat__affix\">{{ prefix() }}</span>\n }\n <span class=\"tk-stat__amount\">{{ value() }}</span>\n @if (suffix()) {\n <span class=\"tk-stat__affix\">{{ suffix() }}</span>\n }\n @if (severityLabel()) {\n <span class=\"tk-stat__sr-only\">{{ severityLabel() }}</span>\n }\n</p>\n\n@if (description()) {\n <p class=\"tk-stat__description\">{{ description() }}</p>\n}\n\n@if (hasProgress()) {\n <tk-progress-bar\n class=\"tk-stat__progress\"\n [value]=\"progress()!\"\n size=\"small\"\n aria-hidden=\"true\" />\n}\n", styles: [":host{display:flex;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:flex-start;gap:var(--tk-spacing-gap-xs, .25rem);min-width:var(--tk-size-base-1000, 10rem);max-width:var(--tk-stat-max-width, var(--tk-size-base-2000, 20rem));padding:var(--tk-spacing-padding-s, .5rem);border-radius:var(--tk-borderRadius-theme-m, var(--tk-borderRadius-m, 1rem));background-color:var(--tk-color-background-soft, #f2f1f1);font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-stat__header{display:flex;align-items:center;justify-content:space-between;gap:var(--tk-spacing-gap-xs, .25rem);width:100%;height:var(--tk-size-base-150, 1.5rem)}.tk-stat__label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1.143;color:var(--tk-color-text-default, #191a1b)}.tk-stat__tag{flex-shrink:0}.tk-stat__value{display:flex;align-items:flex-end;max-width:100%;margin:0;white-space:nowrap}.tk-stat__amount{font-weight:var(--tk-font-weight-600, 600);font-size:var(--tk-font-size-headers-xl, 2.5rem);line-height:1}.tk-stat__affix{font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-l, 1.125rem);line-height:1.4;color:var(--tk-color-text-muted, #8a8a8b)}.tk-stat__description{min-width:0;max-width:100%;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-legal-m, .75rem);line-height:1.334;color:var(--tk-color-text-subtle, #5d5d5e)}.tk-stat__progress{width:100%}.tk-stat__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}:host(.tk-stat--secondary) .tk-stat__amount{color:var(--tk-color-text-default, #191a1b)}:host(.tk-stat--primary) .tk-stat__amount{color:var(--tk-color-primary-default, #16006f)}:host(.tk-stat--success) .tk-stat__amount{color:var(--tk-color-feedback-success-default, #22c55e)}:host(.tk-stat--warn) .tk-stat__amount{color:var(--tk-color-feedback-warn-default, #ffd21b)}:host(.tk-stat--danger) .tk-stat__amount{color:var(--tk-color-feedback-danger-default, #ff6640)}:host(.tk-stat--grouped){background-color:transparent}\n"] }]
120
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], severity: [{ type: i0.Input, args: [{ isSignal: true, alias: "severity", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], tagValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagValue", required: false }] }], tagSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "tagSeverity", required: false }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], severityLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "severityLabel", required: false }] }] } });
121
+
122
+ /**
123
+ * @component StatGroupComponent
124
+ * @description
125
+ * Wraps a row of related `tk-stat` into a single shared surface, inserting a
126
+ * separator between each pair. The group owns the background and the radius; every
127
+ * stat projected into it drops its own surface automatically, through the
128
+ * {@link TK_STAT_GROUP} token this component provides.
129
+ *
130
+ * This component supports:
131
+ * - `orientation`: `'auto'` (a row that collapses to a column on small screens),
132
+ * `'row'` or `'column'`.
133
+ * - `ariaLabel`: names the group for assistive tech. Only when set does the group
134
+ * expose `role="group"` — an unnamed group is noise.
135
+ *
136
+ * @usage
137
+ * ### Basic Usage
138
+ * ```html
139
+ * <tk-stat-group ariaLabel="Resumen de la campaña">
140
+ * <tk-stat label="Usuarios mensuales" [value]="500" description="Audiencia estimada" />
141
+ * <tk-stat label="Estado de la pantalla" [value]="500" severity="success" description="12 de 12 días" />
142
+ * <tk-stat label="Slots" [value]="25" suffix="/200" [progress]="12" />
143
+ * </tk-stat-group>
144
+ * ```
145
+ */
146
+ class StatGroupComponent {
147
+ constructor() {
148
+ /**
149
+ * How the stats are laid out.
150
+ * - `'auto'`: horizontal, collapsing to a column on viewports narrower than a
151
+ * vertical tablet. This is the responsive default the design calls for.
152
+ * - `'row'` / `'column'`: force one direction — for a group inside a narrow
153
+ * sidebar on a wide screen, or one that must stay horizontal on mobile.
154
+ * @default 'auto'
155
+ */
156
+ this.orientation = input('auto', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
157
+ /**
158
+ * The accessible name of the group. When set, the group exposes itself as
159
+ * `role="group"` with this label; when omitted it stays structurally invisible to
160
+ * assistive tech, since an unnamed group adds nothing but noise.
161
+ */
162
+ this.ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
163
+ }
164
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: StatGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
165
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: StatGroupComponent, isStandalone: true, selector: "tk-stat-group", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.tk-stat-group--auto": "orientation() === \"auto\"", "class.tk-stat-group--column": "orientation() === \"column\"", "attr.role": "ariaLabel() ? \"group\" : null", "attr.aria-label": "ariaLabel() || null" }, classAttribute: "tk-stat-group" }, providers: [
166
+ {
167
+ provide: TK_STAT_GROUP,
168
+ useExisting: forwardRef(() => StatGroupComponent),
169
+ },
170
+ ], ngImport: i0, template: '<ng-content />', isInline: true, styles: [":host{display:inline-flex;box-sizing:border-box;flex-direction:row;align-items:stretch;max-width:100%;overflow-x:auto;gap:var(--tk-spacing-gap-m, 1rem);border-radius:var(--tk-borderRadius-theme-m, var(--tk-borderRadius-m, 1rem));background-color:var(--tk-color-background-soft, #f2f1f1);vertical-align:top}:host ::ng-deep>tk-stat{position:relative;flex:0 1 auto}:host ::ng-deep>tk-stat+tk-stat:before{content:\"\";position:absolute;top:var(--tk-spacing-padding-s, .5rem);bottom:var(--tk-spacing-padding-s, .5rem);left:calc(-.5 * var(--tk-spacing-gap-m, 1rem));width:1px;background-color:var(--tk-color-border-subtle, #e4e4e4)}:host(.tk-stat-group--column){flex-direction:column;width:100%}:host(.tk-stat-group--column) ::ng-deep>tk-stat{max-width:none}:host(.tk-stat-group--column) ::ng-deep>tk-stat+tk-stat:before{top:calc(-.5 * var(--tk-spacing-gap-m, 1rem));right:var(--tk-spacing-padding-s, .5rem);bottom:auto;left:var(--tk-spacing-padding-s, .5rem);width:auto;height:1px}@media screen and (max-width:767px){:host(.tk-stat-group--auto){flex-direction:column;width:100%}:host(.tk-stat-group--auto) ::ng-deep>tk-stat{max-width:none}:host(.tk-stat-group--auto) ::ng-deep>tk-stat+tk-stat:before{top:calc(-.5 * var(--tk-spacing-gap-m, 1rem));right:var(--tk-spacing-padding-s, .5rem);bottom:auto;left:var(--tk-spacing-padding-s, .5rem);width:auto;height:1px}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
171
+ }
172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: StatGroupComponent, decorators: [{
173
+ type: Component,
174
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'tk-stat-group', template: '<ng-content />', providers: [
175
+ {
176
+ provide: TK_STAT_GROUP,
177
+ useExisting: forwardRef(() => StatGroupComponent),
178
+ },
179
+ ], host: {
180
+ class: 'tk-stat-group',
181
+ '[class.tk-stat-group--auto]': 'orientation() === "auto"',
182
+ '[class.tk-stat-group--column]': 'orientation() === "column"',
183
+ '[attr.role]': 'ariaLabel() ? "group" : null',
184
+ '[attr.aria-label]': 'ariaLabel() || null',
185
+ }, styles: [":host{display:inline-flex;box-sizing:border-box;flex-direction:row;align-items:stretch;max-width:100%;overflow-x:auto;gap:var(--tk-spacing-gap-m, 1rem);border-radius:var(--tk-borderRadius-theme-m, var(--tk-borderRadius-m, 1rem));background-color:var(--tk-color-background-soft, #f2f1f1);vertical-align:top}:host ::ng-deep>tk-stat{position:relative;flex:0 1 auto}:host ::ng-deep>tk-stat+tk-stat:before{content:\"\";position:absolute;top:var(--tk-spacing-padding-s, .5rem);bottom:var(--tk-spacing-padding-s, .5rem);left:calc(-.5 * var(--tk-spacing-gap-m, 1rem));width:1px;background-color:var(--tk-color-border-subtle, #e4e4e4)}:host(.tk-stat-group--column){flex-direction:column;width:100%}:host(.tk-stat-group--column) ::ng-deep>tk-stat{max-width:none}:host(.tk-stat-group--column) ::ng-deep>tk-stat+tk-stat:before{top:calc(-.5 * var(--tk-spacing-gap-m, 1rem));right:var(--tk-spacing-padding-s, .5rem);bottom:auto;left:var(--tk-spacing-padding-s, .5rem);width:auto;height:1px}@media screen and (max-width:767px){:host(.tk-stat-group--auto){flex-direction:column;width:100%}:host(.tk-stat-group--auto) ::ng-deep>tk-stat{max-width:none}:host(.tk-stat-group--auto) ::ng-deep>tk-stat+tk-stat:before{top:calc(-.5 * var(--tk-spacing-gap-m, 1rem));right:var(--tk-spacing-padding-s, .5rem);bottom:auto;left:var(--tk-spacing-padding-s, .5rem);width:auto;height:1px}}\n"] }]
186
+ }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
187
+
188
+ /**
189
+ * Generated bundle index. Do not edit.
190
+ */
191
+
192
+ export { StatComponent, StatGroupComponent, TK_STAT_GROUP };
193
+ //# sourceMappingURL=tekus-design-system-components-stat.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tekus-design-system-components-stat.mjs","sources":["../../../projects/design-system/components/stat/src/stat.types.ts","../../../projects/design-system/components/stat/src/stat.component.ts","../../../projects/design-system/components/stat/src/stat.component.html","../../../projects/design-system/components/stat/src/stat-group.component.ts","../../../projects/design-system/components/stat/tekus-design-system-components-stat.ts"],"sourcesContent":["import { InjectionToken, Signal } from '@angular/core';\n\n/**\n * The feedback intent of a stat. It drives the color of the value — and only the\n * value: the surface, the label, the description and the progress bar are\n * identical across every severity.\n */\nexport type StatSeverity =\n | 'secondary'\n | 'primary'\n | 'success'\n | 'warn'\n | 'danger';\n\n/**\n * How a `tk-stat-group` lays its children out.\n * - `auto`: a row that collapses to a column on small screens.\n * - `row`: always horizontal.\n * - `column`: always vertical.\n */\nexport type StatGroupOrientation = 'auto' | 'row' | 'column';\n\n/** The contract a `tk-stat-group` exposes to the `tk-stat` children it projects. */\nexport interface StatGroupContext {\n readonly orientation: Signal<StatGroupOrientation>;\n}\n\n/**\n * Provided by `tk-stat-group`, injected optionally by `tk-stat`.\n *\n * This is how a stat discovers that it lives inside a group and must drop its own\n * surface — the group owns the background, so the children must not paint their\n * own. A stat never sets its grouped appearance itself, and a consumer never has\n * to hand it down.\n */\nexport const TK_STAT_GROUP = new InjectionToken<StatGroupContext>('TK_STAT_GROUP');\n","import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { TagComponent, TagSeverity } from '@tekus/design-system/components/tag';\nimport { ProgressBarComponent } from '@tekus/design-system/components/progress-bar';\nimport { StatSeverity, TK_STAT_GROUP } from './stat.types';\n\n/**\n * Counter backing the unique id that links a stat's label to its `aria-labelledby`.\n * A module-level counter (rather than a random uuid) keeps the id stable between\n * server and client renders and deterministic in tests.\n */\nlet _statUid = 0;\n\n/**\n * @component StatComponent\n * @description\n * Displays a single highlighted metric: a large dominant value, a small label above\n * it, and optionally a status tag, a description and a progress bar. Used at the top\n * of listings, summaries and dashboards to surface a key figure at a glance.\n *\n * This component supports:\n * - `label` / `value`: the two required parts. The value is the dominant figure.\n * - `severity`: the feedback intent. Colors the value — and nothing else.\n * - `prefix` / `suffix`: small muted affixes flanking the value (e.g. `'$'`, `'/200'`).\n * - `description`: a secondary line below the value.\n * - `tagValue` / `tagSeverity`: a trailing tag in the header, via `tk-tag`.\n * - `progress`: shows a `tk-progress-bar` below the content when set.\n * - `severityLabel`: screen-reader-only text so `severity` is not color-only.\n *\n * There is no `grouped` input: wrapping stats in `tk-stat-group` applies the grouped\n * appearance automatically.\n *\n * @usage\n * ### Basic Usage\n * ```html\n * <tk-stat label=\"Usuarios mensuales\" [value]=\"500\" description=\"Audiencia estimada\" />\n * <tk-stat label=\"Slots\" [value]=\"25\" suffix=\"/200\" [progress]=\"12\" tagValue=\"Disponible\" tagSeverity=\"success\" />\n * ```\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'tk-stat',\n imports: [TagComponent, ProgressBarComponent],\n templateUrl: './stat.component.html',\n styleUrl: './stat.component.scss',\n host: {\n '[class]': 'hostClass()',\n role: 'group',\n '[attr.aria-labelledby]': 'labelId',\n },\n})\nexport class StatComponent {\n /** The small title shown above the value. */\n label = input.required<string>();\n\n /**\n * The dominant figure. Accepts a number or an already-formatted string\n * (`'1.2K'`, `'99,9 %'`) so the consumer keeps control of formatting and locale.\n */\n value = input.required<string | number>();\n\n /**\n * The feedback intent of the stat, which colors the value.\n * @default 'secondary'\n */\n severity = input<StatSeverity>('secondary');\n\n /** Small muted text rendered immediately before the value (e.g. `'$'`). */\n prefix = input<string>('');\n\n /** Small muted text rendered immediately after the value (e.g. `'/200'`, `'/GB'`). */\n suffix = input<string>('');\n\n /** A secondary line below the value, giving context or the origin of the figure. */\n description = input<string>('');\n\n /** The text of the trailing header tag. The tag is only rendered when this is set. */\n tagValue = input<string>('');\n\n /**\n * The severity of the trailing header tag.\n * @default 'secondary'\n */\n tagSeverity = input<TagSeverity>('secondary');\n\n /**\n * The completion percentage (0-100) shown as a progress bar below the content.\n * Leave `undefined` to omit the bar — `0` is a valid value and renders an empty bar.\n */\n progress = input<number | undefined>(undefined);\n\n /**\n * Screen-reader-only text describing what this stat's `severity` means (e.g.\n * `'sobre la cuota'`). Set it whenever `severity` is not `'secondary'`, so the\n * meaning is not carried by color alone. Saying it visibly through `tagValue` or\n * `description` is usually better, since it serves sighted users too.\n */\n severityLabel = input<string>('');\n\n /**\n * The group this stat is projected into, if any. Injected optionally: a standalone\n * stat resolves `null` and keeps its own surface.\n */\n private readonly group = inject(TK_STAT_GROUP, { optional: true });\n\n /** Whether this stat is projected inside a `tk-stat-group`. */\n readonly grouped = this.group !== null;\n\n /** Unique DOM id linking the rendered label to the host's `aria-labelledby`. */\n readonly labelId = `tk-stat-label-${++_statUid}`;\n\n /**\n * Whether a progress bar should be rendered. Tests for `null`/`undefined` rather\n * than truthiness so that `progress = 0` still renders an empty bar.\n */\n hasProgress = computed(() => this.progress() != null);\n\n /** Computed host class: the BEM block plus the severity and grouped modifiers. */\n hostClass = computed(() => {\n const classes = ['tk-stat', `tk-stat--${this.severity()}`];\n if (this.grouped) {\n classes.push('tk-stat--grouped');\n }\n return classes.join(' ');\n });\n}\n","<div class=\"tk-stat__header\">\n <span class=\"tk-stat__label\" [id]=\"labelId\">{{ label() }}</span>\n @if (tagValue()) {\n <tk-tag class=\"tk-stat__tag\" [value]=\"tagValue()\" [severity]=\"tagSeverity()\" />\n }\n</div>\n\n<p class=\"tk-stat__value\">\n @if (prefix()) {\n <span class=\"tk-stat__affix\">{{ prefix() }}</span>\n }\n <span class=\"tk-stat__amount\">{{ value() }}</span>\n @if (suffix()) {\n <span class=\"tk-stat__affix\">{{ suffix() }}</span>\n }\n @if (severityLabel()) {\n <span class=\"tk-stat__sr-only\">{{ severityLabel() }}</span>\n }\n</p>\n\n@if (description()) {\n <p class=\"tk-stat__description\">{{ description() }}</p>\n}\n\n@if (hasProgress()) {\n <tk-progress-bar\n class=\"tk-stat__progress\"\n [value]=\"progress()!\"\n size=\"small\"\n aria-hidden=\"true\" />\n}\n","import { ChangeDetectionStrategy, Component, forwardRef, input } from '@angular/core';\nimport { StatGroupContext, StatGroupOrientation, TK_STAT_GROUP } from './stat.types';\n\n/**\n * @component StatGroupComponent\n * @description\n * Wraps a row of related `tk-stat` into a single shared surface, inserting a\n * separator between each pair. The group owns the background and the radius; every\n * stat projected into it drops its own surface automatically, through the\n * {@link TK_STAT_GROUP} token this component provides.\n *\n * This component supports:\n * - `orientation`: `'auto'` (a row that collapses to a column on small screens),\n * `'row'` or `'column'`.\n * - `ariaLabel`: names the group for assistive tech. Only when set does the group\n * expose `role=\"group\"` — an unnamed group is noise.\n *\n * @usage\n * ### Basic Usage\n * ```html\n * <tk-stat-group ariaLabel=\"Resumen de la campaña\">\n * <tk-stat label=\"Usuarios mensuales\" [value]=\"500\" description=\"Audiencia estimada\" />\n * <tk-stat label=\"Estado de la pantalla\" [value]=\"500\" severity=\"success\" description=\"12 de 12 días\" />\n * <tk-stat label=\"Slots\" [value]=\"25\" suffix=\"/200\" [progress]=\"12\" />\n * </tk-stat-group>\n * ```\n */\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'tk-stat-group',\n template: '<ng-content />',\n styleUrl: './stat-group.component.scss',\n providers: [\n {\n provide: TK_STAT_GROUP,\n useExisting: forwardRef(() => StatGroupComponent),\n },\n ],\n host: {\n class: 'tk-stat-group',\n '[class.tk-stat-group--auto]': 'orientation() === \"auto\"',\n '[class.tk-stat-group--column]': 'orientation() === \"column\"',\n '[attr.role]': 'ariaLabel() ? \"group\" : null',\n '[attr.aria-label]': 'ariaLabel() || null',\n },\n})\nexport class StatGroupComponent implements StatGroupContext {\n /**\n * How the stats are laid out.\n * - `'auto'`: horizontal, collapsing to a column on viewports narrower than a\n * vertical tablet. This is the responsive default the design calls for.\n * - `'row'` / `'column'`: force one direction — for a group inside a narrow\n * sidebar on a wide screen, or one that must stay horizontal on mobile.\n * @default 'auto'\n */\n orientation = input<StatGroupOrientation>('auto');\n\n /**\n * The accessible name of the group. When set, the group exposes itself as\n * `role=\"group\"` with this label; when omitted it stays structurally invisible to\n * assistive tech, since an unnamed group adds nothing but noise.\n */\n ariaLabel = input<string>('');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;AA2BA;;;;;;;AAOG;MACU,aAAa,GAAG,IAAI,cAAc,CAAmB,eAAe;;AC9BjF;;;;AAIG;AACH,IAAI,QAAQ,GAAG,CAAC;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;MAaU,aAAa,CAAA;AAZ1B,IAAA,WAAA,GAAA;;AAcE,QAAA,IAAA,CAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,2EAAU;AAEhC;;;AAGG;AACH,QAAA,IAAA,CAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,2EAAmB;AAEzC;;;AAGG;AACH,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAe,WAAW,+EAAC;;AAG3C,QAAA,IAAA,CAAA,MAAM,GAAG,KAAK,CAAS,EAAE,6EAAC;;AAG1B,QAAA,IAAA,CAAA,MAAM,GAAG,KAAK,CAAS,EAAE,6EAAC;;AAG1B,QAAA,IAAA,CAAA,WAAW,GAAG,KAAK,CAAS,EAAE,kFAAC;;AAG/B,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAE5B;;;AAGG;AACH,QAAA,IAAA,CAAA,WAAW,GAAG,KAAK,CAAc,WAAW,kFAAC;AAE7C;;;AAGG;AACH,QAAA,IAAA,CAAA,QAAQ,GAAG,KAAK,CAAqB,SAAS,+EAAC;AAE/C;;;;;AAKG;AACH,QAAA,IAAA,CAAA,aAAa,GAAG,KAAK,CAAS,EAAE,oFAAC;AAEjC;;;AAGG;QACc,IAAA,CAAA,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;AAGzD,QAAA,IAAA,CAAA,OAAO,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI;;AAG7B,QAAA,IAAA,CAAA,OAAO,GAAG,CAAA,cAAA,EAAiB,EAAE,QAAQ,EAAE;AAEhD;;;AAGG;AACH,QAAA,IAAA,CAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,kFAAC;;AAGrD,QAAA,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;AACxB,YAAA,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,CAAA,SAAA,EAAY,IAAI,CAAC,QAAQ,EAAE,CAAA,CAAE,CAAC;AAC1D,YAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,gBAAA,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC;YAClC;AACA,YAAA,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;AAC1B,QAAA,CAAC,gFAAC;AACH,IAAA;+GA1EY,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAb,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,MAAA,EAAA,OAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,aAAA,EAAA,sBAAA,EAAA,SAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EClD1B,w0BA+BA,EAAA,MAAA,EAAA,CAAA,6tEAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDUY,YAAY,qGAAE,oBAAoB,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,WAAA,EAAA,MAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FASjC,aAAa,EAAA,UAAA,EAAA,CAAA;kBAZzB,SAAS;sCACS,uBAAuB,CAAC,MAAM,EAAA,QAAA,EACrC,SAAS,EAAA,OAAA,EACV,CAAC,YAAY,EAAE,oBAAoB,CAAC,EAAA,IAAA,EAGvC;AACJ,wBAAA,SAAS,EAAE,aAAa;AACxB,wBAAA,IAAI,EAAE,OAAO;AACb,wBAAA,wBAAwB,EAAE,SAAS;AACpC,qBAAA,EAAA,QAAA,EAAA,w0BAAA,EAAA,MAAA,EAAA,CAAA,6tEAAA,CAAA,EAAA;;;AE7CH;;;;;;;;;;;;;;;;;;;;;;;AAuBG;MAoBU,kBAAkB,CAAA;AAnB/B,IAAA,WAAA,GAAA;AAoBE;;;;;;;AAOG;AACH,QAAA,IAAA,CAAA,WAAW,GAAG,KAAK,CAAuB,MAAM,kFAAC;AAEjD;;;;AAIG;AACH,QAAA,IAAA,CAAA,SAAS,GAAG,KAAK,CAAS,EAAE,gFAAC;AAC9B,IAAA;+GAjBY,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAlB,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,kBAAkB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,2BAAA,EAAA,4BAAA,EAAA,6BAAA,EAAA,8BAAA,EAAA,WAAA,EAAA,gCAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,EAAA,cAAA,EAAA,eAAA,EAAA,EAAA,SAAA,EAdlB;AACT,YAAA;AACE,gBAAA,OAAO,EAAE,aAAa;AACtB,gBAAA,WAAW,EAAE,UAAU,CAAC,MAAM,kBAAkB,CAAC;AAClD,aAAA;AACF,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAPS,gBAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,i1CAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAgBf,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAnB9B,SAAS;AACS,YAAA,IAAA,EAAA,CAAA,EAAA,eAAA,EAAA,uBAAuB,CAAC,MAAM,EAAA,QAAA,EACrC,eAAe,EAAA,QAAA,EACf,gBAAgB,EAAA,SAAA,EAEf;AACT,wBAAA;AACE,4BAAA,OAAO,EAAE,aAAa;AACtB,4BAAA,WAAW,EAAE,UAAU,CAAC,wBAAwB,CAAC;AAClD,yBAAA;qBACF,EAAA,IAAA,EACK;AACJ,wBAAA,KAAK,EAAE,eAAe;AACtB,wBAAA,6BAA6B,EAAE,0BAA0B;AACzD,wBAAA,+BAA+B,EAAE,4BAA4B;AAC7D,wBAAA,aAAa,EAAE,8BAA8B;AAC7C,wBAAA,mBAAmB,EAAE,qBAAqB;AAC3C,qBAAA,EAAA,MAAA,EAAA,CAAA,i1CAAA,CAAA,EAAA;;;AC5CH;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tekus/design-system",
3
3
  "description": "Tekus design system library",
4
- "version": "5.38.1",
4
+ "version": "5.39.0",
5
5
  "license": "UNLICENSED",
6
6
  "peerDependencies": {
7
7
  "@angular/core": "^21.0.0",
@@ -88,6 +88,10 @@
88
88
  "types": "./types/tekus-design-system-components-color-picker.d.ts",
89
89
  "default": "./fesm2022/tekus-design-system-components-color-picker.mjs"
90
90
  },
91
+ "./components/data-list": {
92
+ "types": "./types/tekus-design-system-components-data-list.d.ts",
93
+ "default": "./fesm2022/tekus-design-system-components-data-list.mjs"
94
+ },
91
95
  "./components/date-picker": {
92
96
  "types": "./types/tekus-design-system-components-date-picker.d.ts",
93
97
  "default": "./fesm2022/tekus-design-system-components-date-picker.mjs"
@@ -176,6 +180,10 @@
176
180
  "types": "./types/tekus-design-system-components-sidebar-layout.d.ts",
177
181
  "default": "./fesm2022/tekus-design-system-components-sidebar-layout.mjs"
178
182
  },
183
+ "./components/stat": {
184
+ "types": "./types/tekus-design-system-components-stat.d.ts",
185
+ "default": "./fesm2022/tekus-design-system-components-stat.mjs"
186
+ },
179
187
  "./components/status-bar": {
180
188
  "types": "./types/tekus-design-system-components-status-bar.d.ts",
181
189
  "default": "./fesm2022/tekus-design-system-components-status-bar.mjs"
@@ -0,0 +1,193 @@
1
+ import * as _angular_cdk_layout from '@angular/cdk/layout';
2
+ import * as _angular_core from '@angular/core';
3
+ import { TagSeverity } from '@tekus/design-system/components/tag';
4
+
5
+ /**
6
+ * How the pairs are arranged.
7
+ *
8
+ * - `'list'`: a single vertical column at 100% width. Each item lays out
9
+ * horizontally — term on the left, value on the right — with a divider between items.
10
+ * - `'grid'`: a responsive multi-column grid with no dividers. Each item stacks
11
+ * vertically — term above value. The column count is owned by the component
12
+ * (see `DataListComponent.gridColumnCount`), not by the consumer.
13
+ */
14
+ type TkDataListLayout = 'list' | 'grid';
15
+ /**
16
+ * The closed set of value types a data list can render.
17
+ *
18
+ * This union is deliberately closed: a value is governed, typed data, not free-form
19
+ * content. Adding a type is a system-level change, never something a consumer does
20
+ * inline.
21
+ *
22
+ * It expresses the same idea as `tk-table`'s `TableColumnType` — typed values rather
23
+ * than free-form content — but the two unions are independent and only overlap on
24
+ * `'text'` and `'tag'`. Do not expect parity: `'link'` and `'number'` exist only here,
25
+ * and table's `'image'`, `'actions'`, `'checkbox'`, `'selection'`, `'action-group'` and
26
+ * `'connection-status'` exist only there. Unifying the vocabulary would be a
27
+ * system-level decision affecting both components.
28
+ */
29
+ type TkDataListValueType = 'text' | 'link' | 'tag' | 'number';
30
+ /**
31
+ * A single term→value pair rendered by `tk-data-list`.
32
+ */
33
+ interface TkDataListItem {
34
+ /** The term naming the value. Required. */
35
+ label: string;
36
+ /**
37
+ * The value to render.
38
+ *
39
+ * `null`, `undefined` and blank strings all render as the `--` placeholder,
40
+ * uniformly across every `type`. Note that `0` is a real value, not an empty one.
41
+ */
42
+ value: string | number | null | undefined;
43
+ /**
44
+ * How the value is rendered.
45
+ * @default 'text'
46
+ */
47
+ type?: TkDataListValueType;
48
+ /** An optional secondary line beneath the label. */
49
+ description?: string;
50
+ /**
51
+ * An optional icon shown in a secondary icon avatar before the label.
52
+ * Forwarded to `tk-avatar` as `variant="icon"`; decorative and hidden from
53
+ * assistive technology.
54
+ */
55
+ leadingIcon?: string;
56
+ /**
57
+ * The colour scheme of the tag. Only used when `type` is `'tag'`.
58
+ * @default 'primary'
59
+ */
60
+ tagSeverity?: TagSeverity;
61
+ /**
62
+ * Invoked when the value is activated. Required in practice for `type: 'link'`.
63
+ *
64
+ * Link values render a `tk-button` in its link variant, so they are
65
+ * action-based rather than href-based: this callback is the navigation or
66
+ * action hook. Assistive technology announces them as buttons.
67
+ *
68
+ * A `'link'` without a handler would be a button that does nothing, so it is
69
+ * rendered as plain text instead — no dead control reaches the accessibility
70
+ * tree or the tab order.
71
+ */
72
+ handler?: () => void;
73
+ }
74
+
75
+ /** Rendered in place of a value that is `null`, `undefined` or a blank string. */
76
+ declare const DATA_LIST_EMPTY_PLACEHOLDER = "--";
77
+ /**
78
+ * @component DataListComponent
79
+ * @description
80
+ * Displays a collection of read-only term→value pairs, where each value is a typed
81
+ * piece of data (text, link, tag or number). It is the pair-oriented sibling of
82
+ * `tk-table`: both render governed, typed values, but the data list presents them as
83
+ * label→value pairs instead of rows and columns. Use it for detail panels, record
84
+ * summaries and entity overviews.
85
+ *
86
+ * This component supports:
87
+ * - `items`: the pairs to render. See {@link TkDataListItem}.
88
+ * - `layout`: `'list'` (one column, dividers, term left / value right) or `'grid'`
89
+ * (responsive multi-column, no dividers, term above value).
90
+ * - `emptyPlaceholder`: the text rendered in place of an empty value.
91
+ *
92
+ * The consumer only chooses the layout. Everything that follows from it — item
93
+ * orientation, dividers and the responsive column count — is owned by the component.
94
+ * To vary the layout across breakpoints (e.g. list on mobile, grid on desktop), switch
95
+ * the `layout` input at your own breakpoint.
96
+ *
97
+ * Renders as a description list (`<dl>`) so assistive technology announces each entry
98
+ * as a term→value pair. Interactivity lives inside the value, never on the item — the
99
+ * item itself is neither focusable nor clickable.
100
+ *
101
+ * @usage
102
+ * ### Basic Usage
103
+ * ```html
104
+ * <tk-data-list [items]="details" layout="list"></tk-data-list>
105
+ * ```
106
+ * ```ts
107
+ * details: TkDataListItem[] = [
108
+ * { label: 'Owner', value: 'Ana Bermúdez' },
109
+ * { label: 'Status', value: 'Active', type: 'tag' },
110
+ * { label: 'Devices', value: 2322, type: 'number' },
111
+ * { label: 'Contract', value: 'Open', type: 'link', handler: () => this.openContract() },
112
+ * ];
113
+ * ```
114
+ */
115
+ declare class DataListComponent {
116
+ /**
117
+ * The term→value pairs to render, in order.
118
+ * @default []
119
+ */
120
+ items: _angular_core.InputSignal<TkDataListItem[]>;
121
+ /**
122
+ * How the pairs are arranged.
123
+ * @default 'list'
124
+ */
125
+ layout: _angular_core.InputSignal<TkDataListLayout>;
126
+ /**
127
+ * The text rendered in place of a value that is `null`, `undefined` or blank.
128
+ *
129
+ * Exposed as an input so it can be localised (`'N/A'`, `'Sin dato'`, …). The
130
+ * default is the system-wide placeholder.
131
+ * @default '--'
132
+ */
133
+ emptyPlaceholder: _angular_core.InputSignal<string>;
134
+ private readonly breakpointObserver;
135
+ /**
136
+ * Reactive viewport state across every breakpoint the design system defines.
137
+ *
138
+ * Grid mode sizes off the viewport rather than the component's own width, matching
139
+ * `tk-grid-container`. A consequence worth knowing: a data list inside a narrow
140
+ * drawer still reports the viewport's column count.
141
+ */
142
+ readonly screenChanges: _angular_core.Signal<_angular_cdk_layout.BreakpointState | undefined>;
143
+ /** Whether the grid layout is active. */
144
+ readonly isGrid: _angular_core.Signal<boolean>;
145
+ /**
146
+ * The number of grid columns for the current viewport.
147
+ *
148
+ * Mobile 1 · Tablet 2 · Small 2 · Normal 3 · Extra large 5. Falls back to a single
149
+ * column when the observer has not reported yet, so the first paint is mobile-first
150
+ * rather than over-wide.
151
+ */
152
+ readonly gridColumnCount: _angular_core.Signal<1 | 5 | 3 | 2>;
153
+ /**
154
+ * The `grid-template-columns` value bound on the `<dl>`, or `null` in list layout
155
+ * so the property is left off the element entirely.
156
+ *
157
+ * The track count is capped at the number of items: a data list usually holds fewer
158
+ * pairs than the viewport allows columns, and emitting the full count would leave
159
+ * empty tracks that squeeze every value into a fraction of the available width
160
+ * (e.g. three items over five tracks). Never drops below one, so an empty list still
161
+ * produces a valid value.
162
+ */
163
+ readonly gridTemplateColumns: _angular_core.Signal<string | null>;
164
+ /**
165
+ * Whether a value should render as the `--` placeholder.
166
+ *
167
+ * Applied before the type is considered, so the rule holds uniformly across every
168
+ * type. `0` and `false`-ish numbers are real values and are not treated as empty.
169
+ */
170
+ isEmpty(value: TkDataListItem['value']): boolean;
171
+ /**
172
+ * The effective value type, defaulting to `'text'`.
173
+ *
174
+ * A `'link'` with no `handler` has nothing to invoke, so it resolves to `'text'`: it
175
+ * renders as plain data instead of as a button that does nothing. That keeps a dead
176
+ * control out of the accessibility tree and the tab order entirely, which `disabled`
177
+ * would not do — and `disabled` would also claim the action merely is unavailable
178
+ * right now, which is not the case.
179
+ */
180
+ resolveType(item: TkDataListItem): TkDataListValueType;
181
+ /** The effective tag severity, defaulting to the one used in the design. */
182
+ resolveTagSeverity(item: TkDataListItem): TagSeverity;
183
+ /**
184
+ * The value as a string, for the child components that require one
185
+ * (`tk-tag.value` and `tk-button.label` are both typed `string`).
186
+ */
187
+ asText(value: TkDataListItem['value']): string;
188
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DataListComponent, never>;
189
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DataListComponent, "tk-data-list", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "emptyPlaceholder": { "alias": "emptyPlaceholder"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
190
+ }
191
+
192
+ export { DATA_LIST_EMPTY_PLACEHOLDER, DataListComponent };
193
+ export type { TkDataListItem, TkDataListLayout, TkDataListValueType };
@@ -0,0 +1,161 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { Signal, InjectionToken } from '@angular/core';
3
+ import { TagSeverity } from '@tekus/design-system/components/tag';
4
+
5
+ /**
6
+ * The feedback intent of a stat. It drives the color of the value — and only the
7
+ * value: the surface, the label, the description and the progress bar are
8
+ * identical across every severity.
9
+ */
10
+ type StatSeverity = 'secondary' | 'primary' | 'success' | 'warn' | 'danger';
11
+ /**
12
+ * How a `tk-stat-group` lays its children out.
13
+ * - `auto`: a row that collapses to a column on small screens.
14
+ * - `row`: always horizontal.
15
+ * - `column`: always vertical.
16
+ */
17
+ type StatGroupOrientation = 'auto' | 'row' | 'column';
18
+ /** The contract a `tk-stat-group` exposes to the `tk-stat` children it projects. */
19
+ interface StatGroupContext {
20
+ readonly orientation: Signal<StatGroupOrientation>;
21
+ }
22
+ /**
23
+ * Provided by `tk-stat-group`, injected optionally by `tk-stat`.
24
+ *
25
+ * This is how a stat discovers that it lives inside a group and must drop its own
26
+ * surface — the group owns the background, so the children must not paint their
27
+ * own. A stat never sets its grouped appearance itself, and a consumer never has
28
+ * to hand it down.
29
+ */
30
+ declare const TK_STAT_GROUP: InjectionToken<StatGroupContext>;
31
+
32
+ /**
33
+ * @component StatComponent
34
+ * @description
35
+ * Displays a single highlighted metric: a large dominant value, a small label above
36
+ * it, and optionally a status tag, a description and a progress bar. Used at the top
37
+ * of listings, summaries and dashboards to surface a key figure at a glance.
38
+ *
39
+ * This component supports:
40
+ * - `label` / `value`: the two required parts. The value is the dominant figure.
41
+ * - `severity`: the feedback intent. Colors the value — and nothing else.
42
+ * - `prefix` / `suffix`: small muted affixes flanking the value (e.g. `'$'`, `'/200'`).
43
+ * - `description`: a secondary line below the value.
44
+ * - `tagValue` / `tagSeverity`: a trailing tag in the header, via `tk-tag`.
45
+ * - `progress`: shows a `tk-progress-bar` below the content when set.
46
+ * - `severityLabel`: screen-reader-only text so `severity` is not color-only.
47
+ *
48
+ * There is no `grouped` input: wrapping stats in `tk-stat-group` applies the grouped
49
+ * appearance automatically.
50
+ *
51
+ * @usage
52
+ * ### Basic Usage
53
+ * ```html
54
+ * <tk-stat label="Usuarios mensuales" [value]="500" description="Audiencia estimada" />
55
+ * <tk-stat label="Slots" [value]="25" suffix="/200" [progress]="12" tagValue="Disponible" tagSeverity="success" />
56
+ * ```
57
+ */
58
+ declare class StatComponent {
59
+ /** The small title shown above the value. */
60
+ label: _angular_core.InputSignal<string>;
61
+ /**
62
+ * The dominant figure. Accepts a number or an already-formatted string
63
+ * (`'1.2K'`, `'99,9 %'`) so the consumer keeps control of formatting and locale.
64
+ */
65
+ value: _angular_core.InputSignal<string | number>;
66
+ /**
67
+ * The feedback intent of the stat, which colors the value.
68
+ * @default 'secondary'
69
+ */
70
+ severity: _angular_core.InputSignal<StatSeverity>;
71
+ /** Small muted text rendered immediately before the value (e.g. `'$'`). */
72
+ prefix: _angular_core.InputSignal<string>;
73
+ /** Small muted text rendered immediately after the value (e.g. `'/200'`, `'/GB'`). */
74
+ suffix: _angular_core.InputSignal<string>;
75
+ /** A secondary line below the value, giving context or the origin of the figure. */
76
+ description: _angular_core.InputSignal<string>;
77
+ /** The text of the trailing header tag. The tag is only rendered when this is set. */
78
+ tagValue: _angular_core.InputSignal<string>;
79
+ /**
80
+ * The severity of the trailing header tag.
81
+ * @default 'secondary'
82
+ */
83
+ tagSeverity: _angular_core.InputSignal<TagSeverity>;
84
+ /**
85
+ * The completion percentage (0-100) shown as a progress bar below the content.
86
+ * Leave `undefined` to omit the bar — `0` is a valid value and renders an empty bar.
87
+ */
88
+ progress: _angular_core.InputSignal<number | undefined>;
89
+ /**
90
+ * Screen-reader-only text describing what this stat's `severity` means (e.g.
91
+ * `'sobre la cuota'`). Set it whenever `severity` is not `'secondary'`, so the
92
+ * meaning is not carried by color alone. Saying it visibly through `tagValue` or
93
+ * `description` is usually better, since it serves sighted users too.
94
+ */
95
+ severityLabel: _angular_core.InputSignal<string>;
96
+ /**
97
+ * The group this stat is projected into, if any. Injected optionally: a standalone
98
+ * stat resolves `null` and keeps its own surface.
99
+ */
100
+ private readonly group;
101
+ /** Whether this stat is projected inside a `tk-stat-group`. */
102
+ readonly grouped: boolean;
103
+ /** Unique DOM id linking the rendered label to the host's `aria-labelledby`. */
104
+ readonly labelId: string;
105
+ /**
106
+ * Whether a progress bar should be rendered. Tests for `null`/`undefined` rather
107
+ * than truthiness so that `progress = 0` still renders an empty bar.
108
+ */
109
+ hasProgress: _angular_core.Signal<boolean>;
110
+ /** Computed host class: the BEM block plus the severity and grouped modifiers. */
111
+ hostClass: _angular_core.Signal<string>;
112
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatComponent, never>;
113
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatComponent, "tk-stat", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "severity": { "alias": "severity"; "required": false; "isSignal": true; }; "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; "suffix": { "alias": "suffix"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "tagValue": { "alias": "tagValue"; "required": false; "isSignal": true; }; "tagSeverity": { "alias": "tagSeverity"; "required": false; "isSignal": true; }; "progress": { "alias": "progress"; "required": false; "isSignal": true; }; "severityLabel": { "alias": "severityLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
114
+ }
115
+
116
+ /**
117
+ * @component StatGroupComponent
118
+ * @description
119
+ * Wraps a row of related `tk-stat` into a single shared surface, inserting a
120
+ * separator between each pair. The group owns the background and the radius; every
121
+ * stat projected into it drops its own surface automatically, through the
122
+ * {@link TK_STAT_GROUP} token this component provides.
123
+ *
124
+ * This component supports:
125
+ * - `orientation`: `'auto'` (a row that collapses to a column on small screens),
126
+ * `'row'` or `'column'`.
127
+ * - `ariaLabel`: names the group for assistive tech. Only when set does the group
128
+ * expose `role="group"` — an unnamed group is noise.
129
+ *
130
+ * @usage
131
+ * ### Basic Usage
132
+ * ```html
133
+ * <tk-stat-group ariaLabel="Resumen de la campaña">
134
+ * <tk-stat label="Usuarios mensuales" [value]="500" description="Audiencia estimada" />
135
+ * <tk-stat label="Estado de la pantalla" [value]="500" severity="success" description="12 de 12 días" />
136
+ * <tk-stat label="Slots" [value]="25" suffix="/200" [progress]="12" />
137
+ * </tk-stat-group>
138
+ * ```
139
+ */
140
+ declare class StatGroupComponent implements StatGroupContext {
141
+ /**
142
+ * How the stats are laid out.
143
+ * - `'auto'`: horizontal, collapsing to a column on viewports narrower than a
144
+ * vertical tablet. This is the responsive default the design calls for.
145
+ * - `'row'` / `'column'`: force one direction — for a group inside a narrow
146
+ * sidebar on a wide screen, or one that must stay horizontal on mobile.
147
+ * @default 'auto'
148
+ */
149
+ orientation: _angular_core.InputSignal<StatGroupOrientation>;
150
+ /**
151
+ * The accessible name of the group. When set, the group exposes itself as
152
+ * `role="group"` with this label; when omitted it stays structurally invisible to
153
+ * assistive tech, since an unnamed group adds nothing but noise.
154
+ */
155
+ ariaLabel: _angular_core.InputSignal<string>;
156
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatGroupComponent, never>;
157
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatGroupComponent, "tk-stat-group", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
158
+ }
159
+
160
+ export { StatComponent, StatGroupComponent, TK_STAT_GROUP };
161
+ export type { StatGroupContext, StatGroupOrientation, StatSeverity };