@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
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
derivedParts,
|
|
4
4
|
HostElement,
|
|
5
5
|
isServerControlled,
|
|
6
|
-
|
|
6
|
+
type Ownership,
|
|
7
|
+
ownership,
|
|
7
8
|
SERVER_CONTROLLED,
|
|
8
9
|
setAttr,
|
|
9
|
-
setFlag,
|
|
10
|
-
stillMarked,
|
|
11
10
|
uniqueId,
|
|
12
11
|
WRITTEN,
|
|
13
12
|
warnAboutCopiedMark,
|
|
14
13
|
warnAboutMarkup,
|
|
14
|
+
watcher,
|
|
15
15
|
} from "../../host-element.js"
|
|
16
16
|
export const LT_TABS_TAG = "lt-tabs" as const
|
|
17
17
|
|
|
@@ -158,32 +158,6 @@ function nameAsReferenced(element: Element): string {
|
|
|
158
158
|
return flat(textOf(element, hiddenFromAT(element), true))
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/**
|
|
162
|
-
* What the web component last wrote on a node, and what the server last said.
|
|
163
|
-
*
|
|
164
|
-
* `aria-selected` on the tabs and `hidden` on the panels are both read and
|
|
165
|
-
* written. A naive read is the web component's echo of itself: bare markup
|
|
166
|
-
* got `aria-selected="true"` on the first tab from the web component, and
|
|
167
|
-
* when the server switched tab with a patch that only touched the panels,
|
|
168
|
-
* the web component read its earlier selection back and hid the panel the
|
|
169
|
-
* server had just shown. If something other than what the web component
|
|
170
|
-
* wrote is there, the server has touched it, and it is the server's word. If
|
|
171
|
-
* the same is there, what the server said last applies.
|
|
172
|
-
*/
|
|
173
|
-
type Word = { written: string | null; server: string | null }
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Whether a morph from the server has been there since the web component
|
|
177
|
-
* wrote the attribute: the mark it wrote is gone. Only a value that differed
|
|
178
|
-
* from the server's word was marked (`markWritten`); one that did not has
|
|
179
|
-
* no mark to lose, and its absence says nothing. Read as a sign of the
|
|
180
|
-
* server, every unmarked tab in a row of three released a click under
|
|
181
|
-
* `server-controlled` at once.
|
|
182
|
-
*/
|
|
183
|
-
function markGone(element: Element, name: string, word: Word): boolean {
|
|
184
|
-
return word.written !== word.server && !stillMarked(element, name)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
161
|
/**
|
|
188
162
|
* Keyboard behaviour in a tab list, and the wiring when the markup comes
|
|
189
163
|
* without it.
|
|
@@ -253,7 +227,43 @@ export class LtTabs extends HostElement {
|
|
|
253
227
|
*/
|
|
254
228
|
static observedAttributes = [SERVER_CONTROLLED, "activation"] as const
|
|
255
229
|
|
|
256
|
-
private
|
|
230
|
+
private readonly watch = watcher(
|
|
231
|
+
(records) => {
|
|
232
|
+
if (this.concerns(records)) this.sync()
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
childList: true,
|
|
236
|
+
subtree: true,
|
|
237
|
+
attributes: true,
|
|
238
|
+
// The panel's name is the tab's. idiomorph changes the text in a text
|
|
239
|
+
// node, with nothing else the observer sees.
|
|
240
|
+
characterData: true,
|
|
241
|
+
// `disabled` is included because the web component reads it: if a patch
|
|
242
|
+
// disables the selected tab, the tab stop moves to one that can take
|
|
243
|
+
// focus. `alt`, `aria-hidden` and `title` change the name of a tab.
|
|
244
|
+
attributeFilter: [
|
|
245
|
+
// A morph from the server removes the mark, and the web component
|
|
246
|
+
// puts it back: the same markup must give the same page.
|
|
247
|
+
WRITTEN,
|
|
248
|
+
"aria-selected",
|
|
249
|
+
"tabindex",
|
|
250
|
+
"hidden",
|
|
251
|
+
"disabled",
|
|
252
|
+
"aria-disabled",
|
|
253
|
+
// The wiring the web component sets when the markup came without it,
|
|
254
|
+
// and restores when a patch removes it.
|
|
255
|
+
"role",
|
|
256
|
+
"id",
|
|
257
|
+
"href",
|
|
258
|
+
"aria-controls",
|
|
259
|
+
"aria-labelledby",
|
|
260
|
+
"aria-label",
|
|
261
|
+
"alt",
|
|
262
|
+
"aria-hidden",
|
|
263
|
+
"title",
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
)
|
|
257
267
|
/**
|
|
258
268
|
* The panel the user's tab points to, remembered so a patch cannot take
|
|
259
269
|
* the selection.
|
|
@@ -288,9 +298,18 @@ export class LtTabs extends HostElement {
|
|
|
288
298
|
* link is a tab, from one where the server wrote the roles, where a link
|
|
289
299
|
* without a role is something else, such as a help link.
|
|
290
300
|
*/
|
|
291
|
-
private readonly roledByMe =
|
|
292
|
-
|
|
293
|
-
|
|
301
|
+
private readonly roledByMe = ownership()
|
|
302
|
+
/**
|
|
303
|
+
* What the web component wrote for `aria-selected` on each tab and `hidden`
|
|
304
|
+
* on each panel, and the server's word it wrote against. If something other
|
|
305
|
+
* than what it wrote is there, the server has touched it; if the same is
|
|
306
|
+
* there, what the server said last applies. A patch that only touched the
|
|
307
|
+
* panels once made the component read its earlier selection back and hide
|
|
308
|
+
* the panel the server had just shown. Each is replaced whole when the
|
|
309
|
+
* server's answer makes what is in the markup the starting point.
|
|
310
|
+
*/
|
|
311
|
+
private selectedOwn = ownership()
|
|
312
|
+
private hiddenOwn = ownership()
|
|
294
313
|
/**
|
|
295
314
|
* The form the server hides panels in, the last time it hid something:
|
|
296
315
|
* `""` from `lt.tabs()`, `"until-found"` or something else. The web
|
|
@@ -310,7 +329,7 @@ export class LtTabs extends HostElement {
|
|
|
310
329
|
*/
|
|
311
330
|
private readonly headingIds = new WeakMap<Element, string>()
|
|
312
331
|
/** The name the web component wrote in `aria-label` on a panel without a heading. */
|
|
313
|
-
private readonly
|
|
332
|
+
private readonly labelOwn = ownership()
|
|
314
333
|
/**
|
|
315
334
|
* The tab the user clicked under `server-controlled`, until the server
|
|
316
335
|
* answers.
|
|
@@ -374,41 +393,7 @@ export class LtTabs extends HostElement {
|
|
|
374
393
|
root.addEventListener("beforematch", this.handleBeforematch, true)
|
|
375
394
|
}
|
|
376
395
|
window.addEventListener("hashchange", this.handleHashchange)
|
|
377
|
-
this.
|
|
378
|
-
if (this.concerns(records)) this.sync()
|
|
379
|
-
})
|
|
380
|
-
this.observer.observe(this, {
|
|
381
|
-
childList: true,
|
|
382
|
-
subtree: true,
|
|
383
|
-
attributes: true,
|
|
384
|
-
// The panel's name is the tab's. idiomorph changes the text in a text
|
|
385
|
-
// node, with nothing else the observer sees.
|
|
386
|
-
characterData: true,
|
|
387
|
-
// `disabled` is included because the web component reads it: if a patch
|
|
388
|
-
// disables the selected tab, the tab stop moves to one that can take
|
|
389
|
-
// focus. `alt`, `aria-hidden` and `title` change the name of a tab.
|
|
390
|
-
attributeFilter: [
|
|
391
|
-
// A morph from the server removes the mark, and the web component
|
|
392
|
-
// puts it back: the same markup must give the same page.
|
|
393
|
-
WRITTEN,
|
|
394
|
-
"aria-selected",
|
|
395
|
-
"tabindex",
|
|
396
|
-
"hidden",
|
|
397
|
-
"disabled",
|
|
398
|
-
"aria-disabled",
|
|
399
|
-
// The wiring the web component sets when the markup came without it,
|
|
400
|
-
// and restores when a patch removes it.
|
|
401
|
-
"role",
|
|
402
|
-
"id",
|
|
403
|
-
"href",
|
|
404
|
-
"aria-controls",
|
|
405
|
-
"aria-labelledby",
|
|
406
|
-
"aria-label",
|
|
407
|
-
"alt",
|
|
408
|
-
"aria-hidden",
|
|
409
|
-
"title",
|
|
410
|
-
],
|
|
411
|
-
})
|
|
396
|
+
this.watch.start(this)
|
|
412
397
|
this.sync()
|
|
413
398
|
}
|
|
414
399
|
|
|
@@ -428,8 +413,7 @@ export class LtTabs extends HostElement {
|
|
|
428
413
|
}
|
|
429
414
|
|
|
430
415
|
disconnectedCallback(): void {
|
|
431
|
-
this.
|
|
432
|
-
this.observer = undefined
|
|
416
|
+
this.watch.stop()
|
|
433
417
|
this.removeEventListener("click", this.handleClick, true)
|
|
434
418
|
this.removeEventListener("keydown", this.handleKeydown, true)
|
|
435
419
|
document.removeEventListener("beforematch", this.handleBeforematch, true)
|
|
@@ -492,41 +476,32 @@ export class LtTabs extends HostElement {
|
|
|
492
476
|
|
|
493
477
|
/** The server's word about an attribute, against what the web component last wrote. */
|
|
494
478
|
private serverWord(
|
|
495
|
-
|
|
479
|
+
own: Ownership,
|
|
496
480
|
element: Element,
|
|
497
481
|
name: string,
|
|
498
482
|
): string | null {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (!known) {
|
|
502
|
-
warnAboutCopiedMark(this, element)
|
|
503
|
-
return now
|
|
504
|
-
}
|
|
505
|
-
// The server's when the value is another, or when a morph from the
|
|
506
|
-
// server has been there since the web component wrote it (`WRITTEN`).
|
|
507
|
-
return now !== known.written || !stillMarked(element, name)
|
|
508
|
-
? now
|
|
509
|
-
: known.server
|
|
483
|
+
if (!own.knows(element, name)) warnAboutCopiedMark(this, element)
|
|
484
|
+
return own.serverWord(element, name)
|
|
510
485
|
}
|
|
511
486
|
|
|
512
487
|
/** Is there something on a tab or a panel other than what the web component wrote? */
|
|
513
488
|
private touchedByOthers(): boolean {
|
|
514
489
|
return this.tabs.some((tab) => {
|
|
515
|
-
const selected = this.selectedMemory.get(tab)
|
|
516
490
|
if (
|
|
517
|
-
!selected ||
|
|
518
|
-
tab.getAttribute("aria-selected") !==
|
|
519
|
-
|
|
491
|
+
!this.selectedOwn.knows(tab, "aria-selected") ||
|
|
492
|
+
tab.getAttribute("aria-selected") !==
|
|
493
|
+
this.selectedOwn.written(tab, "aria-selected") ||
|
|
494
|
+
this.selectedOwn.lost(tab, "aria-selected")
|
|
520
495
|
) {
|
|
521
496
|
return true
|
|
522
497
|
}
|
|
523
498
|
const panel = this.panelFor(tab)
|
|
524
499
|
if (!panel) return false
|
|
525
|
-
const hidden = this.hiddenMemory.get(panel)
|
|
526
500
|
return (
|
|
527
|
-
!hidden ||
|
|
528
|
-
panel.getAttribute("hidden") !==
|
|
529
|
-
|
|
501
|
+
!this.hiddenOwn.knows(panel, "hidden") ||
|
|
502
|
+
panel.getAttribute("hidden") !==
|
|
503
|
+
this.hiddenOwn.written(panel, "hidden") ||
|
|
504
|
+
this.hiddenOwn.lost(panel, "hidden")
|
|
530
505
|
)
|
|
531
506
|
})
|
|
532
507
|
}
|
|
@@ -552,33 +527,37 @@ export class LtTabs extends HostElement {
|
|
|
552
527
|
const tabs = this.tabs
|
|
553
528
|
const serverSpoke = tabs.some((tab) => {
|
|
554
529
|
const now = tab.getAttribute("aria-selected")
|
|
555
|
-
const known = this.selectedMemory.get(tab)
|
|
556
530
|
// A new, bare tab has said nothing. Had it counted, the click's
|
|
557
531
|
// `aria-selected` was read as the server's, and a later move with
|
|
558
532
|
// `hidden` alone was undone.
|
|
559
|
-
return
|
|
560
|
-
? now !==
|
|
533
|
+
return this.selectedOwn.knows(tab, "aria-selected")
|
|
534
|
+
? now !== this.selectedOwn.written(tab, "aria-selected") ||
|
|
535
|
+
this.selectedOwn.lost(tab, "aria-selected")
|
|
561
536
|
: now !== null
|
|
562
537
|
})
|
|
563
|
-
this.
|
|
538
|
+
this.hiddenOwn = ownership()
|
|
564
539
|
if (serverSpoke) {
|
|
565
|
-
this.
|
|
540
|
+
this.selectedOwn = ownership()
|
|
566
541
|
return
|
|
567
542
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
543
|
+
// What stands there is the component's, against a server that said
|
|
544
|
+
// nothing: written again as it is, which changes no value, and marked.
|
|
545
|
+
for (const tab of tabs)
|
|
546
|
+
this.selectedOwn.write(
|
|
547
|
+
tab,
|
|
548
|
+
"aria-selected",
|
|
549
|
+
tab.getAttribute("aria-selected"),
|
|
550
|
+
{ server: null, mark: true },
|
|
551
|
+
)
|
|
573
552
|
}
|
|
574
553
|
|
|
575
554
|
private serverSelected(tab: Element): string | null {
|
|
576
|
-
return this.serverWord(this.
|
|
555
|
+
return this.serverWord(this.selectedOwn, tab, "aria-selected")
|
|
577
556
|
}
|
|
578
557
|
|
|
579
558
|
/** The server's word about `hidden` on the panel: the form, or `null` for visible. */
|
|
580
559
|
private serverHidden(panel: Element): string | null {
|
|
581
|
-
return this.serverWord(this.
|
|
560
|
+
return this.serverWord(this.hiddenOwn, panel, "hidden")
|
|
582
561
|
}
|
|
583
562
|
|
|
584
563
|
/**
|
|
@@ -810,16 +789,14 @@ export class LtTabs extends HostElement {
|
|
|
810
789
|
const named = new Set<HTMLElement>()
|
|
811
790
|
for (const tab of this.tabs) {
|
|
812
791
|
if (!tab.hasAttribute("role")) {
|
|
813
|
-
|
|
814
|
-
this.roledByMe.add(tab)
|
|
792
|
+
this.roledByMe.write(tab, "role", "tab")
|
|
815
793
|
}
|
|
816
794
|
const panel = this.panelFor(tab)
|
|
817
795
|
setAttr(tab, "aria-controls", panel?.id ?? linkTarget(tab))
|
|
818
796
|
if (!panel) continue
|
|
819
797
|
|
|
820
798
|
if (!panel.hasAttribute("role")) {
|
|
821
|
-
|
|
822
|
-
this.roledByMe.add(panel)
|
|
799
|
+
this.roledByMe.write(panel, "role", "tabpanel")
|
|
823
800
|
}
|
|
824
801
|
if (named.has(panel)) continue
|
|
825
802
|
named.add(panel)
|
|
@@ -844,7 +821,7 @@ export class LtTabs extends HostElement {
|
|
|
844
821
|
* `aria-label` applies.
|
|
845
822
|
*/
|
|
846
823
|
private name(panel: HTMLElement, tab: HTMLElement): void {
|
|
847
|
-
const ownLabel = this.
|
|
824
|
+
const ownLabel = this.labelOwn.written(panel, "aria-label")
|
|
848
825
|
const label = panel.getAttribute("aria-label")
|
|
849
826
|
if (label !== null && label !== ownLabel) return
|
|
850
827
|
|
|
@@ -887,15 +864,13 @@ export class LtTabs extends HostElement {
|
|
|
887
864
|
|
|
888
865
|
/** Writes the tab's name as `aria-label`, and remembers that the web component wrote it. */
|
|
889
866
|
private setOwnLabel(panel: HTMLElement, text: string): void {
|
|
890
|
-
|
|
891
|
-
this.labelByMe.set(panel, text)
|
|
867
|
+
this.labelOwn.write(panel, "aria-label", text)
|
|
892
868
|
}
|
|
893
869
|
|
|
894
870
|
/** Removes the fallback, when a name that exists has taken over. */
|
|
895
871
|
private dropOwnLabel(panel: HTMLElement): void {
|
|
896
|
-
if (!this.
|
|
897
|
-
|
|
898
|
-
this.labelByMe.delete(panel)
|
|
872
|
+
if (!this.labelOwn.knows(panel, "aria-label")) return
|
|
873
|
+
this.labelOwn.release(panel, "aria-label")
|
|
899
874
|
}
|
|
900
875
|
|
|
901
876
|
/** Does at least one of the ids point to an element that exists? */
|
|
@@ -911,7 +886,7 @@ export class LtTabs extends HostElement {
|
|
|
911
886
|
* anything.
|
|
912
887
|
*
|
|
913
888
|
* The server's word, not what is there: what the web component wrote does
|
|
914
|
-
* not count, see `
|
|
889
|
+
* not count, see `selectedOwn`. `aria-selected` first. When that is missing, as in
|
|
915
890
|
* markup written without JavaScript, `hidden` on the panels says the same:
|
|
916
891
|
* the first panel the server shows belongs to the selected tab. If the
|
|
917
892
|
* markup says nothing, or hides every panel, it is the first, which is what
|
|
@@ -1037,7 +1012,8 @@ export class LtTabs extends HostElement {
|
|
|
1037
1012
|
'selected", and the arrow keys do nothing.',
|
|
1038
1013
|
// An empty `<lt-tabs>` is a region the server has not filled yet,
|
|
1039
1014
|
// and that is not a mistake in the markup.
|
|
1040
|
-
() => this.
|
|
1015
|
+
() => this.tabs.length === 0,
|
|
1016
|
+
{ silentWhenEmpty: true },
|
|
1041
1017
|
)
|
|
1042
1018
|
return
|
|
1043
1019
|
}
|
|
@@ -1347,9 +1323,10 @@ export class LtTabs extends HostElement {
|
|
|
1347
1323
|
// The server's word is noted before the write, or the write was read as
|
|
1348
1324
|
// the server's in the next round.
|
|
1349
1325
|
const server = this.serverSelected(tab)
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1326
|
+
this.selectedOwn.write(tab, "aria-selected", String(chosen), {
|
|
1327
|
+
server,
|
|
1328
|
+
mark: true,
|
|
1329
|
+
})
|
|
1353
1330
|
setAttr(tab, "tabindex", i === stop ? "0" : "-1")
|
|
1354
1331
|
|
|
1355
1332
|
const panel = this.panelFor(tab)
|
|
@@ -1371,10 +1348,7 @@ export class LtTabs extends HostElement {
|
|
|
1371
1348
|
const server = this.serverHidden(panel)
|
|
1372
1349
|
const written = shown ? null : this.hiddenForm
|
|
1373
1350
|
if (panel !== this.revealing) {
|
|
1374
|
-
|
|
1375
|
-
else setAttr(panel, "hidden", written)
|
|
1376
|
-
this.hiddenMemory.set(panel, { written, server })
|
|
1377
|
-
markWritten(panel, "hidden", written !== server)
|
|
1351
|
+
this.hiddenOwn.write(panel, "hidden", written, { server, mark: true })
|
|
1378
1352
|
}
|
|
1379
1353
|
/*
|
|
1380
1354
|
* Only the selected panel is a tab stop, so Tab from the tab list goes
|
|
@@ -2,9 +2,11 @@ import {
|
|
|
2
2
|
addClass,
|
|
3
3
|
defineElement,
|
|
4
4
|
HostElement,
|
|
5
|
+
ownership,
|
|
5
6
|
place,
|
|
6
7
|
setAttr,
|
|
7
8
|
warnAboutMarkup,
|
|
9
|
+
watcher,
|
|
8
10
|
} from "../../host-element.js"
|
|
9
11
|
import { TERM_CLASS, TERM_ENTRY_CLASS } from "./term.js"
|
|
10
12
|
|
|
@@ -66,11 +68,12 @@ function fragmentsOf(word: Element): string[] {
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
|
-
* The `aria-details` the component wrote, per link
|
|
70
|
-
* there is the component's to change
|
|
71
|
-
* other value is the server's word, and
|
|
71
|
+
* The `aria-details` the component wrote, per link, shared by every term as
|
|
72
|
+
* the link may be. A value that is still there is the component's to change
|
|
73
|
+
* when `href` points elsewhere; any other value is the server's word, and
|
|
74
|
+
* stays.
|
|
72
75
|
*/
|
|
73
|
-
const
|
|
76
|
+
const detailsOwn = ownership()
|
|
74
77
|
|
|
75
78
|
type Shown = "no" | "hover" | "pinned"
|
|
76
79
|
|
|
@@ -114,7 +117,16 @@ export class LtTerm extends HostElement {
|
|
|
114
117
|
/** The entry this component shows as the card right now. */
|
|
115
118
|
private entry?: HTMLElement
|
|
116
119
|
private shown: Shown = "no"
|
|
117
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The link and the entry it points at. `class` too: the component sets
|
|
122
|
+
* `lt-term` on a link without it, and a morph takes it again.
|
|
123
|
+
*/
|
|
124
|
+
private readonly observe = watcher(() => this.sync(), {
|
|
125
|
+
childList: true,
|
|
126
|
+
subtree: true,
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: ["href", "class", "aria-details", "aria-expanded"],
|
|
129
|
+
})
|
|
118
130
|
/** Watches the entry while the card is open: a patch can strip what the component set on it. */
|
|
119
131
|
private outside?: MutationObserver
|
|
120
132
|
private visibility?: IntersectionObserver
|
|
@@ -183,23 +195,14 @@ export class LtTerm extends HostElement {
|
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
connectedCallback(): void {
|
|
186
|
-
this.
|
|
187
|
-
this.observer.observe(this, {
|
|
188
|
-
childList: true,
|
|
189
|
-
subtree: true,
|
|
190
|
-
attributes: true,
|
|
191
|
-
// `class` too: the component sets `lt-term` on a link without it, and
|
|
192
|
-
// a morph takes it again.
|
|
193
|
-
attributeFilter: ["href", "class", "aria-details", "aria-expanded"],
|
|
194
|
-
})
|
|
198
|
+
this.observe.start(this)
|
|
195
199
|
LtTerm.watch(this)
|
|
196
200
|
this.sync()
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
disconnectedCallback(): void {
|
|
200
204
|
this.close()
|
|
201
|
-
this.
|
|
202
|
-
this.observer = undefined
|
|
205
|
+
this.observe.stop()
|
|
203
206
|
LtTerm.unwatch(this)
|
|
204
207
|
this.detachWord()
|
|
205
208
|
}
|
|
@@ -241,7 +244,8 @@ export class LtTerm extends HostElement {
|
|
|
241
244
|
warnAboutMarkup(
|
|
242
245
|
this,
|
|
243
246
|
'found no link: an <a> with an href to the glossary entry, such as href="#term-signal". Without it there is no word to explain.',
|
|
244
|
-
() => this.
|
|
247
|
+
() => this.findWord() === null,
|
|
248
|
+
{ silentWhenEmpty: true },
|
|
245
249
|
)
|
|
246
250
|
this.close()
|
|
247
251
|
this.detachWord()
|
|
@@ -290,10 +294,12 @@ export class LtTerm extends HostElement {
|
|
|
290
294
|
// `aria-details` the server wrote is its word, also when it points
|
|
291
295
|
// elsewhere. What the component wrote follows the entry, and goes with it.
|
|
292
296
|
const details = word.getAttribute("aria-details")
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
+
if (
|
|
298
|
+
details === null ||
|
|
299
|
+
details === detailsOwn.written(word, "aria-details")
|
|
300
|
+
) {
|
|
301
|
+
if (entry) detailsOwn.write(word, "aria-details", entry.id)
|
|
302
|
+
else detailsOwn.release(word, "aria-details")
|
|
297
303
|
}
|
|
298
304
|
|
|
299
305
|
if (!entry) {
|