peek-carousel 1.0.2 → 1.0.4

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.
@@ -1,404 +0,0 @@
1
- const DEFAULT_LAYOUT_MODE = 'stack';
2
-
3
- const DEFAULT_OPTIONS = {
4
- startIndex: 0,
5
- layoutMode: DEFAULT_LAYOUT_MODE,
6
- autoRotate: false,
7
- autoRotateInterval: 3000,
8
- swipeThreshold: 50,
9
- dragThreshold: 80,
10
- preloadRange: 2,
11
- enableKeyboard: true,
12
- enableWheel: true,
13
- enableTouch: true,
14
- enableMouse: true
15
- };
16
-
17
- const SELECTORS = {
18
- carousel: '#myCarousel',
19
- nav: '.peek-carousel__nav',
20
- controls: '.peek-carousel__controls',
21
- counter: '.peek-carousel__counter',
22
- prevBtn: '.prev-btn',
23
- layoutButton: '.button__group-item',
24
- layoutButtonActive: '.button__group-item--active[data-layout]',
25
- layoutModeGroup: '[aria-label="Layout mode selection"]',
26
- controlsToggle: '.controls__toggle',
27
- controlsClose: '.controls__close',
28
- controlsPanel: '.controls__panel',
29
- keyboardToggle: '.keyboard__toggle',
30
- keyboardPanel: '.keyboard__panel',
31
- mouseToggle: '.mouse__toggle',
32
- mousePanel: '.mouse__panel',
33
- };
34
-
35
- const AUTO_GENERATED_SELECTORS = [
36
- SELECTORS.nav,
37
- SELECTORS.controls,
38
- SELECTORS.counter,
39
- ];
40
-
41
- const TOGGLEABLE_UI_IDS = [
42
- 'showNavigation',
43
- 'showCounter',
44
- 'showIndicators',
45
- 'showAutoRotateButton'
46
- ];
47
-
48
- const PANELS = [
49
- { name: 'api', panelClass: 'api__panel', toggleClass: 'api__toggle' },
50
- { name: 'controls', panelClass: 'controls__panel', toggleClass: 'controls__toggle' },
51
- { name: 'keyboard', panelClass: 'keyboard__panel', toggleClass: 'keyboard__toggle' },
52
- { name: 'mouse', panelClass: 'mouse__panel', toggleClass: 'mouse__toggle' },
53
- { name: 'share', panelClass: 'share__panel', toggleClass: 'share__toggle' }
54
- ];
55
-
56
- let elements = {};
57
- let panelElements = {};
58
- let carousel = null;
59
-
60
- const buildCarouselOptions = (currentIndex = DEFAULT_OPTIONS.startIndex) => {
61
- const layoutMode = document.querySelector(SELECTORS.layoutButtonActive)?.dataset.layout || DEFAULT_LAYOUT_MODE;
62
-
63
- return {
64
- ...DEFAULT_OPTIONS,
65
- startIndex: currentIndex,
66
- layoutMode,
67
- showNavigation: elements.showNavigation?.checked ?? true,
68
- showCounter: elements.showCounter?.checked ?? true,
69
- showIndicators: elements.showIndicators?.checked ?? true,
70
- showAutoRotateButton: elements.showAutoRotateButton?.checked ?? true,
71
- };
72
- };
73
-
74
- const removeAutoGeneratedElements = (container) => {
75
- for (const selector of AUTO_GENERATED_SELECTORS) {
76
- const element = container.querySelector(selector);
77
- if (element) element.remove();
78
- }
79
- };
80
-
81
- const reinitializeCarousel = () => {
82
- const currentIndex = carousel.state.currentIndex;
83
- carousel.destroy();
84
- removeAutoGeneratedElements(carousel.container);
85
- carousel = new PeekCarousel(SELECTORS.carousel, buildCarouselOptions(currentIndex));
86
- moveCounterToNavigation();
87
- };
88
-
89
- const closeAllPanels = (exceptPanelName = null) => {
90
- for (const name in panelElements) {
91
- if (name === exceptPanelName) continue;
92
-
93
- const { panel, toggle, activeClass } = panelElements[name];
94
- panel?.classList.remove(activeClass);
95
- toggle?.setAttribute('aria-expanded', 'false');
96
- }
97
- };
98
-
99
- const createTogglePanel = (panelName) => {
100
- return () => {
101
- const cachedPanel = panelElements[panelName];
102
- if (!cachedPanel) return;
103
-
104
- const { panel, toggle, activeClass } = cachedPanel;
105
- const isActive = panel.classList.toggle(activeClass);
106
-
107
- toggle?.setAttribute('aria-expanded', isActive);
108
-
109
- if (isActive) {
110
- closeAllPanels(panelName);
111
- }
112
- };
113
- };
114
-
115
- const toggleAPIPanel = createTogglePanel('api');
116
- const toggleControlsPanel = createTogglePanel('controls');
117
- const toggleKeyboardPanel = createTogglePanel('keyboard');
118
- const toggleMousePanel = createTogglePanel('mouse');
119
- const toggleSharePanel = createTogglePanel('share');
120
-
121
- const handleLayoutModeChange = (buttonGroup, clickedButton) => {
122
- const buttons = buttonGroup.querySelectorAll(SELECTORS.layoutButton);
123
- for (const btn of buttons) {
124
- btn.classList.remove('button__group-item--active');
125
- btn.setAttribute('aria-pressed', 'false');
126
- }
127
-
128
- clickedButton.classList.add('button__group-item--active');
129
- clickedButton.setAttribute('aria-pressed', 'true');
130
-
131
- const layoutMode = clickedButton.dataset.layout;
132
- carousel.options.layoutMode = layoutMode;
133
- carousel.updateLayoutClass();
134
-
135
- // [데모용] stack 모드에서 transform 초기화
136
- if (layoutMode === DEFAULT_LAYOUT_MODE) {
137
- carousel.elements.carousel.style.transform = 'none';
138
- }
139
-
140
- carousel.animator.updateCarousel();
141
- };
142
-
143
- const attachEventListeners = () => {
144
- const apiToggleBtn = document.querySelector('.api__toggle');
145
- const toggleBtn = document.querySelector(SELECTORS.controlsToggle);
146
- const keyboardToggleBtn = document.querySelector(SELECTORS.keyboardToggle);
147
- const mouseToggleBtn = document.querySelector(SELECTORS.mouseToggle);
148
- const shareToggleBtn = document.querySelector('.share__toggle');
149
- const closeBtn = document.querySelector(SELECTORS.controlsClose);
150
- const layoutModeGroup = document.querySelector(SELECTORS.layoutModeGroup);
151
-
152
- apiToggleBtn?.addEventListener('click', toggleAPIPanel);
153
- toggleBtn?.addEventListener('click', toggleControlsPanel);
154
- keyboardToggleBtn?.addEventListener('click', toggleKeyboardPanel);
155
- mouseToggleBtn?.addEventListener('click', toggleMousePanel);
156
- shareToggleBtn?.addEventListener('click', toggleSharePanel);
157
- closeBtn?.addEventListener('click', toggleControlsPanel);
158
-
159
- layoutModeGroup?.addEventListener('click', (event) => {
160
- const clickedButton = event.target.closest(SELECTORS.layoutButton);
161
- if (clickedButton) {
162
- handleLayoutModeChange(layoutModeGroup, clickedButton);
163
- }
164
- });
165
-
166
- for (const id of TOGGLEABLE_UI_IDS) {
167
- elements[id]?.addEventListener('change', reinitializeCarousel);
168
- }
169
- };
170
-
171
- const cacheElements = () => {
172
- elements = TOGGLEABLE_UI_IDS.reduce((acc, id) => {
173
- acc[id] = document.getElementById(id);
174
- return acc;
175
- }, {});
176
-
177
- panelElements = PANELS.reduce((acc, config) => {
178
- acc[config.name] = {
179
- panel: document.querySelector(`.${config.panelClass}`),
180
- toggle: document.querySelector(`.${config.toggleClass}`),
181
- activeClass: `${config.panelClass}--active`,
182
- };
183
- return acc;
184
- }, {});
185
- };
186
-
187
- // [데모용] 카운터를 네비게이션 내부로 이동 (이전 버튼 옆에 배치)
188
- const moveCounterToNavigation = () => {
189
- const nav = carousel.container.querySelector(SELECTORS.nav);
190
- if (!nav) return;
191
-
192
- const counter = carousel.container.querySelector(SELECTORS.counter);
193
- if (!counter) return;
194
-
195
- const prevBtn = nav.querySelector(SELECTORS.prevBtn);
196
- if (!prevBtn) return;
197
-
198
- nav.insertBefore(counter, prevBtn.nextSibling);
199
- };
200
-
201
- // API Demo functionality
202
- const initAPIDemo = () => {
203
- const apiButtons = document.querySelectorAll('.api-btn');
204
- const apiOutput = document.getElementById('apiOutput');
205
-
206
- if (!apiButtons.length || !apiOutput) return;
207
-
208
- const handleAPIButtonClick = (event) => {
209
- const button = event.target.closest('.api-btn');
210
- if (!button) return;
211
-
212
- const action = button.dataset.api;
213
- let result;
214
-
215
- try {
216
- switch (action) {
217
- case 'next':
218
- carousel.next();
219
- result = '✓ Moved to next slide';
220
- break;
221
- case 'prev':
222
- carousel.prev();
223
- result = '✓ Moved to previous slide';
224
- break;
225
- case 'goTo':
226
- carousel.goTo(2);
227
- result = '✓ Moved to slide index 2';
228
- break;
229
- case 'startAutoRotate':
230
- carousel.startAutoRotate();
231
- result = '✓ Auto-rotation started';
232
- break;
233
- case 'stopAutoRotate':
234
- carousel.stopAutoRotate();
235
- result = '✓ Auto-rotation stopped';
236
- break;
237
- case 'toggleAutoRotate':
238
- carousel.toggleAutoRotate();
239
- result = `✓ Auto-rotation toggled (now: ${carousel.isAutoRotating ? 'ON' : 'OFF'})`;
240
- break;
241
- case 'getCurrentIndex':
242
- result = `currentIndex: ${carousel.currentIndex}`;
243
- break;
244
- case 'getTotalItems':
245
- result = `totalItems: ${carousel.totalItems}`;
246
- break;
247
- case 'getIsAutoRotating':
248
- result = `isAutoRotating: ${carousel.isAutoRotating}`;
249
- break;
250
- default:
251
- result = 'Unknown API method';
252
- }
253
-
254
- apiOutput.textContent = result;
255
- apiOutput.style.color = '#10b981';
256
- } catch (error) {
257
- apiOutput.textContent = `Error: ${error.message}`;
258
- apiOutput.style.color = '#ef4444';
259
- }
260
- };
261
-
262
- for (let i = 0; i < apiButtons.length; i++) {
263
- apiButtons[i].addEventListener('click', handleAPIButtonClick);
264
- }
265
- };
266
-
267
- // API Search & Filter functionality
268
- const initAPISearchFilter = () => {
269
- const searchInput = document.getElementById('apiSearch');
270
- const filterButtons = document.querySelectorAll('.api-filter__btn');
271
- const apiItems = document.querySelectorAll('.api-method, .api-property');
272
-
273
- if (!searchInput || !filterButtons.length || !apiItems.length) {
274
- console.warn('API Search/Filter elements not found');
275
- return;
276
- }
277
-
278
- console.log('API Search/Filter initialized with', apiItems.length, 'items');
279
-
280
- let currentFilter = 'all';
281
- let currentSearch = '';
282
-
283
- const filterItems = () => {
284
- let visibleCount = 0;
285
-
286
- apiItems.forEach(item => {
287
- const category = item.dataset.category || '';
288
- const searchText = (item.dataset.search || '').toLowerCase();
289
- const matchesFilter = currentFilter === 'all' || category === currentFilter;
290
- const matchesSearch = currentSearch === '' || searchText.includes(currentSearch.toLowerCase());
291
-
292
- if (matchesFilter && matchesSearch) {
293
- item.style.display = '';
294
- visibleCount++;
295
- // Add fade-in animation
296
- item.style.animation = 'fadeIn 0.3s ease-in';
297
- } else {
298
- item.style.display = 'none';
299
- }
300
- });
301
-
302
- console.log('Filtered:', visibleCount, 'visible items (filter:', currentFilter, 'search:', currentSearch, ')');
303
-
304
- // Show "no results" message if needed
305
- const methodsGrid = document.querySelector('#api-methods .api-docs__grid');
306
- const propertiesGrid = document.querySelector('#api-properties .api-docs__grid');
307
-
308
- const showNoResults = (grid, hasVisible) => {
309
- if (!grid) return;
310
-
311
- let noResultsMsg = grid.querySelector('.no-results');
312
- if (!hasVisible) {
313
- if (!noResultsMsg) {
314
- noResultsMsg = document.createElement('div');
315
- noResultsMsg.className = 'no-results';
316
- noResultsMsg.textContent = 'No matching items found';
317
- noResultsMsg.style.cssText = 'grid-column: 1/-1; text-align: center; padding: 2rem; color: rgba(255,255,255,0.6);';
318
- grid.appendChild(noResultsMsg);
319
- }
320
- } else if (noResultsMsg) {
321
- noResultsMsg.remove();
322
- }
323
- };
324
-
325
- const methodsVisible = Array.from(methodsGrid?.querySelectorAll('.api-method') || []).some(el => el.style.display !== 'none');
326
- const propertiesVisible = Array.from(propertiesGrid?.querySelectorAll('.api-property') || []).some(el => el.style.display !== 'none');
327
-
328
- showNoResults(methodsGrid, methodsVisible);
329
- showNoResults(propertiesGrid, propertiesVisible);
330
- };
331
-
332
- // Handle search input
333
- searchInput.addEventListener('input', (e) => {
334
- currentSearch = e.target.value;
335
- console.log('Search input:', currentSearch);
336
- filterItems();
337
- });
338
-
339
- // Handle filter buttons
340
- filterButtons.forEach(button => {
341
- button.addEventListener('click', () => {
342
- // Update active state
343
- filterButtons.forEach(btn => btn.classList.remove('active'));
344
- button.classList.add('active');
345
-
346
- // Update filter
347
- currentFilter = button.dataset.filter;
348
- console.log('Filter clicked:', currentFilter);
349
- filterItems();
350
- });
351
- });
352
- };
353
-
354
- // Smooth scroll for anchor links
355
- const initSmoothScroll = () => {
356
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
357
- anchor.addEventListener('click', function (e) {
358
- const href = this.getAttribute('href');
359
- if (href === '#') return;
360
-
361
- e.preventDefault();
362
- const target = document.querySelector(href);
363
- if (target) {
364
- const offsetTop = target.offsetTop - 20;
365
- window.scrollTo({
366
- top: offsetTop,
367
- behavior: 'smooth'
368
- });
369
- }
370
- });
371
- });
372
- };
373
-
374
- // Add fade-in animation keyframes
375
- const addAnimationStyles = () => {
376
- if (!document.getElementById('api-animations')) {
377
- const style = document.createElement('style');
378
- style.id = 'api-animations';
379
- style.textContent = `
380
- @keyframes fadeIn {
381
- from { opacity: 0; transform: translateY(10px); }
382
- to { opacity: 1; transform: translateY(0); }
383
- }
384
- `;
385
- document.head.appendChild(style);
386
- }
387
- };
388
-
389
- const init = () => {
390
- cacheElements();
391
- carousel = new PeekCarousel(SELECTORS.carousel, buildCarouselOptions());
392
- moveCounterToNavigation();
393
- attachEventListeners();
394
- initAPIDemo();
395
- initAPISearchFilter();
396
- initSmoothScroll();
397
- addAnimationStyles();
398
- };
399
-
400
- if (document.readyState === 'loading') {
401
- document.addEventListener('DOMContentLoaded', init);
402
- } else {
403
- init();
404
- }