UNPKG

apex4x

Version:

The Comprehensive ARIA Development Suite

859 lines (835 loc) 33.2 kB
/*@license ARIA Listbox Module 3.2 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 (!("setListbox" in $A)) { $A.import("RovingTabIndex", { name: "ListboxModule", props: props, once: true, call: function (props) { var isIE = $A.isIE(); $A.addWidgetProfile("Listbox", { configure: function (dc) { return { preload: true, preloadImages: true, preloadCSS: true, className: "aria-listbox", storeData: true, }; }, afterRender: function (dc) { dc.update(); $A.loop( dc.RTI.nodes, function (i, o) { dc.getState( o, $A.getAttr(o, "aria-checked"), $A.hasAttr(o, "aria-checked"), false, dc.RTI.nodes, ); }, "array", ); }, }); $A.extend({ setListbox: function (o, config) { if (this._4X) { config = o; o = this._X; } if ($A.isPlainObject(o)) { config = o; o = config.select || config.listbox || null; } config = config || {}; var tag = $A.extend( true, { parent: "ul", child: "button", parse: function (ref) { return ref.querySelectorAll(tag.child); }, build: { parent: '<ul class="aria-listbox"></ul>', child: '<li><button class="option"><span class="lbl">{OPTION-TEXT}</span></button></li>', }, }, config.tag || {}, ), getState = function ( o, attributeValue, hasAttribute, write, nodes, ) { if (hasAttribute) { var c = 0; if (attributeValue === "true") c = 1; else if (attributeValue === "mixed") c = 2; else attributeValue = "false"; $A.data(o, "check", c); if (write) { $A.setAttr(o, "aria-checked", attributeValue); } return c; } else { var s = $A.data(o, "check"); if ($A.isNum(s)) return s; } return false; }, genListbox = function (ref) { if (!$A.isNode(ref)) return; if (!ref.id) ref.id = $A.genId(); $A.svgFix(ref); DC = $A.toDC( $A.extend( { id: ref.id, content: ref, trigger: init.select.nodeType ? init.select : null, on: {}, widgetType: "Listbox", toggleHide: true, getState: getState, }, config, ), ); init.update(); }, DC = null, init = { update: function () { init.optionNodes = init.select.nodeType ? init.select.querySelectorAll("option") : []; init.options = []; if (init.select.nodeType) { $A.empty(init.listbox); $A.loop( init.optionNodes, function (i, o) { var name = $A.getText(o), c = tag.build.child.replace("{OPTION-TEXT}", name); c = $A.toNode(c, true); var a = c.querySelector(tag.child); if ($A.isNode(a)) { $A.bindObjects(a, o); init.options.push(a); $A.append(c, init.listbox); } }, "array", ); $A.on(init.select, "change", function (ev) { var ix = -1; $A.loop( init.optionNodes, function (i, o) { init.toggleSelect( $A.boundTo(o), o.selected ? true : false, false, init.multiple, true, ); if (ix < 0 && o.selected) ix = i; }, "array", ); DC.RTI.activate(ix >= 0 ? ix : 0); }); } else init.options = tag.parse(init.listbox); $A.loop( init.options, function (i, o) { var check = getState( o, $A.getAttr(o, "data-check"), init.checkable || $A.hasAttr(o, "data-check"), ), n = ($A.hasAttr(o, "data-controls") && $A.morph($A.getAttr(o, "data-controls"))) || ($A.isFn(o.querySelector) && o.querySelector("input")) || false; if (check !== false) { if ($A.isNode(n)) { if (!$A.hasBound(o)) $A.bindObjects(n, o); if (n.checked) check = 1; else if (check) n.checked = true; } var c = "false"; if (check === 1) c = "true"; else if (check === 2) c = "mixed"; $A.setAttr(o, { "aria-checked": c, }); } var select = $A.hasAttr(o, "data-select") || ($A.isNode($A.boundTo(o)) && $A.boundTo(o).selected); $A.setAttr(o, "aria-selected", select ? "true" : "false"); $A.data(o, "_Selected", select); $A.closest(o, function (o) { if (o === init.listbox) return true; $A.setAttr(o, "role", "presentation"); }); $A.on( o, "attributeChange", function ( MutationObject, o, attributeName, attributeValue, attributePriorValue, boundNode, SavedData, ) { if (attributeName === "aria-checked") { if ($A.isNode(n)) { var check = getState(o, attributeValue, true); n.checked = check ? true : false; } } else if (attributeName === "aria-selected") { $A.data(o, "_Selected", attributeValue === "true"); if ( boundNode && $A.data(o, "_Selected") !== (boundNode.selected ? true : false) ) { boundNode.selected = $A.data(o, "_Selected"); } } }, { attributeFilter: ["aria-checked", "aria-selected"], }, ); }, "array", ); $A.updateDisabled(init.options); init.setFlags(); init.setListbox(); init.setRoles(); init.setEvents(); init.setSelected(); }, setFlags: function () { var select = init.select.nodeType ? init.select : config; init.multiple = select.multiple ? true : !init.select.nodeType && init.listbox && (config.multiselect || $A.getAttr(init.listbox, "aria-multiselectable") === "true"); init.required = select.required ? true : false; init.disabled = select.disabled ? true : false; init.checkable = config.checkable ? true : false; init.sortable = config.sortable ? true : false; if (init.sortable) init.multiple = init.checkable = false; if (init.checkable) init.multiple = false; }, setListbox: function () { if (init.multiple) $A.setAttr(init.listbox, "aria-multiselectable", "true"); if (init.required) $A.setAttr(init.listbox, "aria-required", "true"); if (init.disabled) $A.setAttr(init.listbox, "aria-disabled", "true"); var hiddenName = ""; if (init.select.nodeType && $A.isHidden(init.select)) { var tmp = init.select.cloneNode(); tmp.hidden = false; hiddenName = $A.isFn($A.getAccName) && $A.getAccName(tmp).name; } $A.setAttr( init.listbox, "aria-label", config.label || hiddenName || (init.select.nodeType ? $A.isFn($A.getAccName) ? $A.getAccName(init.select).name : "" : ""), ); }, setRoles: function () { $A.remAttr( init.listbox.querySelectorAll( '*[role="listbox"], *[role="option"]', ), "role", ); $A.setAttr(init.listbox, "role", "listbox"); $A.setAttr(init.options, "role", "option"); init.setGrab(); }, setSelected: function () { if (init.select.nodeType) { $A.loop( init.optionNodes, function (i, o) { init.toggleSelect( $A.boundTo(o), o.selected ? true : false, false, init.multiple, true, ); }, "array", ); } }, setEvents: function () { if (DC.RTI) DC.RTI.off(); if (DC.disabled) return; var x = 0, n = init.listbox.querySelector( '*[role="option"][aria-selected="true"]', ); if ($A.isNode(n)) x = $A.inArray(n, init.options) || 0; DC.RTI = new $A.RovingTabIndex( $A.extend( { container: init.listbox, nodes: init.options, orientation: $A.isNum(config.orientation) ? config.orientation : 2, autoSwitch: [].indexOf(config.autoSwitch) !== -1 ? config.autoSwitch : "off", autoLoop: false, startIndex: x, DC: DC, onShiftUp: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.multiple) { init.toggleSelect(option, true); } ev.preventDefault(); }, onShiftDown: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.multiple) { init.toggleSelect(option, true); } ev.preventDefault(); }, onCtrlShiftUp: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { RTI["onShiftUp"].call( this, ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ); ev.preventDefault(); }, onCtrlShiftDown: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { RTI["onShiftDown"].call( this, ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ); ev.preventDefault(); }, onShiftEnd: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.multiple) { var s = init.options.slice(RTI.index); $A.loop( s, function (i, o) { init.toggleSelect(o, true); }, "array", ); } ev.preventDefault(); }, onShiftHome: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.multiple) { var s = init.options.slice(0, RTI.index + 1); $A.loop( s, function (i, o) { init.toggleSelect(o, true); }, "array", ); } ev.preventDefault(); }, onCtrlShiftEnd: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { RTI["onShiftEnd"].call( this, ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ); ev.preventDefault(); }, onCtrlShiftHome: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { RTI["onShiftHome"].call( this, ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ); ev.preventDefault(); }, onPageUp: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { var d = Math.round(init.options.length * 0.1); if (!d) d = 1; var i = init.index - d; if (i < 0) i = 0; RTI.focus(i); ev.preventDefault(); }, onPageDown: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { var d = Math.round(init.options.length * 0.1); if (!d) d = 1; var i = init.index + d; if (i >= init.options.length) i = init.options.length - 1; RTI.focus(i); ev.preventDefault(); }, onSpace: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.sortable) init.toggleGrab(option); else RTI.onClick.apply(option, arguments); if (isIE) { setTimeout(function () { $A.announce( $A.getAttr(option, "aria-description"), ); }, 1); } ev.preventDefault(); }, onCtrlSpace: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.sortable) init.toggleGrab(option); else RTI.onClick.apply(option, arguments); if (isIE) { setTimeout(function () { $A.announce( $A.getAttr(option, "aria-description"), ); }, 1); } ev.preventDefault(); }, onCtrlShiftSpace: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { $A.loop( init.options, function (i, o) { init.toggleSelect(o, false); }, "array", ); if (init.checkable) init.check(init.options, "false"); if (init.sortable) { init.toggleGrab.grabbed = undefined; init.setGrab(); } if (isIE) { setTimeout(function () { $A.announce( $A.getAttr(option, "aria-description"), ); }, 1); } ev.preventDefault(); }, onEsc: function (ev, option, RTI, DC) { if (init.sortable) { init.toggleGrab.grabbed = undefined; init.setGrab(); } if (isIE) { setTimeout(function () { $A.announce( $A.getAttr(option, "aria-description"), ); }, 1); } ev.preventDefault(); }, onFocus: function (ev, option, RTI, DC) { init.index = RTI.index; if (!init.multiple) init.toggleSelect(option, true); if (isIE) { setTimeout(function () { $A.announce( $A.getAttr(option, "aria-description"), ); }, 1); } ev.stopPropagation(); }, onClick: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { var that = option, isDisabled = $A.isDisabled(that), check = getState(that); if (!$A.isNum(check) && init.multiple) check = $A.getAttr(that, "aria-selected") === "true"; if (!isDisabled && init.sortable) init.toggleGrab(that); if (!isDisabled && $A.isFn(config.onActivate)) { config.onActivate.apply(that, [ ev, option, RTI, DC || $A.boundTo(that), check, function (attributeValue) { if ($A.hasAttr(option, "aria-checked")) getState( option, attributeValue, true, true, RTI.nodes, ); else if (attributeValue) $A.setAttr( option, "aria-selected", attributeValue === "true" ? "true" : "false", ); }, ]); } ev.preventDefault(); }, onSelectAll: function ( ev, option, RTI, DC, arrowKeyCode, isTop, isBottom, ) { if (init.multiple) { $A.loop( init.options, function (i, o) { init.toggleSelect(o, true); }, "array", ); } ev.preventDefault(); }, }, config.extendRTI || {}, ), ); $A(init.listbox) .setAttr("tabindex", "0") .on("focus click", function (ev) { if (init.options.length) { if (!$A.isTouch) DC.RTI.focus(); $A.setAttr(init.listbox, "tabindex", "-1"); } }); }, toggleClassName: "selected", selectText: "Selected", unselectText: "Not Selected", toggleSelect: function (o, state, skip, recur, fromOption) { if (!recur && !init.multiple) { $A.loop( init.listbox.querySelectorAll( '*[role="option"][aria-selected="true"]', ), function (i, O) { if (O !== o) init.toggleSelect(O, false, false, true); }, "array", ); } if (!$A.isBool(state)) state = $A.data(o, "_Selected") ? false : true; $A.data(o, "_Selected", state); if (fromOption) { DC.RTI.activate(o); } $A.setAttr(o, "aria-selected", state ? "true" : "false"); $A.toggleClass( o, init.toggleClassName, state, function (state) {}, ); }, grabText: "Grabbable", grabbedText: "Grabbed", dropText: "Droppable", setGrab: function (skip) { if (init.sortable) { $A.remAttr(init.options, [ "aria-grabbed", "aria-dropeffect", "aria-description", ]); if (!skip) $A.setAttr(init.options, { "aria-grabbed": "false", }); if (isIE) $A.setAttr(init.options, { "aria-description": init.grabText, }); } }, toggleGrab: function (o) { if (init.sortable) { init.setGrab(true); if (!init.toggleGrab.grabbed) { init.toggleGrab.grabbed = o; $A.loop( init.options, function (i, n) { var a = {}; if (n === o) { a["aria-grabbed"] = "true"; if (isIE) a["aria-description"] = init.grabbedText; } else { a["aria-dropeffect"] = "move"; if (isIE) a["aria-description"] = init.dropText; } $A.setAttr(n, a); }, "array", ); } else { var x = $A.inArray(o, DC.RTI.nodes) || 0; if (init.select.nodeType) $A.before( $A.boundTo(init.toggleGrab.grabbed), $A.boundTo(o), ); else $A.before(init.toggleGrab.grabbed, o); init.update(); DC.RTI.focus(init.select.nodeType ? x : o); init.toggleGrab.grabbed = undefined; } } }, check: function (o, v) { getState(o, v, true, true, DC.RTI.nodes); }, value: function (o) { var checked = init.listbox.querySelectorAll( '*[role="option"][aria-checked="true"]', ); if (checked && checked.length) return checked; else if (init.select.nodeType) { if ($A.isNode(o)) return $A.getAttr($A.boundTo(o), "value"); else return init.select.value; } else return init.listbox.querySelectorAll( '*[role="option"][aria-selected="true"]', ); }, }; o = $A.morph(o); var gen = function (o) { if ($A.isNative(o)) init.select = o; else if (config.select && $A.isNative($A.morph(config.select))) init.select = $A.morph(config.select); else init.select = { nodeType: false }; config.select = init.select; var ref = $A.getAttr(init.select, "data-controls"); if (ref && $A.isNode($A.morph(ref))) init.listbox = $A.morph(ref); else if (!$A.isNative(o)) init.listbox = o; else if (config.listbox && $A.morph(config.listbox)) init.listbox = $A.morph(config.listbox); else init.listbox = $A.morph(tag.build.parent); config.listbox = init.listbox; if ($A.isNode(init.select) && !$A.isWithin(init.listbox)) $A(init.listbox).before(init.select); init = $A.extend(init, config); config = $A.extend(config, init); genListbox(init.listbox); }; var p = config.fetch && config.fetch.url, 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) { $A.load( p, config.root, { selector: s, }, function (c) { gen(c); }, ); } else gen(o); return $A._XR.call(this, DC); }, }); }, }); } })();