activeadmin_materialize_theme 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);