@atlassian/aui 9.5.2 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aui/aui-prototyping.css +3 -3
- package/dist/aui/aui-prototyping.css.map +1 -1
- package/dist/aui/aui-prototyping.js +9 -9
- package/dist/aui/aui-prototyping.js.map +1 -1
- package/dist/aui/aui-prototyping.nodeps.css +3 -3
- package/dist/aui/aui-prototyping.nodeps.css.map +1 -1
- package/dist/aui/aui-prototyping.nodeps.js +9 -9
- package/dist/aui/aui-prototyping.nodeps.js.map +1 -1
- package/package.json +7 -7
- package/src/css-vendor/jquery/plugins/jquery.select2.css +123 -43
- package/src/js/aui/blanket.js +16 -18
- package/src/js/aui/dialog2.js +97 -19
- package/src/js/aui/dropdown2.js +84 -51
- package/src/js/aui/focus-manager.js +10 -2
- package/src/js/aui/inline-dialog2.js +61 -41
- package/src/js/aui/internal/i18n/aui.js +1 -0
- package/src/js/aui/internal/select/suggestions-model.js +4 -4
- package/src/js/aui/internal/select/suggestions-view.js +11 -11
- package/src/js/aui/internal/select/template.js +4 -4
- package/src/js/aui/select.js +67 -47
- package/src/js/aui/tooltip.js +1 -1
- package/src/js-vendor/jquery/plugins/jquery.select2.js +931 -458
- package/src/less/dialog2.less +0 -4
- package/src/less/dropdown2.less +12 -0
- package/src/less/forms-current.less +1 -0
- package/src/less/single-select.less +4 -0
package/src/js/aui/dropdown2.js
CHANGED
|
@@ -29,17 +29,16 @@ function setDropdownTriggerActiveState(trigger, isActive) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Focus the appropriate thing when a dropdown is shown.
|
|
31
31
|
* The element focussed will vary depending on the type of dropdown and
|
|
32
|
-
* the input modality
|
|
32
|
+
* the input modality.
|
|
33
33
|
*
|
|
34
34
|
* @param {HTMLElement} dropdown - the dropdown being shown
|
|
35
35
|
* @param {Event} [e] - the event that triggered the dropdown being shown
|
|
36
36
|
*/
|
|
37
37
|
function handleFocus(dropdown, e = {}) {
|
|
38
|
-
if (e && e.type) {
|
|
39
|
-
const isMouseEvent = e.type.indexOf('mouse') > -1 || e.type.indexOf('hover') > -1;
|
|
38
|
+
if (e && e.type && e.type.indexOf('mouse') === -1) {
|
|
40
39
|
const isKeyDownEvent = e.type.indexOf('key') > -1;
|
|
41
40
|
if (dropdown.isSubmenu) {
|
|
42
|
-
|
|
41
|
+
dropdown.focusItem(0);
|
|
43
42
|
} else if (isKeyDownEvent){
|
|
44
43
|
const isUpArrow = e.keyCode === keyCode.UP;
|
|
45
44
|
// set focus to last item in the menu to navigate bottom -> up
|
|
@@ -104,17 +103,6 @@ function setDropdownContents (dropdown, json) {
|
|
|
104
103
|
state(dropdown).set('loading-state', SUCCESS);
|
|
105
104
|
dropdown.innerHTML = makeAsyncDropdownContents(json);
|
|
106
105
|
skate.init(dropdown);
|
|
107
|
-
/* TODO: where to check for focus
|
|
108
|
-
* to make the loading state more accessible, we might need to move focus inside the dropdown container,
|
|
109
|
-
* which would change the check from "focus is on trigger" to "focus is within dropdown".
|
|
110
|
-
*/
|
|
111
|
-
doIfTrigger(dropdown, function(trigger) {
|
|
112
|
-
// Only manage focus if the focus was on the triggering element at the time it loaded.
|
|
113
|
-
// Otherwise, the user likely moved on and doesn't want it any more.
|
|
114
|
-
if (document.activeElement === trigger) {
|
|
115
|
-
handleFocus(dropdown);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
106
|
}
|
|
119
107
|
|
|
120
108
|
function setDropdownErrorState (dropdown) {
|
|
@@ -298,8 +286,7 @@ function triggerCreated (trigger) {
|
|
|
298
286
|
.on('aui-button-invoke', handleIt)
|
|
299
287
|
.on('click', handleIt)
|
|
300
288
|
.on('keydown', handleKeydown)
|
|
301
|
-
.on('mouseenter', handleMouseEnter)
|
|
302
|
-
;
|
|
289
|
+
.on('mouseenter', handleMouseEnter);
|
|
303
290
|
}
|
|
304
291
|
|
|
305
292
|
var triggerPrototype = {
|
|
@@ -563,17 +550,16 @@ function bindDropdownBehaviourToLayer(dropdown) {
|
|
|
563
550
|
getAllDropdownItems(dropdown).removeClass('active aui-dropdown2-active');
|
|
564
551
|
|
|
565
552
|
doIfTrigger(dropdown, function (trigger) {
|
|
553
|
+
setDropdownTriggerActiveState(trigger, false);
|
|
554
|
+
|
|
555
|
+
// Focus the submenu trigger when the submenu is closed
|
|
566
556
|
if (wasProbablyClosedViaKeyboard()) {
|
|
567
557
|
trigger.focus();
|
|
568
|
-
setDropdownTriggerActiveState(trigger, trigger.hasSubmenu && trigger.hasSubmenu());
|
|
569
|
-
} else {
|
|
570
|
-
setDropdownTriggerActiveState(trigger, false);
|
|
571
558
|
}
|
|
572
559
|
});
|
|
573
560
|
|
|
574
561
|
// Gets set by submenu trigger invocation. Bad coupling point?
|
|
575
562
|
delete dropdown.isSubmenu;
|
|
576
|
-
setTrigger(dropdown, null);
|
|
577
563
|
});
|
|
578
564
|
}
|
|
579
565
|
|
|
@@ -606,12 +592,13 @@ function bindItemInteractionBehaviourToDropdown (dropdown) {
|
|
|
606
592
|
keyboardCloseDetected();
|
|
607
593
|
} else if (e.keyCode === keyCode.TAB) {
|
|
608
594
|
keyboardCloseDetected();
|
|
609
|
-
|
|
595
|
+
// On TAB we should close the menu and all submenus.
|
|
596
|
+
dropdown.hideAll(false);
|
|
610
597
|
}
|
|
611
598
|
});
|
|
612
599
|
|
|
613
600
|
const hideIfNotSubmenuAndNotInteractive = function(e) {
|
|
614
|
-
|
|
601
|
+
const item = e.currentTarget;
|
|
615
602
|
|
|
616
603
|
if (item.getAttribute('aria-disabled') === 'true') {
|
|
617
604
|
e.preventDefault();
|
|
@@ -620,50 +607,48 @@ function bindItemInteractionBehaviourToDropdown (dropdown) {
|
|
|
620
607
|
|
|
621
608
|
const isSubmenuTrigger = e.currentTarget.hasSubmenu && e.currentTarget.hasSubmenu();
|
|
622
609
|
if (!isSubmenuTrigger && !item.classList.contains('aui-dropdown2-interactive')) {
|
|
623
|
-
|
|
624
|
-
var theMenu = dropdown;
|
|
625
|
-
do {
|
|
626
|
-
var dd = layer(theMenu);
|
|
627
|
-
theMenu = layer(theMenu).below();
|
|
628
|
-
if (dd.$el.is('.aui-dropdown2')) {
|
|
629
|
-
dd.hide(e);
|
|
630
|
-
}
|
|
631
|
-
} while (theMenu);
|
|
610
|
+
dropdown.hideAll(true);
|
|
632
611
|
}
|
|
633
|
-
}
|
|
612
|
+
}
|
|
634
613
|
|
|
635
614
|
$dropdown.on('click keydown', 'a, button, [role="menuitem"], [role="menuitemcheckbox"], [role="checkbox"], [role="menuitemradio"], [role="radio"]', function (e) {
|
|
636
615
|
if (!belongsToDropdown(e.target)) {
|
|
637
616
|
return;
|
|
638
617
|
}
|
|
639
|
-
const item = e.currentTarget;
|
|
640
618
|
const eventKeyCode = e.keyCode;
|
|
641
619
|
const isEnter = eventKeyCode === keyCode.ENTER;
|
|
642
620
|
const isSpace = eventKeyCode === keyCode.SPACE;
|
|
643
621
|
|
|
644
|
-
|
|
645
|
-
// that the dropdown remains visible to allow the click event to eventually fire.
|
|
646
|
-
const itemIgnoresEnter = isEnter && $(item).is('a[href], button');
|
|
647
|
-
|
|
648
|
-
if (!itemIgnoresEnter && (e.type === 'click' || isEnter || isSpace)) {
|
|
622
|
+
if ((e.type === 'click' || isEnter || isSpace)) {
|
|
649
623
|
hideIfNotSubmenuAndNotInteractive(e);
|
|
650
624
|
}
|
|
651
625
|
});
|
|
652
626
|
|
|
653
|
-
// close
|
|
627
|
+
// close all submenus when the mouse moves over items other than its trigger
|
|
654
628
|
$dropdown.on('mouseenter', 'a, button, [role="menuitem"], [role="menuitemcheckbox"], [role="checkbox"], [role="menuitemradio"], [role="radio"]', function (e) {
|
|
655
629
|
if (!belongsToDropdown(e.target)) {
|
|
656
630
|
return;
|
|
657
631
|
}
|
|
658
|
-
var item = e.currentTarget;
|
|
659
|
-
var hasSubmenu = item.hasSubmenu && item.hasSubmenu();
|
|
660
|
-
|
|
661
|
-
if (!e.isDefaultPrevented() && !hasSubmenu) {
|
|
662
|
-
var maybeALayer = layer(dropdown).above();
|
|
663
632
|
|
|
664
|
-
|
|
665
|
-
|
|
633
|
+
// Focus the current element to allow Screen Reader to announce a focused menuitem.
|
|
634
|
+
e.currentTarget.focus();
|
|
635
|
+
|
|
636
|
+
// We should close all submenus above which are not related to the focused trigger.
|
|
637
|
+
// For example if we hover over the trigger for submenu for this trigger be shown, when we move
|
|
638
|
+
// focus/mouse on the another trigger the previous will be closed.
|
|
639
|
+
let layerAbove = $(layer(dropdown).above()).get(0);
|
|
640
|
+
while (layerAbove && layerAbove.isDropdown && layerAbove !== dropdown) {
|
|
641
|
+
const layerId = layerAbove.getAttribute('id');
|
|
642
|
+
const targetDropdownId = e.target.getAttribute('aria-controls');
|
|
643
|
+
const nextLayer = layer(layerAbove).above();
|
|
644
|
+
|
|
645
|
+
if (targetDropdownId !== layerId) {
|
|
646
|
+
// We should .hide() only after we get nextLayer,
|
|
647
|
+
// otherwise we will get the first visible layer.
|
|
648
|
+
layer(layerAbove).hide();
|
|
666
649
|
}
|
|
650
|
+
|
|
651
|
+
layerAbove = $(nextLayer).get(0);
|
|
667
652
|
}
|
|
668
653
|
});
|
|
669
654
|
}
|
|
@@ -751,7 +736,16 @@ var dropdownPrototype = {
|
|
|
751
736
|
* @returns {HTMLElement}
|
|
752
737
|
*/
|
|
753
738
|
show: function (e) {
|
|
754
|
-
|
|
739
|
+
const dropdown = this;
|
|
740
|
+
|
|
741
|
+
// In case we have 2 triggers for the same submenu
|
|
742
|
+
// we can have two active/expanded triggers at the same time.
|
|
743
|
+
// In order to avoid such behavior we need to reset current active trigger,
|
|
744
|
+
// before we update/replace it.
|
|
745
|
+
doIfTrigger(dropdown, (trigger) => {
|
|
746
|
+
setDropdownTriggerActiveState(trigger, false);
|
|
747
|
+
});
|
|
748
|
+
|
|
755
749
|
if (e && e.currentTarget && e.currentTarget.classList.contains('aui-dropdown2-trigger')) {
|
|
756
750
|
setTrigger(dropdown, e.currentTarget);
|
|
757
751
|
}
|
|
@@ -759,6 +753,7 @@ var dropdownPrototype = {
|
|
|
759
753
|
layer(dropdown).show();
|
|
760
754
|
|
|
761
755
|
doIfTrigger(dropdown, function (trigger) {
|
|
756
|
+
setDropdownTriggerActiveState(trigger, true);
|
|
762
757
|
setLayerAlignment(dropdown, trigger);
|
|
763
758
|
});
|
|
764
759
|
|
|
@@ -783,6 +778,35 @@ var dropdownPrototype = {
|
|
|
783
778
|
return this;
|
|
784
779
|
},
|
|
785
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Explicitly hides the whole menu with submenus and focus the initial triggers based on provided param.
|
|
783
|
+
*
|
|
784
|
+
* @param focusTrigger - Get focus back to the initial trigger when menu is closed. Default value is "true".
|
|
785
|
+
* @returns {HTMLElement}
|
|
786
|
+
*/
|
|
787
|
+
hideAll: function (focusTrigger = true) {
|
|
788
|
+
let dropdownMenuBelow = layer(this).below();
|
|
789
|
+
let previousLayout = layer(this);
|
|
790
|
+
|
|
791
|
+
while (dropdownMenuBelow && dropdownMenuBelow.get(0).isDropdown) {
|
|
792
|
+
previousLayout = layer(dropdownMenuBelow);
|
|
793
|
+
dropdownMenuBelow = previousLayout.below();
|
|
794
|
+
previousLayout.hide();
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Get focus back to the dropdown's trigger after
|
|
798
|
+
// the dropdown is closed.
|
|
799
|
+
if (previousLayout && focusTrigger) {
|
|
800
|
+
doIfTrigger(previousLayout.el, (trigger) => {
|
|
801
|
+
trigger.focus();
|
|
802
|
+
setTrigger(previousLayout.el, null);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
layer(this).hide();
|
|
807
|
+
return this;
|
|
808
|
+
},
|
|
809
|
+
|
|
786
810
|
/**
|
|
787
811
|
* Shifts explicit focus to the next available item in the menu
|
|
788
812
|
*
|
|
@@ -796,6 +820,8 @@ var dropdownPrototype = {
|
|
|
796
820
|
if ($items.last()[0] !== selected) {
|
|
797
821
|
idx = $items.toArray().indexOf(selected);
|
|
798
822
|
this.focusItem($items.get(idx + 1));
|
|
823
|
+
} else {
|
|
824
|
+
this.focusItem(0);
|
|
799
825
|
}
|
|
800
826
|
},
|
|
801
827
|
|
|
@@ -812,6 +838,8 @@ var dropdownPrototype = {
|
|
|
812
838
|
if ($items.first()[0] !== selected) {
|
|
813
839
|
idx = $items.toArray().indexOf(selected);
|
|
814
840
|
this.focusItem($items.get(idx - 1));
|
|
841
|
+
} else {
|
|
842
|
+
this.focusItem($items.length - 1);
|
|
815
843
|
}
|
|
816
844
|
},
|
|
817
845
|
|
|
@@ -836,7 +864,12 @@ var dropdownPrototype = {
|
|
|
836
864
|
*/
|
|
837
865
|
isVisible: function () {
|
|
838
866
|
return layer(this).isVisible();
|
|
839
|
-
}
|
|
867
|
+
},
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Shows that current element is a dropdown
|
|
871
|
+
*/
|
|
872
|
+
isDropdown: true
|
|
840
873
|
};
|
|
841
874
|
|
|
842
875
|
// Web component API for dropdowns
|
|
@@ -948,7 +981,7 @@ const ItemLinkEl = skate('aui-item-link', {
|
|
|
948
981
|
const [ItemCheckboxEl, ItemRadioEl] = ['checkbox', 'radio'].map(type => {
|
|
949
982
|
return skate(`aui-item-${type}`, {
|
|
950
983
|
template: template(
|
|
951
|
-
`<span role="${type}" class="aui-dropdown2-${type}" tabindex="-1"><content></content></span>`
|
|
984
|
+
`<span role="menuitem${type}" class="aui-dropdown2-${type}" tabindex="-1"><content></content></span>`
|
|
952
985
|
),
|
|
953
986
|
attributes: {
|
|
954
987
|
'item-id': stringAttributeHandlerGenerator('id'),
|
|
@@ -1053,7 +1086,7 @@ skate('aui-dropdown2-sub-trigger', {
|
|
|
1053
1086
|
|
|
1054
1087
|
// swap from the "menuitemX" role to plain role for VoiceOver
|
|
1055
1088
|
if (supportsVoiceOver()) {
|
|
1056
|
-
checkbox.setAttribute('role',type);
|
|
1089
|
+
checkbox.setAttribute('role', `menuitem${type}`);
|
|
1057
1090
|
}
|
|
1058
1091
|
},
|
|
1059
1092
|
|
|
@@ -71,7 +71,7 @@ function getLastFocus ($el) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function elementTrapsFocus ($el) {
|
|
74
|
-
return $el.is('.aui-dialog2');
|
|
74
|
+
return $el.is('.aui-dialog2') || $el.is('[aui-focus-trap="true"]');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function FocusManager() {
|
|
@@ -146,7 +146,15 @@ function focusTrapHandler(focusTrapStack, event) {
|
|
|
146
146
|
const $focusTrapElement = focusTrapStack[focusTrapStack.length - 1];
|
|
147
147
|
const $tabbableElements = $focusTrapElement.find(':aui-tabbable');
|
|
148
148
|
|
|
149
|
-
//
|
|
149
|
+
// Try to focus the container if:
|
|
150
|
+
// - it is with aui-focus-trap=true attribute
|
|
151
|
+
// - there isn't any focusable element inside.
|
|
152
|
+
if (!$tabbableElements.length && elementTrapsFocus($focusTrapElement)) {
|
|
153
|
+
$focusTrapElement.trigger('focus');
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// it's not possible to trap focus inside something with no focusable elements!
|
|
150
158
|
if (!$tabbableElements.length) {
|
|
151
159
|
return;
|
|
152
160
|
}
|
|
@@ -14,10 +14,13 @@ import getFocusManager from './focus-manager';
|
|
|
14
14
|
const DEFAULT_HOVEROUT_DELAY = 1000;
|
|
15
15
|
|
|
16
16
|
function changeTrigger(element, newTrigger) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
if (isPopupMenu(element)) {
|
|
18
|
+
doIfTrigger(element, function(oldTrigger) {
|
|
19
|
+
oldTrigger.setAttribute('aria-expanded', 'false');
|
|
20
|
+
newTrigger.setAttribute('aria-expanded', element.open);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
21
24
|
setTrigger(element, newTrigger);
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -32,26 +35,6 @@ function enableAlignment (element, trigger) {
|
|
|
32
35
|
eventsEnabled: true
|
|
33
36
|
};
|
|
34
37
|
|
|
35
|
-
alignmentOptions = {... alignmentOptions, ...{
|
|
36
|
-
onCreate: () => {
|
|
37
|
-
if (shouldFocus(element)) {
|
|
38
|
-
getFocusManager().enter($(element), $(trigger));
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
onEvents: {
|
|
42
|
-
enabled: () => {
|
|
43
|
-
if (shouldFocus(element)) {
|
|
44
|
-
getFocusManager().enter($(element));
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
disabled: () => {
|
|
48
|
-
if (shouldFocus(element)) {
|
|
49
|
-
getFocusManager().exit($(element));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}};
|
|
54
|
-
|
|
55
38
|
element._auiAlignment = new Alignment(element, trigger, alignmentOptions);
|
|
56
39
|
}
|
|
57
40
|
}
|
|
@@ -170,6 +153,7 @@ function rebindMouseEvents(el) {
|
|
|
170
153
|
el.addEventListener('blur', onBlur);
|
|
171
154
|
}
|
|
172
155
|
}
|
|
156
|
+
|
|
173
157
|
function namespaceEvent(eventName, elId) {
|
|
174
158
|
return `${eventName}.nested-layer-${elId}`
|
|
175
159
|
}
|
|
@@ -210,8 +194,7 @@ function setupNestedLayerHandlers(el) {
|
|
|
210
194
|
});
|
|
211
195
|
}
|
|
212
196
|
|
|
213
|
-
function teardownNestedLayerHandlers(
|
|
214
|
-
const elId = el.id;
|
|
197
|
+
function teardownNestedLayerHandlers(elId) {
|
|
215
198
|
$(document)
|
|
216
199
|
.off(namespaceEvent('aui-layer-hide', elId))
|
|
217
200
|
.off(namespaceEvent('aui-layer-show', elId))
|
|
@@ -241,6 +224,10 @@ function shouldFocus(element) {
|
|
|
241
224
|
return element.respondsTo !== 'hover';
|
|
242
225
|
}
|
|
243
226
|
|
|
227
|
+
function isPopupMenu(element) {
|
|
228
|
+
return element.getAttribute('role') !== 'dialog';
|
|
229
|
+
}
|
|
230
|
+
|
|
244
231
|
/**
|
|
245
232
|
* Abstracted as skate fires custom attributes handlers before the component creation if they are pre-populated.
|
|
246
233
|
*
|
|
@@ -257,17 +244,39 @@ function maybeInitialise(element) {
|
|
|
257
244
|
layer(element);
|
|
258
245
|
$(element).on({
|
|
259
246
|
// fired only after the layer is shown
|
|
260
|
-
[`${EVENT_PREFIX}show`]: function() {
|
|
247
|
+
[`${EVENT_PREFIX}show`]: function(e) {
|
|
261
248
|
const el = this;
|
|
249
|
+
// This handler can be fired by nested layer/component.
|
|
250
|
+
// We need to be sure that the event was triggered by the inline dialog;
|
|
251
|
+
if (e.target !== el) {
|
|
252
|
+
// otherwise skip.
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
262
256
|
setupNestedLayerHandlers(el);
|
|
263
257
|
doIfTrigger(el, function (trigger) {
|
|
258
|
+
if (shouldFocus(el)) {
|
|
259
|
+
// Focus manager will focus the popup and link popup to the trigger.
|
|
260
|
+
getFocusManager().enter($(el), $(trigger));
|
|
261
|
+
}
|
|
262
|
+
|
|
264
263
|
enableAlignment(el, trigger);
|
|
265
|
-
|
|
264
|
+
|
|
265
|
+
if (isPopupMenu(el)) {
|
|
266
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
267
|
+
}
|
|
266
268
|
});
|
|
267
269
|
},
|
|
268
270
|
// fired only after the layer is hidden
|
|
269
|
-
[`${EVENT_PREFIX}hide`]: function() {
|
|
271
|
+
[`${EVENT_PREFIX}hide`]: function(e) {
|
|
270
272
|
const el = this;
|
|
273
|
+
// This handler can be fired by nested layer/component.
|
|
274
|
+
// We need to be sure that the event was triggered by the inline dialog;
|
|
275
|
+
if (e.target !== el) {
|
|
276
|
+
// otherwise skip.
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
271
280
|
teardownNestedLayerHandlers(el.id);
|
|
272
281
|
// in case the element has been removed from DOM already
|
|
273
282
|
// disablingAlignment may fail if trigger is an anchor due to Popper's logic
|
|
@@ -278,7 +287,13 @@ function maybeInitialise(element) {
|
|
|
278
287
|
destroyAlignment(el);
|
|
279
288
|
}
|
|
280
289
|
doIfTrigger(el, function (trigger) {
|
|
281
|
-
|
|
290
|
+
if (shouldFocus(el)) {
|
|
291
|
+
// Focus manager will focus the trigger that is linked with the popup element.
|
|
292
|
+
getFocusManager().exit($(el));
|
|
293
|
+
}
|
|
294
|
+
if (isPopupMenu(el)) {
|
|
295
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
296
|
+
}
|
|
282
297
|
});
|
|
283
298
|
setTrigger(el, null);
|
|
284
299
|
}
|
|
@@ -390,23 +405,28 @@ const InlineDialogEl = skate('aui-inline-dialog', {
|
|
|
390
405
|
attached: function (element) {
|
|
391
406
|
enforce(element).attributeExists('id');
|
|
392
407
|
element.setAttribute('tabindex', 0);
|
|
393
|
-
element
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
408
|
+
if (isPopupMenu(element)) {
|
|
409
|
+
element.setAttribute('role', 'group');
|
|
410
|
+
doIfTrigger(element, function (trigger) {
|
|
411
|
+
trigger.setAttribute('aria-expanded', element.open);
|
|
412
|
+
});
|
|
413
|
+
forEachTrigger(element, function (trigger) {
|
|
414
|
+
trigger.setAttribute('aria-haspopup', 'true');
|
|
415
|
+
});
|
|
416
|
+
}
|
|
400
417
|
rebindMouseEvents(element);
|
|
401
418
|
},
|
|
402
419
|
|
|
403
420
|
detached: function (element) {
|
|
404
421
|
ifGone(element).then(() => {
|
|
405
422
|
destroyAlignment(element);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
trigger
|
|
409
|
-
|
|
423
|
+
|
|
424
|
+
if (isPopupMenu(element)) {
|
|
425
|
+
forEachTrigger(element, function (trigger) {
|
|
426
|
+
trigger.removeAttribute('aria-haspopup');
|
|
427
|
+
trigger.removeAttribute('aria-expanded');
|
|
428
|
+
});
|
|
429
|
+
}
|
|
410
430
|
});
|
|
411
431
|
},
|
|
412
432
|
|
|
@@ -39,6 +39,7 @@ export default {
|
|
|
39
39
|
'aui.validation.message.maxchecked': 'Tick at most {0,choice,0#0 checkboxes|1#1 checkbox|1<{0,number} checkboxes}.',
|
|
40
40
|
'aui.checkboxmultiselect.clear.selected': 'Clear selected items',
|
|
41
41
|
'aui.select.no.suggestions': 'No suggestions',
|
|
42
|
+
'aui.select.number.suggestions': 'Found {0,choice,0#0 options|1#1 option|1<{0,number} options}.',
|
|
42
43
|
'aui.select.new.suggestions': 'New suggestions added. Please use the up and down arrows to select.',
|
|
43
44
|
'aui.select.new.value': 'new value',
|
|
44
45
|
'aui.toggle.on': 'On',
|
|
@@ -43,15 +43,15 @@ SuggestionsModel.prototype = {
|
|
|
43
43
|
},
|
|
44
44
|
|
|
45
45
|
highlightPrevious: function () {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const current = this._activeIndex;
|
|
47
|
+
const previousActiveIndex = (current === 0) ? this._suggestions.length - 1 : (current - 1);
|
|
48
48
|
this.highlight(previousActiveIndex);
|
|
49
49
|
return this;
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
highlightNext: function () {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const current = this._activeIndex;
|
|
54
|
+
const nextActiveIndex = (current === this._suggestions.length - 1) ? 0 : (current + 1);
|
|
55
55
|
this.highlight(nextActiveIndex);
|
|
56
56
|
return this;
|
|
57
57
|
},
|
|
@@ -54,21 +54,21 @@ function clearActive (element) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
SuggestionsView.prototype = {
|
|
57
|
-
render: function (suggestions, currentLength, listId) {
|
|
57
|
+
render: function (suggestions, currentLength, listId, selected) {
|
|
58
58
|
this.currListId = listId;
|
|
59
|
-
|
|
59
|
+
let html = '';
|
|
60
60
|
|
|
61
61
|
// Do nothing if we have no new suggestions, otherwise append anything else we find.
|
|
62
62
|
if (suggestions.length) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
63
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
64
|
+
const suggestion = suggestions[i];
|
|
65
|
+
const currentIndex = i + currentLength;
|
|
66
|
+
const label = suggestion.getLabel();
|
|
67
|
+
const imageSrc = suggestion.get('img-src');
|
|
68
|
+
const image = imageSrc ? `<img src="${encodeURI(imageSrc)}"/>` : '';
|
|
69
|
+
const newValueText = suggestion.get('new-value') ? (` (<em>${I18n.getText('aui.select.new.value')}</em>)`) : '';
|
|
70
|
+
html += `<li role="option" class="aui-select-suggestion" aria-selected="${selected === currentIndex}" id="${generateListItemID(listId, currentIndex)}">${image}${label}${newValueText}</li>`;
|
|
71
|
+
}
|
|
72
72
|
|
|
73
73
|
// If the old suggestions were empty, a <li> of 'No suggestions' will be appended, we need to remove it
|
|
74
74
|
if (currentLength) {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import template from 'skatejs-template-html';
|
|
2
2
|
|
|
3
3
|
export default template(`
|
|
4
|
-
<input type="text" class="text"
|
|
4
|
+
<input type="text" class="text" role="combobox" aria-expanded="false">
|
|
5
5
|
<select></select>
|
|
6
6
|
<datalist>
|
|
7
7
|
<content select="aui-option"></content>
|
|
8
8
|
</datalist>
|
|
9
|
-
<button class="aui-button"
|
|
10
|
-
<div class="aui-popover"
|
|
11
|
-
<ul class="aui-optionlist"
|
|
9
|
+
<button class="aui-button" tabindex="-1" type="button"></button>
|
|
10
|
+
<div class="aui-popover" data-aui-alignment="bottom left" hidden>
|
|
11
|
+
<ul role="listbox" class="aui-optionlist"></ul>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="aui-select-status assistive" aria-live="polite" role="status"></div>
|
|
14
14
|
`);
|