@atlassian/aui
Version:
Atlassian User Interface library
169 lines (150 loc) • 4.98 kB
JavaScript
import $ from './jquery';
import keyCode from './key-code';
import amdify from './internal/amdify';
import skate from './internal/skate';
import state from './internal/state';
import { warn } from './internal/log';
export function getTrigger(element) {
return state(element).get('last-trigger') || findControllers(element)[0];
}
export function setTrigger(element, trigger) {
var validTrigger = trigger && trigger.nodeType && trigger.nodeType === 1;
return state(element).set('last-trigger', validTrigger ? trigger : false);
}
export function hasTrigger(element) {
return !!getTrigger(element);
}
export function doIfTrigger(element, callback) {
var trigger = getTrigger(element);
if (trigger) {
callback(trigger);
}
}
export function forEachTrigger(element, callback) {
return Array.prototype.forEach.call(findControllers(element), callback);
}
function isNestedAnchor(trigger, target) {
var $closestAnchor = $(target).closest('a[href]', trigger);
return !!$closestAnchor.length && $closestAnchor[0] !== trigger;
}
function findControllers(element) {
const frames = window.frames;
const selector = `[aria-controls="${element.id}"]`;
let controllers = [];
let someFramesAreCrossOrigin = false;
for (let i = 0; i < frames.length; i++) {
try {
let nodeList = frames[i].document.querySelectorAll(selector);
controllers = controllers.concat(Array.prototype.slice.apply(nodeList));
// eslint-disable-next-line no-unused-vars
} catch (e) {
// Silently catch DOM exceptions related to accessing cross-origin frames
someFramesAreCrossOrigin = true;
}
}
const currentDocumentControllers = document.querySelectorAll(selector);
const allControllers = Array.prototype.slice
.apply(currentDocumentControllers)
.concat(controllers);
if (allControllers.length === 0 && someFramesAreCrossOrigin === true) {
warn(
[
`No triggers found for element (${element.id}) in iframes from the same origin.`,
'However some iframes in this document are cross-origin.',
'The trigger-element relations crossing the origin boundary are not supported.',
].join(' ')
);
}
return allControllers;
}
function findControlled(trigger) {
return document.getElementById(trigger.getAttribute('aria-controls'));
}
function isEnabled(element) {
return element.getAttribute('aria-disabled') !== 'true';
}
function triggerMessage(trigger, e) {
if (isEnabled(trigger)) {
var component = findControlled(trigger);
if (component && component.message) {
component.message(e);
}
}
}
/**
* Converts native or jQuery events in to a "message" object.
* Basically helps us keep message types consistent.
*/
function msg(e, type) {
const { target, currentTarget, relatedTarget } = e;
const { keyCode, which } = e;
return {
type,
data: type === 'keydown' ? which || keyCode : undefined,
target,
currentTarget,
relatedTarget,
preventDefault: () => e.preventDefault(),
};
}
function focusingToControlledElement(trigger, e) {
let relatedTarget = e.relatedTarget;
// relatedTarget is always null in IE11 but activeElement is set to correct value
if (!relatedTarget) {
relatedTarget = document.activeElement;
}
const $component = $(findControlled(trigger));
return $component.find(relatedTarget).length > 0;
}
const events = {
click(trigger, e) {
if (!isNestedAnchor(trigger, e.target)) {
triggerMessage(trigger, e);
e.preventDefault();
}
},
keydown(trigger, e) {
const key = e.data;
if (key === keyCode.ENTER || key === keyCode.SPACE) {
e.preventDefault();
e.type = 'click';
events.click(trigger, e);
}
},
mouseenter(trigger, e) {
triggerMessage(trigger, e);
},
mouseleave(trigger, e) {
triggerMessage(trigger, e);
},
focus(trigger, e) {
triggerMessage(trigger, e);
},
blur(trigger, e) {
if (focusingToControlledElement(trigger, e)) {
return;
}
triggerMessage(trigger, e);
},
};
Object.keys(events).forEach(function (name) {
const handler = events[name];
$(document).on(`${name}.aui-trigger`, '[data-aui-trigger]', function (e) {
handler(e.currentTarget, msg(e, name));
});
});
skate('data-aui-trigger', {
type: skate.type.ATTRIBUTE,
prototype: {
disable: function () {
this.setAttribute('aria-disabled', 'true');
},
enable: function () {
this.setAttribute('aria-disabled', 'false');
},
isEnabled: function () {
return isEnabled(this);
},
},
});
amdify('aui/trigger');