@lifecorner/angular-ui-kit 3.23.0 → 3.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs +124 -21
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +6 -6
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +70 -5
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-log-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-notification-center.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-progress-ring.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_theme-dark.scss +4 -0
- package/src/styles/_theme-light.scss +4 -0
- package/src/styles/theme-contrast.spec.ts +291 -8
- package/types/lifecorner-angular-ui-kit-card.d.ts +73 -5
- package/types/lifecorner-angular-ui-kit-file-upload.d.ts +48 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-progress-ring.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/progress-ring/progress-ring.component.ts","../../../../libs/angular-ui-kit/src/lib/progress-ring/progress-ring.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\n\nexport type ProgressRingColor = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';\nexport type ProgressRingSize = 'xs' | 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'lc-progress-ring',\n standalone: true,\n templateUrl: './progress-ring.component.html',\n styleUrls: ['./progress-ring.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Progress ring component for circular progress indication.\n *\n * Features:\n * - Circular SVG progress arc (0–100%)\n * - Color theme variants (primary, secondary, success, warning, error)\n * - Size presets (xs, sm, md, lg)\n * - Optional percentage value display\n * - Animated stroke transitions\n * - Accessible `progressbar` role with `aria-valuenow` and a configurable label\n *\n * @example\n * ```html\n * <lc-progress-ring [value]=\"65\" color=\"success\" [showValue]=\"true\" />\n * ```\n */\nexport class ProgressRingComponent {\n /** Value between 0 and 100. */\n value = input<number>(0);\n\n /** Color theme. */\n color = input<ProgressRingColor>('primary');\n\n /** Size preset. */\n size = input<ProgressRingSize>('md');\n\n /** Show percentage text in center. */\n showValue = input<boolean>(true);\n\n /**\n * Alias of `showValue`, named like `lc-progress-bar`'s `showLabel`. When\n * set it wins over `showValue`.\n */\n showLabel = input<boolean | undefined>(undefined);\n\n /** Accessible name of the progress ring. */\n ariaLabel = input<string>('Progress');\n\n private readonly SIZE_MAP: Record<ProgressRingSize, number> = { xs: 32, sm: 48, md: 64, lg: 96 };\n private readonly STROKE_MAP: Record<ProgressRingSize, number> = { xs: 3, sm: 4, md: 5, lg: 7 };\n\n protected readonly svgSize = computed(() => this.SIZE_MAP[this.size()]);\n protected readonly strokeW = computed(() => this.STROKE_MAP[this.size()]);\n protected readonly viewBox = computed(() => `0 0 ${this.svgSize()} ${this.svgSize()}`);\n protected readonly center = computed(() => this.svgSize() / 2);\n protected readonly radius = computed(() => (this.svgSize() - this.strokeW() * 2) / 2);\n protected readonly circumference = computed(() => 2 * Math.PI * this.radius());\n\n /** `value` clamped to the 0–100 range (drives arc, text and ARIA). */\n protected readonly clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100));\n\n protected readonly shouldShowValue = computed(() => this.showLabel() ?? this.showValue());\n\n protected readonly dashOffset = computed(() => {\n const c = this.circumference();\n return c - (this.clampedValue() / 100) * c;\n });\n\n protected readonly fontSize = computed(() => {\n const map: Record<ProgressRingSize, string> = { xs: '0.5rem', sm: '0.625rem', md: '0.875rem', lg: '1.25rem' };\n return map[this.size()];\n });\n\n protected readonly displayValue = computed(() => `${Math.round(this.clampedValue())}%`);\n}\n","<svg\n [attr.width]=\"svgSize()\" [attr.height]=\"svgSize()\" [attr.viewBox]=\"viewBox()\"\n class=\"lc-progress-ring\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <circle\n [attr.cx]=\"center()\" [attr.cy]=\"center()\" [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeW()\"\n fill=\"none\" class=\"lc-progress-ring__track\"\n />\n <circle\n [attr.cx]=\"center()\" [attr.cy]=\"center()\" [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeW()\"\n [attr.stroke-dasharray]=\"circumference()\" [attr.stroke-dashoffset]=\"dashOffset()\"\n fill=\"none\" stroke-linecap=\"round\"\n [class]=\"'lc-progress-ring__value lc-progress-ring__value--' + color()\"\n [attr.transform]=\"'rotate(-90 ' + center() + ' ' + center() + ')'\"\n />\n @if (shouldShowValue()) {\n <text [attr.x]=\"center()\" [attr.y]=\"center()\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-progress-ring__text\" [style.font-size]=\"fontSize()\">\n {{ displayValue() }}\n </text>\n }\n</svg>\n"],"mappings":";;AAiCA,IAAa,wBAAb,MAAa,sBAAqB;CAEhC,QAAQ,MAAc,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGxB,QAAQ,MAAyB,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,OAAO,MAAwB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAGpC,YAAY,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAMhC,YAAY,MAA2B,QAAU,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAGjD,YAAY,MAAc,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAEpB,WAA6C;EAAE,IAAI;EAAI,IAAI;EAAI,IAAI;EAAI,IAAI;CAAE;CAC7E,aAA+C;EAAE,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;CAAC;CAEzE,UAAU,eAAe,KAAK,SAAS,KAAK,KAAI,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACpD,UAAU,eAAe,KAAK,WAAW,KAAK,KAAI,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACtD,UAAU,eAAe,OAAO,KAAK,QAAO,EAAE,GAAI,KAAK,QAAO,KAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CACnE,SAAS,eAAe,KAAK,QAAO,IAAK,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAC3C,SAAS,gBAAgB,KAAK,QAAO,IAAK,KAAK,QAAO,IAAK,KAAK,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAClE,gBAAgB,eAAe,IAAI,KAAK,KAAK,KAAK,OAAM,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3D,eAAe,eAAe,KAAK,IAAI,KAAK,IAAI,KAAK,MAAK,GAAI,CAAC,GAAG,GAAG,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEvE,kBAAkB,eAAe,KAAK,UAAS,KAAM,KAAK,UAAS,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAEtE,aAAa,eAAc;EAC5C,MAAM,IAAI,KAAK,cAAa;EAC5B,OAAO,IAAK,KAAK,aAAY,IAAK,MAAO;CAC3C,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,WAAW,eAAc;EAE1C,OAAO;GADyC,IAAI;GAAU,IAAI;GAAY,IAAI;GAAY,IAAI;EAC3F,EAAI,KAAK,KAAI;CACtB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,eAAe,eAAe,GAAG,KAAK,MAAM,KAAK,aAAY,CAAE,EAAC,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CA/C5E,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAqB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAArB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAqB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UCjClC;EA4BA,QAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-progress-ring.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/progress-ring/progress-ring.component.ts","../../../../libs/angular-ui-kit/src/lib/progress-ring/progress-ring.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\n\nexport type ProgressRingColor = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';\nexport type ProgressRingSize = 'xs' | 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'lc-progress-ring',\n standalone: true,\n templateUrl: './progress-ring.component.html',\n styleUrls: ['./progress-ring.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Progress ring component for circular progress indication.\n *\n * Features:\n * - Circular SVG progress arc (0–100%)\n * - Color theme variants (primary, secondary, success, warning, error)\n * - Size presets (xs, sm, md, lg)\n * - Optional percentage value display\n * - Animated stroke transitions\n * - Accessible `progressbar` role with `aria-valuenow` and a configurable label\n *\n * @example\n * ```html\n * <lc-progress-ring [value]=\"65\" color=\"success\" [showValue]=\"true\" />\n * ```\n */\nexport class ProgressRingComponent {\n /** Value between 0 and 100. */\n value = input<number>(0);\n\n /** Color theme. */\n color = input<ProgressRingColor>('primary');\n\n /** Size preset. */\n size = input<ProgressRingSize>('md');\n\n /** Show percentage text in center. */\n showValue = input<boolean>(true);\n\n /**\n * Alias of `showValue`, named like `lc-progress-bar`'s `showLabel`. When\n * set it wins over `showValue`.\n */\n showLabel = input<boolean | undefined>(undefined);\n\n /** Accessible name of the progress ring. */\n ariaLabel = input<string>('Progress');\n\n private readonly SIZE_MAP: Record<ProgressRingSize, number> = { xs: 32, sm: 48, md: 64, lg: 96 };\n private readonly STROKE_MAP: Record<ProgressRingSize, number> = { xs: 3, sm: 4, md: 5, lg: 7 };\n\n protected readonly svgSize = computed(() => this.SIZE_MAP[this.size()]);\n protected readonly strokeW = computed(() => this.STROKE_MAP[this.size()]);\n protected readonly viewBox = computed(() => `0 0 ${this.svgSize()} ${this.svgSize()}`);\n protected readonly center = computed(() => this.svgSize() / 2);\n protected readonly radius = computed(() => (this.svgSize() - this.strokeW() * 2) / 2);\n protected readonly circumference = computed(() => 2 * Math.PI * this.radius());\n\n /** `value` clamped to the 0–100 range (drives arc, text and ARIA). */\n protected readonly clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100));\n\n protected readonly shouldShowValue = computed(() => this.showLabel() ?? this.showValue());\n\n protected readonly dashOffset = computed(() => {\n const c = this.circumference();\n return c - (this.clampedValue() / 100) * c;\n });\n\n protected readonly fontSize = computed(() => {\n const map: Record<ProgressRingSize, string> = { xs: '0.5rem', sm: '0.625rem', md: '0.875rem', lg: '1.25rem' };\n return map[this.size()];\n });\n\n protected readonly displayValue = computed(() => `${Math.round(this.clampedValue())}%`);\n}\n","<svg\n [attr.width]=\"svgSize()\" [attr.height]=\"svgSize()\" [attr.viewBox]=\"viewBox()\"\n class=\"lc-progress-ring\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <circle\n [attr.cx]=\"center()\" [attr.cy]=\"center()\" [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeW()\"\n fill=\"none\" class=\"lc-progress-ring__track\"\n />\n <circle\n [attr.cx]=\"center()\" [attr.cy]=\"center()\" [attr.r]=\"radius()\"\n [attr.stroke-width]=\"strokeW()\"\n [attr.stroke-dasharray]=\"circumference()\" [attr.stroke-dashoffset]=\"dashOffset()\"\n fill=\"none\" stroke-linecap=\"round\"\n [class]=\"'lc-progress-ring__value lc-progress-ring__value--' + color()\"\n [attr.transform]=\"'rotate(-90 ' + center() + ' ' + center() + ')'\"\n />\n @if (shouldShowValue()) {\n <text [attr.x]=\"center()\" [attr.y]=\"center()\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-progress-ring__text\" [style.font-size]=\"fontSize()\">\n {{ displayValue() }}\n </text>\n }\n</svg>\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
--color-secondary-700: #{$color-secondary-200};
|
|
52
52
|
--color-secondary-800: #{$color-secondary-100};
|
|
53
53
|
--color-secondary-900: #{$color-secondary-50};
|
|
54
|
+
// Secondary data marks (progress fill, ring arc), not a text colour: the raw
|
|
55
|
+
// 500 step, at least 3:1 against the track and the page and 1.92:1 apart from
|
|
56
|
+
// --color-info (request 036: the ring's inverted secondary-600 was 1.07:1).
|
|
57
|
+
--color-secondary-graphic: #{$color-secondary-500};
|
|
54
58
|
|
|
55
59
|
// ============================================================================
|
|
56
60
|
// Colors - Neutral (Inverted for dark mode)
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
--color-secondary-700: #{$color-secondary-700};
|
|
45
45
|
--color-secondary-800: #{$color-secondary-800};
|
|
46
46
|
--color-secondary-900: #{$color-secondary-900};
|
|
47
|
+
// Secondary data marks (progress fill, ring arc), not a text colour: at
|
|
48
|
+
// least 3:1 against the --color-surface-hover track and the page in both
|
|
49
|
+
// themes (request 036, F-11: the bar fill was secondary-500, 2.83:1).
|
|
50
|
+
--color-secondary-graphic: #{$color-secondary-600};
|
|
47
51
|
|
|
48
52
|
// ============================================================================
|
|
49
53
|
// Colors - Neutral (Grays)
|
|
@@ -743,11 +743,13 @@ describe('Component pairings of 3.21.1 (request 029)', () => {
|
|
|
743
743
|
);
|
|
744
744
|
});
|
|
745
745
|
|
|
746
|
-
it('AC-10: the progress track is unchanged; primary
|
|
746
|
+
it('AC-10: the progress track is unchanged; primary stays raw (secondary: request 036)', () => {
|
|
747
747
|
// The base rule (the forced-colours block repeats the selector with an outline).
|
|
748
748
|
expect(sheets.progress).toMatch(/\n\.progress-bar__track \{\s*flex: 1;\s*background-color: var\(--color-surface-hover\);/);
|
|
749
749
|
expect(fills('progress', '.progress-bar--primary .progress-bar__fill')['background-color']).toMatch(/^#[0-9a-f]{6}$/i);
|
|
750
|
-
|
|
750
|
+
// 036 (F-11): the raw secondary-500 was 2.83:1 against the light track;
|
|
751
|
+
// secondary now reads the data-mark token (4.19 light, 4.05 dark).
|
|
752
|
+
expect(fills('progress', '.progress-bar--secondary .progress-bar__fill')['background-color']).toBe('var(--color-secondary-graphic)');
|
|
751
753
|
});
|
|
752
754
|
|
|
753
755
|
it('AC-9: kanban priorities, dependency relations, arrows and node borders read the inks', () => {
|
|
@@ -967,7 +969,8 @@ describe('Component pairings of 3.22.4 (request 034)', () => {
|
|
|
967
969
|
['nc', '.lc-nc__item--unread .lc-nc__item-priority--low', { 'background-image': flat('--color-surface-hover') }],
|
|
968
970
|
// F-2: the ring arcs take the progress-bar inks.
|
|
969
971
|
['ring', '.lc-progress-ring__value--primary', { stroke: 'var(--color-primary-500)' }],
|
|
970
|
-
|
|
972
|
+
// 036: secondary reads the data-mark token (dark secondary-600 was 1.07:1 from info).
|
|
973
|
+
['ring', '.lc-progress-ring__value--secondary', { stroke: 'var(--color-secondary-graphic)' }],
|
|
971
974
|
...['success', 'warning', 'error', 'info'].map((v): Row => ['ring', `.lc-progress-ring__value--${v}`, { stroke: ink(v) }]),
|
|
972
975
|
// F-3: subtle line tints, secondary line numbers.
|
|
973
976
|
['diff', '.lc-diff__line--added', { background: 'var(--color-success-subtle)' }],
|
|
@@ -1021,9 +1024,11 @@ describe('Component pairings of 3.22.4 (request 034)', () => {
|
|
|
1021
1024
|
// The dash style still tells the relations apart (not colour alone).
|
|
1022
1025
|
expect(depTs).toMatch(/\n {2}uses:\s*\{ color: '[^']+', dashed: true,/);
|
|
1023
1026
|
expect(depTs).toMatch(/\n {2}extends:\s*\{ color: '[^']+', dashed: true,/);
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
+
// Follow-up F-9, done in request 036: `depends` reads the secondary text
|
|
1028
|
+
// (neutral-400 was 2.53:1 light) and the tree edges the field border
|
|
1029
|
+
// (neutral-300 was 1.47:1 light); their contrast is in the 036 block.
|
|
1030
|
+
expect(relationColor('depends')).toBe('var(--color-text-secondary)');
|
|
1031
|
+
expect(depTs.match(/color: 'var\(--color-field-border\)', dashed: false, isCrossRef: false/g)).toHaveLength(2);
|
|
1027
1032
|
});
|
|
1028
1033
|
|
|
1029
1034
|
// The shared status values of 3.22.3, per palette [light, dark] (AC-2).
|
|
@@ -1043,12 +1048,15 @@ describe('Component pairings of 3.22.4 (request 034)', () => {
|
|
|
1043
1048
|
* `+` (4.85 L, 5.17 D). Graphic minimum: the card border on hover and
|
|
1044
1049
|
* selected and the `uses` cross-reference line (3.75 L), the `uses` line
|
|
1045
1050
|
* (4.76 D). The terminal is always dark: 5.28 (debug level and meta).
|
|
1051
|
+
* Request 036: the dark ring secondary moved from the inverted
|
|
1052
|
+
* secondary-600 (8.35, 1.07:1 from info) to --color-secondary-graphic
|
|
1053
|
+
* (4.05), which is now the dark graphic minimum.
|
|
1046
1054
|
*/
|
|
1047
1055
|
const expected = {
|
|
1048
1056
|
text: [4.85, 5.17],
|
|
1049
|
-
graphic: [3.75, 4.
|
|
1057
|
+
graphic: [3.75, 4.05],
|
|
1050
1058
|
ncUnread: { high: [5.52, 5.55], urgent: [10.97, 8.37], low: [5.3, 5.99], category: [5.3, 5.99] },
|
|
1051
|
-
ringTrack: { primary: [3.96, 6.69], secondary: [4.19,
|
|
1059
|
+
ringTrack: { primary: [3.96, 6.69], secondary: [4.19, 4.05], success: [4.81, 6.15], warning: [5.47, 5.6], error: [11.43, 6.37], info: [9.22, 7.8] },
|
|
1052
1060
|
diff: {
|
|
1053
1061
|
numAdded: [6.91, 6.86], numRemoved: [6.58, 9.26], numContext: [7.23, 9.25],
|
|
1054
1062
|
plus: [4.85, 5.17], minus: [10.97, 7.21], contentAdded: [16.24, 9.75], contentRemoved: [15.46, 13.16],
|
|
@@ -1267,3 +1275,278 @@ describe('Component pairings of 3.22.4 (request 034)', () => {
|
|
|
1267
1275
|
expect(levels).toEqual([5.81, 7.78, 11.43, 6.19]);
|
|
1268
1276
|
});
|
|
1269
1277
|
});
|
|
1278
|
+
|
|
1279
|
+
describe('Component pairings of 3.23.1 (request 036)', () => {
|
|
1280
|
+
const lib = (path: string) => resolve(__dirname, '../lib', path);
|
|
1281
|
+
const sheets = {
|
|
1282
|
+
dl: compile(lib('description-list/description-list.component.scss')).css,
|
|
1283
|
+
nc: compile(lib('notification-center/notification-center.component.scss')).css,
|
|
1284
|
+
dep: compile(lib('dependency-viewer/dependency-viewer.component.scss')).css,
|
|
1285
|
+
log: compile(lib('log-viewer/log-viewer.component.scss')).css,
|
|
1286
|
+
progress: compile(lib('progress-bar/progress-bar.component.scss')).css,
|
|
1287
|
+
ring: compile(lib('progress-ring/progress-ring.component.scss')).css,
|
|
1288
|
+
};
|
|
1289
|
+
type Sheet = keyof typeof sheets;
|
|
1290
|
+
const depTs = readFileSync(lib('dependency-viewer/dependency-viewer.component.ts'), 'utf8');
|
|
1291
|
+
const depHtml = readFileSync(lib('dependency-viewer/dependency-viewer.component.html'), 'utf8');
|
|
1292
|
+
|
|
1293
|
+
/** Every rule as its selector list and body (`a, b { … }` names both). */
|
|
1294
|
+
const rulesOf = (css: string) =>
|
|
1295
|
+
[...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => ({
|
|
1296
|
+
selectors: m[1].split(/,(?![^(]*\))/).map((s) => s.trim()),
|
|
1297
|
+
body: m[2],
|
|
1298
|
+
}));
|
|
1299
|
+
const decls = (body: string): Record<string, string> =>
|
|
1300
|
+
Object.fromEntries([...body.matchAll(/([\w-]+):\s*([^;]+);/g)].map((m) => [m[1], m[2].trim()]));
|
|
1301
|
+
const forcedBlock = /@media \(forced-colors: active\) \{((?:[^{}]*\{[^{}]*\})*)\s*\}/g;
|
|
1302
|
+
/**
|
|
1303
|
+
* The declarations of every rule that names `selector`, merged in source
|
|
1304
|
+
* order; rules in forced-colours blocks are left out (`Mark` is no colour).
|
|
1305
|
+
*/
|
|
1306
|
+
const fills = (sheet: Sheet, selector: string): Record<string, string> => {
|
|
1307
|
+
const found = rulesOf(sheets[sheet].replace(forcedBlock, '')).filter((r) => r.selectors.includes(selector));
|
|
1308
|
+
expect(found.length).toBeGreaterThan(0);
|
|
1309
|
+
return Object.assign({}, ...found.map((r) => decls(r.body)));
|
|
1310
|
+
};
|
|
1311
|
+
/** The body of the `@media (forced-colors: active)` blocks of a sheet. */
|
|
1312
|
+
const forced = (sheet: Sheet) => [...sheets[sheet].matchAll(forcedBlock)].map((m) => m[1]).join('\n');
|
|
1313
|
+
const relationColor = (relation: string) => {
|
|
1314
|
+
const found = depTs.match(new RegExp(`\\n ${relation}:\\s*\\{ color: '([^']+)'`));
|
|
1315
|
+
expect(found).not.toBeNull();
|
|
1316
|
+
return (found as RegExpMatchArray)[1];
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
const secondary = 'var(--color-text-secondary)';
|
|
1320
|
+
const graphic = 'var(--color-secondary-graphic)';
|
|
1321
|
+
type Row = [Sheet, string, Record<string, string>];
|
|
1322
|
+
|
|
1323
|
+
const declared: Row[] = [
|
|
1324
|
+
// A (BA-10): the link and primary inks, a 2 px outline instead of the halo.
|
|
1325
|
+
['dl', '.lc-dl__value--primary', { color: 'var(--color-on-primary-subtle)', 'font-weight': 'var(--font-weight-semibold)' }],
|
|
1326
|
+
['dl', '.lc-dl__link', { color: 'var(--color-link)', 'text-decoration': 'none' }],
|
|
1327
|
+
['dl', '.lc-dl__link:hover', { color: 'var(--color-link-hover)', 'text-decoration': 'underline' }],
|
|
1328
|
+
['dl', '.lc-dl__row--clickable:focus-visible', { outline: '2px solid var(--color-primary-500)', 'outline-offset': '2px' }],
|
|
1329
|
+
// F-8: unread message and time in the secondary ink.
|
|
1330
|
+
['nc', '.lc-nc__item--unread .lc-nc__item-message', { color: secondary }],
|
|
1331
|
+
['nc', '.lc-nc__item--unread .lc-nc__item-time', { color: secondary }],
|
|
1332
|
+
// F-10: an opaque marker with its own ink.
|
|
1333
|
+
['log', '.lc-log-viewer__match', { 'background-color': 'var(--color-warning)', color: 'var(--color-on-warning)' }],
|
|
1334
|
+
['log', '.lc-log-viewer--terminal .lc-log-viewer__match', { 'background-color': '#f9e2af', color: '#1e1e2e' }],
|
|
1335
|
+
// F-13: gray is Overlay2 (Mocha) and Subtext1 (Latte).
|
|
1336
|
+
['log', '.ansi-gray', { color: '#9399b2' }],
|
|
1337
|
+
['log', ':root.light .lc-log-viewer--log .ansi-gray', { color: '#5c5f77' }],
|
|
1338
|
+
// F-11 and the ring: the secondary data-mark token.
|
|
1339
|
+
['progress', '.progress-bar--secondary .progress-bar__fill', { 'background-color': graphic }],
|
|
1340
|
+
['progress', '.progress-bar-circular--secondary .progress-bar-circular__fill', { stroke: graphic }],
|
|
1341
|
+
['ring', '.lc-progress-ring__value--secondary', { stroke: graphic }],
|
|
1342
|
+
];
|
|
1343
|
+
|
|
1344
|
+
it.each(declared)('AC-1…13: %s %s declares the design 036 values', (sheet, selector, expectedDecls) => {
|
|
1345
|
+
const found = fills(sheet, selector);
|
|
1346
|
+
for (const [property, value] of Object.entries(expectedDecls)) expect(found[property]).toBe(value);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
it('AC-2: the clickable row has no halo any more; forced colours draw the outline in Highlight', () => {
|
|
1350
|
+
const clickable = rulesOf(sheets.dl).filter((r) => r.selectors.some((s) => s.includes('lc-dl__row--clickable')));
|
|
1351
|
+
for (const r of clickable) expect(decls(r.body)['box-shadow']).toBeUndefined();
|
|
1352
|
+
expect(sheets.dl).not.toMatch(/color-mix/);
|
|
1353
|
+
expect(forced('dl')).toMatch(/\.lc-dl__row--clickable:focus-visible \{\s*outline-color: Highlight;\s*\}/);
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
it('AC-9: forced colours paint every search match, the terminal one too, with Mark / MarkText', () => {
|
|
1357
|
+
expect(forced('log')).toMatch(
|
|
1358
|
+
/\.lc-log-viewer__match,\s*\.lc-log-viewer--terminal \.lc-log-viewer__match \{\s*background-color: Mark;\s*color: MarkText;\s*\}/,
|
|
1359
|
+
);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
it('AC-8: depends, the structural edges and both arrows read the secondary text; tree edges the field border', () => {
|
|
1363
|
+
expect(relationColor('depends')).toBe(secondary);
|
|
1364
|
+
expect(depTs).toContain(`color: edge.structural ? '${secondary}' : style.color,`);
|
|
1365
|
+
expect(depTs.match(/color: 'var\(--color-field-border\)', dashed: false, isCrossRef: false/g)).toHaveLength(2);
|
|
1366
|
+
for (const id of ['default', 'depends']) {
|
|
1367
|
+
expect(depHtml).toMatch(new RegExp(`<marker id="arrow-${id}"[^>]*><path [^>]*fill="var\\(--color-text-secondary\\)"/>`));
|
|
1368
|
+
}
|
|
1369
|
+
expect(depTs).not.toMatch(/--color-neutral-400/);
|
|
1370
|
+
expect(depHtml).not.toMatch(/--color-neutral-400/);
|
|
1371
|
+
// `depends` stays solid; the relations stay apart by dash and label too.
|
|
1372
|
+
expect(depTs).toMatch(/\n {2}depends:\s*\{ color: '[^']+', dashed: false,/);
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
it('AC-12: the log tool buttons stay 28 × 28 (WCAG 2.5.8 by size)', () => {
|
|
1376
|
+
const btn = fills('log', '.lc-log-viewer__tool-btn');
|
|
1377
|
+
expect([btn.width, btn.height]).toEqual(['28px', '28px']);
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Design 036 §5, floored, per palette [light, dark]. Pages: background,
|
|
1382
|
+
* surface, surface-2; a hovered row is --color-surface-hover over them; an
|
|
1383
|
+
* unread NC row is --color-surface-selected over surface and surface-2; the
|
|
1384
|
+
* progress track is --color-surface-hover over the pages.
|
|
1385
|
+
*/
|
|
1386
|
+
const expected = {
|
|
1387
|
+
dl: {
|
|
1388
|
+
linkPage: [5.94, 8.96], linkHover: [5.64, 7.92], linkHoverInkPage: [8.74, 10.53], linkHoverInkHover: [8.29, 9.3],
|
|
1389
|
+
primaryPage: [8.74, 7.57], primaryHover: [8.29, 6.69], ring: [4.17, 7.57],
|
|
1390
|
+
},
|
|
1391
|
+
ncUnread: [6.49, 7.05],
|
|
1392
|
+
dep: { dependsLabel: [7.55, 11.15], dependsLine: [7.55, 11.15], dependsCrossRef: [4.52, 7.55], dependsLegend: [7.23, 9.25], treeSurface: [4.83, 6.7], treeSurface2: [4.62, 5.56] },
|
|
1393
|
+
dependsVsUses: [1.29, 1.66],
|
|
1394
|
+
match: [7.85, 7.85],
|
|
1395
|
+
secondary: { track: [4.19, 4.05], page: [4.41, 4.59], vsInfo: [2.19, 1.92] },
|
|
1396
|
+
// The log variant's gray: Latte in light, Mocha in dark; plain surface and the worst line.
|
|
1397
|
+
ansiGray: { plain: [6.25, 6.11], worst: [4.63, 4.55] },
|
|
1398
|
+
};
|
|
1399
|
+
const floor = (n: number) => Math.floor(n * 100) / 100;
|
|
1400
|
+
|
|
1401
|
+
describe.each(palettes.map(([name, tokens], i) => [name, tokens, i] as const))('%s palette', (_palette, tokens, i) => {
|
|
1402
|
+
const c = (name: string) => color(tokens, name);
|
|
1403
|
+
const value = (text: string): Rgba => {
|
|
1404
|
+
const ref = text.match(/^var\((--[\w-]+)\)$/);
|
|
1405
|
+
return ref ? c(ref[1]) : parse(text);
|
|
1406
|
+
};
|
|
1407
|
+
const lowest = (fg: Rgba, bases: Rgba[]) => Math.min(...bases.map((bg) => ratio(over(fg, bg), bg)));
|
|
1408
|
+
const S = c('--color-surface');
|
|
1409
|
+
const S2 = c('--color-surface-2');
|
|
1410
|
+
const BG = c('--color-background');
|
|
1411
|
+
const pages = [BG, S, S2];
|
|
1412
|
+
const hovered = pages.map((p) => over(c('--color-surface-hover'), p));
|
|
1413
|
+
let cache: ReturnType<typeof measure> | undefined;
|
|
1414
|
+
/** Every §5 cell, measured once on first use (inside a test, not at collection). */
|
|
1415
|
+
const measured = () => (cache ??= measure());
|
|
1416
|
+
|
|
1417
|
+
function measure() {
|
|
1418
|
+
const dlInk = (selector: string) => value(fills('dl', selector).color);
|
|
1419
|
+
const ringInk = value(fills('dl', '.lc-dl__row--clickable:focus-visible').outline.replace(/^2px solid /, ''));
|
|
1420
|
+
const dl = {
|
|
1421
|
+
linkPage: lowest(dlInk('.lc-dl__link'), pages),
|
|
1422
|
+
linkHover: lowest(dlInk('.lc-dl__link'), hovered),
|
|
1423
|
+
linkHoverInkPage: lowest(dlInk('.lc-dl__link:hover'), pages),
|
|
1424
|
+
linkHoverInkHover: lowest(dlInk('.lc-dl__link:hover'), hovered),
|
|
1425
|
+
primaryPage: lowest(dlInk('.lc-dl__value--primary'), pages),
|
|
1426
|
+
primaryHover: lowest(dlInk('.lc-dl__value--primary'), hovered),
|
|
1427
|
+
// The outline sits 2 px outside the row, on the card or the page.
|
|
1428
|
+
ring: lowest(ringInk, pages),
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1431
|
+
const unreadRows = [S, S2].map((row) => over(c('--color-surface-selected'), row));
|
|
1432
|
+
const ncUnread = Math.min(
|
|
1433
|
+
lowest(value(fills('nc', '.lc-nc__item--unread .lc-nc__item-message').color), unreadRows),
|
|
1434
|
+
lowest(value(fills('nc', '.lc-nc__item--unread .lc-nc__item-time').color), unreadRows),
|
|
1435
|
+
);
|
|
1436
|
+
|
|
1437
|
+
// Labels on their surface halo, lines on the viewer surface (cross-refs
|
|
1438
|
+
// at their opacity), legend lines on the legend's surface-2.
|
|
1439
|
+
const halo = value(fills('dep', '.dep-viewer__edge-label').stroke);
|
|
1440
|
+
const lineOpacity = Number(fills('dep', '.dep-viewer__edge--cross-ref').opacity);
|
|
1441
|
+
const legend = value(fills('dep', '.dep-viewer__legend').background);
|
|
1442
|
+
const depends = value(relationColor('depends'));
|
|
1443
|
+
const tree = c('--color-field-border');
|
|
1444
|
+
const dep = {
|
|
1445
|
+
dependsLabel: lowest(depends, [halo]),
|
|
1446
|
+
dependsLine: lowest(depends, [S]),
|
|
1447
|
+
dependsCrossRef: lowest([depends[0], depends[1], depends[2], depends[3] * lineOpacity], [S]),
|
|
1448
|
+
dependsLegend: lowest(depends, [legend]),
|
|
1449
|
+
treeSurface: lowest(tree, [S]),
|
|
1450
|
+
treeSurface2: lowest(tree, [S2]),
|
|
1451
|
+
};
|
|
1452
|
+
const dependsVsUses = ratio(depends, value(relationColor('uses')));
|
|
1453
|
+
|
|
1454
|
+
const mark = fills('log', '.lc-log-viewer__match');
|
|
1455
|
+
const match = ratio(value(mark.color), value(mark['background-color']));
|
|
1456
|
+
|
|
1457
|
+
const track = value(fills('progress', '.progress-bar__track')['background-color']);
|
|
1458
|
+
const bar = value(fills('progress', '.progress-bar--secondary .progress-bar__fill')['background-color']);
|
|
1459
|
+
const ringTrack = value(fills('ring', '.lc-progress-ring__track').stroke);
|
|
1460
|
+
const arc = (v: string) => value(fills('ring', `.lc-progress-ring__value--${v}`).stroke);
|
|
1461
|
+
const secondaryCells = {
|
|
1462
|
+
track: Math.min(lowest(bar, pages.map((p) => over(track, p))), lowest(arc('secondary'), pages.map((p) => over(ringTrack, p)))),
|
|
1463
|
+
page: Math.min(lowest(bar, pages), lowest(arc('secondary'), pages)),
|
|
1464
|
+
vsInfo: ratio(arc('secondary'), arc('info')),
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
// The log variant: surface, the warn and error tints, each with and without hover.
|
|
1468
|
+
const logBg = value(fills('log', '.lc-log-viewer--log')['background-color']);
|
|
1469
|
+
const hover = parse(fills('log', '.lc-log-viewer__line:hover')['background-color']);
|
|
1470
|
+
const tintOf = (lv: string) => over(parse(fills('log', `.lc-log-viewer--log .lc-log-viewer__line--${lv}`)['background-color']), logBg);
|
|
1471
|
+
const logLines = [logBg, tintOf('warn'), tintOf('error')].flatMap((b) => [b, over(hover, b)]);
|
|
1472
|
+
const gray = parse(fills('log', i === 0 ? ':root.light .lc-log-viewer--log .ansi-gray' : '.ansi-gray').color);
|
|
1473
|
+
const ansiGray = { plain: lowest(gray, [logBg]), worst: lowest(gray, logLines) };
|
|
1474
|
+
|
|
1475
|
+
return { dl, ncUnread, dep, dependsVsUses, match, secondaryCells, ansiGray };
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
it('AC-1, AC-2: dl link and primary values reach 4.5:1 on the page and a hovered row; the focus ring 3:1', () => {
|
|
1479
|
+
const { dl } = measured();
|
|
1480
|
+
for (const key of Object.keys(dl) as (keyof typeof dl)[]) {
|
|
1481
|
+
expect([key, floor(dl[key])]).toEqual([key, expected.dl[key][i]]);
|
|
1482
|
+
}
|
|
1483
|
+
expect(Math.min(dl.linkPage, dl.linkHover, dl.linkHoverInkPage, dl.linkHoverInkHover, dl.primaryPage, dl.primaryHover)).toBeGreaterThanOrEqual(4.5);
|
|
1484
|
+
expect(dl.ring).toBeGreaterThanOrEqual(3);
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
it('AC-7: the NC unread message and time reach 4.5:1 on every unread row', () => {
|
|
1488
|
+
const { ncUnread } = measured();
|
|
1489
|
+
expect(floor(ncUnread)).toBe(expected.ncUnread[i]);
|
|
1490
|
+
expect(ncUnread).toBeGreaterThanOrEqual(4.5);
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
it('AC-8: the depends label reaches 4.5:1; depends, cross-ref, legend and tree lines 3:1', () => {
|
|
1494
|
+
const { dep, dependsVsUses } = measured();
|
|
1495
|
+
for (const key of Object.keys(dep) as (keyof typeof dep)[]) {
|
|
1496
|
+
expect([key, floor(dep[key])]).toEqual([key, expected.dep[key][i]]);
|
|
1497
|
+
}
|
|
1498
|
+
expect(dep.dependsLabel).toBeGreaterThanOrEqual(4.5);
|
|
1499
|
+
expect(Math.min(...Object.values(dep))).toBeGreaterThanOrEqual(3);
|
|
1500
|
+
expect(floor(dependsVsUses)).toBe(expected.dependsVsUses[i]);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
it('AC-9: the search match ink reaches 4.5:1 on its opaque marker', () => {
|
|
1504
|
+
const { match } = measured();
|
|
1505
|
+
expect(floor(match)).toBe(expected.match[i]);
|
|
1506
|
+
expect(match).toBeGreaterThanOrEqual(4.5);
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1509
|
+
it('AC-10, AC-11: the secondary bar fill and ring arc reach 3:1 on the track and the page, 1.5:1 from info', () => {
|
|
1510
|
+
const { secondaryCells } = measured();
|
|
1511
|
+
for (const key of Object.keys(secondaryCells) as (keyof typeof secondaryCells)[]) {
|
|
1512
|
+
expect([key, floor(secondaryCells[key])]).toEqual([key, expected.secondary[key][i]]);
|
|
1513
|
+
}
|
|
1514
|
+
expect(Math.min(secondaryCells.track, secondaryCells.page)).toBeGreaterThanOrEqual(3);
|
|
1515
|
+
expect(secondaryCells.vsInfo).toBeGreaterThanOrEqual(1.5);
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it('AC-13: the log variant gray ANSI text reaches 4.5:1 on every line', () => {
|
|
1519
|
+
const { ansiGray } = measured();
|
|
1520
|
+
expect([floor(ansiGray.plain), floor(ansiGray.worst)]).toEqual([expected.ansiGray.plain[i], expected.ansiGray.worst[i]]);
|
|
1521
|
+
expect(ansiGray.worst).toBeGreaterThanOrEqual(4.5);
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
it('AC-15: the shared status values keep their hex; the new token has the design 036 values', () => {
|
|
1525
|
+
const shared: Record<string, [string, string]> = {
|
|
1526
|
+
'--color-success': ['#8ea475', '#8ea475'],
|
|
1527
|
+
'--color-error': ['#9d0e0e', '#9d0e0e'],
|
|
1528
|
+
'--color-warning': ['#e1a040', '#e1a040'],
|
|
1529
|
+
'--color-info': ['#3b6588', '#3b6588'],
|
|
1530
|
+
'--color-secondary-graphic': ['#5a798c', '#7097af'],
|
|
1531
|
+
};
|
|
1532
|
+
for (const [name, hexes] of Object.entries(shared)) expect([name, hex(tokens, name)]).toEqual([name, hexes[i]]);
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
it('AC-9, AC-13: the terminal match reads 12.90:1 on every line; gray 4.5:1 on every line', () => {
|
|
1537
|
+
const T = parse(fills('log', '.lc-log-viewer--terminal')['background-color']);
|
|
1538
|
+
const hover = parse(fills('log', '.lc-log-viewer__line:hover')['background-color']);
|
|
1539
|
+
const tint = (lv: string) => over(parse(fills('log', `.lc-log-viewer--terminal .lc-log-viewer__line--${lv}`)['background-color']), T);
|
|
1540
|
+
const lines = [T, tint('warn'), tint('error')].flatMap((b) => [b, over(hover, b)]);
|
|
1541
|
+
const mark = fills('log', '.lc-log-viewer--terminal .lc-log-viewer__match');
|
|
1542
|
+
const gray = parse(fills('log', '.ansi-gray').color);
|
|
1543
|
+
const lowest = (fg: Rgba, bases: Rgba[]) => Math.min(...bases.map((bg) => ratio(fg, bg)));
|
|
1544
|
+
const terminal = {
|
|
1545
|
+
match: ratio(parse(mark.color), parse(mark['background-color'])),
|
|
1546
|
+
grayPlain: ratio(gray, T),
|
|
1547
|
+
grayWorst: lowest(gray, lines),
|
|
1548
|
+
};
|
|
1549
|
+
expect(Object.fromEntries(Object.entries(terminal).map(([k, v]) => [k, floor(v)]))).toEqual({ match: 12.9, grayPlain: 5.81, grayWorst: 4.61 });
|
|
1550
|
+
expect(Math.min(...Object.values(terminal))).toBeGreaterThanOrEqual(4.5);
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Params } from "@angular/router";
|
|
1
2
|
import * as i0 from "@angular/core";
|
|
2
3
|
/** Visual variant of `lc-card`. */
|
|
3
4
|
export type CardVariant = 'elevated' | 'outlined' | 'flat' | 'raised' | 'dashed';
|
|
@@ -12,6 +13,8 @@ export type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
|
12
13
|
* - Border radius options (none, sm, md, lg, xl, full)
|
|
13
14
|
* - Optional title display, with an optional leading icon tile
|
|
14
15
|
* - Content projection for flexible body content
|
|
16
|
+
* - Link mode (3.24): `href` or `route` make the whole card a real link,
|
|
17
|
+
* the title stays a heading
|
|
15
18
|
*
|
|
16
19
|
* @example
|
|
17
20
|
* ```html
|
|
@@ -23,6 +26,12 @@ export type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;
|
|
|
23
26
|
* <lc-card title="Repository" subtitle="Checkout & profile" icon="git-branch">
|
|
24
27
|
* ...
|
|
25
28
|
* </lc-card>
|
|
29
|
+
*
|
|
30
|
+
* <!-- An overview card that opens a page: heading h2 with a link inside -->
|
|
31
|
+
* <lc-card title="SAP-Ist" variant="outlined" [headingLevel]="2"
|
|
32
|
+
* [route]="['/quellen', 'sap-ist']" [queryParams]="{ ansicht: 'laeufe' }">
|
|
33
|
+
* ...
|
|
34
|
+
* </lc-card>
|
|
26
35
|
* ```
|
|
27
36
|
*/
|
|
28
37
|
export declare class CardComponent {
|
|
@@ -75,7 +84,8 @@ export declare class CardComponent {
|
|
|
75
84
|
* `--shadow-auth-card`, no accent line — the sign-in card
|
|
76
85
|
* - dashed: the muted "coming soon" tile — `--color-muted-tile` fill, 1 px
|
|
77
86
|
* dashed `--color-muted-tile-border`, secondary ink, icons in
|
|
78
|
-
* `--color-muted-tile-icon`. Never clickable: `clickable` is ignored
|
|
87
|
+
* `--color-muted-tile-icon`. Never clickable: `clickable` is ignored;
|
|
88
|
+
* `href` and `route` are ignored too
|
|
79
89
|
* @default 'elevated'
|
|
80
90
|
*/
|
|
81
91
|
variant: import("@angular/core").InputSignal<CardVariant>;
|
|
@@ -85,7 +95,8 @@ export declare class CardComponent {
|
|
|
85
95
|
*/
|
|
86
96
|
padding: import("@angular/core").InputSignal<"sm" | "md" | "none" | "xs" | "lg">;
|
|
87
97
|
/**
|
|
88
|
-
* Whether the card is clickable (hover effects, cursor pointer)
|
|
98
|
+
* Whether the card is clickable (hover effects, cursor pointer). Has no
|
|
99
|
+
* effect on a link card (`href`/`route`).
|
|
89
100
|
* @default false
|
|
90
101
|
*/
|
|
91
102
|
clickable: import("@angular/core").InputSignal<boolean>;
|
|
@@ -107,18 +118,60 @@ export declare class CardComponent {
|
|
|
107
118
|
*/
|
|
108
119
|
headingLevel: import("@angular/core").InputSignal<CardHeadingLevel>;
|
|
109
120
|
/**
|
|
110
|
-
*
|
|
121
|
+
* Accessible name. On a link card (`href`/`route`) it names the link
|
|
122
|
+
* instead of the card; it must contain the visible title (WCAG 2.5.3) and
|
|
123
|
+
* the heading reads it too, so keep it short. Without it the link is named
|
|
124
|
+
* by the title, and the card's content (badge, list) is read as normal
|
|
125
|
+
* content. On other cards it labels the card.
|
|
126
|
+
* @default undefined
|
|
111
127
|
*/
|
|
112
128
|
ariaLabel: import("@angular/core").InputSignal<string | undefined>;
|
|
113
129
|
/**
|
|
114
|
-
*
|
|
130
|
+
* URL the card links to (3.24, quellen row 1). The card becomes a real
|
|
131
|
+
* link: the built-in `title` is an `<a href>` inside its heading, and the
|
|
132
|
+
* link covers the whole card (click, Enter, middle click, "open in new
|
|
133
|
+
* tab", "copy link"). No router needed; works in prerendered HTML. Wins
|
|
134
|
+
* over `route`. Ignored by `dashed`. On a link card `clickable` has no
|
|
135
|
+
* effect and `cardClick` is not emitted.
|
|
136
|
+
* @default undefined
|
|
137
|
+
*/
|
|
138
|
+
href: import("@angular/core").InputSignal<string | undefined>;
|
|
139
|
+
/**
|
|
140
|
+
* In-app route the card links to through the Angular router (`RouterLink`
|
|
141
|
+
* commands: `'/quellen/usm'` or `['/quellen', id]`), relative to the
|
|
142
|
+
* consumer's route, rendered as a real `<a href>` like `href`. Needs the
|
|
143
|
+
* app's router (`provideRouter`); without one, use `href`. `''` and `null`
|
|
144
|
+
* mean no link.
|
|
145
|
+
* @default undefined
|
|
146
|
+
*/
|
|
147
|
+
route: import("@angular/core").InputSignal<string | readonly unknown[] | null | undefined>;
|
|
148
|
+
/**
|
|
149
|
+
* Query parameters of `route`, as `RouterLink`'s `queryParams`
|
|
150
|
+
* (`{ ansicht: 'laeufe' }`). Ignored with `href` (write the query into the
|
|
151
|
+
* URL).
|
|
152
|
+
* @default undefined
|
|
153
|
+
*/
|
|
154
|
+
queryParams: import("@angular/core").InputSignal<Params | null | undefined>;
|
|
155
|
+
/**
|
|
156
|
+
* Emitted when a `clickable` card is clicked or activated with Enter or
|
|
157
|
+
* Space. Not emitted on a link card (`href`/`route`): the link navigates by
|
|
158
|
+
* itself (one navigation); bind the native `(click)` for analytics.
|
|
115
159
|
*/
|
|
116
160
|
readonly cardClick: import("@angular/core").OutputEmitterRef<MouseEvent>;
|
|
161
|
+
/**
|
|
162
|
+
* How the card links (3.24): `href` wins over `route`; `''`/`null` routes
|
|
163
|
+
* are no link, and the dashed "coming soon" tile never links.
|
|
164
|
+
*/
|
|
165
|
+
protected readonly linkMode: import("@angular/core").Signal<"href" | "route" | null>;
|
|
117
166
|
/**
|
|
118
167
|
* Whether the card acts as a button: `clickable`, except for the dashed
|
|
119
|
-
* "coming soon" tile, which is never interactive
|
|
168
|
+
* "coming soon" tile, which is never interactive, and a link card, whose
|
|
169
|
+
* anchor is the only interactive element.
|
|
120
170
|
*/
|
|
121
171
|
protected isClickable: import("@angular/core").Signal<boolean>;
|
|
172
|
+
/** `ariaLabel` on the card; on a link card it names the anchor instead. */
|
|
173
|
+
protected readonly cardAriaLabel: import("@angular/core").Signal<string | undefined>;
|
|
174
|
+
constructor();
|
|
122
175
|
/**
|
|
123
176
|
* Compute card CSS classes based on inputs
|
|
124
177
|
*/
|
|
@@ -207,6 +260,21 @@ export declare class CardComponent {
|
|
|
207
260
|
"required": false;
|
|
208
261
|
"isSignal": true;
|
|
209
262
|
};
|
|
263
|
+
"href": {
|
|
264
|
+
"alias": "href";
|
|
265
|
+
"required": false;
|
|
266
|
+
"isSignal": true;
|
|
267
|
+
};
|
|
268
|
+
"route": {
|
|
269
|
+
"alias": "route";
|
|
270
|
+
"required": false;
|
|
271
|
+
"isSignal": true;
|
|
272
|
+
};
|
|
273
|
+
"queryParams": {
|
|
274
|
+
"alias": "queryParams";
|
|
275
|
+
"required": false;
|
|
276
|
+
"isSignal": true;
|
|
277
|
+
};
|
|
210
278
|
}, {
|
|
211
279
|
"cardClick": "cardClick";
|
|
212
280
|
}, never, ["[card-badge]", "[card-header-action]", "[card-header]", "*", "[card-footer]"], true, never>;
|
|
@@ -6,6 +6,16 @@ export interface FileUploadFile {
|
|
|
6
6
|
size: number;
|
|
7
7
|
type: string;
|
|
8
8
|
}
|
|
9
|
+
/** Why `lc-file-upload` rejected a file. */
|
|
10
|
+
export type FileUploadRejectionCode = 'size' | 'type';
|
|
11
|
+
/** Payload of `lc-file-upload`'s `fileRejected`. */
|
|
12
|
+
export interface FileUploadRejection {
|
|
13
|
+
file: File;
|
|
14
|
+
/** Fixed English text ("File type not accepted", "File too large"); translate from `code`. */
|
|
15
|
+
reason: string;
|
|
16
|
+
/** `'type'`: matches no `accept` token (checked first); `'size'`: over `maxSizeBytes`. */
|
|
17
|
+
code: FileUploadRejectionCode;
|
|
18
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* File upload / dropzone component with drag & drop support.
|
|
11
21
|
*
|
|
@@ -21,11 +31,21 @@ export interface FileUploadFile {
|
|
|
21
31
|
*/
|
|
22
32
|
export declare class FileUploadComponent {
|
|
23
33
|
/**
|
|
24
|
-
* Accepted
|
|
34
|
+
* Accepted types, comma-separated, case-insensitive: extensions (`.csv`),
|
|
35
|
+
* MIME types (`text/csv`), wildcards (`image/*`); `*\/*` accepts every type.
|
|
36
|
+
* Since 3.24 checked for picked and dropped files alike; a file matching no
|
|
37
|
+
* token is rejected with `code: 'type'`. Tokens that are neither, such as a
|
|
38
|
+
* MIME type with parameters (`text/csv;charset=utf-8`), are ignored, as by
|
|
39
|
+
* the browser's picker; none left = every type. Prefer extensions: systems report MIME
|
|
40
|
+
* types differently (a CSV can arrive as `application/vnd.ms-excel`).
|
|
41
|
+
* @default ''
|
|
25
42
|
*/
|
|
26
43
|
accept: import("@angular/core").InputSignal<string>;
|
|
27
44
|
/**
|
|
28
|
-
* Allow multiple files.
|
|
45
|
+
* Allow multiple files. `false`: one file. A new pick or drop replaces the
|
|
46
|
+
* selection with its first accepted file; if all its files are rejected
|
|
47
|
+
* (size or type), the selection is cleared and `filesSelected` emits `[]`.
|
|
48
|
+
* `true`: accepted files are added to the selection.
|
|
29
49
|
* @default false
|
|
30
50
|
*/
|
|
31
51
|
multiple: import("@angular/core").InputSignal<boolean>;
|
|
@@ -60,17 +80,24 @@ export declare class FileUploadComponent {
|
|
|
60
80
|
maxSizeLabel: import("@angular/core").InputSignal<string>;
|
|
61
81
|
/** Accessible name of a selected file's remove button. */
|
|
62
82
|
removeFileAriaLabel: import("@angular/core").InputSignal<string>;
|
|
83
|
+
/**
|
|
84
|
+
* BCP 47 locale of the shown sizes (max line, drop zone name, file list):
|
|
85
|
+
* `'de-DE'` gives "20,0 MB". Units stay B/KB/MB (1024 base). Empty keeps
|
|
86
|
+
* the built-in notation ("20.0 MB"); an invalid locale falls back to it.
|
|
87
|
+
* @default ''
|
|
88
|
+
*/
|
|
89
|
+
locale: import("@angular/core").InputSignal<string>;
|
|
63
90
|
/**
|
|
64
91
|
* Emitted with selected files after validation.
|
|
65
92
|
*/
|
|
66
93
|
readonly filesSelected: import("@angular/core").OutputEmitterRef<FileUploadFile[]>;
|
|
67
94
|
/**
|
|
68
|
-
* Emitted
|
|
95
|
+
* Emitted once per rejected file, before `filesSelected`. `code`: `'type'`
|
|
96
|
+
* (matches no `accept` token; checked first) or `'size'` (over
|
|
97
|
+
* `maxSizeBytes`). `reason` is a fixed English text ("File type not
|
|
98
|
+
* accepted", "File too large"); translate from `code`.
|
|
69
99
|
*/
|
|
70
|
-
readonly fileRejected: import("@angular/core").OutputEmitterRef<
|
|
71
|
-
file: File;
|
|
72
|
-
reason: string;
|
|
73
|
-
}>;
|
|
100
|
+
readonly fileRejected: import("@angular/core").OutputEmitterRef<FileUploadRejection>;
|
|
74
101
|
protected isDragOver: import("@angular/core").WritableSignal<boolean>;
|
|
75
102
|
protected selectedFiles: import("@angular/core").WritableSignal<FileUploadFile[]>;
|
|
76
103
|
protected readonly fileInputRef: import("@angular/core").Signal<ElementRef<HTMLInputElement> | undefined>;
|
|
@@ -80,6 +107,13 @@ export declare class FileUploadComponent {
|
|
|
80
107
|
* accepted and max lines it shows, comma-separated.
|
|
81
108
|
*/
|
|
82
109
|
protected readonly dropzoneAriaLabel: import("@angular/core").Signal<string>;
|
|
110
|
+
/**
|
|
111
|
+
* The size notation of `locale` (3.24): whole bytes, KB/MB to one decimal.
|
|
112
|
+
* Null keeps the built-in `toFixed` notation (no or an invalid locale).
|
|
113
|
+
*/
|
|
114
|
+
private readonly sizeFormats;
|
|
115
|
+
/** The `accept` tokens the check knows, lower-cased; empty = every type. */
|
|
116
|
+
private readonly acceptTokens;
|
|
83
117
|
protected onDragOver(event: DragEvent): void;
|
|
84
118
|
protected onDragLeave(event: DragEvent): void;
|
|
85
119
|
protected onDrop(event: DragEvent): void;
|
|
@@ -87,6 +121,8 @@ export declare class FileUploadComponent {
|
|
|
87
121
|
protected openFilePicker(): void;
|
|
88
122
|
protected removeFile(index: number): void;
|
|
89
123
|
protected formatSize(bytes: number): string;
|
|
124
|
+
/** Why `file` is rejected: type first, then size; null when it is accepted. */
|
|
125
|
+
private rejectionOf;
|
|
90
126
|
private processFiles;
|
|
91
127
|
static ɵfac: i0.ɵɵFactoryDeclaration<FileUploadComponent, never>;
|
|
92
128
|
static ɵcmp: i0.ɵɵComponentDeclaration<FileUploadComponent, "lc-file-upload", never, {
|
|
@@ -130,6 +166,11 @@ export declare class FileUploadComponent {
|
|
|
130
166
|
"required": false;
|
|
131
167
|
"isSignal": true;
|
|
132
168
|
};
|
|
169
|
+
"locale": {
|
|
170
|
+
"alias": "locale";
|
|
171
|
+
"required": false;
|
|
172
|
+
"isSignal": true;
|
|
173
|
+
};
|
|
133
174
|
}, {
|
|
134
175
|
"filesSelected": "filesSelected";
|
|
135
176
|
"fileRejected": "fileRejected";
|