@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.148 → 2.0.0-next.150
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/bundle/openbridge-webcomponents.bundle.js +4561 -3882
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +535 -21
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.css.js +94 -0
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.css.js.map +1 -0
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.d.ts +72 -0
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.d.ts.map +1 -0
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.js +396 -0
- package/dist/components/alert-list-details-experimental/alert-list-details-experimental.js.map +1 -0
- package/dist/components/icon-button/icon-button.d.ts +3 -0
- package/dist/components/icon-button/icon-button.d.ts.map +1 -1
- package/dist/components/icon-button/icon-button.js +6 -0
- package/dist/components/icon-button/icon-button.js.map +1 -1
- package/dist/components/table/table.css.js +43 -0
- package/dist/components/table/table.css.js.map +1 -1
- package/dist/components/table/table.d.ts +31 -1
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +124 -11
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/top-bar/top-bar.d.ts.map +1 -1
- package/dist/components/top-bar/top-bar.js +8 -0
- package/dist/components/top-bar/top-bar.js.map +1 -1
- package/dist/generated/locales/es-419.d.ts +11 -1
- package/dist/generated/locales/es-419.d.ts.map +1 -1
- package/dist/generated/locales/es-419.js +11 -1
- package/dist/generated/locales/es-419.js.map +1 -1
- package/dist/generated/locales/fi-FI.d.ts +11 -1
- package/dist/generated/locales/fi-FI.d.ts.map +1 -1
- package/dist/generated/locales/fi-FI.js +11 -1
- package/dist/generated/locales/fi-FI.js.map +1 -1
- package/dist/integration-systems/integration-bar/integration-bar.d.ts +8 -8
- package/dist/integration-systems/integration-bar/integration-bar.d.ts.map +1 -1
- package/dist/integration-systems/integration-bar/integration-bar.js +13 -1
- package/dist/integration-systems/integration-bar/integration-bar.js.map +1 -1
- package/dist/integration-systems/integration-bar-dropdown/integration-bar-dropdown.d.ts +72 -7
- package/dist/integration-systems/integration-bar-dropdown/integration-bar-dropdown.d.ts.map +1 -1
- package/dist/integration-systems/integration-bar-dropdown/integration-bar-dropdown.js +99 -41
- package/dist/integration-systems/integration-bar-dropdown/integration-bar-dropdown.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-list-details-experimental/alert-list-details-experimental.css +70 -0
- package/src/components/alert-list-details-experimental/alert-list-details-experimental.stories.ts +510 -0
- package/src/components/alert-list-details-experimental/alert-list-details-experimental.ts +457 -0
- package/src/components/icon-button/icon-button.spec.ts +52 -0
- package/src/components/icon-button/icon-button.ts +7 -0
- package/src/components/table/table.css +42 -0
- package/src/components/table/table.stories.ts +279 -0
- package/src/components/table/table.ts +177 -13
- package/src/components/top-bar/top-bar.ts +10 -0
- package/src/generated/locales/es-419.ts +11 -1
- package/src/generated/locales/fi-FI.ts +11 -1
- package/src/integration-systems/integration-bar/integration-bar.ts +21 -9
- package/src/integration-systems/integration-bar-dropdown/integration-bar-dropdown.stories.ts +70 -3
- package/src/integration-systems/integration-bar-dropdown/integration-bar-dropdown.ts +138 -52
- package/src/types.ts +6 -4
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {LitElement, html, unsafeCSS} from 'lit';
|
|
2
|
+
import {customElement} from '../../decorator.js';
|
|
3
|
+
import compentStyle from './alert-list-details-experimental.css?inline';
|
|
4
|
+
import {msg} from '@lit/localize';
|
|
5
|
+
import {property, query, state} from 'lit/decorators.js';
|
|
6
|
+
import '../icon-button/icon-button.js';
|
|
7
|
+
import '../button/button.js';
|
|
8
|
+
import '../../icons/icon-silence-iec.js';
|
|
9
|
+
import '../../icons/icon-alerts.js';
|
|
10
|
+
import '../../icons/icon-alerts-shelf.js';
|
|
11
|
+
import '../../icons/icon-alerts-active.js';
|
|
12
|
+
import '../../icons/icon-alarm-rectified-iec.js';
|
|
13
|
+
import '../../icons/icon-unacknowledged.js';
|
|
14
|
+
import '../../icons/icon-alarm-noack-iec.js';
|
|
15
|
+
import '../../icons/icon-warning-noack-iec.js';
|
|
16
|
+
import '../alert-icon/alert-icon.js';
|
|
17
|
+
import {
|
|
18
|
+
Alert,
|
|
19
|
+
comparePriorityAlerts,
|
|
20
|
+
isActive,
|
|
21
|
+
isAcknowledged,
|
|
22
|
+
isBlocked,
|
|
23
|
+
isShelved,
|
|
24
|
+
} from '../../types.js';
|
|
25
|
+
import {
|
|
26
|
+
excludedFromUnackedFilter,
|
|
27
|
+
requiresAcknowledgement,
|
|
28
|
+
usesAlarmNoAckIcon,
|
|
29
|
+
} from '../../alert-severity.js';
|
|
30
|
+
import {
|
|
31
|
+
ObcTable,
|
|
32
|
+
ObcTableCellClickEvent,
|
|
33
|
+
ObcTableCellData,
|
|
34
|
+
ObcTableCellType,
|
|
35
|
+
ObcTableExpandToggleEvent,
|
|
36
|
+
ObcTableRowClickEvent,
|
|
37
|
+
ObcTableRow,
|
|
38
|
+
ObcTableColumn,
|
|
39
|
+
} from '../table/table.js';
|
|
40
|
+
import '../scrollbar/scrollbar.js';
|
|
41
|
+
|
|
42
|
+
export enum AlertListMode {
|
|
43
|
+
UNACKED = 'unacked',
|
|
44
|
+
ALL = 'all',
|
|
45
|
+
SHELVED = 'shelved',
|
|
46
|
+
BLOCKED = 'blocked',
|
|
47
|
+
RECTIFIED = 'rectified',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ObcAckClickEvent = CustomEvent<{
|
|
51
|
+
alert: Alert;
|
|
52
|
+
}>;
|
|
53
|
+
|
|
54
|
+
export type ObcRowClickEvent = CustomEvent<{
|
|
55
|
+
alert: Alert;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
export function getAlertListModeData(selectedMode: AlertListMode) {
|
|
59
|
+
if (selectedMode === AlertListMode.ALL)
|
|
60
|
+
return {
|
|
61
|
+
name: AlertListMode.ALL,
|
|
62
|
+
title: msg('All'),
|
|
63
|
+
emptyTitle: msg('No active alerts'),
|
|
64
|
+
emptyIcon: html`<obi-alerts></obi-alerts>`,
|
|
65
|
+
filter: (alert: Alert) => !isShelved(alert) && isActive(alert),
|
|
66
|
+
};
|
|
67
|
+
else if (selectedMode === AlertListMode.UNACKED)
|
|
68
|
+
return {
|
|
69
|
+
name: AlertListMode.UNACKED,
|
|
70
|
+
title: msg('Unacked'),
|
|
71
|
+
emptyTitle: msg('No unacknowledged alerts'),
|
|
72
|
+
emptyIcon: html`<obi-unacknowledged></obi-unacknowledged>`,
|
|
73
|
+
filter: (alert: Alert) =>
|
|
74
|
+
!isAcknowledged(alert) &&
|
|
75
|
+
isActive(alert) &&
|
|
76
|
+
!excludedFromUnackedFilter(alert.type) &&
|
|
77
|
+
!isShelved(alert),
|
|
78
|
+
};
|
|
79
|
+
else if (selectedMode === AlertListMode.SHELVED)
|
|
80
|
+
return {
|
|
81
|
+
name: AlertListMode.SHELVED,
|
|
82
|
+
title: msg('Shelved'),
|
|
83
|
+
emptyTitle: msg('No shelved alerts'),
|
|
84
|
+
emptyIcon: html`<obi-alerts-shelf></obi-alerts-shelf>`,
|
|
85
|
+
filter: (alert: Alert) => isShelved(alert),
|
|
86
|
+
};
|
|
87
|
+
else if (selectedMode === AlertListMode.BLOCKED)
|
|
88
|
+
return {
|
|
89
|
+
name: AlertListMode.BLOCKED,
|
|
90
|
+
title: msg('Blocked'),
|
|
91
|
+
emptyTitle: msg('No blocked alerts'),
|
|
92
|
+
emptyIcon: html`<obi-alerts-active></obi-alerts-active>`,
|
|
93
|
+
filter: (alert: Alert) => isBlocked(alert),
|
|
94
|
+
};
|
|
95
|
+
else if (selectedMode === AlertListMode.RECTIFIED)
|
|
96
|
+
return {
|
|
97
|
+
name: AlertListMode.RECTIFIED,
|
|
98
|
+
title: msg('Rectified'),
|
|
99
|
+
emptyTitle: msg('No rectified alerts'),
|
|
100
|
+
emptyIcon: html`<obi-alarm-rectified-iec></obi-alarm-rectified-iec>`,
|
|
101
|
+
filter: (alert: Alert) => !isActive(alert),
|
|
102
|
+
};
|
|
103
|
+
else throw new Error('Invalid selected mode');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function canAckFilter(filter: (alert: Alert) => boolean) {
|
|
107
|
+
return (alert: Alert) =>
|
|
108
|
+
!isAcknowledged(alert) &&
|
|
109
|
+
!alert.noAck &&
|
|
110
|
+
!excludedFromUnackedFilter(alert.type) &&
|
|
111
|
+
filter(alert);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @availableWhen timeFormatter showTime==true
|
|
116
|
+
* @property defaultExpanded - Whether groups start expanded. Set false to open the list collapsed.
|
|
117
|
+
* @fires {ObcAckClickEvent} ack-click - Fired when the user clicks the "ACK" button.
|
|
118
|
+
* @fires {ObcRowClickEvent} row-click - Fired when the user clicks a row.
|
|
119
|
+
* @experimental
|
|
120
|
+
*/
|
|
121
|
+
@customElement('obc-alert-list-details-experimental')
|
|
122
|
+
export class ObcAlertListDetailsExperimental extends LitElement {
|
|
123
|
+
@property({type: String}) selectedMode: AlertListMode = AlertListMode.ALL;
|
|
124
|
+
@property({type: Array}) alerts: Alert[] = [];
|
|
125
|
+
@property({type: Boolean}) showTime: boolean = false;
|
|
126
|
+
@property({attribute: false}) timeFormatter: (time: Date) => string = (
|
|
127
|
+
time: Date
|
|
128
|
+
) => time.toLocaleTimeString(undefined, {hour12: false});
|
|
129
|
+
@property({type: Boolean}) small: boolean = false;
|
|
130
|
+
@property({type: Boolean, attribute: false}) defaultExpanded: boolean = true;
|
|
131
|
+
|
|
132
|
+
@query('obc-table')
|
|
133
|
+
private alertList!: ObcTable;
|
|
134
|
+
|
|
135
|
+
@state() private expansionOverrides = new Map<string, boolean>();
|
|
136
|
+
|
|
137
|
+
private alertByRowId = new Map<string, Alert>();
|
|
138
|
+
|
|
139
|
+
public getVisibleAlerts(): Alert[] {
|
|
140
|
+
const seen = new Set<string>();
|
|
141
|
+
return this.alertList
|
|
142
|
+
.getAllVisibleRows()
|
|
143
|
+
.map((rowId) => this.alertByRowId.get(rowId))
|
|
144
|
+
.filter((alert): alert is Alert => alert !== undefined)
|
|
145
|
+
.filter((alert) => {
|
|
146
|
+
if (seen.has(alert.id)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
seen.add(alert.id);
|
|
150
|
+
return true;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private onRowClick(e: ObcTableRowClickEvent) {
|
|
155
|
+
const row = this.alertByRowId.get(e.detail.row.id);
|
|
156
|
+
if (row) {
|
|
157
|
+
this.dispatchEvent(
|
|
158
|
+
new CustomEvent('row-click', {detail: {alert: row}}) as ObcRowClickEvent
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private onCellButtonClick(e: ObcTableCellClickEvent) {
|
|
164
|
+
const row = this.alertByRowId.get(e.detail.rowId);
|
|
165
|
+
if (row) {
|
|
166
|
+
this.dispatchEvent(
|
|
167
|
+
new CustomEvent('ack-click', {
|
|
168
|
+
detail: {alert: row},
|
|
169
|
+
bubbles: false,
|
|
170
|
+
}) as ObcAckClickEvent
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private onExpandToggle(e: ObcTableExpandToggleEvent) {
|
|
176
|
+
const overrides = new Map(this.expansionOverrides);
|
|
177
|
+
overrides.set(e.detail.rowId, e.detail.expanded);
|
|
178
|
+
this.expansionOverrides = overrides;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private isExpanded(rowId: string) {
|
|
182
|
+
return this.expansionOverrides.get(rowId) ?? this.defaultExpanded;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private get columns() {
|
|
186
|
+
if (this.small) {
|
|
187
|
+
const columns: ObcTableColumn<ObcTableCellData, ObcTableRow>[] = [
|
|
188
|
+
{
|
|
189
|
+
label: 'Status',
|
|
190
|
+
key: 'status',
|
|
191
|
+
sortDirection: 'desc',
|
|
192
|
+
sortable: true,
|
|
193
|
+
compareFunction: (_a, _b, aRow, bRow) => {
|
|
194
|
+
const aAlert = this.alertByRowId.get(aRow.id);
|
|
195
|
+
const bAlert = this.alertByRowId.get(bRow.id);
|
|
196
|
+
if (aAlert && bAlert) {
|
|
197
|
+
return comparePriorityAlerts(aAlert, bAlert);
|
|
198
|
+
}
|
|
199
|
+
return 0;
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
if (this.showTime) {
|
|
204
|
+
columns.push({
|
|
205
|
+
label: 'Activated',
|
|
206
|
+
key: 'time',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
columns.push({
|
|
210
|
+
label: 'ACK-status',
|
|
211
|
+
key: 'action',
|
|
212
|
+
});
|
|
213
|
+
return columns;
|
|
214
|
+
} else {
|
|
215
|
+
const columns: ObcTableColumn<ObcTableCellData, ObcTableRow>[] = [
|
|
216
|
+
{
|
|
217
|
+
label: 'Status',
|
|
218
|
+
key: 'status',
|
|
219
|
+
sortDirection: 'desc',
|
|
220
|
+
sortable: true,
|
|
221
|
+
compareFunction: (_a, _b, aRow, bRow) => {
|
|
222
|
+
const aAlert = this.alertByRowId.get(aRow.id);
|
|
223
|
+
const bAlert = this.alertByRowId.get(bRow.id);
|
|
224
|
+
if (aAlert && bAlert) {
|
|
225
|
+
return comparePriorityAlerts(aAlert, bAlert);
|
|
226
|
+
}
|
|
227
|
+
return 0;
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
label: 'ACK-status',
|
|
232
|
+
key: 'action',
|
|
233
|
+
dividerRight: true,
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
if (this.showTime) {
|
|
237
|
+
columns.push({
|
|
238
|
+
label: 'Activated',
|
|
239
|
+
key: 'time',
|
|
240
|
+
sortable: true,
|
|
241
|
+
compareFunction: (_a, _b, aRow, bRow) => {
|
|
242
|
+
const aAlert = this.alertByRowId.get(aRow.id);
|
|
243
|
+
const bAlert = this.alertByRowId.get(bRow.id);
|
|
244
|
+
if (aAlert && bAlert) {
|
|
245
|
+
const aTime = new Date(aAlert.time);
|
|
246
|
+
const bTime = new Date(bAlert.time);
|
|
247
|
+
return aTime.getTime() - bTime.getTime();
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
columns.push({
|
|
254
|
+
label: 'Tag ID',
|
|
255
|
+
key: 'tagId',
|
|
256
|
+
sortable: true,
|
|
257
|
+
compareFunction: (a, b) => {
|
|
258
|
+
const aText =
|
|
259
|
+
a?.type === ObcTableCellType.Regular ? String(a.text ?? '') : '';
|
|
260
|
+
const bText =
|
|
261
|
+
b?.type === ObcTableCellType.Regular ? String(b.text ?? '') : '';
|
|
262
|
+
return aText.localeCompare(bText);
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
return columns;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private get metadata() {
|
|
270
|
+
return getAlertListModeData(this.selectedMode);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private get filteredAlerts() {
|
|
274
|
+
return this.alerts.filter(this.metadata.filter);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private buildVisibleRows(): ObcTableRow[] {
|
|
278
|
+
const alerts = this.filteredAlerts;
|
|
279
|
+
const alertIds = new Set(alerts.map((alert) => alert.id));
|
|
280
|
+
const membersByGroupId = new Map<string, Alert[]>();
|
|
281
|
+
const roots: Alert[] = [];
|
|
282
|
+
for (const alert of alerts) {
|
|
283
|
+
const groupIds = (alert.memberOf ?? []).filter(
|
|
284
|
+
(groupId) => groupId !== alert.id && alertIds.has(groupId)
|
|
285
|
+
);
|
|
286
|
+
if (groupIds.length === 0) {
|
|
287
|
+
roots.push(alert);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
for (const groupId of groupIds) {
|
|
291
|
+
const members = membersByGroupId.get(groupId) ?? [];
|
|
292
|
+
members.push(alert);
|
|
293
|
+
membersByGroupId.set(groupId, members);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const reachable = new Set<string>();
|
|
298
|
+
const markReachable = (alert: Alert) => {
|
|
299
|
+
if (reachable.has(alert.id)) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
reachable.add(alert.id);
|
|
303
|
+
for (const member of membersByGroupId.get(alert.id) ?? []) {
|
|
304
|
+
markReachable(member);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
roots.forEach(markReachable);
|
|
308
|
+
for (const alert of alerts) {
|
|
309
|
+
if (!reachable.has(alert.id)) {
|
|
310
|
+
roots.push(alert);
|
|
311
|
+
markReachable(alert);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const rows: ObcTableRow[] = [];
|
|
316
|
+
this.alertByRowId = new Map();
|
|
317
|
+
const visit = (
|
|
318
|
+
alert: Alert,
|
|
319
|
+
level: number,
|
|
320
|
+
parentRowId: string | undefined,
|
|
321
|
+
ancestors: Set<string>
|
|
322
|
+
) => {
|
|
323
|
+
const segment = encodeURIComponent(alert.id);
|
|
324
|
+
const rowId =
|
|
325
|
+
parentRowId === undefined ? segment : `${parentRowId}/${segment}`;
|
|
326
|
+
const members = membersByGroupId.get(alert.id) ?? [];
|
|
327
|
+
// A member can name a group that is also its own descendant.
|
|
328
|
+
const expandableMembers = members.filter(
|
|
329
|
+
(member) => !ancestors.has(member.id)
|
|
330
|
+
);
|
|
331
|
+
const expanded = this.isExpanded(rowId);
|
|
332
|
+
|
|
333
|
+
this.alertByRowId.set(rowId, alert);
|
|
334
|
+
rows.push({
|
|
335
|
+
...this.buildRowCells(alert),
|
|
336
|
+
id: rowId,
|
|
337
|
+
parentId: parentRowId,
|
|
338
|
+
level,
|
|
339
|
+
expandable: expandableMembers.length > 0,
|
|
340
|
+
expanded,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (!expanded) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const nextAncestors = new Set(ancestors).add(alert.id);
|
|
347
|
+
for (const member of expandableMembers) {
|
|
348
|
+
visit(member, level + 1, rowId, nextAncestors);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
for (const alert of roots) {
|
|
353
|
+
visit(alert, 0, undefined, new Set());
|
|
354
|
+
}
|
|
355
|
+
return rows;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private buildRowCells(
|
|
359
|
+
alert: Alert
|
|
360
|
+
): Record<string, ObcTableCellData | undefined> {
|
|
361
|
+
let action: ObcTableCellData = {
|
|
362
|
+
type: ObcTableCellType.Regular,
|
|
363
|
+
};
|
|
364
|
+
if (
|
|
365
|
+
!isAcknowledged(alert) &&
|
|
366
|
+
isActive(alert) &&
|
|
367
|
+
requiresAcknowledgement(alert.type)
|
|
368
|
+
) {
|
|
369
|
+
if (alert.noAck) {
|
|
370
|
+
const icon = usesAlarmNoAckIcon(alert.type)
|
|
371
|
+
? html`<obi-alarm-noack-iec usecsscolor></obi-alarm-noack-iec>`
|
|
372
|
+
: html`<obi-warning-noack-iec usecsscolor></obi-warning-noack-iec>`;
|
|
373
|
+
action = {
|
|
374
|
+
type: ObcTableCellType.Regular,
|
|
375
|
+
largeIcon: true,
|
|
376
|
+
icon,
|
|
377
|
+
align: 'center',
|
|
378
|
+
};
|
|
379
|
+
} else {
|
|
380
|
+
action = {
|
|
381
|
+
type: ObcTableCellType.Button,
|
|
382
|
+
text: msg('ACK'),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const status: ObcTableCellData = {
|
|
388
|
+
type: ObcTableCellType.Regular,
|
|
389
|
+
largeIcon: true,
|
|
390
|
+
text: alert.text,
|
|
391
|
+
title: alert.source,
|
|
392
|
+
noWrap: true,
|
|
393
|
+
icon: html`<obc-alert-icon
|
|
394
|
+
.type=${alert.type}
|
|
395
|
+
.acknowledged=${isAcknowledged(alert)}
|
|
396
|
+
.active=${isActive(alert)}
|
|
397
|
+
></obc-alert-icon>`,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const time: ObcTableCellData | undefined = this.showTime
|
|
401
|
+
? {
|
|
402
|
+
type: ObcTableCellType.Regular,
|
|
403
|
+
text: this.timeFormatter(alert.time),
|
|
404
|
+
align: 'center',
|
|
405
|
+
neutral: true,
|
|
406
|
+
}
|
|
407
|
+
: undefined;
|
|
408
|
+
|
|
409
|
+
const tagId: ObcTableCellData | undefined = this.small
|
|
410
|
+
? undefined
|
|
411
|
+
: {
|
|
412
|
+
type: ObcTableCellType.Regular,
|
|
413
|
+
text: '#' + alert.id,
|
|
414
|
+
align: 'right',
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
status,
|
|
418
|
+
time,
|
|
419
|
+
action,
|
|
420
|
+
tagId,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
override render() {
|
|
425
|
+
const selectedList = this.metadata;
|
|
426
|
+
const data = this.buildVisibleRows();
|
|
427
|
+
|
|
428
|
+
return html`
|
|
429
|
+
<div class="wrapper ${this.small ? 'small' : ''}">
|
|
430
|
+
${data.length > 0
|
|
431
|
+
? html` <obc-table
|
|
432
|
+
class="alert-list"
|
|
433
|
+
.data=${data}
|
|
434
|
+
.columns=${this.columns}
|
|
435
|
+
.striped=${true}
|
|
436
|
+
.showHeader=${!this.small}
|
|
437
|
+
@row-click=${this.onRowClick}
|
|
438
|
+
@cell-button-click=${this.onCellButtonClick}
|
|
439
|
+
@expand-toggle=${this.onExpandToggle}
|
|
440
|
+
></obc-table>
|
|
441
|
+
<div class="spacer"></div>`
|
|
442
|
+
: html` <div class="empty-list">
|
|
443
|
+
<div class="icon">${selectedList.emptyIcon}</div>
|
|
444
|
+
<div class="empty-title">${selectedList.emptyTitle}</div>
|
|
445
|
+
</div>`}
|
|
446
|
+
</div>
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
static override styles = unsafeCSS(compentStyle);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
declare global {
|
|
454
|
+
interface HTMLElementTagNameMap {
|
|
455
|
+
'obc-alert-list-details-experimental': ObcAlertListDetailsExperimental;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import './icon-button.js';
|
|
3
|
+
import {ObcIconButton} from './icon-button.js';
|
|
4
|
+
import {render} from 'vitest-browser-lit';
|
|
5
|
+
import {html} from 'lit';
|
|
6
|
+
|
|
7
|
+
describe('obc-icon-button accessible name', () => {
|
|
8
|
+
async function setup(template: ReturnType<typeof html>) {
|
|
9
|
+
const screen = render(template);
|
|
10
|
+
const el = screen.baseElement.querySelector(
|
|
11
|
+
'obc-icon-button'
|
|
12
|
+
) as ObcIconButton;
|
|
13
|
+
await el.updateComplete;
|
|
14
|
+
return el;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function shadowButton(el: ObcIconButton) {
|
|
18
|
+
return el.shadowRoot!.querySelector('button')!;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
it('forwards the host aria-label to the shadow button', async () => {
|
|
22
|
+
const el = await setup(
|
|
23
|
+
html`<obc-icon-button aria-label="Play"></obc-icon-button>`
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(shadowButton(el).getAttribute('aria-label')).toBe('Play');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('follows later changes to the host aria-label', async () => {
|
|
30
|
+
const el = await setup(
|
|
31
|
+
html`<obc-icon-button aria-label="Play"></obc-icon-button>`
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
el.setAttribute('aria-label', 'Pause');
|
|
35
|
+
await el.updateComplete;
|
|
36
|
+
|
|
37
|
+
expect(shadowButton(el).getAttribute('aria-label')).toBe('Pause');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('does not forward aria-labelledby, which cannot resolve across the shadow boundary', async () => {
|
|
41
|
+
const el = await setup(
|
|
42
|
+
html`<obc-icon-button
|
|
43
|
+
aria-label="Play"
|
|
44
|
+
aria-labelledby="external-label"
|
|
45
|
+
></obc-icon-button>`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const button = shadowButton(el);
|
|
49
|
+
expect(button.hasAttribute('aria-labelledby')).toBe(false);
|
|
50
|
+
expect(button.getAttribute('aria-label')).toBe('Play');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -2,6 +2,7 @@ import {LitElement, html, nothing, unsafeCSS} from 'lit';
|
|
|
2
2
|
import {property} from 'lit/decorators.js';
|
|
3
3
|
import iconStyle from './icon-button.css?inline';
|
|
4
4
|
import {classMap} from 'lit/directives/class-map.js';
|
|
5
|
+
import {ifDefined} from 'lit/directives/if-defined.js';
|
|
5
6
|
import {customElement} from '../../decorator.js';
|
|
6
7
|
import {degToRad} from '../../svghelpers/math.js';
|
|
7
8
|
|
|
@@ -96,6 +97,7 @@ export enum IconButtonVariant {
|
|
|
96
97
|
* If undefined, no progress indicator is shown.
|
|
97
98
|
* @property hasLabel - If true, displays a label below the icon using the `label` slot.
|
|
98
99
|
* @property showDivider - If false, and cornerLeft or cornerRight is true, the divider is not shown.
|
|
100
|
+
* @property ariaLabel - Accessible name forwarded to the inner `<button>`, mapped to the `aria-label` attribute. `aria-labelledby` is not supported: ID references cannot cross the shadow boundary.
|
|
99
101
|
* @slot - Icon slot (default): Place an icon such as <obi-search> here.
|
|
100
102
|
* @slot label - Optional label shown below the icon when `hasLabel` is true.
|
|
101
103
|
* @fires click - Fired when the button is clicked (if not disabled).
|
|
@@ -130,6 +132,10 @@ export class ObcIconButton extends LitElement {
|
|
|
130
132
|
|
|
131
133
|
@property({type: Boolean, attribute: false}) showDivider = true;
|
|
132
134
|
|
|
135
|
+
// Reactive so a consumer swapping the name (Play → Pause) re-renders the shadow button.
|
|
136
|
+
@property({type: String, attribute: 'aria-label'})
|
|
137
|
+
override ariaLabel: string | null = null;
|
|
138
|
+
|
|
133
139
|
get progressSpinner() {
|
|
134
140
|
if (this.progress === undefined) {
|
|
135
141
|
return nothing;
|
|
@@ -200,6 +206,7 @@ export class ObcIconButton extends LitElement {
|
|
|
200
206
|
'hide-divider': !this.showDivider,
|
|
201
207
|
})}
|
|
202
208
|
?disabled=${this.disabled}
|
|
209
|
+
aria-label=${ifDefined(this.ariaLabel ?? undefined)}
|
|
203
210
|
part="wrapper"
|
|
204
211
|
>
|
|
205
212
|
${this.progress !== undefined ? this.progressSpinner : nothing}
|
|
@@ -197,6 +197,48 @@
|
|
|
197
197
|
flex-shrink: 0;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/* ---- Hierarchy ---- */
|
|
201
|
+
|
|
202
|
+
/*
|
|
203
|
+
* Indent gutter for a hierarchical row. One step per level plus one for
|
|
204
|
+
* the chevron itself, so a leaf and a group at the same level align.
|
|
205
|
+
*/
|
|
206
|
+
.row-expander {
|
|
207
|
+
--row-indent-step: var(
|
|
208
|
+
--menu-navigation-components-table-item-icon-size
|
|
209
|
+
);
|
|
210
|
+
/* Depth of this row; the row's inline style overrides it. */
|
|
211
|
+
--row-level: 0;
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
justify-content: flex-end;
|
|
215
|
+
flex-shrink: 0;
|
|
216
|
+
align-self: stretch;
|
|
217
|
+
width: calc((var(--row-level) + 1) * var(--row-indent-step));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.row-expander .chevron {
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
justify-content: center;
|
|
224
|
+
/* Pointer target floor, and the gutter is only one step wide. */
|
|
225
|
+
min-width: var(--row-indent-step);
|
|
226
|
+
min-height: var(--row-indent-step);
|
|
227
|
+
align-self: stretch;
|
|
228
|
+
color: var(--on-flat-neutral-color);
|
|
229
|
+
transition: transform 100ms ease-in-out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.row-expander .chevron.expanded {
|
|
233
|
+
transform: rotate(90deg);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@media (prefers-reduced-motion: reduce) {
|
|
237
|
+
.row-expander .chevron {
|
|
238
|
+
transition: none;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
200
242
|
&.large-icon .icon {
|
|
201
243
|
min-width: var(--menu-navigation-components-table-item-icon-size-large);
|
|
202
244
|
min-height: var(
|