@atlassian/aui
Version:
Atlassian User Interface Framework
1,076 lines (904 loc) • 34.1 kB
JavaScript
import './i18n';
import './spin';
import $ from './jquery';
import template from 'skatejs-template-html';
import * as deprecate from './internal/deprecation';
import * as logger from './internal/log';
import { debounceImmediate } from './debounce';
import { supportsVoiceOver } from './internal/browser';
import Alignment from './internal/alignment';
import CustomEvent from './polyfills/custom-event';
import keyCode from './key-code';
import layer from './layer';
import state from './internal/state';
import skate from './internal/skate';
function isChecked(el) {
return $(el).is('.checked, .aui-dropdown2-checked, [aria-checked="true"]');
}
function getTrigger(control) {
return control._triggeringElement || document.querySelector('[aria-controls="' + control.id + '"]');
}
function doIfTrigger(triggerable, callback) {
var trigger = getTrigger(triggerable);
if (trigger) {
callback(trigger);
}
}
function setDropdownTriggerActiveState(trigger, isActive) {
var $trigger = $(trigger);
if (isActive) {
$trigger.attr('aria-expanded', 'true');
$trigger.addClass('active aui-dropdown2-active');
} else {
$trigger.attr('aria-expanded', 'false');
$trigger.removeClass('active aui-dropdown2-active');
}
}
// LOADING STATES
var UNLOADED = 'unloaded';
var LOADING = 'loading';
var ERROR = 'error';
var SUCCESS = 'success';
// ASYNC DROPDOWN FUNCTIONS
function makeAsyncDropdownContents (json) {
var dropdownContents = json.map(function makeSection (sectionData) {
var sectionItemsHtml = sectionData.items.map(function makeSectionItem (itemData) {
function makeBooleanAttribute (attr) {
return itemData[attr] ? `${attr} ="true"` : '';
}
function makeAttribute (attr) {
return itemData[attr] ? `${attr}="${itemData[attr]}"` : '';
}
var tagName = 'aui-item-' + itemData.type;
var itemHtml = `
<${tagName} ${makeAttribute('for')} ${makeAttribute('href')} ${makeBooleanAttribute('interactive')}
${makeBooleanAttribute('checked')} ${makeBooleanAttribute('disabled')} ${makeBooleanAttribute('hidden')}>
${itemData.content}
</${tagName}>`;
return itemHtml;
}).join('');
var sectionAttributes = sectionData.label ? `label="${sectionData.label}"` : '';
var sectionHtml = `
<aui-section ${sectionAttributes}>
${sectionItemsHtml}
</aui-section>`;
return sectionHtml;
}).join('\n');
return dropdownContents;
}
function setDropdownContents (dropdown, json) {
state(dropdown).set('loading-state', SUCCESS);
template.wrap(dropdown).innerHTML = makeAsyncDropdownContents(json);
skate.init(dropdown);
}
function setDropdownErrorState (dropdown) {
state(dropdown).set('loading-state', ERROR);
state(dropdown).set('hasErrorBeenShown', dropdown.isVisible());
template.wrap(dropdown).innerHTML = `
<div class="aui-message aui-message-error aui-dropdown-error">
<p>${AJS.I18n.getText('aui.dropdown.async.error')}</p>
</div>
`;
}
function setDropdownLoadingState (dropdown) {
state(dropdown).set('loading-state', LOADING);
state(dropdown).set('hasErrorBeenShown', false);
doIfTrigger(dropdown, function (trigger) {
trigger.setAttribute('aria-busy', 'true');
});
template.wrap(dropdown).innerHTML = `
<div class="aui-dropdown-loading">
<span class="spinner"></span> ${AJS.I18n.getText('aui.dropdown.async.loading')}
</div>
`;
$(dropdown).find('.spinner').spin();
}
function setDropdownLoaded (dropdown) {
doIfTrigger(dropdown, function (trigger) {
trigger.setAttribute('aria-busy', 'false');
});
}
function loadContentsIfAsync (dropdown) {
if (!dropdown.src || state(dropdown).get('loading-state') === LOADING) {
return;
}
setDropdownLoadingState(dropdown);
$.ajax(dropdown.src)
.done(function (json, status, xhr) {
var isValidStatus = xhr.status === 200;
if (isValidStatus) {
setDropdownContents(dropdown, json);
} else {
setDropdownErrorState(dropdown);
}
})
.fail(function () {
setDropdownErrorState(dropdown);
})
.always(function () {
setDropdownLoaded(dropdown);
});
}
function loadContentWhenMouseEnterTrigger(dropdown) {
var isDropdownUnloaded = state(dropdown).get('loading-state') === UNLOADED;
var hasCurrentErrorBeenShown = state(dropdown).get('hasErrorBeenShown');
if (isDropdownUnloaded || hasCurrentErrorBeenShown && !dropdown.isVisible()) {
loadContentsIfAsync(dropdown);
}
}
function loadContentWhenMenuShown(dropdown) {
var isDropdownUnloaded = state(dropdown).get('loading-state') === UNLOADED;
var hasCurrentErrorBeenShown = state(dropdown).get('hasErrorBeenShown');
if (isDropdownUnloaded || hasCurrentErrorBeenShown) {
loadContentsIfAsync(dropdown);
}
if (state(dropdown).get('loading-state') === ERROR) {
state(dropdown).set('hasErrorBeenShown', true);
}
}
// The dropdown's trigger
// ----------------------
function triggerCreated (trigger) {
var dropdownID = trigger.getAttribute('aria-controls');
if (!dropdownID) {
dropdownID = trigger.getAttribute('aria-owns');
if (!dropdownID) {
logger.error('Dropdown triggers need either a "aria-owns" or "aria-controls" attribute');
} else {
trigger.removeAttribute('aria-owns');
trigger.setAttribute('aria-controls', dropdownID);
}
}
trigger.setAttribute('aria-haspopup', true);
trigger.setAttribute('aria-expanded', false);
const shouldSetHref = trigger.nodeName === 'A' && !trigger.href;
if (shouldSetHref) {
trigger.setAttribute('href', '#' + dropdownID);
}
function handleIt(e) {
e.preventDefault();
if (!trigger.isEnabled()) {
return;
}
var dropdown = document.getElementById(dropdownID);
// AUI-4271 - Maintains legacy integration with parent elements.
const $trigger = $(trigger);
if ($trigger.parent().hasClass('aui-buttons')) {
dropdown.classList.add('aui-dropdown2-in-buttons');
}
if ($trigger.parents().hasClass('aui-header')) {
dropdown.classList.add('aui-dropdown2-in-header');
}
dropdown.toggle(e);
dropdown.isSubmenu = trigger.hasSubmenu();
return dropdown;
}
function handleMouseEnter(e) {
e.preventDefault();
if (!trigger.isEnabled()) {
return;
}
var dropdown = document.getElementById(dropdownID);
loadContentWhenMouseEnterTrigger(dropdown);
if (trigger.hasSubmenu()) {
dropdown.show(e);
dropdown.isSubmenu = trigger.hasSubmenu();
}
return dropdown;
}
function handleKeydown(e) {
var normalInvoke = (e.keyCode === keyCode.ENTER || e.keyCode === keyCode.SPACE);
var submenuInvoke = (e.keyCode === keyCode.RIGHT && trigger.hasSubmenu());
var rootMenuInvoke = ((e.keyCode === keyCode.UP || e.keyCode === keyCode.DOWN) && !trigger.hasSubmenu());
if (normalInvoke || submenuInvoke || rootMenuInvoke) {
var dropdown = handleIt(e);
if (dropdown) {
dropdown.focusItem(0);
}
}
}
$(trigger)
.on('aui-button-invoke', handleIt)
.on('click', handleIt)
.on('keydown', handleKeydown)
.on('mouseenter', handleMouseEnter)
;
}
var triggerPrototype = {
disable: function () {
this.setAttribute('aria-disabled', 'true');
},
enable: function () {
this.setAttribute('aria-disabled', 'false');
},
isEnabled: function () {
return this.getAttribute('aria-disabled') !== 'true';
},
hasSubmenu: function () {
var triggerClasses = (this.className || '').split(/\s+/);
return triggerClasses.indexOf('aui-dropdown2-sub-trigger') !== -1;
}
};
skate('aui-dropdown2-trigger', {
type: skate.type.CLASSNAME,
created: triggerCreated,
prototype: triggerPrototype
});
//To remove at a later date. Some dropdown triggers initialise lazily, so we need to listen for mousedown
//and synchronously init before the click event is fired.
//TODO: delete in AUI 8.0.0, see AUI-2868
function bindLazyTriggerInitialisation() {
$(document).on('mousedown', '.aui-dropdown2-trigger', function () {
var isElementSkated = this.hasAttribute('resolved');
if (!isElementSkated) {
skate.init(this);
var lazyDeprecate = deprecate.getMessageLogger('Dropdown2 lazy initialisation', {
removeInVersion: '8.0.0',
alternativeName: 'initialisation on DOM insertion',
sinceVersion: '5.8.0',
extraInfo: 'Dropdown2 triggers should have all necessary attributes on DOM insertion',
deprecationType: 'JS'
});
lazyDeprecate();
}
});
}
bindLazyTriggerInitialisation();
skate('aui-dropdown2-sub-trigger', {
type: skate.type.CLASSNAME,
created: function (trigger) {
trigger.className += ' aui-dropdown2-trigger';
skate.init(trigger);
}
});
// Dropdown trigger groups
// -----------------------
$(document).on('mouseenter', '.aui-dropdown2-trigger-group a, .aui-dropdown2-trigger-group button', function (e) {
const $item = $(e.currentTarget);
if ($item.is('.aui-dropdown2-active')) {
return; // No point doing anything if we're hovering over the already-active item trigger.
}
if ($item.closest('.aui-dropdown2').length) {
return; // We don't want to deal with dropdown items, just the potential triggers in the group.
}
const $triggerGroup = $item.closest('.aui-dropdown2-trigger-group');
const $groupActiveTrigger = $triggerGroup.find('.aui-dropdown2-active');
if ($groupActiveTrigger.length && $item.is('.aui-dropdown2-trigger')) {
$groupActiveTrigger.blur(); // Remove focus from the previously opened menu.
$item.trigger('aui-button-invoke'); // Open this trigger's menu.
e.preventDefault();
}
const $groupFocusedTrigger = $triggerGroup.find(':focus');
if ($groupFocusedTrigger.length && $item.is('.aui-dropdown2-trigger')) {
$groupFocusedTrigger.blur();
}
});
// Dropdown items
// --------------
function getDropdownItems (dropdown, filter) {
return $(dropdown)
.find([
// Legacy markup.
'> ul > li',
'> .aui-dropdown2-section > ul > li',
// Accessible markup.
'> div[role] > .aui-dropdown2-section > div[role="group"] > ul[role] > li[role]',
// Web component.
'aui-item-link',
'aui-item-checkbox',
'aui-item-radio'
].join(', '))
.filter(filter)
.children('a, button, [role="checkbox"], [role="menuitemcheckbox"], [role="radio"], [role="menuitemradio"]');
}
function getAllDropdownItems (dropdown) {
return getDropdownItems(dropdown, function () {
return true;
});
}
function getVisibleDropdownItems (dropdown) {
return getDropdownItems(dropdown, function () {
return this.className.indexOf('hidden') === -1 && !this.hasAttribute('hidden');
});
}
function amendDropdownItem (item) {
var $item = $(item);
$item.attr('tabindex', '-1');
/**
* Honouring the documentation.
* @link https://docs.atlassian.com/aui/latest/docs/dropdown2.html
*/
if ($item.hasClass('aui-dropdown2-disabled') || $item.parent().hasClass('aui-dropdown2-hidden')) {
$item.attr('aria-disabled', true);
}
}
function amendDropdownContent (dropdown) {
// Add assistive semantics to each dropdown item
getAllDropdownItems(dropdown).each(function () {
amendDropdownItem(this);
});
}
/**
* Honours behaviour for code written using only the legacy class names.
* To maintain old behaviour (i.e., remove the 'hidden' class and the item will become un-hidden)
* whilst allowing our code to only depend on the new classes, we need to
* keep the state of the DOM in sync with legacy classes.
*
* Calling this function will add the new namespaced classes to elements with legacy names.
* @returns {Function} a function to remove the new namespaced classes, only from the elements they were added to.
*/
function migrateAndSyncLegacyClassNames (dropdown) {
var $dropdown = $(dropdown);
// Migrate away from legacy class names
var $hiddens = $dropdown.find('.hidden').addClass('aui-dropdown2-hidden');
var $disableds = $dropdown.find('.disabled').addClass('aui-dropdown2-disabled');
var $interactives = $dropdown.find('.interactive').addClass('aui-dropdown2-interactive');
return function revertToOriginalMarkup () {
$hiddens.removeClass('aui-dropdown2-hidden');
$disableds.removeClass('aui-dropdown2-disabled');
$interactives.removeClass('aui-dropdown2-interactive');
};
}
// The Dropdown itself
// -------------------
function setLayerAlignment(dropdown, trigger) {
var hasSubmenu = trigger && trigger.hasSubmenu && trigger.hasSubmenu();
var hasSubmenuAlignment = dropdown.getAttribute('data-aui-alignment') === 'submenu auto';
if (!hasSubmenu && hasSubmenuAlignment) {
restorePreviousAlignment(dropdown);
}
var hasAnyAlignment = dropdown.hasAttribute('data-aui-alignment');
if (hasSubmenu && !hasSubmenuAlignment) {
saveCurrentAlignment(dropdown);
dropdown.setAttribute('data-aui-alignment', 'submenu auto');
dropdown.setAttribute('data-aui-alignment-static', true);
} else if (!hasAnyAlignment) {
dropdown.setAttribute('data-aui-alignment', 'bottom auto');
dropdown.setAttribute('data-aui-alignment-static', true);
}
if (dropdown._auiAlignment) {
dropdown._auiAlignment.destroy();
}
dropdown._auiAlignment = new Alignment(dropdown, trigger);
dropdown._auiAlignment.enable();
}
function saveCurrentAlignment(dropdown) {
var $dropdown = $(dropdown);
if (dropdown.hasAttribute('data-aui-alignment')) {
$dropdown.data('previous-data-aui-alignment', dropdown.getAttribute('data-aui-alignment'));
}
$dropdown.data('had-data-aui-alignment-static', dropdown.hasAttribute('data-aui-alignment-static'));
}
function restorePreviousAlignment(dropdown) {
var $dropdown = $(dropdown);
var previousAlignment = $dropdown.data('previous-data-aui-alignment');
if (previousAlignment) {
dropdown.setAttribute('data-aui-alignment', previousAlignment);
} else {
dropdown.removeAttribute('data-aui-alignment');
}
$dropdown.removeData('previous-data-aui-alignment');
if (!$dropdown.data('had-data-aui-alignment-static')) {
dropdown.removeAttribute('data-aui-alignment-static');
}
$dropdown.removeData('had-data-aui-alignment-static');
}
function getDropdownHideLocation(dropdown, trigger) {
var possibleHome = trigger.getAttribute('data-dropdown2-hide-location');
return document.getElementById(possibleHome) || dropdown.parentNode;
}
var keyboardClose = false;
function keyboardCloseDetected () {
keyboardClose = true;
}
function wasProbablyClosedViaKeyboard () {
var result = (keyboardClose === true);
keyboardClose = false;
return result;
}
function bindDropdownBehaviourToLayer(dropdown) {
layer(dropdown);
dropdown.addEventListener('aui-layer-show', function () {
$(dropdown).trigger('aui-dropdown2-show');
dropdown._syncClasses = migrateAndSyncLegacyClassNames(dropdown);
amendDropdownContent(this);
doIfTrigger(dropdown, function (trigger) {
setDropdownTriggerActiveState(trigger, true);
dropdown._returnTo = getDropdownHideLocation(dropdown, trigger);
});
});
dropdown.addEventListener('aui-layer-hide', function () {
$(dropdown).trigger('aui-dropdown2-hide');
if (dropdown._syncClasses) {
dropdown._syncClasses();
delete dropdown._syncClasses;
}
if (dropdown._auiAlignment) {
dropdown._auiAlignment.disable();
dropdown._auiAlignment.destroy();
}
if (dropdown._returnTo) {
if (dropdown.parentNode && dropdown.parentNode !== dropdown._returnTo) {
dropdown._returnTo.appendChild(dropdown);
}
}
$(dropdown).removeClass('aui-dropdown2-in-buttons');
getVisibleDropdownItems(dropdown).removeClass('active aui-dropdown2-active');
doIfTrigger(dropdown, function (trigger) {
if (wasProbablyClosedViaKeyboard()) {
trigger.focus();
setDropdownTriggerActiveState(trigger, trigger.hasSubmenu && trigger.hasSubmenu());
} else {
setDropdownTriggerActiveState(trigger, false);
}
});
// Gets set by submenu trigger invocation. Bad coupling point?
delete dropdown.isSubmenu;
dropdown._triggeringElement = null;
});
}
function bindItemInteractionBehaviourToDropdown (dropdown) {
var $dropdown = $(dropdown);
$dropdown.on('keydown', function (e) {
if (e.keyCode === keyCode.DOWN) {
dropdown.focusNext();
e.preventDefault();
} else if (e.keyCode === keyCode.UP) {
dropdown.focusPrevious();
e.preventDefault();
} else if (e.keyCode === keyCode.LEFT) {
if (dropdown.isSubmenu) {
keyboardCloseDetected();
dropdown.hide(e);
e.preventDefault();
}
} else if (e.keyCode === keyCode.ESCAPE) {
// The closing will be handled by the LayerManager!
keyboardCloseDetected();
} else if (e.keyCode === keyCode.TAB) {
keyboardCloseDetected();
dropdown.hide(e);
}
});
const hideIfNotSubmenuAndNotInteractive = function(e) {
var $item = $(e.currentTarget);
if ($item.attr('aria-disabled') === 'true') {
e.preventDefault();
return;
}
const isSubmenuTrigger = e.currentTarget.hasSubmenu && e.currentTarget.hasSubmenu();
if (!isSubmenuTrigger && !$item.is('.aui-dropdown2-interactive')) {
var theMenu = dropdown;
do {
var dd = layer(theMenu);
theMenu = layer(theMenu).below();
if (dd.$el.is('.aui-dropdown2')) {
dd.hide(e);
}
} while (theMenu);
}
}
$dropdown.on('click keydown', 'a, button, [role="menuitem"], [role="menuitemcheckbox"], [role="checkbox"], [role="menuitemradio"], [role="radio"]', function (e) {
const item = e.currentTarget;
const $item = $(item);
const eventKeyCode = e.keyCode;
const isEnter = eventKeyCode === keyCode.ENTER;
const isSpace = eventKeyCode === keyCode.SPACE;
// AUI-4283: Accessibility - need to ignore enter on links/buttons so
// that the dropdown remains visible to allow the click event to eventually fire.
const itemIgnoresEnter = isEnter && $item.is('a[href], button');
if (!itemIgnoresEnter && (e.type === 'click' || isEnter || isSpace)) {
hideIfNotSubmenuAndNotInteractive(e);
}
});
// close a submenus when the mouse moves over items other than its trigger
$dropdown.on('mouseenter', 'a, button, [role="menuitem"], [role="menuitemcheckbox"], [role="checkbox"], [role="menuitemradio"], [role="radio"]', function (e) {
var item = e.currentTarget;
var hasSubmenu = item.hasSubmenu && item.hasSubmenu();
if (!e.isDefaultPrevented() && !hasSubmenu) {
var maybeALayer = layer(dropdown).above();
if (maybeALayer) {
layer(maybeALayer).hide();
}
}
});
}
$(window).on('resize', debounceImmediate(function () {
$('.aui-dropdown2').each(function (index, dropdown) {
skate.init(dropdown);
if (dropdown.isVisible()){
dropdown.hide();
}
});
}, 1000));
// Dropdowns
// ---------
function dropdownCreated (dropdown) {
var $dropdown = $(dropdown);
$dropdown.addClass('aui-dropdown2');
// swap the inner div to presentation as application is only needed for Windows
if (supportsVoiceOver()) {
$dropdown.find('> div[role="application"]').attr('role', 'presentation');
}
if (dropdown.hasAttribute('data-container')) {
$dropdown.attr('data-aui-alignment-container', $dropdown.attr('data-container'));
$dropdown.removeAttr('data-container');
}
bindDropdownBehaviourToLayer(dropdown);
bindItemInteractionBehaviourToDropdown(dropdown);
dropdown.hide();
$(dropdown).delegate('.aui-dropdown2-checkbox:not(.disabled):not(.aui-dropdown2-disabled)', 'click keydown', function (e) {
if (e.type === 'click' || e.keyCode === keyCode.ENTER || e.keyCode === keyCode.SPACE) {
let checkbox = this;
if (e.isDefaultPrevented()) {
return;
}
if (checkbox.isInteractive()) {
e.preventDefault();
}
if (checkbox.isEnabled()) {
// toggle the checked state
if (checkbox.isChecked()) {
checkbox.uncheck();
} else {
checkbox.check();
}
}
}
});
$(dropdown).delegate('.aui-dropdown2-radio:not(.checked):not(.aui-dropdown2-checked):not(.disabled):not(.aui-dropdown2-disabled)', 'click keydown', function (e) {
if (e.type === 'click' || e.keyCode === keyCode.ENTER || e.keyCode === keyCode.SPACE) {
let radio = this;
if (e.isDefaultPrevented()) {
return;
}
if (radio.isInteractive()) {
e.preventDefault();
}
let $radio = $(this);
if (this.isEnabled() && this.isChecked() === false) {
// toggle the checked state
$radio.closest('ul,[role=group]').find('.aui-dropdown2-checked').not(this).each(function () {
this.uncheck();
});
radio.check();
}
}
});
}
var dropdownPrototype = {
/**
* Toggles the visibility of the dropdown menu
*/
toggle: function (e) {
if (this.isVisible()) {
this.hide(e);
} else {
this.show(e);
}
},
/**
* Explicitly shows the menu
*
* @returns {HTMLElement}
*/
show: function (e) {
if (e && e.currentTarget && e.currentTarget.classList.contains('aui-dropdown2-trigger')) {
this._triggeringElement = e.currentTarget;
}
layer(this).show();
var dropdown = this;
doIfTrigger(dropdown, function (trigger) {
setLayerAlignment(dropdown, trigger);
});
return this;
},
/**
* Explicitly hides the menu
*
* @returns {HTMLElement}
*/
hide: function () {
layer(this).hide();
return this;
},
/**
* Shifts explicit focus to the next available item in the menu
*
* @returns {undefined}
*/
focusNext: function () {
var $items = getVisibleDropdownItems(this);
var selected = document.activeElement;
var idx;
if ($items.last()[0] !== selected) {
idx = $items.toArray().indexOf(selected);
this.focusItem($items.get(idx + 1));
}
},
/**
* Shifts explicit focus to the previous available item in the menu
*
* @returns {undefined}
*/
focusPrevious: function () {
var $items = getVisibleDropdownItems(this);
var selected = document.activeElement;
var idx;
if ($items.first()[0] !== selected) {
idx = $items.toArray().indexOf(selected);
this.focusItem($items.get(idx - 1));
}
},
/**
* Shifts explicit focus to the menu item matching the index param
*/
focusItem: function (item) {
var $items = getVisibleDropdownItems(this);
var $item;
if (typeof item === 'number') {
item = $items.get(item);
}
$item = $(item);
$item.focus();
$items.removeClass('active aui-dropdown2-active');
$item.addClass('active aui-dropdown2-active');
},
/**
* Checks whether or not the menu is currently displayed
*
* @returns {Boolean}
*/
isVisible: function () {
return layer(this).isVisible();
}
};
// Web component API for dropdowns
// -------------------------------
var disabledAttributeHandler = {
created: function (element) {
var a = element.children[0];
a.setAttribute('aria-disabled', 'true');
a.className += ' aui-dropdown2-disabled';
},
removed: function (element) {
var a = element.children[0];
a.setAttribute('aria-disabled', 'false');
$(a).removeClass('aui-dropdown2-disabled');
}
};
var interactiveAttributeHandler = {
created: function (element) {
var a = element.children[0];
a.className += ' aui-dropdown2-interactive';
},
removed: function (element) {
var a = element.children[0];
$(a).removeClass('aui-dropdown2-interactive');
}
};
var checkedAttributeHandler = {
created: function (element) {
var a = element.children[0];
$(a).addClass('checked aui-dropdown2-checked');
a.setAttribute('aria-checked', true);
element.dispatchEvent(new CustomEvent('change', {bubbles: true}));
},
removed: function (element) {
var a = element.children[0];
$(a).removeClass('checked aui-dropdown2-checked');
a.setAttribute('aria-checked', false);
element.dispatchEvent(new CustomEvent('change', {bubbles: true}));
}
};
var hiddenAttributeHandler = {
created: function (element) {
disabledAttributeHandler.created(element);
},
removed: function (element) {
disabledAttributeHandler.removed(element);
}
};
skate('aui-item-link', {
template: template(
'<a role="menuitem" tabindex="-1"><content></content></a>'
),
attributes: {
disabled: disabledAttributeHandler,
interactive: interactiveAttributeHandler,
hidden: hiddenAttributeHandler,
href: {
created: function (element, change) {
var a = element.children[0];
a.setAttribute('href', change.newValue);
},
updated: function (element, change) {
var a = element.children[0];
a.setAttribute('href', change.newValue);
},
removed: function (element) {
var a = element.children[0];
a.removeAttribute('href');
}
},
for: {
created: function (element) {
var anchor = element.children[0];
anchor.setAttribute('aria-controls', element.getAttribute('for'));
$(anchor).addClass('aui-dropdown2-sub-trigger');
},
updated: function (element) {
var anchor = element.children[0];
anchor.setAttribute('aria-controls', element.getAttribute('for'));
},
removed: function (element) {
var anchor = element.children[0];
anchor.removeAttribute('aria-controls');
$(anchor).removeClass('aui-dropdown2-sub-trigger');
}
}
}
});
skate('aui-item-checkbox', {
template: template(
'<span role="checkbox" class="aui-dropdown2-checkbox" tabindex="-1"><content></content></span>'
),
attributes: {
disabled: disabledAttributeHandler,
interactive: interactiveAttributeHandler,
checked: checkedAttributeHandler,
hidden: hiddenAttributeHandler
}
});
skate('aui-item-radio', {
template: template(
'<span role="radio" class="aui-dropdown2-radio" tabindex="-1"><content></content></span>'
),
attributes: {
disabled: disabledAttributeHandler,
interactive: interactiveAttributeHandler,
checked: checkedAttributeHandler,
hidden: hiddenAttributeHandler
}
});
skate('aui-section', {
template: template(`
<strong aria-role="presentation" class="aui-dropdown2-heading"></strong>
<div role="group">
<content></content>
</div>
`),
attributes: {
label: function (element, data) {
var headingElement = element.children[0];
var groupElement = element.children[1];
headingElement.textContent = data.newValue;
groupElement.setAttribute('aria-label', data.newValue);
}
},
created: function (element) {
element.className += ' aui-dropdown2-section';
element.setAttribute('role', 'presentation');
}
});
skate('aui-dropdown-menu', {
template: template(`
<div role="application">
<content></content>
</div>
`),
created: function (dropdown) {
dropdown.setAttribute('role', 'menu');
dropdown.className = 'aui-dropdown2 aui-style-default aui-layer';
state(dropdown).set('loading-state', UNLOADED);
// Now skate the .aui-dropdown2 behaviour.
skate.init(dropdown);
},
attributes: {
src: {}
},
prototype: dropdownPrototype,
events: {
'aui-layer-show': loadContentWhenMenuShown
}
});
// Legacy dropdown inits
// ---------------------
skate('aui-dropdown2', {
type: skate.type.CLASSNAME,
created: dropdownCreated,
prototype: dropdownPrototype
});
skate('data-aui-dropdown2', {
type: skate.type.ATTRIBUTE,
created: dropdownCreated,
prototype: dropdownPrototype
});
// Checkboxes and radios
// ---------------------
skate('aui-dropdown2-checkbox', {
type: skate.type.CLASSNAME,
created: function (checkbox) {
var checked = isChecked(checkbox);
if (checked) {
$(checkbox).addClass('checked aui-dropdown2-checked');
}
checkbox.setAttribute('aria-checked', checked);
checkbox.setAttribute('tabindex', '0');
// swap from menuitemcheckbox to just plain checkbox for VoiceOver
if (supportsVoiceOver()) {
checkbox.setAttribute('role','checkbox');
}
},
prototype: {
isEnabled: function () {
return !(this.getAttribute('aria-disabled') !== null && this.getAttribute('aria-disabled') === 'true');
},
isChecked: function () {
return this.getAttribute('aria-checked') !== null && this.getAttribute('aria-checked') === 'true';
},
isInteractive: function () {
return $(this).hasClass('aui-dropdown2-interactive');
},
uncheck: function () {
if (this.parentNode.tagName.toLowerCase() === 'aui-item-checkbox') {
this.parentNode.removeAttribute('checked');
}
this.setAttribute('aria-checked', 'false');
$(this).removeClass('checked aui-dropdown2-checked');
$(this).trigger('aui-dropdown2-item-uncheck');
},
check: function () {
if (this.parentNode.tagName.toLowerCase() === 'aui-item-checkbox') {
this.parentNode.setAttribute('checked', '');
}
this.setAttribute('aria-checked', 'true');
$(this).addClass('checked aui-dropdown2-checked');
$(this).trigger('aui-dropdown2-item-check');
}
}
});
skate('aui-dropdown2-radio', {
type: skate.type.CLASSNAME,
created: function (radio) {
// add a dash of ARIA
var checked = isChecked(radio);
if (checked) {
$(radio).addClass('checked aui-dropdown2-checked');
}
radio.setAttribute('aria-checked', checked);
radio.setAttribute('tabindex', '0');
// swap from menuitemradio to just plain radio for VoiceOver
if (supportsVoiceOver()) {
radio.setAttribute('role','radio');
}
},
prototype: {
isEnabled: function () {
return !(this.getAttribute('aria-disabled') !== null && this.getAttribute('aria-disabled') === 'true');
},
isChecked: function () {
return this.getAttribute('aria-checked') !== null && this.getAttribute('aria-checked') === 'true';
},
isInteractive: function () {
return $(this).hasClass('aui-dropdown2-interactive');
},
uncheck: function () {
if (this.parentNode.tagName.toLowerCase() === 'aui-item-radio') {
this.parentNode.removeAttribute('checked');
}
this.setAttribute('aria-checked', 'false');
$(this).removeClass('checked aui-dropdown2-checked');
$(this).trigger('aui-dropdown2-item-uncheck');
},
check: function () {
if (this.parentNode.tagName.toLowerCase() === 'aui-item-radio') {
this.parentNode.setAttribute('checked', '');
}
this.setAttribute('aria-checked', 'true');
$(this).addClass('checked aui-dropdown2-checked');
$(this).trigger('aui-dropdown2-item-check');
}
}
});
;