@fusion.dev/helix-headless 0.173.0 → 0.175.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/custom-elements.json +141 -43
- package/dist/app-header/root/app-header-root.d.ts +2 -1
- package/dist/app-header/root/app-header-root.js +6 -1
- package/dist/button/root/button-root.d.ts +6 -1
- package/dist/button/root/button-root.js +1 -0
- package/dist/command/root/command-root.d.ts +4 -2
- package/dist/command/root/command-root.js +1 -0
- package/dist/date-picker/root/date-picker-root.d.ts +6 -0
- package/dist/date-picker/root/date-picker-root.js +7 -1
- package/dist/dialog/form-method.d.ts +12 -0
- package/dist/dialog/form-method.js +13 -0
- package/dist/dialog/root/dialog-root.d.ts +1 -9
- package/dist/dialog/root/dialog-root.js +2 -11
- package/dist/drawer/root/drawer-root.d.ts +2 -0
- package/dist/drawer/root/drawer-root.js +6 -0
- package/dist/lightbox/item/lightbox-item.d.ts +1 -0
- package/dist/lightbox/root/lightbox-root.d.ts +25 -5
- package/dist/lightbox/root/lightbox-root.js +33 -10
- package/dist/notifications/group/notification-group.js +2 -2
- package/dist/notifications/notification/notification.js +4 -5
- package/dist/notifications/root/notifications-root.d.ts +4 -3
- package/dist/notifications/root/notifications-root.js +1 -1
- package/dist/pagination/root/pagination-root.d.ts +2 -1
- package/dist/pagination/root/pagination-root.js +6 -1
- package/dist/tooltip/root/tooltip-root.d.ts +5 -2
- package/dist/tooltip/root/tooltip-root.js +11 -11
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +50 -40
- package/types/vue.d.ts +6 -5
|
@@ -16,7 +16,9 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
|
|
|
16
16
|
*
|
|
17
17
|
* A lightbox shows one item at a time and has no scrolling track, so `active`
|
|
18
18
|
* holds the current item, as in `Tabs`. The arrow keys change the item while
|
|
19
|
-
* focus stays in the dialog.
|
|
19
|
+
* focus stays in the dialog. `active` names an item by its `value`, and an
|
|
20
|
+
* item with no `value` by its place, counted from 1, as in `Steps`, so a set
|
|
21
|
+
* written without values moves and `helix-activate` says `1`, `2` and so on.
|
|
20
22
|
*
|
|
21
23
|
* Set `controls` for the viewer's own previous, next and close controls, named
|
|
22
24
|
* by `previous-label`, `next-label` and `dismiss-label`, whose glyphs you can
|
|
@@ -33,9 +35,9 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
|
|
|
33
35
|
* `--lightbox-pan-x` and `--lightbox-pan-y`, written only while zoomed, and a
|
|
34
36
|
* swipe moves to the next item only while the item is at its own size. With
|
|
35
37
|
* `controls`, the viewer also draws zoom-out and zoom-in controls, for a
|
|
36
|
-
* pointer with no wheel and no second finger. Once a reader stops zooming,
|
|
37
|
-
* live region says the new scale in
|
|
38
|
-
* "Zoomed to 150%".
|
|
38
|
+
* pointer with no wheel and no second finger. Once a reader stops zooming, and
|
|
39
|
+
* at once for a key or a zoom control, the live region says the new scale in
|
|
40
|
+
* the words of `zoom-status-label`, such as "Zoomed to 150%".
|
|
39
41
|
*
|
|
40
42
|
* Each item is a slide: a `group` with the role description in
|
|
41
43
|
* `slide-role-label`, named by its `label` or by `slide-label`. A touch or a pen
|
|
@@ -232,7 +234,14 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
232
234
|
private readonly zoomer;
|
|
233
235
|
/** Writes the zoom on the viewport, and nothing at the item's own size. */
|
|
234
236
|
private drawZoom;
|
|
235
|
-
/**
|
|
237
|
+
/** Whether the zoom is changing by one discrete step, a key or a control, which is said at once. */
|
|
238
|
+
private zoomStep;
|
|
239
|
+
/**
|
|
240
|
+
* Says the new scale in the live region: a moment after the last change of
|
|
241
|
+
* a pinch, a drag or the wheel, and at once for a key or a zoom control,
|
|
242
|
+
* which is one change a reader is waiting to hear. Said only after the
|
|
243
|
+
* pause, a reader pressing `+` heard nothing until it ended.
|
|
244
|
+
*/
|
|
236
245
|
private sayZoom;
|
|
237
246
|
/**
|
|
238
247
|
* Zooms the shown item to `scale`, held between `1` and `max-zoom`, about
|
|
@@ -346,12 +355,23 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
|
|
|
346
355
|
private zoomIn;
|
|
347
356
|
/** Zooms out by as much, as `-` and the zoom-out control do. */
|
|
348
357
|
private zoomOut;
|
|
358
|
+
/** Changes the zoom by one discrete step, which `sayZoom` says at once. */
|
|
359
|
+
private stepZoom;
|
|
349
360
|
/** Home and End, which are an index rather than a step. */
|
|
350
361
|
private toEnd;
|
|
351
362
|
/** The item shown at the last update, whose zoom a different item does not keep. */
|
|
352
363
|
private zoomedItem;
|
|
353
364
|
/** The item on screen: the one `active` names, or the first where it names none. */
|
|
354
365
|
private get shown();
|
|
366
|
+
/** The item `active` names, by `value` or by place, or the first where it names none. */
|
|
367
|
+
private named;
|
|
368
|
+
/**
|
|
369
|
+
* Requests a new shown item, and returns whether it moved.
|
|
370
|
+
*
|
|
371
|
+
* Where `active` names nothing, it is first written with the item on
|
|
372
|
+
* screen, so `helix-activate` names the item you saw as `previous`.
|
|
373
|
+
*/
|
|
374
|
+
requestActive(next: string, cause?: string): boolean;
|
|
355
375
|
/**
|
|
356
376
|
* A press on the backdrop or on the empty area around the item.
|
|
357
377
|
*
|
|
@@ -12,6 +12,7 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
12
12
|
import { HelixHeadlessLightboxItem } from '../item/lightbox-item.js';
|
|
13
13
|
import { percentOf } from '../zoom.js';
|
|
14
14
|
import { ZoomController } from '../zoom-controller.js';
|
|
15
|
+
const keyOf = (item, index) => item.value || String(index + 1);
|
|
15
16
|
let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerActive(MixinControllerOpen(HelixElement)) {
|
|
16
17
|
constructor() {
|
|
17
18
|
super(...arguments);
|
|
@@ -76,6 +77,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
76
77
|
onChange: (scale, pan) => this.drawZoom(scale, pan),
|
|
77
78
|
surface: () => this.partNamed('viewport'),
|
|
78
79
|
});
|
|
80
|
+
this.zoomStep = false;
|
|
79
81
|
this.fullscreen = new FullscreenController(this, (full) => {
|
|
80
82
|
this.filled = full;
|
|
81
83
|
});
|
|
@@ -164,7 +166,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
164
166
|
this.zoomSaid.cancel();
|
|
165
167
|
return;
|
|
166
168
|
}
|
|
167
|
-
this.zoomSaid.start(400, () => {
|
|
169
|
+
this.zoomSaid.start(this.zoomStep ? 0 : 400, () => {
|
|
168
170
|
this.announcement = formatWords(this, 'zoomStatusLabel', {
|
|
169
171
|
percent: percentOf(this.zoomScale),
|
|
170
172
|
scale: this.zoomScale,
|
|
@@ -225,7 +227,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
225
227
|
: Math.min(Math.max(from + by, 0), last);
|
|
226
228
|
const target = items[next];
|
|
227
229
|
if (target && target !== shown) {
|
|
228
|
-
this.requestActive(target
|
|
230
|
+
this.requestActive(keyOf(target, next));
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
zoomKey(key) {
|
|
@@ -239,7 +241,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
239
241
|
this.zoomOut();
|
|
240
242
|
}
|
|
241
243
|
else if (key === '0') {
|
|
242
|
-
this.zoomer.reset();
|
|
244
|
+
this.stepZoom(() => this.zoomer.reset());
|
|
243
245
|
}
|
|
244
246
|
else {
|
|
245
247
|
return false;
|
|
@@ -266,19 +268,40 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
266
268
|
return true;
|
|
267
269
|
}
|
|
268
270
|
zoomIn() {
|
|
269
|
-
this.zoomer.zoomTo(this.zoomer.scale * 1.5);
|
|
271
|
+
this.stepZoom(() => this.zoomer.zoomTo(this.zoomer.scale * 1.5));
|
|
270
272
|
}
|
|
271
273
|
zoomOut() {
|
|
272
|
-
this.zoomer.zoomTo(this.zoomer.scale / 1.5);
|
|
274
|
+
this.stepZoom(() => this.zoomer.zoomTo(this.zoomer.scale / 1.5));
|
|
275
|
+
}
|
|
276
|
+
stepZoom(change) {
|
|
277
|
+
this.zoomStep = true;
|
|
278
|
+
try {
|
|
279
|
+
change();
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
this.zoomStep = false;
|
|
283
|
+
}
|
|
273
284
|
}
|
|
274
285
|
toEnd(at) {
|
|
275
286
|
const target = this.items[at];
|
|
276
287
|
if (target && target !== this.shown) {
|
|
277
|
-
this.requestActive(target
|
|
288
|
+
this.requestActive(keyOf(target, at));
|
|
278
289
|
}
|
|
279
290
|
}
|
|
280
291
|
get shown() {
|
|
281
|
-
return
|
|
292
|
+
return this.named(this.items);
|
|
293
|
+
}
|
|
294
|
+
named(items) {
|
|
295
|
+
const keyed = items.map((item, index) => ({ item, value: keyOf(item, index) }));
|
|
296
|
+
return resolveActiveItem(this.active ?? '', keyed)?.item;
|
|
297
|
+
}
|
|
298
|
+
requestActive(next, cause) {
|
|
299
|
+
const items = this.items;
|
|
300
|
+
const shown = this.active === '' ? this.shown : undefined;
|
|
301
|
+
if (shown) {
|
|
302
|
+
this.active = keyOf(shown, items.indexOf(shown));
|
|
303
|
+
}
|
|
304
|
+
return super.requestActive(next, cause);
|
|
282
305
|
}
|
|
283
306
|
dragged(event) {
|
|
284
307
|
const from = this.pressedAt;
|
|
@@ -348,7 +371,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
348
371
|
}
|
|
349
372
|
mark() {
|
|
350
373
|
const items = this.items;
|
|
351
|
-
const current =
|
|
374
|
+
const current = this.named(items);
|
|
352
375
|
items.forEach((item, at) => {
|
|
353
376
|
item.current = item === current;
|
|
354
377
|
whenHydrated(item, () => {
|
|
@@ -376,7 +399,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
376
399
|
return;
|
|
377
400
|
}
|
|
378
401
|
const items = this.items;
|
|
379
|
-
const current =
|
|
402
|
+
const current = this.named(items);
|
|
380
403
|
const at = current ? items.indexOf(current) : -1;
|
|
381
404
|
this.announcement =
|
|
382
405
|
at === -1 || !current
|
|
@@ -389,7 +412,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
|
|
|
389
412
|
}
|
|
390
413
|
place() {
|
|
391
414
|
const items = this.items;
|
|
392
|
-
const current =
|
|
415
|
+
const current = this.named(items);
|
|
393
416
|
return current
|
|
394
417
|
? formatWords(this, 'statusLabel', {
|
|
395
418
|
count: items.length,
|
|
@@ -41,7 +41,7 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
|
|
|
41
41
|
}
|
|
42
42
|
willUpdate(changed) {
|
|
43
43
|
super.willUpdate(changed);
|
|
44
|
-
hostAria(this.internals, { role: '
|
|
44
|
+
hostAria(this.internals, { label: this.label, role: 'article' });
|
|
45
45
|
}
|
|
46
46
|
firstUpdated(changed) {
|
|
47
47
|
super.firstUpdated(changed);
|
|
@@ -92,7 +92,7 @@ let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extend
|
|
|
92
92
|
${count}`}
|
|
93
93
|
<span part="actions"><slot name="actions"></slot></span>
|
|
94
94
|
</div>
|
|
95
|
-
<div id=${ITEMS_ID} part="items" role="
|
|
95
|
+
<div id=${ITEMS_ID} part="items" role="feed" aria-labelledby=${LABEL_ID}>
|
|
96
96
|
<slot></slot>
|
|
97
97
|
</div>`;
|
|
98
98
|
}
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, deepActiveElement, deepContains, focusableElements, HelixElement, HelixReadEvent,
|
|
7
|
+
import { customElement, deepActiveElement, deepContains, focusableElements, HelixElement, HelixReadEvent, MixinDismissible, requestChange, slotFilled, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -61,9 +61,6 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
|
|
|
61
61
|
}
|
|
62
62
|
willUpdate(changed) {
|
|
63
63
|
super.willUpdate(changed);
|
|
64
|
-
hostAria(this.internals, { role: 'listitem' });
|
|
65
|
-
this.internals.ariaPosInSet = this.position > 0 ? String(this.position) : null;
|
|
66
|
-
this.internals.ariaSetSize = this.setSize === 0 ? null : String(this.setSize);
|
|
67
64
|
this.toggleState('read', this.read);
|
|
68
65
|
}
|
|
69
66
|
updated(changed) {
|
|
@@ -116,8 +113,10 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends MixinDis
|
|
|
116
113
|
render() {
|
|
117
114
|
return html `<div
|
|
118
115
|
part="item"
|
|
119
|
-
role="
|
|
116
|
+
role="article"
|
|
120
117
|
aria-labelledby="label"
|
|
118
|
+
aria-posinset=${this.position > 0 ? String(this.position) : nothing}
|
|
119
|
+
aria-setsize=${this.setSize === 0 ? nothing : String(this.setSize)}
|
|
121
120
|
tabindex=${this.stop ? 0 : -1}
|
|
122
121
|
>
|
|
123
122
|
<span part="media"><slot name="media"></slot></span>
|
|
@@ -21,8 +21,9 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
21
21
|
* the items and written to an attribute, so a stylesheet and a badge can both
|
|
22
22
|
* use it without counting.
|
|
23
23
|
*
|
|
24
|
-
* **The feed is one `Tab` stop for its rows.**
|
|
25
|
-
*
|
|
24
|
+
* **The feed is one `Tab` stop for its rows.** It is an APG feed: the list is
|
|
25
|
+
* a `feed`, busy while `loading` is set, and each row is an `article` that
|
|
26
|
+
* takes focus itself and is read by its label. `ArrowDown` and `ArrowUp` move to the next and the
|
|
26
27
|
* previous row, `Home` and `End` to the first and the last, and `PageDown` and
|
|
27
28
|
* `PageUp` move a row on or back from anywhere inside one, as in the APG feed
|
|
28
29
|
* pattern. The controls inside a row stay in the `Tab` order. Set `total` when
|
|
@@ -77,7 +78,7 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
77
78
|
* @csspart error - Shown while `load-error` is set, under any rows there are, in place of the empty and loading lines.
|
|
78
79
|
* @csspart footer - The region below the list.
|
|
79
80
|
* @csspart header - The region above the list.
|
|
80
|
-
* @csspart list - The list itself.
|
|
81
|
+
* @csspart list - The list itself, a `feed` whose rows are its articles.
|
|
81
82
|
* @csspart live - A copy of the empty, loading and error messages, in a live region that screen readers announce when they change. Visually hidden in the styled tier.
|
|
82
83
|
* @csspart loading - Shown while the feed is loading.
|
|
83
84
|
* @csspart mark-all - The control that marks everything read.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/**
|
|
4
4
|
* @summary Controls for moving between the pages of a long set of results.
|
|
5
5
|
*
|
|
@@ -63,6 +63,7 @@ export declare class HelixHeadlessPagination extends HelixElement {
|
|
|
63
63
|
* `registerWords('de', { 'helix-pagination': { nextLabel: 'Nächste Seite' } })`.
|
|
64
64
|
*/
|
|
65
65
|
static words: string;
|
|
66
|
+
static styles: CSSResultGroup;
|
|
66
67
|
/**
|
|
67
68
|
* How many pages to show at each end, whatever the current one is.
|
|
68
69
|
*/
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { customElement, deepActiveElement, fillTemplate, formatNumbers, formatWords, HelixActivateEvent, HelixElement, HelixNavigateEvent, hostAria, requestChange, words, wordsLocale, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretDoubleLeft, caretDoubleRight, caretLeft, caretRight, } from '@fusion.dev/helix-icons';
|
|
9
|
-
import { html, nothing } from 'lit';
|
|
9
|
+
import { css, html, nothing, } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { repeat } from 'lit/directives/repeat.js';
|
|
12
12
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
@@ -48,6 +48,11 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
static { this.words = 'helix-pagination'; }
|
|
51
|
+
static { this.styles = css `
|
|
52
|
+
[part='list'] {
|
|
53
|
+
list-style: none;
|
|
54
|
+
}
|
|
55
|
+
`; }
|
|
51
56
|
get page() {
|
|
52
57
|
const asked = this.pageState ?? this.defaultPage;
|
|
53
58
|
return Math.min(this.count, Math.max(1, Math.floor(asked) || 1));
|
|
@@ -28,8 +28,11 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
|
|
|
28
28
|
* markup is described when you first point at it or focus it, so a screen
|
|
29
29
|
* reader in reading mode meets it with no description until then. Where the
|
|
30
30
|
* browser has no element references, nothing links the two, and the tooltip
|
|
31
|
-
* warns once on first open. The tooltip can also wrap its trigger
|
|
32
|
-
*
|
|
31
|
+
* warns once on first open. The tooltip can also wrap its trigger. A Helix
|
|
32
|
+
* component inside is described at once, and a plain element as in the `for`
|
|
33
|
+
* form: when you first point at it or focus it, or at rest where it names the
|
|
34
|
+
* tooltip's `id` in its own `aria-describedby`, which a reference to the
|
|
35
|
+
* element around it allows. In the
|
|
33
36
|
* `for` form the tooltip itself is out of the accessibility tree while its
|
|
34
37
|
* bubble is down, so you meet its words once, as the trigger's description.
|
|
35
38
|
* A trigger whose name is already the tooltip's words, such as an icon button
|
|
@@ -251,21 +251,27 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
|
|
|
251
251
|
if (this.linked) {
|
|
252
252
|
return;
|
|
253
253
|
}
|
|
254
|
+
const trigger = this.for === '' ? this.trigger : this.named;
|
|
255
|
+
const names = `${trigger?.getAttribute('aria-describedby') ?? ''} ${trigger?.getAttribute('aria-labelledby') ?? ''}`
|
|
256
|
+
.split(/\s+/)
|
|
257
|
+
.filter(Boolean);
|
|
258
|
+
if (this.id !== '' && names.includes(this.id)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
254
261
|
if (this.for === '') {
|
|
255
262
|
if (this.triggerSays(this.describedText)) {
|
|
256
263
|
return;
|
|
257
264
|
}
|
|
258
265
|
reportRuntime({
|
|
259
266
|
kind: 'wrapped-trigger',
|
|
260
|
-
message: `<${this.localName}>
|
|
261
|
-
|
|
262
|
-
'
|
|
263
|
-
'the trigger
|
|
267
|
+
message: `<${this.localName}> opened before its trigger was described, so a screen reader ` +
|
|
268
|
+
'reading the page meets the trigger without its text. Give this tooltip an `id` and ' +
|
|
269
|
+
'add `aria-describedby="<that id>"` to the trigger inside it. Written in your own ' +
|
|
270
|
+
'markup it describes the trigger at rest and survives server rendering.',
|
|
264
271
|
tag: this.localName,
|
|
265
272
|
});
|
|
266
273
|
return;
|
|
267
274
|
}
|
|
268
|
-
const trigger = this.named;
|
|
269
275
|
if (!trigger) {
|
|
270
276
|
reportRuntime({
|
|
271
277
|
kind: 'missing-trigger',
|
|
@@ -276,12 +282,6 @@ let HelixHeadlessTooltip = class HelixHeadlessTooltip extends MixinControllerOpe
|
|
|
276
282
|
});
|
|
277
283
|
return;
|
|
278
284
|
}
|
|
279
|
-
const names = `${trigger.getAttribute('aria-describedby') ?? ''} ${trigger.getAttribute('aria-labelledby') ?? ''}`
|
|
280
|
-
.split(/\s+/)
|
|
281
|
-
.filter(Boolean);
|
|
282
|
-
if (this.id !== '' && names.includes(this.id)) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
285
|
reportRuntime({
|
|
286
286
|
kind: 'unlinked-trigger',
|
|
287
287
|
message: `<${this.localName} for="${this.for}"> is not read out with its trigger. The link is ` +
|