1/* eslint-disable */ 2this.BX = this.BX || {}; 3this.BX.UI = this.BX.UI || {}; 4(function (exports, main_core) { 5 'use strict'; 6 7 const focusableElements = ['a[href]', 'input:not([disabled]):not([type=hidden])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'area[href]', 'summary', 'iframe', 'object', 'embed', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', '[tabindex]:not([disabled]):not([hidden])']; 8 const FOCUSABLE_SELECTOR = focusableElements.join(','); 9 10 const supportsCheckVisibility = !main_core.Type.isUndefined(window.Element) && 'checkVisibility' in window.Element.prototype; 11 const NON_TEXT_INPUT_TYPES = new Set(['button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'range', 'reset', 'submit']); 12 const NON_EDITABLE_INPUT_TYPES = new Set(['button', 'checkbox', 'file', 'hidden', 'image', 'radio', 'range', 'reset', 'submit']); 13 class InteractivityChecker { 14 static isDisabled(element) { 15 return main_core.Type.isElementNode(element) && (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true'); 16 } 17 static isTextInput(element) { 18 if (!main_core.Type.isElementNode(element)) { 19 return false; 20 } 21 if (element.tagName === 'INPUT') { 22 return !NON_TEXT_INPUT_TYPES.has(element.type); 23 } 24 return element.tagName === 'TEXTAREA' || element.isContentEditable; 25 } 26 static isEditable(element) { 27 if (!main_core.Type.isElementNode(element)) { 28 return false; 29 } 30 if (element.tagName === 'INPUT') { 31 return !NON_EDITABLE_INPUT_TYPES.has(element.type); 32 } 33 return element.tagName === 'TEXTAREA' || element.tagName === 'SELECT' || element.isContentEditable; 34 } 35 static isVisible(element) { 36 if (!main_core.Type.isElementNode(element) || !element.isConnected) { 37 return false; 38 } 39 if (supportsCheckVisibility) { 40 return element.checkVisibility({ 41 visibilityProperty: true, 42 opacityProperty: true 43 }); 44 } 45 const hasGeometry = element.offsetWidth > 0 || element.offsetHeight > 0 || element.getClientRects().length > 0; 46 return hasGeometry && getComputedStyle(element).visibility === 'visible'; 47 } 48 static isTabbable(element) { 49 if (!this.isFocusable(element)) { 50 return false; 51 } 52 return !this.hasNegativeTabIndex(element); 53 } 54 static hasNegativeTabIndex(element) { 55 const tabindex = element?.getAttribute('tabindex'); 56 if (tabindex === null) { 57 return false; 58 } 59 return Number.parseInt(tabindex, 10) < 0; 60 } 61 static isFocusable(element) { 62 if (!main_core.Type.isElementNode(element) || element.closest('[inert]') || !element.isConnected) { 63 return false; 64 } 65 return element.matches(FOCUSABLE_SELECTOR) && this.isVisible(element); 66 } 67 } 68 69 const STORAGE_KEY = 'bx:a11y:debug'; 70 const ALL = '*'; 71 const CATEGORY_LABELS = { 72 'focus-monitor': 'FocusMonitor', 73 'focus-trap': 'FocusTrap', 74 'focus-zone': 'FocusZone', 75 'input-modality': 'InputModality', 76 'live-announcer': 'LiveAnnouncer' 77 }; 78 class AccessibilityLogger { 79 static #categories = new Set(); 80 static #all = false; 81 static #initialized = false; 82 static #init() { 83 if (this.#initialized) { 84 return; 85 } 86 this.#initialized = true; 87 try { 88 const raw = localStorage.getItem(STORAGE_KEY); 89 if (raw === ALL) { 90 this.#all = true; 91 } else if (raw) { 92 const parsed = JSON.parse(raw); 93 if (Array.isArray(parsed)) { 94 for (const category of parsed) { 95 this.#categories.add(category); 96 } 97 } 98 } 99 } catch { 100 } 101 } 102 static enable(category) { 103 this.#init(); 104 if (category) { 105 this.#categories.add(category); 106 } else { 107 this.#all = true; 108 } 109 this.#save(); 110 } 111 static disable(category) { 112 this.#init(); 113 if (category) { 114 this.#categories.delete(category); 115 } else { 116 this.#all = false; 117 this.#categories.clear(); 118 } 119 this.#save(); 120 } 121 static isEnabled(category) { 122 this.#init(); 123 return this.#all || this.#categories.has(category); 124 } 125 static log(category, message, ...args) { 126 if (!this.isEnabled(category)) { 127 return; 128 } 129 const label = CATEGORY_LABELS[category] || category; 130 console.log(`%c[${label}]%c ${message}`, 'color: #0075ff; font-weight: bold;', 'color: inherit; font-weight: normal;', ...args); 131 } 132 static logNode(category, message, node) { 133 if (!this.isEnabled(category) || !main_core.Type.isElementNode(node)) { 134 return; 135 } 136 const tag = node.tagName.toLowerCase(); 137 const classes = node.className ? `.${[...node.classList].join('.')}` : '';
138 const id = node.id ? `#${node.id}` : ''; 139 const selector = `${tag}${classes}${id}`; 140 const role = node.getAttribute('role'); 141 const labelledBy = node.getAttribute('aria-labelledby'); 142 const labelledByText = labelledBy ? labelledBy.split(/\s+/).map(labelId => document.getElementById(labelId)?.textContent?.trim()).filter(Boolean).join(' ') : ''; 143 const ariaLabel = (node.getAttribute('aria-label') || labelledByText || node.textContent.trim().slice(0, 30) || 'â').replaceAll(/\s+/g, ' '); 144 const attrs = [role && `role="${role}"`, node.getAttribute('aria-expanded') !== null && `aria-expanded="${node.getAttribute('aria-expanded')}"`, node.getAttribute('href') && `href="${node.getAttribute('href')}"`, node.getAttribute('tabindex') && `tabindex="${node.getAttribute('tabindex')}"`].filter(Boolean).join(' '); 145 const label = CATEGORY_LABELS[category] || category; 146 const parts = [`%c[${label}]%c ${message}`, `%c${selector}%c`, `"%c${ariaLabel}%c"`, attrs && `%c${attrs}%c`].filter(Boolean).join(' '); 147 console.log(parts, 'color: #0075ff; font-weight: bold;', 148 'color: inherit; font-weight: normal;', 149 'color: #0075ff; font-weight: bold;', 150 'color: inherit; font-weight: normal;', 151 'color: #e36209; font-style: italic;', 152 'color: inherit; font-style: normal;', 153 ...(attrs ? ['color: #9b59b6;', 154 'color: inherit;' 155 ] : [])); 156 } 157 static warn(category, message, ...args) { 158 if (!this.isEnabled(category)) { 159 return; 160 } 161 const label = CATEGORY_LABELS[category] || category; 162 console.warn(`[${label}] ${message}`, ...args); 163 } 164 static #save() { 165 try { 166 if (this.#all) { 167 localStorage.setItem(STORAGE_KEY, ALL); 168 } else if (this.#categories.size > 0) { 169 localStorage.setItem(STORAGE_KEY, JSON.stringify([...this.#categories])); 170 } else { 171 localStorage.removeItem(STORAGE_KEY); 172 } 173 } catch { 174 } 175 } 176 } 177 178 const RESTORE_FOCUS_EVENT = 'a11y:restore-focus'; 179 class FocusNavigator { 180 static get FOCUSABLE_SELECTOR() { 181 return FOCUSABLE_SELECTOR; 182 } 183 static isFocusLost(node) { 184 const activeElement = this.getActiveElement(node); 185 return activeElement === null || activeElement.tagName === 'BODY'; 186 } 187 static getFirst(container, options) { 188 return this.#traverse(container, 'first', options); 189 } 190 static getLast(container, options) { 191 return this.#traverse(container, 'last', options); 192 } 193 static getNext(container, options) { 194 return this.#traverse(container, 'next', options); 195 } 196 static getPrevious(container, options) { 197 return this.#traverse(container, 'previous', options); 198 } 199 static focusFirst(container, options) { 200 return this.focusTarget(this.#traverse(container, 'first', options), options); 201 } 202 static focusLast(container, options) { 203 return this.focusTarget(this.#traverse(container, 'last', options), options); 204 } 205 static focusNext(container, options) { 206 return this.focusTarget(this.#traverse(container, 'next', options), options); 207 } 208 static focusPrevious(container, options) { 209 return this.focusTarget(this.#traverse(container, 'previous', options), options); 210 } 211 static focusContainer(container, options) { 212 if (!container.hasAttribute('tabindex')) { 213 container.setAttribute('tabindex', '-1'); 214 } 215 this.focusTarget(container, options); 216 return container; 217 } 218 static getActiveElement(node) { 219 const { 220 activeElement 221 } = this.#getDocument(node); 222 if (activeElement?.tagName === 'IFRAME' && activeElement.contentDocument?.body) { 223 const iframe = activeElement; 224 return this.getActiveElement(iframe.contentDocument?.body); 225 } 226 if (main_core.Type.isElementNode(activeElement)) { 227 return activeElement; 228 } 229 return null; 230 } 231 static createWalker(container, options) { 232 const { 233 tabbableOnly = false, 234 accept 235 } = options ?? {}; 236 const ownerDocument = container.ownerDocument ?? document; 237 return ownerDocument.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, { 238 acceptNode(node) { 239 const el = node; 240 if (!InteractivityChecker.isFocusable(el)) { 241 return NodeFilter.FILTER_SKIP; 242 } 243 if (tabbableOnly && el.tabIndex < 0) { 244 return NodeFilter.FILTER_SKIP; 245 } 246 if (accept && !accept(el) && el.tagName !== 'IFRAME') { 247 return NodeFilter.FILTER_SKIP; 248 } 249 return NodeFilter.FILTER_ACCEPT; 250 } 251 }); 252 } 253 static *#walk(container, options) { 254 const walker = this.createWalker(container, options); 255 for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) { 256 const el = node; 257 if (el.tagName === 'IFRAME') { 258 try { 259 const iframe = el; 260 const body = iframe.contentDocument?.body; 261 if (body) { 262 yield* this.#walk(body, options); 263 continue; 264 } 265 } catch { 266 } 267 } 268 yield el; 269 } 270 } 271 static #traverse(container, direction, options) { 272 const walkerOptions = { 273 tabbableOnly: options?.tabbableOnly ?? true, 274 accept: options?.accept 275 }; 276 switch (direction) { 277 case 'first': 278 { 279 return this.#walk(container, walkerOptions).next().value ?? null; 280 } 281 case 'last': 282 { 283 let last = null; 284 for (const el of this.#walk(container, walkerOptions)) { 285 last = el; 286 } 287 return last; 288 } 289 case 'next': 290 { 291 const from = this.#resolveFrom(container, options); 292 let found = from === null; 293 for (const el of this.#walk(container, walkerOptions)) { 294 if (found) { 295 return el; 296 } 297 if (el === from) { 298 found = true; 299 } 300 } 301 if (options?.wrap) { 302 return this.#walk(container, walkerOptions).next().value ?? null; 303 } 304 return null; 305 } 306 case 'previous': 307 { 308 const from = this.#resolveFrom(container, options); 309 let prev = null; 310 for (const el of this.#walk(container, walkerOptions)) { 311 if (el === from) { 312 break; 313 } 314 prev = el; 315 } 316 if (prev) { 317 return prev; 318 } 319 if (options?.wrap) { 320 return this.#traverse(container, 'last', options); 321 } 322 return null; 323 } 324 } 325 return null; 326 } 327 static focusBySelector(container, selector, options) { 328 const { 329 accept: outerAccept, 330 ...rest 331 } = options ?? {}; 332 const accept = el => {
333 return el.matches(selector) && (outerAccept ? outerAccept(el) : true); 334 }; 335 const from = this.#resolveFrom(container, options); 336 let found = from === null; 337 for (const el of this.#walk(container, { 338 ...rest, 339 accept 340 })) { 341 if (found) { 342 return this.focusTarget(el, options); 343 } 344 if (el === from) { 345 found = true; 346 } 347 } 348 if (options?.wrap) { 349 const el = this.#walk(container, { 350 ...rest, 351 accept 352 }).next().value; 353 if (el) { 354 return this.focusTarget(el, options); 355 } 356 } 357 return null; 358 } 359 static #resolveFrom(container, options) { 360 const from = options?.from ?? this.getActiveElement(container); 361 if (!from || container === from) { 362 return null; 363 } 364 if (container.contains(from)) { 365 return from; 366 } 367 let doc = from.ownerDocument; 368 while (doc !== null) { 369 const frame = doc.defaultView?.frameElement; 370 if (!frame) { 371 return null; 372 } 373 if (container.contains(frame)) { 374 return from; 375 } 376 doc = frame.ownerDocument; 377 } 378 return null; 379 } 380 static focusTarget(target, options) { 381 if (!target) { 382 return null; 383 } 384 const { 385 preventScroll, 386 focusVisible 387 } = options ?? {}; 388 target.focus({ 389 preventScroll, 390 focusVisible 391 }); 392 return target; 393 } 394 static restoreFocus(target, options) { 395 if (!main_core.Type.isElementNode(target)) { 396 return null; 397 } 398 try { 399 const customEvent = new CustomEvent(RESTORE_FOCUS_EVENT, { 400 bubbles: true, 401 cancelable: true 402 }); 403 const dispatchResult = target.dispatchEvent(customEvent); 404 if (dispatchResult) { 405 this.focusTarget(target, options); 406 AccessibilityLogger.log('focus-monitor', 'restored focus to', target); 407 return target; 408 } 409 } catch { 410 AccessibilityLogger.warn('focus-monitor', 'failed to restore focus to', target); 411 } 412 return null; 413 } 414 static #getDocument(node) { 415 if (!node) { 416 return document; 417 } 418 if (node.window === node) { 419 return node.document; 420 } 421 return node.ownerDocument ?? document; 422 } 423 } 424 425 class FocusHistory { 426 #limit; 427 #stack = []; 428 constructor(limit = 25) { 429 this.#limit = limit; 430 } 431 record(el) { 432 if (!InteractivityChecker.isFocusable(el)) { 433 return; 434 } 435 if (this.#stack[this.#stack.length - 1]?.deref() === el) { 436 return; 437 } 438 for (let i = this.#stack.length - 1; i >= 0; i--) { 439 const current = this.#stack[i].deref(); 440 if (!current) { 441 this.#stack.splice(i, 1); 442 continue; 443 } 444 if (current === el) { 445 this.#stack.splice(i, 1); 446 break; 447 } 448 } 449 AccessibilityLogger.logNode('focus-monitor', 'recorded', el); 450 this.#stack.push(new WeakRef(el)); 451 this.#trimByWindow(el.ownerDocument); 452 } 453 getLastValid() { 454 for (let i = this.#stack.length - 1; i >= 0; i--) { 455 const el = this.#stack[i].deref(); 456 if (!el) { 457 this.#stack.splice(i, 1); 458 continue; 459 } 460 if (InteractivityChecker.isFocusable(el)) { 461 this.#stack.length = i + 1; 462 return el; 463 } 464 this.#stack.splice(i, 1); 465 } 466 return null; 467 } 468 #trimByWindow(doc) { 469 const docIndexes = []; 470 for (let i = this.#stack.length - 1; i >= 0; i--) { 471 const current = this.#stack[i].deref(); 472 if (!current) { 473 this.#stack.splice(i, 1); 474 continue; 475 } 476 if (current.ownerDocument === doc) { 477 docIndexes.push(i); 478 } 479 } 480 const excess = docIndexes.length - this.#limit; 481 if (excess <= 0) { 482 return; 483 } 484 for (let j = docIndexes.length - excess; j < docIndexes.length; j++) { 485 this.#stack.splice(docIndexes[j], 1); 486 } 487 } 488 } 489 490 const NAV_KEYS = new Set(['Tab', 'Escape', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End']); 491 const BARE_MODIFIER_KEYS = new Set(['Alt', 'AltGraph', 'CapsLock', 'Control', 'Meta', 'NumLock', 'ScrollLock', 'Shift']); 492 const POINTER_TYPES = new Set(['mouse', 'pen', 'touch']); 493 class InputModalityTracker { 494 #modality = 'unknown'; 495 #pointerType = null; 496 #lastNavKey = null; 497 #lastNavShift = false; 498 #attachedDocs = []; 499 #detachHandlers = new WeakMap(); 500 attach(doc) { 501 if (this.#detachHandlers.has(doc)) { 502 return; 503 } 504 const onKeyDown = event => { 505 if (InputModalityTracker.isBareModifier(event.key)) { 506 return; 507 }
508 if (NAV_KEYS.has(event.key)) { 509 this.#lastNavKey = event.key; 510 this.#lastNavShift = event.shiftKey; 511 } else { 512 this.#lastNavKey = null; 513 this.#lastNavShift = false; 514 } 515 this.#setModality('keyboard'); 516 }; 517 const onPointer = event => { 518 this.#pointerType = POINTER_TYPES.has(event.pointerType) ? event.pointerType : 'mouse'; 519 this.#lastNavKey = null; 520 this.#lastNavShift = false; 521 this.#setModality('pointer'); 522 }; 523 main_core.Event.bind(doc, 'keydown', onKeyDown, true); 524 main_core.Event.bind(doc, 'pointerdown', onPointer, true); 525 const detach = () => { 526 main_core.Event.unbind(doc, 'keydown', onKeyDown, true); 527 main_core.Event.unbind(doc, 'pointerdown', onPointer, true); 528 }; 529 this.#attachedDocs.push(new WeakRef(doc)); 530 this.#detachHandlers.set(doc, detach); 531 doc.documentElement.dataset.inputModality = this.#modality; 532 } 533 detach(doc) { 534 const detach = this.#detachHandlers.get(doc); 535 if (detach) { 536 detach(); 537 this.#detachHandlers.delete(doc); 538 this.#cleanupWeakRefs(); 539 } 540 } 541 static isBareModifier(key) { 542 return BARE_MODIFIER_KEYS.has(key); 543 } 544 static enableDebug() { 545 AccessibilityLogger.enable('input-modality'); 546 } 547 static disableDebug() { 548 AccessibilityLogger.disable('input-modality'); 549 } 550 #setModality(modality) { 551 if (this.#modality !== modality) { 552 AccessibilityLogger.log('input-modality', `${this.#modality} -> ${modality}`); 553 this.#modality = modality; 554 this.#updateAllDocuments(); 555 } 556 } 557 #updateAllDocuments() { 558 const aliveRefs = []; 559 for (const weakRef of this.#attachedDocs) { 560 const doc = weakRef.deref(); 561 if (doc) { 562 doc.documentElement.dataset.inputModality = this.#modality; 563 aliveRefs.push(weakRef); 564 } 565 } 566 this.#attachedDocs = aliveRefs; 567 } 568 #cleanupWeakRefs() { 569 this.#attachedDocs = this.#attachedDocs.filter(weakRef => { 570 const doc = weakRef.deref(); 571 return doc && this.#detachHandlers.has(doc); 572 }); 573 } 574 getLastModality() { 575 return this.#modality; 576 } 577 getLastPointerType() { 578 return this.#modality === 'pointer' ? this.#pointerType : null; 579 } 580 getLastNavigationKey() { 581 return this.#modality === 'keyboard' ? this.#lastNavKey : null; 582 } 583 isLastNavigationReversed() { 584 return this.#modality === 'keyboard' && this.#lastNavKey === 'Tab' && this.#lastNavShift; 585 } 586 } 587 588 class AccessibilitySettings { 589 static useFocusTrapInDialogs() { 590 const settings = main_core.Extension.getSettings('ui.a11y'); 591 return settings.get('useFocusTrapInDialogs') === true; 592 } 593 static restoreLostFocus() { 594 const settings = main_core.Extension.getSettings('ui.a11y'); 595 return settings.get('restoreLostFocus') === true; 596 } 597 static suppressFocusOnRestore() { 598 const settings = main_core.Extension.getSettings('ui.a11y'); 599 return settings.get('suppressFocusOnRestore') === true; 600 } 601 } 602 603 class FocusMonitor { 604 static #instance = null; 605 #history; 606 #modality; 607 #documents = new WeakMap(); 608 constructor() { 609 this.#history = new FocusHistory(25); 610 this.#modality = new InputModalityTracker(); 611 this.#attachDocument(document); 612 } 613 static get Instance() { 614 return this.initialize(); 615 } 616 static initialize() { 617 const topWindow = main_core.Page.getRootWindow(); 618 if (topWindow !== window) { 619 const monitor = main_core.Reflection.getClass('top.BX.UI.Accessibility.FocusMonitor'); 620 if (monitor !== null) { 621 return monitor.Instance; 622 } 623 } 624 if (this.#instance === null) { 625 this.#instance = new FocusMonitor(); 626 } 627 return this.#instance; 628 } 629 static enableDebug() { 630 AccessibilityLogger.enable('focus-monitor'); 631 } 632 static disableDebug() { 633 AccessibilityLogger.disable('focus-monitor'); 634 } 635 static shouldRestoreLostFocus() { 636 return AccessibilitySettings.restoreLostFocus(); 637 } 638 #attachDocument(doc) { 639 if (this.#documents.has(doc)) { 640 return; 641 } 642 let pendingRestore = null; 643 const onFocusIn = event => { 644 if (pendingRestore !== null) { 645 clearTimeout(pendingRestore); 646 pendingRestore = null; 647 } 648 const target = event.target; 649 if (main_core.Type.isElementNode(target)) { 650 this.#history.record(target); 651 if (target.tagName === 'IFRAME') { 652 this.attachIframe(target); 653 } 654 } 655 }; 656 const onFocusOut = () => { 657 if (pendingRestore !== null) { 658 return; 659 } 660 pendingRestore = window.setTimeout(() => { 661 pendingRestore = null; 662 this.#tryRestoreLostFocus(doc); 663 }, 0); 664 }; 665 doc.addEventListener('focusin', onFocusIn, true); 666 doc.addEventListener('focusout', onFocusOut, true); 667 const observer = this.#attachObserver(doc); 668 this.#modality.attach(doc); 669 this.#documents.set(doc, { 670 detach: () => { 671 if (pendingRestore !== null) { 672 clearTimeout(pendingRestore); 673 } 674 doc.removeEventListener('focusin', onFocusIn, true); 675 doc.removeEventListener('focusout', onFocusOut, true); 676 this.#modality.detach(doc); 677 observer?.disconnect(); 678 } 679 }); 680 } 681 attachIframe(iframe) { 682 if (!main_core.Type.isElementNode(iframe) || iframe.tagName !== 'IFRAME') { 683 return; 684 } 685 try { 686 const doc = iframe.contentDocument; 687 if (!doc) { 688 return; 689 } 690 const state = this.#documents.get(doc); 691 if (state) { 692 return; 693 } 694 this.#attachDocument(doc); 695 } catch { 696 } 697 } 698 detachIframe(iframe) { 699 try { 700 const doc = iframe.contentDocument; 701 if (!doc) { 702 return; 703 } 704 const state = this.#documents.get(doc); 705 if (state) { 706 state.detach();
707 this.#documents.delete(doc); 708 } 709 } catch { 710 } 711 } 712 #tryRestoreLostFocus(doc) { 713 if (!FocusMonitor.shouldRestoreLostFocus()) { 714 return; 715 } 716 const active = doc.activeElement; 717 if (active && active !== doc.body) { 718 return; 719 } 720 if (this.#modality.getLastModality() !== 'keyboard') { 721 return; 722 } 723 const target = this.#history.getLastValid(); 724 if (target) { 725 this.#restoreElementFocus(target); 726 return; 727 } 728 this.#restoreFocusToRoot(doc); 729 } 730 restoreFocus() { 731 const target = this.#history.getLastValid(); 732 if (target) { 733 this.#restoreElementFocus(target); 734 return; 735 } 736 this.#restoreFocusToRoot(document); 737 } 738 #restoreFocusToRoot(doc) { 739 AccessibilityLogger.log('focus-monitor', 'restoring focus to root'); 740 FocusNavigator.restoreFocus(this.getRoot(doc), { 741 preventScroll: true 742 }); 743 } 744 getRoot(doc = document) { 745 return doc.querySelector('[data-focus-root]') || doc.querySelector('main') || doc.body; 746 } 747 #restoreElementFocus(el) { 748 if (!el.isConnected) { 749 return; 750 } 751 const ownerWindow = el.ownerDocument.defaultView; 752 if (!ownerWindow) { 753 return; 754 } 755 const activeElement = FocusNavigator.getActiveElement(el); 756 if (activeElement === el) { 757 return; 758 } 759 if (ownerWindow === window) { 760 FocusNavigator.restoreFocus(el, { 761 preventScroll: true 762 }); 763 return; 764 } 765 ownerWindow.requestAnimationFrame(() => { 766 FocusNavigator.restoreFocus(el, { 767 preventScroll: true 768 }); 769 }); 770 } 771 getLastInputModality() { 772 return this.#modality.getLastModality(); 773 } 774 getModalityTracker() { 775 return this.#modality; 776 } 777 #attachObserver(doc) { 778 if (!FocusMonitor.shouldRestoreLostFocus()) { 779 return null; 780 } 781 if (main_core.Browser.isFirefox() || main_core.Browser.isSafari()) { 782 const observer = new MutationObserver(mutations => { 783 this.#tryRestoreLostFocus(doc); 784 }); 785 const observe = () => observer.observe(doc.body, { 786 childList: true, 787 subtree: true, 788 characterData: false 789 }); 790 if (doc.readyState === 'loading') { 791 doc.addEventListener('DOMContentLoaded', observe); 792 } else { 793 observe(); 794 } 795 return observer; 796 } 797 return null; 798 } 799 } 800 801 const PRECEDING = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS; 802 const FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY; 803 class FocusTrap { 804 #id = `focus-trap-${main_core.Text.getRandom()}`; 805 #container; 806 #options; 807 #active = false; 808 #looped = true; 809 #initialized = false; 810 #restoreFocus = null; 811 #preventScroll = true; 812 #startFocusBoundary = null; 813 #endFocusBoundary = null; 814 #lastFocusedElement = null; 815 #alreadyInert = new WeakSet(); 816 #startFocusHandler = this.#handleStartBoundaryFocus.bind(this); 817 #endFocusHandler = this.#handleEndBoundaryFocus.bind(this); 818 constructor(container, options = {}) { 819 this.#container = container; 820 this.#options = main_core.Type.isPlainObject(options) ? options : {}; 821 this.setRestoreFocus(this.#options.restoreFocus ?? null); 822 if (this.#options.looped !== undefined) { 823 this.setLooped(this.#options.looped); 824 } 825 if (this.#options.preventScroll !== undefined) { 826 this.setPreventScroll(this.#options.preventScroll); 827 } 828 } 829 static enableDebug() { 830 AccessibilityLogger.enable('focus-trap'); 831 } 832 static disableDebug() { 833 AccessibilityLogger.disable('focus-trap'); 834 } 835 activate(options = {}) { 836 if (this.#active) { 837 return; 838 } 839 if (!this.#initialized) { 840 this.#init(); 841 } 842 if (this.#lastFocusedElement === null) { 843 this.captureActiveElement(); 844 } 845 this.#active = true; 846 if (this.#looped) { 847 this.#setBoundariesFocusable(true); 848 } 849 const { 850 initialFocus 851 } = { 852 initialFocus: true, 853 ...options 854 }; 855 if (initialFocus) { 856 this.applyInitialFocus(); 857 } 858 this.#setOutsideIsolation(true); 859 } 860 deactivate() { 861 if (!this.#active) { 862 return; 863 } 864 this.#active = false; 865 this.#setBoundariesFocusable(false); 866 this.#setOutsideIsolation(false); 867 this.restoreFocus(); 868 } 869 destroy() { 870 this.deactivate(); 871 if (!this.#initialized) { 872 return; 873 }
874 main_core.Event.unbind(this.#startFocusBoundary, 'focus', this.#startFocusHandler); 875 main_core.Event.unbind(this.#endFocusBoundary, 'focus', this.#endFocusHandler); 876 main_core.Dom.remove(this.#startFocusBoundary); 877 main_core.Dom.remove(this.#endFocusBoundary); 878 this.#initialized = false; 879 } 880 setLooped(flag) { 881 if (!main_core.Type.isBoolean(flag)) { 882 return; 883 } 884 this.#looped = flag; 885 if (this.#active) { 886 this.#setBoundariesFocusable(flag); 887 } 888 } 889 setPreventScroll(flag) { 890 this.#preventScroll = main_core.Type.isBoolean(flag) ? flag : true; 891 } 892 isLooped() { 893 return this.#looped; 894 } 895 isActive() { 896 return this.#active; 897 } 898 setLastFocusedElement(el) { 899 if (main_core.Type.isElementNode(el) && el.tagName !== 'BODY' && !this.contains(el)) { 900 this.#lastFocusedElement = el; 901 AccessibilityLogger.logNode('focus-trap', 'set last focus', el); 902 } 903 } 904 captureActiveElement() { 905 const activeElement = FocusNavigator.getActiveElement(); 906 if (activeElement !== null) { 907 this.setLastFocusedElement(activeElement); 908 } 909 return activeElement; 910 } 911 contains(el) { 912 return this.#container.contains(el); 913 } 914 focusFirst(options) { 915 return FocusNavigator.focusFirst(this.#container, this.#prepareFocusOptions(options)); 916 } 917 focusLast(options) { 918 return FocusNavigator.focusLast(this.#container, this.#prepareFocusOptions(options)); 919 } 920 focusNext(options) { 921 return FocusNavigator.focusNext(this.#container, this.#prepareFocusOptions(options)); 922 } 923 focusPrevious(options) { 924 return FocusNavigator.focusPrevious(this.#container, this.#prepareFocusOptions(options)); 925 } 926 focusContainer(options) { 927 return FocusNavigator.focusContainer(this.#container, this.#prepareFocusOptions(options)); 928 } 929 focusBySelector(selector, options) { 930 return FocusNavigator.focusBySelector(this.#container, selector, this.#prepareFocusOptions(options)); 931 } 932 #prepareFocusOptions(options) { 933 const preparedOptions = main_core.Type.isPlainObject(options) ? { 934 ...options 935 } : {}; 936 if (main_core.Type.isNil(preparedOptions.preventScroll)) { 937 preparedOptions.preventScroll = this.#preventScroll; 938 } 939 return preparedOptions; 940 } 941 getId() { 942 return this.#id; 943 } 944 #init() { 945 this.#startFocusBoundary = this.#createFocusBoundary(); 946 this.#endFocusBoundary = this.#createFocusBoundary(); 947 main_core.Event.bind(this.#startFocusBoundary, 'focus', this.#startFocusHandler); 948 main_core.Event.bind(this.#endFocusBoundary, 'focus', this.#endFocusHandler); 949 this.#container.insertAdjacentElement('beforebegin', this.#startFocusBoundary); 950 this.#container.insertAdjacentElement('afterend', this.#endFocusBoundary); 951 this.#initialized = true; 952 } 953 #createFocusBoundary() { 954 const el = document.createElement('div'); 955 el.setAttribute('aria-hidden', 'true'); 956 el.setAttribute('data-focus-trap', this.getId()); 957 main_core.Dom.style(el, { 958 position: 'fixed', 959 width: 0, 960 height: 0, 961 opacity: '0', 962 pointerEvents: 'none', 963 outline: 'none' 964 }); 965 return el; 966 } 967 #handleStartBoundaryFocus(event) { 968 if (!this.#active || !this.#startFocusBoundary) { 969 return; 970 } 971 const position = event.relatedTarget ? this.#startFocusBoundary.compareDocumentPosition(event.relatedTarget) : 0; 972 const isBackward = position & FOLLOWING; 973 if (!isBackward) { 974 if (!this.focusFirst()) { 975 this.focusContainer(); 976 } 977 return; 978 } 979 const target = this.#options.startBoundary ? this.#resolveBoundary(this.#options.startBoundary) : null; 980 if (target) { 981 FocusNavigator.focusTarget(target, { 982 preventScroll: this.#preventScroll 983 }); 984 return; 985 } 986 if (!this.focusLast()) { 987 this.focusContainer(); 988 } 989 } 990 #handleEndBoundaryFocus(event) { 991 if (!this.#active || !this.#endFocusBoundary) { 992 return; 993 } 994 const position = event.relatedTarget ? this.#endFocusBoundary.compareDocumentPosition(event.relatedTarget) : 0; 995 const isBackward = position & PRECEDING; 996 if (!isBackward) { 997 if (!this.focusLast()) { 998 this.focusContainer(); 999 } 1000 return; 1001 } 1002 const target = this.#options.endBoundary ? this.#resolveBoundary(this.#options.endBoundary) : null; 1003 if (target) { 1004 FocusNavigator.focusTarget(target, { 1005 preventScroll: this.#preventScroll 1006 }); 1007 return; 1008 } 1009 if (!this.focusFirst()) { 1010 this.focusContainer(); 1011 } 1012 } 1013 #resolveBoundary(boundary) { 1014 if (main_core.Type.isStringFilled(boundary)) { 1015 const el = document.querySelector(boundary); 1016 return el !== null && main_core.Type.isElementNode(el) && InteractivityChecker.isFocusable(el) ? el : null; 1017 } 1018 if (main_core.Type.isElementNode(boundary) && InteractivityChecker.isFocusable(boundary)) { 1019 return boundary; 1020 }
1021 if (main_core.Type.isFunction(boundary)) { 1022 const el = boundary(); 1023 return main_core.Type.isElementNode(el) && InteractivityChecker.isFocusable(el) ? el : null; 1024 } 1025 return null; 1026 } 1027 #setBoundariesFocusable(enabled) { 1028 const tabindex = enabled ? '0' : '-1'; 1029 this.#startFocusBoundary?.setAttribute('tabindex', tabindex); 1030 this.#endFocusBoundary?.setAttribute('tabindex', tabindex); 1031 } 1032 applyInitialFocus() { 1033 const initialFocus = this.#options.initialFocus ?? true; 1034 if (initialFocus === false) { 1035 return; 1036 } 1037 if (!this.#options.forceInitialFocus) { 1038 const activeElement = FocusNavigator.getActiveElement(this.#container); 1039 if (activeElement && this.#container.contains(activeElement) && activeElement !== this.#container) { 1040 return; 1041 } 1042 } 1043 const candidates = Array.isArray(initialFocus) ? initialFocus : [initialFocus]; 1044 for (const candidate of candidates) { 1045 if (this.#tryInitialFocus(candidate)) { 1046 return; 1047 } 1048 } 1049 this.focusContainer(); 1050 } 1051 #tryInitialFocus(initialFocus) { 1052 if (initialFocus === false) { 1053 return false; 1054 } 1055 if (initialFocus === 'container') { 1056 this.focusContainer(); 1057 return true; 1058 } 1059 if (main_core.Type.isStringFilled(initialFocus)) { 1060 return this.focusBySelector(initialFocus) !== null; 1061 } 1062 if (main_core.Type.isFunction(initialFocus)) { 1063 const el = initialFocus(); 1064 if (InteractivityChecker.isFocusable(el)) { 1065 FocusNavigator.focusTarget(el, { 1066 preventScroll: this.#preventScroll 1067 }); 1068 return true; 1069 } 1070 return false; 1071 } 1072 if (initialFocus === true || initialFocus === 'first-tabbable' || main_core.Type.isNil(initialFocus)) { 1073 return Boolean(this.focusFirst()); 1074 } 1075 return false; 1076 } 1077 setRestoreFocus(restore) { 1078 if (main_core.Type.isBoolean(restore) || main_core.Type.isNull(restore) || main_core.Type.isStringFilled(restore) || main_core.Type.isFunction(restore) || main_core.Type.isElementNode(restore)) { 1079 this.#restoreFocus = restore; 1080 } 1081 } 1082 restoreFocus() { 1083 try { 1084 if (this.#restoreFocus === null && this.#shouldKeepCurrentFocus()) { 1085 this.#scheduleLostFocusRestore(this.#lastFocusedElement); 1086 return; 1087 } 1088 const restoreFocus = this.#restoreFocus ?? true; 1089 if (restoreFocus === false) { 1090 return; 1091 } 1092 if (main_core.Type.isStringFilled(restoreFocus)) { 1093 const doc = this.#container.ownerDocument ?? document; 1094 let el = null; 1095 try { 1096 el = doc.querySelector(restoreFocus); 1097 } catch { 1098 console.error('FocusTrap: invalid restoreFocus selector provided'); 1099 } 1100 if (el !== null && InteractivityChecker.isFocusable(el)) { 1101 FocusNavigator.restoreFocus(el, { 1102 preventScroll: true 1103 }); 1104 return; 1105 } 1106 } 1107 if (main_core.Type.isElementNode(restoreFocus) && InteractivityChecker.isFocusable(restoreFocus)) { 1108 FocusNavigator.restoreFocus(restoreFocus, { 1109 preventScroll: true 1110 }); 1111 return; 1112 } 1113 if (main_core.Type.isFunction(restoreFocus)) { 1114 const el = restoreFocus(); 1115 if (InteractivityChecker.isFocusable(el)) { 1116 FocusNavigator.restoreFocus(el, { 1117 preventScroll: true 1118 }); 1119 return; 1120 } 1121 } 1122 if (this.#lastFocusedElement !== null && InteractivityChecker.isFocusable(this.#lastFocusedElement)) { 1123 let eventListeners = []; 1124 const suppressFocusOnRestore = main_core.Type.isBoolean(this.#options.suppressFocusOnRestore) ? this.#options.suppressFocusOnRestore : AccessibilitySettings.suppressFocusOnRestore(); 1125 try { 1126 if (suppressFocusOnRestore) { 1127 eventListeners = main_core.Event.getEventListeners(this.#lastFocusedElement, 'focus'); 1128 if (eventListeners.length > 0) { 1129 main_core.Event.unbindAll(this.#lastFocusedElement, 'focus'); 1130 } 1131 } 1132 AccessibilityLogger.logNode('focus-trap', 'back to last focus', this.#lastFocusedElement); 1133 FocusNavigator.restoreFocus(this.#lastFocusedElement, { 1134 preventScroll: true 1135 }); 1136 } finally { 1137 if (suppressFocusOnRestore) { 1138 for (const eventListener of eventListeners) { 1139 main_core.Event.bind(this.#lastFocusedElement, 'focus', eventListener.listener); 1140 } 1141 } 1142 } 1143 return; 1144 } 1145 FocusMonitor.Instance.restoreFocus(); 1146 } finally { 1147 this.#lastFocusedElement = null; 1148 } 1149 } 1150 #shouldKeepCurrentFocus() { 1151 if (FocusNavigator.isFocusLost(this.#container)) { 1152 return false;
1153 } 1154 const activeElement = FocusNavigator.getActiveElement(this.#container); 1155 return activeElement !== null && !this.#containsElement(activeElement); 1156 } 1157 #scheduleLostFocusRestore(lastFocusedElement) { 1158 setTimeout(() => { 1159 if (!FocusNavigator.isFocusLost(this.#container)) { 1160 return; 1161 } 1162 if (lastFocusedElement !== null && InteractivityChecker.isFocusable(lastFocusedElement)) { 1163 AccessibilityLogger.logNode('focus-trap', 'back to last focus (lost)', lastFocusedElement); 1164 FocusNavigator.restoreFocus(lastFocusedElement, { 1165 preventScroll: true 1166 }); 1167 return; 1168 } 1169 FocusMonitor.Instance.restoreFocus(); 1170 }, 0); 1171 } 1172 #containsElement(element) { 1173 if (this.contains(element)) { 1174 return true; 1175 } 1176 let doc = element.ownerDocument; 1177 while (doc?.defaultView?.frameElement) { 1178 const frame = doc.defaultView.frameElement; 1179 if (main_core.Type.isElementNode(frame) && this.contains(frame)) { 1180 return true; 1181 } 1182 doc = frame.ownerDocument; 1183 } 1184 return false; 1185 } 1186 #setOutsideIsolation(enable) { 1187 if (this.#options.isolateOutside !== true) { 1188 return; 1189 } 1190 const containers = [this.#container, ...this.#getOutsideExceptionElements()]; 1191 const topLevelContainers = containers.filter(el => { 1192 return !containers.some(other => other !== el && other.contains(el)); 1193 }); 1194 const adjacentElements = this.#getAdjacentElements(topLevelContainers); 1195 if (enable) { 1196 this.#alreadyInert = new WeakSet(); 1197 for (const el of adjacentElements) { 1198 if (el === this.#container || this.#container.contains(el) || el.getAttribute('data-focus-trap') === this.getId()) { 1199 continue; 1200 } 1201 if (el.parentNode?.nodeName === 'BODY' && el.offsetWidth === 0 && el.offsetHeight === 0) { 1202 continue; 1203 } 1204 if (el.inert || el.hasAttribute('inert')) { 1205 this.#alreadyInert.add(el); 1206 } 1207 el.setAttribute('inert', 'true'); 1208 } 1209 } else { 1210 for (const el of adjacentElements) { 1211 if (this.#alreadyInert.has(el)) { 1212 continue; 1213 } 1214 el.removeAttribute('inert'); 1215 } 1216 this.#alreadyInert = new WeakSet(); 1217 } 1218 } 1219 #getAdjacentElements(containers) { 1220 const adjacentElements = new Set(); 1221 const containerAncestors = new Set(); 1222 for (const container of containers) { 1223 let currentElement = container; 1224 while (currentElement && currentElement.tagName !== 'BODY') { 1225 containerAncestors.add(currentElement); 1226 const parent = currentElement.parentElement; 1227 const siblings = parent ? parent.children : []; 1228 for (const sibling of siblings) { 1229 if (sibling.tagName !== 'SCRIPT' && sibling.tagName !== 'STYLE' && !sibling.getAttribute('data-a11y-ignore-inert') && main_core.Type.isElementNode(sibling)) { 1230 adjacentElements.add(sibling); 1231 } 1232 } 1233 currentElement = parent; 1234 } 1235 } 1236 containerAncestors.forEach(el => { 1237 adjacentElements.delete(el); 1238 }); 1239 return adjacentElements; 1240 } 1241 #getOutsideExceptionElements() { 1242 if (!main_core.Type.isArrayFilled(this.#options.outsideExceptionSelectors)) { 1243 return []; 1244 } 1245 try { 1246 const selectors = this.#options.outsideExceptionSelectors.join(','); 1247 return [...document.querySelectorAll(selectors)]; 1248 } catch { 1249 console.error('FocusTrap: invalid outsideExceptionSelectors provided'); 1250 return []; 1251 } 1252 } 1253 } 1254 1255 const FocusTrapDirective = { 1256 mounted(el, binding) { 1257 const { 1258 active, 1259 options 1260 } = normalizeValue(binding.value); 1261 el.__focusTrap = new FocusTrap(el, options); 1262 if (active) { 1263 el.__focusTrap.activate(); 1264 } 1265 }, 1266 updated(el, binding) { 1267 const { 1268 active, 1269 options 1270 } = normalizeValue(binding.value); 1271 const { 1272 active: prevActive, 1273 options: prevOptions 1274 } = normalizeValue(binding.oldValue); 1275 if (options !== prevOptions && options) { 1276 el.__focusTrap?.destroy(); 1277 el.__focusTrap = new FocusTrap(el, options); 1278 } 1279 const trap = el.__focusTrap; 1280 if (!trap) { 1281 return; 1282 }
1283 if (active) { 1284 trap.activate(); 1285 } else { 1286 trap.deactivate(); 1287 } 1288 }, 1289 unmounted(el) { 1290 el.__focusTrap?.destroy(); 1291 delete el.__focusTrap; 1292 } 1293 }; 1294 function normalizeValue(value) { 1295 if (main_core.Type.isBoolean(value)) { 1296 return { 1297 active: value 1298 }; 1299 } 1300 if (main_core.Type.isPlainObject(value)) { 1301 return { 1302 active: value.active !== false, 1303 options: value.options 1304 }; 1305 } 1306 return { 1307 active: false 1308 }; 1309 } 1310 1311 class LiveAnnouncer { 1312 static #instance = null; 1313 #el; 1314 #queue = []; 1315 #isSpeaking = false; 1316 #frameId = null; 1317 #timerId = null; 1318 #ready = false; 1319 #readyFrameId = null; 1320 #baseDelay = 500; 1321 #charDelay = 30; 1322 #maxDelay = 4000; 1323 #maxMessageLength = 160; 1324 static enableDebug() { 1325 AccessibilityLogger.enable('live-announcer'); 1326 } 1327 static disableDebug() { 1328 AccessibilityLogger.disable('live-announcer'); 1329 } 1330 static announce(message, politeness = 'polite') { 1331 if (!LiveAnnouncer.#instance) { 1332 LiveAnnouncer.#instance = new LiveAnnouncer(); 1333 } 1334 LiveAnnouncer.#instance.announce(message, politeness); 1335 } 1336 static destroy() { 1337 LiveAnnouncer.#instance?.destroy(); 1338 LiveAnnouncer.#instance = null; 1339 } 1340 constructor(options) { 1341 const { 1342 politeness = 'polite', 1343 container = document.body || document.documentElement, 1344 baseDelay, 1345 charDelay, 1346 maxDelay, 1347 maxMessageLength 1348 } = options ?? {}; 1349 if (main_core.Type.isNumber(baseDelay)) { 1350 this.#baseDelay = baseDelay; 1351 } 1352 if (main_core.Type.isNumber(charDelay)) { 1353 this.#charDelay = charDelay; 1354 } 1355 if (main_core.Type.isNumber(maxDelay)) { 1356 this.#maxDelay = maxDelay; 1357 } 1358 if (main_core.Type.isNumber(maxMessageLength)) { 1359 this.#maxMessageLength = maxMessageLength; 1360 } 1361 this.#el = this.#createLiveRegion(politeness); 1362 main_core.Dom.append(this.#el, container); 1363 this.#readyFrameId = requestAnimationFrame(() => { 1364 this.#readyFrameId = null; 1365 this.#ready = true; 1366 this.#process(); 1367 }); 1368 } 1369 announce(message, politeness = 'polite') { 1370 const normalized = this.#normalizeMessage(message); 1371 if (!normalized) { 1372 return; 1373 } 1374 AccessibilityLogger.log('live-announcer', `[${politeness}] ${normalized}`); 1375 const last = this.#queue[this.#queue.length - 1]; 1376 if (last && last.message === normalized && last.politeness === politeness) { 1377 return; 1378 } 1379 if (politeness === 'assertive') { 1380 this.#clearPendingAnnouncement(); 1381 this.#queue.unshift({ 1382 message: normalized, 1383 politeness 1384 }); 1385 } else { 1386 this.#queue.push({ 1387 message: normalized, 1388 politeness 1389 }); 1390 } 1391 this.#process(); 1392 } 1393 destroy() { 1394 if (this.#readyFrameId !== null) { 1395 cancelAnimationFrame(this.#readyFrameId); 1396 this.#readyFrameId = null; 1397 } 1398 this.#queue.length = 0; 1399 this.#clearPendingAnnouncement(); 1400 this.#el.remove(); 1401 } 1402 #process() { 1403 if (!this.#ready || this.#isSpeaking || this.#queue.length === 0) { 1404 return; 1405 } 1406 const item = this.#queue.shift(); 1407 if (!item) { 1408 return; 1409 } 1410 this.#isSpeaking = true; 1411 const { 1412 message, 1413 politeness 1414 } = item; 1415 const delay = this.#getDelay(message); 1416 this.#el.setAttribute('aria-live', politeness); 1417 this.#el.textContent = ''; 1418 this.#frameId = requestAnimationFrame(() => { 1419 this.#frameId = null; 1420 this.#el.textContent = message; 1421 this.#timerId = setTimeout(() => { 1422 this.#timerId = null; 1423 this.#isSpeaking = false; 1424 if (this.#queue.length === 0) { 1425 this.#el.textContent = ''; 1426 } 1427 this.#process(); 1428 }, delay); 1429 }); 1430 } 1431 #clearPendingAnnouncement() { 1432 this.#isSpeaking = false; 1433 if (this.#frameId !== null) { 1434 cancelAnimationFrame(this.#frameId); 1435 this.#frameId = null; 1436 } 1437 if (this.#timerId !== null) { 1438 clearTimeout(this.#timerId); 1439 this.#timerId = null; 1440 } 1441 this.#el.textContent = ''; 1442 } 1443 #getDelay(message) { 1444 const delay = this.#baseDelay + message.length * this.#charDelay; 1445 return Math.min(delay, this.#maxDelay); 1446 } 1447 #normalizeMessage(message) { 1448 if (!main_core.Type.isStringFilled(message)) { 1449 return ''; 1450 } 1451 const text = message.trim(); 1452 if (!text) { 1453 return ''; 1454 } 1455 if (text.length <= this.#maxMessageLength) { 1456 return text; 1457 } 1458 const truncated = text.slice(0, this.#maxMessageLength - 1); 1459 const trimmedToWord = truncated.replace(/\s+\S*$/, ''); 1460 return `${trimmedToWord || truncated}\u2026`; 1461 } 1462 #createLiveRegion(politeness) { 1463 const el = document.createElement('div'); 1464 el.setAttribute('aria-live', politeness); 1465 el.setAttribute('aria-atomic', 'true'); 1466 el.setAttribute('data-a11y-ignore-inert', 'true'); 1467 main_core.Dom.style(el, { 1468 position: 'absolute', 1469 width: '1px', 1470 height: '1px', 1471 margin: '-1px', 1472 padding: '0', 1473 overflow: 'hidden', 1474 clip: 'rect(0 0 0 0)', 1475 whiteSpace: 'nowrap', 1476 border: '0' 1477 }); 1478 return el; 1479 } 1480 } 1481 1482 const ARROW_HORIZONTAL = 0b0000000001; 1483 const ARROW_VERTICAL = 0b0000000010; 1484 const JK_BITS = 0b0000000100; 1485 const HL_BITS = 0b0000001000; 1486 const HOME_AND_END = 0b0000010000; 1487 const WS_BITS = 0b0000100000; 1488 const AD_BITS = 0b0001000000; 1489 const TAB_BIT = 0b0010000000; 1490 const PAGE_UP_DOWN = 0b0100000000; 1491 const BACKSPACE_BIT = 0b1000000000; 1492 const FocusKeys = { 1493 ArrowHorizontal: ARROW_HORIZONTAL, 1494 ArrowVertical: ARROW_VERTICAL, 1495 JK: JK_BITS, 1496 HL: HL_BITS, 1497 HomeAndEnd: HOME_AND_END, 1498 WS: WS_BITS, 1499 AD: AD_BITS, 1500 Tab: TAB_BIT, 1501 PageUpDown: PAGE_UP_DOWN, 1502 Backspace: BACKSPACE_BIT, 1503 ArrowAll: ARROW_HORIZONTAL | ARROW_VERTICAL, 1504 HJKL: HL_BITS | JK_BITS, 1505 WASD: WS_BITS | AD_BITS, 1506 All: ARROW_HORIZONTAL | ARROW_VERTICAL | JK_BITS | HL_BITS | HOME_AND_END | WS_BITS | AD_BITS | TAB_BIT | PAGE_UP_DOWN | BACKSPACE_BIT 1507 }; 1508 1509 const ActiveDescendant = { 1510 ELEMENT_ATTR: 'data-active-descendant', 1511 CONTAINER_ATTR: 'data-has-active-descendant', 1512 EXPLICIT: 'activated-by-keyboard', 1513 IMPLICIT: 'activated-by-other' 1514 }; 1515 const KEY_TO_BIT = { 1516 ArrowLeft: FocusKeys.ArrowHorizontal, 1517 ArrowDown: FocusKeys.ArrowVertical, 1518 ArrowUp: FocusKeys.ArrowVertical, 1519 ArrowRight: FocusKeys.ArrowHorizontal, 1520 h: FocusKeys.HL, 1521 j: FocusKeys.JK, 1522 k: FocusKeys.JK, 1523 l: FocusKeys.HL, 1524 a: FocusKeys.AD, 1525 s: FocusKeys.WS, 1526 w: FocusKeys.WS, 1527 d: FocusKeys.AD, 1528 Tab: FocusKeys.Tab, 1529 Home: FocusKeys.HomeAndEnd, 1530 End: FocusKeys.HomeAndEnd, 1531 PageUp: FocusKeys.PageUpDown, 1532 PageDown: FocusKeys.PageUpDown, 1533 Backspace: FocusKeys.Backspace 1534 }; 1535 const KEY_TO_DIRECTION = { 1536 ArrowLeft: 'previous', 1537 ArrowDown: 'next', 1538 ArrowUp: 'previous', 1539 ArrowRight: 'next', 1540 h: 'previous', 1541 j: 'next', 1542 k: 'previous', 1543 l: 'next', 1544 a: 'previous', 1545 s: 'next', 1546 w: 'previous', 1547 d: 'next', 1548 Tab: 'next', 1549 Home: 'start', 1550 End: 'end', 1551 PageUp: 'start', 1552 PageDown: 'end', 1553 Backspace: 'previous' 1554 }; 1555 class FocusZone { 1556 #container; 1557 #options; 1558 #active = false;
1559 #bindKeys; 1560 #focusOutBehavior; 1561 #focusInStrategy; 1562 #preventScroll; 1563 #ignoreHoverEvents; 1564 #tabbableOnly; 1565 #elementSet = new Set(); 1566 #savedTabIndex = new WeakMap(); 1567 #currentFocusedElement = null; 1568 #activeDescendantControl = null; 1569 #observer = null; 1570 #syncFrameId = null; 1571 #bindings = []; 1572 #generatedContainerId = false; 1573 #generatedElementIds = new WeakSet(); 1574 #keyDownHandler = this.#handleKeyDown.bind(this); 1575 #focusInHandler = this.#handleFocusIn.bind(this); 1576 #mouseMoveHandler = this.#handleMouseMove.bind(this); 1577 #controlFocusInHandler = this.#handleControlFocusIn.bind(this); 1578 #controlFocusOutHandler = this.#handleControlFocusOut.bind(this); 1579 constructor(container, options = {}) { 1580 this.#container = container; 1581 this.#options = main_core.Type.isPlainObject(options) ? options : {}; 1582 this.#activeDescendantControl = this.#options.activeDescendantControl ?? null; 1583 this.#focusOutBehavior = this.#options.focusOutBehavior ?? 'stop'; 1584 this.#focusInStrategy = this.#options.focusInStrategy ?? 'previous'; 1585 this.#preventScroll = this.#options.preventScroll ?? false; 1586 this.#ignoreHoverEvents = this.#options.ignoreHoverEvents ?? false; 1587 this.#tabbableOnly = this.#options.tabbableOnly ?? false; 1588 this.#bindKeys = this.#options.bindKeys ?? (this.#options.getNextFocusable ? FocusKeys.ArrowAll : FocusKeys.ArrowVertical) | FocusKeys.HomeAndEnd; 1589 } 1590 static enableDebug() { 1591 AccessibilityLogger.enable('focus-zone'); 1592 } 1593 static disableDebug() { 1594 AccessibilityLogger.disable('focus-zone'); 1595 } 1596 activate() { 1597 if (this.#active) { 1598 return; 1599 } 1600 this.#active = true; 1601 this.#elementSet = this.#collectFocusableSet(); 1602 for (const element of this.#elementSet) { 1603 this.#saveAndSetTabIndex(element); 1604 } 1605 if (this.#activeDescendantControl) { 1606 if (!this.#container.id) { 1607 this.#container.setAttribute('id', `fz-${main_core.Text.getRandom()}`); 1608 this.#generatedContainerId = true; 1609 } 1610 this.#activeDescendantControl.setAttribute('aria-controls', this.#container.id); 1611 } 1612 const preventInitialFocus = this.#focusInStrategy === 'initial' && this.#activeDescendantControl; 1613 if (!preventInitialFocus) { 1614 this.#updateFocusedElement(this.#resolveInitialFocusedElement()); 1615 } 1616 this.#attachEventListeners(); 1617 this.#attachObserver(); 1618 AccessibilityLogger.log('focus-zone', `activated with ${this.#elementSet.size} elements`); 1619 } 1620 deactivate() { 1621 if (!this.#active) { 1622 return; 1623 } 1624 this.#active = false; 1625 if (this.#observer) { 1626 this.#observer.disconnect(); 1627 this.#observer = null; 1628 } 1629 if (this.#syncFrameId !== null) { 1630 cancelAnimationFrame(this.#syncFrameId); 1631 this.#syncFrameId = null; 1632 } 1633 this.#detachEventListeners(); 1634 for (const element of this.#elementSet) { 1635 this.#restoreTabIndex(element); 1636 if (this.#generatedElementIds.has(element)) { 1637 element.removeAttribute('id'); 1638 this.#generatedElementIds.delete(element); 1639 } 1640 } 1641 if (this.#activeDescendantControl) { 1642 this.#clearActiveDescendant(); 1643 this.#activeDescendantControl.removeAttribute('aria-controls'); 1644 } 1645 if (this.#generatedContainerId) { 1646 this.#container.removeAttribute('id'); 1647 this.#generatedContainerId = false; 1648 } 1649 this.#elementSet.clear(); 1650 this.#currentFocusedElement = null; 1651 AccessibilityLogger.log('focus-zone', 'deactivated'); 1652 } 1653 isActive() { 1654 return this.#active; 1655 } 1656 getCurrentFocusedElement() { 1657 return this.#currentFocusedElement; 1658 } 1659 refreshElements() { 1660 if (this.#active) { 1661 this.#syncFocusableElements(); 1662 } 1663 } 1664 #collectFocusableSet() { 1665 const elements = new Set(); 1666 const walker = FocusNavigator.createWalker(this.#container, { 1667 tabbableOnly: this.#tabbableOnly, 1668 accept: this.#options.focusableElementFilter 1669 }); 1670 for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) { 1671 elements.add(node); 1672 } 1673 return elements; 1674 } 1675 #getNavigatorOptions(from = null, wrap = false) { 1676 return { 1677 from: from ?? undefined, 1678 tabbableOnly: false, 1679 accept: el => this.#elementSet.has(el), 1680 wrap 1681 }; 1682 } 1683 #findManagedAncestor(node) { 1684 let el = main_core.Type.isElementNode(node) ? node : node.parentElement; 1685 while (el && el !== this.#container) { 1686 if (this.#elementSet.has(el)) { 1687 return el; 1688 } 1689 el = el.parentElement; 1690 } 1691 return null; 1692 } 1693 #saveAndSetTabIndex(element) { 1694 if (!this.#savedTabIndex.has(element)) { 1695 this.#savedTabIndex.set(element, element.getAttribute('tabindex')); 1696 } 1697 element.setAttribute('tabindex', '-1'); 1698 } 1699 #restoreTabIndex(element) { 1700 const savedIndex = this.#savedTabIndex.get(element); 1701 if (savedIndex !== undefined) { 1702 if (savedIndex === null) { 1703 element.removeAttribute('tabindex'); 1704 } else { 1705 element.setAttribute('tabindex', savedIndex); 1706 } 1707 this.#savedTabIndex.delete(element); 1708 } 1709 } 1710 #updateFocusedElement(to = null, directlyActivated = false) { 1711 const from = this.#currentFocusedElement; 1712 this.#currentFocusedElement = to; 1713 if (this.#activeDescendantControl) { 1714 if (to && FocusNavigator.getActiveElement() === this.#activeDescendantControl) { 1715 this.#setActiveDescendant(from, to, directlyActivated); 1716 } else { 1717 this.#clearActiveDescendant(); 1718 } 1719 return; 1720 } 1721 if (from && from !== to && this.#savedTabIndex.has(from)) { 1722 from.setAttribute('tabindex', '-1'); 1723 } 1724 if (to) { 1725 to.setAttribute('tabindex', '0'); 1726 } 1727 } 1728 #setActiveDescendant(from, to, directlyActivated = false) { 1729 if (!to.id) { 1730 to.setAttribute('id', `fz-${main_core.Text.getRandom()}`); 1731 this.#generatedElementIds.add(to); 1732 } 1733 if (from && from !== to) { 1734 from.removeAttribute(ActiveDescendant.ELEMENT_ATTR); 1735 } 1736 if (!this.#activeDescendantControl) { 1737 return; 1738 } 1739 const previousId = this.#activeDescendantControl.getAttribute('aria-activedescendant'); 1740 const nextActivationMode = directlyActivated ? ActiveDescendant.EXPLICIT : ActiveDescendant.IMPLICIT; 1741 const currentActivationMode = to.getAttribute(ActiveDescendant.ELEMENT_ATTR); 1742 if (previousId === to.id && currentActivationMode === nextActivationMode) { 1743 return; 1744 } 1745 this.#activeDescendantControl.setAttribute('aria-activedescendant', to.id); 1746 this.#container.setAttribute(ActiveDescendant.CONTAINER_ATTR, to.id); 1747 to.setAttribute(ActiveDescendant.ELEMENT_ATTR, nextActivationMode); 1748 AccessibilityLogger.logNode('focus-zone', 'active descendant ->', to); 1749 this.#options.onActiveDescendantChanged?.(to, from, directlyActivated); 1750 } 1751 #clearActiveDescendant(previousElement = this.#currentFocusedElement) { 1752 if (this.#focusInStrategy === 'first') { 1753 this.#currentFocusedElement = null; 1754 } 1755 this.#activeDescendantControl?.removeAttribute('aria-activedescendant'); 1756 this.#container.removeAttribute(ActiveDescendant.CONTAINER_ATTR); 1757 if (previousElement) { 1758 previousElement.removeAttribute(ActiveDescendant.ELEMENT_ATTR); 1759 } 1760 const stale = this.#container.querySelectorAll(`[${ActiveDescendant.ELEMENT_ATTR}]`); 1761 for (const element of stale) { 1762 element.removeAttribute(ActiveDescendant.ELEMENT_ATTR); 1763 } 1764 this.#options.onActiveDescendantChanged?.(null, previousElement, false); 1765 } 1766 #handleKeyDown(event) {
1767 const keyBit = KEY_TO_BIT[event.key]; 1768 if (keyBit === undefined || this.#elementSet.size === 0) { 1769 return; 1770 } 1771 if (event.defaultPrevented || (keyBit & this.#bindKeys) === 0 || FocusZone.#isNativeInputKey(event, FocusNavigator.getActiveElement())) { 1772 return; 1773 } 1774 const direction = FocusZone.#getDirection(event); 1775 let nextElement = null; 1776 if (this.#options.getNextFocusable) { 1777 nextElement = this.#options.getNextFocusable(direction, FocusNavigator.getActiveElement() ?? null, event); 1778 } 1779 if (!nextElement) { 1780 const wrap = this.#focusOutBehavior === 'wrap' && event.key !== 'Tab'; 1781 const navOptions = this.#getNavigatorOptions(this.#currentFocusedElement, wrap); 1782 switch (direction) { 1783 case 'previous': 1784 { 1785 nextElement = FocusNavigator.getPrevious(this.#container, navOptions); 1786 break; 1787 } 1788 case 'next': 1789 { 1790 nextElement = FocusNavigator.getNext(this.#container, navOptions); 1791 break; 1792 } 1793 case 'start': 1794 { 1795 nextElement = FocusNavigator.getFirst(this.#container, navOptions); 1796 break; 1797 } 1798 default: 1799 { 1800 nextElement = FocusNavigator.getLast(this.#container, navOptions); 1801 } 1802 } 1803 } 1804 if (this.#activeDescendantControl) { 1805 this.#updateFocusedElement(nextElement || this.#currentFocusedElement, true); 1806 } else if (nextElement) { 1807 nextElement.focus({ 1808 preventScroll: this.#preventScroll 1809 }); 1810 } 1811 if (event.key !== 'Tab' || nextElement) { 1812 event.preventDefault(); 1813 } 1814 } 1815 #handleFocusIn(event) { 1816 if (!main_core.Type.isElementNode(event.target)) { 1817 return; 1818 } 1819 if (this.#activeDescendantControl) { 1820 this.#handleFocusInActiveDescendant(event); 1821 } else { 1822 this.#handleFocusInRovingTabindex(event); 1823 } 1824 } 1825 #handleFocusInActiveDescendant(event) { 1826 const target = event.target; 1827 if (this.#elementSet.has(target)) { 1828 this.#activeDescendantControl?.focus({ 1829 preventScroll: this.#preventScroll 1830 }); 1831 this.#updateFocusedElement(target); 1832 } 1833 } 1834 #handleFocusInRovingTabindex(event) { 1835 const target = event.target; 1836 const tracker = FocusMonitor.Instance.getModalityTracker(); 1837 const isPointerFocus = tracker.getLastModality() === 'pointer'; 1838 if (isPointerFocus) { 1839 if (this.#elementSet.has(target)) { 1840 this.#updateFocusedElement(target); 1841 } 1842 return; 1843 } 1844 if (!this.#elementSet.has(target)) { 1845 return; 1846 } 1847 if (this.#focusInStrategy === 'previous') { 1848 this.#updateFocusedElement(target); 1849 } else if (this.#focusInStrategy === 'closest' || this.#focusInStrategy === 'first') { 1850 if (this.#isFocusEnteringFromOutside(event.relatedTarget)) { 1851 const targetElement = this.#getEntryFocusedElement(); 1852 if (targetElement) { 1853 targetElement.focus({ 1854 preventScroll: this.#preventScroll 1855 }); 1856 } 1857 return; 1858 } 1859 this.#updateFocusedElement(target); 1860 } else if (main_core.Type.isFunction(this.#focusInStrategy)) { 1861 if (this.#isFocusEnteringFromOutside(event.relatedTarget)) { 1862 const elementToFocus = this.#focusInStrategy(event.relatedTarget); 1863 if (elementToFocus && this.#elementSet.has(elementToFocus)) { 1864 elementToFocus.focus({ 1865 preventScroll: this.#preventScroll 1866 }); 1867 return; 1868 } 1869 AccessibilityLogger.warn('focus-zone', 'focusInStrategy returned an element outside the managed focus set.'); 1870 } 1871 this.#updateFocusedElement(target); 1872 } 1873 } 1874 #resolveInitialFocusedElement() { 1875 if (main_core.Type.isFunction(this.#focusInStrategy)) { 1876 const element = this.#focusInStrategy(this.#currentFocusedElement); 1877 return element && this.#elementSet.has(element) ? element : null; 1878 } 1879 return FocusNavigator.getFirst(this.#container, this.#getNavigatorOptions()); 1880 } 1881 #getEntryFocusedElement() { 1882 const navOptions = this.#getNavigatorOptions(); 1883 return FocusMonitor.Instance.getModalityTracker().isLastNavigationReversed() ? FocusNavigator.getLast(this.#container, navOptions) : FocusNavigator.getFirst(this.#container, navOptions); 1884 } 1885 #isFocusEnteringFromOutside(relatedTarget) { 1886 return main_core.Type.isElementNode(relatedTarget) && !this.#container.contains(relatedTarget); 1887 } 1888 #handleMouseMove(event) { 1889 const { 1890 target 1891 } = event; 1892 if (!main_core.Type.isElementNode(target)) { 1893 return; 1894 } 1895 const managedElement = this.#findManagedAncestor(target); 1896 if (managedElement) { 1897 this.#updateFocusedElement(managedElement); 1898 } 1899 } 1900 #handleControlFocusIn() { 1901 const preventInitialFocus = this.#focusInStrategy === 'initial' && this.#activeDescendantControl; 1902 if (this.#currentFocusedElement) { 1903 this.#setActiveDescendant(null, this.#currentFocusedElement); 1904 } else if (!preventInitialFocus) { 1905 this.#updateFocusedElement(FocusNavigator.getFirst(this.#container, this.#getNavigatorOptions())); 1906 } 1907 } 1908 #handleControlFocusOut() { 1909 this.#clearActiveDescendant(); 1910 } 1911 #listen(target, event, handler, capture = false) { 1912 main_core.Event.bind(target, event, handler, capture); 1913 this.#bindings.push([target, event, handler, capture]); 1914 } 1915 #attachEventListeners() {
1916 const keyboardTarget = this.#activeDescendantControl ?? this.#container; 1917 this.#listen(keyboardTarget, 'keydown', this.#keyDownHandler); 1918 this.#listen(this.#container, 'focusin', this.#focusInHandler); 1919 if (this.#activeDescendantControl) { 1920 if (!this.#ignoreHoverEvents) { 1921 this.#listen(this.#container, 'mousemove', this.#mouseMoveHandler, true); 1922 } 1923 this.#listen(this.#activeDescendantControl, 'focusin', this.#controlFocusInHandler); 1924 this.#listen(this.#activeDescendantControl, 'focusout', this.#controlFocusOutHandler); 1925 } 1926 } 1927 #detachEventListeners() { 1928 for (const [target, event, handler, capture] of this.#bindings) { 1929 main_core.Event.unbind(target, event, handler, capture); 1930 } 1931 this.#bindings = []; 1932 } 1933 #attachObserver() { 1934 this.#observer = new MutationObserver(mutations => { 1935 if (this.#isOwnTabIndexWrite(mutations)) { 1936 return; 1937 } 1938 this.#scheduleSyncFocusableElements(); 1939 }); 1940 this.#observer.observe(this.#container, { 1941 attributes: true, 1942 subtree: true, 1943 childList: true, 1944 attributeFilter: ['hidden', 'disabled', 'tabindex', 'inert', 'contenteditable', 'aria-disabled'] 1945 }); 1946 } 1947 #isOwnTabIndexWrite(mutations) { 1948 if (this.#tabbableOnly) { 1949 return false; 1950 } 1951 return mutations.every(record => { 1952 return record.type === 'attributes' && record.attributeName === 'tabindex' && this.#elementSet.has(record.target); 1953 }); 1954 } 1955 #scheduleSyncFocusableElements() { 1956 if (!this.#active || this.#syncFrameId !== null) { 1957 return; 1958 } 1959 this.#syncFrameId = requestAnimationFrame(() => { 1960 this.#syncFrameId = null; 1961 this.#syncFocusableElements(); 1962 }); 1963 } 1964 #syncFocusableElements() { 1965 if (!this.#active) { 1966 return; 1967 } 1968 const newSet = this.#collectFocusableSet(); 1969 const oldSet = this.#elementSet; 1970 for (const element of oldSet) { 1971 if (!newSet.has(element)) { 1972 this.#restoreTabIndex(element); 1973 } 1974 } 1975 for (const element of newSet) { 1976 if (!oldSet.has(element)) { 1977 this.#saveAndSetTabIndex(element); 1978 } 1979 } 1980 this.#elementSet = newSet; 1981 if (this.#currentFocusedElement && !newSet.has(this.#currentFocusedElement)) { 1982 this.#updateFocusedElement(FocusNavigator.getFirst(this.#container, this.#getNavigatorOptions())); 1983 } else if (!this.#currentFocusedElement && newSet.size > 0) { 1984 const preventInitialFocus = this.#focusInStrategy === 'initial' && this.#activeDescendantControl; 1985 if (!preventInitialFocus) { 1986 this.#updateFocusedElement(FocusNavigator.getFirst(this.#container, this.#getNavigatorOptions())); 1987 } 1988 } 1989 } 1990 static #getDirection(event) { 1991 const direction = KEY_TO_DIRECTION[event.key]; 1992 if (event.key === 'Tab' && event.shiftKey) { 1993 return 'previous'; 1994 } 1995 const isMac = main_core.Browser.isMac(); 1996 if (isMac && event.metaKey || !isMac && event.ctrlKey) { 1997 if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { 1998 return 'start'; 1999 } 2000 if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { 2001 return 'end'; 2002 } 2003 } 2004 return direction; 2005 } 2006 static #isNativeInputKey(event, activeElement) { 2007 const { 2008 key, 2009 metaKey, 2010 altKey 2011 } = event; 2012 const codePoint = key.codePointAt(0) ?? 0; 2013 const isSingleChar = key.length === 1 || key.length === 2 && codePoint >= 0xD800 && codePoint <= 0xDBFF; 2014 const isEditable = InteractivityChecker.isEditable(activeElement); 2015 const isSelect = activeElement?.tagName === 'SELECT'; 2016 if (isEditable && (isSingleChar || key === 'Home' || key === 'End')) { 2017 return true; 2018 } 2019 if (isSelect) { 2020 const isMac = main_core.Browser.isMac(); 2021 if (key === 'ArrowDown' && isMac && !metaKey) { 2022 return true; 2023 } 2024 return key === 'ArrowDown' && !isMac && altKey; 2025 } 2026 if (isEditable) { 2027 const isInputElement = activeElement?.tagName === 'TEXTAREA' || activeElement?.tagName === 'INPUT'; 2028 const inputEl = activeElement; 2029 const cursorAtStart = isInputElement && inputEl.selectionStart === 0 && inputEl.selectionEnd === 0; 2030 const cursorAtEnd = isInputElement && inputEl.selectionStart === inputEl.value.length && inputEl.selectionEnd === inputEl.value.length; 2031 if (key === 'ArrowLeft' && !cursorAtStart) { 2032 return true; 2033 } 2034 if (key === 'ArrowRight' && !cursorAtEnd) { 2035 return true; 2036 } 2037 const isContentEditable = main_core.Type.isElementNode(activeElement) && activeElement.isContentEditable; 2038 if (activeElement?.tagName === 'TEXTAREA' || isContentEditable) { 2039 if (key === 'PageUp' || key === 'PageDown') { 2040 return true; 2041 } 2042 if (key === 'ArrowUp' && !cursorAtStart) { 2043 return true; 2044 } 2045 if (key === 'ArrowDown' && !cursorAtEnd) { 2046 return true; 2047 } 2048 } 2049 } 2050 return false;
2051 } 2052 } 2053 2054 class VisuallyHidden extends HTMLElement { 2055 connectedCallback() { 2056 Object.assign(this.style, { 2057 position: 'absolute', 2058 width: '1px', 2059 height: '1px', 2060 padding: '0', 2061 margin: '-1px', 2062 overflow: 'hidden', 2063 clip: 'rect(0, 0, 0, 0)', 2064 whiteSpace: 'nowrap', 2065 border: '0' 2066 }); 2067 } 2068 } 2069 if (!customElements.get('visually-hidden')) { 2070 customElements.define('visually-hidden', VisuallyHidden); 2071 } 2072 2073 FocusMonitor.initialize(); 2074 2075 exports.AccessibilityLogger = AccessibilityLogger; 2076 exports.AccessibilitySettings = AccessibilitySettings; 2077 exports.ActiveDescendant = ActiveDescendant; 2078 exports.FocusKeys = FocusKeys; 2079 exports.FocusMonitor = FocusMonitor; 2080 exports.FocusNavigator = FocusNavigator; 2081 exports.FocusTrap = FocusTrap; 2082 exports.FocusTrapDirective = FocusTrapDirective; 2083 exports.FocusZone = FocusZone; 2084 exports.InputModalityTracker = InputModalityTracker; 2085 exports.InteractivityChecker = InteractivityChecker; 2086 exports.LiveAnnouncer = LiveAnnouncer; 2087 exports.RESTORE_FOCUS_EVENT = RESTORE_FOCUS_EVENT; 2088 exports.VisuallyHidden = VisuallyHidden; 2089 2090})(this.BX.UI.Accessibility = this.BX.UI.Accessibility || {}, BX); 2091//# sourceMappingURL=a11y.bundle.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.