UNPKG

@atlassian/aui

Version:

Atlassian User Interface Framework

1,076 lines (904 loc) 34.1 kB
'use strict'; 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'); } } });