apex4x
Version:
The Comprehensive ARIA Development Suite
429 lines (412 loc) • 14.5 kB
JavaScript
/*@license
ARIA Menu Module 3.4 for Apex 4X
Author: Bryan Garaventa (https://www.linkedin.com/in/bgaraventa)
Home: WhatSock.com : Download: https://github.com/whatsock/apex
License: MIT (https://opensource.org/licenses/MIT)
Required dependencies: RovingTabIndex.js
*/
(function () {
if (!("setMenu" in $A)) {
$A.dcMenus = [];
$A.addWidgetProfile("Menu", {
configure: function (dc) {
return {
horizontalHelpTip:
"To move through items, press left or right arrow.",
verticalHelpTip: "To move through items, press up or down arrow.",
toggleHide: true,
forceFocus: true,
forceFocusWithin: true,
returnFocus: true,
preload: true,
preloadImages: true,
preloadCSS: true,
className: "menu",
escToClose: true,
click: function (ev, dc) {
ev.stopPropagation();
},
storeData: true,
};
},
role: function (dc) {
return {
role: "menu",
"aria-orientation": $A.getOrientation(dc.RTI.nodes).orientation,
};
},
beforeRender: function (dc) {
if (dc.isTopMenu) {
for (var i = 0; i < $A.dcMenus.length; i++) {
var mDC = $A.dcMenus[i];
if (mDC && dc !== mDC) {
if (mDC.loaded) mDC.bypass();
else if (mDC.isAnimating && mDC.loading && $A.Velocity) {
mDC.abortLoad = true;
$A.Velocity.animate(mDC.wrapper, "finish");
mDC.css(mDC.style);
}
}
}
}
},
afterRender: function (dc) {
if (!$A.isTouch) {
setTimeout(function () {
$A.announce(
dc[
dc.getAttr("aria-orientation") === "horizontal"
? "horizontalHelpTip"
: "verticalHelpTip"
],
);
}, 1);
}
if (!dc.isTopMenu) $A.setAttr(dc.triggerNode, "aria-expanded", "true");
},
beforeRemove: function (dc) {
$A.loop(
dc.RTI.children,
function (i, o) {
o.DC.remove();
},
"map",
);
},
afterRemove: function (dc) {
if (!dc.isTopMenu) $A.setAttr(dc.triggerNode, "aria-expanded", "false");
},
});
var isIE = $A.isIE();
$A.extend({
setMenu: function (o, config) {
if (this._4X) {
config = o;
o = this._X;
}
if ($A.isPlainObject(o)) {
config = o;
o = config.trigger || null;
}
if (!o) return null;
config = config || {};
var main = null,
tag = $A.extend(
true,
{
parent: "ul",
child: "a",
parse: function (ref) {
if (isIE) {
var mItems = [];
$A.query(ref.children, function (i, o) {
var c = $A.first(o, function (e) {
if (e.nodeName.toLowerCase() === tag.child) return true;
});
if ($A.isNode(c)) mItems.push(c);
});
return mItems;
} else return ref.querySelectorAll(":scope > * > " + tag.child);
},
},
config.tag || {},
),
getState = function (o, attributeValue, hasAttribute, write, nodes) {
if (hasAttribute) {
var isRadio = $A.getAttr(o, "role") === "menuitemradio",
c = 0;
if (attributeValue === "true") c = 1;
else if (!isRadio && attributeValue === "mixed") c = 2;
else attributeValue = "false";
$A.data(o, "check", c);
if (write) {
if (isRadio && $A.isArray(nodes))
$A.loop(
nodes,
function (i, n) {
if ($A.hasAttr(n, "aria-checked") && n !== o)
$A.setAttr(n, "aria-checked", "false");
},
"array",
);
$A.setAttr(o, "aria-checked", attributeValue);
}
return c;
} else {
var s = $A.data(o, "check");
if ($A.isNum(s)) return s;
}
return false;
},
genMenu = function (o, p, list, isTop) {
if (!$A.isNode(o)) return;
var ref =
list ||
$A.getAttr(o, "data-menu") ||
$A.next(o, function (e) {
if (e.nodeName.toLowerCase() === tag.parent) return true;
});
ref = $A.morph(ref);
if (!$A.isNode(ref)) return;
$A.setAttr(o, "aria-haspopup", "true");
if (!isTop) $A.setAttr(o, "aria-expanded", "false");
var mItems = tag.parse(ref);
$A.svgFix(ref);
var DC = $A.toDC(
$A.extend(
{
trigger: o,
content: ref,
on: "openmenu",
widgetType: "Menu",
isTopMenu: isTop,
autoCloseSameWidget: false,
toggleHide: true,
getState: getState,
},
config,
),
);
if (p)
DC.map({
parent: p.DC,
});
DC.RTI = new $A.RovingTabIndex(
$A.extend(
{
DC: DC,
parent: p,
trigger: o,
nodes: mItems,
startIndex: 0,
orientation: 2,
autoSwitch: config.autoSwitch || "semi",
autoLoop: true,
onOpen: function (ev, triggerNode, RTI, DC, arrowKey) {
var that = this,
isDisabled = $A.isDisabled(that),
check = getState(triggerNode);
if (isDisabled && !arrowKey) {
if ($A.isDC(RTI.DC)) RTI.DC.top.remove();
return;
}
if ($A.isDC(DC)) {
DC.render();
ev.preventDefault();
} else if (arrowKey) {
return;
} else if ($A.isFn(config.onActivate)) {
config.onActivate.apply(that, [
ev,
triggerNode,
RTI,
DC || $A.boundTo(triggerNode),
check,
function (attributeValue) {
var r = $A.getAttr(triggerNode, "role"),
isRadio = ["menuitemradio"].indexOf(r) !== -1,
isCheckbox = ["menuitemcheckbox"].indexOf(r) !== -1;
if (isRadio || isCheckbox)
getState(
triggerNode,
attributeValue,
true,
true,
isRadio ? RTI.nodes : null,
);
},
$A.getAttr(triggerNode, "role") === "menuitemradio",
]);
}
},
onSpace: function (ev, triggerNode, RTI, DC) {
if ($A.isDC(DC)) DC.render();
ev.preventDefault();
},
onEnter: function (ev, triggerNode, RTI, DC) {
if ($A.isDC(DC)) DC.render();
ev.preventDefault();
},
onClose: function (ev, triggerNode, RTI, DC, arrowKey) {
if ($A.isDC(RTI.DC) && RTI.parent)
RTI.DC.remove(function () {
setTimeout(function () {
$A.announce(
RTI.parent.DC[
RTI.parent.DC.getAttr("aria-orientation") ===
"horizontal"
? "horizontalHelpTip"
: "verticalHelpTip"
],
);
}, 1);
});
ev.preventDefault();
},
onEsc: function (ev, triggerNode, RTI, DC) {
if ($A.isDC(RTI.DC)) RTI.DC.remove();
ev.preventDefault();
},
onShiftTab: function (ev, triggerNode, RTI, DC) {
if ($A.isDC(RTI.DC)) RTI.DC.top.remove();
ev.preventDefault();
},
onTab: function (ev, triggerNode, RTI, DC) {
if ($A.isDC(RTI.DC)) RTI.DC.top.remove();
ev.preventDefault();
},
},
config.extendRTI || {},
),
);
$A.loop(
mItems,
function (i, o) {
genMenu(o, DC.RTI);
var radio = getState(
o,
$A.getAttr(o, "data-radio"),
$A.hasAttr(o, "data-radio"),
),
check = getState(
o,
$A.getAttr(o, "data-check"),
$A.hasAttr(o, "data-check"),
),
n =
($A.isFn(o.querySelector) && o.querySelector("input")) ||
false;
if ($A.isNum(radio)) {
if (n && n.checked) radio = 1;
$A.setAttr(o, {
role: "menuitemradio",
"aria-checked": radio ? "true" : "false",
});
} else if ($A.isNum(check)) {
if (n && n.checked) check = 1;
var c = "false";
if (check === 1) c = "true";
else if (check === 2) c = "mixed";
$A.setAttr(o, {
role: "menuitemcheckbox",
"aria-checked": c,
});
} else $A.setAttr(o, "role", "menuitem");
if (radio !== false || check !== false) {
$A(o).on(
"attributeChange",
function (
MutationObject,
o,
attributeName,
attributeValue,
attributePriorValue,
DC,
SavedData,
) {
if ($A.isNode(n)) {
var check = getState(o, attributeValue, true);
n.checked = check ? true : false;
}
},
{
attributeFilter: ["aria-checked"],
},
);
}
$A.closest(o, function (o) {
if (o === ref) return true;
$A.setAttr(o, "role", "presentation");
});
},
"array",
);
$A.updateDisabled(mItems);
return DC;
};
var DC = null;
$A.query(o, config.context || document, function (i, o) {
var gen = function (m) {
DC = genMenu(o, null, m, true);
if ($A.isDC(DC.top) && $A.inArray(DC.top, $A.dcMenus) === -1)
$A.dcMenus.push(DC.top);
$A.on(window.document, "click.closemenus", function () {
DC.top.remove();
});
if (!config.rightClick) config.leftClick = true;
var e;
if (config.leftClick) {
e = {
keydown: function (ev, dc) {
var k = $A.keyEvent(ev);
if (k === 40 || k === 13 || k === 32) {
dc.render();
ev.stopPropagation();
ev.preventDefault();
}
},
click: function (ev, dc) {
dc.render();
ev.stopPropagation();
ev.preventDefault();
},
};
}
if (config.rightClick) {
e = {
contextmenu: function (ev, dc) {
ev.preventDefault();
},
mouseup: function (ev, dc) {
var btn = -1;
if (!$A.isNum(ev.which))
btn = ev.button < 2 ? 1 : ev.button === 4 ? 3 : 2;
else btn = ev.which < 2 ? 1 : ev.which === 2 ? 3 : 2;
if (btn === 2) {
dc.render();
ev.preventDefault();
}
},
keydown: function (ev, dc) {
var k = $A.keyEvent(ev);
if (k === 93 || (ev.shiftKey && k === 121)) {
dc.render();
ev.preventDefault();
}
},
};
}
$A.on(o, e);
};
var p =
(config.fetch && config.fetch.url) || $A.getAttr(o, "data-menu"),
s =
(config.fetch &&
config.fetch.data &&
config.fetch.data.selector) ||
$A.getSelectorFromURI(p),
isP = s && $A.isPath(p) ? true : false;
config.fetch = null;
if (isP) {
config.toggleHide = false;
var d = $A.toNode();
$A.load(
p,
d,
{
selector: s,
},
function (c) {
main = c;
gen(main);
},
);
} else {
gen(main);
}
});
return $A._XR.call(this, DC);
},
});
}
})();