UNPKG

13.3 kBJavaScriptView Raw
1function _typeof(obj) {
2 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
3 _typeof = function _typeof(obj) {
4 return typeof obj;
5 };
6 } else {
7 _typeof = function _typeof(obj) {
8 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
9 };
10 }
11
12 return _typeof(obj);
13}
14
15function _classCallCheck(instance, Constructor) {
16 if (!(instance instanceof Constructor)) {
17 throw new TypeError("Cannot call a class as a function");
18 }
19}
20
21function _defineProperties(target, props) {
22 for (var i = 0; i < props.length; i++) {
23 var descriptor = props[i];
24 descriptor.enumerable = descriptor.enumerable || false;
25 descriptor.configurable = true;
26 if ("value" in descriptor) descriptor.writable = true;
27 Object.defineProperty(target, descriptor.key, descriptor);
28 }
29}
30
31function _createClass(Constructor, protoProps, staticProps) {
32 if (protoProps) _defineProperties(Constructor.prototype, protoProps);
33 if (staticProps) _defineProperties(Constructor, staticProps);
34 return Constructor;
35}
36
37function _possibleConstructorReturn(self, call) {
38 if (call && (_typeof(call) === "object" || typeof call === "function")) {
39 return call;
40 }
41
42 return _assertThisInitialized(self);
43}
44
45function _assertThisInitialized(self) {
46 if (self === void 0) {
47 throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
48 }
49
50 return self;
51}
52
53function _getPrototypeOf(o) {
54 _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
55 return o.__proto__ || Object.getPrototypeOf(o);
56 };
57 return _getPrototypeOf(o);
58}
59
60function _inherits(subClass, superClass) {
61 if (typeof superClass !== "function" && superClass !== null) {
62 throw new TypeError("Super expression must either be null or a function");
63 }
64
65 subClass.prototype = Object.create(superClass && superClass.prototype, {
66 constructor: {
67 value: subClass,
68 writable: true,
69 configurable: true
70 }
71 });
72 if (superClass) _setPrototypeOf(subClass, superClass);
73}
74
75function _setPrototypeOf(o, p) {
76 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
77 o.__proto__ = p;
78 return o;
79 };
80
81 return _setPrototypeOf(o, p);
82}
83/**
84 * Copyright IBM Corp. 2016, 2018
85 *
86 * This source code is licensed under the Apache-2.0 license found in the
87 * LICENSE file in the root directory of this source tree.
88 */
89
90
91import settings from '../../globals/js/settings';
92import mixin from '../../globals/js/misc/mixin';
93import createComponent from '../../globals/js/mixins/create-component';
94import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
95import trackBlur from '../../globals/js/mixins/track-blur';
96import eventMatches from '../../globals/js/misc/event-matches';
97import on from '../../globals/js/misc/on';
98
99var toArray = function toArray(arrayLike) {
100 return Array.prototype.slice.call(arrayLike);
101};
102
103var Dropdown =
104/*#__PURE__*/
105function (_mixin) {
106 _inherits(Dropdown, _mixin);
107 /**
108 * A selector with drop downs.
109 * @extends CreateComponent
110 * @extends InitComponentBySearch
111 * @extends TrackBlur
112 * @param {HTMLElement} element The element working as a selector.
113 * @param {Object} [options] The component options.
114 * @param {string} [options.selectorItem] The CSS selector to find clickable areas in dropdown items.
115 * @param {string} [options.selectorItemSelected] The CSS selector to find the clickable area in the selected dropdown item.
116 * @param {string} [options.classSelected] The CSS class for the selected dropdown item.
117 * @param {string} [options.classOpen] The CSS class for the open state.
118 * @param {string} [options.classDisabled] The CSS class for the disabled state.
119 * @param {string} [options.eventBeforeSelected]
120 * The name of the custom event fired before a drop down item is selected.
121 * Cancellation of this event stops selection of drop down item.
122 * @param {string} [options.eventAfterSelected] The name of the custom event fired after a drop down item is selected.
123 */
124
125
126 function Dropdown(element, options) {
127 var _this;
128
129 _classCallCheck(this, Dropdown);
130
131 _this = _possibleConstructorReturn(this, _getPrototypeOf(Dropdown).call(this, element, options));
132
133 _this.manage(on(_this.element.ownerDocument, 'click', function (event) {
134 _this._toggle(event);
135 }));
136
137 _this.manage(on(_this.element, 'keydown', function (event) {
138 _this._handleKeyDown(event);
139 }));
140
141 _this.manage(on(_this.element, 'click', function (event) {
142 var item = eventMatches(event, _this.options.selectorItem);
143
144 if (item) {
145 _this.select(item);
146 }
147 }));
148
149 return _this;
150 }
151 /**
152 * Handles keydown event.
153 * @param {Event} event The event triggering this method.
154 */
155
156
157 _createClass(Dropdown, [{
158 key: "_handleKeyDown",
159 value: function _handleKeyDown(event) {
160 var isOpen = this.element.classList.contains(this.options.classOpen);
161 var direction = {
162 38: this.constructor.NAVIGATE.BACKWARD,
163 40: this.constructor.NAVIGATE.FORWARD
164 }[event.which];
165
166 if (isOpen && direction !== undefined) {
167 this.navigate(direction);
168 event.preventDefault(); // Prevents up/down keys from scrolling container
169 } else {
170 this._toggle(event);
171 }
172 }
173 /**
174 * Opens and closes the dropdown menu.
175 * @param {Event} [event] The event triggering this method.
176 */
177
178 }, {
179 key: "_toggle",
180 value: function _toggle(event) {
181 var _this2 = this;
182
183 var isDisabled = this.element.classList.contains(this.options.classDisabled);
184
185 if (isDisabled) {
186 return;
187 }
188
189 if ([13, 32, 40].indexOf(event.which) >= 0 && !event.target.matches(this.options.selectorItem) || event.which === 27 || event.type === 'click') {
190 var isOpen = this.element.classList.contains(this.options.classOpen);
191 var isOfSelf = this.element.contains(event.target);
192 var actions = {
193 add: isOfSelf && event.which === 40 && !isOpen,
194 remove: (!isOfSelf || event.which === 27) && isOpen,
195 toggle: isOfSelf && event.which !== 27 && event.which !== 40
196 };
197 Object.keys(actions).forEach(function (action) {
198 if (actions[action]) {
199 _this2.element.classList[action](_this2.options.classOpen);
200
201 _this2.element.focus();
202 }
203 });
204 var listItems = toArray(this.element.querySelectorAll(this.options.selectorItem));
205 listItems.forEach(function (item) {
206 if (_this2.element.classList.contains(_this2.options.classOpen)) {
207 item.tabIndex = 0;
208 } else {
209 item.tabIndex = -1;
210 }
211 });
212 }
213 }
214 /**
215 * @returns {Element} Currently highlighted element.
216 */
217
218 }, {
219 key: "getCurrentNavigation",
220 value: function getCurrentNavigation() {
221 var focused = this.element.ownerDocument.activeElement;
222 return focused.nodeType === Node.ELEMENT_NODE && focused.matches(this.options.selectorItem) ? focused : null;
223 }
224 /**
225 * Moves up/down the focus.
226 * @param {number} direction The direction of navigating.
227 */
228
229 }, {
230 key: "navigate",
231 value: function navigate(direction) {
232 var items = toArray(this.element.querySelectorAll(this.options.selectorItem));
233 var start = this.getCurrentNavigation() || this.element.querySelector(this.options.selectorItemSelected);
234
235 var getNextItem = function getNextItem(old) {
236 var handleUnderflow = function handleUnderflow(i, l) {
237 return i + (i >= 0 ? 0 : l);
238 };
239
240 var handleOverflow = function handleOverflow(i, l) {
241 return i - (i < l ? 0 : l);
242 }; // `items.indexOf(old)` may be -1 (Scenario of no previous focus)
243
244
245 var index = Math.max(items.indexOf(old) + direction, -1);
246 return items[handleUnderflow(handleOverflow(index, items.length), items.length)];
247 };
248
249 for (var current = getNextItem(start); current && current !== start; current = getNextItem(current)) {
250 if (!current.matches(this.options.selectorItemHidden) && !current.parentNode.matches(this.options.selectorItemHidden) && !current.matches(this.options.selectorItemSelected)) {
251 current.focus();
252 break;
253 }
254 }
255 }
256 /**
257 * Handles clicking on the dropdown options, doing the following:
258 * * Change Dropdown text to selected option.
259 * * Remove selected option from options when selected.
260 * * Emit custom events.
261 * @param {HTMLElement} itemToSelect The element to be activated.
262 */
263
264 }, {
265 key: "select",
266 value: function select(itemToSelect) {
267 var _this3 = this;
268
269 var eventStart = new CustomEvent(this.options.eventBeforeSelected, {
270 bubbles: true,
271 cancelable: true,
272 detail: {
273 item: itemToSelect
274 }
275 });
276
277 if (this.element.dispatchEvent(eventStart)) {
278 if (this.element.dataset.dropdownType !== 'navigation') {
279 var selectorText = this.element.dataset.dropdownType !== 'inline' ? this.options.selectorText : this.options.selectorTextInner;
280 var text = this.element.querySelector(selectorText);
281
282 if (text) {
283 text.innerHTML = itemToSelect.innerHTML;
284 }
285
286 itemToSelect.classList.add(this.options.classSelected);
287 }
288
289 this.element.dataset.value = itemToSelect.parentElement.dataset.value;
290 toArray(this.element.querySelectorAll(this.options.selectorItemSelected)).forEach(function (item) {
291 if (itemToSelect !== item) {
292 item.classList.remove(_this3.options.classSelected);
293 }
294 });
295 this.element.dispatchEvent(new CustomEvent(this.options.eventAfterSelected, {
296 bubbles: true,
297 cancelable: true,
298 detail: {
299 item: itemToSelect
300 }
301 }));
302 }
303 }
304 /**
305 * Closes the dropdown menu if this component loses focus.
306 */
307
308 }, {
309 key: "handleBlur",
310 value: function handleBlur() {
311 this.element.classList.remove(this.options.classOpen);
312 }
313 /**
314 * The map associating DOM element and selector instance.
315 * @member Dropdown.components
316 * @type {WeakMap}
317 */
318
319 }], [{
320 key: "options",
321
322 /**
323 * The component options.
324 * If `options` is specified in the constructor, {@linkcode Dropdown.create .create()}, or {@linkcode Dropdown.init .init()},
325 * properties in this object are overriden for the instance being create and how {@linkcode Dropdown.init .init()} works.
326 * @member Dropdown.options
327 * @type {Object}
328 * @property {string} selectorInit The CSS selector to find selectors.
329 * @property {string} [selectorText] The CSS selector to find the element showing the selected item.
330 * @property {string} [selectorTextInner] The CSS selector to find the element showing the selected item, used for inline mode.
331 * @property {string} [selectorItem] The CSS selector to find clickable areas in dropdown items.
332 * @property {string} [selectorItemHidden]
333 * The CSS selector to find hidden dropdown items.
334 * Used to skip dropdown items for keyboard navigation.
335 * @property {string} [selectorItemSelected] The CSS selector to find the clickable area in the selected dropdown item.
336 * @property {string} [classSelected] The CSS class for the selected dropdown item.
337 * @property {string} [classOpen] The CSS class for the open state.
338 * @property {string} [classDisabled] The CSS class for the disabled state.
339 * @property {string} [eventBeforeSelected]
340 * The name of the custom event fired before a drop down item is selected.
341 * Cancellation of this event stops selection of drop down item.
342 * @property {string} [eventAfterSelected] The name of the custom event fired after a drop down item is selected.
343 */
344 get: function get() {
345 var prefix = settings.prefix;
346 return {
347 selectorInit: '[data-dropdown]',
348 selectorText: ".".concat(prefix, "--dropdown-text"),
349 selectorTextInner: ".".concat(prefix, "--dropdown-text__inner"),
350 selectorItem: ".".concat(prefix, "--dropdown-link"),
351 selectorItemSelected: ".".concat(prefix, "--dropdown--selected"),
352 selectorItemHidden: "[hidden],[aria-hidden=\"true\"]",
353 classSelected: "".concat(prefix, "--dropdown--selected"),
354 classOpen: "".concat(prefix, "--dropdown--open"),
355 classDisabled: "".concat(prefix, "--dropdown--disabled"),
356 eventBeforeSelected: 'dropdown-beingselected',
357 eventAfterSelected: 'dropdown-selected'
358 };
359 }
360 /**
361 * Enum for navigating backward/forward.
362 * @readonly
363 * @member Dropdown.NAVIGATE
364 * @type {Object}
365 * @property {number} BACKWARD Navigating backward.
366 * @property {number} FORWARD Navigating forward.
367 */
368
369 }]);
370
371 Dropdown.components = new WeakMap();
372 Dropdown.NAVIGATE = {
373 BACKWARD: -1,
374 FORWARD: 1
375 };
376 return Dropdown;
377}(mixin(createComponent, initComponentBySearch, trackBlur));
378
379export default Dropdown;
\No newline at end of file