@winkintel/bootstrap-svelte 1.0.4 → 1.0.6

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 (81) hide show
  1. package/README.md +1 -1
  2. package/dist/Alert/alert.svelte +4 -2
  3. package/dist/Badge/badge.svelte +6 -4
  4. package/dist/Badge/badge.svelte.d.ts +2 -2
  5. package/dist/Badge/types.d.ts +2 -1
  6. package/dist/Badge/types.js +3 -11
  7. package/dist/Button/button.svelte +29 -9
  8. package/dist/ButtonGroup/button-group.svelte +3 -2
  9. package/dist/ButtonGroup/button-toolbar.svelte +3 -2
  10. package/dist/ButtonGroup/types.contract.d.ts +1 -0
  11. package/dist/ButtonGroup/types.contract.js +15 -0
  12. package/dist/ButtonGroup/types.d.ts +2 -2
  13. package/dist/Carousel/carousel-indicator-button.svelte +8 -1
  14. package/dist/Carousel/carousel-item.svelte +29 -9
  15. package/dist/Carousel/carousel.svelte +12 -5
  16. package/dist/Carousel/carousel.svelte.js +200 -42
  17. package/dist/Col/col.svelte +30 -22
  18. package/dist/Collapse/collapse.svelte +3 -3
  19. package/dist/Collapse/transition.js +78 -15
  20. package/dist/Collapse/types.d.ts +1 -0
  21. package/dist/Dropdown/dropdown-item.svelte +7 -0
  22. package/dist/Dropdown/dropdown.svelte.js +66 -19
  23. package/dist/Form/form-check-input.svelte +6 -6
  24. package/dist/Form/form-color-input.svelte +8 -3
  25. package/dist/Form/form-date-input.svelte +4 -4
  26. package/dist/Form/form-datetime-local-input.svelte +4 -4
  27. package/dist/Form/form-email-input.svelte +4 -4
  28. package/dist/Form/form-file-input.svelte +46 -6
  29. package/dist/Form/form-file-input.svelte.d.ts +3 -2
  30. package/dist/Form/form-hidden-input.svelte +1 -1
  31. package/dist/Form/form-hidden-input.svelte.d.ts +1 -1
  32. package/dist/Form/form-image-input.svelte +1 -1
  33. package/dist/Form/form-image-input.svelte.d.ts +2 -1
  34. package/dist/Form/form-month-input.svelte +4 -4
  35. package/dist/Form/form-number-input.svelte +4 -4
  36. package/dist/Form/form-password-input.svelte +4 -4
  37. package/dist/Form/form-radio-input.svelte +53 -3
  38. package/dist/Form/form-radio-input.svelte.d.ts +3 -2
  39. package/dist/Form/form-range-input.svelte +1 -1
  40. package/dist/Form/form-range-input.svelte.d.ts +2 -1
  41. package/dist/Form/form-search-input.svelte +4 -4
  42. package/dist/Form/form-select.svelte +7 -7
  43. package/dist/Form/form-switch-input.svelte +4 -4
  44. package/dist/Form/form-telephone-input.svelte +4 -4
  45. package/dist/Form/form-text-area.svelte +4 -4
  46. package/dist/Form/form-text-input.svelte +4 -4
  47. package/dist/Form/form-time-input.svelte +4 -4
  48. package/dist/Form/form-url-input.svelte +4 -4
  49. package/dist/Form/form-week-Input.svelte +4 -4
  50. package/dist/Form/types.d.ts +26 -20
  51. package/dist/ListGroup/list-group-item-action.svelte +14 -7
  52. package/dist/Modal/modal-title.svelte +7 -7
  53. package/dist/Modal/modal.svelte +86 -26
  54. package/dist/Modal/modal.svelte.js +15 -0
  55. package/dist/Nav/nav-link.svelte +12 -8
  56. package/dist/Navbar/navbar-collapse.svelte +18 -7
  57. package/dist/Navbar/navbar-collapse.svelte.d.ts +1 -1
  58. package/dist/Navbar/navbar-nav.svelte +1 -1
  59. package/dist/Navbar/navbar-toggler.svelte +9 -7
  60. package/dist/Navbar/navbar-toggler.svelte.d.ts +1 -1
  61. package/dist/Navbar/types.d.ts +3 -2
  62. package/dist/Offcanvas/offcanvas.svelte +38 -7
  63. package/dist/Pagination/pagination-item.svelte +27 -20
  64. package/dist/Pagination/pagination-link.svelte +20 -5
  65. package/dist/Pagination/pagination.svelte.js +28 -8
  66. package/dist/Placeholder/placeholder-item.svelte +7 -2
  67. package/dist/Placeholder/placeholder-item.svelte.d.ts +1 -1
  68. package/dist/Placeholder/types.d.ts +1 -0
  69. package/dist/Portal/portal.svelte +6 -1
  70. package/dist/Progress/progress-bar.svelte +22 -9
  71. package/dist/Row/row.svelte +9 -10
  72. package/dist/Row/types.d.ts +8 -7
  73. package/dist/Scrollspy/scrollspy-attachment.svelte.js +47 -28
  74. package/dist/Tooltip/tooltip.svelte +57 -10
  75. package/dist/common/css.js +96 -24
  76. package/dist/common/navbar-offcanvas.svelte.d.ts +2 -0
  77. package/dist/common/navbar-offcanvas.svelte.js +5 -4
  78. package/dist/common/overlay-stack.d.ts +13 -0
  79. package/dist/common/overlay-stack.js +49 -0
  80. package/dist/common/scrollbar.js +24 -8
  81. package/package.json +3 -1
@@ -12,11 +12,18 @@ export class CarouselRootState {
12
12
  static PAUSE_DEFAULT = 'hover';
13
13
  static RIDE_DEFAULT = false;
14
14
  static TRANSITION_DURATION_DEFAULT = 600;
15
+ static normalizeInterval(value) {
16
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : CarouselRootState.INTERNAL_DEFAULT;
17
+ }
18
+ static normalizeTransitionDuration(value) {
19
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : CarouselRootState.TRANSITION_DURATION_DEFAULT;
20
+ }
15
21
  // Private state properties...
16
22
  #activeItemIndex = $state(0); // Zero-based index for active item
17
23
  #animation = $state(CarouselRootState.ANIMATION_DEFAULT);
18
24
  #hasUserInteraction = $state(false); // Used to delay autoplay until user interacts when ride=true
19
- #indicators = [];
25
+ #indicators = $state([]);
26
+ #hasStarted = false;
20
27
  #interval = $state(0);
21
28
  #timeoutId = $state(null);
22
29
  #pendingTimeoutIds = [];
@@ -25,12 +32,17 @@ export class CarouselRootState {
25
32
  #isHovered = $state(false);
26
33
  #isPaused = $state(true);
27
34
  #isPointerDown = $state(false);
28
- #items = [];
35
+ #isTouchGesture = false;
36
+ #items = $state([]);
29
37
  #nextActiveItemIndex = $state(-1); // Tracks the index of the slide that will become active (-1 when no transition is pending)
30
38
  #pauseInteraction = $state(CarouselRootState.PAUSE_DEFAULT);
31
39
  #pointerStartX = $state(0);
40
+ #registrationOrderScheduled = false;
41
+ #resumeTimeoutId = null;
32
42
  #ride = $state(false);
33
43
  #transitionDuration = $state(CarouselRootState.TRANSITION_DURATION_DEFAULT);
44
+ #transitionGeneration = 0;
45
+ #transitionTimeoutId = null;
34
46
  #visibilityListener = null;
35
47
  // Public readonly properties...
36
48
  isSlideAnimation = $derived.by(() => this.animation === 'slide');
@@ -40,11 +52,13 @@ export class CarouselRootState {
40
52
  constructor(props) {
41
53
  this.props = props;
42
54
  this.#animation = this.props.animation || CarouselRootState.ANIMATION_DEFAULT;
43
- this.#interval = this.props.interval || CarouselRootState.INTERNAL_DEFAULT;
55
+ this.#interval = CarouselRootState.normalizeInterval(this.props.interval);
44
56
  this.#pauseInteraction = this.props.pause !== undefined ? this.props.pause : CarouselRootState.PAUSE_DEFAULT;
45
57
  this.#ride = this.props.ride || CarouselRootState.RIDE_DEFAULT;
46
- this.#transitionDuration = this.props.transitionDuration || CarouselRootState.TRANSITION_DURATION_DEFAULT;
58
+ this.#transitionDuration = CarouselRootState.normalizeTransitionDuration(this.props.transitionDuration);
47
59
  this.registerItem = this.registerItem.bind(this);
60
+ this.unregisterItem = this.unregisterItem.bind(this);
61
+ this.unregisterIndicator = this.unregisterIndicator.bind(this);
48
62
  this.isItemActive = this.isItemActive.bind(this);
49
63
  this.next = this.next.bind(this);
50
64
  this.prev = this.prev.bind(this);
@@ -56,6 +70,7 @@ export class CarouselRootState {
56
70
  this.handlePointerDown = this.handlePointerDown.bind(this);
57
71
  this.handlePointerMove = this.handlePointerMove.bind(this);
58
72
  this.handlePointerUp = this.handlePointerUp.bind(this);
73
+ this.handlePointerCancel = this.handlePointerCancel.bind(this);
59
74
  // Setup Page Visibility API listener
60
75
  if (typeof document !== 'undefined' && 'hidden' in document) {
61
76
  this.#visibilityListener = () => {
@@ -68,11 +83,6 @@ export class CarouselRootState {
68
83
  };
69
84
  document.addEventListener('visibilitychange', this.#visibilityListener);
70
85
  }
71
- // Start autoplay immediately in the browser if ride='carousel', otherwise wait for user interaction.
72
- // During static prerendering there is no browser timer API, so autoplay starts during hydration instead.
73
- if (typeof window !== 'undefined' && this.#ride === 'carousel' && !this.#timeoutId) {
74
- this.cycle();
75
- }
76
86
  }
77
87
  // Determines the appropriate interval duration, prioritizing the active item's specific interval
78
88
  getCurrentItemInterval() {
@@ -80,8 +90,10 @@ export class CarouselRootState {
80
90
  return this.#interval;
81
91
  }
82
92
  const activeItem = this.#items[this.#activeItemIndex];
83
- if (activeItem && activeItem.interval) {
84
- return activeItem.interval;
93
+ if (activeItem?.interval !== undefined) {
94
+ return typeof activeItem.interval === 'number' && Number.isFinite(activeItem.interval) && activeItem.interval > 0
95
+ ? activeItem.interval
96
+ : this.#interval;
85
97
  }
86
98
  return this.#interval;
87
99
  }
@@ -98,6 +110,7 @@ export class CarouselRootState {
98
110
  callback();
99
111
  }, delay);
100
112
  this.#pendingTimeoutIds.push(timeoutId);
113
+ return timeoutId;
101
114
  }
102
115
  get animation() {
103
116
  return this.#animation;
@@ -115,7 +128,7 @@ export class CarouselRootState {
115
128
  return this.#interval;
116
129
  }
117
130
  set interval(interval) {
118
- this.#interval = interval;
131
+ this.#interval = CarouselRootState.normalizeInterval(interval);
119
132
  if (this.shouldCycle()) {
120
133
  this.cycle();
121
134
  }
@@ -136,7 +149,7 @@ export class CarouselRootState {
136
149
  return this.#transitionDuration;
137
150
  }
138
151
  set transitionDuration(duration) {
139
- this.#transitionDuration = duration;
152
+ this.#transitionDuration = CarouselRootState.normalizeTransitionDuration(duration);
140
153
  }
141
154
  get isAnimating() {
142
155
  return this.#isAnimating;
@@ -147,10 +160,82 @@ export class CarouselRootState {
147
160
  if (item.props.isActive && !this.#isAnimating) {
148
161
  this.#activeItemIndex = item.index;
149
162
  }
163
+ if (this.#hasStarted && this.#items.length === 2 && this.shouldCycle()) {
164
+ this.cycle();
165
+ }
166
+ }
167
+ unregisterItem(item) {
168
+ const removedIndex = this.#items.indexOf(item);
169
+ if (removedIndex === -1) {
170
+ return;
171
+ }
172
+ if (removedIndex === this.#activeItemIndex || removedIndex === this.#nextActiveItemIndex) {
173
+ this.#invalidateTransition();
174
+ }
175
+ this.#items.splice(removedIndex, 1);
176
+ if (this.#items.length === 0) {
177
+ this.#activeItemIndex = 0;
178
+ this.#nextActiveItemIndex = -1;
179
+ this.#isAnimating = false;
180
+ this.pause();
181
+ return;
182
+ }
183
+ if (removedIndex < this.#activeItemIndex) {
184
+ this.#activeItemIndex--;
185
+ }
186
+ else if (removedIndex === this.#activeItemIndex) {
187
+ this.#activeItemIndex = Math.min(removedIndex, this.#items.length - 1);
188
+ }
189
+ if (removedIndex < this.#nextActiveItemIndex) {
190
+ this.#nextActiveItemIndex--;
191
+ }
192
+ else if (removedIndex === this.#nextActiveItemIndex) {
193
+ this.#nextActiveItemIndex = -1;
194
+ }
150
195
  }
151
196
  registerIndicator(indicator) {
152
197
  this.#indicators.push(indicator);
153
198
  }
199
+ scheduleRegistrationOrder() {
200
+ if (this.#registrationOrderScheduled)
201
+ return;
202
+ this.#registrationOrderScheduled = true;
203
+ queueMicrotask(() => {
204
+ this.#registrationOrderScheduled = false;
205
+ if (this.#isDisposed)
206
+ return;
207
+ this.#reorderRegistrations();
208
+ });
209
+ }
210
+ unregisterIndicator(indicator) {
211
+ const index = this.#indicators.indexOf(indicator);
212
+ if (index !== -1) {
213
+ this.#indicators.splice(index, 1);
214
+ }
215
+ }
216
+ getItemIndex(item) {
217
+ return this.#items.indexOf(item);
218
+ }
219
+ getIndicatorIndex(indicator) {
220
+ return this.#indicators.indexOf(indicator);
221
+ }
222
+ #reorderRegistrations() {
223
+ const activeItem = this.#items[this.#activeItemIndex];
224
+ const nextActiveItem = this.#items[this.#nextActiveItemIndex];
225
+ const compareElementOrder = (first, second) => {
226
+ const firstElement = first.props.orderElementRef ?? first.props.elementRef;
227
+ const secondElement = second.props.orderElementRef ?? second.props.elementRef;
228
+ if (!firstElement || !secondElement || firstElement === secondElement) {
229
+ return firstElement ? -1 : secondElement ? 1 : 0;
230
+ }
231
+ const position = firstElement.compareDocumentPosition(secondElement);
232
+ return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : position & Node.DOCUMENT_POSITION_PRECEDING ? 1 : 0;
233
+ };
234
+ this.#items.sort(compareElementOrder);
235
+ this.#indicators.sort(compareElementOrder);
236
+ this.#activeItemIndex = activeItem ? this.#items.indexOf(activeItem) : Math.min(this.#activeItemIndex, this.#items.length - 1);
237
+ this.#nextActiveItemIndex = nextActiveItem ? this.#items.indexOf(nextActiveItem) : -1;
238
+ }
154
239
  isItemActive(index) {
155
240
  return this.#activeItemIndex === index;
156
241
  }
@@ -169,31 +254,29 @@ export class CarouselRootState {
169
254
  getIndicatorCount() {
170
255
  return this.#indicators.length;
171
256
  }
257
+ registerUserInteraction() {
258
+ this.#hasUserInteraction = true;
259
+ if (!this.#isTouchGesture && this.#ride === true && !this.#timeoutId) {
260
+ this.cycle();
261
+ }
262
+ }
172
263
  next() {
173
- if (this.#isAnimating) {
264
+ if (this.#isAnimating || this.#items.length < 2) {
174
265
  return;
175
266
  }
176
267
  const nextIndex = (this.#activeItemIndex + 1) % this.#items.length;
177
268
  this.to(nextIndex, 'next');
178
269
  // Mark that user interaction has occurred
179
- this.#hasUserInteraction = true;
180
- // Start cycling if ride is true and this is first interaction
181
- if (this.#ride === true && !this.#timeoutId) {
182
- this.cycle();
183
- }
270
+ this.registerUserInteraction();
184
271
  }
185
272
  prev() {
186
- if (this.#isAnimating) {
273
+ if (this.#isAnimating || this.#items.length < 2) {
187
274
  return;
188
275
  }
189
276
  const prevIndex = (this.#activeItemIndex - 1 + this.#items.length) % this.#items.length;
190
277
  this.to(prevIndex, 'prev');
191
278
  // Mark that user interaction has occurred
192
- this.#hasUserInteraction = true;
193
- // Start cycling if ride is true and this is first interaction
194
- if (this.#ride === true && !this.#timeoutId) {
195
- this.cycle();
196
- }
279
+ this.registerUserInteraction();
197
280
  }
198
281
  /**
199
282
  * Navigates to the slide at the specified index with the given transition direction.
@@ -226,6 +309,7 @@ export class CarouselRootState {
226
309
  }
227
310
  this.#isAnimating = true;
228
311
  this.#nextActiveItemIndex = index;
312
+ const generation = ++this.#transitionGeneration;
229
313
  // For slide and crossfade animations, we set directional classes and clean up after transition
230
314
  if (this.#animation === 'slide' || this.#animation === 'crossfade') {
231
315
  if (isNext) {
@@ -238,12 +322,16 @@ export class CarouselRootState {
238
322
  toItem.direction = 'end';
239
323
  toItem.order = 'prev';
240
324
  }
241
- this.#schedule(() => {
325
+ this.#transitionTimeoutId = this.#schedule(() => {
326
+ this.#transitionTimeoutId = null;
327
+ if (generation !== this.#transitionGeneration || !this.#items.includes(fromItem) || !this.#items.includes(toItem)) {
328
+ return;
329
+ }
242
330
  fromItem.order = undefined;
243
331
  fromItem.direction = undefined;
244
332
  toItem.order = undefined;
245
333
  toItem.direction = undefined;
246
- this.#activeItemIndex = index;
334
+ this.#activeItemIndex = this.#items.indexOf(toItem);
247
335
  this.#nextActiveItemIndex = -1;
248
336
  this.#isAnimating = false;
249
337
  // Restart cycle with new slide's interval if cycling
@@ -260,10 +348,14 @@ export class CarouselRootState {
260
348
  if (isPrev) {
261
349
  toItem.order = 'prev';
262
350
  }
263
- this.#schedule(() => {
351
+ this.#transitionTimeoutId = this.#schedule(() => {
352
+ this.#transitionTimeoutId = null;
353
+ if (generation !== this.#transitionGeneration || !this.#items.includes(toItem)) {
354
+ return;
355
+ }
264
356
  toItem.order = undefined;
265
357
  this.#isAnimating = false;
266
- }, this.#transitionDuration / 2);
358
+ }, this.#transitionDuration);
267
359
  this.#activeItemIndex = index;
268
360
  this.#nextActiveItemIndex = -1;
269
361
  // Restart cycle with new slide's interval if cycling
@@ -274,7 +366,10 @@ export class CarouselRootState {
274
366
  // For no animation, we simply update the active index on the next tick
275
367
  if (this.#animation === 'none') {
276
368
  tick().then(() => {
277
- this.#activeItemIndex = index;
369
+ if (generation !== this.#transitionGeneration || !this.#items.includes(fromItem) || !this.#items.includes(toItem)) {
370
+ return;
371
+ }
372
+ this.#activeItemIndex = this.#items.indexOf(toItem);
278
373
  this.#nextActiveItemIndex = -1;
279
374
  this.#isAnimating = false;
280
375
  // Restart cycle with new slide's interval if cycling
@@ -292,7 +387,12 @@ export class CarouselRootState {
292
387
  if (typeof window === 'undefined') {
293
388
  return;
294
389
  }
295
- if (this.#isDisposed) {
390
+ if (this.#isDisposed || !this.shouldCycle() || this.#items.length < 2) {
391
+ return;
392
+ }
393
+ // A mount/start or reactive ride update can race with an in-progress touch.
394
+ // Keep the carousel paused until the single delayed touch resume fires.
395
+ if (this.#isPointerDown || this.#resumeTimeoutId !== null) {
296
396
  return;
297
397
  }
298
398
  // Don't start cycling if we're hovered and pause='hover'
@@ -310,6 +410,7 @@ export class CarouselRootState {
310
410
  const currentInterval = this.getCurrentItemInterval();
311
411
  // Schedule the next slide using setTimeout
312
412
  this.#timeoutId = window.setTimeout(() => {
413
+ this.#timeoutId = null;
313
414
  if (!this.#isPaused && !this.#isAnimating) {
314
415
  this.next();
315
416
  }
@@ -344,6 +445,7 @@ export class CarouselRootState {
344
445
  handlePointerDown(pointerEvent) {
345
446
  // Only track primary touch pointer
346
447
  if (pointerEvent.pointerType === 'touch' && pointerEvent.isPrimary) {
448
+ this.#cancelTouchResume();
347
449
  this.#isPointerDown = true;
348
450
  this.#pointerStartX = pointerEvent.clientX;
349
451
  // Pause autoplay during touch interaction
@@ -366,29 +468,81 @@ export class CarouselRootState {
366
468
  * Determines swipe direction based on horizontal movement distance.
367
469
  */
368
470
  handlePointerUp(pointerEvent) {
369
- if (!this.#isPointerDown || pointerEvent.pointerType !== 'touch' || !pointerEvent.isPrimary) {
471
+ if (!this.#isPointerDown) {
472
+ return;
473
+ }
474
+ this.#isPointerDown = false;
475
+ if (pointerEvent.pointerType !== 'touch' || !pointerEvent.isPrimary) {
476
+ this.#scheduleTouchResume();
370
477
  return;
371
478
  }
372
479
  const deltaX = pointerEvent.clientX - this.#pointerStartX;
373
480
  // Consider it a swipe if movement exceeds threshold (50px)
374
481
  if (Math.abs(deltaX) > 50) {
482
+ this.#isTouchGesture = true;
375
483
  if (deltaX > 0) {
376
484
  this.prev(); // Swipe right means go to previous
377
485
  }
378
486
  else {
379
487
  this.next(); // Swipe left means go to next
380
488
  }
489
+ this.#isTouchGesture = false;
381
490
  }
382
- this.#isPointerDown = false;
383
491
  // Resume cycling after touch with a delay to prevent immediate transition
384
- if (this.shouldCycle()) {
385
- this.#schedule(() => {
492
+ this.#scheduleTouchResume();
493
+ }
494
+ handlePointerCancel() {
495
+ if (!this.#isPointerDown) {
496
+ return;
497
+ }
498
+ this.#isPointerDown = false;
499
+ this.#scheduleTouchResume();
500
+ }
501
+ #invalidateTransition() {
502
+ this.#transitionGeneration++;
503
+ if (this.#transitionTimeoutId !== null) {
504
+ window.clearTimeout(this.#transitionTimeoutId);
505
+ this.#pendingTimeoutIds = this.#pendingTimeoutIds.filter((id) => id !== this.#transitionTimeoutId);
506
+ this.#transitionTimeoutId = null;
507
+ }
508
+ for (const item of this.#items) {
509
+ item.order = undefined;
510
+ item.direction = undefined;
511
+ }
512
+ this.#nextActiveItemIndex = -1;
513
+ this.#isAnimating = false;
514
+ }
515
+ #cancelTouchResume() {
516
+ if (this.#resumeTimeoutId === null) {
517
+ return;
518
+ }
519
+ window.clearTimeout(this.#resumeTimeoutId);
520
+ this.#pendingTimeoutIds = this.#pendingTimeoutIds.filter((id) => id !== this.#resumeTimeoutId);
521
+ this.#resumeTimeoutId = null;
522
+ }
523
+ #scheduleTouchResume() {
524
+ this.#cancelTouchResume();
525
+ if (!this.shouldCycle() || this.#isDisposed) {
526
+ return;
527
+ }
528
+ this.#schedule(() => {
529
+ this.#resumeTimeoutId = null;
530
+ if (!this.#isDisposed && this.shouldCycle()) {
386
531
  this.cycle();
387
- }, 1000);
532
+ }
533
+ }, 1000);
534
+ this.#resumeTimeoutId = this.#pendingTimeoutIds.at(-1) ?? null;
535
+ }
536
+ start() {
537
+ this.#hasStarted = true;
538
+ if (this.shouldCycle()) {
539
+ this.cycle();
388
540
  }
389
541
  }
390
542
  dispose() {
391
543
  this.#isDisposed = true;
544
+ this.#invalidateTransition();
545
+ this.#cancelTouchResume();
392
546
  if (this.#timeoutId) {
393
547
  window.clearTimeout(this.#timeoutId);
394
548
  this.#timeoutId = null;
@@ -413,8 +567,12 @@ export class CarouselItemState {
413
567
  root;
414
568
  #order = $state(undefined);
415
569
  #direction = $state(undefined);
416
- index;
417
- interval;
570
+ get index() {
571
+ return this.root.getItemIndex(this);
572
+ }
573
+ get interval() {
574
+ return this.props.interval;
575
+ }
418
576
  isActive = $derived.by(() => this.root.isItemActive(this.index));
419
577
  isNext = $derived(this.#order === 'next');
420
578
  isPrev = $derived(this.#order === 'prev');
@@ -434,8 +592,6 @@ export class CarouselItemState {
434
592
  constructor(props, root) {
435
593
  this.props = props;
436
594
  this.root = root;
437
- this.index = this.root.getItemCount();
438
- this.interval = this.props.interval || 5000;
439
595
  }
440
596
  get order() {
441
597
  return this.#order;
@@ -493,15 +649,17 @@ export class CarouselIndicatorButtonState {
493
649
  root;
494
650
  isActive = $derived.by(() => this.root.isItemActive(this.index) && !this.root.isAnimating);
495
651
  isNextActive = $derived.by(() => this.root.isNextItemActive(this.index));
496
- index;
652
+ get index() {
653
+ return this.root.getIndicatorIndex(this);
654
+ }
497
655
  constructor(props, root) {
498
656
  this.props = props;
499
657
  this.root = root;
500
- this.index = this.root.getIndicatorCount();
501
658
  this.onclick = this.onclick.bind(this);
502
659
  }
503
660
  onclick(event) {
504
661
  event.preventDefault();
662
+ this.root.registerUserInteraction();
505
663
  const order = this.index > this.root.getActiveIndex() ? 'next' : 'prev';
506
664
  this.root.to(this.index, order);
507
665
  }
@@ -57,29 +57,37 @@ A component that implements Bootstrap's grid column functionality.
57
57
  -->
58
58
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
59
59
  let { children, class: classValues, elementRef = $bindable(null), offset, order, sizing, ...restOfProps } = $props();
60
+ const breakpoints = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'xxl']);
61
+ const isIntegerInRange = (value, min, max) => typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;
62
+ const isSizingValue = (value) => value === 'auto' || value === '*' || isIntegerInRange(value, 1, 12);
63
+ const isOffsetValue = (value) => isIntegerInRange(value, 0, 11);
64
+ const isOrderValue = (value) => value === 'first' || value === 'last' || isIntegerInRange(value, 0, 5);
60
65
  // Generate sizing classes based on the sizing prop
61
66
  let sizingClasses = $derived.by(() => {
62
67
  // If sizing is a string and is 'auto', return a single col-auto class
63
- if (typeof sizing === 'string' && sizing === 'auto') {
68
+ if (sizing === 'auto') {
64
69
  return [`col-${sizing}`];
65
70
  }
66
71
  // If sizing is a number (OneToTwelve type), generate a simple col-n class
67
- if (typeof sizing === 'number') {
72
+ if (isIntegerInRange(sizing, 1, 12)) {
68
73
  return [`col-${sizing}`];
69
74
  }
70
75
  // If sizing is an object (BreakpointSizingMap type), generate breakpoint-specific classes
71
76
  else if (sizing && typeof sizing === 'object') {
72
- return Object.entries(sizing).map(([breakpoint, value]) => {
73
- // If the breakpoint is 'xs', return 'col-{value}'
74
- if (breakpoint === 'xs') {
75
- return `col-${value}`;
77
+ return Object.entries(sizing).flatMap(([breakpoint, value]) => {
78
+ if (!breakpoints.has(breakpoint) || !isSizingValue(value)) {
79
+ return [];
76
80
  }
77
81
  // If the value is '*', return just 'col-{breakpoint}'
78
82
  if (value === '*') {
79
- return `col-${breakpoint}`;
83
+ return [breakpoint === 'xs' ? 'col' : `col-${breakpoint}`];
84
+ }
85
+ // If the breakpoint is 'xs', return 'col-{value}'
86
+ if (breakpoint === 'xs') {
87
+ return [`col-${value}`];
80
88
  }
81
89
  // Otherwise, return 'col-{breakpoint}-{value}'
82
- return `col-${breakpoint}-${value}`;
90
+ return [`col-${breakpoint}-${value}`];
83
91
  });
84
92
  }
85
93
  // Default: no sizing classes
@@ -88,18 +96,16 @@ let sizingClasses = $derived.by(() => {
88
96
  // Generate offset classes if provided
89
97
  let offsetClasses = $derived.by(() => {
90
98
  // If offset is a number (ZeroToEleven type), generate a simple offset-n class
91
- if (typeof offset === 'number') {
99
+ if (isOffsetValue(offset)) {
92
100
  return [`offset-${offset}`];
93
101
  }
94
102
  // If offset is an object (ColOffsetMap type), generate breakpoint-specific classes
95
103
  else if (offset && typeof offset === 'object') {
96
- return Object.entries(offset).map(([breakpoint, value]) => {
97
- // If the value is '*', return just 'offset-{breakpoint}'
98
- // if (value === '*') {
99
- // return `offset-${breakpoint}`;
100
- // }
101
- // Otherwise, return 'offset-{breakpoint}-{value}'
102
- return `offset-${breakpoint}-${value}`;
104
+ return Object.entries(offset).flatMap(([breakpoint, value]) => {
105
+ if (!breakpoints.has(breakpoint) || !isOffsetValue(value)) {
106
+ return [];
107
+ }
108
+ return [breakpoint === 'xs' ? `offset-${value}` : `offset-${breakpoint}-${value}`];
103
109
  });
104
110
  }
105
111
  // Default: no offset classes
@@ -108,24 +114,26 @@ let offsetClasses = $derived.by(() => {
108
114
  // Generate order classes if provided
109
115
  let orderClasses = $derived.by(() => {
110
116
  // If order is a string (e.g., 'first' or 'last'), return a single order-first or order-last class
111
- if (typeof order === 'string' && (order === 'first' || order === 'last')) {
117
+ if (order === 'first' || order === 'last') {
112
118
  return [`order-${order}`];
113
119
  }
114
120
  // If order is a number (ZeroToEleven type), generate a simple order-n class
115
- if (typeof order === 'number') {
121
+ if (isIntegerInRange(order, 0, 5)) {
116
122
  return [`order-${order}`];
117
123
  }
118
124
  // If order is an object (ColOrderMap type), generate breakpoint-specific classes
119
125
  else if (order && typeof order === 'object') {
120
- return Object.entries(order).map(([breakpoint, value]) => {
121
- // return 'order-{breakpoint}-{value}'
122
- return `order-${breakpoint}-${value}`;
126
+ return Object.entries(order).flatMap(([breakpoint, value]) => {
127
+ if (!breakpoints.has(breakpoint) || !isOrderValue(value)) {
128
+ return [];
129
+ }
130
+ return [breakpoint === 'xs' ? `order-${value}` : `order-${breakpoint}-${value}`];
123
131
  });
124
132
  }
125
133
  // Default: no order classes
126
134
  return [];
127
135
  });
128
- let classes = $derived(uniqueClsx({ col: sizing === undefined }, ...sizingClasses, ...offsetClasses, ...orderClasses, classValues));
136
+ let classes = $derived(uniqueClsx({ col: sizingClasses.length === 0 }, ...sizingClasses, ...offsetClasses, ...orderClasses, classValues));
129
137
  </script>
130
138
 
131
139
  <div bind:this={elementRef} class={classes} {...restOfProps}>
@@ -43,7 +43,7 @@ Supports both vertical and horizontal collapse directions.
43
43
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
44
44
  import { noop } from '../common/noop.js';
45
45
  import { collapseIn, collapseOut } from './transition.js';
46
- let { children, class: classValues, transitionDuration: _transitionDuration = 350, elementRef = $bindable(null), isHorizontal = false, isExpanded = false, onCollapse = noop, onCollapsed = noop, onExpand = noop, onExpanded = noop, ...restOfProps } = $props();
46
+ let { children, class: classValues, transitionDuration, elementRef = $bindable(null), isHorizontal = false, isExpanded = false, onCollapse = noop, onCollapsed = noop, onExpand = noop, onExpanded = noop, ...restOfProps } = $props();
47
47
  let classes = $derived(uniqueClsx('collapse', { 'collapse-horizontal': isHorizontal }, { show: isExpanded }, classValues));
48
48
  </script>
49
49
 
@@ -55,8 +55,8 @@ let classes = $derived(uniqueClsx('collapse', { 'collapse-horizontal': isHorizon
55
55
  onintroend={onExpanded}
56
56
  onoutrostart={onCollapse}
57
57
  onoutroend={onCollapsed}
58
- in:collapseIn={{ isHorizontal }}
59
- out:collapseOut={{ isHorizontal }}
58
+ in:collapseIn={{ isHorizontal, transitionDuration }}
59
+ out:collapseOut={{ isHorizontal, transitionDuration }}
60
60
  {...restOfProps}>
61
61
  {@render children?.()}
62
62
  </div>