@atlaskit/top-layer 6.2.4 → 6.2.5
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 +9 -0
- package/dist/cjs/testing/polyfill.js +144 -24
- package/dist/es2019/testing/polyfill.js +140 -25
- package/dist/esm/testing/polyfill.js +144 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/top-layer
|
|
2
2
|
|
|
3
|
+
## 6.2.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`b82a5dc33d636`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b82a5dc33d636) -
|
|
8
|
+
The testing polyfill now fires a cancelable cancel event when Escape targets a dialog. Unless
|
|
9
|
+
keydown or cancel is prevented, the dialog closes and emits its existing closing beforetoggle,
|
|
10
|
+
toggle and close events. Escape respects closedby and dialog/popover opening order.
|
|
11
|
+
|
|
3
12
|
## 6.2.4
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _bindEventListener = require("bind-event-listener");
|
|
4
|
+
// Node supports findLast, but the package defaults to older array type declarations.
|
|
5
|
+
/// <reference lib="es2023.array" />
|
|
4
6
|
/* eslint-disable compat/compat */
|
|
5
7
|
/**
|
|
6
8
|
* Popover API & HTMLDialogElement polyfills for jsdom.
|
|
@@ -482,40 +484,133 @@ function handleDocumentPointerup(event) {
|
|
|
482
484
|
});
|
|
483
485
|
}
|
|
484
486
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
487
|
+
/**
|
|
488
|
+
* Represents one dialog or popover opening so queued Escape requests cannot close a later opening.
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Tracks dialog and popover Escape targets in opening order.
|
|
493
|
+
*/
|
|
494
|
+
var escapeLayers = [];
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Removes an opening from Escape tracking so it cannot receive a later dismissal request.
|
|
498
|
+
*/
|
|
499
|
+
function unregisterEscapeLayer(_ref18) {
|
|
500
|
+
var element = _ref18.element;
|
|
501
|
+
var index = escapeLayers.findIndex(function (layer) {
|
|
502
|
+
return layer.element === element;
|
|
503
|
+
});
|
|
504
|
+
if (index !== -1) {
|
|
505
|
+
escapeLayers.splice(index, 1);
|
|
496
506
|
}
|
|
497
|
-
return null;
|
|
498
507
|
}
|
|
499
508
|
|
|
500
|
-
|
|
501
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Records a new opening, replacing any stale registration for the same element.
|
|
511
|
+
*/
|
|
512
|
+
function registerEscapeLayer(_ref19) {
|
|
513
|
+
var layer = _ref19.layer;
|
|
514
|
+
unregisterEscapeLayer({
|
|
515
|
+
element: layer.element
|
|
516
|
+
});
|
|
517
|
+
escapeLayers.push(layer);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Checks whether a tracked element is still connected and open, including direct DOM changes.
|
|
522
|
+
*/
|
|
523
|
+
function isEscapeLayerOpen(_ref20) {
|
|
524
|
+
var layer = _ref20.layer;
|
|
525
|
+
return layer.element.isConnected && (layer.type === 'dialog' ? layer.element.open : isPopoverOpen({
|
|
526
|
+
element: layer.element
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Removes registrations for closed or disconnected elements before choosing an Escape target.
|
|
532
|
+
*/
|
|
533
|
+
function pruneClosedEscapeLayers() {
|
|
534
|
+
escapeLayers.filter(function (layer) {
|
|
535
|
+
return !isEscapeLayerOpen({
|
|
536
|
+
layer: layer
|
|
537
|
+
});
|
|
538
|
+
}).forEach(function (layer) {
|
|
539
|
+
return unregisterEscapeLayer({
|
|
540
|
+
element: layer.element
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Checks whether a layer accepts Escape, using closedby and modality for dialogs.
|
|
547
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#computed-closed-by-state
|
|
548
|
+
*/
|
|
549
|
+
function acceptsEscape(_ref21) {
|
|
550
|
+
var _layer$element$getAtt;
|
|
551
|
+
var layer = _ref21.layer;
|
|
552
|
+
if (layer.type === 'popover') {
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
var closedBy = (_layer$element$getAtt = layer.element.getAttribute('closedby')) === null || _layer$element$getAtt === void 0 ? void 0 : _layer$element$getAtt.toLowerCase();
|
|
556
|
+
if (closedBy === 'none') {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (closedBy === 'any' || closedBy === 'closerequest') {
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
// Missing or invalid closedby enables Escape for modal dialogs only.
|
|
563
|
+
return Boolean(layer.element._isModal);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Dismisses the latest eligible Escape target, allowing application handlers to prevent dialog cancellation.
|
|
568
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#set-up-the-dialog-close-watcher
|
|
569
|
+
*/
|
|
502
570
|
function handleDocumentKeydown(event) {
|
|
503
|
-
|
|
571
|
+
// A prevented key event must not invoke its default dismissal action.
|
|
572
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
504
573
|
return;
|
|
505
574
|
}
|
|
506
|
-
|
|
507
|
-
|
|
575
|
+
pruneClosedEscapeLayers();
|
|
576
|
+
|
|
577
|
+
// Capture this opening so handlers cannot redirect the same Escape to another layer.
|
|
578
|
+
var layer = escapeLayers.findLast(function (candidate) {
|
|
579
|
+
return acceptsEscape({
|
|
580
|
+
layer: candidate
|
|
581
|
+
});
|
|
508
582
|
});
|
|
509
|
-
if (
|
|
510
|
-
topHint.hidePopover();
|
|
583
|
+
if (!layer) {
|
|
511
584
|
return;
|
|
512
585
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
topAuto.hidePopover();
|
|
586
|
+
if (layer.type === 'popover') {
|
|
587
|
+
// Popover dismissal is synchronous.
|
|
588
|
+
layer.element.hidePopover();
|
|
589
|
+
return;
|
|
518
590
|
}
|
|
591
|
+
|
|
592
|
+
// Defer the dialog default action until application keydown listeners have run.
|
|
593
|
+
setTimeout(function () {
|
|
594
|
+
// Handlers may prevent Escape, change eligibility, or close and reopen the target.
|
|
595
|
+
if (event.defaultPrevented || !escapeLayers.includes(layer) || !isEscapeLayerOpen({
|
|
596
|
+
layer: layer
|
|
597
|
+
}) || !acceptsEscape({
|
|
598
|
+
layer: layer
|
|
599
|
+
})) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
var allowed = layer.element.dispatchEvent(new Event('cancel', {
|
|
603
|
+
cancelable: true,
|
|
604
|
+
bubbles: false
|
|
605
|
+
}));
|
|
606
|
+
// The cancel handler can close or reopen the dialog; only close the original opening.
|
|
607
|
+
if (allowed && escapeLayers.includes(layer) && isEscapeLayerOpen({
|
|
608
|
+
layer: layer
|
|
609
|
+
})) {
|
|
610
|
+
// close() dispatches beforetoggle and queues toggle and close notifications.
|
|
611
|
+
layer.element.close();
|
|
612
|
+
}
|
|
613
|
+
}, 0);
|
|
519
614
|
}
|
|
520
615
|
if (typeof HTMLElement !== 'undefined') {
|
|
521
616
|
var schedulePopoverToggle = createToggleScheduler();
|
|
@@ -597,6 +692,12 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
597
692
|
});
|
|
598
693
|
if (stack && !stack.includes(this)) {
|
|
599
694
|
stack.push(this);
|
|
695
|
+
registerEscapeLayer({
|
|
696
|
+
layer: {
|
|
697
|
+
type: 'popover',
|
|
698
|
+
element: this
|
|
699
|
+
}
|
|
700
|
+
});
|
|
600
701
|
}
|
|
601
702
|
clearOpacityOnOpenToggle({
|
|
602
703
|
element: this
|
|
@@ -632,6 +733,9 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
632
733
|
cancelable: false
|
|
633
734
|
});
|
|
634
735
|
this.removeAttribute('data-popover-open');
|
|
736
|
+
unregisterEscapeLayer({
|
|
737
|
+
element: this
|
|
738
|
+
});
|
|
635
739
|
var stack = stackForMode({
|
|
636
740
|
mode: getPopoverMode({
|
|
637
741
|
element: this
|
|
@@ -763,6 +867,12 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
763
867
|
});
|
|
764
868
|
this.setAttribute('open', '');
|
|
765
869
|
this._isModal = true;
|
|
870
|
+
registerEscapeLayer({
|
|
871
|
+
layer: {
|
|
872
|
+
type: 'dialog',
|
|
873
|
+
element: this
|
|
874
|
+
}
|
|
875
|
+
});
|
|
766
876
|
scheduleDialogToggle({
|
|
767
877
|
dialog: this,
|
|
768
878
|
newState: 'open',
|
|
@@ -785,6 +895,13 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
785
895
|
});
|
|
786
896
|
this.setAttribute('open', '');
|
|
787
897
|
this._isModal = false;
|
|
898
|
+
// Non-modal dialogs can opt into Escape through closedby.
|
|
899
|
+
registerEscapeLayer({
|
|
900
|
+
layer: {
|
|
901
|
+
type: 'dialog',
|
|
902
|
+
element: this
|
|
903
|
+
}
|
|
904
|
+
});
|
|
788
905
|
scheduleDialogToggle({
|
|
789
906
|
dialog: this,
|
|
790
907
|
newState: 'open',
|
|
@@ -808,6 +925,9 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
808
925
|
cancelable: false
|
|
809
926
|
});
|
|
810
927
|
this.removeAttribute('open');
|
|
928
|
+
unregisterEscapeLayer({
|
|
929
|
+
element: this
|
|
930
|
+
});
|
|
811
931
|
this._isModal = false;
|
|
812
932
|
if (returnValue !== undefined) {
|
|
813
933
|
this.returnValue = returnValue;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Node supports findLast, but the package defaults to older array type declarations.
|
|
2
|
+
/// <reference lib="es2023.array" />
|
|
1
3
|
/* eslint-disable compat/compat */
|
|
2
4
|
/**
|
|
3
5
|
* Popover API & HTMLDialogElement polyfills for jsdom.
|
|
@@ -495,41 +497,129 @@ function handleDocumentPointerup(event) {
|
|
|
495
497
|
});
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
/**
|
|
501
|
+
* Represents one dialog or popover opening so queued Escape requests cannot close a later opening.
|
|
502
|
+
*/
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Tracks dialog and popover Escape targets in opening order.
|
|
506
|
+
*/
|
|
507
|
+
const escapeLayers = [];
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Removes an opening from Escape tracking so it cannot receive a later dismissal request.
|
|
511
|
+
*/
|
|
512
|
+
function unregisterEscapeLayer({
|
|
513
|
+
element
|
|
503
514
|
}) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
return candidate;
|
|
508
|
-
}
|
|
509
|
-
stack.pop();
|
|
515
|
+
const index = escapeLayers.findIndex(layer => layer.element === element);
|
|
516
|
+
if (index !== -1) {
|
|
517
|
+
escapeLayers.splice(index, 1);
|
|
510
518
|
}
|
|
511
|
-
return null;
|
|
512
519
|
}
|
|
513
520
|
|
|
514
|
-
|
|
515
|
-
|
|
521
|
+
/**
|
|
522
|
+
* Records a new opening, replacing any stale registration for the same element.
|
|
523
|
+
*/
|
|
524
|
+
function registerEscapeLayer({
|
|
525
|
+
layer
|
|
526
|
+
}) {
|
|
527
|
+
unregisterEscapeLayer({
|
|
528
|
+
element: layer.element
|
|
529
|
+
});
|
|
530
|
+
escapeLayers.push(layer);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Checks whether a tracked element is still connected and open, including direct DOM changes.
|
|
535
|
+
*/
|
|
536
|
+
function isEscapeLayerOpen({
|
|
537
|
+
layer
|
|
538
|
+
}) {
|
|
539
|
+
return layer.element.isConnected && (layer.type === 'dialog' ? layer.element.open : isPopoverOpen({
|
|
540
|
+
element: layer.element
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Removes registrations for closed or disconnected elements before choosing an Escape target.
|
|
546
|
+
*/
|
|
547
|
+
function pruneClosedEscapeLayers() {
|
|
548
|
+
escapeLayers.filter(layer => !isEscapeLayerOpen({
|
|
549
|
+
layer
|
|
550
|
+
})).forEach(layer => unregisterEscapeLayer({
|
|
551
|
+
element: layer.element
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Checks whether a layer accepts Escape, using closedby and modality for dialogs.
|
|
557
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#computed-closed-by-state
|
|
558
|
+
*/
|
|
559
|
+
function acceptsEscape({
|
|
560
|
+
layer
|
|
561
|
+
}) {
|
|
562
|
+
var _layer$element$getAtt;
|
|
563
|
+
if (layer.type === 'popover') {
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
const closedBy = (_layer$element$getAtt = layer.element.getAttribute('closedby')) === null || _layer$element$getAtt === void 0 ? void 0 : _layer$element$getAtt.toLowerCase();
|
|
567
|
+
if (closedBy === 'none') {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
if (closedBy === 'any' || closedBy === 'closerequest') {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
// Missing or invalid closedby enables Escape for modal dialogs only.
|
|
574
|
+
return Boolean(layer.element._isModal);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Dismisses the latest eligible Escape target, allowing application handlers to prevent dialog cancellation.
|
|
579
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#set-up-the-dialog-close-watcher
|
|
580
|
+
*/
|
|
516
581
|
function handleDocumentKeydown(event) {
|
|
517
|
-
|
|
582
|
+
// A prevented key event must not invoke its default dismissal action.
|
|
583
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
518
584
|
return;
|
|
519
585
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
586
|
+
pruneClosedEscapeLayers();
|
|
587
|
+
|
|
588
|
+
// Capture this opening so handlers cannot redirect the same Escape to another layer.
|
|
589
|
+
const layer = escapeLayers.findLast(candidate => acceptsEscape({
|
|
590
|
+
layer: candidate
|
|
591
|
+
}));
|
|
592
|
+
if (!layer) {
|
|
525
593
|
return;
|
|
526
594
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
topAuto.hidePopover();
|
|
595
|
+
if (layer.type === 'popover') {
|
|
596
|
+
// Popover dismissal is synchronous.
|
|
597
|
+
layer.element.hidePopover();
|
|
598
|
+
return;
|
|
532
599
|
}
|
|
600
|
+
|
|
601
|
+
// Defer the dialog default action until application keydown listeners have run.
|
|
602
|
+
setTimeout(() => {
|
|
603
|
+
// Handlers may prevent Escape, change eligibility, or close and reopen the target.
|
|
604
|
+
if (event.defaultPrevented || !escapeLayers.includes(layer) || !isEscapeLayerOpen({
|
|
605
|
+
layer
|
|
606
|
+
}) || !acceptsEscape({
|
|
607
|
+
layer
|
|
608
|
+
})) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const allowed = layer.element.dispatchEvent(new Event('cancel', {
|
|
612
|
+
cancelable: true,
|
|
613
|
+
bubbles: false
|
|
614
|
+
}));
|
|
615
|
+
// The cancel handler can close or reopen the dialog; only close the original opening.
|
|
616
|
+
if (allowed && escapeLayers.includes(layer) && isEscapeLayerOpen({
|
|
617
|
+
layer
|
|
618
|
+
})) {
|
|
619
|
+
// close() dispatches beforetoggle and queues toggle and close notifications.
|
|
620
|
+
layer.element.close();
|
|
621
|
+
}
|
|
622
|
+
}, 0);
|
|
533
623
|
}
|
|
534
624
|
if (typeof HTMLElement !== 'undefined') {
|
|
535
625
|
const schedulePopoverToggle = createToggleScheduler();
|
|
@@ -611,6 +701,12 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
611
701
|
});
|
|
612
702
|
if (stack && !stack.includes(this)) {
|
|
613
703
|
stack.push(this);
|
|
704
|
+
registerEscapeLayer({
|
|
705
|
+
layer: {
|
|
706
|
+
type: 'popover',
|
|
707
|
+
element: this
|
|
708
|
+
}
|
|
709
|
+
});
|
|
614
710
|
}
|
|
615
711
|
clearOpacityOnOpenToggle({
|
|
616
712
|
element: this
|
|
@@ -646,6 +742,9 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
646
742
|
cancelable: false
|
|
647
743
|
});
|
|
648
744
|
this.removeAttribute('data-popover-open');
|
|
745
|
+
unregisterEscapeLayer({
|
|
746
|
+
element: this
|
|
747
|
+
});
|
|
649
748
|
const stack = stackForMode({
|
|
650
749
|
mode: getPopoverMode({
|
|
651
750
|
element: this
|
|
@@ -773,6 +872,12 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
773
872
|
});
|
|
774
873
|
this.setAttribute('open', '');
|
|
775
874
|
this._isModal = true;
|
|
875
|
+
registerEscapeLayer({
|
|
876
|
+
layer: {
|
|
877
|
+
type: 'dialog',
|
|
878
|
+
element: this
|
|
879
|
+
}
|
|
880
|
+
});
|
|
776
881
|
scheduleDialogToggle({
|
|
777
882
|
dialog: this,
|
|
778
883
|
newState: 'open',
|
|
@@ -795,6 +900,13 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
795
900
|
});
|
|
796
901
|
this.setAttribute('open', '');
|
|
797
902
|
this._isModal = false;
|
|
903
|
+
// Non-modal dialogs can opt into Escape through closedby.
|
|
904
|
+
registerEscapeLayer({
|
|
905
|
+
layer: {
|
|
906
|
+
type: 'dialog',
|
|
907
|
+
element: this
|
|
908
|
+
}
|
|
909
|
+
});
|
|
798
910
|
scheduleDialogToggle({
|
|
799
911
|
dialog: this,
|
|
800
912
|
newState: 'open',
|
|
@@ -818,6 +930,9 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
818
930
|
cancelable: false
|
|
819
931
|
});
|
|
820
932
|
this.removeAttribute('open');
|
|
933
|
+
unregisterEscapeLayer({
|
|
934
|
+
element: this
|
|
935
|
+
});
|
|
821
936
|
this._isModal = false;
|
|
822
937
|
if (returnValue !== undefined) {
|
|
823
938
|
this.returnValue = returnValue;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Node supports findLast, but the package defaults to older array type declarations.
|
|
2
|
+
/// <reference lib="es2023.array" />
|
|
1
3
|
/* eslint-disable compat/compat */
|
|
2
4
|
/**
|
|
3
5
|
* Popover API & HTMLDialogElement polyfills for jsdom.
|
|
@@ -480,40 +482,133 @@ function handleDocumentPointerup(event) {
|
|
|
480
482
|
});
|
|
481
483
|
}
|
|
482
484
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
485
|
+
/**
|
|
486
|
+
* Represents one dialog or popover opening so queued Escape requests cannot close a later opening.
|
|
487
|
+
*/
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Tracks dialog and popover Escape targets in opening order.
|
|
491
|
+
*/
|
|
492
|
+
var escapeLayers = [];
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Removes an opening from Escape tracking so it cannot receive a later dismissal request.
|
|
496
|
+
*/
|
|
497
|
+
function unregisterEscapeLayer(_ref18) {
|
|
498
|
+
var element = _ref18.element;
|
|
499
|
+
var index = escapeLayers.findIndex(function (layer) {
|
|
500
|
+
return layer.element === element;
|
|
501
|
+
});
|
|
502
|
+
if (index !== -1) {
|
|
503
|
+
escapeLayers.splice(index, 1);
|
|
494
504
|
}
|
|
495
|
-
return null;
|
|
496
505
|
}
|
|
497
506
|
|
|
498
|
-
|
|
499
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Records a new opening, replacing any stale registration for the same element.
|
|
509
|
+
*/
|
|
510
|
+
function registerEscapeLayer(_ref19) {
|
|
511
|
+
var layer = _ref19.layer;
|
|
512
|
+
unregisterEscapeLayer({
|
|
513
|
+
element: layer.element
|
|
514
|
+
});
|
|
515
|
+
escapeLayers.push(layer);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Checks whether a tracked element is still connected and open, including direct DOM changes.
|
|
520
|
+
*/
|
|
521
|
+
function isEscapeLayerOpen(_ref20) {
|
|
522
|
+
var layer = _ref20.layer;
|
|
523
|
+
return layer.element.isConnected && (layer.type === 'dialog' ? layer.element.open : isPopoverOpen({
|
|
524
|
+
element: layer.element
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Removes registrations for closed or disconnected elements before choosing an Escape target.
|
|
530
|
+
*/
|
|
531
|
+
function pruneClosedEscapeLayers() {
|
|
532
|
+
escapeLayers.filter(function (layer) {
|
|
533
|
+
return !isEscapeLayerOpen({
|
|
534
|
+
layer: layer
|
|
535
|
+
});
|
|
536
|
+
}).forEach(function (layer) {
|
|
537
|
+
return unregisterEscapeLayer({
|
|
538
|
+
element: layer.element
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Checks whether a layer accepts Escape, using closedby and modality for dialogs.
|
|
545
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#computed-closed-by-state
|
|
546
|
+
*/
|
|
547
|
+
function acceptsEscape(_ref21) {
|
|
548
|
+
var _layer$element$getAtt;
|
|
549
|
+
var layer = _ref21.layer;
|
|
550
|
+
if (layer.type === 'popover') {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
var closedBy = (_layer$element$getAtt = layer.element.getAttribute('closedby')) === null || _layer$element$getAtt === void 0 ? void 0 : _layer$element$getAtt.toLowerCase();
|
|
554
|
+
if (closedBy === 'none') {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
if (closedBy === 'any' || closedBy === 'closerequest') {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
// Missing or invalid closedby enables Escape for modal dialogs only.
|
|
561
|
+
return Boolean(layer.element._isModal);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Dismisses the latest eligible Escape target, allowing application handlers to prevent dialog cancellation.
|
|
566
|
+
* Spec: https://html.spec.whatwg.org/multipage/interactive-elements.html#set-up-the-dialog-close-watcher
|
|
567
|
+
*/
|
|
500
568
|
function handleDocumentKeydown(event) {
|
|
501
|
-
|
|
569
|
+
// A prevented key event must not invoke its default dismissal action.
|
|
570
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
502
571
|
return;
|
|
503
572
|
}
|
|
504
|
-
|
|
505
|
-
|
|
573
|
+
pruneClosedEscapeLayers();
|
|
574
|
+
|
|
575
|
+
// Capture this opening so handlers cannot redirect the same Escape to another layer.
|
|
576
|
+
var layer = escapeLayers.findLast(function (candidate) {
|
|
577
|
+
return acceptsEscape({
|
|
578
|
+
layer: candidate
|
|
579
|
+
});
|
|
506
580
|
});
|
|
507
|
-
if (
|
|
508
|
-
topHint.hidePopover();
|
|
581
|
+
if (!layer) {
|
|
509
582
|
return;
|
|
510
583
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
topAuto.hidePopover();
|
|
584
|
+
if (layer.type === 'popover') {
|
|
585
|
+
// Popover dismissal is synchronous.
|
|
586
|
+
layer.element.hidePopover();
|
|
587
|
+
return;
|
|
516
588
|
}
|
|
589
|
+
|
|
590
|
+
// Defer the dialog default action until application keydown listeners have run.
|
|
591
|
+
setTimeout(function () {
|
|
592
|
+
// Handlers may prevent Escape, change eligibility, or close and reopen the target.
|
|
593
|
+
if (event.defaultPrevented || !escapeLayers.includes(layer) || !isEscapeLayerOpen({
|
|
594
|
+
layer: layer
|
|
595
|
+
}) || !acceptsEscape({
|
|
596
|
+
layer: layer
|
|
597
|
+
})) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
var allowed = layer.element.dispatchEvent(new Event('cancel', {
|
|
601
|
+
cancelable: true,
|
|
602
|
+
bubbles: false
|
|
603
|
+
}));
|
|
604
|
+
// The cancel handler can close or reopen the dialog; only close the original opening.
|
|
605
|
+
if (allowed && escapeLayers.includes(layer) && isEscapeLayerOpen({
|
|
606
|
+
layer: layer
|
|
607
|
+
})) {
|
|
608
|
+
// close() dispatches beforetoggle and queues toggle and close notifications.
|
|
609
|
+
layer.element.close();
|
|
610
|
+
}
|
|
611
|
+
}, 0);
|
|
517
612
|
}
|
|
518
613
|
if (typeof HTMLElement !== 'undefined') {
|
|
519
614
|
var schedulePopoverToggle = createToggleScheduler();
|
|
@@ -595,6 +690,12 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
595
690
|
});
|
|
596
691
|
if (stack && !stack.includes(this)) {
|
|
597
692
|
stack.push(this);
|
|
693
|
+
registerEscapeLayer({
|
|
694
|
+
layer: {
|
|
695
|
+
type: 'popover',
|
|
696
|
+
element: this
|
|
697
|
+
}
|
|
698
|
+
});
|
|
598
699
|
}
|
|
599
700
|
clearOpacityOnOpenToggle({
|
|
600
701
|
element: this
|
|
@@ -630,6 +731,9 @@ if (typeof HTMLElement !== 'undefined') {
|
|
|
630
731
|
cancelable: false
|
|
631
732
|
});
|
|
632
733
|
this.removeAttribute('data-popover-open');
|
|
734
|
+
unregisterEscapeLayer({
|
|
735
|
+
element: this
|
|
736
|
+
});
|
|
633
737
|
var stack = stackForMode({
|
|
634
738
|
mode: getPopoverMode({
|
|
635
739
|
element: this
|
|
@@ -761,6 +865,12 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
761
865
|
});
|
|
762
866
|
this.setAttribute('open', '');
|
|
763
867
|
this._isModal = true;
|
|
868
|
+
registerEscapeLayer({
|
|
869
|
+
layer: {
|
|
870
|
+
type: 'dialog',
|
|
871
|
+
element: this
|
|
872
|
+
}
|
|
873
|
+
});
|
|
764
874
|
scheduleDialogToggle({
|
|
765
875
|
dialog: this,
|
|
766
876
|
newState: 'open',
|
|
@@ -783,6 +893,13 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
783
893
|
});
|
|
784
894
|
this.setAttribute('open', '');
|
|
785
895
|
this._isModal = false;
|
|
896
|
+
// Non-modal dialogs can opt into Escape through closedby.
|
|
897
|
+
registerEscapeLayer({
|
|
898
|
+
layer: {
|
|
899
|
+
type: 'dialog',
|
|
900
|
+
element: this
|
|
901
|
+
}
|
|
902
|
+
});
|
|
786
903
|
scheduleDialogToggle({
|
|
787
904
|
dialog: this,
|
|
788
905
|
newState: 'open',
|
|
@@ -806,6 +923,9 @@ if (typeof HTMLDialogElement !== 'undefined') {
|
|
|
806
923
|
cancelable: false
|
|
807
924
|
});
|
|
808
925
|
this.removeAttribute('open');
|
|
926
|
+
unregisterEscapeLayer({
|
|
927
|
+
element: this
|
|
928
|
+
});
|
|
809
929
|
this._isModal = false;
|
|
810
930
|
if (returnValue !== undefined) {
|
|
811
931
|
this.returnValue = returnValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/top-layer",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.5",
|
|
4
4
|
"description": "Low-level layering primitives based on popover and dialog",
|
|
5
5
|
"homepage": "https://atlassian.design/components/top-layer",
|
|
6
6
|
"repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
|