activeadmin_materialize_theme 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +26 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/config/activeadmin_materialize_theme_manifest.js +1 -0
  6. data/app/assets/javascripts/activeadmin_materialize_theme.js +65 -0
  7. data/app/assets/javascripts/materialize/anime.min.js +34 -0
  8. data/app/assets/javascripts/materialize/autocomplete.js +450 -0
  9. data/app/assets/javascripts/materialize/bin/materialize.js +12374 -0
  10. data/app/assets/javascripts/materialize/bin/materialize.min.js +6 -0
  11. data/app/assets/javascripts/materialize/buttons.js +354 -0
  12. data/app/assets/javascripts/materialize/cards.js +40 -0
  13. data/app/assets/javascripts/materialize/carousel.js +717 -0
  14. data/app/assets/javascripts/materialize/cash.js +960 -0
  15. data/app/assets/javascripts/materialize/characterCounter.js +136 -0
  16. data/app/assets/javascripts/materialize/chips.js +481 -0
  17. data/app/assets/javascripts/materialize/collapsible.js +275 -0
  18. data/app/assets/javascripts/materialize/component.js +44 -0
  19. data/app/assets/javascripts/materialize/datepicker.js +975 -0
  20. data/app/assets/javascripts/materialize/dropdown.js +617 -0
  21. data/app/assets/javascripts/materialize/forms.js +275 -0
  22. data/app/assets/javascripts/materialize/global.js +427 -0
  23. data/app/assets/javascripts/materialize/materialbox.js +453 -0
  24. data/app/assets/javascripts/materialize/modal.js +382 -0
  25. data/app/assets/javascripts/materialize/parallax.js +138 -0
  26. data/app/assets/javascripts/materialize/pushpin.js +145 -0
  27. data/app/assets/javascripts/materialize/range.js +263 -0
  28. data/app/assets/javascripts/materialize/scrollspy.js +295 -0
  29. data/app/assets/javascripts/materialize/select.js +432 -0
  30. data/app/assets/javascripts/materialize/sidenav.js +580 -0
  31. data/app/assets/javascripts/materialize/slider.js +359 -0
  32. data/app/assets/javascripts/materialize/tabs.js +402 -0
  33. data/app/assets/javascripts/materialize/tapTarget.js +314 -0
  34. data/app/assets/javascripts/materialize/timepicker.js +647 -0
  35. data/app/assets/javascripts/materialize/toasts.js +310 -0
  36. data/app/assets/javascripts/materialize/tooltip.js +303 -0
  37. data/app/assets/javascripts/materialize/waves.js +335 -0
  38. data/app/assets/stylesheets/activeadmin_materialize_theme/base.scss +107 -0
  39. data/app/assets/stylesheets/activeadmin_materialize_theme/components/footer.scss +18 -0
  40. data/app/assets/stylesheets/activeadmin_materialize_theme/components/form.scss +140 -0
  41. data/app/assets/stylesheets/activeadmin_materialize_theme/components/header.scss +61 -0
  42. data/app/assets/stylesheets/activeadmin_materialize_theme/components/layout_index.scss +83 -0
  43. data/app/assets/stylesheets/activeadmin_materialize_theme/components/layout_show.scss +56 -0
  44. data/app/assets/stylesheets/activeadmin_materialize_theme/components/sidebar.scss +37 -0
  45. data/app/assets/stylesheets/activeadmin_materialize_theme/components/title_bar.scss +43 -0
  46. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/LICENSE +21 -0
  47. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/README.md +91 -0
  48. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_badges.scss +55 -0
  49. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_buttons.scss +322 -0
  50. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_cards.scss +195 -0
  51. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_carousel.scss +90 -0
  52. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_chips.scss +90 -0
  53. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_collapsible.scss +91 -0
  54. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_color-classes.scss +32 -0
  55. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_color-variables.scss +370 -0
  56. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_datepicker.scss +191 -0
  57. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_dropdown.scss +85 -0
  58. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_global.scss +769 -0
  59. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_grid.scss +156 -0
  60. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_icons-material-design.scss +5 -0
  61. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_materialbox.scss +43 -0
  62. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_modal.scss +94 -0
  63. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_navbar.scss +208 -0
  64. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_normalize.scss +447 -0
  65. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_preloader.scss +334 -0
  66. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_pulse.scss +34 -0
  67. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_sidenav.scss +216 -0
  68. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_slider.scss +92 -0
  69. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_table_of_contents.scss +33 -0
  70. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_tabs.scss +99 -0
  71. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_tapTarget.scss +103 -0
  72. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_timepicker.scss +183 -0
  73. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_toast.scss +58 -0
  74. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_tooltip.scss +32 -0
  75. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_transitions.scss +13 -0
  76. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_typography.scss +60 -0
  77. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_variables.scss +349 -0
  78. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/_waves.scss +114 -0
  79. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_checkboxes.scss +200 -0
  80. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_file-input.scss +44 -0
  81. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_forms.scss +22 -0
  82. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_input-fields.scss +354 -0
  83. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_radio-buttons.scss +115 -0
  84. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_range.scss +161 -0
  85. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_select.scss +180 -0
  86. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/components/forms/_switches.scss +89 -0
  87. data/app/assets/stylesheets/activeadmin_materialize_theme/materialize/materialize.scss +41 -0
  88. data/app/assets/stylesheets/activeadmin_materialize_theme/normalize.css +349 -0
  89. data/app/assets/stylesheets/activeadmin_materialize_theme/theme.scss +13 -0
  90. data/app/assets/stylesheets/activeadmin_materialize_theme/variables.scss +14 -0
  91. data/lib/activeadmin_materialize_theme.rb +6 -0
  92. data/lib/activeadmin_materialize_theme/engine.rb +7 -0
  93. data/lib/activeadmin_materialize_theme/version.rb +5 -0
  94. metadata +149 -0
@@ -0,0 +1,432 @@
1
+ (function($) {
2
+ 'use strict';
3
+
4
+ let _defaults = {
5
+ classes: '',
6
+ dropdownOptions: {}
7
+ };
8
+
9
+ /**
10
+ * @class
11
+ *
12
+ */
13
+ class FormSelect extends Component {
14
+ /**
15
+ * Construct FormSelect instance
16
+ * @constructor
17
+ * @param {Element} el
18
+ * @param {Object} options
19
+ */
20
+ constructor(el, options) {
21
+ super(FormSelect, el, options);
22
+
23
+ // Don't init if browser default version
24
+ if (this.$el.hasClass('browser-default')) {
25
+ return;
26
+ }
27
+
28
+ this.el.M_FormSelect = this;
29
+
30
+ /**
31
+ * Options for the select
32
+ * @member FormSelect#options
33
+ */
34
+ this.options = $.extend({}, FormSelect.defaults, options);
35
+
36
+ this.isMultiple = this.$el.prop('multiple');
37
+
38
+ // Setup
39
+ this.el.tabIndex = -1;
40
+ this._keysSelected = {};
41
+ this._valueDict = {}; // Maps key to original and generated option element.
42
+ this._setupDropdown();
43
+
44
+ this._setupEventHandlers();
45
+ }
46
+
47
+ static get defaults() {
48
+ return _defaults;
49
+ }
50
+
51
+ static init(els, options) {
52
+ return super.init(this, els, options);
53
+ }
54
+
55
+ /**
56
+ * Get Instance
57
+ */
58
+ static getInstance(el) {
59
+ let domElem = !!el.jquery ? el[0] : el;
60
+ return domElem.M_FormSelect;
61
+ }
62
+
63
+ /**
64
+ * Teardown component
65
+ */
66
+ destroy() {
67
+ this._removeEventHandlers();
68
+ this._removeDropdown();
69
+ this.el.M_FormSelect = undefined;
70
+ }
71
+
72
+ /**
73
+ * Setup Event Handlers
74
+ */
75
+ _setupEventHandlers() {
76
+ this._handleSelectChangeBound = this._handleSelectChange.bind(this);
77
+ this._handleOptionClickBound = this._handleOptionClick.bind(this);
78
+ this._handleInputClickBound = this._handleInputClick.bind(this);
79
+
80
+ $(this.dropdownOptions)
81
+ .find('li:not(.optgroup)')
82
+ .each((el) => {
83
+ el.addEventListener('click', this._handleOptionClickBound);
84
+ });
85
+ this.el.addEventListener('change', this._handleSelectChangeBound);
86
+ this.input.addEventListener('click', this._handleInputClickBound);
87
+ }
88
+
89
+ /**
90
+ * Remove Event Handlers
91
+ */
92
+ _removeEventHandlers() {
93
+ $(this.dropdownOptions)
94
+ .find('li:not(.optgroup)')
95
+ .each((el) => {
96
+ el.removeEventListener('click', this._handleOptionClickBound);
97
+ });
98
+ this.el.removeEventListener('change', this._handleSelectChangeBound);
99
+ this.input.removeEventListener('click', this._handleInputClickBound);
100
+ }
101
+
102
+ /**
103
+ * Handle Select Change
104
+ * @param {Event} e
105
+ */
106
+ _handleSelectChange(e) {
107
+ this._setValueToInput();
108
+ }
109
+
110
+ /**
111
+ * Handle Option Click
112
+ * @param {Event} e
113
+ */
114
+ _handleOptionClick(e) {
115
+ e.preventDefault();
116
+ let option = $(e.target).closest('li')[0];
117
+ let key = option.id;
118
+ if (!$(option).hasClass('disabled') && !$(option).hasClass('optgroup') && key.length) {
119
+ let selected = true;
120
+
121
+ if (this.isMultiple) {
122
+ // Deselect placeholder option if still selected.
123
+ let placeholderOption = $(this.dropdownOptions).find('li.disabled.selected');
124
+ if (placeholderOption.length) {
125
+ placeholderOption.removeClass('selected');
126
+ placeholderOption.find('input[type="checkbox"]').prop('checked', false);
127
+ this._toggleEntryFromArray(placeholderOption[0].id);
128
+ }
129
+ selected = this._toggleEntryFromArray(key);
130
+ } else {
131
+ $(this.dropdownOptions)
132
+ .find('li')
133
+ .removeClass('selected');
134
+ $(option).toggleClass('selected', selected);
135
+ }
136
+
137
+ // Set selected on original select option
138
+ // Only trigger if selected state changed
139
+ let prevSelected = $(this._valueDict[key].el).prop('selected');
140
+ if (prevSelected !== selected) {
141
+ $(this._valueDict[key].el).prop('selected', selected);
142
+ this.$el.trigger('change');
143
+ }
144
+ }
145
+
146
+ e.stopPropagation();
147
+ }
148
+
149
+ /**
150
+ * Handle Input Click
151
+ */
152
+ _handleInputClick() {
153
+ if (this.dropdown && this.dropdown.isOpen) {
154
+ this._setValueToInput();
155
+ this._setSelectedStates();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Setup dropdown
161
+ */
162
+ _setupDropdown() {
163
+ this.wrapper = document.createElement('div');
164
+ $(this.wrapper).addClass('select-wrapper ' + this.options.classes);
165
+ this.$el.before($(this.wrapper));
166
+ this.wrapper.appendChild(this.el);
167
+
168
+ if (this.el.disabled) {
169
+ this.wrapper.classList.add('disabled');
170
+ }
171
+
172
+ // Create dropdown
173
+ this.$selectOptions = this.$el.children('option, optgroup');
174
+ this.dropdownOptions = document.createElement('ul');
175
+ this.dropdownOptions.id = `select-options-${M.guid()}`;
176
+ $(this.dropdownOptions).addClass(
177
+ 'dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : '')
178
+ );
179
+
180
+ // Create dropdown structure.
181
+ if (this.$selectOptions.length) {
182
+ this.$selectOptions.each((el) => {
183
+ if ($(el).is('option')) {
184
+ // Direct descendant option.
185
+ let optionEl;
186
+ if (this.isMultiple) {
187
+ optionEl = this._appendOptionWithIcon(this.$el, el, 'multiple');
188
+ } else {
189
+ optionEl = this._appendOptionWithIcon(this.$el, el);
190
+ }
191
+
192
+ this._addOptionToValueDict(el, optionEl);
193
+ } else if ($(el).is('optgroup')) {
194
+ // Optgroup.
195
+ let selectOptions = $(el).children('option');
196
+ $(this.dropdownOptions).append(
197
+ $('<li class="optgroup"><span>' + el.getAttribute('label') + '</span></li>')[0]
198
+ );
199
+
200
+ selectOptions.each((el) => {
201
+ let optionEl = this._appendOptionWithIcon(this.$el, el, 'optgroup-option');
202
+ this._addOptionToValueDict(el, optionEl);
203
+ });
204
+ }
205
+ });
206
+ }
207
+
208
+ this.$el.after(this.dropdownOptions);
209
+
210
+ // Add input dropdown
211
+ this.input = document.createElement('input');
212
+ $(this.input).addClass('select-dropdown dropdown-trigger');
213
+ this.input.setAttribute('type', 'text');
214
+ this.input.setAttribute('readonly', 'true');
215
+ this.input.setAttribute('data-target', this.dropdownOptions.id);
216
+ if (this.el.disabled) {
217
+ $(this.input).prop('disabled', 'true');
218
+ }
219
+
220
+ this.$el.before(this.input);
221
+ this._setValueToInput();
222
+
223
+ // Add caret
224
+ let dropdownIcon = $(
225
+ '<svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'
226
+ );
227
+ this.$el.before(dropdownIcon[0]);
228
+
229
+ // Initialize dropdown
230
+ if (!this.el.disabled) {
231
+ let dropdownOptions = $.extend({}, this.options.dropdownOptions);
232
+
233
+ // Add callback for centering selected option when dropdown content is scrollable
234
+ dropdownOptions.onOpenEnd = (el) => {
235
+ let selectedOption = $(this.dropdownOptions)
236
+ .find('.selected')
237
+ .first();
238
+
239
+ if (selectedOption.length) {
240
+ // Focus selected option in dropdown
241
+ M.keyDown = true;
242
+ this.dropdown.focusedIndex = selectedOption.index();
243
+ this.dropdown._focusFocusedItem();
244
+ M.keyDown = false;
245
+
246
+ // Handle scrolling to selected option
247
+ if (this.dropdown.isScrollable) {
248
+ let scrollOffset =
249
+ selectedOption[0].getBoundingClientRect().top -
250
+ this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option
251
+ scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown
252
+ this.dropdownOptions.scrollTop = scrollOffset;
253
+ }
254
+ }
255
+ };
256
+
257
+ if (this.isMultiple) {
258
+ dropdownOptions.closeOnClick = false;
259
+ }
260
+ this.dropdown = M.Dropdown.init(this.input, dropdownOptions);
261
+ }
262
+
263
+ // Add initial selections
264
+ this._setSelectedStates();
265
+ }
266
+
267
+ /**
268
+ * Add option to value dict
269
+ * @param {Element} el original option element
270
+ * @param {Element} optionEl generated option element
271
+ */
272
+ _addOptionToValueDict(el, optionEl) {
273
+ let index = Object.keys(this._valueDict).length;
274
+ let key = this.dropdownOptions.id + index;
275
+ let obj = {};
276
+ optionEl.id = key;
277
+
278
+ obj.el = el;
279
+ obj.optionEl = optionEl;
280
+ this._valueDict[key] = obj;
281
+ }
282
+
283
+ /**
284
+ * Remove dropdown
285
+ */
286
+ _removeDropdown() {
287
+ $(this.wrapper)
288
+ .find('.caret')
289
+ .remove();
290
+ $(this.input).remove();
291
+ $(this.dropdownOptions).remove();
292
+ $(this.wrapper).before(this.$el);
293
+ $(this.wrapper).remove();
294
+ }
295
+
296
+ /**
297
+ * Setup dropdown
298
+ * @param {Element} select select element
299
+ * @param {Element} option option element from select
300
+ * @param {String} type
301
+ * @return {Element} option element added
302
+ */
303
+ _appendOptionWithIcon(select, option, type) {
304
+ // Add disabled attr if disabled
305
+ let disabledClass = option.disabled ? 'disabled ' : '';
306
+ let optgroupClass = type === 'optgroup-option' ? 'optgroup-option ' : '';
307
+ let multipleCheckbox = this.isMultiple
308
+ ? `<label><input type="checkbox"${disabledClass}"/><span>${option.innerHTML}</span></label>`
309
+ : option.innerHTML;
310
+ let liEl = $('<li></li>');
311
+ let spanEl = $('<span></span>');
312
+ spanEl.html(multipleCheckbox);
313
+ liEl.addClass(`${disabledClass} ${optgroupClass}`);
314
+ liEl.append(spanEl);
315
+
316
+ // add icons
317
+ let iconUrl = option.getAttribute('data-icon');
318
+ if (!!iconUrl) {
319
+ let imgEl = $(`<img alt="" src="${iconUrl}">`);
320
+ liEl.prepend(imgEl);
321
+ }
322
+
323
+ // Check for multiple type.
324
+ $(this.dropdownOptions).append(liEl[0]);
325
+ return liEl[0];
326
+ }
327
+
328
+ /**
329
+ * Toggle entry from option
330
+ * @param {String} key Option key
331
+ * @return {Boolean} if entry was added or removed
332
+ */
333
+ _toggleEntryFromArray(key) {
334
+ let notAdded = !this._keysSelected.hasOwnProperty(key);
335
+ let $optionLi = $(this._valueDict[key].optionEl);
336
+
337
+ if (notAdded) {
338
+ this._keysSelected[key] = true;
339
+ } else {
340
+ delete this._keysSelected[key];
341
+ }
342
+
343
+ $optionLi.toggleClass('selected', notAdded);
344
+
345
+ // Set checkbox checked value
346
+ $optionLi.find('input[type="checkbox"]').prop('checked', notAdded);
347
+
348
+ // use notAdded instead of true (to detect if the option is selected or not)
349
+ $optionLi.prop('selected', notAdded);
350
+
351
+ return notAdded;
352
+ }
353
+
354
+ /**
355
+ * Set text value to input
356
+ */
357
+ _setValueToInput() {
358
+ let values = [];
359
+ let options = this.$el.find('option');
360
+
361
+ options.each((el) => {
362
+ if ($(el).prop('selected')) {
363
+ let text = $(el).text();
364
+ values.push(text);
365
+ }
366
+ });
367
+
368
+ if (!values.length) {
369
+ let firstDisabled = this.$el.find('option:disabled').eq(0);
370
+ if (firstDisabled.length && firstDisabled[0].value === '') {
371
+ values.push(firstDisabled.text());
372
+ }
373
+ }
374
+
375
+ this.input.value = values.join(', ');
376
+ }
377
+
378
+ /**
379
+ * Set selected state of dropdown to match actual select element
380
+ */
381
+ _setSelectedStates() {
382
+ this._keysSelected = {};
383
+
384
+ for (let key in this._valueDict) {
385
+ let option = this._valueDict[key];
386
+ let optionIsSelected = $(option.el).prop('selected');
387
+ $(option.optionEl)
388
+ .find('input[type="checkbox"]')
389
+ .prop('checked', optionIsSelected);
390
+ if (optionIsSelected) {
391
+ this._activateOption($(this.dropdownOptions), $(option.optionEl));
392
+ this._keysSelected[key] = true;
393
+ } else {
394
+ $(option.optionEl).removeClass('selected');
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Make option as selected and scroll to selected position
401
+ * @param {jQuery} collection Select options jQuery element
402
+ * @param {Element} newOption element of the new option
403
+ */
404
+ _activateOption(collection, newOption) {
405
+ if (newOption) {
406
+ if (!this.isMultiple) {
407
+ collection.find('li.selected').removeClass('selected');
408
+ }
409
+ let option = $(newOption);
410
+ option.addClass('selected');
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Get Selected Values
416
+ * @return {Array} Array of selected values
417
+ */
418
+ getSelectedValues() {
419
+ let selectedValues = [];
420
+ for (let key in this._keysSelected) {
421
+ selectedValues.push(this._valueDict[key].el.value);
422
+ }
423
+ return selectedValues;
424
+ }
425
+ }
426
+
427
+ M.FormSelect = FormSelect;
428
+
429
+ if (M.jQueryLoaded) {
430
+ M.initializeJqueryWrapper(FormSelect, 'formSelect', 'M_FormSelect');
431
+ }
432
+ })(cash);
@@ -0,0 +1,580 @@
1
+ (function($, anim) {
2
+ 'use strict';
3
+
4
+ let _defaults = {
5
+ edge: 'left',
6
+ draggable: true,
7
+ inDuration: 250,
8
+ outDuration: 200,
9
+ onOpenStart: null,
10
+ onOpenEnd: null,
11
+ onCloseStart: null,
12
+ onCloseEnd: null,
13
+ preventScrolling: true
14
+ };
15
+
16
+ /**
17
+ * @class
18
+ */
19
+ class Sidenav extends Component {
20
+ /**
21
+ * Construct Sidenav instance and set up overlay
22
+ * @constructor
23
+ * @param {Element} el
24
+ * @param {Object} options
25
+ */
26
+ constructor(el, options) {
27
+ super(Sidenav, el, options);
28
+
29
+ this.el.M_Sidenav = this;
30
+ this.id = this.$el.attr('id');
31
+
32
+ /**
33
+ * Options for the Sidenav
34
+ * @member Sidenav#options
35
+ * @prop {String} [edge='left'] - Side of screen on which Sidenav appears
36
+ * @prop {Boolean} [draggable=true] - Allow swipe gestures to open/close Sidenav
37
+ * @prop {Number} [inDuration=250] - Length in ms of enter transition
38
+ * @prop {Number} [outDuration=200] - Length in ms of exit transition
39
+ * @prop {Function} onOpenStart - Function called when sidenav starts entering
40
+ * @prop {Function} onOpenEnd - Function called when sidenav finishes entering
41
+ * @prop {Function} onCloseStart - Function called when sidenav starts exiting
42
+ * @prop {Function} onCloseEnd - Function called when sidenav finishes exiting
43
+ */
44
+ this.options = $.extend({}, Sidenav.defaults, options);
45
+
46
+ /**
47
+ * Describes open/close state of Sidenav
48
+ * @type {Boolean}
49
+ */
50
+ this.isOpen = false;
51
+
52
+ /**
53
+ * Describes if Sidenav is fixed
54
+ * @type {Boolean}
55
+ */
56
+ this.isFixed = this.el.classList.contains('sidenav-fixed');
57
+
58
+ /**
59
+ * Describes if Sidenav is being draggeed
60
+ * @type {Boolean}
61
+ */
62
+ this.isDragged = false;
63
+
64
+ // Window size variables for window resize checks
65
+ this.lastWindowWidth = window.innerWidth;
66
+ this.lastWindowHeight = window.innerHeight;
67
+
68
+ this._createOverlay();
69
+ this._createDragTarget();
70
+ this._setupEventHandlers();
71
+ this._setupClasses();
72
+ this._setupFixed();
73
+
74
+ Sidenav._sidenavs.push(this);
75
+ }
76
+
77
+ static get defaults() {
78
+ return _defaults;
79
+ }
80
+
81
+ static init(els, options) {
82
+ return super.init(this, els, options);
83
+ }
84
+
85
+ /**
86
+ * Get Instance
87
+ */
88
+ static getInstance(el) {
89
+ let domElem = !!el.jquery ? el[0] : el;
90
+ return domElem.M_Sidenav;
91
+ }
92
+
93
+ /**
94
+ * Teardown component
95
+ */
96
+ destroy() {
97
+ this._removeEventHandlers();
98
+ this._enableBodyScrolling();
99
+ this._overlay.parentNode.removeChild(this._overlay);
100
+ this.dragTarget.parentNode.removeChild(this.dragTarget);
101
+ this.el.M_Sidenav = undefined;
102
+ this.el.style.transform = '';
103
+
104
+ let index = Sidenav._sidenavs.indexOf(this);
105
+ if (index >= 0) {
106
+ Sidenav._sidenavs.splice(index, 1);
107
+ }
108
+ }
109
+
110
+ _createOverlay() {
111
+ let overlay = document.createElement('div');
112
+ this._closeBound = this.close.bind(this);
113
+ overlay.classList.add('sidenav-overlay');
114
+
115
+ overlay.addEventListener('click', this._closeBound);
116
+
117
+ document.body.appendChild(overlay);
118
+ this._overlay = overlay;
119
+ }
120
+
121
+ _setupEventHandlers() {
122
+ if (Sidenav._sidenavs.length === 0) {
123
+ document.body.addEventListener('click', this._handleTriggerClick);
124
+ }
125
+
126
+ this._handleDragTargetDragBound = this._handleDragTargetDrag.bind(this);
127
+ this._handleDragTargetReleaseBound = this._handleDragTargetRelease.bind(this);
128
+ this._handleCloseDragBound = this._handleCloseDrag.bind(this);
129
+ this._handleCloseReleaseBound = this._handleCloseRelease.bind(this);
130
+ this._handleCloseTriggerClickBound = this._handleCloseTriggerClick.bind(this);
131
+
132
+ this.dragTarget.addEventListener('touchmove', this._handleDragTargetDragBound);
133
+ this.dragTarget.addEventListener('touchend', this._handleDragTargetReleaseBound);
134
+ this._overlay.addEventListener('touchmove', this._handleCloseDragBound);
135
+ this._overlay.addEventListener('touchend', this._handleCloseReleaseBound);
136
+ this.el.addEventListener('touchmove', this._handleCloseDragBound);
137
+ this.el.addEventListener('touchend', this._handleCloseReleaseBound);
138
+ this.el.addEventListener('click', this._handleCloseTriggerClickBound);
139
+
140
+ // Add resize for side nav fixed
141
+ if (this.isFixed) {
142
+ this._handleWindowResizeBound = this._handleWindowResize.bind(this);
143
+ window.addEventListener('resize', this._handleWindowResizeBound);
144
+ }
145
+ }
146
+
147
+ _removeEventHandlers() {
148
+ if (Sidenav._sidenavs.length === 1) {
149
+ document.body.removeEventListener('click', this._handleTriggerClick);
150
+ }
151
+
152
+ this.dragTarget.removeEventListener('touchmove', this._handleDragTargetDragBound);
153
+ this.dragTarget.removeEventListener('touchend', this._handleDragTargetReleaseBound);
154
+ this._overlay.removeEventListener('touchmove', this._handleCloseDragBound);
155
+ this._overlay.removeEventListener('touchend', this._handleCloseReleaseBound);
156
+ this.el.removeEventListener('touchmove', this._handleCloseDragBound);
157
+ this.el.removeEventListener('touchend', this._handleCloseReleaseBound);
158
+ this.el.removeEventListener('click', this._handleCloseTriggerClickBound);
159
+
160
+ // Remove resize for side nav fixed
161
+ if (this.isFixed) {
162
+ window.removeEventListener('resize', this._handleWindowResizeBound);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Handle Trigger Click
168
+ * @param {Event} e
169
+ */
170
+ _handleTriggerClick(e) {
171
+ let $trigger = $(e.target).closest('.sidenav-trigger');
172
+ if (e.target && $trigger.length) {
173
+ let sidenavId = M.getIdFromTrigger($trigger[0]);
174
+
175
+ let sidenavInstance = document.getElementById(sidenavId).M_Sidenav;
176
+ if (sidenavInstance) {
177
+ sidenavInstance.open($trigger);
178
+ }
179
+ e.preventDefault();
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Set variables needed at the beggining of drag
185
+ * and stop any current transition.
186
+ * @param {Event} e
187
+ */
188
+ _startDrag(e) {
189
+ let clientX = e.targetTouches[0].clientX;
190
+ this.isDragged = true;
191
+ this._startingXpos = clientX;
192
+ this._xPos = this._startingXpos;
193
+ this._time = Date.now();
194
+ this._width = this.el.getBoundingClientRect().width;
195
+ this._overlay.style.display = 'block';
196
+ this._initialScrollTop = this.isOpen ? this.el.scrollTop : M.getDocumentScrollTop();
197
+ this._verticallyScrolling = false;
198
+ anim.remove(this.el);
199
+ anim.remove(this._overlay);
200
+ }
201
+
202
+ /**
203
+ * Set variables needed at each drag move update tick
204
+ * @param {Event} e
205
+ */
206
+ _dragMoveUpdate(e) {
207
+ let clientX = e.targetTouches[0].clientX;
208
+ let currentScrollTop = this.isOpen ? this.el.scrollTop : M.getDocumentScrollTop();
209
+ this.deltaX = Math.abs(this._xPos - clientX);
210
+ this._xPos = clientX;
211
+ this.velocityX = this.deltaX / (Date.now() - this._time);
212
+ this._time = Date.now();
213
+ if (this._initialScrollTop !== currentScrollTop) {
214
+ this._verticallyScrolling = true;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Handles Dragging of Sidenav
220
+ * @param {Event} e
221
+ */
222
+ _handleDragTargetDrag(e) {
223
+ // Check if draggable
224
+ if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) {
225
+ return;
226
+ }
227
+
228
+ // If not being dragged, set initial drag start variables
229
+ if (!this.isDragged) {
230
+ this._startDrag(e);
231
+ }
232
+
233
+ // Run touchmove updates
234
+ this._dragMoveUpdate(e);
235
+
236
+ // Calculate raw deltaX
237
+ let totalDeltaX = this._xPos - this._startingXpos;
238
+
239
+ // dragDirection is the attempted user drag direction
240
+ let dragDirection = totalDeltaX > 0 ? 'right' : 'left';
241
+
242
+ // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction
243
+ totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
244
+ if (this.options.edge === dragDirection) {
245
+ totalDeltaX = 0;
246
+ }
247
+
248
+ /**
249
+ * transformX is the drag displacement
250
+ * transformPrefix is the initial transform placement
251
+ * Invert values if Sidenav is right edge
252
+ */
253
+ let transformX = totalDeltaX;
254
+ let transformPrefix = 'translateX(-100%)';
255
+ if (this.options.edge === 'right') {
256
+ transformPrefix = 'translateX(100%)';
257
+ transformX = -transformX;
258
+ }
259
+
260
+ // Calculate open/close percentage of sidenav, with open = 1 and close = 0
261
+ this.percentOpen = Math.min(1, totalDeltaX / this._width);
262
+
263
+ // Set transform and opacity styles
264
+ this.el.style.transform = `${transformPrefix} translateX(${transformX}px)`;
265
+ this._overlay.style.opacity = this.percentOpen;
266
+ }
267
+
268
+ /**
269
+ * Handle Drag Target Release
270
+ */
271
+ _handleDragTargetRelease() {
272
+ if (this.isDragged) {
273
+ if (this.percentOpen > 0.2) {
274
+ this.open();
275
+ } else {
276
+ this._animateOut();
277
+ }
278
+
279
+ this.isDragged = false;
280
+ this._verticallyScrolling = false;
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Handle Close Drag
286
+ * @param {Event} e
287
+ */
288
+ _handleCloseDrag(e) {
289
+ if (this.isOpen) {
290
+ // Check if draggable
291
+ if (!this.options.draggable || this._isCurrentlyFixed() || this._verticallyScrolling) {
292
+ return;
293
+ }
294
+
295
+ // If not being dragged, set initial drag start variables
296
+ if (!this.isDragged) {
297
+ this._startDrag(e);
298
+ }
299
+
300
+ // Run touchmove updates
301
+ this._dragMoveUpdate(e);
302
+
303
+ // Calculate raw deltaX
304
+ let totalDeltaX = this._xPos - this._startingXpos;
305
+
306
+ // dragDirection is the attempted user drag direction
307
+ let dragDirection = totalDeltaX > 0 ? 'right' : 'left';
308
+
309
+ // Don't allow totalDeltaX to exceed Sidenav width or be dragged in the opposite direction
310
+ totalDeltaX = Math.min(this._width, Math.abs(totalDeltaX));
311
+ if (this.options.edge !== dragDirection) {
312
+ totalDeltaX = 0;
313
+ }
314
+
315
+ let transformX = -totalDeltaX;
316
+ if (this.options.edge === 'right') {
317
+ transformX = -transformX;
318
+ }
319
+
320
+ // Calculate open/close percentage of sidenav, with open = 1 and close = 0
321
+ this.percentOpen = Math.min(1, 1 - totalDeltaX / this._width);
322
+
323
+ // Set transform and opacity styles
324
+ this.el.style.transform = `translateX(${transformX}px)`;
325
+ this._overlay.style.opacity = this.percentOpen;
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Handle Close Release
331
+ */
332
+ _handleCloseRelease() {
333
+ if (this.isOpen && this.isDragged) {
334
+ if (this.percentOpen > 0.8) {
335
+ this._animateIn();
336
+ } else {
337
+ this.close();
338
+ }
339
+
340
+ this.isDragged = false;
341
+ this._verticallyScrolling = false;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Handles closing of Sidenav when element with class .sidenav-close
347
+ */
348
+ _handleCloseTriggerClick(e) {
349
+ let $closeTrigger = $(e.target).closest('.sidenav-close');
350
+ if ($closeTrigger.length && !this._isCurrentlyFixed()) {
351
+ this.close();
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Handle Window Resize
357
+ */
358
+ _handleWindowResize() {
359
+ // Only handle horizontal resizes
360
+ if (this.lastWindowWidth !== window.innerWidth) {
361
+ if (window.innerWidth > 992) {
362
+ this.open();
363
+ } else {
364
+ this.close();
365
+ }
366
+ }
367
+
368
+ this.lastWindowWidth = window.innerWidth;
369
+ this.lastWindowHeight = window.innerHeight;
370
+ }
371
+
372
+ _setupClasses() {
373
+ if (this.options.edge === 'right') {
374
+ this.el.classList.add('right-aligned');
375
+ this.dragTarget.classList.add('right-aligned');
376
+ }
377
+ }
378
+
379
+ _removeClasses() {
380
+ this.el.classList.remove('right-aligned');
381
+ this.dragTarget.classList.remove('right-aligned');
382
+ }
383
+
384
+ _setupFixed() {
385
+ if (this._isCurrentlyFixed()) {
386
+ this.open();
387
+ }
388
+ }
389
+
390
+ _isCurrentlyFixed() {
391
+ return this.isFixed && window.innerWidth > 992;
392
+ }
393
+
394
+ _createDragTarget() {
395
+ let dragTarget = document.createElement('div');
396
+ dragTarget.classList.add('drag-target');
397
+ document.body.appendChild(dragTarget);
398
+ this.dragTarget = dragTarget;
399
+ }
400
+
401
+ _preventBodyScrolling() {
402
+ let body = document.body;
403
+ body.style.overflow = 'hidden';
404
+ }
405
+
406
+ _enableBodyScrolling() {
407
+ let body = document.body;
408
+ body.style.overflow = '';
409
+ }
410
+
411
+ open() {
412
+ if (this.isOpen === true) {
413
+ return;
414
+ }
415
+
416
+ this.isOpen = true;
417
+
418
+ // Run onOpenStart callback
419
+ if (typeof this.options.onOpenStart === 'function') {
420
+ this.options.onOpenStart.call(this, this.el);
421
+ }
422
+
423
+ // Handle fixed Sidenav
424
+ if (this._isCurrentlyFixed()) {
425
+ anim.remove(this.el);
426
+ anim({
427
+ targets: this.el,
428
+ translateX: 0,
429
+ duration: 0,
430
+ easing: 'easeOutQuad'
431
+ });
432
+ this._enableBodyScrolling();
433
+ this._overlay.style.display = 'none';
434
+
435
+ // Handle non-fixed Sidenav
436
+ } else {
437
+ if (this.options.preventScrolling) {
438
+ this._preventBodyScrolling();
439
+ }
440
+
441
+ if (!this.isDragged || this.percentOpen != 1) {
442
+ this._animateIn();
443
+ }
444
+ }
445
+ }
446
+
447
+ close() {
448
+ if (this.isOpen === false) {
449
+ return;
450
+ }
451
+
452
+ this.isOpen = false;
453
+
454
+ // Run onCloseStart callback
455
+ if (typeof this.options.onCloseStart === 'function') {
456
+ this.options.onCloseStart.call(this, this.el);
457
+ }
458
+
459
+ // Handle fixed Sidenav
460
+ if (this._isCurrentlyFixed()) {
461
+ let transformX = this.options.edge === 'left' ? '-105%' : '105%';
462
+ this.el.style.transform = `translateX(${transformX})`;
463
+
464
+ // Handle non-fixed Sidenav
465
+ } else {
466
+ this._enableBodyScrolling();
467
+
468
+ if (!this.isDragged || this.percentOpen != 0) {
469
+ this._animateOut();
470
+ } else {
471
+ this._overlay.style.display = 'none';
472
+ }
473
+ }
474
+ }
475
+
476
+ _animateIn() {
477
+ this._animateSidenavIn();
478
+ this._animateOverlayIn();
479
+ }
480
+
481
+ _animateSidenavIn() {
482
+ let slideOutPercent = this.options.edge === 'left' ? -1 : 1;
483
+ if (this.isDragged) {
484
+ slideOutPercent =
485
+ this.options.edge === 'left'
486
+ ? slideOutPercent + this.percentOpen
487
+ : slideOutPercent - this.percentOpen;
488
+ }
489
+
490
+ anim.remove(this.el);
491
+ anim({
492
+ targets: this.el,
493
+ translateX: [`${slideOutPercent * 100}%`, 0],
494
+ duration: this.options.inDuration,
495
+ easing: 'easeOutQuad',
496
+ complete: () => {
497
+ // Run onOpenEnd callback
498
+ if (typeof this.options.onOpenEnd === 'function') {
499
+ this.options.onOpenEnd.call(this, this.el);
500
+ }
501
+ }
502
+ });
503
+ }
504
+
505
+ _animateOverlayIn() {
506
+ let start = 0;
507
+ if (this.isDragged) {
508
+ start = this.percentOpen;
509
+ } else {
510
+ $(this._overlay).css({
511
+ display: 'block'
512
+ });
513
+ }
514
+
515
+ anim.remove(this._overlay);
516
+ anim({
517
+ targets: this._overlay,
518
+ opacity: [start, 1],
519
+ duration: this.options.inDuration,
520
+ easing: 'easeOutQuad'
521
+ });
522
+ }
523
+
524
+ _animateOut() {
525
+ this._animateSidenavOut();
526
+ this._animateOverlayOut();
527
+ }
528
+
529
+ _animateSidenavOut() {
530
+ let endPercent = this.options.edge === 'left' ? -1 : 1;
531
+ let slideOutPercent = 0;
532
+ if (this.isDragged) {
533
+ slideOutPercent =
534
+ this.options.edge === 'left'
535
+ ? endPercent + this.percentOpen
536
+ : endPercent - this.percentOpen;
537
+ }
538
+
539
+ anim.remove(this.el);
540
+ anim({
541
+ targets: this.el,
542
+ translateX: [`${slideOutPercent * 100}%`, `${endPercent * 105}%`],
543
+ duration: this.options.outDuration,
544
+ easing: 'easeOutQuad',
545
+ complete: () => {
546
+ // Run onOpenEnd callback
547
+ if (typeof this.options.onCloseEnd === 'function') {
548
+ this.options.onCloseEnd.call(this, this.el);
549
+ }
550
+ }
551
+ });
552
+ }
553
+
554
+ _animateOverlayOut() {
555
+ anim.remove(this._overlay);
556
+ anim({
557
+ targets: this._overlay,
558
+ opacity: 0,
559
+ duration: this.options.outDuration,
560
+ easing: 'easeOutQuad',
561
+ complete: () => {
562
+ $(this._overlay).css('display', 'none');
563
+ }
564
+ });
565
+ }
566
+ }
567
+
568
+ /**
569
+ * @static
570
+ * @memberof Sidenav
571
+ * @type {Array.<Sidenav>}
572
+ */
573
+ Sidenav._sidenavs = [];
574
+
575
+ M.Sidenav = Sidenav;
576
+
577
+ if (M.jQueryLoaded) {
578
+ M.initializeJqueryWrapper(Sidenav, 'sidenav', 'M_Sidenav');
579
+ }
580
+ })(cash, M.anime);