@lifecorner/angular-ui-kit 3.25.1 → 3.27.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-button.mjs +18 -2
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-callout.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-chat.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-color-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-date-range-picker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-datepicker.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-dependency-viewer.mjs +47 -9
- 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-document-viewer.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-document-viewer.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs +8 -4
- package/fesm2022/lifecorner-angular-ui-kit-file-upload.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-filter-bar.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gallery.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-gantt-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-kanban-board.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-list.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-next-step-card.mjs +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-next-step-card.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-number-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-number-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-rich-text-editor.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-scatter-plot.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-search-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-select.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-split-pane.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +3 -2
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tabs.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-tag-input.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-textarea.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs +2 -2
- package/fesm2022/lifecorner-angular-ui-kit-toggle-group.mjs.map +1 -1
- package/package.json +1 -1
- package/src/styles/_reset.scss +4 -2
- package/src/styles/focus-indicators.spec.ts +199 -0
- package/types/lifecorner-angular-ui-kit-button.d.ts +20 -0
- package/types/lifecorner-angular-ui-kit-dependency-viewer.d.ts +33 -4
- package/types/lifecorner-angular-ui-kit-description-list.d.ts +4 -0
- package/types/lifecorner-angular-ui-kit-file-upload.d.ts +11 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { compile } from 'sass';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Request 042 (3.27, design 042 D-1 to D-9): the guard for F-18 and F-19.
|
|
6
|
+
* Every element of design §6.1 rows B–F has a `:focus-visible` indicator of its
|
|
7
|
+
* own (kit ring: 2 px `--color-primary-500`), `Highlight` in forced colours;
|
|
8
|
+
* the global button reset only takes the ring from mouse focus; a field's hover
|
|
9
|
+
* rule never applies while it is focused. Read from the compiled SCSS sources.
|
|
10
|
+
*/
|
|
11
|
+
const sheets = new Map<string, string>();
|
|
12
|
+
const css = (file: string): string => {
|
|
13
|
+
if (!sheets.has(file)) sheets.set(file, compile(resolve(__dirname, file.startsWith('_') ? file : `../lib/${file}`)).css);
|
|
14
|
+
return sheets.get(file) as string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const rulesOf = (text: string) =>
|
|
18
|
+
[...text.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((m) => ({
|
|
19
|
+
selectors: m[1].split(/,(?![^(]*\))/).map((s) => s.trim()),
|
|
20
|
+
body: m[2],
|
|
21
|
+
}));
|
|
22
|
+
const decls = (body: string): Record<string, string> =>
|
|
23
|
+
Object.fromEntries([...body.matchAll(/([\w-]+):\s*([^;]+);/g)].map((m) => [m[1], m[2].replace(/\s+/g, ' ').trim()]));
|
|
24
|
+
const forcedBlock = /@media \(forced-colors: active\) \{((?:[^{}]*\{[^{}]*\})*)\s*\}/g;
|
|
25
|
+
|
|
26
|
+
/** Every rule naming `selector`, merged in source order, forced-colours blocks left out. */
|
|
27
|
+
const fills = (file: string, selector: string): Record<string, string> => {
|
|
28
|
+
const found = rulesOf(css(file).replace(forcedBlock, '')).filter((r) => r.selectors.includes(selector));
|
|
29
|
+
expect([file, selector, found.length > 0]).toEqual([file, selector, true]);
|
|
30
|
+
return Object.assign({}, ...found.map((r) => decls(r.body)));
|
|
31
|
+
};
|
|
32
|
+
/** The same, inside the forced-colours blocks only. */
|
|
33
|
+
const forcedFills = (file: string, selector: string): Record<string, string> => {
|
|
34
|
+
const inner = [...css(file).matchAll(forcedBlock)].map((m) => m[1]).join('\n');
|
|
35
|
+
return Object.assign({}, ...rulesOf(inner).filter((r) => r.selectors.includes(selector)).map((r) => decls(r.body)));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const RING = '2px solid var(--color-primary-500)';
|
|
39
|
+
|
|
40
|
+
/** Design 042 §3: [sheet, element class, outline offset]. Rows B–D, kanban (D-3), remove (D-8), dep canvas (D-7). */
|
|
41
|
+
const rings: [string, string, string][] = [
|
|
42
|
+
['rich-text-editor/rich-text-editor.component.scss', '.lc-rich-text-editor__toolbar-btn', '2px'],
|
|
43
|
+
['rich-text-editor/rich-text-editor.component.scss', '.lc-rich-text-editor__mode-btn', '2px'],
|
|
44
|
+
['document-viewer/document-viewer.component.scss', '.doc-viewer__btn', '2px'],
|
|
45
|
+
['document-viewer/document-viewer.component.scss', '.doc-viewer__download-btn', '2px'],
|
|
46
|
+
['filter-bar/filter-bar.component.scss', '.lc-filter-bar__toggle-btn', '-2px'],
|
|
47
|
+
['select/select.component.scss', '.lc-select__clear', '2px'],
|
|
48
|
+
['gallery/gallery.component.scss', '.gallery__filter-btn', '2px'],
|
|
49
|
+
['log-viewer/log-viewer.component.scss', '.lc-log-viewer__level-pill', '2px'],
|
|
50
|
+
['callout/callout.component.scss', '.callout__dismiss', '2px'],
|
|
51
|
+
['notification-center/notification-center.component.scss', '.lc-nc__action-btn', '2px'],
|
|
52
|
+
['notification-center/notification-center.component.scss', '.lc-nc__filter', '2px'],
|
|
53
|
+
['tabs/tabs.component.scss', '.lc-tabs__panel', '-2px'],
|
|
54
|
+
['chat/chat.component.scss', '.lc-chat__attachment', '2px'],
|
|
55
|
+
['chat/chat.component.scss', '.lc-chat__attach-btn', '2px'],
|
|
56
|
+
['chat/chat.component.scss', '.lc-chat__send-btn', '2px'],
|
|
57
|
+
['chat/chat.component.scss', '.lc-chat__pending-remove', '2px'],
|
|
58
|
+
['description-list/description-list.component.scss', '.lc-dl__link', '2px'],
|
|
59
|
+
['color-picker/color-picker.component.scss', '.color-picker__native', '2px'],
|
|
60
|
+
['table/table.component.scss', '.lc-table__checkbox', '2px'],
|
|
61
|
+
['number-input/number-input.component.scss', '.number-input__button', '-2px'],
|
|
62
|
+
['tag-input/tag-input.component.scss', '.lc-tag-input__tag-remove', '2px'],
|
|
63
|
+
['file-upload/file-upload.component.scss', '.file-upload__file-remove', '2px'],
|
|
64
|
+
// Review 042 r1: inset, an outside ring measured 2.19:1 on an over-limit column (light).
|
|
65
|
+
['kanban-board/kanban-board.component.scss', '.lc-kanban__card', '-2px'],
|
|
66
|
+
['dependency-viewer/dependency-viewer.component.scss', '.dep-viewer__canvas--scroll', '-2px'],
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
describe('Focus indicators (request 042)', () => {
|
|
70
|
+
describe('D-1: the global button reset', () => {
|
|
71
|
+
it('AC-5: takes the outline from mouse focus only, so a button with no ring of its own keeps the browser ring on Tab', () => {
|
|
72
|
+
const reset = css('_reset.scss');
|
|
73
|
+
expect(reset).toMatch(/\nbutton:focus:not\(:focus-visible\) \{\s*outline: none;\s*\}/);
|
|
74
|
+
expect(reset).not.toMatch(/\nbutton:focus \{/);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('D-2: the kit ring on every §6.1 B–D element', () => {
|
|
79
|
+
it.each(rings)('AC-2: %s %s draws a solid 2 px primary-500 outline (offset %s), no halo', (file, cls, offset) => {
|
|
80
|
+
const ring = fills(file, `${cls}:focus-visible`);
|
|
81
|
+
expect([ring.outline, ring['outline-offset']]).toEqual([RING, offset]);
|
|
82
|
+
expect(ring['box-shadow']).toBeUndefined();
|
|
83
|
+
expect(ring.transition).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it.each(rings)('AC-3: %s %s turns Highlight in forced colours', (file, cls) => {
|
|
87
|
+
expect(forcedFills(file, `${cls}:focus-visible`)['outline-color']).toBe('Highlight');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it.each(rings)('AC-4: %s %s draws no ring on mouse focus (no outline on a bare :focus rule)', (file, cls) => {
|
|
91
|
+
const bare = rulesOf(css(file).replace(forcedBlock, '')).filter((r) => r.selectors.includes(`${cls}:focus`));
|
|
92
|
+
for (const r of bare) expect([cls, decls(r.body).outline ?? 'none']).toEqual([cls, 'none']);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('AC-2: the dimmed buttons turn fully opaque on focus (the ring is not dimmed with them)', () => {
|
|
96
|
+
expect(fills('callout/callout.component.scss', '.callout__dismiss').opacity).toBe('0.6');
|
|
97
|
+
expect(fills('callout/callout.component.scss', '.callout__dismiss:focus-visible').opacity).toBe('1');
|
|
98
|
+
expect(fills('tag-input/tag-input.component.scss', '.lc-tag-input__tag-remove').opacity).toBe('0.7');
|
|
99
|
+
expect(fills('tag-input/tag-input.component.scss', '.lc-tag-input__tag-remove:focus-visible').opacity).toBe('1');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Offset 2px → -2px by review 042 r1: outside, the ring sat on the column tint
|
|
103
|
+
// (2.19:1 on an over-limit column, light); inset it sits on the card (4.36 / 9.12).
|
|
104
|
+
it('AC-2: the kanban card drops its 20 % halo (1.28:1) for an inset ring on the card and keeps the border change', () => {
|
|
105
|
+
expect(fills('kanban-board/kanban-board.component.scss', '.lc-kanban__card:focus-visible')).toEqual({
|
|
106
|
+
outline: RING,
|
|
107
|
+
'outline-offset': '-2px',
|
|
108
|
+
'border-color': 'var(--color-primary-500)',
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('D-3: indicators that forced colours drop', () => {
|
|
114
|
+
const forced: [string, string, Record<string, string>][] = [
|
|
115
|
+
['toggle-group/toggle-group.component.scss', '.lc-toggle-group__btn:focus-visible', { outline: '2px solid Highlight', 'outline-offset': '-2px' }],
|
|
116
|
+
['list/list.component.scss', '.lc-list__item:focus-visible', { outline: '2px solid Highlight', 'outline-offset': '-2px' }],
|
|
117
|
+
['gantt-chart/gantt-chart.component.scss', '.lc-gantt__bar:focus-visible', { outline: '2px solid Highlight', 'outline-offset': '2px' }],
|
|
118
|
+
['split-pane/split-pane.component.scss', '.lc-split-pane__separator:focus-visible', { outline: '2px solid Highlight', 'outline-offset': '-2px' }],
|
|
119
|
+
['scatter-plot/scatter-plot.component.scss', '.lc-scatter-plot__dot:focus-visible', { stroke: 'Highlight' }],
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
it.each(forced)('AC-3: %s %s has its own forced-colours indicator', (file, selector, expected) => {
|
|
123
|
+
expect(forcedFills(file, selector)).toEqual(expected);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('AC-7: their normal-mode indicators are unchanged', () => {
|
|
127
|
+
expect(fills('toggle-group/toggle-group.component.scss', '.lc-toggle-group__btn:focus-visible')['box-shadow']).toBe('inset 0 0 0 2px var(--color-primary-500)');
|
|
128
|
+
expect(fills('list/list.component.scss', '.lc-list__item:focus-visible')['box-shadow']).toBe('inset 0 0 0 2px var(--color-primary-500)');
|
|
129
|
+
expect(fills('gantt-chart/gantt-chart.component.scss', '.lc-gantt__bar:focus-visible')['box-shadow']).toBe('0 0 0 2px var(--color-surface), 0 0 0 4px var(--color-primary-500)');
|
|
130
|
+
expect(fills('split-pane/split-pane.component.scss', '.lc-split-pane__separator:focus-visible::before').background).toBe('var(--color-primary-500)');
|
|
131
|
+
expect(fills('scatter-plot/scatter-plot.component.scss', '.lc-scatter-plot__dot:focus-visible').stroke).toBe('var(--color-text-primary)');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('D-9: the rich-text editing surface', () => {
|
|
136
|
+
const frame = '.lc-rich-text-editor:has(.lc-rich-text-editor__rich:focus-visible, .lc-rich-text-editor__textarea:focus-visible)';
|
|
137
|
+
const file = 'rich-text-editor/rich-text-editor.component.scss';
|
|
138
|
+
|
|
139
|
+
it('AC-2: the frame shows the field ring (lc-input pattern); the surfaces keep outline: none', () => {
|
|
140
|
+
expect(fills(file, frame)).toEqual({ 'border-color': 'var(--color-primary-500)', outline: RING, 'outline-offset': '0px' });
|
|
141
|
+
expect(fills(file, '.lc-rich-text-editor__rich').outline).toBe('none');
|
|
142
|
+
expect(fills(file, '.lc-rich-text-editor__textarea').outline).toBe('none');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('AC-3: the ancestor ring has its own Highlight rule', () => {
|
|
146
|
+
expect(forcedFills(file, frame)).toEqual({ 'border-color': 'Highlight', 'outline-color': 'Highlight' });
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('D-4: the light header on a dark page', () => {
|
|
151
|
+
const file = 'header/header.component.scss';
|
|
152
|
+
const own = ['.lc-header__nav-link', '.lc-header__logo', '.lc-header__context-info', '.lc-header__profile-trigger'];
|
|
153
|
+
|
|
154
|
+
it('AC-2: the header-owned rings read the private ring colour, primary-500 by default', () => {
|
|
155
|
+
for (const cls of own) {
|
|
156
|
+
expect([cls, fills(file, `${cls}:focus-visible`).outline]).toEqual([cls, '2px solid var(--_lc-header-ring, var(--color-primary-500))']);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('AC-2: theme="light" pins the light primary-500: 4.36:1 on its white bar in both themes (dark primary-500 was 1.89)', () => {
|
|
161
|
+
const light = fills(file, '.lc-header--light');
|
|
162
|
+
expect([light['--_lc-header-ring'], light['--lc-header-bg']]).toEqual(['#208497', '#ffffff']);
|
|
163
|
+
const lum = (hex: string) =>
|
|
164
|
+
[1, 3, 5]
|
|
165
|
+
.map((i) => parseInt(hex.slice(i, i + 2), 16) / 255)
|
|
166
|
+
.map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4))
|
|
167
|
+
.reduce((sum, v, i) => sum + v * [0.2126, 0.7152, 0.0722][i], 0);
|
|
168
|
+
expect(Math.floor(((lum('#ffffff') + 0.05) / (lum('#208497') + 0.05)) * 100) / 100).toBe(4.36);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('D-6: focus wins over hover in every field with the pattern (F-19)', () => {
|
|
173
|
+
// [sheet, hover selector after the fix, declarations it keeps]
|
|
174
|
+
const hovers: [string, string, Record<string, string>][] = [
|
|
175
|
+
['textarea/textarea.component.scss', '.lc-textarea--outline:hover:not(:disabled):not(:read-only):not(:focus)', { 'border-color': 'var(--color-primary-400)' }],
|
|
176
|
+
['textarea/textarea.component.scss', '.lc-textarea--filled:hover:not(:disabled):not(:read-only):not(:focus)', { 'background-color': 'var(--color-surface-hover)' }],
|
|
177
|
+
['datepicker/datepicker.component.scss', '.lc-datepicker:hover:not(:disabled):not([readonly]):not(:focus)', { 'border-color': 'var(--color-primary-400)' }],
|
|
178
|
+
['datepicker/datepicker.component.scss', '.lc-datepicker.lc-datepicker--filled:hover:not(:disabled):not([readonly]):not(:focus)', { 'background-color': 'var(--color-surface-hover)', 'border-color': 'var(--color-border)' }],
|
|
179
|
+
['input/input.component.scss', '.input-container:hover:not(.input-disabled):not(.input-readonly):not(:focus-within)', { 'border-color': 'var(--color-text-secondary)' }],
|
|
180
|
+
['select/select.component.scss', '.lc-select:hover:not(.lc-select--disabled):not(:focus)', { 'border-color': 'var(--color-text-secondary)' }],
|
|
181
|
+
['select/select.component.scss', '.lc-select.lc-select--filled:hover:not(.lc-select--disabled):not(:focus)', { 'background-color': 'var(--color-surface-hover)', 'border-color': 'var(--color-border)' }],
|
|
182
|
+
['search-input/search-input.component.scss', '.search-input:hover:not(.search-input--disabled):not(:focus-within)', { 'border-color': 'var(--color-text-secondary)' }],
|
|
183
|
+
['tag-input/tag-input.component.scss', '.lc-tag-input__container:hover:not(.lc-tag-input__container--focused)', { 'border-color': 'var(--color-text-secondary)' }],
|
|
184
|
+
['date-range-picker/date-range-picker.component.scss', '.lc-drp__field:hover:not(.lc-drp__field--disabled):not(:focus-within)', { 'border-color': 'var(--color-text-secondary)' }],
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
it.each(hovers)('AC-6/AC-7: %s %s keeps its look and excludes focus', (file, selector, expected) => {
|
|
188
|
+
expect(fills(file, selector)).toEqual(expected);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it.each(hovers)('AC-6: %s has no other hover rule on that field', (file, selector) => {
|
|
192
|
+
const field = selector.slice(0, selector.indexOf(':hover'));
|
|
193
|
+
const others = rulesOf(css(file).replace(forcedBlock, ''))
|
|
194
|
+
.flatMap((r) => r.selectors)
|
|
195
|
+
.filter((s) => s.startsWith(`${field}:hover`) && s !== selector);
|
|
196
|
+
expect(others).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -54,6 +54,19 @@ export declare class ButtonComponent {
|
|
|
54
54
|
readonly iconOnly: import("@angular/core").InputSignal<boolean>;
|
|
55
55
|
readonly fullWidth: import("@angular/core").InputSignal<boolean>;
|
|
56
56
|
readonly ariaLabel: import("@angular/core").InputSignal<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Id, or space-separated ids, of elements that describe the button, set as
|
|
59
|
+
* `aria-describedby` on the inner `<button>`: a screen reader reads the
|
|
60
|
+
* button's name and then their text, also while `disabled` or `loading`.
|
|
61
|
+
* Use it to tie a visible reason line ("Wählen Sie zuerst eine Datei.") to a
|
|
62
|
+
* disabled button; `disabledReason` stays a tooltip for pointers (with both
|
|
63
|
+
* set, the description comes from the ids, so the tooltip is not read
|
|
64
|
+
* twice). Empty renders no attribute. The kit does not check that the ids
|
|
65
|
+
* exist. Don't put `aria-describedby` on `<lc-button>` itself: it would stay
|
|
66
|
+
* on the host. (3.26)
|
|
67
|
+
* @default ''
|
|
68
|
+
*/
|
|
69
|
+
readonly ariaDescribedBy: import("@angular/core").InputSignal<string>;
|
|
57
70
|
readonly type: import("@angular/core").InputSignal<ButtonType>;
|
|
58
71
|
/**
|
|
59
72
|
* Native tooltip (title attribute) on the button.
|
|
@@ -146,6 +159,11 @@ export declare class ButtonComponent {
|
|
|
146
159
|
"required": false;
|
|
147
160
|
"isSignal": true;
|
|
148
161
|
};
|
|
162
|
+
"ariaDescribedBy": {
|
|
163
|
+
"alias": "ariaDescribedBy";
|
|
164
|
+
"required": false;
|
|
165
|
+
"isSignal": true;
|
|
166
|
+
};
|
|
149
167
|
"type": {
|
|
150
168
|
"alias": "type";
|
|
151
169
|
"required": false;
|
|
@@ -186,6 +204,8 @@ export declare class ButtonComponent {
|
|
|
186
204
|
* `title`, `aria-*`) passes through untouched, and the component adds no event
|
|
187
205
|
* listeners: after server-side rendering or prerendering the markup is a plain
|
|
188
206
|
* `<a class="btn btn-primary btn-md" href="…">` that works without JavaScript.
|
|
207
|
+
* For a description, set `aria-describedby` on the anchor itself; unlike
|
|
208
|
+
* `lc-button` there is no `ariaDescribedBy` input (3.26).
|
|
189
209
|
* `routerLink` can sit on the same anchor for in-app navigation.
|
|
190
210
|
*
|
|
191
211
|
* Same variants, sizes and modifiers as `lc-button` (it shares its stylesheet).
|
|
@@ -269,19 +269,30 @@ export declare class DependencyViewerComponent {
|
|
|
269
269
|
* `contain` fits both axes, so everything is visible at once but a big graph
|
|
270
270
|
* shrinks to get there. `fit-width` fits the width only and lets the viewer grow
|
|
271
271
|
* as tall as the graph needs, so the nodes keep a readable size and the *page*
|
|
272
|
-
* scrolls;
|
|
273
|
-
* process view generally wants `fit-width`.
|
|
272
|
+
* scrolls; past `minNodeSize`, a graph wider than the frame scrolls sideways
|
|
273
|
+
* inside it. Past roughly 20 nodes a process view generally wants `fit-width`.
|
|
274
274
|
*/
|
|
275
275
|
readonly fitMode: import("@angular/core").InputSignal<DependencyFitMode | null>;
|
|
276
276
|
/**
|
|
277
277
|
* Smallest node width, in px, that fitting may shrink to. Below it the fit stops
|
|
278
278
|
* scaling down and lets the graph overflow instead — which `fit-width` turns into
|
|
279
|
-
*
|
|
279
|
+
* sideways scrolling of the frame (pinned to the start, see `regionLabel`), and
|
|
280
|
+
* `contain` into clipping. Pair it with `fit-width`.
|
|
280
281
|
*
|
|
281
282
|
* Nodes are 160px wide unscaled (wider — up to 240px — when the graph's labels
|
|
282
|
-
* need it), so e.g. `80` allows shrinking to half size.
|
|
283
|
+
* need it), so e.g. `80` allows shrinking to half size. `200` keeps the 12 px
|
|
284
|
+
* labels at 10 px or more whatever the node width (160–240 px): the phone
|
|
285
|
+
* recipe is `fitMode="fit-width"` with `minNodeSize` 200.
|
|
283
286
|
*/
|
|
284
287
|
readonly minNodeSize: import("@angular/core").InputSignal<number | null>;
|
|
288
|
+
/**
|
|
289
|
+
* Accessible name of the canvas while it scrolls sideways (`fitMode="fit-width"`
|
|
290
|
+
* with a `minNodeSize` the frame can't hold). It is then a focusable
|
|
291
|
+
* `role="region"`, scrolled with the arrow keys. Set a short, localised name
|
|
292
|
+
* that is unique on the page. Not used otherwise.
|
|
293
|
+
* @default 'Dependency graph'
|
|
294
|
+
*/
|
|
295
|
+
readonly regionLabel: import("@angular/core").InputSignal<string>;
|
|
285
296
|
/**
|
|
286
297
|
* Viewport navigation: drag-to-pan, wheel-zoom and the toolbar's zoom buttons.
|
|
287
298
|
* `false` freezes the viewport — node selection, expansion and collapse keep
|
|
@@ -378,6 +389,19 @@ export declare class DependencyViewerComponent {
|
|
|
378
389
|
protected effectiveFitMode: import("@angular/core").Signal<DependencyFitMode>;
|
|
379
390
|
/** The host only gives up its configured height when the fit made it grow. */
|
|
380
391
|
protected hostHeight: import("@angular/core").Signal<string>;
|
|
392
|
+
/**
|
|
393
|
+
* Horizontal extent of the graph, in layout units, while `fit-width` is held
|
|
394
|
+
* at `minNodeSize` and the graph is wider than the frame; `null` otherwise.
|
|
395
|
+
* The canvas then scrolls sideways instead of clipping both ends.
|
|
396
|
+
*/
|
|
397
|
+
private scrollBox;
|
|
398
|
+
/** Whether the canvas scrolls sideways (see `scrollBox`). */
|
|
399
|
+
protected scrolling: import("@angular/core").Signal<boolean>;
|
|
400
|
+
/**
|
|
401
|
+
* Scroll width of the canvas: the graph's right edge at the current pan and
|
|
402
|
+
* zoom. The svg's own box is unscaled, so it can't be the scroll extent.
|
|
403
|
+
*/
|
|
404
|
+
protected scrollWidth: import("@angular/core").Signal<string | null>;
|
|
381
405
|
/** `minNodeSize` expressed as the scale it forbids fitting to go below. */
|
|
382
406
|
private minFitScale;
|
|
383
407
|
constructor();
|
|
@@ -500,6 +524,11 @@ export declare class DependencyViewerComponent {
|
|
|
500
524
|
"required": false;
|
|
501
525
|
"isSignal": true;
|
|
502
526
|
};
|
|
527
|
+
"regionLabel": {
|
|
528
|
+
"alias": "regionLabel";
|
|
529
|
+
"required": false;
|
|
530
|
+
"isSignal": true;
|
|
531
|
+
};
|
|
503
532
|
"interactive": {
|
|
504
533
|
"alias": "interactive";
|
|
505
534
|
"required": false;
|
|
@@ -46,6 +46,10 @@ export type DescriptionListSeparator = 'line' | 'divider' | 'none';
|
|
|
46
46
|
* Features:
|
|
47
47
|
* - `rows` layout: term on the left, value on the right, with an optional
|
|
48
48
|
* dotted leader line connecting them (the DS2.0 "spec sheet" look).
|
|
49
|
+
* - `rows`: a row that is too narrow is shared — term and value each keep
|
|
50
|
+
* their width up to half of the free width (row − 32 px); the longer one
|
|
51
|
+
* wraps in the rest: the term at spaces, the value at spaces or inside an
|
|
52
|
+
* unbroken string (3.26).
|
|
49
53
|
* - `stacked` layout: term above value, for narrow columns.
|
|
50
54
|
* - Per-row value emphasis and optional links.
|
|
51
55
|
* - Semantic markup (`<dl>`/`<dt>`/`<dd>`) for accessibility; `clickable`
|
|
@@ -78,7 +78,12 @@ export declare class FileUploadComponent {
|
|
|
78
78
|
* @default 'Max:'
|
|
79
79
|
*/
|
|
80
80
|
maxSizeLabel: import("@angular/core").InputSignal<string>;
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Accessible name of a selected file's remove button, followed by the file
|
|
83
|
+
* name ("Remove file bericht.csv"), so each button in the list has its own
|
|
84
|
+
* name.
|
|
85
|
+
* @default 'Remove file'
|
|
86
|
+
*/
|
|
82
87
|
removeFileAriaLabel: import("@angular/core").InputSignal<string>;
|
|
83
88
|
/**
|
|
84
89
|
* BCP 47 locale of the shown sizes (max line, drop zone name, file list):
|
|
@@ -119,6 +124,11 @@ export declare class FileUploadComponent {
|
|
|
119
124
|
protected onDrop(event: DragEvent): void;
|
|
120
125
|
protected onFileChange(event: Event): void;
|
|
121
126
|
protected openFilePicker(): void;
|
|
127
|
+
/**
|
|
128
|
+
* Enter or Space on the zone (3.25.2): no page scroll on Space, one open per
|
|
129
|
+
* press (auto-repeat ignored).
|
|
130
|
+
*/
|
|
131
|
+
protected onKeyActivate(event: Event): void;
|
|
122
132
|
protected removeFile(index: number): void;
|
|
123
133
|
protected formatSize(bytes: number): string;
|
|
124
134
|
/** Why `file` is rejected: type first, then size; null when it is accepted. */
|