@kupola/kupola 1.2.0 → 1.3.1

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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +330 -286
  3. package/adapters/axios.d.ts +34 -0
  4. package/adapters/axios.js +122 -0
  5. package/adapters/navios-http.d.ts +110 -0
  6. package/adapters/navios-http.js +151 -0
  7. package/dist/css/accessibility.css +119 -0
  8. package/dist/css/animations.css +224 -0
  9. package/dist/css/brand-themes.css +300 -0
  10. package/dist/css/colors_and_type.css +441 -0
  11. package/dist/css/components-ext.css +4165 -0
  12. package/dist/css/components.css +1483 -0
  13. package/dist/css/responsive.css +697 -0
  14. package/dist/css/scaffold.css +145 -0
  15. package/dist/css/states.css +316 -0
  16. package/dist/css/theme-dark.css +296 -0
  17. package/dist/css/theme-light.css +296 -0
  18. package/dist/css/utilities.css +171 -0
  19. package/dist/kupola.cjs.js +219 -161
  20. package/dist/kupola.cjs.js.map +1 -1
  21. package/dist/kupola.esm.js +6643 -5709
  22. package/dist/kupola.esm.js.map +1 -1
  23. package/dist/kupola.umd.js +219 -161
  24. package/dist/kupola.umd.js.map +1 -1
  25. package/dist/plugins/vite-plugin-kupola.js +120 -0
  26. package/dist/types/kupola.d.ts +421 -323
  27. package/js/calendar.js +334 -25
  28. package/js/carousel.js +182 -48
  29. package/js/collapse.js +148 -34
  30. package/js/color-picker.js +416 -108
  31. package/js/component.js +8 -19
  32. package/js/countdown.js +9 -8
  33. package/js/data-bind.js +73 -16
  34. package/js/datepicker.js +488 -110
  35. package/js/depends.js +710 -0
  36. package/js/dialog.js +4 -2
  37. package/js/drawer.js +172 -8
  38. package/js/dropdown.js +272 -17
  39. package/js/dynamic-tags.js +156 -40
  40. package/js/fileupload.js +9 -8
  41. package/js/form.js +280 -254
  42. package/js/global-events.js +281 -188
  43. package/js/heatmap.js +10 -7
  44. package/js/i18n.js +18 -10
  45. package/js/icons.js +141 -161
  46. package/js/image-preview.js +146 -2
  47. package/js/initializer.js +113 -71
  48. package/js/kupola-core.js +123 -45
  49. package/js/kupola-lifecycle.js +13 -11
  50. package/js/message.js +8 -1
  51. package/js/modal.js +207 -59
  52. package/js/notification.js +8 -1
  53. package/js/numberinput.js +9 -8
  54. package/js/pagination.js +263 -0
  55. package/js/registry.js +29 -12
  56. package/js/select.js +482 -27
  57. package/js/slide-captcha.js +11 -2
  58. package/js/slider.js +442 -25
  59. package/js/statcard.js +9 -7
  60. package/js/table.js +1210 -0
  61. package/js/tag.js +268 -14
  62. package/js/theme.js +14 -43
  63. package/js/timepicker.js +335 -66
  64. package/js/tooltip.js +317 -86
  65. package/js/utils.js +6 -2
  66. package/js/validation.js +6 -2
  67. package/js/virtual-list.js +11 -7
  68. package/js/web-components.js +288 -0
  69. package/package.json +77 -67
  70. package/plugins/vite-plugin-kupola.js +120 -0
  71. package/types/kupola.d.ts +421 -323
  72. package/CHANGELOG.md +0 -130
  73. package/INTEGRATION.md +0 -440
  74. package/PROJECT_SUMMARY.md +0 -312
  75. package/SKILL.md +0 -572
  76. package/dist/utils/utils/Kupola.cs +0 -77
  77. package/dist/utils/utils/Kupola.java +0 -104
  78. package/dist/utils/utils/kupola.go +0 -120
  79. package/dist/utils/utils/kupola.js +0 -63
  80. package/dist/utils/utils/kupola.py +0 -1392
  81. package/dist/utils/utils/kupola.rb +0 -69
  82. package/js/composition-api.js +0 -458
  83. package/js/error-handler.js +0 -181
  84. package/js/http.js +0 -419
  85. package/js/kupola-devtools.js +0 -598
  86. package/js/performance.js +0 -250
  87. package/js/router.js +0 -396
  88. package/js/security.js +0 -189
  89. package/js/test-utils.js +0 -251
  90. package/templates/base.html +0 -30
  91. package/templates/base_dashboard.html +0 -99
  92. package/utils/Kupola.cs +0 -77
  93. package/utils/Kupola.java +0 -104
  94. package/utils/kupola.go +0 -120
  95. package/utils/kupola.js +0 -63
  96. package/utils/kupola.py +0 -1392
  97. package/utils/kupola.rb +0 -69
package/js/carousel.js CHANGED
@@ -1,5 +1,5 @@
1
1
  class Carousel {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.track = element.querySelector('.ds-carousel__track');
5
5
  this.items = element.querySelectorAll('.ds-carousel__item');
@@ -8,13 +8,34 @@ class Carousel {
8
8
  this.indicators = element.querySelectorAll('.ds-carousel__indicator');
9
9
  this.autoBtn = element.querySelector('.ds-carousel__auto');
10
10
 
11
+ // Options
12
+ this.mode = options.mode || element.getAttribute('data-carousel-mode') || 'slide'; // slide | fade
13
+ this.vertical = options.vertical || element.hasAttribute('data-carousel-vertical');
14
+ this.autoPlay = options.autoPlay !== false;
15
+ this.interval = options.interval || parseInt(element.getAttribute('data-carousel-interval')) || 3000;
16
+ this.transitionDuration = options.transitionDuration || parseInt(element.getAttribute('data-carousel-duration')) || 500;
17
+ this.loop = options.loop !== false;
18
+ this.pauseOnHover = options.pauseOnHover !== false;
19
+ this.swipe = options.swipe !== false;
20
+ this.swipeThreshold = options.swipeThreshold || 50;
21
+ this.keyboardNav = options.keyboardNav || element.hasAttribute('data-carousel-keyboard');
22
+ this.onChange = options.onChange || null;
23
+
11
24
  this.currentIndex = 0;
12
25
  this.totalItems = this.items.length;
13
26
  this.autoPlayTimer = null;
14
27
  this.isAutoPlaying = false;
28
+ this.isTransitioning = false;
29
+
30
+ // Touch state
31
+ this.touchStartX = 0;
32
+ this.touchStartY = 0;
33
+ this.touchDeltaX = 0;
34
+ this.touchDeltaY = 0;
35
+ this.isSwiping = false;
15
36
 
16
- this._mouseEnterHandler = () => this.stopAutoPlay();
17
- this._mouseLeaveHandler = () => { if (this.isAutoPlaying) this.startAutoPlay(); };
37
+ this._mouseEnterHandler = () => { if (this.pauseOnHover) this.stopAutoPlay(); };
38
+ this._mouseLeaveHandler = () => { if (this.pauseOnHover && this.autoPlay) this.startAutoPlay(); };
18
39
 
19
40
  this.init();
20
41
  }
@@ -25,12 +46,8 @@ class Carousel {
25
46
  this._autoClickHandler = () => this.toggleAutoPlay();
26
47
  this._indicatorClickHandlers = [];
27
48
 
28
- if (this.prevBtn) {
29
- this.prevBtn.addEventListener('click', this._prevClickHandler);
30
- }
31
- if (this.nextBtn) {
32
- this.nextBtn.addEventListener('click', this._nextClickHandler);
33
- }
49
+ if (this.prevBtn) this.prevBtn.addEventListener('click', this._prevClickHandler);
50
+ if (this.nextBtn) this.nextBtn.addEventListener('click', this._nextClickHandler);
34
51
 
35
52
  this.indicators.forEach((indicator, index) => {
36
53
  const handler = () => this.goTo(index);
@@ -38,54 +55,125 @@ class Carousel {
38
55
  indicator.addEventListener('click', handler);
39
56
  });
40
57
 
41
- if (this.autoBtn) {
42
- this.autoBtn.addEventListener('click', this._autoClickHandler);
58
+ if (this.autoBtn) this.autoBtn.addEventListener('click', this._autoClickHandler);
59
+
60
+ // Touch/swipe support
61
+ if (this.swipe) {
62
+ this._touchStartHandler = (e) => this._handleTouchStart(e);
63
+ this._touchMoveHandler = (e) => this._handleTouchMove(e);
64
+ this._touchEndHandler = () => this._handleTouchEnd();
65
+
66
+ this.element.addEventListener('touchstart', this._touchStartHandler, { passive: true });
67
+ this.element.addEventListener('touchmove', this._touchMoveHandler, { passive: false });
68
+ this.element.addEventListener('touchend', this._touchEndHandler);
69
+ }
70
+
71
+ // Keyboard navigation
72
+ if (this.keyboardNav) {
73
+ this._keydownHandler = (e) => {
74
+ if (!this.element.contains(document.activeElement)) return;
75
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
76
+ e.preventDefault();
77
+ this.prev();
78
+ } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
79
+ e.preventDefault();
80
+ this.next();
81
+ }
82
+ };
83
+ this.element.addEventListener('keydown', this._keydownHandler);
84
+ }
85
+
86
+ // Apply mode class
87
+ if (this.mode === 'fade') {
88
+ this.element.classList.add('ds-carousel--fade');
89
+ }
90
+ if (this.vertical) {
91
+ this.element.classList.add('ds-carousel--vertical');
92
+ }
93
+
94
+ // Set transition duration
95
+ if (this.track) {
96
+ this.track.style.transitionDuration = this.transitionDuration + 'ms';
43
97
  }
44
98
 
45
99
  this.updateIndicators();
46
- this.startAutoPlay();
100
+ if (this.autoPlay) this.startAutoPlay();
47
101
 
48
102
  this.element.addEventListener('mouseenter', this._mouseEnterHandler);
49
103
  this.element.addEventListener('mouseleave', this._mouseLeaveHandler);
50
104
  }
51
105
 
52
106
  goTo(index) {
107
+ if (this.isTransitioning) return;
53
108
  if (index < 0 || index >= this.totalItems) return;
54
109
 
110
+ this.isTransitioning = true;
111
+ const prevIndex = this.currentIndex;
55
112
  this.currentIndex = index;
56
- const offset = -index * 100;
57
- this.track.style.transform = `translateX(${offset}%)`;
113
+
114
+ if (this.mode === 'fade') {
115
+ this.items.forEach((item, i) => {
116
+ item.style.opacity = i === index ? '1' : '0';
117
+ item.style.zIndex = i === index ? '1' : '0';
118
+ });
119
+ } else if (this.vertical) {
120
+ const offset = -index * 100;
121
+ this.track.style.transform = `translateY(${offset}%)`;
122
+ } else {
123
+ const offset = -index * 100;
124
+ this.track.style.transform = `translateX(${offset}%)`;
125
+ }
126
+
58
127
  this.updateIndicators();
128
+
129
+ setTimeout(() => {
130
+ this.isTransitioning = false;
131
+ }, this.transitionDuration);
132
+
133
+ if (this.onChange) {
134
+ this.onChange({ index, prevIndex, total: this.totalItems });
135
+ }
136
+
137
+ this.element.dispatchEvent(new CustomEvent('kupola:carousel-change', {
138
+ detail: { index, prevIndex, total: this.totalItems },
139
+ bubbles: true
140
+ }));
59
141
  }
60
142
 
61
143
  prev() {
62
- const newIndex = this.currentIndex > 0 ? this.currentIndex - 1 : this.totalItems - 1;
63
- this.goTo(newIndex);
144
+ if (this.currentIndex > 0) {
145
+ this.goTo(this.currentIndex - 1);
146
+ } else if (this.loop) {
147
+ this.goTo(this.totalItems - 1);
148
+ }
64
149
  }
65
150
 
66
151
  next() {
67
- const newIndex = this.currentIndex < this.totalItems - 1 ? this.currentIndex + 1 : 0;
68
- this.goTo(newIndex);
152
+ if (this.currentIndex < this.totalItems - 1) {
153
+ this.goTo(this.currentIndex + 1);
154
+ } else if (this.loop) {
155
+ this.goTo(0);
156
+ }
69
157
  }
70
158
 
71
159
  updateIndicators() {
72
160
  this.indicators.forEach((indicator, index) => {
73
- if (index === this.currentIndex) {
74
- indicator.classList.add('is-active');
75
- } else {
76
- indicator.classList.remove('is-active');
77
- }
161
+ indicator.classList.toggle('is-active', index === this.currentIndex);
78
162
  });
163
+
164
+ // Update prev/next button states for non-loop mode
165
+ if (!this.loop) {
166
+ if (this.prevBtn) this.prevBtn.disabled = this.currentIndex === 0;
167
+ if (this.nextBtn) this.nextBtn.disabled = this.currentIndex === this.totalItems - 1;
168
+ }
79
169
  }
80
170
 
81
171
  startAutoPlay() {
82
172
  if (this.totalItems <= 1) return;
83
173
  this.stopAutoPlay();
84
174
  this.isAutoPlaying = true;
85
- if (this.autoBtn) {
86
- this.autoBtn.classList.add('is-active');
87
- }
88
- this.autoPlayTimer = setInterval(() => this.next(), 3000);
175
+ if (this.autoBtn) this.autoBtn.classList.add('is-active');
176
+ this.autoPlayTimer = setInterval(() => this.next(), this.interval);
89
177
  }
90
178
 
91
179
  stopAutoPlay() {
@@ -93,42 +181,87 @@ class Carousel {
93
181
  clearInterval(this.autoPlayTimer);
94
182
  this.autoPlayTimer = null;
95
183
  }
96
- if (this.autoBtn) {
97
- this.autoBtn.classList.remove('is-active');
98
- }
184
+ this.isAutoPlaying = false;
185
+ if (this.autoBtn) this.autoBtn.classList.remove('is-active');
99
186
  }
100
187
 
101
188
  toggleAutoPlay() {
102
189
  if (this.isAutoPlaying) {
103
- this.isAutoPlaying = false;
104
190
  this.stopAutoPlay();
105
191
  } else {
106
192
  this.startAutoPlay();
107
193
  }
108
194
  }
109
195
 
196
+ // Touch handlers
197
+ _handleTouchStart(e) {
198
+ this.touchStartX = e.touches[0].clientX;
199
+ this.touchStartY = e.touches[0].clientY;
200
+ this.touchDeltaX = 0;
201
+ this.touchDeltaY = 0;
202
+ this.isSwiping = true;
203
+
204
+ if (this.isAutoPlaying) {
205
+ this.stopAutoPlay();
206
+ this._wasAutoPlaying = true;
207
+ }
208
+ }
209
+
210
+ _handleTouchMove(e) {
211
+ if (!this.isSwiping) return;
212
+
213
+ this.touchDeltaX = e.touches[0].clientX - this.touchStartX;
214
+ this.touchDeltaY = e.touches[0].clientY - this.touchStartY;
215
+
216
+ // Prevent page scroll if swiping horizontally
217
+ const absDx = Math.abs(this.touchDeltaX);
218
+ const absDy = Math.abs(this.touchDeltaY);
219
+
220
+ if (absDx > absDy && absDx > 10) {
221
+ e.preventDefault();
222
+ }
223
+ }
224
+
225
+ _handleTouchEnd() {
226
+ if (!this.isSwiping) return;
227
+ this.isSwiping = false;
228
+
229
+ const absDeltaX = Math.abs(this.touchDeltaX);
230
+ const absDeltaY = Math.abs(this.touchDeltaY);
231
+
232
+ if (absDeltaX > this.swipeThreshold && absDeltaX > absDeltaY) {
233
+ if (this.touchDeltaX > 0) {
234
+ this.prev();
235
+ } else {
236
+ this.next();
237
+ }
238
+ }
239
+
240
+ if (this._wasAutoPlaying) {
241
+ this.startAutoPlay();
242
+ this._wasAutoPlaying = false;
243
+ }
244
+ }
245
+
110
246
  destroy() {
111
247
  this.stopAutoPlay();
112
248
  this.element.removeEventListener('mouseenter', this._mouseEnterHandler);
113
249
  this.element.removeEventListener('mouseleave', this._mouseLeaveHandler);
114
250
 
115
- if (this.prevBtn && this._prevClickHandler) {
116
- this.prevBtn.removeEventListener('click', this._prevClickHandler);
117
- }
118
- if (this.nextBtn && this._nextClickHandler) {
119
- this.nextBtn.removeEventListener('click', this._nextClickHandler);
120
- }
121
- if (this.autoBtn && this._autoClickHandler) {
122
- this.autoBtn.removeEventListener('click', this._autoClickHandler);
123
- }
251
+ if (this.prevBtn && this._prevClickHandler) this.prevBtn.removeEventListener('click', this._prevClickHandler);
252
+ if (this.nextBtn && this._nextClickHandler) this.nextBtn.removeEventListener('click', this._nextClickHandler);
253
+ if (this.autoBtn && this._autoClickHandler) this.autoBtn.removeEventListener('click', this._autoClickHandler);
124
254
 
125
255
  this.indicators.forEach((indicator, index) => {
126
256
  const handler = this._indicatorClickHandlers[index];
127
- if (handler) {
128
- indicator.removeEventListener('click', handler);
129
- }
257
+ if (handler) indicator.removeEventListener('click', handler);
130
258
  });
131
259
 
260
+ if (this._touchStartHandler) this.element.removeEventListener('touchstart', this._touchStartHandler);
261
+ if (this._touchMoveHandler) this.element.removeEventListener('touchmove', this._touchMoveHandler);
262
+ if (this._touchEndHandler) this.element.removeEventListener('touchend', this._touchEndHandler);
263
+ if (this._keydownHandler) this.element.removeEventListener('keydown', this._keydownHandler);
264
+
132
265
  this._prevClickHandler = null;
133
266
  this._nextClickHandler = null;
134
267
  this._autoClickHandler = null;
@@ -136,10 +269,10 @@ class Carousel {
136
269
  }
137
270
  }
138
271
 
139
- function initCarousel(element) {
272
+ function initCarousel(element, options) {
140
273
  if (element.__kupolaInitialized) return;
141
274
 
142
- const carousel = new Carousel(element);
275
+ const carousel = new Carousel(element, options);
143
276
  element.__kupolaInstance = carousel;
144
277
  element.__kupolaInitialized = true;
145
278
  }
@@ -160,9 +293,9 @@ function cleanupCarousel(element) {
160
293
  element.__kupolaInitialized = false;
161
294
  }
162
295
 
163
- if (typeof module !== 'undefined' && module.exports) {
164
- module.exports = { Carousel, initCarousel, initCarousels, cleanupCarousel };
165
- } else {
296
+ export { Carousel, initCarousel, initCarousels, cleanupCarousel };
297
+
298
+ if (typeof window !== 'undefined') {
166
299
  window.Carousel = Carousel;
167
300
  window.initCarousel = initCarousel;
168
301
  window.initCarousels = initCarousels;
@@ -171,3 +304,4 @@ if (typeof module !== 'undefined' && module.exports) {
171
304
  window.kupolaInitializer.register('carousel', initCarousel, cleanupCarousel);
172
305
  }
173
306
  }
307
+
package/js/collapse.js CHANGED
@@ -1,48 +1,133 @@
1
1
  class Collapse {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.headers = [];
5
5
  this._listeners = [];
6
+
7
+ // Options
8
+ this.accordion = options.accordion || element.hasAttribute('data-collapse-accordion');
9
+ this.animationDuration = options.animationDuration || parseInt(element.getAttribute('data-collapse-duration')) || 300;
10
+ this.disabledItems = options.disabledItems || [];
11
+ this.defaultExpanded = options.defaultExpanded || []; // indices or 'all'
12
+
6
13
  this._init();
7
14
  }
8
15
 
9
16
  _init() {
10
17
  const headers = this.element.querySelectorAll('.ds-collapse__header');
11
18
 
12
- headers.forEach(header => {
19
+ headers.forEach((header, index) => {
13
20
  const item = header.closest('.ds-collapse__item');
14
21
  const content = header.nextElementSibling;
15
22
 
16
23
  if (!item || !content || !content.classList.contains('ds-collapse__content')) return;
17
24
 
18
- const isActive = item.classList.contains('is-active');
19
- content.style.height = isActive ? content.scrollHeight + 'px' : '0';
25
+ const isDisabled = item.hasAttribute('data-collapse-disabled') || this.disabledItems.includes(index);
26
+ if (isDisabled) {
27
+ item.classList.add('is-disabled');
28
+ }
29
+
30
+ // Handle default expanded
31
+ let isActive = item.classList.contains('is-active');
32
+ if (this.defaultExpanded === 'all') {
33
+ isActive = true;
34
+ } else if (Array.isArray(this.defaultExpanded) && this.defaultExpanded.includes(index)) {
35
+ isActive = true;
36
+ }
37
+
38
+ if (isActive) {
39
+ item.classList.add('is-active');
40
+ content.style.height = content.scrollHeight + 'px';
41
+ content.style.overflow = 'hidden';
42
+ // After animation, allow overflow
43
+ setTimeout(() => {
44
+ if (item.classList.contains('is-active')) {
45
+ content.style.height = 'auto';
46
+ content.style.overflow = 'visible';
47
+ }
48
+ }, this.animationDuration);
49
+ } else {
50
+ item.classList.remove('is-active');
51
+ content.style.height = '0';
52
+ content.style.overflow = 'hidden';
53
+ }
20
54
 
21
55
  const clickHandler = () => {
22
- const isExpanded = content.style.height !== '0px';
56
+ if (isDisabled) return;
57
+ const isExpanded = item.classList.contains('is-active');
58
+
59
+ if (this.accordion && !isExpanded) {
60
+ // Close all other items first
61
+ this.headers.forEach((h, i) => {
62
+ if (i !== index && h.item.classList.contains('is-active')) {
63
+ this._collapseItem(h);
64
+ }
65
+ });
66
+ }
23
67
 
24
68
  if (isExpanded) {
25
- content.style.height = '0';
26
- item.classList.remove('is-active');
69
+ this._collapseItem({ item, content });
27
70
  } else {
28
- content.style.height = content.scrollHeight + 'px';
29
- item.classList.add('is-active');
30
-
31
- const transitionHandler = function onTransitionEnd() {
32
- content.removeEventListener('transitionend', transitionHandler);
33
- content.style.height = '';
34
- };
35
- content.addEventListener('transitionend', transitionHandler);
36
- this._listeners.push({ el: content, event: 'transitionend', handler: transitionHandler });
71
+ this._expandItem({ item, content });
37
72
  }
73
+
74
+ // Dispatch event
75
+ this.element.dispatchEvent(new CustomEvent('kupola:collapse-toggle', {
76
+ detail: { index, expanded: !isExpanded, item },
77
+ bubbles: true
78
+ }));
38
79
  };
39
80
 
40
81
  header.addEventListener('click', clickHandler);
41
- this.headers.push({ header, item, content, clickHandler });
82
+ this.headers.push({ header, item, content, clickHandler, isDisabled });
42
83
  this._listeners.push({ el: header, event: 'click', handler: clickHandler });
43
84
  });
44
85
  }
45
86
 
87
+ _expandItem(headerInfo) {
88
+ const { item, content } = headerInfo;
89
+ content.style.overflow = 'hidden';
90
+ content.style.height = '0';
91
+
92
+ // Force reflow
93
+ content.offsetHeight;
94
+
95
+ content.style.transition = `height ${this.animationDuration}ms ease`;
96
+ content.style.height = content.scrollHeight + 'px';
97
+ item.classList.add('is-active');
98
+
99
+ const transitionHandler = () => {
100
+ content.removeEventListener('transitionend', transitionHandler);
101
+ if (item.classList.contains('is-active')) {
102
+ content.style.height = 'auto';
103
+ content.style.overflow = 'visible';
104
+ }
105
+ content.style.transition = '';
106
+ };
107
+ content.addEventListener('transitionend', transitionHandler);
108
+ this._listeners.push({ el: content, event: 'transitionend', handler: transitionHandler });
109
+ }
110
+
111
+ _collapseItem(headerInfo) {
112
+ const { item, content } = headerInfo;
113
+ content.style.overflow = 'hidden';
114
+ content.style.height = content.scrollHeight + 'px';
115
+
116
+ // Force reflow
117
+ content.offsetHeight;
118
+
119
+ content.style.transition = `height ${this.animationDuration}ms ease`;
120
+ content.style.height = '0';
121
+ item.classList.remove('is-active');
122
+
123
+ const transitionHandler = () => {
124
+ content.removeEventListener('transitionend', transitionHandler);
125
+ content.style.transition = '';
126
+ };
127
+ content.addEventListener('transitionend', transitionHandler);
128
+ this._listeners.push({ el: content, event: 'transitionend', handler: transitionHandler });
129
+ }
130
+
46
131
  destroy() {
47
132
  this._listeners.forEach(({ el, event, handler }) => {
48
133
  el.removeEventListener(event, handler);
@@ -54,46 +139,74 @@ class Collapse {
54
139
 
55
140
  toggle(index) {
56
141
  const headerInfo = this.headers[index];
57
- if (headerInfo) {
142
+ if (headerInfo && !headerInfo.isDisabled) {
58
143
  headerInfo.clickHandler();
59
144
  }
60
145
  }
61
146
 
62
147
  expand(index) {
63
148
  const headerInfo = this.headers[index];
64
- if (headerInfo && !headerInfo.item.classList.contains('is-active')) {
65
- headerInfo.clickHandler();
149
+ if (headerInfo && !headerInfo.item.classList.contains('is-active') && !headerInfo.isDisabled) {
150
+ if (this.accordion) {
151
+ this.headers.forEach((h, i) => {
152
+ if (i !== index && h.item.classList.contains('is-active')) {
153
+ this._collapseItem(h);
154
+ }
155
+ });
156
+ }
157
+ this._expandItem(headerInfo);
66
158
  }
67
159
  }
68
160
 
69
161
  collapse(index) {
70
162
  const headerInfo = this.headers[index];
71
163
  if (headerInfo && headerInfo.item.classList.contains('is-active')) {
72
- headerInfo.clickHandler();
164
+ this._collapseItem(headerInfo);
73
165
  }
74
166
  }
75
167
 
76
168
  expandAll() {
169
+ if (this.accordion) return; // Can't expand all in accordion mode
77
170
  this.headers.forEach((headerInfo, index) => {
78
- if (!headerInfo.item.classList.contains('is-active')) {
79
- this.toggle(index);
171
+ if (!headerInfo.item.classList.contains('is-active') && !headerInfo.isDisabled) {
172
+ this._expandItem(headerInfo);
80
173
  }
81
174
  });
82
175
  }
83
176
 
84
177
  collapseAll() {
85
- this.headers.forEach((headerInfo, index) => {
178
+ this.headers.forEach((headerInfo) => {
86
179
  if (headerInfo.item.classList.contains('is-active')) {
87
- this.toggle(index);
180
+ this._collapseItem(headerInfo);
88
181
  }
89
182
  });
90
183
  }
184
+
185
+ getExpandedIndices() {
186
+ return this.headers
187
+ .map((h, i) => h.item.classList.contains('is-active') ? i : -1)
188
+ .filter(i => i >= 0);
189
+ }
190
+
191
+ disable(index) {
192
+ if (this.headers[index]) {
193
+ this.headers[index].isDisabled = true;
194
+ this.headers[index].item.classList.add('is-disabled');
195
+ }
196
+ }
197
+
198
+ enable(index) {
199
+ if (this.headers[index]) {
200
+ this.headers[index].isDisabled = false;
201
+ this.headers[index].item.classList.remove('is-disabled');
202
+ }
203
+ }
91
204
  }
92
205
 
93
- function initCollapse(element) {
206
+ function initCollapse(element, options) {
94
207
  if (element.__kupolaInitialized) return;
95
208
 
96
- const collapse = new Collapse(element);
209
+ const collapse = new Collapse(element, options);
97
210
  element.__kupolaInstance = collapse;
98
211
  element.__kupolaInitialized = true;
99
212
  }
@@ -114,14 +227,15 @@ function initCollapses() {
114
227
  });
115
228
  }
116
229
 
117
- if (typeof module !== 'undefined' && module.exports) {
118
- module.exports = { Collapse, initCollapses, initCollapse, cleanupCollapse };
119
- } else {
230
+ export { Collapse, initCollapses, initCollapse, cleanupCollapse };
231
+
232
+ if (typeof window !== 'undefined') {
120
233
  window.Collapse = Collapse;
121
234
  window.initCollapse = initCollapse;
122
235
  window.cleanupCollapse = cleanupCollapse;
236
+ window.initCollapses = initCollapses;
237
+
238
+ if (window.kupolaInitializer) {
239
+ window.kupolaInitializer.register('collapse', initCollapse, cleanupCollapse);
240
+ }
123
241
  }
124
-
125
- if (window.kupolaInitializer) {
126
- window.kupolaInitializer.register('collapse', initCollapse, cleanupCollapse);
127
- }