@lo-tek/interfaces 0.37.0 → 0.39.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 +160 -2
- package/README.md +4 -3
- package/agent/astro.md +1 -1
- package/agent/bundles.md +4 -2
- package/agent/datastar.md +48 -48
- package/agent/html.md +51 -51
- package/agent/react.md +6 -5
- package/agent/templates.md +49 -49
- package/core/lotek-core.wasm +0 -0
- package/core/lotek.wasm +0 -0
- package/dist/components/default-texts.d.ts +6 -0
- package/dist/components/default-texts.d.ts.map +1 -1
- package/dist/components/default-texts.js +6 -0
- package/dist/components/default-texts.js.map +1 -1
- package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -1
- package/dist/components/frame/dialog/lt-dialog.js +5 -2
- package/dist/components/frame/dialog/lt-dialog.js.map +1 -1
- package/dist/components/frame/error-summary/error-summary.d.ts +15 -2
- package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -1
- package/dist/components/frame/error-summary/error-summary.js +33 -11
- package/dist/components/frame/error-summary/error-summary.js.map +1 -1
- package/dist/components/frame/error-summary/lt-error-summary.d.ts +11 -0
- package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -1
- package/dist/components/frame/error-summary/lt-error-summary.js +114 -8
- package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -1
- package/dist/components/frame/field/lt-field.d.ts.map +1 -1
- package/dist/components/frame/field/lt-field.js +34 -14
- package/dist/components/frame/field/lt-field.js.map +1 -1
- package/dist/components/frame/popover/lt-popover.d.ts +6 -2
- package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
- package/dist/components/frame/popover/lt-popover.js +44 -14
- package/dist/components/frame/popover/lt-popover.js.map +1 -1
- package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -1
- package/dist/components/frame/session-timeout/lt-session-timeout.js +43 -6
- package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -1
- package/dist/components/frame/suggestion/lt-suggestion.d.ts +8 -2
- package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -1
- package/dist/components/frame/suggestion/lt-suggestion.js +56 -37
- package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -1
- package/dist/components/frame/tabs/lt-tabs.d.ts +32 -10
- package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -1
- package/dist/components/frame/tabs/lt-tabs.js +85 -21
- package/dist/components/frame/tabs/lt-tabs.js.map +1 -1
- package/dist/components/host-element.d.ts +52 -0
- package/dist/components/host-element.d.ts.map +1 -1
- package/dist/components/host-element.js +164 -1
- package/dist/components/host-element.js.map +1 -1
- package/dist/components/standalone/connection-status/connection-status.d.ts +5 -1
- package/dist/components/standalone/connection-status/connection-status.d.ts.map +1 -1
- package/dist/components/standalone/connection-status/connection-status.js +5 -1
- package/dist/components/standalone/connection-status/connection-status.js.map +1 -1
- package/dist/components/standalone/connection-status/lt-connection-status.d.ts +11 -6
- package/dist/components/standalone/connection-status/lt-connection-status.d.ts.map +1 -1
- package/dist/components/standalone/connection-status/lt-connection-status.js +34 -10
- package/dist/components/standalone/connection-status/lt-connection-status.js.map +1 -1
- package/dist/diagnostics/core.d.ts +34 -0
- package/dist/diagnostics/core.d.ts.map +1 -1
- package/dist/diagnostics/core.js +14 -2
- package/dist/diagnostics/core.js.map +1 -1
- package/dist/lotek.css +1 -1
- package/dist/lt.d.ts +1 -1
- package/dist/react.d.ts +2 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/vocabulary/classes.d.ts.map +1 -1
- package/dist/vocabulary/classes.js +34 -0
- package/dist/vocabulary/classes.js.map +1 -1
- package/dist/vocabulary/elements.d.ts.map +1 -1
- package/dist/vocabulary/elements.js +87 -7
- package/dist/vocabulary/elements.js.map +1 -1
- package/dist/vocabulary/types.d.ts +60 -0
- package/dist/vocabulary/types.d.ts.map +1 -1
- package/live-templates.xml +3 -2
- package/manifest/manifest.json +172 -8
- package/manifest/manifest.schema.json +80 -0
- package/package.json +2 -1
- package/src/components/default-texts.ts +7 -0
- package/src/components/frame/dialog/lt-dialog.ts +5 -2
- package/src/components/frame/error-summary/error-summary.ts +45 -11
- package/src/components/frame/error-summary/lt-error-summary.ts +121 -7
- package/src/components/frame/field/lt-field.ts +65 -28
- package/src/components/frame/popover/lt-popover.ts +43 -12
- package/src/components/frame/session-timeout/lt-session-timeout.ts +40 -6
- package/src/components/frame/suggestion/lt-suggestion.ts +57 -36
- package/src/components/frame/tabs/lt-tabs.ts +114 -38
- package/src/components/host-element.ts +190 -1
- package/src/components/standalone/connection-status/connection-status.ts +7 -1
- package/src/components/standalone/connection-status/lt-connection-status.ts +36 -10
- package/src/diagnostics/core.ts +19 -2
- package/src/vocabulary/classes.ts +34 -0
- package/src/vocabulary/elements.ts +87 -7
- package/src/vocabulary/types.ts +56 -0
- package/web-types.json +1 -1
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
HostElement,
|
|
8
8
|
isServerControlled,
|
|
9
9
|
languageOf,
|
|
10
|
+
rememberedId,
|
|
10
11
|
SERVER_CONTROLLED,
|
|
11
12
|
setAttr,
|
|
12
13
|
setFlag,
|
|
13
14
|
setText,
|
|
14
|
-
uniqueId,
|
|
15
15
|
warnAboutMarkup,
|
|
16
16
|
} from "../../host-element.js"
|
|
17
17
|
import {
|
|
@@ -53,6 +53,8 @@ export const LT_SUGGESTION_PARTS = {
|
|
|
53
53
|
status: "[role='status']",
|
|
54
54
|
/** The hidden field that receives the code of the option the user chose. */
|
|
55
55
|
value: `input.${SUGGESTION_VALUE_CLASS}`,
|
|
56
|
+
/** The label, which gets `for` from the field's id. */
|
|
57
|
+
label: "label",
|
|
56
58
|
} as const
|
|
57
59
|
|
|
58
60
|
const CONTROL_SELECTOR = LT_SUGGESTION_PARTS.control
|
|
@@ -270,11 +272,23 @@ export class LtSuggestion extends HostElement {
|
|
|
270
272
|
* callback also fires during the upgrade, before the children exist.
|
|
271
273
|
*/
|
|
272
274
|
if (name === "prefiltered" && this.observer) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
/*
|
|
276
|
+
* After the whole update, as the other repairs: a morph sets the
|
|
277
|
+
* attribute before it has replaced the options, and the count was
|
|
278
|
+
* the old list's. And through `repair()`, which filters only a list
|
|
279
|
+
* that stays open: the filter announces, and a closed list said "One
|
|
280
|
+
* match" to nobody's search.
|
|
281
|
+
*/
|
|
282
|
+
queueMicrotask(() => {
|
|
283
|
+
if (!this.observer) return
|
|
284
|
+
// The count present when the attribute is set is the starting point,
|
|
285
|
+
// and nothing to announce. Without this the first change afterwards
|
|
286
|
+
// was taken as the starting point, and was lost.
|
|
287
|
+
this.announcedCount = this.prefiltered ? this.visible.length : undefined
|
|
288
|
+
// The whole repair, not the filter alone: the same update can lock
|
|
289
|
+
// the field, and `repair()` closes the list before it filters.
|
|
290
|
+
this.sync()
|
|
291
|
+
})
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
|
|
@@ -388,7 +402,7 @@ export class LtSuggestion extends HostElement {
|
|
|
388
402
|
setAttr(
|
|
389
403
|
labelElement,
|
|
390
404
|
"id",
|
|
391
|
-
this.
|
|
405
|
+
rememberedId(this.ids, labelElement, "lt-suggestion-label"),
|
|
392
406
|
)
|
|
393
407
|
}
|
|
394
408
|
if (label !== null) setAttr(list, "aria-label", null)
|
|
@@ -399,17 +413,6 @@ export class LtSuggestion extends HostElement {
|
|
|
399
413
|
})
|
|
400
414
|
}
|
|
401
415
|
|
|
402
|
-
/** The id an element should have: the one it has, otherwise the one the web component created last. */
|
|
403
|
-
private rememberedId(element: Element, prefix: string): string {
|
|
404
|
-
if (element.id) return element.id
|
|
405
|
-
let id = this.ids.get(element)
|
|
406
|
-
if (!id) {
|
|
407
|
-
id = uniqueId(prefix)
|
|
408
|
-
this.ids.set(element, id)
|
|
409
|
-
}
|
|
410
|
-
return id
|
|
411
|
-
}
|
|
412
|
-
|
|
413
416
|
/**
|
|
414
417
|
* Fills in the wiring where the markup arrived without it. Only what is
|
|
415
418
|
* missing.
|
|
@@ -430,7 +433,7 @@ export class LtSuggestion extends HostElement {
|
|
|
430
433
|
if (!control.hasAttribute("aria-autocomplete")) {
|
|
431
434
|
setAttr(control, "aria-autocomplete", "list")
|
|
432
435
|
}
|
|
433
|
-
const label = this.querySelector(
|
|
436
|
+
const label = this.querySelector(LT_SUGGESTION_PARTS.label)
|
|
434
437
|
const root = this.getRootNode() as Document | ShadowRoot
|
|
435
438
|
|
|
436
439
|
if (label) {
|
|
@@ -547,7 +550,7 @@ export class LtSuggestion extends HostElement {
|
|
|
547
550
|
const wanted = control.getAttribute("aria-controls")
|
|
548
551
|
const free = wanted && !root.getElementById?.(wanted)
|
|
549
552
|
if (free) this.ids.set(list, wanted)
|
|
550
|
-
setAttr(list, "id", this.
|
|
553
|
+
setAttr(list, "id", rememberedId(this.ids, list, "lt-suggestion-list"))
|
|
551
554
|
}
|
|
552
555
|
if (!control.hasAttribute("aria-controls")) {
|
|
553
556
|
setAttr(control, "aria-controls", list.id)
|
|
@@ -567,7 +570,11 @@ export class LtSuggestion extends HostElement {
|
|
|
567
570
|
this.roledByMe.add(option)
|
|
568
571
|
}
|
|
569
572
|
if (!option.id) {
|
|
570
|
-
setAttr(
|
|
573
|
+
setAttr(
|
|
574
|
+
option,
|
|
575
|
+
"id",
|
|
576
|
+
rememberedId(this.ids, option, "lt-suggestion-option"),
|
|
577
|
+
)
|
|
571
578
|
}
|
|
572
579
|
// From the start, not only when the list closes: otherwise the options
|
|
573
580
|
// the server sent were without it until then, while the rest of the list
|
|
@@ -600,6 +607,12 @@ export class LtSuggestion extends HostElement {
|
|
|
600
607
|
this.writeCode(valueField, this.chosen.value)
|
|
601
608
|
}
|
|
602
609
|
|
|
610
|
+
// A field that became `readonly` or `disabled` while the list was open
|
|
611
|
+
// closes it. Otherwise the repair kept it open, and Escape does nothing in
|
|
612
|
+
// a locked field. First, so nothing below writes for a list that closes.
|
|
613
|
+
if (!isServerControlled(this) && this.locked && this.wantOpen)
|
|
614
|
+
this.setOpen(false)
|
|
615
|
+
|
|
603
616
|
// An emptied status element gets its text back while the list is open,
|
|
604
617
|
// where the count is what the user is working on. Not otherwise: every
|
|
605
618
|
// patch of the region would announce the old count again. And not when
|
|
@@ -616,12 +629,17 @@ export class LtSuggestion extends HostElement {
|
|
|
616
629
|
setText(status, this.announcedText)
|
|
617
630
|
}
|
|
618
631
|
|
|
619
|
-
if (isServerControlled(this))
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
632
|
+
if (isServerControlled(this)) {
|
|
633
|
+
/*
|
|
634
|
+
* The server decides whether the list is open and what is
|
|
635
|
+
* highlighted, not what matches: an update that changes an open list
|
|
636
|
+
* is filtered here too. Before, the list showed every option the
|
|
637
|
+
* server sent, whatever the user had typed, and an emptied list kept
|
|
638
|
+
* the empty message hidden.
|
|
639
|
+
*/
|
|
640
|
+
if (this.listElement?.hidden === false) this.filter()
|
|
641
|
+
return
|
|
642
|
+
}
|
|
625
643
|
|
|
626
644
|
if (this.wantOpen !== undefined) {
|
|
627
645
|
/*
|
|
@@ -1276,8 +1294,17 @@ export class LtSuggestion extends HostElement {
|
|
|
1276
1294
|
|
|
1277
1295
|
private handleFocus = (): void => {
|
|
1278
1296
|
if (this.locked) return
|
|
1279
|
-
this.
|
|
1297
|
+
this.openFiltered()
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Opens the list, and filters and announces only when it opens. Filtering
|
|
1302
|
+
* first announced "No matches" for a list that `setOpen` then kept closed,
|
|
1303
|
+
* since there was nothing to show.
|
|
1304
|
+
*/
|
|
1305
|
+
private openFiltered(): void {
|
|
1280
1306
|
this.setOpen(true)
|
|
1307
|
+
if (this.wantOpen) this.filter()
|
|
1281
1308
|
}
|
|
1282
1309
|
|
|
1283
1310
|
private handleFocusOut = (event: FocusEvent): void => {
|
|
@@ -1297,10 +1324,7 @@ export class LtSuggestion extends HostElement {
|
|
|
1297
1324
|
|
|
1298
1325
|
if (event.key === "ArrowDown") {
|
|
1299
1326
|
event.preventDefault()
|
|
1300
|
-
if (!open)
|
|
1301
|
-
this.filter()
|
|
1302
|
-
this.setOpen(true)
|
|
1303
|
-
}
|
|
1327
|
+
if (!open) this.openFiltered()
|
|
1304
1328
|
this.markActive(this.activeIndex + 1)
|
|
1305
1329
|
} else if (event.key === "ArrowUp") {
|
|
1306
1330
|
event.preventDefault()
|
|
@@ -1308,10 +1332,7 @@ export class LtSuggestion extends HostElement {
|
|
|
1308
1332
|
// `aria-activedescendant` pointed to an option in a list the field at the
|
|
1309
1333
|
// same time reported as closed, and the screen reader announced something
|
|
1310
1334
|
// that was not on screen.
|
|
1311
|
-
if (!open)
|
|
1312
|
-
this.filter()
|
|
1313
|
-
this.setOpen(true)
|
|
1314
|
-
}
|
|
1335
|
+
if (!open) this.openFiltered()
|
|
1315
1336
|
// If nothing is highlighted, arrow up goes to the last one.
|
|
1316
1337
|
// `activeIndex - 1` would have given the second to last, since nothing
|
|
1317
1338
|
// highlighted is -1.
|
|
@@ -3,10 +3,14 @@ import {
|
|
|
3
3
|
derivedParts,
|
|
4
4
|
HostElement,
|
|
5
5
|
isServerControlled,
|
|
6
|
+
markWritten,
|
|
6
7
|
SERVER_CONTROLLED,
|
|
7
8
|
setAttr,
|
|
8
9
|
setFlag,
|
|
10
|
+
stillMarked,
|
|
9
11
|
uniqueId,
|
|
12
|
+
WRITTEN,
|
|
13
|
+
warnAboutCopiedMark,
|
|
10
14
|
warnAboutMarkup,
|
|
11
15
|
} from "../../host-element.js"
|
|
12
16
|
export const LT_TABS_TAG = "lt-tabs" as const
|
|
@@ -168,6 +172,18 @@ function nameAsReferenced(element: Element): string {
|
|
|
168
172
|
*/
|
|
169
173
|
type Word = { written: string | null; server: string | null }
|
|
170
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
|
+
|
|
171
187
|
/**
|
|
172
188
|
* Keyboard behaviour in a tab list, and the wiring when the markup comes
|
|
173
189
|
* without it.
|
|
@@ -239,22 +255,34 @@ export class LtTabs extends HostElement {
|
|
|
239
255
|
|
|
240
256
|
private observer?: MutationObserver
|
|
241
257
|
/**
|
|
242
|
-
* The
|
|
258
|
+
* The panel the user's tab points to, remembered so a patch cannot take
|
|
259
|
+
* the selection.
|
|
243
260
|
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
261
|
+
* Not the index and not the node. An index is not an identity: if the
|
|
262
|
+
* server inserts a tab first in the list, the same index points to
|
|
263
|
+
* something else. The node is not one either: the links have no id, so
|
|
264
|
+
* Datastar and idiomorph match them by position, and the node the user
|
|
265
|
+
* pressed became the inserted tab. The panel's id is the server's and
|
|
266
|
+
* stays; a tab pointing to another panel is another tab.
|
|
248
267
|
*
|
|
249
|
-
*
|
|
250
|
-
* link itself, as `innerHTML` does, the tab is a different one, and then
|
|
251
|
-
* the web component forgets the selection rather than guessing; the address
|
|
252
|
-
* still has it, see `selectFromAddress`.
|
|
268
|
+
* Remembered with the link, `chosenTab`.
|
|
253
269
|
*
|
|
254
270
|
* `undefined` means the user has not selected anything yet, and then the
|
|
255
271
|
* server's markup applies.
|
|
256
272
|
*/
|
|
273
|
+
private chosenTarget?: string
|
|
274
|
+
/**
|
|
275
|
+
* The link the user pressed, with `chosenTarget`. While it is in the tab
|
|
276
|
+
* list and points to the same panel, it is the user's tab, also when
|
|
277
|
+
* another tab points to that panel too. When a morph has turned it into
|
|
278
|
+
* another tab, or dropped it while it kept other links, the tab that points
|
|
279
|
+
* to the panel is found. When the links were replaced, none of the row is
|
|
280
|
+
* left, as `innerHTML` does, and the selection is forgotten rather than
|
|
281
|
+
* guessed; the address still has it, see `selectFromAddress`.
|
|
282
|
+
*/
|
|
257
283
|
private chosenTab?: HTMLElement
|
|
284
|
+
/** The tab list as it was when the user's tab was last found. */
|
|
285
|
+
private chosenRow: HTMLElement[] = []
|
|
258
286
|
/**
|
|
259
287
|
* Roles the web component set itself. Tells a bare tab list, where every
|
|
260
288
|
* link is a tab, from one where the server wrote the roles, where a link
|
|
@@ -287,7 +315,7 @@ export class LtTabs extends HostElement {
|
|
|
287
315
|
* The tab the user clicked under `server-controlled`, until the server
|
|
288
316
|
* answers.
|
|
289
317
|
*
|
|
290
|
-
* It is not the user's selection in the sense of `
|
|
318
|
+
* It is not the user's selection in the sense of `chosenTarget`: it is never
|
|
291
319
|
* restored against the server's word. It only lasts until someone else has
|
|
292
320
|
* touched `aria-selected` or `hidden`, added a tab or a panel, removed the
|
|
293
321
|
* tab, or removed `server-controlled`. Without it, `repair()` read the
|
|
@@ -360,6 +388,9 @@ export class LtTabs extends HostElement {
|
|
|
360
388
|
// disables the selected tab, the tab stop moves to one that can take
|
|
361
389
|
// focus. `alt`, `aria-hidden` and `title` change the name of a tab.
|
|
362
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,
|
|
363
394
|
"aria-selected",
|
|
364
395
|
"tabindex",
|
|
365
396
|
"hidden",
|
|
@@ -385,7 +416,7 @@ export class LtTabs extends HostElement {
|
|
|
385
416
|
// `server-controlled` switched on midway: the web component lets go, and
|
|
386
417
|
// the next patch decides.
|
|
387
418
|
if (name === SERVER_CONTROLLED && isServerControlled(this)) {
|
|
388
|
-
this.
|
|
419
|
+
this.choose(undefined)
|
|
389
420
|
// What is there now is the starting point. Without this the tab list
|
|
390
421
|
// jumped back to the tab the server last named, before the user chose
|
|
391
422
|
// another. But as when the server answers a click: `aria-selected` the
|
|
@@ -426,28 +457,77 @@ export class LtTabs extends HostElement {
|
|
|
426
457
|
).filter((tab) => linkTarget(tab) !== null)
|
|
427
458
|
}
|
|
428
459
|
|
|
460
|
+
/** Remembers the user's tab: the link, and the panel it points to. */
|
|
461
|
+
private choose(tab: HTMLElement | undefined): void {
|
|
462
|
+
this.chosenTab = tab
|
|
463
|
+
this.chosenTarget = tab ? (linkTarget(tab) ?? undefined) : undefined
|
|
464
|
+
this.chosenRow = tab ? this.tabs : []
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Where the user's tab is now, or -1: the link while it points to the same
|
|
469
|
+
* panel; the tab that points to that panel when a morph has turned the
|
|
470
|
+
* link into another tab, or dropped it and kept others (the morphers match
|
|
471
|
+
* links without ids by position, and look ahead when one is removed);
|
|
472
|
+
* none when the links were replaced, with nothing of the row left.
|
|
473
|
+
*/
|
|
474
|
+
private chosenIndex(): number {
|
|
475
|
+
const tabs = this.tabs
|
|
476
|
+
const link = this.chosenTab
|
|
477
|
+
if (!link || this.chosenTarget === undefined) return -1
|
|
478
|
+
const at = tabs.indexOf(link)
|
|
479
|
+
if (at >= 0 && linkTarget(link) === this.chosenTarget) {
|
|
480
|
+
this.chosenRow = tabs
|
|
481
|
+
return at
|
|
482
|
+
}
|
|
483
|
+
const morphed = at >= 0 || this.chosenRow.some((tab) => tabs.includes(tab))
|
|
484
|
+
if (!morphed) return -1
|
|
485
|
+
const moved = tabs.findIndex((tab) => linkTarget(tab) === this.chosenTarget)
|
|
486
|
+
if (moved >= 0) {
|
|
487
|
+
this.chosenTab = tabs[moved]
|
|
488
|
+
this.chosenRow = tabs
|
|
489
|
+
}
|
|
490
|
+
return moved
|
|
491
|
+
}
|
|
492
|
+
|
|
429
493
|
/** The server's word about an attribute, against what the web component last wrote. */
|
|
430
494
|
private serverWord(
|
|
431
495
|
memory: WeakMap<Element, Word>,
|
|
432
496
|
element: Element,
|
|
433
|
-
|
|
497
|
+
name: string,
|
|
434
498
|
): string | null {
|
|
499
|
+
const now = element.getAttribute(name)
|
|
435
500
|
const known = memory.get(element)
|
|
436
|
-
if (!known)
|
|
437
|
-
|
|
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
|
|
438
510
|
}
|
|
439
511
|
|
|
440
512
|
/** Is there something on a tab or a panel other than what the web component wrote? */
|
|
441
513
|
private touchedByOthers(): boolean {
|
|
442
514
|
return this.tabs.some((tab) => {
|
|
443
515
|
const selected = this.selectedMemory.get(tab)
|
|
444
|
-
if (
|
|
516
|
+
if (
|
|
517
|
+
!selected ||
|
|
518
|
+
tab.getAttribute("aria-selected") !== selected.written ||
|
|
519
|
+
markGone(tab, "aria-selected", selected)
|
|
520
|
+
) {
|
|
445
521
|
return true
|
|
446
522
|
}
|
|
447
523
|
const panel = this.panelFor(tab)
|
|
448
524
|
if (!panel) return false
|
|
449
525
|
const hidden = this.hiddenMemory.get(panel)
|
|
450
|
-
return
|
|
526
|
+
return (
|
|
527
|
+
!hidden ||
|
|
528
|
+
panel.getAttribute("hidden") !== hidden.written ||
|
|
529
|
+
markGone(panel, "hidden", hidden)
|
|
530
|
+
)
|
|
451
531
|
})
|
|
452
532
|
}
|
|
453
533
|
|
|
@@ -476,7 +556,9 @@ export class LtTabs extends HostElement {
|
|
|
476
556
|
// A new, bare tab has said nothing. Had it counted, the click's
|
|
477
557
|
// `aria-selected` was read as the server's, and a later move with
|
|
478
558
|
// `hidden` alone was undone.
|
|
479
|
-
return known
|
|
559
|
+
return known
|
|
560
|
+
? now !== known.written || markGone(tab, "aria-selected", known)
|
|
561
|
+
: now !== null
|
|
480
562
|
})
|
|
481
563
|
this.hiddenMemory = new WeakMap()
|
|
482
564
|
if (serverSpoke) {
|
|
@@ -484,28 +566,19 @@ export class LtTabs extends HostElement {
|
|
|
484
566
|
return
|
|
485
567
|
}
|
|
486
568
|
for (const tab of tabs) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
})
|
|
569
|
+
const written = tab.getAttribute("aria-selected")
|
|
570
|
+
this.selectedMemory.set(tab, { written, server: null })
|
|
571
|
+
markWritten(tab, "aria-selected", written !== null)
|
|
491
572
|
}
|
|
492
573
|
}
|
|
493
574
|
|
|
494
575
|
private serverSelected(tab: Element): string | null {
|
|
495
|
-
return this.serverWord(
|
|
496
|
-
this.selectedMemory,
|
|
497
|
-
tab,
|
|
498
|
-
tab.getAttribute("aria-selected"),
|
|
499
|
-
)
|
|
576
|
+
return this.serverWord(this.selectedMemory, tab, "aria-selected")
|
|
500
577
|
}
|
|
501
578
|
|
|
502
579
|
/** The server's word about `hidden` on the panel: the form, or `null` for visible. */
|
|
503
580
|
private serverHidden(panel: Element): string | null {
|
|
504
|
-
return this.serverWord(
|
|
505
|
-
this.hiddenMemory,
|
|
506
|
-
panel,
|
|
507
|
-
panel.getAttribute("hidden"),
|
|
508
|
-
)
|
|
581
|
+
return this.serverWord(this.hiddenMemory, panel, "hidden")
|
|
509
582
|
}
|
|
510
583
|
|
|
511
584
|
/**
|
|
@@ -879,7 +952,7 @@ export class LtTabs extends HostElement {
|
|
|
879
952
|
this.releasePending()
|
|
880
953
|
}
|
|
881
954
|
|
|
882
|
-
if (
|
|
955
|
+
if (this.chosenTarget === undefined || isServerControlled(this)) {
|
|
883
956
|
/*
|
|
884
957
|
* No user selection to restore, so the server's word decides, and it
|
|
885
958
|
* is written out in full: `aria-selected`, the tab stop and `hidden` in
|
|
@@ -898,7 +971,8 @@ export class LtTabs extends HostElement {
|
|
|
898
971
|
return
|
|
899
972
|
}
|
|
900
973
|
|
|
901
|
-
const
|
|
974
|
+
const chosen = this.chosenTarget
|
|
975
|
+
const index = this.chosenIndex()
|
|
902
976
|
if (index < 0) {
|
|
903
977
|
/*
|
|
904
978
|
* The tab no longer exists. The server has sent something else, and
|
|
@@ -917,8 +991,8 @@ export class LtTabs extends HostElement {
|
|
|
917
991
|
* loads the panel's content or writes the selection to the address
|
|
918
992
|
* would never learn that it moved.
|
|
919
993
|
*/
|
|
920
|
-
const lostPanel = this.
|
|
921
|
-
this.
|
|
994
|
+
const lostPanel = chosen === undefined ? null : this.byFragment(chosen)
|
|
995
|
+
this.choose(undefined)
|
|
922
996
|
// The server marked a tab itself. Then that applies, and the panels and
|
|
923
997
|
// the tab stop are written to match it, even when nothing else gave the
|
|
924
998
|
// observer another round.
|
|
@@ -938,10 +1012,10 @@ export class LtTabs extends HostElement {
|
|
|
938
1012
|
const tab = this.tabs[index]
|
|
939
1013
|
this.apply(index)
|
|
940
1014
|
if (this.panelFor(tab) === lostPanel) {
|
|
941
|
-
this.
|
|
1015
|
+
this.choose(tab)
|
|
942
1016
|
return
|
|
943
1017
|
}
|
|
944
|
-
if (fromAddress >= 0) this.
|
|
1018
|
+
if (fromAddress >= 0) this.choose(tab)
|
|
945
1019
|
this.showInAddress(index)
|
|
946
1020
|
this.notify(index)
|
|
947
1021
|
}
|
|
@@ -1225,7 +1299,7 @@ export class LtTabs extends HostElement {
|
|
|
1225
1299
|
// No memory when the server decides the selection. Without this a
|
|
1226
1300
|
// selection made while the attribute was there would be restored the
|
|
1227
1301
|
// moment someone removed it again.
|
|
1228
|
-
if (!isServerControlled(this)) this.
|
|
1302
|
+
if (!isServerControlled(this)) this.choose(tabs[index])
|
|
1229
1303
|
this.apply(index)
|
|
1230
1304
|
if (isServerControlled(this)) this.pendingTab = tabs[index]
|
|
1231
1305
|
this.showInAddress(index)
|
|
@@ -1275,6 +1349,7 @@ export class LtTabs extends HostElement {
|
|
|
1275
1349
|
const server = this.serverSelected(tab)
|
|
1276
1350
|
setAttr(tab, "aria-selected", String(chosen))
|
|
1277
1351
|
this.selectedMemory.set(tab, { written: String(chosen), server })
|
|
1352
|
+
markWritten(tab, "aria-selected", String(chosen) !== server)
|
|
1278
1353
|
setAttr(tab, "tabindex", i === stop ? "0" : "-1")
|
|
1279
1354
|
|
|
1280
1355
|
const panel = this.panelFor(tab)
|
|
@@ -1299,6 +1374,7 @@ export class LtTabs extends HostElement {
|
|
|
1299
1374
|
if (written === null) setFlag(panel, "hidden", false)
|
|
1300
1375
|
else setAttr(panel, "hidden", written)
|
|
1301
1376
|
this.hiddenMemory.set(panel, { written, server })
|
|
1377
|
+
markWritten(panel, "hidden", written !== server)
|
|
1302
1378
|
}
|
|
1303
1379
|
/*
|
|
1304
1380
|
* Only the selected panel is a tab stop, so Tab from the tab list goes
|