UNPKG

@atlassian/aui

Version:

Atlassian User Interface Framework

316 lines (271 loc) 9.36 kB
'use strict'; import $ from './jquery'; import Alignment from './internal/alignment'; import amdify from './internal/amdify'; import attributes from './internal/attributes'; import enforce from './internal/enforcer'; import globalize from './internal/globalize'; import layer from './layer'; import skate from './internal/skate'; import state from './internal/state'; var DEFAULT_HOVEROUT_DELAY = 1000; function getTrigger (element) { return document.querySelector('[aria-controls="' + element.id + '"]'); } function doIfTrigger (element, callback) { var trigger = getTrigger(element); if (trigger) { callback(trigger); } } function initAlignment (element, trigger) { if (!element._auiAlignment) { element._auiAlignment = new Alignment(element, trigger); } } function enableAlignment (element, trigger) { initAlignment(element, trigger); element._auiAlignment.enable(); } function disableAlignment (element, trigger) { initAlignment(element, trigger); element._auiAlignment.disable(); } function handleMessage (element, message) { var messageTypeMap = { toggle: ['click'], hover: ['mouseenter', 'mouseleave', 'focus', 'blur'] }; var messageList = messageTypeMap[element.respondsTo]; if (messageList && messageList.indexOf(message.type) > -1) { messageHandler[message.type](element, message); } } var messageHandler = { click: function (element) { if (element.open) { if (!layer(element).isPersistent()) { element.open = false; } } else { element.open = true; } }, mouseenter: function (element) { if (!element.open) { element.open = true; } if (element._clearMouseleaveTimeout) { element._clearMouseleaveTimeout(); } }, mouseleave: function (element) { if (layer(element).isPersistent() || !element.open) { return; } if (element._clearMouseleaveTimeout) { element._clearMouseleaveTimeout(); } var timeout = setTimeout(function () { if (!state(element).get('mouse-inside')) { element.open = false; } }, DEFAULT_HOVEROUT_DELAY); element._clearMouseleaveTimeout = function () { clearTimeout(timeout); element._clearMouseleaveTimeout = null; }; }, focus: function (element) { if (!element.open) { element.open = true; } }, blur: function (element) { if (!layer(element).isPersistent() && element.open) { element.open = false; } } }; function onMouseEnter(e) { var element = e.target; state(element).set('mouse-inside', true); element.message({ type: 'mouseenter' }); } function onMouseLeave(e) { var element = e.target; state(element).set('mouse-inside', false); element.message({ type: 'mouseleave' }); } function rebindMouseEvents(el) { state(el).set('mouse-inside', undefined); el.removeEventListener('mouseenter', onMouseEnter); el.removeEventListener('mouseleave', onMouseLeave); if (el.respondsTo === 'hover') { state(el).set('mouse-inside', false); el.addEventListener('mouseenter', onMouseEnter); el.addEventListener('mouseleave', onMouseLeave); } } function showInlineDialog(el) { layer(el).show(); if (layer(el).isVisible()) { doIfTrigger(el, function (trigger) { enableAlignment(el, trigger); trigger.setAttribute('aria-expanded', 'true'); }); } else { el.open = false; } } function hideInlineDialog(el) { layer(el).hide(); if (!layer(el).isVisible()) { doIfTrigger(el, function (trigger) { disableAlignment(el, trigger); trigger.setAttribute('aria-expanded', 'false'); }); } else { el.open = true; } } function reflectOpenness(el) { const isInitalizing = !el.hasAttribute('aria-hidden'); const shouldBeOpen = el.hasAttribute('open'); if (isInitalizing || el.open !== shouldBeOpen) { if (shouldBeOpen) { state(el).set('is-processing-show', true); showInlineDialog(el); state(el).set('is-processing-show', false); } else { hideInlineDialog(el); } } } const RESPONDS_TO_ATTRIBUTE_ENUM = { attribute: 'responds-to', values: ['toggle', 'hover'], missingDefault: 'toggle', invalidDefault: 'toggle' }; let inlineDialog = skate('aui-inline-dialog', { prototype: { /** * Returns whether the inline dialog is open. */ get open() { return layer(this).isVisible(); }, /** * Opens or closes the inline dialog, returning whether the dialog is * open or closed as a result (since event handlers can prevent either * action). * * You should check the value of open after setting this * value since the before show/hide events may have prevented it. */ set open(value) { // TODO AUI-3726 Revisit double calls to canceled event handlers. // Explicitly calling reflectOpenness(…) in this setter means // that in native we'll get two sync calls to reflectOpenness(…) // and in polyfill one sync (here) and one async (attr change // handler). The latter of the two calls, for both cases, will // usually be a noop (except when show/hide events are cancelled). attributes.setBooleanAttribute(this, 'open', value); reflectOpenness(this); }, get persistent() { return this.hasAttribute('persistent'); }, set persistent(value) { attributes.setBooleanAttribute(this, 'persistent', value); }, get respondsTo() { var attr = RESPONDS_TO_ATTRIBUTE_ENUM.attribute; return attributes.computeEnumValue(RESPONDS_TO_ATTRIBUTE_ENUM, this.getAttribute(attr)); }, set respondsTo(value) { const oldComputedValue = this.respondsTo; attributes.setEnumAttribute(this, RESPONDS_TO_ATTRIBUTE_ENUM, value); if (oldComputedValue !== this.respondsTo) { rebindMouseEvents(this); } }, /** * Handles the receiving of a message from another component. * * @param {Object} msg The message to act on. * * @returns {HTMLElement} */ message: function (msg) { handleMessage(this, msg); return this; } }, created: function (element) { state(element).set('is-processing-show', false); doIfTrigger(element, function (trigger) { trigger.setAttribute('aria-expanded', element.open); trigger.setAttribute('aria-haspopup', 'true'); }); }, attributes: { 'aria-hidden': function (element, change) { if (change.newValue === 'true') { const trigger = getTrigger(element); if (trigger) { trigger.setAttribute('aria-expanded', 'false'); } } // Whenever layer manager hides us, we need to sync the open attribute. attributes.setBooleanAttribute(element, 'open', change.newValue === 'false'); }, open: function (element) { // skate runs the created callback for attributes before the // element is attached to the DOM, so guard against that. if (document.body.contains(element)) { reflectOpenness(element); } }, 'responds-to': function (element, change) { const oldComputedValue = attributes.computeEnumValue(RESPONDS_TO_ATTRIBUTE_ENUM, change.oldValue); const newComputedValue = attributes.computeEnumValue(RESPONDS_TO_ATTRIBUTE_ENUM, change.newValue); if (oldComputedValue !== newComputedValue) { rebindMouseEvents(element); } } }, attached: function (element) { enforce(element).attributeExists('id'); if (element.hasAttribute('open')) { // show() can cause the element to be reattached (to the <body>), // so guard against a nested show() call that blows up the layer // manager (since it sees us pushing the same element twice). if (!state(element).get('is-processing-show')) { reflectOpenness(element); } } else { reflectOpenness(element); } rebindMouseEvents(element); }, detached: function (element) { if (element._auiAlignment) { element._auiAlignment.destroy(); } }, template: function (element) { var elem = $('<div class="aui-inline-dialog-contents"></div>').append(element.childNodes); $(element) .addClass('aui-layer') .html(elem); } }); amdify('aui/inline-dialog2', inlineDialog); globalize('InlineDialog2', inlineDialog); export default inlineDialog;