@lo-tek/interfaces 0.42.0 → 0.43.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/CHANGELOG.md +82 -0
- package/agent/astro.md +1 -1
- package/agent/bundles.md +1 -1
- package/agent/datastar.md +50 -50
- package/agent/html.md +53 -53
- package/agent/kotlin.md +4 -4
- package/agent/react.md +1 -1
- package/agent/templates.md +50 -50
- package/dist/components/frame/dialog/lt-dialog.d.ts +11 -3
- package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -1
- package/dist/components/frame/dialog/lt-dialog.js +59 -46
- package/dist/components/frame/dialog/lt-dialog.js.map +1 -1
- package/dist/components/frame/error-summary/lt-error-summary.d.ts +9 -6
- package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -1
- package/dist/components/frame/error-summary/lt-error-summary.js +38 -57
- package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -1
- package/dist/components/frame/field/lt-field.d.ts +17 -26
- package/dist/components/frame/field/lt-field.d.ts.map +1 -1
- package/dist/components/frame/field/lt-field.js +81 -143
- package/dist/components/frame/field/lt-field.js.map +1 -1
- package/dist/components/frame/popover/lt-popover.d.ts +1 -1
- package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
- package/dist/components/frame/popover/lt-popover.js +31 -33
- package/dist/components/frame/popover/lt-popover.js.map +1 -1
- package/dist/components/frame/session-timeout/lt-session-timeout.d.ts +14 -2
- package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -1
- package/dist/components/frame/session-timeout/lt-session-timeout.js +73 -46
- package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -1
- package/dist/components/frame/suggestion/lt-suggestion.d.ts +5 -7
- package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -1
- package/dist/components/frame/suggestion/lt-suggestion.js +57 -72
- package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -1
- package/dist/components/frame/tabs/lt-tabs.d.ts +14 -5
- package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -1
- package/dist/components/frame/tabs/lt-tabs.js +86 -106
- package/dist/components/frame/tabs/lt-tabs.js.map +1 -1
- package/dist/components/frame/term/lt-term.d.ts +5 -1
- package/dist/components/frame/term/lt-term.d.ts.map +1 -1
- package/dist/components/frame/term/lt-term.js +23 -21
- package/dist/components/frame/term/lt-term.js.map +1 -1
- package/dist/components/host-element.d.ts +137 -2
- package/dist/components/host-element.d.ts.map +1 -1
- package/dist/components/host-element.js +187 -4
- package/dist/components/host-element.js.map +1 -1
- package/dist/lotek.css +1 -1
- package/dist/react.d.ts +51 -437
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +48 -208
- package/dist/react.js.map +1 -1
- package/manifest/manifest.json +1 -1
- package/package.json +7 -3
- package/src/components/frame/dialog/lt-dialog.ts +56 -50
- package/src/components/frame/error-summary/lt-error-summary.ts +33 -46
- package/src/components/frame/field/lt-field.ts +99 -207
- package/src/components/frame/popover/lt-popover.ts +37 -35
- package/src/components/frame/session-timeout/lt-session-timeout.ts +88 -46
- package/src/components/frame/suggestion/lt-suggestion.ts +59 -73
- package/src/components/frame/tabs/lt-tabs.ts +96 -122
- package/src/components/frame/term/lt-term.ts +27 -21
- package/src/components/host-element.ts +306 -3
- package/src/react.ts +125 -240
- package/web-types.json +1 -1
|
@@ -2,11 +2,16 @@ import {
|
|
|
2
2
|
addClass,
|
|
3
3
|
defineElement,
|
|
4
4
|
HostElement,
|
|
5
|
+
hasName,
|
|
5
6
|
languageOf,
|
|
7
|
+
nameFromHeading,
|
|
8
|
+
ownership,
|
|
9
|
+
restoreModalOpen,
|
|
6
10
|
setAttr,
|
|
7
11
|
setText,
|
|
8
12
|
uniqueId,
|
|
9
13
|
warnAboutMarkup,
|
|
14
|
+
watcher,
|
|
10
15
|
whenUpgraded,
|
|
11
16
|
} from "../../host-element.js"
|
|
12
17
|
import {
|
|
@@ -218,6 +223,13 @@ export class LtSessionTimeout extends HostElement {
|
|
|
218
223
|
private expired = false
|
|
219
224
|
/** The id the component gave the heading, so the same one comes back. */
|
|
220
225
|
private titleId?: string
|
|
226
|
+
/**
|
|
227
|
+
* The `aria-labelledby` the component wrote on the dialog. Another value
|
|
228
|
+
* there is the renderer's, and stays; the component's own follows the
|
|
229
|
+
* heading, so a patch that replaces the heading does not leave the name
|
|
230
|
+
* pointing at an id that is gone.
|
|
231
|
+
*/
|
|
232
|
+
private readonly own = ownership()
|
|
221
233
|
/**
|
|
222
234
|
* The dialog the component opened, until `close` has arrived.
|
|
223
235
|
*
|
|
@@ -228,8 +240,47 @@ export class LtSessionTimeout extends HostElement {
|
|
|
228
240
|
* `session-extend`.
|
|
229
241
|
*/
|
|
230
242
|
private shownDialog?: HTMLDialogElement
|
|
231
|
-
/**
|
|
232
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Watches the subtree: `open` disappearing from a dialog in the top layer
|
|
245
|
+
* (see `repairOpen()`), and the role and the name the component wrote.
|
|
246
|
+
*/
|
|
247
|
+
private readonly watch = watcher(
|
|
248
|
+
(records) => {
|
|
249
|
+
// The number is text, and a tick that writes it is no reason to look.
|
|
250
|
+
const replaced = records.some((record) =>
|
|
251
|
+
[...record.addedNodes, ...record.removedNodes].some(
|
|
252
|
+
(node) => node instanceof Element,
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
const attributes = records.some((record) => record.type === "attributes")
|
|
256
|
+
if (!replaced && !attributes) return
|
|
257
|
+
this.repairOpen()
|
|
258
|
+
/*
|
|
259
|
+
* A morph of the open warning takes the role and the name the component
|
|
260
|
+
* wrote, since the renderer did not send them, and the screen reader had
|
|
261
|
+
* a modal dialog without a name until the warning closed. They are
|
|
262
|
+
* written again while it shows. Such a morph also removes `open`, which
|
|
263
|
+
* is enough to get here; the wiring's own attributes are watched for a
|
|
264
|
+
* morph that leaves `open` alone.
|
|
265
|
+
*/
|
|
266
|
+
// A heading the renderer replaced, too.
|
|
267
|
+
const dialog = this.dialog
|
|
268
|
+
if (dialog?.matches(":modal")) this.wire(dialog)
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
subtree: true,
|
|
272
|
+
childList: true,
|
|
273
|
+
attributes: true,
|
|
274
|
+
attributeFilter: [
|
|
275
|
+
"open",
|
|
276
|
+
"role",
|
|
277
|
+
"class",
|
|
278
|
+
"id",
|
|
279
|
+
"aria-labelledby",
|
|
280
|
+
"aria-label",
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
)
|
|
233
284
|
|
|
234
285
|
/** Seconds without activity before the warning appears. */
|
|
235
286
|
get warnAt(): number {
|
|
@@ -270,31 +321,7 @@ export class LtSessionTimeout extends HostElement {
|
|
|
270
321
|
* dialog, without the component having to hold on to a particular node.
|
|
271
322
|
*/
|
|
272
323
|
this.addEventListener("close", this.handleClose, true)
|
|
273
|
-
this.
|
|
274
|
-
this.repairOpen()
|
|
275
|
-
/*
|
|
276
|
-
* A morph of the open warning takes the role and the name the component
|
|
277
|
-
* wrote, since the renderer did not send them, and the screen reader had
|
|
278
|
-
* a modal dialog without a name until the warning closed. They are
|
|
279
|
-
* written again while it shows. Such a morph also removes `open`, which
|
|
280
|
-
* is enough to get here; the wiring's own attributes are watched for a
|
|
281
|
-
* morph that leaves `open` alone.
|
|
282
|
-
*/
|
|
283
|
-
const dialog = this.dialog
|
|
284
|
-
if (dialog?.matches(":modal")) this.wire(dialog)
|
|
285
|
-
})
|
|
286
|
-
this.openObserver.observe(this, {
|
|
287
|
-
subtree: true,
|
|
288
|
-
attributes: true,
|
|
289
|
-
attributeFilter: [
|
|
290
|
-
"open",
|
|
291
|
-
"role",
|
|
292
|
-
"class",
|
|
293
|
-
"id",
|
|
294
|
-
"aria-labelledby",
|
|
295
|
-
"aria-label",
|
|
296
|
-
],
|
|
297
|
-
})
|
|
324
|
+
this.watch.start(this)
|
|
298
325
|
this.ticker = window.setInterval(() => this.tick(), 1000)
|
|
299
326
|
this.validate()
|
|
300
327
|
|
|
@@ -321,8 +348,7 @@ export class LtSessionTimeout extends HostElement {
|
|
|
321
348
|
)
|
|
322
349
|
}
|
|
323
350
|
this.removeEventListener("close", this.handleClose, true)
|
|
324
|
-
this.
|
|
325
|
-
this.openObserver = undefined
|
|
351
|
+
this.watch.stop()
|
|
326
352
|
if (this.ticker) window.clearInterval(this.ticker)
|
|
327
353
|
this.ticker = undefined
|
|
328
354
|
}
|
|
@@ -380,15 +406,36 @@ export class LtSessionTimeout extends HostElement {
|
|
|
380
406
|
this,
|
|
381
407
|
"found no <dialog>. The warning cannot be shown, and the " +
|
|
382
408
|
"session expires without warning.",
|
|
383
|
-
() => this.
|
|
409
|
+
() => this.dialog === null,
|
|
410
|
+
{ silentWhenEmpty: true },
|
|
384
411
|
)
|
|
385
412
|
this.validateParts()
|
|
386
413
|
}
|
|
387
414
|
|
|
415
|
+
/** Says so when the dialog has no name, once the page has settled. */
|
|
416
|
+
private warnNameless(): void {
|
|
417
|
+
warnAboutMarkup(
|
|
418
|
+
this,
|
|
419
|
+
"the dialog has no name: it has no aria-labelledby, no aria-label " +
|
|
420
|
+
"and no heading, so a screen reader announces the warning " +
|
|
421
|
+
"without saying what it is about. Give it a heading, or " +
|
|
422
|
+
"aria-label on the <dialog>.",
|
|
423
|
+
() => {
|
|
424
|
+
const now = this.dialog
|
|
425
|
+
return (
|
|
426
|
+
now !== null &&
|
|
427
|
+
!hasName(now) &&
|
|
428
|
+
!now.querySelector(LT_SESSION_TIMEOUT_PARTS.title)
|
|
429
|
+
)
|
|
430
|
+
},
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
388
434
|
/** The parts inside the dialog. Each of them makes the warning worse when missing. */
|
|
389
435
|
private validateParts(): void {
|
|
390
436
|
const dialog = this.dialog
|
|
391
437
|
if (!dialog) return
|
|
438
|
+
this.warnNameless()
|
|
392
439
|
warnAboutMarkup(
|
|
393
440
|
this,
|
|
394
441
|
`found no .${SESSION_TIMEOUT_COUNT_CLASS} in the dialog. The user cannot see how much time is left.`,
|
|
@@ -418,19 +465,16 @@ export class LtSessionTimeout extends HostElement {
|
|
|
418
465
|
private wire(dialog: HTMLDialogElement): void {
|
|
419
466
|
addClass(dialog, SESSION_TIMEOUT_DIALOG_CLASS)
|
|
420
467
|
if (!dialog.hasAttribute("role")) setAttr(dialog, "role", "alertdialog")
|
|
421
|
-
|
|
422
|
-
dialog.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
setAttr(title, "id", this.titleId)
|
|
432
|
-
}
|
|
433
|
-
setAttr(dialog, "aria-labelledby", title.id)
|
|
468
|
+
const name = nameFromHeading(this.own, this, dialog, () => {
|
|
469
|
+
const title = dialog.querySelector(LT_SESSION_TIMEOUT_PARTS.title)
|
|
470
|
+
if (!title) return null
|
|
471
|
+
if (!title.id) {
|
|
472
|
+
this.titleId ??= uniqueId("lt-session-timeout-title")
|
|
473
|
+
setAttr(title, "id", this.titleId)
|
|
474
|
+
}
|
|
475
|
+
return title.id
|
|
476
|
+
})
|
|
477
|
+
if (name === "none") this.warnNameless()
|
|
434
478
|
}
|
|
435
479
|
|
|
436
480
|
/**
|
|
@@ -446,9 +490,7 @@ export class LtSessionTimeout extends HostElement {
|
|
|
446
490
|
* The same rule as in `<lt-dialog>`.
|
|
447
491
|
*/
|
|
448
492
|
private repairOpen(): void {
|
|
449
|
-
|
|
450
|
-
if (!dialog?.matches(":modal") || dialog.open) return
|
|
451
|
-
setAttr(dialog, "open", "")
|
|
493
|
+
restoreModalOpen(this.dialog)
|
|
452
494
|
}
|
|
453
495
|
|
|
454
496
|
private registerActivity = (): void => {
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
HostElement,
|
|
8
8
|
isServerControlled,
|
|
9
9
|
languageOf,
|
|
10
|
+
ownership,
|
|
10
11
|
rememberedId,
|
|
11
12
|
SERVER_CONTROLLED,
|
|
12
13
|
setAttr,
|
|
13
14
|
setFlag,
|
|
14
15
|
setText,
|
|
15
16
|
warnAboutMarkup,
|
|
17
|
+
watcher,
|
|
16
18
|
} from "../../host-element.js"
|
|
17
19
|
import {
|
|
18
20
|
SUGGESTION_EMPTY_CLASS,
|
|
@@ -119,7 +121,40 @@ export class LtSuggestion extends HostElement {
|
|
|
119
121
|
...COUNT_ATTRIBUTES,
|
|
120
122
|
] as const
|
|
121
123
|
|
|
122
|
-
private
|
|
124
|
+
private readonly watch = watcher(() => this.sync(), {
|
|
125
|
+
childList: true,
|
|
126
|
+
subtree: true,
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: [
|
|
129
|
+
"hidden",
|
|
130
|
+
"aria-expanded",
|
|
131
|
+
"aria-selected",
|
|
132
|
+
"aria-activedescendant",
|
|
133
|
+
// The wiring the web component fills in when the markup arrived without it.
|
|
134
|
+
"role",
|
|
135
|
+
"id",
|
|
136
|
+
"for",
|
|
137
|
+
"aria-controls",
|
|
138
|
+
"autocomplete",
|
|
139
|
+
"aria-autocomplete",
|
|
140
|
+
"class",
|
|
141
|
+
// The value field's `value` is an attribute, and a morph can write
|
|
142
|
+
// the server's empty value over the code the user chose.
|
|
143
|
+
"value",
|
|
144
|
+
// `form` on the field picks which form the reset belongs to.
|
|
145
|
+
"form",
|
|
146
|
+
// A field that becomes `readonly` or `disabled` must close the list.
|
|
147
|
+
"readonly",
|
|
148
|
+
"disabled",
|
|
149
|
+
"aria-disabled",
|
|
150
|
+
// The list sits in the top layer. A morph that removes `popover`
|
|
151
|
+
// closes the popover, and the web component sets it back.
|
|
152
|
+
"popover",
|
|
153
|
+
// The list's name, from the label.
|
|
154
|
+
"aria-labelledby",
|
|
155
|
+
"aria-label",
|
|
156
|
+
],
|
|
157
|
+
})
|
|
123
158
|
private control?: HTMLInputElement
|
|
124
159
|
private list?: HTMLElement
|
|
125
160
|
private empty?: HTMLElement
|
|
@@ -128,7 +163,7 @@ export class LtSuggestion extends HostElement {
|
|
|
128
163
|
/** The ids the web component created, per node, so a patch gets the same ones back. */
|
|
129
164
|
private readonly ids = new WeakMap<Element, string>()
|
|
130
165
|
/** The roles the web component set. See `derivedParts`. */
|
|
131
|
-
private readonly roledByMe =
|
|
166
|
+
private readonly roledByMe = ownership()
|
|
132
167
|
/** True while the web component dispatches events, so it does not answer itself. */
|
|
133
168
|
private choosing = false
|
|
134
169
|
/**
|
|
@@ -155,17 +190,12 @@ export class LtSuggestion extends HostElement {
|
|
|
155
190
|
private writtenCode?: string
|
|
156
191
|
/** The form listened to for `reset`. Follows the field, which a patch can move. */
|
|
157
192
|
private boundForm?: HTMLFormElement | null
|
|
158
|
-
/** The `for` the web component wrote on the label. */
|
|
159
|
-
private readonly forByMe = new WeakMap<Element, string>()
|
|
160
193
|
/**
|
|
161
|
-
*
|
|
162
|
-
* pointing to the label, or `aria-label` with its
|
|
163
|
-
*
|
|
194
|
+
* What the web component wrote on the label (`for`) and the list (its
|
|
195
|
+
* name: `aria-labelledby` pointing to the label, or `aria-label` with its
|
|
196
|
+
* text). Something else there is the server's, and stays.
|
|
164
197
|
*/
|
|
165
|
-
private readonly
|
|
166
|
-
Element,
|
|
167
|
-
{ name: "aria-labelledby" | "aria-label"; value: string }
|
|
168
|
-
>()
|
|
198
|
+
private readonly own = ownership()
|
|
169
199
|
/**
|
|
170
200
|
* Labels where the server wrote a `for` pointing to another id. Reported
|
|
171
201
|
* once; the memory is not reset, since the rewritten `for` cannot be told
|
|
@@ -214,41 +244,7 @@ export class LtSuggestion extends HostElement {
|
|
|
214
244
|
* The attributes are included, not just the children. Every write below
|
|
215
245
|
* compares first, or the observer would trigger itself forever.
|
|
216
246
|
*/
|
|
217
|
-
this.
|
|
218
|
-
this.observer.observe(this, {
|
|
219
|
-
childList: true,
|
|
220
|
-
subtree: true,
|
|
221
|
-
attributes: true,
|
|
222
|
-
attributeFilter: [
|
|
223
|
-
"hidden",
|
|
224
|
-
"aria-expanded",
|
|
225
|
-
"aria-selected",
|
|
226
|
-
"aria-activedescendant",
|
|
227
|
-
// The wiring the web component fills in when the markup arrived without it.
|
|
228
|
-
"role",
|
|
229
|
-
"id",
|
|
230
|
-
"for",
|
|
231
|
-
"aria-controls",
|
|
232
|
-
"autocomplete",
|
|
233
|
-
"aria-autocomplete",
|
|
234
|
-
"class",
|
|
235
|
-
// The value field's `value` is an attribute, and a morph can write
|
|
236
|
-
// the server's empty value over the code the user chose.
|
|
237
|
-
"value",
|
|
238
|
-
// `form` on the field picks which form the reset belongs to.
|
|
239
|
-
"form",
|
|
240
|
-
// A field that becomes `readonly` or `disabled` must close the list.
|
|
241
|
-
"readonly",
|
|
242
|
-
"disabled",
|
|
243
|
-
"aria-disabled",
|
|
244
|
-
// The list sits in the top layer. A morph that removes `popover`
|
|
245
|
-
// closes the popover, and the web component sets it back.
|
|
246
|
-
"popover",
|
|
247
|
-
// The list's name, from the label.
|
|
248
|
-
"aria-labelledby",
|
|
249
|
-
"aria-label",
|
|
250
|
-
],
|
|
251
|
-
})
|
|
247
|
+
this.watch.start(this)
|
|
252
248
|
this.sync()
|
|
253
249
|
window.addEventListener("resize", this.reposition)
|
|
254
250
|
window.addEventListener("scroll", this.reposition, true)
|
|
@@ -269,10 +265,10 @@ export class LtSuggestion extends HostElement {
|
|
|
269
265
|
/*
|
|
270
266
|
* The attribute can come and go while the page is alive, and then the
|
|
271
267
|
* list must be corrected at once rather than on the next keystroke.
|
|
272
|
-
* `this.
|
|
268
|
+
* `this.watch.watching` signals that the web component is in the document: the
|
|
273
269
|
* callback also fires during the upgrade, before the children exist.
|
|
274
270
|
*/
|
|
275
|
-
if (name === "prefiltered" && this.
|
|
271
|
+
if (name === "prefiltered" && this.watch.watching) {
|
|
276
272
|
/*
|
|
277
273
|
* After the whole update, as the other repairs: a morph sets the
|
|
278
274
|
* attribute before it has replaced the options, and the count was
|
|
@@ -281,7 +277,7 @@ export class LtSuggestion extends HostElement {
|
|
|
281
277
|
* match" to nobody's search.
|
|
282
278
|
*/
|
|
283
279
|
queueMicrotask(() => {
|
|
284
|
-
if (!this.
|
|
280
|
+
if (!this.watch.watching) return
|
|
285
281
|
// The count present when the attribute is set is the starting point,
|
|
286
282
|
// and nothing to announce. Without this the first change afterwards
|
|
287
283
|
// was taken as the starting point, and was lost.
|
|
@@ -366,13 +362,12 @@ export class LtSuggestion extends HostElement {
|
|
|
366
362
|
* gets the label's text as `aria-label`, without the field's value.
|
|
367
363
|
*/
|
|
368
364
|
private nameList(list: HTMLElement, control: HTMLInputElement): void {
|
|
369
|
-
const mine = this.listNameByMe.get(list)
|
|
370
365
|
const labelledby = list.getAttribute("aria-labelledby")
|
|
371
366
|
const label = list.getAttribute("aria-label")
|
|
372
367
|
const fromServer =
|
|
373
368
|
(labelledby !== null &&
|
|
374
|
-
|
|
375
|
-
(label !== null &&
|
|
369
|
+
this.own.written(list, "aria-labelledby") !== labelledby) ||
|
|
370
|
+
(label !== null && this.own.written(list, "aria-label") !== label)
|
|
376
371
|
if (fromServer) return
|
|
377
372
|
|
|
378
373
|
const labelElement =
|
|
@@ -388,14 +383,13 @@ export class LtSuggestion extends HostElement {
|
|
|
388
383
|
)
|
|
389
384
|
text += node.textContent ?? ""
|
|
390
385
|
text = text.replace(/\s+/g, " ").trim()
|
|
391
|
-
|
|
386
|
+
// One name at a time is the component's: writing one releases the other.
|
|
387
|
+
this.own.release(list, "aria-labelledby")
|
|
392
388
|
if (text === "") {
|
|
393
|
-
|
|
394
|
-
this.listNameByMe.delete(list)
|
|
389
|
+
this.own.release(list, "aria-label")
|
|
395
390
|
return
|
|
396
391
|
}
|
|
397
|
-
|
|
398
|
-
this.listNameByMe.set(list, { name: "aria-label", value: text })
|
|
392
|
+
this.own.write(list, "aria-label", text)
|
|
399
393
|
return
|
|
400
394
|
}
|
|
401
395
|
|
|
@@ -406,12 +400,8 @@ export class LtSuggestion extends HostElement {
|
|
|
406
400
|
rememberedId(this.ids, labelElement, "lt-suggestion-label"),
|
|
407
401
|
)
|
|
408
402
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.listNameByMe.set(list, {
|
|
412
|
-
name: "aria-labelledby",
|
|
413
|
-
value: labelElement.id,
|
|
414
|
-
})
|
|
403
|
+
this.own.release(list, "aria-label")
|
|
404
|
+
this.own.write(list, "aria-labelledby", labelElement.id)
|
|
415
405
|
}
|
|
416
406
|
|
|
417
407
|
/**
|
|
@@ -448,12 +438,11 @@ export class LtSuggestion extends HostElement {
|
|
|
448
438
|
if (
|
|
449
439
|
current !== null &&
|
|
450
440
|
current !== control.id &&
|
|
451
|
-
current !== this.
|
|
441
|
+
current !== this.own.written(label, "for")
|
|
452
442
|
) {
|
|
453
443
|
this.wrongFor.add(label)
|
|
454
444
|
}
|
|
455
|
-
|
|
456
|
-
this.forByMe.set(label, control.id)
|
|
445
|
+
this.own.write(label, "for", control.id)
|
|
457
446
|
}
|
|
458
447
|
warnAboutMarkup(
|
|
459
448
|
this,
|
|
@@ -567,8 +556,7 @@ export class LtSuggestion extends HostElement {
|
|
|
567
556
|
for (const option of this.options) {
|
|
568
557
|
addClass(option, SUGGESTION_OPTION_CLASS)
|
|
569
558
|
if (!option.hasAttribute("role")) {
|
|
570
|
-
|
|
571
|
-
this.roledByMe.add(option)
|
|
559
|
+
this.roledByMe.write(option, "role", "option")
|
|
572
560
|
}
|
|
573
561
|
if (!option.id) {
|
|
574
562
|
setAttr(
|
|
@@ -697,8 +685,7 @@ export class LtSuggestion extends HostElement {
|
|
|
697
685
|
window.removeEventListener("resize", this.reposition)
|
|
698
686
|
window.removeEventListener("scroll", this.reposition, true)
|
|
699
687
|
this.removeEventListener("focusout", this.handleFocusOut)
|
|
700
|
-
this.
|
|
701
|
-
this.observer = undefined
|
|
688
|
+
this.watch.stop()
|
|
702
689
|
this.unbind()
|
|
703
690
|
}
|
|
704
691
|
|
|
@@ -859,9 +846,8 @@ export class LtSuggestion extends HostElement {
|
|
|
859
846
|
"to, and neither filtering nor the arrow keys work.",
|
|
860
847
|
// An empty element is a region the server has not filled yet, and not
|
|
861
848
|
// an error in the markup.
|
|
862
|
-
() =>
|
|
863
|
-
|
|
864
|
-
this.querySelector(CONTROL_SELECTOR) === null,
|
|
849
|
+
() => this.querySelector(CONTROL_SELECTOR) === null,
|
|
850
|
+
{ silentWhenEmpty: true },
|
|
865
851
|
)
|
|
866
852
|
// The field is gone. Firefox sends no `focusout` when the focused node is
|
|
867
853
|
// removed, so the list would otherwise stay open without a field.
|