@lifecorner/angular-ui-kit 3.22.0 → 3.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -150,7 +150,7 @@ var StatTrendComponent = class StatTrendComponent {
|
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
ngImport: i0,
|
|
153
|
-
template: "<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n
|
|
153
|
+
template: "<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n <!-- Request 031 (WCAG 2.5.3): a space before the row, the pill and the sparkline keeps the\n text \"Label Value +3,1 %\"; in a flex container it gets no box and renders nothing. -->\n &ngsp;\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n &ngsp;<span class=\"lc-stat-trend__change\" [class]=\"trendColorClass()\">\n <span class=\"lc-stat-trend__icon\" [attr.aria-hidden]=\"sentiment() ? 'true' : null\">{{ trendIcon() }}</span>\n <!-- The hidden text follows the change without a space (\"+3,1 %, gestiegen\", review r1 N2);\n without a sentiment the text node stays \" +3.1% \" as before 3.14. -->\n @if (sentiment()) {\n {{ change() }}<span class=\"lc-stat-trend__sr-only\">{{ srText() }}</span>\n } @else {\n {{ change() }}\n }\n </span>\n }\n </div>\n\n @if (hasSparkline()) {\n &ngsp;<lc-sparkline\n [data]=\"sparklineData()\"\n [color]=\"resolvedSparklineColor()\"\n [width]=\"120\"\n [height]=\"28\"\n [filled]=\"true\"\n [strokeWidth]=\"1.5\"\n ></lc-sparkline>\n }\n</div>\n",
|
|
154
154
|
styles: [".lc-stat-trend{display:inline-flex;flex-direction:column;gap:.25rem}.lc-stat-trend__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.lc-stat-trend__header-icon{color:var(--color-text-tertiary);flex-shrink:0}.lc-stat-trend__label{font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);font-weight:500;text-transform:uppercase;letter-spacing:.05em}.lc-stat-trend__row{display:flex;align-items:baseline;gap:.5rem}.lc-stat-trend__value{font-size:var(--typography-font-size-2xl, 1.5rem);font-weight:700;color:var(--color-text-primary);line-height:1.2}.lc-stat-trend__change{display:inline-flex;align-items:center;gap:.125rem;font-size:var(--typography-font-size-sm, .875rem);font-weight:600;border-radius:var(--border-radius-full);padding:.0625rem .5rem}.lc-stat-trend__change--up,.lc-stat-trend__change--positive{color:var(--color-text-success);background-color:var(--color-success-subtle)}.lc-stat-trend__change--down,.lc-stat-trend__change--negative{color:var(--color-text-error);background-color:var(--color-error-subtle)}.lc-stat-trend__change--neutral{color:var(--color-text-secondary);background-color:var(--color-surface-hover)}.lc-stat-trend__icon{font-size:.75em}.lc-stat-trend__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"],
|
|
155
155
|
dependencies: [{
|
|
156
156
|
kind: "component",
|
|
@@ -198,7 +198,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
198
198
|
standalone: true,
|
|
199
199
|
imports: [SparklineComponent, IconComponent],
|
|
200
200
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
201
|
-
template: "<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n
|
|
201
|
+
template: "<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n <!-- Request 031 (WCAG 2.5.3): a space before the row, the pill and the sparkline keeps the\n text \"Label Value +3,1 %\"; in a flex container it gets no box and renders nothing. -->\n &ngsp;\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n &ngsp;<span class=\"lc-stat-trend__change\" [class]=\"trendColorClass()\">\n <span class=\"lc-stat-trend__icon\" [attr.aria-hidden]=\"sentiment() ? 'true' : null\">{{ trendIcon() }}</span>\n <!-- The hidden text follows the change without a space (\"+3,1 %, gestiegen\", review r1 N2);\n without a sentiment the text node stays \" +3.1% \" as before 3.14. -->\n @if (sentiment()) {\n {{ change() }}<span class=\"lc-stat-trend__sr-only\">{{ srText() }}</span>\n } @else {\n {{ change() }}\n }\n </span>\n }\n </div>\n\n @if (hasSparkline()) {\n &ngsp;<lc-sparkline\n [data]=\"sparklineData()\"\n [color]=\"resolvedSparklineColor()\"\n [width]=\"120\"\n [height]=\"28\"\n [filled]=\"true\"\n [strokeWidth]=\"1.5\"\n ></lc-sparkline>\n }\n</div>\n",
|
|
202
202
|
styles: [".lc-stat-trend{display:inline-flex;flex-direction:column;gap:.25rem}.lc-stat-trend__header{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.lc-stat-trend__header-icon{color:var(--color-text-tertiary);flex-shrink:0}.lc-stat-trend__label{font-size:var(--typography-font-size-xs, .75rem);color:var(--color-text-tertiary);font-weight:500;text-transform:uppercase;letter-spacing:.05em}.lc-stat-trend__row{display:flex;align-items:baseline;gap:.5rem}.lc-stat-trend__value{font-size:var(--typography-font-size-2xl, 1.5rem);font-weight:700;color:var(--color-text-primary);line-height:1.2}.lc-stat-trend__change{display:inline-flex;align-items:center;gap:.125rem;font-size:var(--typography-font-size-sm, .875rem);font-weight:600;border-radius:var(--border-radius-full);padding:.0625rem .5rem}.lc-stat-trend__change--up,.lc-stat-trend__change--positive{color:var(--color-text-success);background-color:var(--color-success-subtle)}.lc-stat-trend__change--down,.lc-stat-trend__change--negative{color:var(--color-text-error);background-color:var(--color-error-subtle)}.lc-stat-trend__change--neutral{color:var(--color-text-secondary);background-color:var(--color-surface-hover)}.lc-stat-trend__icon{font-size:.75em}.lc-stat-trend__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"]
|
|
203
203
|
}]
|
|
204
204
|
}],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-stat-trend.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stat-trend/stat-trend.component.ts","../../../../libs/angular-ui-kit/src/lib/stat-trend/stat-trend.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { SparklineComponent, SparklineColor } from '@lifecorner/angular-ui-kit/sparkline';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\nexport type StatTrendDirection = 'up' | 'down' | 'neutral';\n\n/** Whether a change is good or bad for the reader, independent of its direction. */\nexport type StatTrendSentiment = 'positive' | 'negative' | 'neutral';\n\n/**\n * Words of the screen-reader text that `lc-stat-trend` adds to the change when\n * `sentiment` is set (\", up, unfavourable\"). Override single keys via `labels`.\n */\nexport interface StatTrendLabels {\n /** Direction up. EN \"up\", DE \"gestiegen\". */\n up: string;\n /** Direction down. EN \"down\", DE \"gesunken\". */\n down: string;\n /** Direction neutral. EN \"unchanged\", DE \"unverändert\". */\n unchanged: string;\n /** Sentiment positive. EN \"favourable\", DE \"günstig\". */\n positive: string;\n /** Sentiment negative. EN \"unfavourable\", DE \"ungünstig\". */\n negative: string;\n /** Sentiment neutral. EN \"neutral\", DE \"neutral\". */\n neutral: string;\n}\n\nconst DEFAULT_LABELS: StatTrendLabels = {\n up: 'up',\n down: 'down',\n unchanged: 'unchanged',\n positive: 'favourable',\n negative: 'unfavourable',\n neutral: 'neutral',\n};\n\n@Component({\n selector: 'lc-stat-trend',\n standalone: true,\n imports: [SparklineComponent, IconComponent],\n templateUrl: './stat-trend.component.html',\n styleUrls: ['./stat-trend.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Stat trend component for displaying KPI values with change indicators.\n *\n * Features:\n * - Value display with label and optional icon\n * - Change percentage with auto-detected trend direction (up/down/neutral)\n * - Color-coded trend indicator (green for up, red for down), or by `sentiment`\n * when a rise is bad (costs) or a fall is good (churn)\n * - Optional embedded sparkline chart\n * - Configurable sparkline color theme\n *\n * With `sentiment` set, the arrow is hidden from screen readers and a visually\n * hidden text names the direction and the sentiment (\"+3.1 %, up, unfavourable\"),\n * so the meaning never rests on colour alone. Translate it with `labels`.\n *\n * @example\n * ```html\n * <lc-stat-trend label=\"Revenue\" value=\"$42,350\" change=\"+12.5%\" [sparklineData]=\"data\" />\n * <lc-stat-trend label=\"Costs\" value=\"€48.2m\" change=\"+3.1%\" sentiment=\"negative\" />\n * ```\n */\nexport class StatTrendComponent {\n /** Main KPI label. */\n label = input<string>('');\n\n /** Main KPI value to display. */\n value = input.required<string>();\n\n /** Change value text, e.g. \"+12%\" or \"-3.5%\". */\n change = input<string>('');\n\n /** Trend direction for color coding. Auto-detected from change if not set. */\n direction = input<StatTrendDirection | undefined>(undefined);\n\n /**\n * Whether the change is good or bad, independent of its direction (a rise in\n * costs is negative). Colours the change and the default sparkline; the arrow\n * keeps the direction. Unset: derived from the direction.\n * @default undefined\n */\n sentiment = input<StatTrendSentiment | undefined>(undefined);\n\n /**\n * Words of the screen-reader text added to the change when `sentiment` is set.\n * Missing keys fall back to the English defaults (up, down, unchanged,\n * favourable, unfavourable, neutral).\n * @default {}\n */\n labels = input<Partial<StatTrendLabels>>({});\n\n /** Sparkline data points. If empty, no sparkline is shown. */\n sparklineData = input<number[]>([]);\n\n /** Color of the sparkline. Auto-matched to trend direction if not set. */\n sparklineColor = input<SparklineColor | undefined>(undefined);\n\n /** Optional icon name (Tabler icon). */\n icon = input<string>('');\n\n protected readonly resolvedDirection = computed<StatTrendDirection>(() => {\n const dir = this.direction();\n if (dir) return dir;\n const c = this.change();\n if (c.startsWith('+') || c.startsWith('↑')) return 'up';\n if (c.startsWith('-') || c.startsWith('↓')) return 'down';\n return 'neutral';\n });\n\n protected readonly trendColorClass = computed(() => {\n const sentiment = this.sentiment();\n if (sentiment) return `lc-stat-trend__change--${sentiment}`;\n const map: Record<StatTrendDirection, string> = {\n up: 'lc-stat-trend__change--up',\n down: 'lc-stat-trend__change--down',\n neutral: 'lc-stat-trend__change--neutral',\n };\n return map[this.resolvedDirection()];\n });\n\n protected readonly resolvedSparklineColor = computed<SparklineColor>(() => {\n const c = this.sparklineColor();\n if (c) return c;\n const sentiment = this.sentiment();\n if (sentiment) {\n const bySentiment: Record<StatTrendSentiment, SparklineColor> = {\n positive: 'success',\n negative: 'error',\n neutral: 'info',\n };\n return bySentiment[sentiment];\n }\n const map: Record<StatTrendDirection, SparklineColor> = {\n up: 'success',\n down: 'error',\n neutral: 'info',\n };\n return map[this.resolvedDirection()];\n });\n\n protected readonly trendIcon = computed(() => {\n const map: Record<StatTrendDirection, string> = {\n up: '↑',\n down: '↓',\n neutral: '→',\n };\n return map[this.resolvedDirection()];\n });\n\n /** \", up, unfavourable\": rendered only when `sentiment` is set. */\n protected readonly srText = computed(() => {\n const sentiment = this.sentiment();\n if (!sentiment) return '';\n const text: StatTrendLabels = { ...DEFAULT_LABELS, ...this.labels() };\n const dir = this.resolvedDirection();\n const direction = dir === 'neutral' ? text.unchanged : text[dir];\n return `, ${direction}, ${text[sentiment]}`;\n });\n\n protected readonly hasSparkline = computed(() => this.sparklineData().length >= 2);\n}\n","<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n <span class=\"lc-stat-trend__change\" [class]=\"trendColorClass()\">\n <span class=\"lc-stat-trend__icon\" [attr.aria-hidden]=\"sentiment() ? 'true' : null\">{{ trendIcon() }}</span>\n <!-- The hidden text follows the change without a space (\"+3,1 %, gestiegen\", review r1 N2);\n without a sentiment the text node stays \" +3.1% \" as before 3.14. -->\n @if (sentiment()) {\n {{ change() }}<span class=\"lc-stat-trend__sr-only\">{{ srText() }}</span>\n } @else {\n {{ change() }}\n }\n </span>\n }\n </div>\n\n @if (hasSparkline()) {\n <lc-sparkline\n [data]=\"sparklineData()\"\n [color]=\"resolvedSparklineColor()\"\n [width]=\"120\"\n [height]=\"28\"\n [filled]=\"true\"\n [strokeWidth]=\"1.5\"\n ></lc-sparkline>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-stat-trend.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stat-trend/stat-trend.component.ts","../../../../libs/angular-ui-kit/src/lib/stat-trend/stat-trend.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { SparklineComponent, SparklineColor } from '@lifecorner/angular-ui-kit/sparkline';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\nexport type StatTrendDirection = 'up' | 'down' | 'neutral';\n\n/** Whether a change is good or bad for the reader, independent of its direction. */\nexport type StatTrendSentiment = 'positive' | 'negative' | 'neutral';\n\n/**\n * Words of the screen-reader text that `lc-stat-trend` adds to the change when\n * `sentiment` is set (\", up, unfavourable\"). Override single keys via `labels`.\n */\nexport interface StatTrendLabels {\n /** Direction up. EN \"up\", DE \"gestiegen\". */\n up: string;\n /** Direction down. EN \"down\", DE \"gesunken\". */\n down: string;\n /** Direction neutral. EN \"unchanged\", DE \"unverändert\". */\n unchanged: string;\n /** Sentiment positive. EN \"favourable\", DE \"günstig\". */\n positive: string;\n /** Sentiment negative. EN \"unfavourable\", DE \"ungünstig\". */\n negative: string;\n /** Sentiment neutral. EN \"neutral\", DE \"neutral\". */\n neutral: string;\n}\n\nconst DEFAULT_LABELS: StatTrendLabels = {\n up: 'up',\n down: 'down',\n unchanged: 'unchanged',\n positive: 'favourable',\n negative: 'unfavourable',\n neutral: 'neutral',\n};\n\n@Component({\n selector: 'lc-stat-trend',\n standalone: true,\n imports: [SparklineComponent, IconComponent],\n templateUrl: './stat-trend.component.html',\n styleUrls: ['./stat-trend.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Stat trend component for displaying KPI values with change indicators.\n *\n * Features:\n * - Value display with label and optional icon\n * - Change percentage with auto-detected trend direction (up/down/neutral)\n * - Color-coded trend indicator (green for up, red for down), or by `sentiment`\n * when a rise is bad (costs) or a fall is good (churn)\n * - Optional embedded sparkline chart\n * - Configurable sparkline color theme\n *\n * With `sentiment` set, the arrow is hidden from screen readers and a visually\n * hidden text names the direction and the sentiment (\"+3.1 %, up, unfavourable\"),\n * so the meaning never rests on colour alone. Translate it with `labels`.\n *\n * @example\n * ```html\n * <lc-stat-trend label=\"Revenue\" value=\"$42,350\" change=\"+12.5%\" [sparklineData]=\"data\" />\n * <lc-stat-trend label=\"Costs\" value=\"€48.2m\" change=\"+3.1%\" sentiment=\"negative\" />\n * ```\n */\nexport class StatTrendComponent {\n /** Main KPI label. */\n label = input<string>('');\n\n /** Main KPI value to display. */\n value = input.required<string>();\n\n /** Change value text, e.g. \"+12%\" or \"-3.5%\". */\n change = input<string>('');\n\n /** Trend direction for color coding. Auto-detected from change if not set. */\n direction = input<StatTrendDirection | undefined>(undefined);\n\n /**\n * Whether the change is good or bad, independent of its direction (a rise in\n * costs is negative). Colours the change and the default sparkline; the arrow\n * keeps the direction. Unset: derived from the direction.\n * @default undefined\n */\n sentiment = input<StatTrendSentiment | undefined>(undefined);\n\n /**\n * Words of the screen-reader text added to the change when `sentiment` is set.\n * Missing keys fall back to the English defaults (up, down, unchanged,\n * favourable, unfavourable, neutral).\n * @default {}\n */\n labels = input<Partial<StatTrendLabels>>({});\n\n /** Sparkline data points. If empty, no sparkline is shown. */\n sparklineData = input<number[]>([]);\n\n /** Color of the sparkline. Auto-matched to trend direction if not set. */\n sparklineColor = input<SparklineColor | undefined>(undefined);\n\n /** Optional icon name (Tabler icon). */\n icon = input<string>('');\n\n protected readonly resolvedDirection = computed<StatTrendDirection>(() => {\n const dir = this.direction();\n if (dir) return dir;\n const c = this.change();\n if (c.startsWith('+') || c.startsWith('↑')) return 'up';\n if (c.startsWith('-') || c.startsWith('↓')) return 'down';\n return 'neutral';\n });\n\n protected readonly trendColorClass = computed(() => {\n const sentiment = this.sentiment();\n if (sentiment) return `lc-stat-trend__change--${sentiment}`;\n const map: Record<StatTrendDirection, string> = {\n up: 'lc-stat-trend__change--up',\n down: 'lc-stat-trend__change--down',\n neutral: 'lc-stat-trend__change--neutral',\n };\n return map[this.resolvedDirection()];\n });\n\n protected readonly resolvedSparklineColor = computed<SparklineColor>(() => {\n const c = this.sparklineColor();\n if (c) return c;\n const sentiment = this.sentiment();\n if (sentiment) {\n const bySentiment: Record<StatTrendSentiment, SparklineColor> = {\n positive: 'success',\n negative: 'error',\n neutral: 'info',\n };\n return bySentiment[sentiment];\n }\n const map: Record<StatTrendDirection, SparklineColor> = {\n up: 'success',\n down: 'error',\n neutral: 'info',\n };\n return map[this.resolvedDirection()];\n });\n\n protected readonly trendIcon = computed(() => {\n const map: Record<StatTrendDirection, string> = {\n up: '↑',\n down: '↓',\n neutral: '→',\n };\n return map[this.resolvedDirection()];\n });\n\n /** \", up, unfavourable\": rendered only when `sentiment` is set. */\n protected readonly srText = computed(() => {\n const sentiment = this.sentiment();\n if (!sentiment) return '';\n const text: StatTrendLabels = { ...DEFAULT_LABELS, ...this.labels() };\n const dir = this.resolvedDirection();\n const direction = dir === 'neutral' ? text.unchanged : text[dir];\n return `, ${direction}, ${text[sentiment]}`;\n });\n\n protected readonly hasSparkline = computed(() => this.sparklineData().length >= 2);\n}\n","<div class=\"lc-stat-trend\">\n <div class=\"lc-stat-trend__header\">\n @if (label()) {\n <span class=\"lc-stat-trend__label\">{{ label() }}</span>\n }\n @if (icon()) {\n <lc-icon [name]=\"icon()\" size=\"sm\" class=\"lc-stat-trend__header-icon\" [decorative]=\"true\" />\n }\n </div>\n <!-- Request 031 (WCAG 2.5.3): a space before the row, the pill and the sparkline keeps the\n text \"Label Value +3,1 %\"; in a flex container it gets no box and renders nothing. -->\n &ngsp;\n <div class=\"lc-stat-trend__row\">\n <span class=\"lc-stat-trend__value\">{{ value() }}</span>\n\n @if (change()) {\n &ngsp;<span class=\"lc-stat-trend__change\" [class]=\"trendColorClass()\">\n <span class=\"lc-stat-trend__icon\" [attr.aria-hidden]=\"sentiment() ? 'true' : null\">{{ trendIcon() }}</span>\n <!-- The hidden text follows the change without a space (\"+3,1 %, gestiegen\", review r1 N2);\n without a sentiment the text node stays \" +3.1% \" as before 3.14. -->\n @if (sentiment()) {\n {{ change() }}<span class=\"lc-stat-trend__sr-only\">{{ srText() }}</span>\n } @else {\n {{ change() }}\n }\n </span>\n }\n </div>\n\n @if (hasSparkline()) {\n &ngsp;<lc-sparkline\n [data]=\"sparklineData()\"\n [color]=\"resolvedSparklineColor()\"\n [width]=\"120\"\n [height]=\"28\"\n [filled]=\"true\"\n [strokeWidth]=\"1.5\"\n ></lc-sparkline>\n }\n</div>\n"],"mappings":"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"}
|