j1-template 2022.0.17 → 2022.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/assets/error_pages/HTTP204.html +1 -1
  3. data/assets/error_pages/HTTP400.html +1 -1
  4. data/assets/error_pages/HTTP401.html +1 -1
  5. data/assets/error_pages/HTTP403.html +1 -1
  6. data/assets/error_pages/HTTP404.html +1 -1
  7. data/assets/error_pages/HTTP444.html +1 -1
  8. data/assets/error_pages/HTTP445.html +1 -1
  9. data/assets/error_pages/HTTP446.html +1 -1
  10. data/assets/error_pages/HTTP447.html +1 -1
  11. data/assets/error_pages/HTTP448.html +1 -1
  12. data/assets/error_pages/HTTP500.html +1 -1
  13. data/assets/error_pages/HTTP501.html +1 -1
  14. data/assets/error_pages/HTTP502.html +1 -1
  15. data/assets/error_pages/HTTP503.html +1 -1
  16. data/assets/themes/j1/adapter/js/customFunctions.js +221 -0
  17. data/assets/themes/j1/adapter/js/customModule.js +221 -0
  18. data/assets/themes/j1/adapter/js/dropdowns.js +319 -0
  19. data/assets/themes/j1/adapter/js/rtable.js +2 -6
  20. data/assets/themes/j1/modules/dropdowns/css/theme/uno/dropdowns.css +109 -0
  21. data/assets/themes/j1/modules/dropdowns/css/theme/uno/dropdowns.min.css +15 -0
  22. data/assets/themes/j1/modules/dropdowns/js/cash.js +978 -0
  23. data/assets/themes/j1/modules/dropdowns/js/dropdowns.js +864 -0
  24. data/lib/j1/commands/generate.rb +25 -14
  25. data/lib/j1/commands/rebuild.rb +1 -0
  26. data/lib/j1/commands/reset.rb +6 -3
  27. data/lib/j1/commands/setup.rb +18 -13
  28. data/lib/j1/commands/site.rb +2 -1
  29. data/lib/j1/utils/exec2.rb +33 -7
  30. data/lib/j1/version.rb +3 -3
  31. data/lib/starter_web/Gemfile +1 -1
  32. data/lib/starter_web/_config.yml +1 -1
  33. data/lib/starter_web/_data/apps/carousel.yml +138 -140
  34. data/lib/starter_web/_data/blocks/banner.yml +7 -7
  35. data/lib/starter_web/_data/modules/defaults/dropdowns.yml +164 -0
  36. data/lib/starter_web/_data/modules/dropdowns.yml +77 -0
  37. data/lib/starter_web/_data/resources.yml +73 -2
  38. data/lib/starter_web/_includes/attributes.asciidoc +1 -1
  39. data/lib/starter_web/_plugins/lunr_index.rb +2 -2
  40. data/lib/starter_web/package.json +2 -2
  41. data/lib/starter_web/pages/public/about/about_site.adoc +1 -1
  42. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/000_intro.adoc +1 -1
  43. data/lib/starter_web/pages/public/asciidoc_skeletons/documentation/documentation.adoc +1 -1
  44. data/lib/starter_web/pages/public/asciidoc_skeletons/multi-document/multi.adoc +1 -1
  45. data/lib/starter_web/pages/public/asciidoc_skeletons/simple-document/simple.adoc +1 -1
  46. data/lib/starter_web/pages/public/learn/roundtrip/100_present_images.adoc +1 -1
  47. data/lib/starter_web/pages/public/learn/roundtrip/100_present_images.ads.asciidoc +1 -1
  48. data/lib/starter_web/pages/public/learn/roundtrip/100_present_images.base.asciidoc +1 -1
  49. data/lib/starter_web/pages/public/learn/roundtrip/100_present_images.comments.asciidoc +1 -1
  50. data/lib/starter_web/pages/public/learn/roundtrip/200_typography.adoc +1 -1
  51. data/lib/starter_web/pages/public/learn/roundtrip/300_icon_fonts.adoc +1 -1
  52. data/lib/starter_web/pages/public/learn/roundtrip/400_asciidoc_extensions.adoc +1 -1
  53. data/lib/starter_web/pages/public/learn/roundtrip/410_bs_modals_extentions.adoc +1 -1
  54. data/lib/starter_web/pages/public/learn/roundtrip/420_responsive_tables_extensions.adoc +1 -1
  55. data/lib/starter_web/pages/public/learn/roundtrip/500_themes.adoc +1 -1
  56. data/lib/starter_web/pages/public/learn/roundtrip/600_quicksearch.adoc +1 -1
  57. data/lib/starter_web/pages/public/learn/where_to_go.adoc +1 -1
  58. data/lib/starter_web/pages/public/legal/en/100_copyright.adoc +1 -1
  59. data/lib/starter_web/pages/public/legal/en/200_impress.adoc +1 -1
  60. data/lib/starter_web/pages/public/legal/en/400_comment_policy.adoc +1 -1
  61. data/lib/starter_web/pages/public/manuals/j1-dropdown.adoc +294 -0
  62. data/lib/starter_web/pages/public/manuals/{dropdown-help.adoc → msdropdown.adoc} +0 -0
  63. data/lib/starter_web/pages/public/panels/intro_panel/panel.adoc +1 -1
  64. data/lib/starter_web/pages/public/previewer/preview_bootstrap_theme.adoc +1 -1
  65. data/lib/starter_web/utilsrv/_defaults/package.json +1 -1
  66. data/lib/starter_web/utilsrv/package.json +1 -1
  67. metadata +14 -6
  68. data/assets/themes/j1/modules/fab/css/theme/uno/fab.css +0 -373
  69. data/assets/themes/j1/modules/fab/css/theme/uno/fab.min.css +0 -15
@@ -0,0 +1,864 @@
1
+ /*
2
+ # -----------------------------------------------------------------------------
3
+ # ~/assets/themes/j1/modules/dropdowns/js/dropdowns.js
4
+ # MODIFIED DROPDOWN JS from project Materialize
5
+ # Provides JS Core funtions for the J1 DROPDOWNS Module
6
+ #
7
+ # Product/Info:
8
+ # https://jekyll.one
9
+ # https://github.com/Dogfalo/materialize
10
+ #
11
+ # Copyright (C) 2021 Juergen Adams
12
+ #
13
+ # J1 Template is licensed under the MIT License.
14
+ # See: https://github.com/jekyll-one-org/J1 Template/blob/master/LICENSE
15
+ # -----------------------------------------------------------------------------
16
+ # TODO:
17
+ # jadams, 2020-10-11: module needs to be rewitten to PURE jQuery
18
+ # -----------------------------------------------------------------------------
19
+ */
20
+ 'use strict';
21
+
22
+ // -----------------------------------------------------------------------------
23
+ // ESLint shimming
24
+ // -----------------------------------------------------------------------------
25
+ /* eslint indent: "off" */
26
+ /* eslint no-unused-vars: "off" */
27
+ /* eslint no-undef: "off" */
28
+ /* eslint no-redeclare: "off" */
29
+ /* eslint indent: "off" */
30
+ // -----------------------------------------------------------------------------
31
+
32
+ (function($, anim) {
33
+
34
+ var _defaults = {
35
+ direction: 'top',
36
+ hoverEnabled: true,
37
+ toolbarEnabled: false
38
+ };
39
+
40
+ $.fn.reverse = [].reverse;
41
+
42
+ // jadams, 2020-10-10, added class from main (materialize.js)
43
+ //
44
+ class Component {
45
+
46
+ constructor(classDef, el, options) {
47
+ // Display error if el is valid HTML Element
48
+ if (!(el instanceof Element)) {
49
+ console.error(Error(el + ' is not an HTML Element'));
50
+ }
51
+
52
+ // If exists, destroy and reinitialize in child
53
+ var ins = classDef.getInstance(el);
54
+ if (!!ins) {
55
+ ins.destroy();
56
+ }
57
+
58
+ this.el = el;
59
+ this.$el = cash(el);
60
+ }
61
+
62
+ /**
63
+ * Initializes components
64
+ * @param {class} classDef
65
+ * @param {Element | NodeList | jQuery} els
66
+ * @param {Object} options
67
+ */
68
+ static init(classDef, els, options) {
69
+ var instances = null;
70
+ if (els instanceof Element) {
71
+ instances = new classDef(els, options);
72
+ } else if (!!els && (els.jquery || els.cash || els instanceof NodeList)) {
73
+ var instancesArr = [];
74
+ for (var i = 0; i < els.length; i++) {
75
+ instancesArr.push(new classDef(els[i], options));
76
+ }
77
+ instances = instancesArr;
78
+ }
79
+ return instances;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Initialize jQuery wrapper for plugin
85
+ * @param {Class} plugin javascript class
86
+ * @param {string} pluginName jQuery plugin name
87
+ * @param {string} classRef Class reference name
88
+ */
89
+ j1.initializeJqueryWrapper = function (plugin, pluginName, classRef) {
90
+ jQuery.fn[pluginName] = function (methodOrOptions) {
91
+ // Call plugin method if valid method name is passed in
92
+ if (plugin.prototype[methodOrOptions]) {
93
+ var params = Array.prototype.slice.call(arguments, 1);
94
+
95
+ // Getter methods
96
+ if (methodOrOptions.slice(0, 3) === 'get') {
97
+ var instance = this.first()[0][classRef];
98
+ return instance[methodOrOptions].apply(instance, params);
99
+ }
100
+
101
+ // Void methods
102
+ return this.each(function () {
103
+ var instance = this[classRef];
104
+ instance[methodOrOptions].apply(instance, params);
105
+ });
106
+
107
+ // Initialize plugin if options or no argument is passed in
108
+ } else if (typeof methodOrOptions === 'object' || !methodOrOptions) {
109
+ plugin.init(this, arguments[0]);
110
+ return this;
111
+ }
112
+
113
+ // Return error if an unrecognized method name is passed in
114
+ jQuery.error("Method " + methodOrOptions + " does not exist on jQuery." + pluginName);
115
+ };
116
+ };
117
+
118
+ /**
119
+ * @class
120
+ */
121
+ class Dropdowns extends Component {
122
+ constructor(el, options) {
123
+ super(Dropdowns, el, options);
124
+
125
+ this.el.M_Dropdown = this;
126
+ Dropdowns._dropdowns.push(this);
127
+
128
+ this.id = this.getIdFromTrigger(el);
129
+ this.dropdownEl = document.getElementById(this.id);
130
+ this.$dropdownEl = $(this.dropdownEl);
131
+
132
+ /**
133
+ * Options for the dropdown
134
+ * @member Dropdowns#options
135
+ * @prop {String} [alignment='left'] - Edge which the dropdown is aligned to
136
+ * @prop {Boolean} [autoFocus=true] - Automatically focus dropdown el for keyboard
137
+ * @prop {Boolean} [constrainWidth=true] - Constrain width to width of the button
138
+ * @prop {Element} container - Container element to attach dropdown to (optional)
139
+ * @prop {Boolean} [coverTrigger=true] - Place dropdown over trigger
140
+ * @prop {Boolean} [closeOnClick=true] - Close on click of dropdown item
141
+ * @prop {Boolean} [hover=false] - Open dropdown on hover
142
+ * @prop {Number} [inDuration=150] - Duration of open animation in ms
143
+ * @prop {Number} [outDuration=250] - Duration of close animation in ms
144
+ * @prop {Function} onOpenStart - Function called when dropdown starts opening
145
+ * @prop {Function} onOpenEnd - Function called when dropdown finishes opening
146
+ * @prop {Function} onCloseStart - Function called when dropdown starts closing
147
+ * @prop {Function} onCloseEnd - Function called when dropdown finishes closing
148
+ */
149
+ this.options = $.extend({}, Dropdowns.defaults, options);
150
+
151
+ /**
152
+ * Describes open/close state of dropdown
153
+ * @type {Boolean}
154
+ */
155
+ this.isOpen = false;
156
+
157
+ /**
158
+ * Describes if dropdown content is scrollable
159
+ * @type {Boolean}
160
+ */
161
+ this.isScrollable = false;
162
+
163
+ /**
164
+ * Describes if touch moving on dropdown content
165
+ * @type {Boolean}
166
+ */
167
+ this.isTouchMoving = false;
168
+
169
+ this.focusedIndex = -1;
170
+ this.filterQuery = [];
171
+
172
+ this.keys = {
173
+ TAB: 9,
174
+ ENTER: 13,
175
+ ESC: 27,
176
+ ARROW_UP: 38,
177
+ ARROW_DOWN: 40
178
+ };
179
+
180
+ // Move dropdown-content after dropdown-trigger
181
+ if (!!this.options.container) {
182
+ $(this.options.container).append(this.dropdownEl);
183
+ } else {
184
+ this.$el.after(this.dropdownEl);
185
+ }
186
+
187
+ this._makeDropdownFocusable();
188
+ this._resetFilterQueryBound = this._resetFilterQuery.bind(this);
189
+ this._handleDocumentClickBound = this._handleDocumentClick.bind(this);
190
+ this._handleDocumentTouchmoveBound = this._handleDocumentTouchmove.bind(this);
191
+ this._handleDropdownClickBound = this._handleDropdownClick.bind(this);
192
+ this._handleDropdownKeydownBound = this._handleDropdownKeydown.bind(this);
193
+ this._handleTriggerKeydownBound = this._handleTriggerKeydown.bind(this);
194
+ this._setupEventHandlers();
195
+ }
196
+
197
+ static get defaults() {
198
+ return _defaults;
199
+ }
200
+
201
+ static init(els, options) {
202
+ return super.init(this, els, options);
203
+ }
204
+
205
+ /**
206
+ * Get Instance
207
+ */
208
+ static getInstance(el) {
209
+ let domElem = !!el.jquery ? el[0] : el;
210
+ return domElem.M_Dropdown;
211
+ }
212
+
213
+ /**
214
+ * Gets id of component from a trigger
215
+ * @param {Element} trigger trigger
216
+ * @returns {string}
217
+ */
218
+ getIdFromTrigger (trigger) {
219
+ var id = trigger.getAttribute('data-target');
220
+ if (!id) {
221
+ id = trigger.getAttribute('href');
222
+ if (id) {
223
+ id = id.slice(1);
224
+ } else {
225
+ id = '';
226
+ }
227
+ }
228
+ return id;
229
+ }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // executeFunctionByName()
233
+ // execute a function by NAME (functionName) in a browser context
234
+ // (e.g. window) the function is published
235
+ // ---------------------------------------------------------------------------
236
+ executeFunctionByName (functionName, context /*, args */) {
237
+ var args = Array.prototype.slice.call(arguments, 2);
238
+ var namespaces = functionName.split('.');
239
+ var func = namespaces.pop();
240
+ for(var i = 0; i < namespaces.length; i++) {
241
+ context = context[namespaces[i]];
242
+ }
243
+ return context[func].apply(context, args);
244
+ }
245
+
246
+ checkPossibleAlignments (el, container, bounding, offset) {
247
+ var canAlign = {
248
+ top: true,
249
+ right: true,
250
+ bottom: true,
251
+ left: true,
252
+ spaceOnTop: null,
253
+ spaceOnRight: null,
254
+ spaceOnBottom: null,
255
+ spaceOnLeft: null
256
+ };
257
+
258
+ var containerAllowsOverflow = getComputedStyle(container).overflow === 'visible';
259
+ var containerRect = container.getBoundingClientRect();
260
+ var containerHeight = Math.min(containerRect.height, window.innerHeight);
261
+ var containerWidth = Math.min(containerRect.width, window.innerWidth);
262
+ var elOffsetRect = el.getBoundingClientRect();
263
+
264
+ var scrollLeft = container.scrollLeft;
265
+ var scrollTop = container.scrollTop;
266
+
267
+ var scrolledX = bounding.left - scrollLeft;
268
+ var scrolledYTopEdge = bounding.top - scrollTop;
269
+ var scrolledYBottomEdge = bounding.top + elOffsetRect.height - scrollTop;
270
+
271
+ // Check for container and viewport for left
272
+ canAlign.spaceOnRight = !containerAllowsOverflow ? containerWidth - (scrolledX + bounding.width) : window.innerWidth - (elOffsetRect.left + bounding.width);
273
+ if (canAlign.spaceOnRight < 0) {
274
+ canAlign.left = false;
275
+ }
276
+
277
+ // Check for container and viewport for Right
278
+ canAlign.spaceOnLeft = !containerAllowsOverflow ? scrolledX - bounding.width + elOffsetRect.width : elOffsetRect.right - bounding.width;
279
+ if (canAlign.spaceOnLeft < 0) {
280
+ canAlign.right = false;
281
+ }
282
+
283
+ // Check for container and viewport for Top
284
+ canAlign.spaceOnBottom = !containerAllowsOverflow ? containerHeight - (scrolledYTopEdge + bounding.height + offset) : window.innerHeight - (elOffsetRect.top + bounding.height + offset);
285
+ if (canAlign.spaceOnBottom < 0) {
286
+ canAlign.top = false;
287
+ }
288
+
289
+ // Check for container and viewport for Bottom
290
+ canAlign.spaceOnTop = !containerAllowsOverflow ? scrolledYBottomEdge - (bounding.height - offset) : elOffsetRect.bottom - (bounding.height + offset);
291
+ if (canAlign.spaceOnTop < 0) {
292
+ canAlign.bottom = false;
293
+ }
294
+
295
+ return canAlign;
296
+ }
297
+
298
+ /**
299
+ * Teardown component
300
+ */
301
+ destroy() {
302
+ this._resetDropdownStyles();
303
+ this._removeEventHandlers();
304
+ Dropdowns._dropdowns.splice(Dropdowns._dropdowns.indexOf(this), 1);
305
+ this.el.M_Dropdown = undefined;
306
+ }
307
+
308
+ /**
309
+ * Setup Event Handlers
310
+ */
311
+ _setupEventHandlers() {
312
+ // Trigger keydown handler
313
+ this.el.addEventListener('keydown', this._handleTriggerKeydownBound);
314
+
315
+ // Item click handler
316
+ this.dropdownEl.addEventListener('click', this._handleDropdownClickBound);
317
+
318
+ // Hover event handlers
319
+ if (this.options.hover) {
320
+ this._handleMouseEnterBound = this._handleMouseEnter.bind(this);
321
+ this.el.addEventListener('mouseenter', this._handleMouseEnterBound);
322
+ this._handleMouseLeaveBound = this._handleMouseLeave.bind(this);
323
+ this.el.addEventListener('mouseleave', this._handleMouseLeaveBound);
324
+ this.dropdownEl.addEventListener('mouseleave', this._handleMouseLeaveBound);
325
+
326
+ // Click event handlers
327
+ } else {
328
+ this._handleClickBound = this._handleClick.bind(this);
329
+ this.el.addEventListener('click', this._handleClickBound);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Remove Event Handlers
335
+ */
336
+ _removeEventHandlers() {
337
+ this.el.removeEventListener('keydown', this._handleTriggerKeydownBound);
338
+ this.dropdownEl.removeEventListener('click', this._handleDropdownClickBound);
339
+
340
+ if (this.options.hover) {
341
+ this.el.removeEventListener('mouseenter', this._handleMouseEnterBound);
342
+ this.el.removeEventListener('mouseleave', this._handleMouseLeaveBound);
343
+ this.dropdownEl.removeEventListener('mouseleave', this._handleMouseLeaveBound);
344
+ } else {
345
+ this.el.removeEventListener('click', this._handleClickBound);
346
+ }
347
+ }
348
+
349
+ _setupTemporaryEventHandlers() {
350
+ // Use capture phase event handler to prevent click
351
+ document.body.addEventListener('click', this._handleDocumentClickBound, true);
352
+ document.body.addEventListener('touchend', this._handleDocumentClickBound);
353
+ document.body.addEventListener('touchmove', this._handleDocumentTouchmoveBound);
354
+ this.dropdownEl.addEventListener('keydown', this._handleDropdownKeydownBound);
355
+ }
356
+
357
+ _removeTemporaryEventHandlers() {
358
+ // Use capture phase event handler to prevent click
359
+ document.body.removeEventListener('click', this._handleDocumentClickBound, true);
360
+ document.body.removeEventListener('touchend', this._handleDocumentClickBound);
361
+ document.body.removeEventListener('touchmove', this._handleDocumentTouchmoveBound);
362
+ this.dropdownEl.removeEventListener('keydown', this._handleDropdownKeydownBound);
363
+ }
364
+
365
+ _handleClick(e) {
366
+ e.preventDefault();
367
+ this.open();
368
+ }
369
+
370
+ _handleMouseEnter() {
371
+ this.open();
372
+ }
373
+
374
+ _handleMouseLeave(e) {
375
+ let toEl = e.toElement || e.relatedTarget;
376
+ let leaveToDropdownContent = !!$(toEl).closest('.dropdown-content').length;
377
+ let leaveToActiveDropdownTrigger = false;
378
+
379
+ let $closestTrigger = $(toEl).closest('.dropdowns');
380
+ if (
381
+ $closestTrigger.length &&
382
+ !!$closestTrigger[0].M_Dropdown &&
383
+ $closestTrigger[0].M_Dropdown.isOpen
384
+ ) {
385
+ leaveToActiveDropdownTrigger = true;
386
+ }
387
+
388
+ // Close hover dropdown if mouse did not leave to either active dropdown-trigger or dropdown-content
389
+ if (!leaveToActiveDropdownTrigger && !leaveToDropdownContent) {
390
+ this.close();
391
+ }
392
+ }
393
+
394
+ _handleDocumentClick(e) {
395
+ let $target = $(e.target);
396
+ if (
397
+ this.options.closeOnClick &&
398
+ $target.closest('.dropdown-content').length &&
399
+ !this.isTouchMoving
400
+ ) {
401
+ // isTouchMoving to check if scrolling on mobile.
402
+ setTimeout(() => {
403
+ this.close();
404
+ }, 0);
405
+ } else if (
406
+ $target.closest('.dropdowns').length ||
407
+ !$target.closest('.dropdown-content').length
408
+ ) {
409
+ setTimeout(() => {
410
+ this.close();
411
+ }, 0);
412
+ }
413
+ this.isTouchMoving = false;
414
+ }
415
+
416
+ _handleTriggerKeydown(e) {
417
+ // ARROW DOWN OR ENTER WHEN SELECT IS CLOSED - open Dropdowns
418
+ if ((e.which === this.keys.ARROW_DOWN || e.which === this.keys.ENTER) && !this.isOpen) {
419
+ e.preventDefault();
420
+ this.open();
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Handle Document Touchmove
426
+ * @param {Event} e
427
+ */
428
+ _handleDocumentTouchmove(e) {
429
+ let $target = $(e.target);
430
+ if ($target.closest('.dropdown-content').length) {
431
+ this.isTouchMoving = true;
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Handle Dropdowns Click
437
+ * @param {Event} e
438
+ */
439
+ _handleDropdownClick(e) {
440
+
441
+ // onItemClick callback (by reference)
442
+ // if (typeof this.options.onItemClick === 'function') {
443
+ // let itemEl = $(e.target).closest('li')[0];
444
+ // this.options.onItemClick.call(this, itemEl);
445
+ // }
446
+
447
+ // onItemClick callback (by name)
448
+ if (this.options.onItemClick !== 'false') {
449
+ var _this = this;
450
+ // var itemEl = $(e.target).closest('li')[0];
451
+ this.executeFunctionByName(this.options.onItemClick, window, _this);
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Handle Dropdowns Keydown
457
+ * @param {Event} e
458
+ */
459
+ _handleDropdownKeydown(e) {
460
+ if (e.which === this.keys.TAB) {
461
+ e.preventDefault();
462
+ this.close();
463
+
464
+ // Navigate down dropdown list
465
+ } else if ((e.which === this.keys.ARROW_DOWN || e.which === this.keys.ARROW_UP) && this.isOpen) {
466
+ e.preventDefault();
467
+ let direction = e.which === this.keys.ARROW_DOWN ? 1 : -1;
468
+ let newFocusedIndex = this.focusedIndex;
469
+ let foundNewIndex = false;
470
+ do {
471
+ newFocusedIndex = newFocusedIndex + direction;
472
+
473
+ if (
474
+ !!this.dropdownEl.children[newFocusedIndex] &&
475
+ this.dropdownEl.children[newFocusedIndex].tabIndex !== -1
476
+ ) {
477
+ foundNewIndex = true;
478
+ break;
479
+ }
480
+ } while (newFocusedIndex < this.dropdownEl.children.length && newFocusedIndex >= 0);
481
+
482
+ if (foundNewIndex) {
483
+ this.focusedIndex = newFocusedIndex;
484
+ this._focusFocusedItem();
485
+ }
486
+
487
+ // ENTER selects choice on focused item
488
+ } else if (e.which === this.keys.ENTER && this.isOpen) {
489
+ // Search for <a> and <button>
490
+ let focusedElement = this.dropdownEl.children[this.focusedIndex];
491
+ let $activatableElement = $(focusedElement)
492
+ .find('a, button')
493
+ .first();
494
+
495
+ // Click a or button tag if exists, otherwise click li tag
496
+ if (!!$activatableElement.length) {
497
+ $activatableElement[0].click();
498
+ } else if (!!focusedElement) {
499
+ focusedElement.click();
500
+ }
501
+
502
+ // Close dropdown on ESC
503
+ } else if (e.which === this.keys.ESC && this.isOpen) {
504
+ e.preventDefault();
505
+ this.close();
506
+ }
507
+
508
+ // CASE WHEN USER TYPE LETTERS
509
+ let letter = String.fromCharCode(e.which).toLowerCase(),
510
+ nonLetters = [9, 13, 27, 38, 40];
511
+ if (letter && nonLetters.indexOf(e.which) === -1) {
512
+ this.filterQuery.push(letter);
513
+
514
+ let string = this.filterQuery.join(''),
515
+ newOptionEl = $(this.dropdownEl)
516
+ .find('li')
517
+ .filter((el) => {
518
+ return (
519
+ $(el)
520
+ .text()
521
+ .toLowerCase()
522
+ .indexOf(string) === 0
523
+ );
524
+ })[0];
525
+
526
+ if (newOptionEl) {
527
+ this.focusedIndex = $(newOptionEl).index();
528
+ this._focusFocusedItem();
529
+ }
530
+ }
531
+
532
+ this.filterTimeout = setTimeout(this._resetFilterQueryBound, 1000);
533
+ }
534
+
535
+ /**
536
+ * Setup dropdown
537
+ */
538
+ _resetFilterQuery() {
539
+ this.filterQuery = [];
540
+ }
541
+
542
+ _resetDropdownStyles() {
543
+ this.$dropdownEl.css({
544
+ display: '',
545
+ width: '',
546
+ height: '',
547
+ left: '',
548
+ top: '',
549
+ 'transform-origin': '',
550
+ transform: '',
551
+ opacity: ''
552
+ });
553
+ }
554
+
555
+ _makeDropdownFocusable() {
556
+ // Needed for arrow key navigation
557
+ this.dropdownEl.tabIndex = 0;
558
+
559
+ // Only set tabindex if it hasn't been set by user
560
+ $(this.dropdownEl)
561
+ .children()
562
+ .each(function(el) {
563
+ if (!el.getAttribute('tabindex')) {
564
+ el.setAttribute('tabindex', 0);
565
+ }
566
+ });
567
+ }
568
+
569
+ _focusFocusedItem() {
570
+ if (
571
+ this.focusedIndex >= 0 &&
572
+ this.focusedIndex < this.dropdownEl.children.length &&
573
+ this.options.autoFocus
574
+ ) {
575
+ this.dropdownEl.children[this.focusedIndex].focus();
576
+ }
577
+ }
578
+
579
+ _getDropdownPosition() {
580
+ let offsetParentBRect = this.el.offsetParent.getBoundingClientRect();
581
+ let triggerBRect = this.el.getBoundingClientRect();
582
+ let dropdownBRect = this.dropdownEl.getBoundingClientRect();
583
+
584
+ let idealHeight = dropdownBRect.height;
585
+ let idealWidth = dropdownBRect.width;
586
+ let idealXPos = triggerBRect.left - dropdownBRect.left;
587
+ let idealYPos = triggerBRect.top - dropdownBRect.top;
588
+
589
+ let dropdownBounds = {
590
+ left: idealXPos,
591
+ top: idealYPos,
592
+ height: idealHeight,
593
+ width: idealWidth
594
+ };
595
+
596
+ // Countainer here will be closest ancestor with overflow: hidden
597
+ let closestOverflowParent = !!this.dropdownEl.offsetParent
598
+ ? this.dropdownEl.offsetParent
599
+ : this.dropdownEl.parentNode;
600
+
601
+ let alignments = this.checkPossibleAlignments(
602
+ this.el,
603
+ closestOverflowParent,
604
+ dropdownBounds,
605
+ this.options.coverTrigger ? 0 : triggerBRect.height
606
+ );
607
+
608
+ let verticalAlignment = 'top';
609
+ let horizontalAlignment = this.options.alignment;
610
+ idealYPos += this.options.coverTrigger ? 0 : triggerBRect.height;
611
+
612
+ // Reset isScrollable
613
+ this.isScrollable = false;
614
+
615
+ if (!alignments.top) {
616
+ if (alignments.bottom) {
617
+ verticalAlignment = 'bottom';
618
+ } else {
619
+ this.isScrollable = true;
620
+
621
+ // Determine which side has most space and cutoff at correct height
622
+ if (alignments.spaceOnTop > alignments.spaceOnBottom) {
623
+ verticalAlignment = 'bottom';
624
+ idealHeight += alignments.spaceOnTop;
625
+ idealYPos -= alignments.spaceOnTop;
626
+ } else {
627
+ idealHeight += alignments.spaceOnBottom;
628
+ }
629
+ }
630
+ }
631
+
632
+ // If preferred horizontal alignment is possible
633
+ if (!alignments[horizontalAlignment]) {
634
+ let oppositeAlignment = horizontalAlignment === 'left' ? 'right' : 'left';
635
+ if (alignments[oppositeAlignment]) {
636
+ horizontalAlignment = oppositeAlignment;
637
+ } else {
638
+ // Determine which side has most space and cutoff at correct height
639
+ if (alignments.spaceOnLeft > alignments.spaceOnRight) {
640
+ horizontalAlignment = 'right';
641
+ idealWidth += alignments.spaceOnLeft;
642
+ idealXPos -= alignments.spaceOnLeft;
643
+ } else {
644
+ horizontalAlignment = 'left';
645
+ idealWidth += alignments.spaceOnRight;
646
+ }
647
+ }
648
+ }
649
+
650
+ if (verticalAlignment === 'bottom') {
651
+ idealYPos =
652
+ idealYPos - dropdownBRect.height + (this.options.coverTrigger ? triggerBRect.height : 0);
653
+ }
654
+ if (horizontalAlignment === 'right') {
655
+ idealXPos = idealXPos - dropdownBRect.width + triggerBRect.width;
656
+ }
657
+ return {
658
+ x: idealXPos,
659
+ y: idealYPos,
660
+ verticalAlignment: verticalAlignment,
661
+ horizontalAlignment: horizontalAlignment,
662
+ height: idealHeight,
663
+ width: idealWidth
664
+ };
665
+ }
666
+
667
+ /**
668
+ * Animate in dropdown
669
+ */
670
+ _animateIn() {
671
+ anim.remove(this.dropdownEl);
672
+ anim({
673
+ targets: this.dropdownEl,
674
+ opacity: {
675
+ value: [0, 1],
676
+ easing: 'easeOutQuad'
677
+ },
678
+ scaleX: [0.3, 1],
679
+ scaleY: [0.3, 1],
680
+ duration: this.options.inDuration,
681
+ easing: 'easeOutQuint',
682
+ complete: (anim) => {
683
+ if (this.options.autoFocus) {
684
+ this.dropdownEl.focus();
685
+ }
686
+
687
+ // onOpenEnd callback (by reference)
688
+ if (typeof this.options.onOpenEnd === 'function') {
689
+ this.options.onOpenEnd.call(this, this.el);
690
+ }
691
+ }
692
+ });
693
+ }
694
+
695
+ /**
696
+ * Animate out dropdown
697
+ */
698
+ _animateOut() {
699
+ anim.remove(this.dropdownEl);
700
+ anim({
701
+ targets: this.dropdownEl,
702
+ opacity: {
703
+ value: 0,
704
+ easing: 'easeOutQuint'
705
+ },
706
+ scaleX: 0.3,
707
+ scaleY: 0.3,
708
+ duration: this.options.outDuration,
709
+ easing: 'easeOutQuint',
710
+ complete: (anim) => {
711
+ this._resetDropdownStyles();
712
+
713
+ // onCloseEnd callback (by reference)
714
+ if (typeof this.options.onCloseEnd === 'function') {
715
+ this.options.onCloseEnd.call(this, this.el);
716
+ }
717
+ }
718
+ });
719
+ }
720
+
721
+ /**
722
+ * Place dropdown
723
+ */
724
+ _placeDropdown() {
725
+ // Set width before calculating positionInfo
726
+ let idealWidth = this.options.constrainWidth
727
+ ? this.el.getBoundingClientRect().width
728
+ : this.dropdownEl.getBoundingClientRect().width;
729
+ this.dropdownEl.style.width = idealWidth + 'px';
730
+
731
+ let positionInfo = this._getDropdownPosition();
732
+ this.dropdownEl.style.left = positionInfo.x + 'px';
733
+ this.dropdownEl.style.top = positionInfo.y + 'px';
734
+ this.dropdownEl.style.height = positionInfo.height + 'px';
735
+ this.dropdownEl.style.width = positionInfo.width + 'px';
736
+ this.dropdownEl.style.transformOrigin = `${
737
+ positionInfo.horizontalAlignment === 'left' ? '0' : '100%'
738
+ } ${positionInfo.verticalAlignment === 'top' ? '0' : '100%'}`;
739
+ }
740
+
741
+ /**
742
+ * Open Dropdowns
743
+ */
744
+ open() {
745
+ if (this.isOpen) {
746
+ return;
747
+ }
748
+ this.isOpen = true;
749
+
750
+ // onOpen callback (by reference)
751
+ // if (typeof this.options.onOpen === 'function') {
752
+ // this.options.onOpen.call(this, this.el);
753
+ // }
754
+
755
+ var _this = this;
756
+ var listItems = '#' + _this.id + " li";
757
+ var menuItems = document.querySelectorAll(listItems);
758
+
759
+ // Loop through each <li> element
760
+ for (var i=0; i < menuItems.length; i++) {
761
+ // adding a event listener, mark selected menuItem by class active
762
+ menuItems[i].addEventListener('click', function(event) {
763
+ event.preventDefault();
764
+ for(var i=0; i < menuItems.length; i++) {
765
+ if (menuItems[i].classList.contains('active')) {
766
+ menuItems[i].classList.remove('active');
767
+ }
768
+ }
769
+ // add `active` to current clicked element
770
+ this.classList.add('active');
771
+ }, false);
772
+ }
773
+
774
+ // onOpen callback (ny name)
775
+ var _this = this;
776
+ if (this.options.onOpen) {
777
+ this.executeFunctionByName(this.options.onOpen, window, _this);
778
+ }
779
+
780
+ // Reset styles
781
+ this._resetDropdownStyles();
782
+ this.dropdownEl.style.display = 'block';
783
+
784
+ this._placeDropdown();
785
+ this._animateIn();
786
+ this._setupTemporaryEventHandlers();
787
+ }
788
+
789
+ /**
790
+ * Close Dropdowns
791
+ */
792
+ close() {
793
+ if (!this.isOpen) {
794
+ return;
795
+ }
796
+ this.isOpen = false;
797
+ this.focusedIndex = -1;
798
+
799
+ // onClose callback (by reference)
800
+ // if (typeof this.options.onClose === 'function') {
801
+ // this.options.onCloseStart.call(this, this.el);
802
+ // }
803
+
804
+ // onClose callback (by name)
805
+ var _this = this;
806
+ if (this.options.onClose) {
807
+ this.executeFunctionByName(this.options.onClose, window, _this);
808
+ }
809
+
810
+ this._animateOut();
811
+ this._removeTemporaryEventHandlers();
812
+
813
+ if (this.options.autoFocus) {
814
+ this.el.focus();
815
+ }
816
+ }
817
+
818
+ /**
819
+ * Recalculate dimensions
820
+ */
821
+ recalculateDimensions() {
822
+ if (this.isOpen) {
823
+ this.$dropdownEl.css({
824
+ width: '',
825
+ height: '',
826
+ left: '',
827
+ top: '',
828
+ 'transform-origin': ''
829
+ });
830
+ this._placeDropdown();
831
+ }
832
+ }
833
+ }
834
+
835
+ /**
836
+ * @static
837
+ * @memberof Dropdowns
838
+ */
839
+ Dropdowns._dropdowns = [];
840
+
841
+ // jadams, 2020-10-10: moved to j1 name space
842
+ //
843
+ // M.FloatingActionButton = FloatingActionButton;
844
+ j1.dropdowns = Dropdowns;
845
+
846
+ // jadams, 2020-10-10: check how to transform to jQuery
847
+ //
848
+
849
+ // Check for jQuery
850
+ //
851
+ j1.jQueryLoaded = !!window.jQuery;
852
+
853
+ if (j1.jQueryLoaded) {
854
+ j1.initializeJqueryWrapper (
855
+ Dropdowns,
856
+ 'dropdown',
857
+ 'M_Dropdown'
858
+ );
859
+ }
860
+
861
+ // jadams, 2020-10-10: TODO: check if anime could be a replacement
862
+ // for (huge) animate.css
863
+ // })($, j1.anime);
864
+ })(cash, j1.anime);