peek-carousel 1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +238 -0
  3. package/README.md +234 -0
  4. package/dist/peek-carousel.css +1 -0
  5. package/dist/peek-carousel.esm.js +1368 -0
  6. package/dist/peek-carousel.esm.js.map +1 -0
  7. package/dist/peek-carousel.esm.min.js +8 -0
  8. package/dist/peek-carousel.esm.min.js.map +1 -0
  9. package/dist/peek-carousel.js +1376 -0
  10. package/dist/peek-carousel.js.map +1 -0
  11. package/dist/peek-carousel.min.css +1 -0
  12. package/dist/peek-carousel.min.js +8 -0
  13. package/dist/peek-carousel.min.js.map +1 -0
  14. package/examples/example-built.html +367 -0
  15. package/examples/example.css +2216 -0
  16. package/examples/example.js +404 -0
  17. package/examples/example.min.css +1 -0
  18. package/examples/example.min.js +1 -0
  19. package/package.json +92 -0
  20. package/src/core/PeekCarousel.js +294 -0
  21. package/src/core/config.js +49 -0
  22. package/src/core/constants.js +73 -0
  23. package/src/modules/Animator.js +244 -0
  24. package/src/modules/AutoRotate.js +43 -0
  25. package/src/modules/EventHandler.js +390 -0
  26. package/src/modules/Navigator.js +116 -0
  27. package/src/modules/UIManager.js +170 -0
  28. package/src/peek-carousel.d.ts +34 -0
  29. package/src/styles/base/_accessibility.scss +16 -0
  30. package/src/styles/base/_mixins.scss +7 -0
  31. package/src/styles/base/_variables.scss +75 -0
  32. package/src/styles/components/_carousel.scss +5 -0
  33. package/src/styles/components/_counter.scss +109 -0
  34. package/src/styles/components/_indicators.scss +154 -0
  35. package/src/styles/components/_navigation.scss +193 -0
  36. package/src/styles/components/carousel/_carousel-base.scss +99 -0
  37. package/src/styles/components/carousel/_carousel-classic.scss +76 -0
  38. package/src/styles/components/carousel/_carousel-mixins.scss +18 -0
  39. package/src/styles/components/carousel/_carousel-radial.scss +72 -0
  40. package/src/styles/components/carousel/_carousel-stack.scss +84 -0
  41. package/src/styles/components/carousel/_carousel-variables.scss +118 -0
  42. package/src/styles/peek-carousel.scss +11 -0
  43. package/src/utils/dom.js +53 -0
  44. package/src/utils/helpers.js +46 -0
  45. package/src/utils/icons.js +92 -0
  46. package/src/utils/preloader.js +69 -0
  47. package/types/index.d.ts +34 -0
@@ -0,0 +1,390 @@
1
+ import { KEYS, BREAKPOINTS } from '../core/constants.js';
2
+ import { isMobile } from '../utils/helpers.js';
3
+
4
+ const WHEEL_CONFIG = Object.freeze({
5
+ threshold: 50,
6
+ timeout: 150,
7
+ cooldown: 100,
8
+ });
9
+
10
+ const DRAG_CONFIG = Object.freeze({
11
+ touchThreshold: 15,
12
+ mouseThreshold: 10,
13
+ velocityThreshold: 0.5,
14
+ });
15
+
16
+ const RESIZE_DEBOUNCE = 100;
17
+
18
+ export class EventHandler {
19
+ constructor(carousel) {
20
+ this.carousel = carousel;
21
+ this.boundHandlers = new Map();
22
+
23
+ this.touch = {
24
+ startX: 0,
25
+ endX: 0,
26
+ };
27
+
28
+ this.drag = {
29
+ active: false,
30
+ startX: 0,
31
+ currentX: 0,
32
+ lastX: 0,
33
+ lastTime: 0,
34
+ velocity: 0,
35
+ };
36
+
37
+ this.wheel = {
38
+ isScrolling: false,
39
+ scrollTimeout: null,
40
+ lastWheelTime: 0,
41
+ accumulatedDelta: 0,
42
+ };
43
+ }
44
+
45
+ init() {
46
+ this.initNavigationButtons();
47
+ this.initKeyboard();
48
+ this.initWheel();
49
+ this.initItemClick();
50
+ this.initIndicatorClick();
51
+ this.initTouch();
52
+ this.initMouse();
53
+ this.initResize();
54
+ }
55
+
56
+ stopAutoRotateAndNavigate(navigationFn) {
57
+ this.completeCurrentIndicator();
58
+ this.carousel.autoRotate.stop();
59
+ navigationFn();
60
+ }
61
+
62
+ completeCurrentIndicator() {
63
+ const currentIndicator = this.carousel.indicators[this.carousel.state.currentIndex];
64
+ if (currentIndicator && currentIndicator.classList.contains('peek-carousel__indicator--active')) {
65
+ currentIndicator.classList.add('peek-carousel__indicator--completed');
66
+ }
67
+ }
68
+
69
+ resetDragState(index) {
70
+ this.carousel.ui.removeDraggingClass(index);
71
+ this.carousel.ui.clearDragTransform();
72
+ }
73
+
74
+ updateDraggingClass(dragDistance, currentIndex, threshold) {
75
+ if (dragDistance > threshold) {
76
+ this.carousel.ui.addDraggingClass(currentIndex, 'right');
77
+ } else if (dragDistance < -threshold) {
78
+ this.carousel.ui.addDraggingClass(currentIndex, 'left');
79
+ }
80
+ }
81
+
82
+ initDragState(clientX) {
83
+ this.drag.active = true;
84
+ this.drag.startX = clientX;
85
+ this.drag.currentX = clientX;
86
+ this.drag.lastX = clientX;
87
+ this.drag.lastTime = Date.now();
88
+ this.drag.velocity = 0;
89
+ }
90
+
91
+ resetMouseCursor() {
92
+ this.carousel.elements.carousel.style.cursor = 'grab';
93
+ }
94
+
95
+ calculateWheelDelta(e) {
96
+ const deltaX = Math.abs(e.deltaX);
97
+ const deltaY = Math.abs(e.deltaY);
98
+ const isHorizontal = deltaX > deltaY;
99
+
100
+ // [개발참고] 수평: 왼쪽(-) = 다음, 오른쪽(+) = 이전
101
+ // 수직: 아래(+) = 다음, 위(-) = 이전
102
+ return isHorizontal ? -e.deltaX : e.deltaY;
103
+ }
104
+
105
+ resetWheelState() {
106
+ this.wheel.isScrolling = false;
107
+ this.wheel.accumulatedDelta = 0;
108
+ }
109
+
110
+ initNavigationButtons() {
111
+ const { prevBtn, nextBtn, autoRotateBtn } = this.carousel.elements;
112
+
113
+ if (prevBtn) {
114
+ this.addHandler(prevBtn, 'click', () => {
115
+ this.stopAutoRotateAndNavigate(() => this.carousel.navigator.prev());
116
+ });
117
+ }
118
+
119
+ if (nextBtn) {
120
+ this.addHandler(nextBtn, 'click', () => {
121
+ this.stopAutoRotateAndNavigate(() => this.carousel.navigator.next());
122
+ });
123
+ }
124
+
125
+ if (autoRotateBtn) {
126
+ this.addHandler(autoRotateBtn, 'click', () => {
127
+ this.carousel.autoRotate.toggle();
128
+ });
129
+ }
130
+ }
131
+
132
+ initKeyboard() {
133
+ if (!this.carousel.options.enableKeyboard) return;
134
+
135
+ const handler = (e) => {
136
+ const { navigator, autoRotate, totalItems } = this.carousel;
137
+
138
+ switch (e.key) {
139
+ case KEYS.arrowLeft:
140
+ autoRotate.stop();
141
+ navigator.prev();
142
+ break;
143
+
144
+ case KEYS.arrowRight:
145
+ autoRotate.stop();
146
+ navigator.next();
147
+ break;
148
+
149
+ case KEYS.home:
150
+ e.preventDefault();
151
+ autoRotate.stop();
152
+ navigator.goTo(0);
153
+ break;
154
+
155
+ case KEYS.end:
156
+ e.preventDefault();
157
+ autoRotate.stop();
158
+ navigator.goTo(totalItems - 1);
159
+ break;
160
+
161
+ case KEYS.space:
162
+ e.preventDefault();
163
+ autoRotate.toggle();
164
+ break;
165
+
166
+ default:
167
+ const numKey = parseInt(e.key);
168
+ if (numKey >= 1 && numKey <= totalItems) {
169
+ e.preventDefault();
170
+ autoRotate.stop();
171
+ navigator.goTo(numKey - 1);
172
+ }
173
+ }
174
+ };
175
+
176
+ this.addHandler(document, 'keydown', handler);
177
+ }
178
+
179
+ initWheel() {
180
+ if (!this.carousel.options.enableWheel) return;
181
+
182
+ const handler = (e) => {
183
+ const deltaX = Math.abs(e.deltaX);
184
+ const deltaY = Math.abs(e.deltaY);
185
+
186
+ if (deltaX < 1 && deltaY < 1) {
187
+ return;
188
+ }
189
+
190
+ if (deltaX === deltaY) {
191
+ return;
192
+ }
193
+
194
+ e.preventDefault();
195
+
196
+ const currentTime = Date.now();
197
+
198
+ if (currentTime - this.wheel.lastWheelTime < WHEEL_CONFIG.cooldown) {
199
+ return;
200
+ }
201
+
202
+ if (!this.wheel.isScrolling) {
203
+ this.wheel.isScrolling = true;
204
+ this.wheel.accumulatedDelta = 0;
205
+ this.carousel.autoRotate.stop();
206
+ this.carousel.animator.stopMomentum();
207
+ }
208
+
209
+ this.wheel.accumulatedDelta += this.calculateWheelDelta(e);
210
+
211
+ if (Math.abs(this.wheel.accumulatedDelta) >= WHEEL_CONFIG.threshold) {
212
+ const direction = this.wheel.accumulatedDelta > 0 ? 1 : -1;
213
+ this.carousel.navigator.rotate(direction);
214
+
215
+ this.wheel.accumulatedDelta = 0;
216
+ this.wheel.lastWheelTime = currentTime;
217
+ }
218
+
219
+ clearTimeout(this.wheel.scrollTimeout);
220
+ this.wheel.scrollTimeout = setTimeout(() => {
221
+ this.resetWheelState();
222
+ }, WHEEL_CONFIG.timeout);
223
+ };
224
+
225
+ this.addHandler(
226
+ this.carousel.elements.carousel,
227
+ 'wheel',
228
+ handler,
229
+ { passive: false }
230
+ );
231
+ }
232
+
233
+ initItemClick() {
234
+ const { items } = this.carousel;
235
+ for (let i = 0; i < items.length; i++) {
236
+ this.addHandler(items[i], 'click', () => {
237
+ this.carousel.autoRotate.stop();
238
+ this.carousel.navigator.handleItemClick(i);
239
+ });
240
+ }
241
+ }
242
+
243
+ initIndicatorClick() {
244
+ const { indicators } = this.carousel;
245
+ for (let i = 0; i < indicators.length; i++) {
246
+ this.addHandler(indicators[i], 'click', () => {
247
+ this.carousel.autoRotate.stop();
248
+ this.carousel.navigator.handleIndicatorClick(i);
249
+ });
250
+ }
251
+ }
252
+
253
+ initTouch() {
254
+ if (!this.carousel.options.enableTouch) return;
255
+
256
+ this.addHandler(this.carousel.elements.carousel, 'touchstart', (e) => {
257
+ this.touch.startX = e.changedTouches[0].screenX;
258
+ });
259
+
260
+ this.addHandler(this.carousel.elements.carousel, 'touchmove', (e) => {
261
+ const touchCurrentX = e.changedTouches[0].screenX;
262
+ const dragDistance = touchCurrentX - this.touch.startX;
263
+ const { currentIndex } = this.carousel.state;
264
+
265
+ this.carousel.ui.updateDragTransform(dragDistance);
266
+ this.updateDraggingClass(dragDistance, currentIndex, DRAG_CONFIG.touchThreshold);
267
+ });
268
+
269
+ this.addHandler(this.carousel.elements.carousel, 'touchend', (e) => {
270
+ this.touch.endX = e.changedTouches[0].screenX;
271
+ const swipeDistance = this.touch.endX - this.touch.startX;
272
+ const { swipeThreshold } = this.carousel.options;
273
+ const { currentIndex } = this.carousel.state;
274
+
275
+ this.resetDragState(currentIndex);
276
+
277
+ if (swipeDistance < -swipeThreshold) {
278
+ this.carousel.autoRotate.stop();
279
+ this.carousel.navigator.next();
280
+ } else if (swipeDistance > swipeThreshold) {
281
+ this.carousel.autoRotate.stop();
282
+ this.carousel.navigator.prev();
283
+ }
284
+ });
285
+ }
286
+
287
+ initMouse() {
288
+ if (!this.carousel.options.enableMouse) return;
289
+
290
+ this.addHandler(this.carousel.elements.carousel, 'mousedown', (e) => {
291
+ if (isMobile()) return;
292
+
293
+ this.initDragState(e.clientX);
294
+ this.carousel.autoRotate.stop();
295
+ this.carousel.animator.stopMomentum();
296
+ this.carousel.elements.carousel.style.cursor = 'grabbing';
297
+ e.preventDefault();
298
+ });
299
+
300
+ this.addHandler(document, 'mousemove', (e) => {
301
+ if (!this.drag.active) return;
302
+
303
+ const currentTime = Date.now();
304
+ const deltaTime = currentTime - this.drag.lastTime;
305
+ const deltaX = e.clientX - this.drag.lastX;
306
+
307
+ if (deltaTime > 0) {
308
+ this.drag.velocity = deltaX / deltaTime;
309
+ }
310
+
311
+ this.drag.currentX = e.clientX;
312
+ this.drag.lastX = e.clientX;
313
+ this.drag.lastTime = currentTime;
314
+
315
+ const dragDistance = this.drag.currentX - this.drag.startX;
316
+ const { currentIndex } = this.carousel.state;
317
+
318
+ this.carousel.ui.updateDragTransform(dragDistance);
319
+ this.updateDraggingClass(dragDistance, currentIndex, DRAG_CONFIG.mouseThreshold);
320
+
321
+ if (Math.abs(dragDistance) > this.carousel.options.dragThreshold) {
322
+ const direction = dragDistance > 0 ? -1 : 1;
323
+ this.carousel.navigator.rotate(direction);
324
+ this.drag.startX = this.drag.currentX;
325
+ this.resetDragState(currentIndex);
326
+ }
327
+ });
328
+
329
+ this.addHandler(document, 'mouseup', () => {
330
+ if (!this.drag.active) return;
331
+
332
+ this.drag.active = false;
333
+ this.resetMouseCursor();
334
+
335
+ const { currentIndex } = this.carousel.state;
336
+ this.resetDragState(currentIndex);
337
+
338
+ if (Math.abs(this.drag.velocity) > DRAG_CONFIG.velocityThreshold) {
339
+ this.carousel.animator.startMomentum(this.drag.velocity);
340
+ }
341
+ });
342
+
343
+ this.addHandler(this.carousel.elements.carousel, 'mouseleave', () => {
344
+ if (this.drag.active) {
345
+ this.drag.active = false;
346
+ this.resetMouseCursor();
347
+
348
+ const { currentIndex } = this.carousel.state;
349
+ this.resetDragState(currentIndex);
350
+ }
351
+ });
352
+
353
+ if (window.innerWidth > BREAKPOINTS.mobile) {
354
+ this.resetMouseCursor();
355
+ }
356
+ }
357
+
358
+ initResize() {
359
+ let resizeTimer;
360
+ const handler = () => {
361
+ clearTimeout(resizeTimer);
362
+ resizeTimer = setTimeout(() => {
363
+ this.carousel.animator.updateCarousel();
364
+ }, RESIZE_DEBOUNCE);
365
+ };
366
+
367
+ this.addHandler(window, 'resize', handler);
368
+ }
369
+
370
+ addHandler(element, event, handler, options) {
371
+ element.addEventListener(event, handler, options);
372
+
373
+ const key = `${event}-${Date.now()}-${Math.random()}`;
374
+ this.boundHandlers.set(key, { element, event, handler, options });
375
+ }
376
+
377
+ destroy() {
378
+ if (this.wheel.scrollTimeout) {
379
+ clearTimeout(this.wheel.scrollTimeout);
380
+ this.wheel.scrollTimeout = null;
381
+ }
382
+
383
+ for (const { element, event, handler, options } of this.boundHandlers.values()) {
384
+ element.removeEventListener(event, handler, options);
385
+ }
386
+ this.boundHandlers.clear();
387
+
388
+ this.carousel = null;
389
+ }
390
+ }
@@ -0,0 +1,116 @@
1
+ import { normalizeIndex } from '../utils/helpers.js';
2
+
3
+ const PROXIMITY_THRESHOLD = 2;
4
+
5
+ export class Navigator {
6
+ constructor(carousel) {
7
+ this.carousel = carousel;
8
+ }
9
+
10
+ get currentIndex() {
11
+ return this.carousel.state.currentIndex;
12
+ }
13
+
14
+ set currentIndex(value) {
15
+ this.carousel.state.currentIndex = normalizeIndex(value, this.carousel.totalItems);
16
+ }
17
+
18
+ getShortestDistance(from, to) {
19
+ const total = this.carousel.totalItems;
20
+ const normalizedTo = normalizeIndex(to, total);
21
+ const normalizedFrom = normalizeIndex(from, total);
22
+
23
+ const forwardDist = (normalizedTo - normalizedFrom + total) % total;
24
+ const backwardDist = (normalizedFrom - normalizedTo + total) % total;
25
+
26
+ return forwardDist <= backwardDist ? forwardDist : -backwardDist;
27
+ }
28
+
29
+ isNearby(from, to) {
30
+ const distance = Math.abs(this.getShortestDistance(from, to));
31
+ return distance <= PROXIMITY_THRESHOLD;
32
+ }
33
+
34
+ updateAfterNavigation() {
35
+ this.carousel.animator.updateCarousel();
36
+ this.carousel.updateCounter();
37
+
38
+ if (this.carousel.options.preloadRange > 0) {
39
+ this.carousel.preloadImages();
40
+ }
41
+ }
42
+
43
+ rotate(direction) {
44
+ this.currentIndex = this.currentIndex + direction;
45
+ this.updateAfterNavigation();
46
+ }
47
+
48
+ next() {
49
+ this.rotate(1);
50
+ }
51
+
52
+ prev() {
53
+ this.rotate(-1);
54
+ }
55
+
56
+ goTo(index) {
57
+ const normalizedIndex = normalizeIndex(index, this.carousel.totalItems);
58
+ if (normalizedIndex === this.currentIndex) return;
59
+
60
+ this.currentIndex = normalizedIndex;
61
+ this.updateAfterNavigation();
62
+ }
63
+
64
+ navigateIfDifferent(targetIndex, callback) {
65
+ const normalizedIndex = normalizeIndex(targetIndex, this.carousel.totalItems);
66
+ if (normalizedIndex === this.currentIndex) return false;
67
+
68
+ callback(normalizedIndex);
69
+ return true;
70
+ }
71
+
72
+ handleItemClick(index) {
73
+ this.navigateIfDifferent(index, (normalizedIndex) => {
74
+ const { layoutMode } = this.carousel.options;
75
+
76
+ if (layoutMode === 'radial') {
77
+ this.handleRadialItemClick(normalizedIndex);
78
+ } else {
79
+ this.handleStackItemClick(normalizedIndex);
80
+ }
81
+ });
82
+ }
83
+
84
+ handleRadialItemClick(normalizedIndex) {
85
+ const shortestDist = this.getShortestDistance(this.currentIndex, normalizedIndex);
86
+
87
+ if (Math.abs(shortestDist) > 1) {
88
+ const direction = shortestDist > 0 ? 1 : -1;
89
+ this.rotate(direction);
90
+ } else {
91
+ this.rotate(shortestDist);
92
+ }
93
+ }
94
+
95
+ handleStackItemClick(normalizedIndex) {
96
+ if (this.isNearby(this.currentIndex, normalizedIndex)) {
97
+ const shortestDist = this.getShortestDistance(this.currentIndex, normalizedIndex);
98
+ this.rotate(shortestDist);
99
+ } else {
100
+ this.goTo(normalizedIndex);
101
+ }
102
+ }
103
+
104
+ handleIndicatorClick(index) {
105
+ this.navigateIfDifferent(index, (normalizedIndex) => {
106
+ const { layoutMode } = this.carousel.options;
107
+
108
+ if (layoutMode === 'radial') {
109
+ const shortestDist = this.getShortestDistance(this.currentIndex, normalizedIndex);
110
+ this.rotate(shortestDist);
111
+ } else {
112
+ this.goTo(normalizedIndex);
113
+ }
114
+ });
115
+ }
116
+ }
@@ -0,0 +1,170 @@
1
+ import { CLASS_NAMES, ARIA, DURATIONS } from '../core/constants.js';
2
+ import { addClass, removeClass, setAttribute, setCSSVar } from '../utils/dom.js';
3
+
4
+ const DRAG_TRANSFORM_CONFIG = Object.freeze({
5
+ stack: {
6
+ maxDrag: 200,
7
+ offsetMultiplier: 100,
8
+ rotationMultiplier: 3,
9
+ },
10
+ radial: {
11
+ rotationSensitivity: 0.2,
12
+ },
13
+ classic: {
14
+ dragSensitivity: 0.5,
15
+ },
16
+ });
17
+
18
+ export class UIManager {
19
+ constructor(carousel) {
20
+ this.carousel = carousel;
21
+ }
22
+
23
+ updateActiveStates(currentIndex) {
24
+ for (let i = 0; i < this.carousel.items.length; i++) {
25
+ const item = this.carousel.items[i];
26
+ removeClass(item, CLASS_NAMES.itemActive, CLASS_NAMES.itemPrev, CLASS_NAMES.itemNext);
27
+ item.removeAttribute(ARIA.current);
28
+ }
29
+
30
+ for (let i = 0; i < this.carousel.indicators.length; i++) {
31
+ const indicator = this.carousel.indicators[i];
32
+ removeClass(
33
+ indicator,
34
+ CLASS_NAMES.indicatorActive,
35
+ CLASS_NAMES.indicatorProgress
36
+ );
37
+ setAttribute(indicator, ARIA.selected, 'false');
38
+ setAttribute(indicator, ARIA.tabindex, '-1');
39
+ }
40
+
41
+ const currentItem = this.carousel.items[currentIndex];
42
+ const currentIndicator = this.carousel.indicators[currentIndex];
43
+
44
+ if (currentItem) {
45
+ addClass(currentItem, CLASS_NAMES.itemActive);
46
+ setAttribute(currentItem, ARIA.current, 'true');
47
+ }
48
+
49
+ if (currentIndicator) {
50
+ removeClass(currentIndicator, CLASS_NAMES.indicatorCompleted);
51
+ addClass(currentIndicator, CLASS_NAMES.indicatorActive);
52
+ setAttribute(currentIndicator, ARIA.selected, 'true');
53
+ setAttribute(currentIndicator, ARIA.tabindex, '0');
54
+
55
+ if (this.carousel.autoRotate.isActive) {
56
+ this.updateIndicatorProgress(currentIndicator);
57
+ }
58
+ }
59
+ }
60
+
61
+ updateIndicatorProgress(indicator) {
62
+ setCSSVar(
63
+ indicator,
64
+ '--progress-duration',
65
+ `${this.carousel.options.autoRotateInterval}ms`
66
+ );
67
+
68
+ setTimeout(() => {
69
+ if (indicator) {
70
+ addClass(indicator, CLASS_NAMES.indicatorProgress);
71
+ }
72
+ }, DURATIONS.progressReset);
73
+ }
74
+
75
+ clearPeekItems() {
76
+ for (let i = 0; i < this.carousel.items.length; i++) {
77
+ const item = this.carousel.items[i];
78
+ removeClass(item, CLASS_NAMES.itemPrev, CLASS_NAMES.itemNext);
79
+ }
80
+ }
81
+
82
+ setPeekItems(prevIndex, nextIndex) {
83
+ const prevItem = this.carousel.items[prevIndex];
84
+ const nextItem = this.carousel.items[nextIndex];
85
+
86
+ if (prevItem) addClass(prevItem, CLASS_NAMES.itemPrev);
87
+ if (nextItem) addClass(nextItem, CLASS_NAMES.itemNext);
88
+ }
89
+
90
+ updateAutoRotateButton(isActive) {
91
+ const { autoRotateBtn } = this.carousel.elements;
92
+ if (!autoRotateBtn) return;
93
+
94
+ if (isActive) {
95
+ addClass(autoRotateBtn, CLASS_NAMES.btnActive);
96
+ setAttribute(autoRotateBtn, ARIA.pressed, 'true');
97
+ } else {
98
+ removeClass(autoRotateBtn, CLASS_NAMES.btnActive);
99
+ setAttribute(autoRotateBtn, ARIA.pressed, 'false');
100
+ }
101
+ }
102
+
103
+ addDraggingClass(index, direction) {
104
+ const item = this.carousel.items[index];
105
+ if (!item) return;
106
+
107
+ const leftClass = CLASS_NAMES.itemDraggingLeft;
108
+ const rightClass = CLASS_NAMES.itemDraggingRight;
109
+
110
+ removeClass(item, leftClass, rightClass);
111
+
112
+ if (direction === 'left') {
113
+ addClass(item, leftClass);
114
+ } else if (direction === 'right') {
115
+ addClass(item, rightClass);
116
+ }
117
+ }
118
+
119
+ removeDraggingClass(index) {
120
+ const item = this.carousel.items[index];
121
+ if (!item) return;
122
+
123
+ removeClass(item, CLASS_NAMES.itemDraggingLeft, CLASS_NAMES.itemDraggingRight);
124
+ }
125
+
126
+ round(value, decimals = 2) {
127
+ return Math.round(value * 10 ** decimals) / 10 ** decimals;
128
+ }
129
+
130
+ applyEasing(progress) {
131
+ return progress * (2 - Math.abs(progress));
132
+ }
133
+
134
+ updateDragTransform(dragDistance) {
135
+ const { layoutMode } = this.carousel.options;
136
+
137
+ if (layoutMode === 'stack') {
138
+ // [개발참고] Stack 모드: 탄성 효과 적용 (easeOutQuad)
139
+ const config = DRAG_TRANSFORM_CONFIG.stack;
140
+ const clampedDrag = Math.max(-config.maxDrag, Math.min(config.maxDrag, dragDistance));
141
+ const progress = clampedDrag / config.maxDrag;
142
+ const easedProgress = this.applyEasing(progress);
143
+
144
+ const dragOffset = this.round(easedProgress * config.offsetMultiplier);
145
+ const dragRotation = this.round(easedProgress * config.rotationMultiplier);
146
+
147
+ this.carousel.container.style.setProperty('--drag-offset', `${dragOffset}px`);
148
+ this.carousel.container.style.setProperty('--drag-rotation', `${dragRotation}deg`);
149
+ } else if (layoutMode === 'radial') {
150
+ const config = DRAG_TRANSFORM_CONFIG.radial;
151
+ const dragRotation = this.round(dragDistance * config.rotationSensitivity);
152
+
153
+ this.carousel.container.style.setProperty('--drag-rotation-y', `${dragRotation}deg`);
154
+ } else if (layoutMode === 'classic') {
155
+ const config = DRAG_TRANSFORM_CONFIG.classic;
156
+ const dragOffset = this.round(dragDistance * config.dragSensitivity);
157
+
158
+ this.carousel.container.style.setProperty('--drag-offset', `${dragOffset}px`);
159
+ }
160
+ }
161
+
162
+ clearDragTransform() {
163
+ this.carousel.container.style.setProperty('--drag-offset', '0px');
164
+ this.carousel.container.style.setProperty('--drag-rotation', '0deg');
165
+ this.carousel.container.style.setProperty('--drag-rotation-y', '0deg');
166
+ }
167
+
168
+ destroy() {
169
+ }
170
+ }
@@ -0,0 +1,34 @@
1
+ export interface PeekCarouselOptions {
2
+ startIndex?: number;
3
+ layoutMode?: 'stack' | 'radial' | 'classic';
4
+ autoRotate?: boolean;
5
+ autoRotateInterval?: number;
6
+ swipeThreshold?: number;
7
+ dragThreshold?: number;
8
+ preloadRange?: number;
9
+ enableKeyboard?: boolean;
10
+ enableWheel?: boolean;
11
+ enableTouch?: boolean;
12
+ enableMouse?: boolean;
13
+ showNavigation?: boolean;
14
+ showCounter?: boolean;
15
+ showIndicators?: boolean;
16
+ showAutoRotateButton?: boolean;
17
+ }
18
+
19
+ export default class PeekCarousel {
20
+ currentIndex: number;
21
+ totalItems: number;
22
+ isAutoRotating: boolean;
23
+
24
+ constructor(selector: string | HTMLElement, options?: PeekCarouselOptions);
25
+
26
+ next(): void;
27
+ prev(): void;
28
+ goTo(index: number): void;
29
+ startAutoRotate(): void;
30
+ stopAutoRotate(): void;
31
+ toggleAutoRotate(): void;
32
+ updateLayoutClass(): void;
33
+ destroy(): void;
34
+ }