@natachah/vanilla-frontend 0.0.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 (104) hide show
  1. package/.gitlab-ci.yml +40 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +11 -0
  4. package/docs/index.html +36 -0
  5. package/docs/main.js +32 -0
  6. package/docs/pages/components/badge.html +154 -0
  7. package/docs/pages/components/button.html +186 -0
  8. package/docs/pages/components/card.html +184 -0
  9. package/docs/pages/components/dialog.html +334 -0
  10. package/docs/pages/components/disclosure.html +310 -0
  11. package/docs/pages/components/dropdown.html +255 -0
  12. package/docs/pages/components/form.html +331 -0
  13. package/docs/pages/components/list.html +140 -0
  14. package/docs/pages/components/loading.html +58 -0
  15. package/docs/pages/components/media.html +130 -0
  16. package/docs/pages/components/nav.html +119 -0
  17. package/docs/pages/components/progress.html +47 -0
  18. package/docs/pages/components/slider.html +311 -0
  19. package/docs/pages/components/table.html +168 -0
  20. package/docs/pages/javascript/autofill.html +170 -0
  21. package/docs/pages/javascript/checkall.html +59 -0
  22. package/docs/pages/javascript/comfort.html +134 -0
  23. package/docs/pages/javascript/consent.html +112 -0
  24. package/docs/pages/javascript/cookie.html +81 -0
  25. package/docs/pages/javascript/form.html +199 -0
  26. package/docs/pages/javascript/scroll.html +209 -0
  27. package/docs/pages/javascript/sidebar.html +53 -0
  28. package/docs/pages/javascript/sortable.html +148 -0
  29. package/docs/pages/javascript/toggle.html +191 -0
  30. package/docs/pages/javascript/tree.html +221 -0
  31. package/docs/pages/layout/grid.html +201 -0
  32. package/docs/pages/layout/reset.html +53 -0
  33. package/docs/pages/layout/typography.html +324 -0
  34. package/docs/pages/quick-start/conventions.html +112 -0
  35. package/docs/pages/quick-start/customization.html +187 -0
  36. package/docs/pages/quick-start/installation.html +95 -0
  37. package/docs/pages/quick-start/mixins.html +228 -0
  38. package/docs/pages/test.html +15 -0
  39. package/docs/src/js/demo.js +98 -0
  40. package/docs/src/js/doc-code.js +102 -0
  41. package/docs/src/js/doc-demo.js +14 -0
  42. package/docs/src/js/doc-layout.js +108 -0
  43. package/docs/src/scss/demo.scss +77 -0
  44. package/docs/src/scss/layout.scss +160 -0
  45. package/docs/src/scss/style.scss +278 -0
  46. package/docs/vite.config.mjs +23 -0
  47. package/esbuild.mjs +25 -0
  48. package/js/_autofill.js +131 -0
  49. package/js/_check-all.js +77 -0
  50. package/js/_comfort.js +174 -0
  51. package/js/_consent.js +84 -0
  52. package/js/_dialog.js +164 -0
  53. package/js/_dropdown.js +101 -0
  54. package/js/_scroll.js +184 -0
  55. package/js/_sidebar.js +97 -0
  56. package/js/_slider.js +249 -0
  57. package/js/_sortable.js +143 -0
  58. package/js/_tabpanel.js +88 -0
  59. package/js/_toggle.js +123 -0
  60. package/js/_tree.js +85 -0
  61. package/js/tests/autofill.test.js +157 -0
  62. package/js/tests/base-component.test.js +108 -0
  63. package/js/tests/check-all.test.js +88 -0
  64. package/js/tests/comfort.test.js +219 -0
  65. package/js/tests/consent.test.js +84 -0
  66. package/js/tests/cookie.test.js +102 -0
  67. package/js/tests/dialog.test.js +189 -0
  68. package/js/tests/dropdown.test.js +115 -0
  69. package/js/tests/form-helper.test.js +155 -0
  70. package/js/tests/scroll.test.js +203 -0
  71. package/js/tests/sidebar.test.js +99 -0
  72. package/js/tests/slider.test.js +307 -0
  73. package/js/tests/sortable.test.js +124 -0
  74. package/js/tests/tabpanel.test.js +114 -0
  75. package/js/tests/toggle.test.js +190 -0
  76. package/js/tests/tree.test.js +165 -0
  77. package/js/utilities/_base-component.js +101 -0
  78. package/js/utilities/_cookie.js +98 -0
  79. package/js/utilities/_error.js +80 -0
  80. package/js/utilities/_form-helper.js +101 -0
  81. package/package.json +42 -0
  82. package/scss/_badge.scss +37 -0
  83. package/scss/_button.scss +34 -0
  84. package/scss/_card.scss +122 -0
  85. package/scss/_dialog.scss +116 -0
  86. package/scss/_disclosure.scss +101 -0
  87. package/scss/_dropdown.scss +68 -0
  88. package/scss/_form.scss +197 -0
  89. package/scss/_grid.scss +40 -0
  90. package/scss/_group.scss +57 -0
  91. package/scss/_list.scss +18 -0
  92. package/scss/_loading.scss +49 -0
  93. package/scss/_media.scss +37 -0
  94. package/scss/_nav.scss +72 -0
  95. package/scss/_progress.scss +40 -0
  96. package/scss/_slider.scss +35 -0
  97. package/scss/_table.scss +36 -0
  98. package/scss/utilities/_mixin.scss +322 -0
  99. package/scss/utilities/_reset.scss +145 -0
  100. package/scss/utilities/_typography.scss +107 -0
  101. package/scss/vanilla-frontend.scss +23 -0
  102. package/scss/variables/_root.scss +70 -0
  103. package/scss/variables/_setting.scss +63 -0
  104. package/vitest.config.js +7 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ------------------------------------------------------------------
3
+ * Dropdown
4
+ * ------------------------------------------------------------------
5
+ * This class enable the functionality to un/collapse some element by another
6
+ * This component is a simpler version of the Toggle
7
+ *
8
+ * @author Natacha Herth
9
+ * @version 0.0.1
10
+ * @copyright Natacha Herth, design & web development
11
+ *
12
+ */
13
+
14
+ import BaseComponent from './utilities/_base-component'
15
+ import ErrorMessage from './utilities/_error'
16
+
17
+ export default class Dropdown extends BaseComponent {
18
+
19
+ static OPTIONS = {
20
+ closable: true
21
+ }
22
+
23
+ /**
24
+ * Creates an instance
25
+ *
26
+ * @param {HTMLElement} el - The HTML element
27
+ * @param {object} options - The custom options
28
+ * @constructor
29
+ */
30
+ constructor(el, options = {}) {
31
+
32
+ // Check for errors
33
+ if (options.closable && typeof options.closable !== 'boolean') throw new Error(ErrorMessage.typeOf('options.closable', 'boolean'))
34
+
35
+ // Run the SUPER constructor from BaseComponent
36
+ super(el, options)
37
+
38
+ // Define the properties
39
+ this._button = this._element.querySelector('[aria-controls]')
40
+ this._content = document.getElementById(this._button.getAttribute('aria-controls'))
41
+
42
+ // Init the event listener
43
+ this.#init()
44
+
45
+ }
46
+
47
+ /**
48
+ * Init the event listener
49
+ *
50
+ * @private
51
+ */
52
+ #init() {
53
+
54
+ // Toggle the content on click on the button
55
+ this._button.addEventListener('click', () => this.#toggle())
56
+
57
+ // Close the content is click outside
58
+ if (this._options.closable) {
59
+ this._content.addEventListener('blur', (e) => {
60
+ if (e.relatedTarget !== this._button) this.#toggle()
61
+ })
62
+ }
63
+
64
+ }
65
+
66
+ /**
67
+ * Define the value
68
+ *
69
+ * @return {string}
70
+ * @private
71
+ */
72
+ get value() {
73
+ return this._button.getAttribute('aria-pressed') === 'true'
74
+ }
75
+
76
+ /**
77
+ * Toggle the [hidden] and [aria-expanded] attributes
78
+ *
79
+ * @private
80
+ */
81
+ #toggle() {
82
+
83
+ // Define new value
84
+ const value = !this.value
85
+
86
+ // Change the [aria-pressed] and [aria-expanded] attribute on button
87
+ this._button.setAttribute('aria-pressed', value)
88
+ this._button.setAttribute('aria-expanded', value)
89
+
90
+ // Change visibility of the content
91
+ this._content.hidden = !value
92
+
93
+ // Put the focus on the content
94
+ if (value) this._content.focus()
95
+
96
+ // Emmit event
97
+ this.emmitEvent('changed', { isOpen: !this._content.hidden })
98
+
99
+ }
100
+
101
+ }
package/js/_scroll.js ADDED
@@ -0,0 +1,184 @@
1
+ /**
2
+ * ------------------------------------------------------------------
3
+ * Scroll
4
+ * ------------------------------------------------------------------
5
+ * This script enable an HTML element to scroll to a position and to spy some buttons
6
+ *
7
+ * @author Natacha Herth
8
+ * @version 0.0.1
9
+ * @copyright Natacha Herth, design & web development
10
+ */
11
+
12
+ import BaseComponent from "./utilities/_base-component"
13
+ import ErrorMessage from "./utilities/_error"
14
+
15
+ export default class Scroll extends BaseComponent {
16
+
17
+ static OPTIONS = {
18
+ behavior: 'smooth', // Can be auto, smooth or instant
19
+ navigation: null, // Id of the navigation
20
+ gaps: {
21
+ top: 100,
22
+ bottom: 100,
23
+ spy: 100
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Creates an instance
29
+ *
30
+ * @param {HTMLElement} el - The HTML element
31
+ * @param {object} options - The custom options
32
+ * @constructor
33
+ */
34
+ constructor(el, options = {}) {
35
+
36
+ // Check for errors
37
+ if (options.gaps) {
38
+ if (!(options.gaps instanceof Object)) throw new Error(ErrorMessage.instanceOf('options.gaps', 'Object'))
39
+ if (options.gaps.top && typeof options.gaps.top !== 'number') throw new Error(ErrorMessage.typeOf('options.gaps.top', 'number'))
40
+ if (options.gaps.bottom && typeof options.gaps.bottom !== 'number') throw new Error(ErrorMessage.typeOf('options.gaps.bottom', 'number'))
41
+ if (options.gaps.spy && typeof options.gaps.spy !== 'number') throw new Error(ErrorMessage.typeOf('options.gaps.spy', 'number'))
42
+ }
43
+ if (options.behavior && !['auto', 'smooth', 'instant'].includes(options.behavior)) throw new Error(ErrorMessage.enumOf('options.behavior', 'auto|smooth|instant'))
44
+ if (options.navigation && !document.getElementById(options.navigation)) throw new Error(ErrorMessage.existById('element', options.navigation))
45
+
46
+ // Run the SUPER constructor from BaseComponent
47
+ super(el, options)
48
+
49
+ // Reduce animation
50
+ const isReduced = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true
51
+ if (isReduced) this._options.behavior = 'instant'
52
+
53
+ // Define the properties
54
+ this._buttons = {
55
+ top: document.querySelector(`[data-scroll-top="${this._element.id}"]`) ?? null,
56
+ bottom: document.querySelector(`[data-scroll-bottom="${this._element.id}"]`) ?? null
57
+ }
58
+
59
+ const links = [...document.querySelectorAll(`#${this._options.navigation} a[href^="#"]`)].filter((a) => document.getElementById(a.getAttribute('href').substring(1)))
60
+ this._spy = {
61
+ enable: this._options.navigation && links.length ? true : false,
62
+ links: links,
63
+ targets: links.map(a => document.getElementById(a.getAttribute('href').substring(1))),
64
+ current: 0
65
+ }
66
+
67
+ // Init the event listener
68
+ this.#init()
69
+
70
+ }
71
+
72
+ /**
73
+ * Get the height of the element
74
+ * It's a get method because the height can change by screen size, CSS and more !
75
+ *
76
+ * @returns {int}
77
+ */
78
+ get _height() {
79
+ return this._element.scrollHeight - this._element.clientHeight
80
+ }
81
+
82
+ /**
83
+ * Init the event listener
84
+ *
85
+ * @private
86
+ */
87
+ #init() {
88
+
89
+ // SCROLL
90
+ if (this._element.nodeName === 'HTML') {
91
+ window.onscroll = () => this.#scroll()
92
+ } else {
93
+ this._element.onscroll = () => this.#scroll()
94
+ }
95
+
96
+ // CLICK on links
97
+ [...this._spy.links].forEach((link, index) => link.addEventListener('click', (e) => {
98
+ e.preventDefault()
99
+ this.goTo(this._spy.targets[index].offsetTop)
100
+ }))
101
+
102
+ // CLICK on buttons
103
+ if (this._buttons.top) this._buttons.top.addEventListener('click', () => this.scrollTop())
104
+ if (this._buttons.bottom) this._buttons.bottom.addEventListener('click', () => this.scrollBottom())
105
+
106
+ }
107
+
108
+ /**
109
+ * Define what to do on scroll
110
+ *
111
+ * @private
112
+ */
113
+ #scroll() {
114
+
115
+ // If Spy, run the method #spy
116
+ if (this._spy.enable) this.#spy()
117
+
118
+ // Toggle the top button
119
+ if (this._buttons.top) this._buttons.top.hidden = this._element.scrollTop <= this._options.gaps.top
120
+
121
+ // Toggle the bottom button
122
+ if (this._buttons.bottom) this._buttons.bottom.hidden = this._element.scrollTop >= this._height - this._options.gaps.bottom
123
+
124
+ }
125
+
126
+ /**
127
+ * Spy if current section changed, and toggle the [aria-current] attribute on links
128
+ *
129
+ * @private
130
+ */
131
+ #spy() {
132
+
133
+ const current = this._spy.targets.length - [...this._spy.targets].reverse().findIndex((target) => this._element.scrollTop >= target.offsetTop - this._options.gaps.spy) - 1
134
+
135
+ if (current !== this._spy.current) {
136
+
137
+ // Remove [aria-current] on each links
138
+ this._spy.links.forEach(link => link.removeAttribute('aria-current'))
139
+
140
+ // Define current section
141
+ this._spy.current = current
142
+
143
+ // Set [aria-current] on link
144
+ if (this._spy.links[this._spy.current]) this._spy.links[this._spy.current].setAttribute('aria-current', 'location')
145
+
146
+ }
147
+
148
+ }
149
+
150
+ /**
151
+ * Scroll to a certain distance
152
+ *
153
+ * @param {number} distance
154
+ */
155
+ goTo(distance) {
156
+
157
+ // Check for errors
158
+ if (typeof distance !== 'number') throw new Error(ErrorMessage.typeOf('distance', 'number'))
159
+
160
+ // Scroll to
161
+ this._element.scrollTo({
162
+ top: distance,
163
+ behavior: this._options.behavior
164
+ })
165
+
166
+ }
167
+
168
+ /**
169
+ * Scroll to the top
170
+ *
171
+ */
172
+ scrollTop() {
173
+ this.goTo(0)
174
+ }
175
+
176
+ /**
177
+ * Scroll to the bottom
178
+ *
179
+ */
180
+ scrollBottom() {
181
+ this.goTo(this._height)
182
+ }
183
+
184
+ }
package/js/_sidebar.js ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * ------------------------------------------------------------------
3
+ * Sidebar
4
+ * ------------------------------------------------------------------
5
+ * This class enable the functionality to toggles multiple tabpanel
6
+ *
7
+ * @author Natacha Herth
8
+ * @version 0.0.1
9
+ * @copyright Natacha Herth, design & web development
10
+ *
11
+ */
12
+
13
+ import BaseComponent from './utilities/_base-component'
14
+ import ErrorMessage from "./utilities/_error"
15
+
16
+ export default class Sidebar extends BaseComponent {
17
+
18
+ static OPTIONS = {
19
+ breakpoint: 960,
20
+ }
21
+
22
+ /**
23
+ * Creates an instance
24
+ *
25
+ * @param {HTMLElement} el - The HTML element
26
+ * @param {object} options - The custom options
27
+ * @constructor
28
+ */
29
+ constructor(el, options = {}) {
30
+
31
+ if (options.breakpoint && typeof options.breakpoint !== 'number') throw new Error(ErrorMessage.typeOf('options.breakpoint', 'number'))
32
+
33
+ // Run the SUPER constructor from BaseComponent
34
+ super(el, options)
35
+
36
+ // Define the properties
37
+ this._buttons = document.querySelectorAll(`[aria-controls=${this._element.id}]`) ?? []
38
+
39
+ this._backdrop = document.getElementById('backdrop')
40
+
41
+ this._timeout = false
42
+
43
+ this._isOpen = !this._element.hidden
44
+
45
+ // Init the event listener
46
+ this.#init()
47
+
48
+ }
49
+
50
+ /**
51
+ * Init the event listener
52
+ *
53
+ * @private
54
+ */
55
+ #init() {
56
+
57
+ // On load check if need to open/close the sidebar
58
+ if (window.innerWidth > this._options.breakpoint && !this._isOpen) this.#toggle()
59
+
60
+ // On resize check if need to open/close the sidebar
61
+ window.onresize = () => {
62
+ clearTimeout(this._timeout)
63
+ this._timeout = setTimeout(() => {
64
+ if ((window.innerWidth <= this._options.breakpoint && this._isOpen) || (window.innerWidth > this._options.breakpoint && !this._isOpen)) this.#toggle()
65
+ }, 250)
66
+ }
67
+
68
+ // On click on the button toggle the sidebar
69
+ this._buttons.forEach((button) => button.addEventListener('click', () => this.#toggle()))
70
+
71
+ // On click on the backdrop, close the sidebar
72
+ this._backdrop.addEventListener('click', () => this.#toggle())
73
+
74
+ }
75
+
76
+ /**
77
+ * Toggle the sidebar
78
+ *
79
+ * @private
80
+ */
81
+ #toggle() {
82
+
83
+ // Change the state
84
+ this._isOpen = !this._isOpen
85
+
86
+ // Change the [aria-pressed] & [aria-expanded] attribute on the <button>
87
+ this._buttons.forEach((button) => {
88
+ button.setAttribute('aria-pressed', this._isOpen)
89
+ button.setAttribute('aria-expanded', this._isOpen)
90
+ })
91
+
92
+ // Change the [hidden] attribute on the sidebar
93
+ this._element.hidden = !this._isOpen
94
+
95
+ }
96
+
97
+ }
package/js/_slider.js ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * ------------------------------------------------------------------
3
+ * Slider
4
+ * ------------------------------------------------------------------
5
+ * This class enable the functionality to make an element slider
6
+ *
7
+ * @author Natacha Herth
8
+ * @version 0.0.1
9
+ * @copyright Natacha Herth, design & web development
10
+ *
11
+ * * keep check on the scrollend event: https://caniuse.com/?search=scrollend
12
+ */
13
+
14
+ import BaseComponent from './utilities/_base-component'
15
+ import ErrorMessage from "./utilities/_error"
16
+
17
+ export default class Slider extends BaseComponent {
18
+
19
+ static OPTIONS = {
20
+ behavior: 'smooth', // Can be auto, smooth or instant
21
+ loop: false,
22
+ autoplay: false
23
+ }
24
+
25
+ /**
26
+ * Creates an instance
27
+ *
28
+ * @param {HTMLElement} el - The HTML element
29
+ * @param {object} options - The custom options
30
+ * @constructor
31
+ */
32
+ constructor(el, options = {}) {
33
+
34
+ // Check for errors
35
+ if (options.behavior && !['auto', 'smooth', 'instant'].includes(options.behavior)) throw new Error(ErrorMessage.enumOf('options.behavior', 'auto|smooth|instant'))
36
+ if (options.loop && typeof options.loop !== 'boolean') throw new Error(ErrorMessage.typeOf('options.loop', 'boolean'))
37
+ if (options.autoplay && (typeof options.autoplay !== 'boolean' && typeof options.autoplay !== 'number')) throw new Error(ErrorMessage.typeOf('options.autoplay', 'boolean|number'))
38
+
39
+ // Run the SUPER constructor from BaseComponent
40
+ super(el, options)
41
+
42
+ // Reduce animation
43
+ const isReduced = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true
44
+ if (isReduced) this._options.behavior = 'instant'
45
+
46
+ // If loop, clone first and last slides (needed in case or scrolling/grabbing and slide effect)
47
+ if (this._options.loop) {
48
+
49
+ const cloneFirst = this._element.firstElementChild.cloneNode(true)
50
+ const cloneLast = this._element.lastElementChild.cloneNode(true)
51
+
52
+ cloneFirst.removeAttribute('id')
53
+ cloneFirst.removeAttribute('aria-hidden')
54
+ cloneFirst.removeAttribute('role')
55
+ this._element.append(cloneFirst)
56
+
57
+ cloneLast.removeAttribute('id')
58
+ cloneLast.removeAttribute('aria-hidden')
59
+ cloneLast.removeAttribute('role')
60
+ this._element.prepend(cloneLast)
61
+
62
+ }
63
+
64
+ // Define the properties (don't use .children because of clones in loop)
65
+ this._slides = this._element.querySelectorAll('[role=tabpanel]')
66
+
67
+ this._buttons = {
68
+ prev: document.querySelector(`[aria-controls=${this._element.id}][data-slider-prev]`),
69
+ next: document.querySelector(`[aria-controls=${this._element.id}][data-slider-next]`),
70
+ tabs: document.querySelectorAll(`[aria-controls=${this._element.id}][role=tablist] [role=tab]`)
71
+ }
72
+
73
+ this._current = 0
74
+
75
+ this._interval = null
76
+
77
+ this.#init()
78
+
79
+ }
80
+
81
+ /**
82
+ * Init the event listener
83
+ *
84
+ * @private
85
+ */
86
+ #init() {
87
+
88
+ // AUTOPLAY
89
+ if (this._options.autoplay) this._interval = setInterval(() => this.next(), this._options.autoplay)
90
+
91
+ // CLICK next
92
+ if (this._buttons.next) this._buttons.next.addEventListener('click', () => this.next())
93
+
94
+ // CLICK prev
95
+ if (this._buttons.prev) this._buttons.prev.addEventListener('click', () => this.prev())
96
+
97
+ // CLICK tabs
98
+ if (this._buttons.tabs.length) this._buttons.tabs.forEach((tab, index) => tab.addEventListener('click', () => this.goTo(index)))
99
+
100
+ // CLICK scroll
101
+ this._element.addEventListener('scroll', () => {
102
+
103
+ // Clear timeout to avoid multiple request
104
+ clearTimeout(this._element.scrollTimeout)
105
+
106
+ // Run event before changed
107
+ this.emmitEvent('changing', { current: this._current })
108
+
109
+ // Set the timeout
110
+ this._element.scrollTimeout = setTimeout(() => {
111
+
112
+ // Check loop on scrolling
113
+ if (this._options.loop) this.#loop()
114
+
115
+ // Toggle the attributes
116
+ this.#change()
117
+
118
+ }, 150)
119
+
120
+ // Make sure to reset the interval to keep the slide duration when manually change
121
+ if (this._options.autoplay) {
122
+ clearInterval(this._interval)
123
+ this._interval = setInterval(() => this.next(), this._options.autoplay)
124
+ }
125
+
126
+ })
127
+
128
+ }
129
+
130
+ /**
131
+ * Change the current slide and toggle the attributes
132
+ *
133
+ * @private
134
+ */
135
+ #change() {
136
+
137
+ // Define the index
138
+ const index = this._slides.length - [...this._slides].reverse().findIndex((slide) => this._element.scrollLeft >= slide.offsetLeft) - 1
139
+
140
+ // Check if index change to avoid multiple call
141
+ if (index !== this._current) {
142
+
143
+ // Define the new current
144
+ this._current = index
145
+
146
+ // Change the [aria-selected] attribute on tabs
147
+ this._buttons.tabs.forEach((tab, index) => tab.setAttribute('aria-selected', index === this._current))
148
+
149
+ // Change the [aria-hidden] attribute on slide
150
+ this._slides.forEach((slide, index) => slide.setAttribute('aria-hidden', index !== this._current))
151
+
152
+ // Toggle [disabled] attribute on the next button
153
+ if (this._buttons.next && !this._options.loop) this._buttons.next.disabled = this._current === this._slides.length - 1
154
+
155
+ // Toggle [disabled] attribute on the prev button
156
+ if (this._buttons.prev && !this._options.loop) this._buttons.prev.disabled = this._current === 0
157
+
158
+ // Run event after changed
159
+ this.emmitEvent('changed', { current: this._current })
160
+
161
+ }
162
+
163
+ }
164
+
165
+ /**
166
+ * Loop scrolling when reach the start/end of the slide
167
+ *
168
+ * @returns
169
+ * @private
170
+ */
171
+ #loop() {
172
+
173
+ // Going to much left => go to the last slide
174
+ if (this._element.scrollLeft <= this._element.offsetWidth) {
175
+ this._element.scrollTo(this._slides[this._slides.length - 1].offsetLeft, 0)
176
+ return
177
+ }
178
+
179
+ // Going to much right => go to the first slide
180
+ if (this._element.scrollWidth - this._element.scrollLeft <= this._element.offsetWidth) {
181
+ this._element.scrollTo(this._slides[0].offsetLeft, 0)
182
+ return
183
+ }
184
+
185
+ }
186
+
187
+ /**
188
+ * Go to a slide by index
189
+ *
190
+ * @param {int} index - The index number of the slide
191
+ */
192
+ goTo(index) {
193
+
194
+ // Check for errors
195
+ if (typeof index !== 'number') throw new Error(ErrorMessage.typeOf('index', 'number'))
196
+
197
+ // Define the offset, if loop get the first or last slide offset
198
+ let offset
199
+
200
+ if (this._options.loop && (index < 0 || index > this._slides.length - 1)) {
201
+ offset = this._element.children[index + 1].offsetLeft
202
+ } else {
203
+ index = index < 0 ? this._slides.length - 1 : index % this._slides.length
204
+ offset = this._slides[index].offsetLeft
205
+ }
206
+
207
+ // Scroll to position
208
+ this._element.scrollTo({
209
+ left: offset,
210
+ behavior: this._options.behavior
211
+ })
212
+
213
+ }
214
+
215
+ /**
216
+ * Go to the next slide
217
+ *
218
+ */
219
+ next() {
220
+
221
+ // Define the new _current
222
+ const index = this._current + 1
223
+
224
+ // If last item => return
225
+ if (!this._options.loop && !this._options.autoplay && index === this._slides.length) return
226
+
227
+ // Run method goTo()
228
+ this.goTo(index)
229
+
230
+ }
231
+
232
+ /**
233
+ * Go to the previous slide
234
+ *
235
+ */
236
+ prev() {
237
+
238
+ // Define the new _current
239
+ const index = this._current - 1
240
+
241
+ // If first item => return
242
+ if (!this._options.loop && index < 0) return
243
+
244
+ // Run method goTo()
245
+ this.goTo(index)
246
+
247
+ }
248
+
249
+ }