@kupola/kupola 1.2.0 → 1.4.0

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 (111) 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/css/components-ext.css +157 -0
  8. package/css/components.css +69 -0
  9. package/css/theme-dark.css +5 -5
  10. package/css/utilities.css +119 -1
  11. package/dist/css/accessibility.css +119 -0
  12. package/dist/css/animations.css +224 -0
  13. package/dist/css/brand-themes.css +300 -0
  14. package/dist/css/colors_and_type.css +441 -0
  15. package/dist/css/components-ext.css +4165 -0
  16. package/dist/css/components.css +1483 -0
  17. package/dist/css/kupola.css +1 -12
  18. package/dist/css/kupola.min.css +1 -0
  19. package/dist/css/responsive.css +697 -0
  20. package/dist/css/scaffold.css +145 -0
  21. package/dist/css/states.css +316 -0
  22. package/dist/css/theme-dark.css +296 -0
  23. package/dist/css/theme-light.css +296 -0
  24. package/dist/css/utilities.css +171 -0
  25. package/dist/icons.svg +284 -0
  26. package/dist/kupola.cjs.js +17409 -257
  27. package/dist/kupola.cjs.js.map +1 -1
  28. package/dist/kupola.css +9000 -0
  29. package/dist/kupola.esm.js +17183 -7597
  30. package/dist/kupola.esm.js.map +1 -1
  31. package/dist/kupola.min.css +1 -0
  32. package/dist/kupola.min.js +2 -0
  33. package/dist/kupola.min.js.map +1 -0
  34. package/dist/kupola.umd.js +17415 -257
  35. package/dist/kupola.umd.js.map +1 -1
  36. package/dist/plugins/vite-plugin-kupola.js +120 -0
  37. package/dist/types/kupola.d.ts +421 -323
  38. package/js/calendar.js +334 -25
  39. package/js/carousel.js +182 -48
  40. package/js/collapse.js +148 -34
  41. package/js/color-picker.js +416 -108
  42. package/js/component.js +8 -19
  43. package/js/countdown.js +9 -8
  44. package/js/data-bind.js +73 -16
  45. package/js/datepicker.js +488 -110
  46. package/js/depends.js +710 -0
  47. package/js/dialog.js +4 -2
  48. package/js/drawer.js +172 -8
  49. package/js/dropdown.js +272 -17
  50. package/js/dynamic-tags.js +156 -40
  51. package/js/fileupload.js +9 -8
  52. package/js/form.js +280 -254
  53. package/js/global-events.js +281 -188
  54. package/js/heatmap.js +10 -7
  55. package/js/i18n.js +18 -10
  56. package/js/icons.js +141 -161
  57. package/js/image-preview.js +146 -2
  58. package/js/initializer.js +151 -71
  59. package/js/kupola-core.js +123 -45
  60. package/js/kupola-lifecycle.js +13 -11
  61. package/js/message.js +8 -1
  62. package/js/modal.js +207 -59
  63. package/js/notification.js +8 -1
  64. package/js/numberinput.js +9 -8
  65. package/js/pagination.js +263 -0
  66. package/js/registry.js +29 -12
  67. package/js/select.js +482 -27
  68. package/js/slide-captcha.js +11 -2
  69. package/js/slider.js +442 -25
  70. package/js/statcard.js +9 -7
  71. package/js/table.js +1210 -0
  72. package/js/tag.js +268 -14
  73. package/js/theme-standalone.js +88 -0
  74. package/js/theme.js +14 -43
  75. package/js/timepicker.js +335 -66
  76. package/js/tooltip.js +317 -86
  77. package/js/utils.js +6 -2
  78. package/js/validation.js +6 -2
  79. package/js/virtual-list.js +11 -7
  80. package/js/web-components.js +288 -0
  81. package/package.json +81 -67
  82. package/plugins/vite-plugin-kupola.js +120 -0
  83. package/scripts/build-css.cjs +113 -0
  84. package/scripts/build-svg-sprite.cjs +107 -0
  85. package/types/kupola.d.ts +421 -323
  86. package/CHANGELOG.md +0 -130
  87. package/INTEGRATION.md +0 -440
  88. package/PROJECT_SUMMARY.md +0 -312
  89. package/SKILL.md +0 -572
  90. package/dist/utils/utils/Kupola.cs +0 -77
  91. package/dist/utils/utils/Kupola.java +0 -104
  92. package/dist/utils/utils/kupola.go +0 -120
  93. package/dist/utils/utils/kupola.js +0 -63
  94. package/dist/utils/utils/kupola.py +0 -1392
  95. package/dist/utils/utils/kupola.rb +0 -69
  96. package/js/composition-api.js +0 -458
  97. package/js/error-handler.js +0 -181
  98. package/js/http.js +0 -419
  99. package/js/kupola-devtools.js +0 -598
  100. package/js/performance.js +0 -250
  101. package/js/router.js +0 -396
  102. package/js/security.js +0 -189
  103. package/js/test-utils.js +0 -251
  104. package/templates/base.html +0 -30
  105. package/templates/base_dashboard.html +0 -99
  106. package/utils/Kupola.cs +0 -77
  107. package/utils/Kupola.java +0 -104
  108. package/utils/kupola.go +0 -120
  109. package/utils/kupola.js +0 -63
  110. package/utils/kupola.py +0 -1392
  111. 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
- }