@aurodesignsystem/auro-dialog 4.2.1 → 4.2.2

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.
@@ -2288,6 +2288,48 @@ const computePosition = (reference, floating, options) => {
2288
2288
  });
2289
2289
  };
2290
2290
 
2291
+ /**
2292
+ * Generates a v4 UUID with a three-tier fallback for insecure contexts.
2293
+ *
2294
+ * Tier 1 (secure contexts): crypto.randomUUID() — native, fully random.
2295
+ * Tier 2 (insecure contexts): crypto.getRandomValues() — available on all
2296
+ * modern browsers regardless of secure-context status.
2297
+ * Tier 3 (last resort, no crypto API): new Date().getTime() — lower entropy,
2298
+ * but sufficient for element IDs where collision risk is negligible.
2299
+ *
2300
+ * See: https://w3c.github.io/webcrypto/#dom-crypto-randomuuid (secure-context restriction)
2301
+ */
2302
+ function generateUUID() {
2303
+ let uuid;
2304
+
2305
+ if (
2306
+ typeof crypto !== "undefined" &&
2307
+ typeof crypto.randomUUID === "function"
2308
+ ) {
2309
+ uuid = crypto.randomUUID();
2310
+ } else if (
2311
+ typeof crypto !== "undefined" &&
2312
+ typeof crypto.getRandomValues === "function"
2313
+ ) {
2314
+ const bytes = new Uint8Array(16);
2315
+ crypto.getRandomValues(bytes);
2316
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
2317
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
2318
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0"));
2319
+ uuid = `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
2320
+ } else {
2321
+ let d = new Date().getTime();
2322
+ uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2323
+ const r = (d % 16) | 0;
2324
+ d = (d - r) / 16 || new Date().getTime();
2325
+ return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
2326
+ });
2327
+ }
2328
+
2329
+ // CSS selectors require IDs starting with a letter; replace a leading digit with 'a'.
2330
+ return /^[0-9]/.test(uuid) ? `a${uuid.slice(1)}` : uuid;
2331
+ }
2332
+
2291
2333
  /* eslint-disable line-comment-position, no-inline-comments */
2292
2334
 
2293
2335
 
@@ -2575,30 +2617,61 @@ class AuroFloatingUI {
2575
2617
 
2576
2618
  /**
2577
2619
  * @private
2578
- * Controls whether to lock the scrolling for the document's body.
2579
- * @param {Boolean} lock - If true, locks the body's scrolling functionlity; otherwise, unlock.
2620
+ * Mirrors `aria-modal` onto the `<dialog>` inside the bib, tracking the page
2621
+ * scroll lock: an overlay that holds the page hides it from assistive tech.
2622
+ *
2623
+ * The removal is called only from inside lockScroll()'s ownership guard: run
2624
+ * outside it, it fired on every autoUpdate tick of every NON-owning instance,
2625
+ * and a repositioning dropdown stripped `aria-modal` off an open modal dialog
2626
+ * it had nothing to do with. The write is unguarded by design — see the call
2627
+ * site — but is skipped when the attribute already reads correctly, so the
2628
+ * repeat costs no mutation record.
2629
+ * @param {Boolean} modal - True to mark the dialog modal; false to unmark it.
2580
2630
  */
2581
- lockScroll(lock = true) {
2631
+ mirrorAriaModal(modal) {
2582
2632
  const element = this.element;
2633
+ const dialog = element?.bib
2634
+ ? (element.bib?.shadowRoot || element.bib || element).querySelector(
2635
+ "dialog",
2636
+ )
2637
+ : undefined;
2583
2638
 
2584
- if (!element?.bib) {
2639
+ if (!dialog) {
2585
2640
  return;
2586
2641
  }
2587
2642
 
2588
- const dialog = (
2589
- element.bib?.shadowRoot ||
2590
- element.bib ||
2591
- element
2592
- ).querySelector("dialog");
2593
- if (dialog) {
2594
- if (lock) {
2643
+ if (modal) {
2644
+ if (dialog.getAttribute("aria-modal") !== "true") {
2595
2645
  dialog.setAttribute("aria-modal", "true");
2596
- } else {
2597
- dialog.removeAttribute("aria-modal");
2598
2646
  }
2647
+ } else {
2648
+ dialog.removeAttribute("aria-modal");
2649
+ }
2650
+ }
2651
+
2652
+ /**
2653
+ * @private
2654
+ * Controls whether to lock the scrolling for the document's body.
2655
+ * @param {Boolean} lock - If true, locks the body's scrolling functionlity; otherwise, unlock.
2656
+ */
2657
+ lockScroll(lock = true) {
2658
+ const element = this.element;
2659
+
2660
+ // Locking needs a bib to lock around; unlocking must not, or a bib that has
2661
+ // already been detached would leave the page frozen with no way back.
2662
+ if (lock && !element?.bib) {
2663
+ return;
2599
2664
  }
2600
2665
 
2601
2666
  if (lock) {
2667
+ // Re-asserted on every lock call rather than only on the unlocked ->
2668
+ // locked transition. If the bib's inner <dialog> is replaced while this
2669
+ // instance still holds the lock, the replacement would otherwise go
2670
+ // unmarked until a full unlock/lock cycle. Safe to repeat: it targets
2671
+ // this instance's own element, so it cannot reach another overlay's
2672
+ // dialog, and mirrorAriaModal() skips the write when already correct.
2673
+ this.mirrorAriaModal(true);
2674
+
2602
2675
  if (!this._scrollLocked) {
2603
2676
  this._scrollLocked = true;
2604
2677
  this._savedScrollY = window.scrollY;
@@ -2656,6 +2729,8 @@ class AuroFloatingUI {
2656
2729
  }
2657
2730
  } else {
2658
2731
  if (this._scrollLocked) {
2732
+ this.mirrorAriaModal(false);
2733
+
2659
2734
  if (this._viewportHandler && window.visualViewport) {
2660
2735
  window.visualViewport.removeEventListener(
2661
2736
  "resize",
@@ -2768,15 +2843,47 @@ class AuroFloatingUI {
2768
2843
  } else if (this.configureTrial < MAX_CONFIGURATION_COUNT) {
2769
2844
  this.configureTrial += 1;
2770
2845
 
2771
- setTimeout(() => {
2846
+ // The id is retained so disconnect() can cancel it. A retry that fires
2847
+ // after teardown re-enters this method with isPopoverVisible still true
2848
+ // — nothing clears it on the teardown path — and takes the page scroll
2849
+ // lock back, with no owner left to release it.
2850
+ //
2851
+ // This is not the rare timing race it reads as: the lookup above wants
2852
+ // a `.container` inside the BIB's own shadow root, which only a bib
2853
+ // that is itself a custom element has. A consumer whose bib is a plain
2854
+ // element (auro-dialog's `#bib` is a div, with its `.container` a
2855
+ // sibling in the host's shadow root) can never satisfy it, so it
2856
+ // retries the full MAX_CONFIGURATION_COUNT every time. Correcting the
2857
+ // lookup would change bib sizing for those consumers and is tracked
2858
+ // separately; cancelling the timer is what keeps it from freezing the
2859
+ // page.
2860
+ this._configureRetryId = setTimeout(() => {
2861
+ this._configureRetryId = undefined;
2772
2862
  this.configureBibStrategy(value);
2773
2863
  }, 0);
2774
2864
  }
2775
2865
 
2776
2866
  if (element.isPopoverVisible) {
2777
- this.lockScroll(value === "fullscreen");
2867
+ // Positioning strategy is not scroll ownership. Both "fullscreen" and
2868
+ // "dialog" lay the bib over the page and trap focus inside it, so both
2869
+ // must freeze the page behind them (AB#1647843). Gating this on
2870
+ // "fullscreen" alone left dismissible desktop dialogs and drawers
2871
+ // scrollable, and actively released the lock on every reposition
2872
+ // (AB#1625424, AB#1625435). The nested "cover" strategy takes the else
2873
+ // branch and stays scrollable by design — it sits inside its parent
2874
+ // rather than over the page.
2875
+ this.lockScroll(true);
2778
2876
  }
2779
2877
  } else {
2878
+ // The mirror of the lock above: a strategy that does not own the page must
2879
+ // hand the lock back, not merely decline to take it. autoUpdate re-runs
2880
+ // this method on every resize tick, and getPositioningStrategy() flips
2881
+ // "fullscreen" -> "floating" across the breakpoint for dropdown behavior —
2882
+ // so an overlay that locked while narrow can land here while still open.
2883
+ // Without this, the page stays frozen behind a small floating bib until
2884
+ // hideBib() happens to run.
2885
+ this.lockScroll(false);
2886
+
2780
2887
  element.bib.style.position = "";
2781
2888
  element.bib.removeAttribute("isfullscreen");
2782
2889
  element.isBibFullscreen = false;
@@ -2886,6 +2993,46 @@ class AuroFloatingUI {
2886
2993
  this.hideBib("focusloss");
2887
2994
  }
2888
2995
 
2996
+ /**
2997
+ * @private
2998
+ * Installs the handlers that dismiss an open overlay.
2999
+ *
3000
+ * Shared by showBib(), which opens the overlay, and configure(), which has to
3001
+ * put these back after a rewire tore them down under an overlay that is still
3002
+ * open. A non-modal overlay dismisses on outside click, focus loss and
3003
+ * Escape; a modal one instead swallows Escape so the native dialog's
3004
+ * CloseWatcher never sees it (AB#1613688).
3005
+ */
3006
+ setupDismissHandlers() {
3007
+ const element = this.element;
3008
+ if (!element) {
3009
+ return;
3010
+ }
3011
+
3012
+ if (!element.modal) {
3013
+ this.setupHideHandlers();
3014
+ return;
3015
+ }
3016
+
3017
+ if (this.keyDownHandler) {
3018
+ document.removeEventListener("keydown", this.keyDownHandler);
3019
+ }
3020
+ this.keyDownHandler = (evt) => {
3021
+ if (evt.key === "Escape" && element.isPopoverVisible) {
3022
+ // Intercept at keydown so CloseWatcher never sees the keystroke.
3023
+ // Canceling `cancel` alone is insufficient — a second Esc with no
3024
+ // intervening user activation triggers the anti-trap and fires `close`
3025
+ // directly, bypassing any `cancel` preventDefault (AB#1613688).
3026
+ // stopImmediatePropagation is intentional: it prevents other document
3027
+ // keydown handlers (e.g. consumer code, auro-dialog) from also acting
3028
+ // on Escape while the modal owns the key.
3029
+ evt.preventDefault();
3030
+ evt.stopImmediatePropagation();
3031
+ }
3032
+ };
3033
+ document.addEventListener("keydown", this.keyDownHandler);
3034
+ }
3035
+
2889
3036
  setupHideHandlers() {
2890
3037
  const element = this.element;
2891
3038
  if (!element) {
@@ -3058,27 +3205,7 @@ class AuroFloatingUI {
3058
3205
 
3059
3206
  // prevent double showing: isPopovervisible gets first and showBib gets called later
3060
3207
  if (!this.showing) {
3061
- if (!element.modal) {
3062
- this.setupHideHandlers();
3063
- } else {
3064
- if (this.keyDownHandler) {
3065
- document.removeEventListener("keydown", this.keyDownHandler);
3066
- }
3067
- this.keyDownHandler = (evt) => {
3068
- if (evt.key === "Escape" && element.isPopoverVisible) {
3069
- // Intercept at keydown so CloseWatcher never sees the keystroke.
3070
- // Canceling `cancel` alone is insufficient — a second Esc with no
3071
- // intervening user activation triggers the anti-trap and fires `close`
3072
- // directly, bypassing any `cancel` preventDefault (AB#1613688).
3073
- // stopImmediatePropagation is intentional: it prevents other document
3074
- // keydown handlers (e.g. consumer code, auro-dialog) from also acting
3075
- // on Escape while the modal owns the key.
3076
- evt.preventDefault();
3077
- evt.stopImmediatePropagation();
3078
- }
3079
- };
3080
- document.addEventListener("keydown", this.keyDownHandler);
3081
- }
3208
+ this.setupDismissHandlers();
3082
3209
  this.showing = true;
3083
3210
  element.isPopoverVisible = true;
3084
3211
  this.position();
@@ -3306,7 +3433,7 @@ class AuroFloatingUI {
3306
3433
 
3307
3434
  this.id = element.getAttribute("id");
3308
3435
  if (!this.id) {
3309
- this.id = window.crypto.randomUUID();
3436
+ this.id = generateUUID();
3310
3437
  element.setAttribute("id", this.id);
3311
3438
  }
3312
3439
 
@@ -3318,7 +3445,35 @@ class AuroFloatingUI {
3318
3445
  this.enableKeyboardHandling = enableKeyboardHandling;
3319
3446
 
3320
3447
  this.eventPrefix = eventPrefix;
3321
- if (this.element !== elem) {
3448
+
3449
+ // A retained scroll lock's saved body styles were captured for whichever
3450
+ // element held it, so only a same-element rewire may keep it. An actual
3451
+ // element swap has to take the full teardown, or this instance is left
3452
+ // holding a lock that no longer describes the page it points at — with no
3453
+ // owner able to release it (AB#1647843).
3454
+ const rewiringSameElement = this.element === elem;
3455
+
3456
+ if (!rewiringSameElement) {
3457
+ // Tear down against the outgoing element, before this.element is
3458
+ // repointed. Everything disconnect() releases is element-scoped:
3459
+ // aria-modal on that element's inner dialog, the bib transform saved for
3460
+ // it, its autoUpdate loop and its trigger listeners. Repointing first
3461
+ // aimed all of it at the incoming element — the outgoing dialog kept
3462
+ // aria-modal="true" for good, its bib stayed transformed, and the
3463
+ // incoming bib had the outgoing element's saved transform written onto
3464
+ // it. The trigger gate below read the incoming element's trigger, which
3465
+ // is not assigned until later in this method, so on a swap teardown was
3466
+ // usually skipped outright and the page stayed locked with no owner.
3467
+ if (this.element?.trigger) {
3468
+ this.disconnect({ teardown: true });
3469
+ }
3470
+
3471
+ // disconnect() does not clear this.showing, and the rewire block at the
3472
+ // end of this method only runs for a same-element rewire. Left true, the
3473
+ // incoming element's first showBib() is silently dropped by its
3474
+ // `!this.showing` guard and the overlay never opens.
3475
+ this.showing = false;
3476
+
3322
3477
  this.element = elem;
3323
3478
  }
3324
3479
 
@@ -3331,8 +3486,10 @@ class AuroFloatingUI {
3331
3486
  this.behavior = element.behavior;
3332
3487
  }
3333
3488
 
3334
- if (element.trigger) {
3335
- this.disconnect();
3489
+ if (rewiringSameElement && element.trigger) {
3490
+ // Rewiring, not tearing down — an overlay that is open right now keeps
3491
+ // its page scroll lock across a trigger change.
3492
+ this.disconnect({ teardown: false });
3336
3493
  }
3337
3494
  element.trigger =
3338
3495
  element.triggerElement ||
@@ -3361,11 +3518,61 @@ class AuroFloatingUI {
3361
3518
  element.trigger.addEventListener("focus", this.handleEvent);
3362
3519
  element.trigger.addEventListener("blur", this.handleEvent);
3363
3520
  }
3521
+
3522
+ // disconnect() above stripped the dismissal handlers and killed the
3523
+ // autoUpdate loop; the trigger listeners re-registered here replace
3524
+ // neither. An overlay that is open right now would otherwise be left on
3525
+ // screen with outside-click, focus-loss and Escape all dead and its
3526
+ // position frozen — and, since the lock is deliberately retained, holding
3527
+ // the page with only its original trigger able to give it back.
3528
+ if (rewiringSameElement && this.showing) {
3529
+ this.setupDismissHandlers();
3530
+
3531
+ if (element.bib && (element.trigger || element.parentNode)) {
3532
+ element.cleanup = autoUpdate(
3533
+ element.trigger || element.parentNode,
3534
+ element.bib,
3535
+ () => {
3536
+ this.position();
3537
+ },
3538
+ );
3539
+ }
3540
+ }
3364
3541
  }
3365
3542
 
3366
- disconnect() {
3543
+ /**
3544
+ * Tears the floater's connections down.
3545
+ *
3546
+ * Two callers with different intent share this: a real teardown, which must
3547
+ * release the page scroll lock, and configure(), which re-runs it purely to
3548
+ * rewire a changed trigger and must leave an open overlay's lock alone.
3549
+ * @param {Object} [options] - Teardown options.
3550
+ * @param {Boolean} [options.teardown] - False when only rewiring; keeps the
3551
+ * page scroll lock held for an overlay that is still open.
3552
+ * @returns {void}
3553
+ */
3554
+ disconnect({ teardown = true } = {}) {
3367
3555
  this.cleanupHideHandlers();
3368
3556
 
3557
+ // Cancel any pending configureBibStrategy() retry. It would otherwise fire
3558
+ // a macrotask after the unlock below, re-lock the page, and leave it frozen
3559
+ // with the floater already gone. Cancelled on a rewire too: configure()
3560
+ // re-runs position() itself, so a stale retry has nothing to contribute.
3561
+ if (this._configureRetryId !== undefined) {
3562
+ clearTimeout(this._configureRetryId);
3563
+ this._configureRetryId = undefined;
3564
+ }
3565
+
3566
+ // Tearing down while the bib is still open would otherwise strand
3567
+ // body{position:fixed} and leave the page permanently unscrollable —
3568
+ // hideBib() is the only other unlock, and it never runs in this path.
3569
+ // Skipped when merely rewiring: configure() routes through here on every
3570
+ // triggerElement change, and releasing the lock there would unlock the page
3571
+ // behind an overlay that is still open (AB#1625424, AB#1625435).
3572
+ if (teardown) {
3573
+ this.lockScroll(false);
3574
+ }
3575
+
3369
3576
  const element = this.element;
3370
3577
  if (!element) {
3371
3578
  return;
@@ -3520,6 +3727,14 @@ class ComponentBase extends i$2 {
3520
3727
  this.isBibFullscreen = false;
3521
3728
  this.floater = new AuroFloatingUI(this, 'dialog');
3522
3729
 
3730
+ /**
3731
+ * @private
3732
+ * True once _teardown() has released the floater and nothing has rewired
3733
+ * it. Read by connectedCallback() to rewire a remounted dialog, and by
3734
+ * _teardown() to stay idempotent.
3735
+ */
3736
+ this._floaterTornDown = false;
3737
+
3523
3738
  const versioning = new AuroDependencyVersioning();
3524
3739
 
3525
3740
  /**
@@ -3580,10 +3795,12 @@ class ComponentBase extends i$2 {
3580
3795
 
3581
3796
  /**
3582
3797
  * DEPRECATED - use `close-button-appearance="inverse" instead.
3798
+ * @deprecated Use `close-button-appearance="inverse"` instead.
3583
3799
  */
3584
3800
  onDark: {
3585
3801
  type: Boolean,
3586
- reflect: true
3802
+ reflect: true,
3803
+ attribute: 'ondark'
3587
3804
  },
3588
3805
 
3589
3806
  /**
@@ -3601,7 +3818,8 @@ class ComponentBase extends i$2 {
3601
3818
  */
3602
3819
  isBibFullscreen: {
3603
3820
  type: Boolean,
3604
- reflect: true
3821
+ reflect: true,
3822
+ attribute: 'isbibfullscreen'
3605
3823
  },
3606
3824
 
3607
3825
  /**
@@ -3670,7 +3888,7 @@ class ComponentBase extends i$2 {
3670
3888
  slotWrapper.classList.remove("dialog-footer");
3671
3889
  }
3672
3890
 
3673
- this.floater.configure(this, this.floaterConfig.prefix);
3891
+ this._configureFloater();
3674
3892
 
3675
3893
  // Forward FloatingUI toggle event to backward-compatible 'toggle' event
3676
3894
  this.addEventListener('auroDialog-toggled', (event) => {
@@ -3679,9 +3897,15 @@ class ComponentBase extends i$2 {
3679
3897
  }
3680
3898
  });
3681
3899
 
3682
- // Always intercept the native ESC/cancel event so we can decide
3683
- // whether to honour it based on `modal`. Re-dispatch via FloatingUI
3684
- // for non-modal so the hide lifecycle runs correctly.
3900
+ // Defense-in-depth for the native ESC/cancel event. In practice neither
3901
+ // branch fires today: auro-library's floatingUI binds its own document-level
3902
+ // keydown handler, which closes a non-modal dialog via hideBib("keydown")
3903
+ // and, for a modal, swallows the keystroke with preventDefault() +
3904
+ // stopImmediatePropagation() before the platform CloseWatcher ever sees it
3905
+ // (AB#1613688). This listener is kept rather than deleted because it is the
3906
+ // component-side guarantee that a modal never self-closes on ESC: if the
3907
+ // library stops swallowing, the platform `cancel` would otherwise close a
3908
+ // modal dialog, which is exactly the behavior `modal` exists to prevent.
3685
3909
  this.dialog.addEventListener('cancel', (e) => {
3686
3910
  e.preventDefault();
3687
3911
  if (!this.modal) {
@@ -3728,12 +3952,78 @@ class ComponentBase extends i$2 {
3728
3952
  }
3729
3953
 
3730
3954
  if (changedProperties.has('triggerElement')) {
3731
- this.floater.configure(this, this.floaterConfig.prefix);
3955
+ this._configureFloater();
3956
+ }
3957
+ }
3958
+
3959
+ /**
3960
+ * @private
3961
+ * Wires the floater to this element and records that it is live, so a later
3962
+ * reconnect can tell a torn-down floater from one that never lost its wiring.
3963
+ * @returns {void}
3964
+ */
3965
+ _configureFloater() {
3966
+ this._floaterTornDown = false;
3967
+ this.floater.configure(this, this.floaterConfig.prefix);
3968
+ }
3969
+
3970
+ connectedCallback() {
3971
+ super.connectedCallback();
3972
+
3973
+ // Lit does not re-run firstUpdated() on reconnect, so without this a host
3974
+ // that removes and re-inserts the dialog across separate tasks — Vue
3975
+ // <keep-alive>, caching tab/accordion hosts, virtualized lists, or anything
3976
+ // that parks the node in a DocumentFragment in one task and inserts it in
3977
+ // the next — gets back a dead element: _teardown() dropped the trigger
3978
+ // listeners, ran element.cleanup(), tore down autoUpdate and released the
3979
+ // page scroll lock, and nothing rewired any of it.
3980
+ //
3981
+ // Only re-configure when a teardown actually ran. On the normal first
3982
+ // connect the flag is false and firstUpdated() owns the single configure
3983
+ // call; on a same-task DOM move the deferred teardown never runs, so the
3984
+ // flag stays false here too and the move is still a no-op.
3985
+ if (this.hasUpdated && this._floaterTornDown) {
3986
+ this._configureFloater();
3732
3987
  }
3733
3988
  }
3734
3989
 
3735
3990
  disconnectedCallback() {
3736
3991
  super.disconnectedCallback();
3992
+
3993
+ // A same-document DOM move (appendChild of an already-parented node) fires
3994
+ // disconnectedCallback and reconnects within the same task. Defer the
3995
+ // teardown one microtask and bail if the element came back, so a reparent
3996
+ // does not dismantle a dialog that is still open: floater.disconnect()
3997
+ // releases the page scroll lock and tears down autoUpdate, which would
3998
+ // leave the dialog visible over a scrollable page with nothing left to
3999
+ // re-lock it — the AB#1625424 symptom returning by a different route.
4000
+ //
4001
+ // A host that removes and re-inserts across tasks (not a move) is treated
4002
+ // as a real unmount, which is the correct reading of that sequence.
4003
+ queueMicrotask(() => {
4004
+ if (this.isConnected) {
4005
+ return;
4006
+ }
4007
+ this._teardown();
4008
+ });
4009
+ }
4010
+
4011
+ /**
4012
+ * @private
4013
+ * Releases everything the dialog owns outside its own shadow root.
4014
+ * Split out of disconnectedCallback so a DOM move can skip it.
4015
+ *
4016
+ * Idempotent: a repeated disconnect in the same task must not invoke
4017
+ * floater.disconnect() twice, and connectedCallback() reads the same flag to
4018
+ * decide whether the floater needs rewiring.
4019
+ * @returns {void}
4020
+ */
4021
+ _teardown() {
4022
+ if (this._floaterTornDown) {
4023
+ return;
4024
+ }
4025
+ this._floaterTornDown = true;
4026
+
3737
4027
  if (this._hidePopoverTimerId) {
3738
4028
  clearTimeout(this._hidePopoverTimerId);
3739
4029
  this._hidePopoverTimerId = undefined;
package/demo/index.md CHANGED
@@ -93,6 +93,7 @@
93
93
  <!-- AURO-GENERATED-CONTENT:END -->
94
94
  <auro-header level="2" id="modalExample">Modal</auro-header>
95
95
  <p>The <code>modal</code> attribute locks the user into interacting with the dialog. The close icon is not rendered and clicking outside the dialog will not dismiss it, so a <code>modal</code> dialog must include an explicit action button in the footer to dismiss it.</p>
96
+ <p>Freezing the page behind the dialog is not what <code>modal</code> controls — every dialog does that while it is open, and restores the scroll position on close. What <code>modal</code> changes is how the dialog can be dismissed. See the Accessibility page for details.</p>
96
97
  <div class="exampleWrapper">
97
98
  <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/modal.html) -->
98
99
  <!-- The below content is automatically added from ./../apiExamples/modal.html -->