@atlassian/aui
Version:
Atlassian User Interface Framework
316 lines (271 loc) • 9.36 kB
JavaScript
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;
;