rivet-core
Version:
Indiana University design system
1,327 lines (1,130 loc) • 190 kB
JavaScript
/*!
* rivet-core - @version 2.9.1
*
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*/
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Rivet = function (exports) {
'use strict';
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* Element.matches() polyfill
*****************************************************************************/
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
/******************************************************************************
* Element.closest() polyfill
*
* @see https://go.iu.edu/4ftm
*****************************************************************************/
if (!Element.prototype.closest) {
Element.prototype.closest = function (selector) {
var el = this;
var ancestor = this;
if (!document.documentElement.contains(el)) {
return null;
}
do {
if (ancestor.matches(selector)) {
return ancestor;
}
ancestor = ancestor.parentElement;
} while (ancestor !== null);
return null;
};
}
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* CustomEvent polyfill
*
* @see https://go.iu.edu/4ftn
*****************************************************************************/
(function () {
if (typeof window.CustomEvent === 'function') {
return false;
}
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var customEvent = document.createEvent('CustomEvent');
customEvent.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return customEvent;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
/******************************************************************************
* Copyright (C) 2022 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* Array.from() polyfill
*
* @see https://go.iu.edu/4ftl
*****************************************************************************/
if (!Array.from) {
Array.from = function () {
var symbolIterator;
try {
symbolIterator = Symbol.iterator ? Symbol.iterator : 'Symbol(Symbol.iterator)';
} catch (e) {
symbolIterator = 'Symbol(Symbol.iterator)';
}
var toStr = Object.prototype.toString;
var isCallable = function isCallable(fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function toInteger(value) {
var number = Number(value);
if (isNaN(number)) return 0;
if (number === 0 || !isFinite(number)) return number;
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function toLength(value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
var setGetItemHandler = function setGetItemHandler(isIterator, items) {
var iterator = isIterator && items[symbolIterator]();
return function getItem(k) {
return isIterator ? iterator.next() : items[k];
};
};
var getArray = function getArray(T, A, len, getItem, isIterator, mapFn) {
// 16. Let k be 0.
var k = 0; // 17. Repeat, while k < len… or while iterator is done (also steps a - h)
while (k < len || isIterator) {
var item = getItem(k);
var kValue = isIterator ? item.value : item;
if (isIterator && item.done) {
return A;
} else {
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
}
k += 1;
}
if (isIterator) {
throw new TypeError('Array.from: provided arrayLike or iterator has length more then 2 ** 52 - 1');
} else {
A.length = len;
}
return A;
}; // The length property of the from method is 1.
return function from(arrayLikeOrIterator
/*, mapFn, thisArg */
) {
// 1. Let C be the this value.
var C = this; // 2. Let items be ToObject(arrayLikeOrIterator).
var items = Object(arrayLikeOrIterator);
var isIterator = isCallable(items[symbolIterator]); // 3. ReturnIfAbrupt(items).
if (arrayLikeOrIterator == null && !isIterator) {
throw new TypeError('Array.from requires an array-like object or iterator - not null or undefined');
} // 4. If mapfn is undefined, then var mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
} // 5. b. If thisArg was supplied, var T be thisArg; else var T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
} // 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length); // 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method
// of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
return getArray(T, A, len, setGetItemHandler(isIterator, items), isIterator, mapFn);
};
}();
}
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* ChildNode.remove() polyfill
*
* @see https://go.iu.edu/4fto
*****************************************************************************/
(function (arr) {
arr.forEach(function (item) {
if (item.hasOwnProperty('remove')) {
return;
}
Object.defineProperty(item, 'remove', {
configurable: true,
enumerable: true,
writable: true,
value: function remove() {
if (this.parentNode === null) {
return;
}
this.parentNode.removeChild(this);
}
});
});
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
"inert" in HTMLElement.prototype || (Object.defineProperty(HTMLElement.prototype, "inert", {
enumerable: !0,
get: function get() {
return this.hasAttribute("inert");
},
set: function set(h) {
h ? this.setAttribute("inert", "") : this.removeAttribute("inert");
}
}), window.addEventListener("load", function () {
function h(a) {
var b = null;
try {
b = new KeyboardEvent("keydown", {
keyCode: 9,
which: 9,
key: "Tab",
code: "Tab",
keyIdentifier: "U+0009",
shiftKey: !!a,
bubbles: !0
});
} catch (g) {
try {
b = document.createEvent("KeyboardEvent"), b.initKeyboardEvent("keydown", !0, !0, window, "Tab", 0, a ? "Shift" : "", !1, "en");
} catch (d) {}
}
if (b) {
try {
Object.defineProperty(b, "keyCode", {
value: 9
});
} catch (g) {}
document.dispatchEvent(b);
}
}
function k(a) {
for (; a && a !== document.documentElement;) {
if (a.hasAttribute("inert")) return a;
a = a.parentElement;
}
return null;
}
function e(a) {
var b = a.path;
return b && b[0] || a.target;
}
function l(a) {
a.path[a.path.length - 1] !== window && (m(e(a)), a.preventDefault(), a.stopPropagation());
}
function m(a) {
var b = k(a);
if (b) {
if (document.hasFocus() && 0 !== f) {
var g = (c || document).activeElement;
h(0 > f ? !0 : !1);
if (g != (c || document).activeElement) return;
var d = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, {
acceptNode: function acceptNode(a) {
return !a || !a.focus || 0 > a.tabIndex ? NodeFilter.FILTER_SKIP : b.contains(a) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
}
});
d.currentNode = b;
d = (-1 === Math.sign(f) ? d.previousNode : d.nextNode).bind(d);
for (var e; e = d();) {
if (e.focus(), (c || document).activeElement !== g) return;
}
}
a.blur();
}
}
(function (a) {
var b = document.createElement("style");
b.type = "text/css";
b.styleSheet ? b.styleSheet.cssText = a : b.appendChild(document.createTextNode(a));
document.body.appendChild(b);
})("/*[inert]*/*[inert]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}");
var n = function n(a) {
return null;
};
window.ShadowRoot && (n = function n(a) {
for (; a && a !== document.documentElement;) {
if (a instanceof window.ShadowRoot) return a;
a = a.parentNode;
}
return null;
});
var f = 0;
document.addEventListener("keydown", function (a) {
f = 9 === a.keyCode ? a.shiftKey ? -1 : 1 : 0;
});
document.addEventListener("mousedown", function (a) {
f = 0;
});
var c = null;
document.body.addEventListener("focus", function (a) {
var b = e(a);
a = b == a.target ? null : n(b);
if (a != c) {
if (c) {
if (!(c instanceof window.ShadowRoot)) throw Error("not shadow root: " + c);
c.removeEventListener("focusin", l, !0);
}
a && a.addEventListener("focusin", l, !0);
c = a;
}
m(b);
}, !0);
document.addEventListener("click", function (a) {
var b = e(a);
k(b) && (a.preventDefault(), a.stopPropagation());
}, !0);
}));
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
var globalSettings = {
prefix: 'rvt'
};
var Lie = typeof Promise === 'function' ? Promise : function (fn) {
var queue = [],
resolved = 0,
value;
fn(function ($) {
value = $;
resolved = 1;
queue.splice(0).forEach(then);
});
return {
then: then
};
function then(fn) {
return resolved ? setTimeout(fn, 0, value) : queue.push(fn), this;
}
};
var TRUE = true,
FALSE = false;
var QSA$1 = 'querySelectorAll';
function add(node) {
this.observe(node, {
subtree: TRUE,
childList: TRUE
});
}
/**
* Start observing a generic document or root element.
* @param {Function} callback triggered per each dis/connected node
* @param {Element?} root by default, the global document to observe
* @param {Function?} MO by default, the global MutationObserver
* @returns {MutationObserver}
*/
var notify = function notify(callback, root, MO) {
var loop = function loop(nodes, added, removed, connected, pass) {
for (var i = 0, length = nodes.length; i < length; i++) {
var node = nodes[i];
if (pass || QSA$1 in node) {
if (connected) {
if (!added.has(node)) {
added.add(node);
removed["delete"](node);
callback(node, connected);
}
} else if (!removed.has(node)) {
removed.add(node);
added["delete"](node);
callback(node, connected);
}
if (!pass) loop(node[QSA$1]('*'), added, removed, connected, TRUE);
}
}
};
var observer = new (MO || MutationObserver)(function (records) {
for (var added = new Set(), removed = new Set(), i = 0, length = records.length; i < length; i++) {
var _records$i = records[i],
addedNodes = _records$i.addedNodes,
removedNodes = _records$i.removedNodes;
loop(removedNodes, added, removed, FALSE, FALSE);
loop(addedNodes, added, removed, TRUE, FALSE);
}
});
observer.add = add;
observer.add(root || document);
return observer;
};
var QSA = 'querySelectorAll';
var _self = self,
document$1 = _self.document,
Element$1 = _self.Element,
MutationObserver$1 = _self.MutationObserver,
Set$1 = _self.Set,
WeakMap$1 = _self.WeakMap;
var elements = function elements(element) {
return QSA in element;
};
var filter = [].filter;
var QSAO = function QSAO(options) {
var live = new WeakMap$1();
var drop = function drop(elements) {
for (var i = 0, length = elements.length; i < length; i++) {
live["delete"](elements[i]);
}
};
var flush = function flush() {
var records = observer.takeRecords();
for (var i = 0, length = records.length; i < length; i++) {
parse(filter.call(records[i].removedNodes, elements), false);
parse(filter.call(records[i].addedNodes, elements), true);
}
};
var matches = function matches(element) {
return element.matches || element.webkitMatchesSelector || element.msMatchesSelector;
};
var notifier = function notifier(element, connected) {
var selectors;
if (connected) {
for (var q, m = matches(element), i = 0, length = query.length; i < length; i++) {
if (m.call(element, q = query[i])) {
if (!live.has(element)) live.set(element, new Set$1());
selectors = live.get(element);
if (!selectors.has(q)) {
selectors.add(q);
options.handle(element, connected, q);
}
}
}
} else if (live.has(element)) {
selectors = live.get(element);
live["delete"](element);
selectors.forEach(function (q) {
options.handle(element, connected, q);
});
}
};
var parse = function parse(elements) {
var connected = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
for (var i = 0, length = elements.length; i < length; i++) {
notifier(elements[i], connected);
}
};
var query = options.query;
var root = options.root || document$1;
var observer = notify(notifier, root, MutationObserver$1);
var attachShadow = Element$1.prototype.attachShadow;
if (attachShadow) Element$1.prototype.attachShadow = function (init) {
var shadowRoot = attachShadow.call(this, init);
observer.add(shadowRoot);
return shadowRoot;
};
if (query.length) parse(root[QSA](query));
return {
drop: drop,
flush: flush,
observer: observer,
parse: parse
};
};
var create = Object.create,
keys = Object.keys;
var attributes = new WeakMap();
var lazy = new Set();
var query = [];
var config = {};
var defined = {};
var attributeChangedCallback = function attributeChangedCallback(records, o) {
for (var h = attributes.get(o), i = 0, length = records.length; i < length; i++) {
var _records$i2 = records[i],
target = _records$i2.target,
attributeName = _records$i2.attributeName,
oldValue = _records$i2.oldValue;
var newValue = target.getAttribute(attributeName);
h.attributeChanged(attributeName, oldValue, newValue);
}
};
var set = function set(value, m, l, o) {
var handler = create(o, {
element: {
enumerable: true,
value: value
}
});
for (var i = 0, length = l.length; i < length; i++) {
value.addEventListener(l[i].t, handler, l[i].o);
}
m.set(value, handler);
if (handler.init) handler.init();
var observedAttributes = o.observedAttributes;
if (observedAttributes) {
var mo = new MutationObserver(attributeChangedCallback);
mo.observe(value, {
attributes: true,
attributeOldValue: true,
attributeFilter: observedAttributes.map(function (attributeName) {
if (value.hasAttribute(attributeName)) handler.attributeChanged(attributeName, null, value.getAttribute(attributeName));
return attributeName;
})
});
attributes.set(mo, handler);
}
return handler;
};
var _QSAO = QSAO({
query: query,
handle: function handle(element, connected, selector) {
var _config$selector = config[selector],
m = _config$selector.m,
l = _config$selector.l,
o = _config$selector.o;
var handler = m.get(element) || set(element, m, l, o);
var method = connected ? 'connected' : 'disconnected';
if (method in handler) handler[method]();
}
}),
drop = _QSAO.drop,
flush = _QSAO.flush,
parse = _QSAO.parse;
var define = function define(selector, definition) {
if (-1 < query.indexOf(selector)) throw new Error('duplicated: ' + selector);
flush();
var listeners = [];
var retype = create(null);
for (var k = keys(definition), i = 0, length = k.length; i < length; i++) {
var key = k[i];
if (/^on/.test(key) && !/Options$/.test(key)) {
var options = definition[key + 'Options'] || false;
var lower = key.toLowerCase();
var type = lower.slice(2);
listeners.push({
t: type,
o: options
});
retype[type] = key;
if (lower !== key) {
type = key.slice(2, 3).toLowerCase() + key.slice(3);
retype[type] = key;
listeners.push({
t: type,
o: options
});
}
}
}
if (listeners.length) {
definition.handleEvent = function (event) {
this[retype[event.type]](event);
};
}
query.push(selector);
config[selector] = {
m: new WeakMap(),
l: listeners,
o: definition
};
parse(document.querySelectorAll(selector));
whenDefined(selector);
if (!lazy.has(selector)) defined[selector]._();
};
var whenDefined = function whenDefined(selector) {
if (!(selector in defined)) {
var _,
$ = new Lie(function ($) {
_ = $;
});
defined[selector] = {
_: _,
$: $
};
}
return defined[selector].$;
};
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* Abstract base class from which all Rivet component classes are derived.
*****************************************************************************/
var Component = /*#__PURE__*/function () {
function Component() {
_classCallCheck(this, Component);
}
_createClass(Component, null, [{
key: "initAll",
value:
/****************************************************************************
* Initializes all current and future instances of the component that are
* added to the DOM.
*
* @static
***************************************************************************/
function initAll() {
this.init(this.selector);
}
/****************************************************************************
* Initializes a specific component instance with the given selector.
*
* @static
* @param {string} selector - CSS selector of component to initialize
* @returns {HTMLElement} The initialized component
***************************************************************************/
}, {
key: "init",
value: function init(selector) {
define(selector, this.methods);
return document.querySelector(selector);
}
/****************************************************************************
* Gets the component's CSS selector.
*
* @abstract
* @static
* @returns {string} The CSS selector
***************************************************************************/
}, {
key: "selector",
get: function get() {
/* Virtual, must be implemented by subclass. */
}
/****************************************************************************
* Gets the component's methods.
*
* @abstract
* @static
* @returns {Object} The component's methods
***************************************************************************/
}, {
key: "methods",
get: function get() {
/* Virtual, must be implemented by subclass. */
}
/****************************************************************************
* Binds the given method to the component DOM element.
*
* @static
* @param {Component} self - Component instance
* @param {string} name - Method name
* @param {Function} method - Method to bind
***************************************************************************/
}, {
key: "bindMethodToDOMElement",
value: function bindMethodToDOMElement(self, name, method) {
Object.defineProperty(self.element, name, {
value: method.bind(self),
writable: false
});
}
/****************************************************************************
* Dispatches a custom browser event.
*
* @static
* @param {string} eventName - Event name
* @param {HTMLElement} element - Event target
* @param {Object?} detail - Optional event details
* @returns {boolean} Event success or failure
***************************************************************************/
}, {
key: "dispatchCustomEvent",
value: function dispatchCustomEvent(eventName, element) {
var detail = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var prefix = globalSettings.prefix;
var event = new CustomEvent("".concat(prefix).concat(eventName), {
bubbles: true,
cancelable: true,
detail: detail
});
return element.dispatchEvent(event);
}
/****************************************************************************
* Dispatches a "component added" browser event.
*
* @static
* @param {HTMLElement} element - New component DOM element
* @returns {boolean} Event success or failure
***************************************************************************/
}, {
key: "dispatchComponentAddedEvent",
value: function dispatchComponentAddedEvent(element) {
return this.dispatchCustomEvent('ComponentAdded', document, {
component: element
});
}
/****************************************************************************
* Dispatches a "component removed" browser event.
*
* @static
* @param {HTMLElement} element - Removed component DOM element
* @returns {boolean} Event success or failure
***************************************************************************/
}, {
key: "dispatchComponentRemovedEvent",
value: function dispatchComponentRemovedEvent(element) {
return this.dispatchCustomEvent('ComponentRemoved', document, {
component: element
});
}
/****************************************************************************
* Watches the component's DOM and updates references to child elements
* if the DOM changes. Accepts an optional callback to perform additional
* updates to the component on DOM change.
*
* @static
* @param {Object} self - Component instance
* @param {Function} callback - Optional callback
***************************************************************************/
}, {
key: "watchForDOMChanges",
value: function watchForDOMChanges(self) {
var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
self.observer = new MutationObserver(function (mutationList, observer) {
self._initElements();
if (callback) {
callback();
}
});
self.observer.observe(self.element, {
childList: true,
subtree: true
});
}
/****************************************************************************
* Stop watching the component's DOM for changes.
*
* @static
* @param {Object} self - Component instance
***************************************************************************/
}, {
key: "stopWatchingForDOMChanges",
value: function stopWatchingForDOMChanges(self) {
self.observer.disconnect();
}
/****************************************************************************
* Generates a random unique ID for a component's data attributes. Rivet
* components and their child elements are automatically assigned IDs if the
* developer does not manually specify one in the markup.
*
* @static
* @returns {string} Unique ID
***************************************************************************/
}, {
key: "generateUniqueId",
value: function generateUniqueId() {
return globalSettings.prefix + '-' + Math.random().toString(20).substr(2, 12);
}
/****************************************************************************
* Sets the given element attribute if no value was already specified in the
* component's markup.
*
* @static
* @param {HTMLElement} element - Element to set attribute on
* @param {string} attribute - Attribute name
* @param {string} value - Attribute value
***************************************************************************/
}, {
key: "setAttributeIfNotSpecified",
value: function setAttributeIfNotSpecified(element, attribute, value) {
var existingValue = element.getAttribute(attribute);
if (!existingValue) {
element.setAttribute(attribute, value);
}
}
}]);
return Component;
}();
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
var keyCodes = {
up: 38,
down: 40,
left: 37,
right: 39,
tab: 9,
enter: 13,
escape: 27,
home: 36,
end: 35,
pageUp: 33,
pageDown: 34
};
/******************************************************************************
* Copyright (C) 2024 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
var SUPPRESS_EVENT = true;
/******************************************************************************
* Copyright (C) 2018 The Trustees of Indiana University
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/******************************************************************************
* The accordion component can be used to group content into sections that can
* be opened and closed.
*
* @see https://rivet.iu.edu/components/accordion/
*****************************************************************************/
var Accordion = /*#__PURE__*/function (_Component) {
_inherits(Accordion, _Component);
var _super = _createSuper(Accordion);
function Accordion() {
_classCallCheck(this, Accordion);
return _super.apply(this, arguments);
}
_createClass(Accordion, null, [{
key: "selector",
get:
/****************************************************************************
* Gets the accordion's CSS selector.
*
* @static
* @returns {string} The CSS selector
***************************************************************************/
function get() {
return '[data-rvt-accordion]';
}
/****************************************************************************
* Gets an object containing the methods that should be attached to the
* component's root DOM element. Used by wicked-elements to initialize a DOM
* element with Web Component-like behavior.
*
* @static
* @returns {Object} Object with component methods
***************************************************************************/
}, {
key: "methods",
get: function get() {
return {
/************************************************************************
* Initializes the accordion.
***********************************************************************/
init: function init() {
this._initSelectors();
this._initElements();
this._initAttributes();
this._setInitialPanelStates();
Component.bindMethodToDOMElement(this, 'open', this.open);
Component.bindMethodToDOMElement(this, 'close', this.close);
},
/************************************************************************
* Initializes accordion child element selectors.
*
* @private
***********************************************************************/
_initSelectors: function _initSelectors() {
this.triggerAttribute = 'data-rvt-accordion-trigger';
this.panelAttribute = 'data-rvt-accordion-panel';
this.triggerSelector = "[".concat(this.triggerAttribute, "]");
this.panelSelector = "[".concat(this.panelAttribute, "]");
},
/************************************************************************
* Initializes accordion child elements.
*
* @private
***********************************************************************/
_initElements: function _initElements() {
this.triggers = Array.from(this.element.querySelectorAll(this.triggerSelector));
this.panels = Array.from(this.element.querySelectorAll(this.panelSelector));
},
/************************************************************************
* Initializes accordion attributes.
*
* @private
***********************************************************************/
_initAttributes: function _initAttributes() {
this._assignComponentElementIds();
this._setTriggerButtonTypeAttributes();
},
/************************************************************************
* Assigns random IDs to the accordion component's child elements if
* IDs were not already specified in the markup.
*
* @private
***********************************************************************/
_assignComponentElementIds: function _assignComponentElementIds() {
this._assignTriggerIds();
this._assignPanelIds();
},
/************************************************************************
* Assigns a random ID to each trigger.
*
* @private
***********************************************************************/
_assignTriggerIds: function _assignTriggerIds() {
var _this = this;
this.triggers.forEach(function (trigger) {
var id = Component.generateUniqueId();
Component.setAttributeIfNotSpecified(trigger, _this.triggerAttribute, id);
Component.setAttributeIfNotSpecified(trigger, 'id', "".concat(id, "-label"));
});
},
/************************************************************************
* Assigns a random ID to each panel.
*
* @private
***********************************************************************/
_assignPanelIds: function _assignPanelIds() {
var numPanels = this.panels.length;
for (var i = 0; i < numPanels; i++) {
var trigger = this.triggers[i];
var panel = this.panels[i];
var panelId = trigger.getAttribute(this.triggerAttribute);
Component.setAttributeIfNotSpecified(panel, this.panelAttribute, panelId);
Component.setAttributeIfNotSpecified(panel, 'id', panelId);
Component.setAttributeIfNotSpecified(panel, 'aria-labelledby', "".concat(panelId, "-label"));
}
},
/************************************************************************
* Adds `type="button"` to each trigger's button element.
*
* @private
***********************************************************************/
_setTriggerButtonTypeAttributes: function _setTriggerButtonTypeAttributes() {
this.triggers.forEach(function (trigger) {
Component.setAttributeIfNotSpecified(trigger, 'type', 'button');
});
},
/************************************************************************
* Sets the initial state of the accordion's panels.
*
* @private
***********************************************************************/
_setInitialPanelStates: function _setInitialPanelStates() {
this._shouldOpenAllPanels() ? this._openAllPanels() : this._setPanelDefaultStates();
},
/************************************************************************
* Returns true if all panels should be opened when the component is
* added to the DOM.
*
* @private
* @returns {boolean} Panels should be opened
***********************************************************************/
_shouldOpenAllPanels: function _shouldOpenAllPanels() {
return this.element.hasAttribute('data-rvt-accordion-open-all');
},
/************************************************************************
* Opens all panels.
*
* @private
***********************************************************************/
_openAllPanels: function _openAllPanels() {
var _this2 = this;
this.panels.forEach(function (panel) {
_this2.open(panel.getAttribute(_this2.panelAttribute), SUPPRESS_EVENT);
});
},
/************************************************************************
* Sets the default open/closed state for each panel based on the ARIA
* attributes set by the developer.
*
* @private
***********************************************************************/
_setPanelDefaultStates: function _setPanelDefaultStates() {
var _this3 = this;
this.panels.forEach(function (panel) {
_this3._panelShouldBeOpen(panel) ? _this3.open(panel.getAttribute(_this3.panelAttribute), SUPPRESS_EVENT) : _this3.close(panel.getAttribute(_this3.panelAttribute), SUPPRESS_EVENT);
});
},
/************************************************************************
* Returns true if the given panel element should be opened on page load.
*
* @private
* @param {HTMLElement} panel - Panel DOM element
* @returns {boolean} Panel should be opened
***********************************************************************/
_panelShouldBeOpen: function _panelShouldBeOpen(panel) {
return panel.hasAttribute('data-rvt-accordion-panel-init');
},
/************************************************************************
* Called when the accordion is added to the DOM.
***********************************************************************/
connected: function connected() {
Component.dispatchComponentAddedEvent(this.element);
Component.watchForDOMChanges(this);
},
/************************************************************************
* Called when the accordion is removed from the DOM.
***********************************************************************/
disconnected: function disconnected() {
Component.dispatchComponentRemovedEvent(this.element);
Component.stopWatchingForDOMChanges(this);
},
/************************************************************************
* Handles click events broadcast to the accordion.
*
* @param {Event} event - Click event
***********************************************************************/
onClick: function onClick(event) {
if (!this._eventOriginatedInsideTrigger(event)) {
return;
}
this._setTriggerToToggle(event);
this._triggerToToggleIsOpen() ? this.close(this.triggerToToggleId) : this.open(this.triggerToToggleId);
},
/************************************************************************
* Returns true if the given event originated inside one of the
* accordion's panel triggers.
*
* @private
* @param {Event} event - Event
* @returns {boolean} Event originated inside panel trigger
***********************************************************************/
_eventOriginatedInsideTrigger: function _eventOriginatedInsideTrigger(event) {
return event.target.closest(this.triggerSelector);
},
/************************************************************************
* Sets references to the panel trigger to be toggled by the given click
* event. These references are used by other click handler submethods.
*
* @private
* @param {Event} event - Click event
***********************************************************************/
_setTriggerToToggle: function _setTriggerToToggle(event) {
this.triggerToToggle = event.target.closest(this.triggerSelector);
this.triggerToToggleId = this.triggerToToggle.getAttribute(this.triggerAttribute);
},
/************************************************************************
* Returns true if the panel trigger to toggle is already open.
*
* @private
* @returns {boolean} Click originated inside panel trigger
***********************************************************************/
_triggerToToggleIsOpen: function _triggerToToggleIsOpen() {
return this.triggerToToggle.getAttribute('aria-expanded') === 'true';
},
/************************************************************************
* Handles keydown events broadcast to the accordion.
*
* @param {Event} event - Keydown event
***********************************************************************/
onKeydown: function onKeydown(event) {
if (!this._eventOriginatedInsideTrigger(event)) {
return;
}
this._setNeighboringTriggerIndexes(event);
switch (event.keyCode) {
case keyCodes.up:
event.preventDefault();
this._focusPreviousTrigger();
break;
case keyCodes.down:
event.preventDefault();
this._focusNextTrigger();
break;
case keyCodes.home:
this._focusFirstTrigger();
break;
case keyCodes.end:
this._focusLastTrigger();
break;
}
},
/************************************************************************
* Sets the indexes of the panel trigger before and after the one from
* which the given keydown event originated. Used to determine which
* panel trigger should receive focus when the up and down arrow keys
* are pressed.
*
* @private
* @param {Event} event - Keydown event
***********************************************************************/
_setNeighboringTriggerIndexes: function _setNeighboringTriggerIndexes(event) {
var currentTrigger = event.target.closest(this.triggerSelector);
this.previousTriggerIndex = this.triggers.indexOf(currentTrigger) - 1;
this.nextTriggerIndex = this.triggers.indexOf(currentTrigger) + 1;
},
/************************************************************************
* Moves focus to the panel trigger before the one that currently has
* focus. If focus is currently on the first trigger, move focus to the
* last trigger.
*
* @private
***********************************************************************/
_focusPreviousTrigger: function _focusPreviousTrigger() {
this.triggers[this.previousTriggerIndex] ? this.triggers[this.previousTriggerIndex].focus() : this.triggers[this.triggers.length - 1].focus();
},
/************************************************************************
* Moves focus to the panel trigger after the one that currently has
* focus. If focus is currently on the last trigger, move focus to the
* first trigger.
*
* @private
***********************************************************************/
_focusNextTrigger: function _focusNextTrigger() {
this.triggers[this.nextTriggerIndex] ? this.triggers[this.nextTriggerIndex].focus() : this.triggers[0].focus();
},
/************************************************************************
* Moves focus to the first panel trigger.
*
* @private
***********************************************************************/
_focusFirstTrigger: function _focusFirstTrigger() {
this.triggers[0].focus();
},
/************************************************************************
* Moves focus to the last panel trigger.
*
* @private
***********************************************************************/
_focusLastTrigger: function _focusLastTrigger() {
this.triggers[this.triggers.length - 1].focus();
},
/************************************************************************
* Opens the panel with the given data-rvt-accordion-panel ID value.
*
* @param {string} childMenuId - Panel ID
* @param {boolean} suppressEvent - Suppress open event
***********************************************************************/
open: function open(panelId) {
var suppressEvent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
this._setPanelToOpen(panelId);
if (!this._panelToOpenExists()) {
console.warn("No such accordion panel '".concat(panelId, "' in open()"));
return;
}
if (!suppressEvent) if (!this._eventDispatched('AccordionOpened', this.panelToOpen)) {
return;
}
this._openPanel();
},
/************************************************************************
* Sets references to the panel to be opened. These references are used
* by other submethods.
*
* @private
* @param {string} panelId - Panel ID
***********************************************************************/
_setPanelToOpen: function _setPanelToOpen(panelId) {
this.triggerToOpen = this.element.querySelector("[".concat(this.triggerAttribute, " = \"").concat(panelId, "\"]"));
this.panelToOpen = this.element.querySelector("[".concat(this.panelAttribute, " = \"").concat(panelId, "\"]"));
},
/************************************************************************
* Returns true if the panel to open actually exists in the DOM.
*
* @private
* @retu