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,404 @@
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
+ }
@@ -0,0 +1 @@
1
+ :root{--color-primary:#667eea;--color-secondary:#764ba2;--color-bg-dark:#1a1a2e;--color-bg-black:#000;--color-white:#fff;--color-overlay:rgba(0,0,0,.7);--color-overlay-hover:rgba(0,0,0,.8);--color-border:hsla(0,0%,100%,.2);--color-border-hover:hsla(0,0%,100%,.35);--color-bg-item:hsla(0,0%,100%,.05);--color-bg-item-hover:hsla(0,0%,100%,.1);--color-bg-item-light:hsla(0,0%,100%,.08);--spacing-xs:8px;--spacing-sm:10px;--spacing-md:12px;--spacing-lg:20px;--border-radius:8px;--border-radius-lg:12px;--button-size:48px;--button-size-mobile:40px;--transition:all 0.3s ease;--transition-fast:all 0.2s ease}*{box-sizing:border-box;margin:0;padding:0}body,html{height:100%;overflow:hidden}body{background:radial-gradient(ellipse at center,var(--color-bg-dark) 0,var(--color-bg-black) 100%);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;min-height:100vh;position:relative;width:100%}body:before{animation:gridMove 20s linear infinite;background-image:linear-gradient(rgba(102,126,234,.03) 1px,transparent 0),linear-gradient(90deg,rgba(102,126,234,.03) 1px,transparent 0),linear-gradient(rgba(102,126,234,.05) 1px,transparent 0),linear-gradient(90deg,rgba(102,126,234,.05) 1px,transparent 0);background-position:0 0,0 0,0 0,0 0;background-size:20px 20px,20px 20px,100px 100px,100px 100px;content:"";inset:0;position:absolute;z-index:-1}@keyframes gridMove{0%{background-position:0 0,0 0,0 0,0 0}to{background-position:20px 20px,20px 20px,100px 100px,100px 100px}}.wrapper{display:grid;height:100vh;place-items:center;position:relative;width:100vw}.circle__button{align-items:center;backdrop-filter:blur(12px);background:var(--color-overlay);border:1px solid var(--color-border);border-radius:50%;color:var(--color-white);display:flex;height:var(--button-size);justify-content:center;transition:var(--transition);width:var(--button-size)}.circle__button:hover{background:var(--color-overlay-hover);border-color:var(--color-border-hover)}.github__link{left:var(--spacing-lg);position:absolute;text-decoration:none;top:var(--spacing-lg);z-index:200}.github__link svg{transition:transform .3s ease}.github__link:hover svg{animation:octocat-wave .6s ease-in-out}.tooltip{backdrop-filter:blur(12px);background:var(--color-overlay-hover);border:1px solid var(--color-border);border-radius:6px;color:var(--color-white);font-size:.875rem;left:100%;margin-left:var(--spacing-md);opacity:0;padding:.5rem .75rem;pointer-events:none;position:absolute;top:50%;transform:translateY(-50%);transition:var(--transition-fast);white-space:nowrap}.tooltip:before{border:6px solid transparent;border-right:6px solid var(--color-border)}.tooltip:after,.tooltip:before{content:"";position:absolute;right:100%;top:50%;transform:translateY(-50%)}.tooltip:after{border:5px solid transparent;border-right:5px solid var(--color-overlay-hover);margin-right:-1px}.github__link:hover .tooltip{opacity:1;transform:translateY(-50%) translateX(4px)}@keyframes octocat-wave{0%,to{transform:rotate(0deg)}20%{transform:rotate(-15deg)}40%{transform:rotate(10deg)}60%{transform:rotate(-10deg)}80%{transform:rotate(5deg)}}.intro__text{color:#fff;left:50%;pointer-events:none;position:absolute;text-align:center;top:10%;transform:translateX(-50%);z-index:10}.intro__text h1{background:linear-gradient(135deg,var(--color-primary) 0,var(--color-secondary) 100%);-webkit-background-clip:text;font-size:2.5rem;font-weight:700;margin-bottom:1rem;-webkit-text-fill-color:transparent;background-clip:text}.intro__text p{font-size:1.2rem;opacity:.8}.top__controls{display:flex;gap:var(--spacing-sm);position:absolute;right:var(--spacing-lg);top:var(--spacing-lg);z-index:250}.controls__toggle,.keyboard__toggle,.mouse__toggle{cursor:pointer}.controls__toggle:hover,.keyboard__toggle:hover,.mouse__toggle:hover{background:var(--color-overlay-hover);border-color:var(--color-border-hover)}.controls__toggle:hover .tooltip,.keyboard__toggle:hover .tooltip,.mouse__toggle:hover .tooltip{opacity:1;transform:translateY(-50%) translateX(-4px)}.controls__toggle[aria-expanded=true],.keyboard__toggle[aria-expanded=true],.mouse__toggle[aria-expanded=true]{background:var(--color-overlay-hover);border-color:var(--color-primary)}.controls__toggle svg{transition:transform .3s ease}.controls__toggle[aria-expanded=true] svg{transform:rotate(90deg)}.controls__toggle .tooltip,.keyboard__toggle .tooltip,.mouse__toggle .tooltip{left:auto;margin-left:0;margin-right:var(--spacing-md);right:100%;transform:translateY(-50%)}.controls__toggle .tooltip:before,.keyboard__toggle .tooltip:before,.mouse__toggle .tooltip:before{border-left-color:var(--color-border);border-right-color:transparent;left:100%;right:auto}.controls__toggle .tooltip:after,.keyboard__toggle .tooltip:after,.mouse__toggle .tooltip:after{border-left-color:var(--color-overlay-hover);border-right-color:transparent;left:100%;margin-left:-1px;margin-right:0;right:auto}.controls__panel,.keyboard__panel,.mouse__panel{backdrop-filter:blur(16px);background:var(--color-overlay);border:1px solid var(--color-border);border-radius:var(--border-radius-lg);max-height:calc(100vh - var(--spacing-lg) - var(--button-size) - var(--spacing-sm) - var(--spacing-lg));opacity:0;overflow-y:auto;padding:1.25rem;pointer-events:none;position:absolute;right:var(--spacing-lg);top:calc(var(--spacing-lg) + var(--button-size) + var(--spacing-sm));transform:translateY(-10px);transition:transform .25s ease,opacity .25s ease,visibility .25s;visibility:hidden;width:300px;z-index:200}.keyboard__panel{right:calc(var(--spacing-lg) + (var(--button-size) + var(--spacing-sm))*2)}.mouse__panel{right:calc(var(--spacing-lg) + var(--button-size) + var(--spacing-sm))}.controls__panel:before,.keyboard__panel:before,.mouse__panel:before{border-bottom:8px solid var(--color-border);border-left:8px solid transparent;border-right:8px solid transparent;content:"";height:0;position:absolute;right:14px;top:-8px;width:0}.controls__panel:after,.keyboard__panel:after,.mouse__panel:after{border-bottom:7px solid var(--color-overlay);border-left:7px solid transparent;border-right:7px solid transparent;content:"";height:0;position:absolute;right:15px;top:-6px;width:0}.controls__panel--active,.keyboard__panel--active,.mouse__panel--active{opacity:1;pointer-events:auto;transform:translateY(0);visibility:visible}.controls__header,.keyboard__header,.mouse__header{border-bottom:1px solid hsla(0,0%,100%,.1);margin-bottom:1.25rem;padding-bottom:1rem}.controls__header h3,.keyboard__header h3,.mouse__header h3{color:var(--color-white);font-size:1.125rem;font-weight:600;margin:0}.control__section{margin-bottom:1.25rem}.control__section:last-child{margin-bottom:0}.section__label{color:var(--color-white);display:block;font-size:.8125rem;font-weight:600;letter-spacing:.05em;margin-bottom:.75rem;opacity:.7;text-transform:uppercase}.button__group{display:grid;gap:.5rem;grid-template-columns:repeat(3,1fr)}.button__group-item{align-items:center;background:var(--color-bg-item);border:1px solid var(--color-border);border-radius:var(--border-radius);color:var(--color-white);cursor:pointer;display:flex;flex-direction:column;font-size:.75rem;font-weight:500;gap:.375rem;padding:.75rem .5rem;transition:var(--transition)}.button__group-item:hover{background:var(--color-bg-item-hover);border-color:var(--color-border-hover)}.button__group-item--active{background:var(--color-primary);border-color:var(--color-primary)}.button__group-item svg{opacity:.8}.button__group-item--active svg{opacity:1}.switch__group{display:flex;flex-direction:column;gap:.75rem}.switch__item{align-items:center;background:var(--color-bg-item);border:1px solid var(--color-border);border-radius:var(--border-radius);cursor:pointer;display:flex;justify-content:space-between;padding:.75rem;position:relative;transition:var(--transition)}.switch__item:hover{background:var(--color-bg-item-light);border-color:var(--color-border-hover)}.switch__label{align-items:center;color:var(--color-white);display:flex;font-size:.875rem;font-weight:500;gap:.5rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}.switch__label svg{opacity:.8}.switch__item input[type=checkbox]{height:0;opacity:0;position:absolute;width:0}.switch__slider{background:hsla(0,0%,100%,.2);border-radius:12px;height:24px;position:relative;transition:var(--transition);width:44px}.switch__slider:before{background:var(--color-white);border-radius:50%;content:"";height:20px;left:2px;position:absolute;top:2px;transition:var(--transition);width:20px}.switch__item input[type=checkbox]:checked~.switch__slider{background:var(--color-primary)}.switch__item input[type=checkbox]:checked~.switch__slider:before{transform:translateX(20px)}.keyboard__shortcuts{display:flex;flex-direction:column;gap:.75rem}.shortcut__item{align-items:center;background:var(--color-bg-item);border:1px solid var(--color-border);border-radius:var(--border-radius);display:flex;justify-content:space-between;padding:.75rem;transition:var(--transition)}.shortcut__item:hover{border-color:var(--color-border-hover)}.shortcut__item:hover,.shortcut__key{background:var(--color-bg-item-light)}.shortcut__key{align-items:center;border:1px solid var(--color-border);border-radius:4px;box-shadow:0 2px 0 rgba(0,0,0,.2);color:var(--color-white);display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,monospace;font-size:.8125rem;font-weight:600;justify-content:center;min-width:2.5rem;padding:.375rem .625rem}.shortcut__key--range{letter-spacing:.05em;min-width:3.5rem}.shortcut__description{color:var(--color-white);font-size:.875rem;font-weight:500}.mouse__controls{display:flex;flex-direction:column;gap:.75rem}.control__item{align-items:flex-start;background:var(--color-bg-item);border:1px solid var(--color-border);border-radius:var(--border-radius);display:flex;gap:.875rem;padding:.875rem;transition:var(--transition)}.control__item:hover{border-color:var(--color-border-hover)}.control__icon,.control__item:hover{background:var(--color-bg-item-light)}.control__icon{align-items:center;border:1px solid var(--color-border);border-radius:6px;color:var(--color-primary);display:flex;flex-shrink:0;height:2.5rem;justify-content:center;width:2.5rem}.control__content{display:flex;flex:1;flex-direction:column;gap:.25rem;min-width:0}.control__title{color:var(--color-white);font-size:.875rem;font-weight:600;line-height:1.3}.control__description{color:hsla(0,0%,100%,.65);font-size:.75rem;font-weight:400;line-height:1.4}.peek-carousel__caption-title{display:block;font-size:1.8rem;font-weight:700;margin-bottom:.5rem}.peek-carousel__caption-subtitle{color:hsla(0,0%,100%,.8);font-size:1.1rem}.peek-carousel__nav{align-items:center;bottom:7rem;display:flex;gap:1rem;left:50%;position:absolute;transform:translateX(-50%)}.peek-carousel__counter{margin:0;position:static!important;transform:none!important}.peek-carousel>.peek-carousel__counter{align-items:center;backdrop-filter:blur(12px);background:var(--color-overlay);border:1px solid var(--color-border);border-radius:var(--border-radius);bottom:7rem;color:var(--color-white);display:flex;font-size:.875rem;font-weight:500;height:-moz-max-content;height:max-content;justify-content:center;left:50%;line-height:1;padding:.5rem 1rem;position:absolute!important;transform:translateX(-50%)!important}.peek-carousel__controls{bottom:3rem}.share__panel{backdrop-filter:blur(16px);background:var(--color-overlay);border:1px solid var(--color-border);border-radius:var(--border-radius-lg);max-height:calc(100vh - var(--spacing-lg) - var(--button-size) - var(--spacing-sm) - var(--spacing-lg));opacity:0;overflow-y:auto;padding:1.25rem;pointer-events:none;position:absolute;right:var(--spacing-lg);top:calc(var(--spacing-lg) + var(--button-size) + var(--spacing-sm));transform:translateY(-10px);transition:transform .25s ease,opacity .25s ease,visibility .25s;visibility:hidden;width:300px;z-index:200}.share__panel:before{border-bottom:8px solid var(--color-border);border-left:8px solid transparent;border-right:8px solid transparent;right:calc(14px + (var(--button-size) + var(--spacing-sm))*0);top:-8px}.share__panel:after,.share__panel:before{content:"";height:0;position:absolute;width:0}.share__panel:after{border-bottom:7px solid var(--color-overlay);border-left:7px solid transparent;border-right:7px solid transparent;right:calc(15px + (var(--button-size) + var(--spacing-sm))*0);top:-6px}.share__panel--active{opacity:1;pointer-events:auto;transform:translateY(0);visibility:visible}.share__header{border-bottom:1px solid hsla(0,0%,100%,.1);margin-bottom:1.25rem;padding-bottom:1rem}.share__header h3{color:var(--color-white);font-size:1.125rem;font-weight:600;margin:0}.share__buttons{display:flex;flex-direction:column;gap:.75rem}.share__item{align-items:flex-start;background:var(--color-bg-item);border:1px solid var(--color-border);border-radius:var(--border-radius);display:flex;gap:.875rem;padding:.875rem;text-decoration:none;transition:var(--transition)}.share__item:hover{border-color:var(--color-border-hover)}.share__icon,.share__item:hover{background:var(--color-bg-item-light)}.share__icon{align-items:center;border:1px solid var(--color-border);border-radius:6px;color:var(--color-primary);display:flex;flex-shrink:0;height:2.5rem;justify-content:center;transition:var(--transition);width:2.5rem}.share__item--twitter:hover .share__icon{background:#000;border-color:#000;color:var(--color-white)}.share__item--facebook:hover .share__icon{background:#1877f2;border-color:#1877f2;color:var(--color-white)}.share__item--linkedin:hover .share__icon{background:#0a66c2;border-color:#0a66c2;color:var(--color-white)}.share__item--reddit:hover .share__icon{background:#ff4500;border-color:#ff4500;color:var(--color-white)}.share__content{display:flex;flex:1;flex-direction:column;gap:.25rem;min-width:0}.share__title{color:var(--color-white);font-size:.875rem;font-weight:600;line-height:1.3}.share__description{color:hsla(0,0%,100%,.65);font-size:.75rem;font-weight:400;line-height:1.4}.share__toggle{cursor:pointer}.share__toggle:hover{background:var(--color-overlay-hover);border-color:var(--color-border-hover)}.share__toggle:hover .tooltip{opacity:1;transform:translateY(-50%) translateX(-4px)}.share__toggle[aria-expanded=true]{background:var(--color-overlay-hover);border-color:var(--color-primary)}.share__toggle .tooltip{left:auto;margin-left:0;margin-right:var(--spacing-md);right:100%;transform:translateY(-50%)}.share__toggle .tooltip:before{border-left-color:var(--color-border);border-right-color:transparent;left:100%;right:auto}.share__toggle .tooltip:after{border-left-color:var(--color-overlay-hover);border-right-color:transparent;left:100%;margin-left:-1px;margin-right:0;right:auto}@media (max-width:768px){.circle__button{height:var(--button-size-mobile);width:var(--button-size-mobile)}.github__link{left:var(--spacing-sm);top:var(--spacing-sm)}.github__link svg{height:24px;width:24px}.tooltip{display:none}.intro__text{max-width:calc(100vw - 2rem);padding:0 1rem;top:18%}.intro__text h1{font-size:1.8rem;margin-bottom:.5rem}.intro__text p{font-size:.8rem}.controls__toggle,.top__controls{right:var(--spacing-sm);top:var(--spacing-sm)}.controls__toggle svg,.keyboard__toggle svg,.mouse__toggle svg{height:20px;width:20px}.controls__panel,.keyboard__panel,.mouse__panel{left:var(--spacing-sm);max-height:calc(100vh - var(--spacing-sm) - var(--button-size-mobile) - var(--spacing-xs) - var(--spacing-sm) - 2rem);max-width:none;padding:1rem;right:var(--spacing-sm);top:calc(var(--spacing-sm) + var(--button-size-mobile) + var(--spacing-xs));width:auto}.keyboard__panel,.mouse__panel{left:var(--spacing-sm);right:var(--spacing-sm)}.controls__panel:after,.controls__panel:before,.keyboard__panel:after,.keyboard__panel:before,.mouse__panel:after,.mouse__panel:before{left:50%;right:auto;transform:translateX(-50%)}.controls__header,.keyboard__header,.mouse__header{margin-bottom:1rem;padding-bottom:.75rem}.controls__header h3,.keyboard__header h3,.mouse__header h3{font-size:1rem}.control__section{margin-bottom:1rem}.section__label{font-size:.8125rem}.button__group-item{font-size:.6875rem;padding:.625rem .375rem}.button__group-item svg{height:14px;width:14px}.switch__item{padding:.625rem}.switch__label{font-size:.8125rem}.switch__label svg{height:14px;width:14px}.switch__slider{height:22px;width:40px}.switch__slider:before{height:18px;width:18px}.switch__item input[type=checkbox]:checked~.switch__slider:before{transform:translateX(18px)}.peek-carousel__caption-title{font-size:1.5rem}.peek-carousel__caption-subtitle{font-size:.95rem}.peek-carousel__nav{bottom:10rem}.peek-carousel>.peek-carousel__counter{bottom:10rem;font-size:.8125rem;padding:.375rem .75rem}.peek-carousel__controls{bottom:7rem}.share__panel{left:var(--spacing-sm);max-height:calc(100vh - var(--spacing-sm) - var(--button-size-mobile) - var(--spacing-xs) - var(--spacing-sm) - 2rem);max-width:none;padding:1rem;right:var(--spacing-sm);top:calc(var(--spacing-sm) + var(--button-size-mobile) + var(--spacing-xs));width:auto}.share__panel:after,.share__panel:before{left:50%;right:auto;transform:translateX(-50%)}.share__header{margin-bottom:1rem;padding-bottom:.75rem}.share__header h3{font-size:1rem}.share__item{padding:.75rem}.share__icon{height:2.25rem;width:2.25rem}.share__icon svg{height:18px;width:18px}}
@@ -0,0 +1 @@
1
+ const DEFAULT_LAYOUT_MODE="stack",DEFAULT_OPTIONS={startIndex:0,layoutMode:"stack",autoRotate:!1,autoRotateInterval:3e3,swipeThreshold:50,dragThreshold:80,preloadRange:2,enableKeyboard:!0,enableWheel:!0,enableTouch:!0,enableMouse:!0},SELECTORS={carousel:"#myCarousel",nav:".peek-carousel__nav",controls:".peek-carousel__controls",counter:".peek-carousel__counter",prevBtn:".prev-btn",layoutButton:".button__group-item",layoutButtonActive:".button__group-item--active[data-layout]",layoutModeGroup:'[aria-label="Layout mode selection"]',controlsToggle:".controls__toggle",controlsClose:".controls__close",controlsPanel:".controls__panel",keyboardToggle:".keyboard__toggle",keyboardPanel:".keyboard__panel",mouseToggle:".mouse__toggle",mousePanel:".mouse__panel"},AUTO_GENERATED_SELECTORS=[SELECTORS.nav,SELECTORS.controls,SELECTORS.counter],TOGGLEABLE_UI_IDS=["showNavigation","showCounter","showIndicators","showAutoRotateButton"],PANELS=[{name:"controls",panelClass:"controls__panel",toggleClass:"controls__toggle"},{name:"keyboard",panelClass:"keyboard__panel",toggleClass:"keyboard__toggle"},{name:"mouse",panelClass:"mouse__panel",toggleClass:"mouse__toggle"},{name:"share",panelClass:"share__panel",toggleClass:"share__toggle"}];let elements={},panelElements={},carousel=null;const buildCarouselOptions=(e=DEFAULT_OPTIONS.startIndex)=>{const t=document.querySelector(SELECTORS.layoutButtonActive)?.dataset.layout||"stack";return{...DEFAULT_OPTIONS,startIndex:e,layoutMode:t,showNavigation:elements.showNavigation?.checked??!0,showCounter:elements.showCounter?.checked??!0,showIndicators:elements.showIndicators?.checked??!0,showAutoRotateButton:elements.showAutoRotateButton?.checked??!0}},removeAutoGeneratedElements=e=>{for(const t of AUTO_GENERATED_SELECTORS){const o=e.querySelector(t);o&&o.remove()}},reinitializeCarousel=()=>{const e=carousel.state.currentIndex;carousel.destroy(),removeAutoGeneratedElements(carousel.container),carousel=new PeekCarousel(SELECTORS.carousel,buildCarouselOptions(e)),moveCounterToNavigation()},closeAllPanels=(e=null)=>{for(const t in panelElements){if(t===e)continue;const{panel:o,toggle:a,activeClass:l}=panelElements[t];o?.classList.remove(l),a?.setAttribute("aria-expanded","false")}},createTogglePanel=e=>()=>{const t=panelElements[e];if(!t)return;const{panel:o,toggle:a,activeClass:l}=t,n=o.classList.toggle(l);a?.setAttribute("aria-expanded",n),n&&closeAllPanels(e)},toggleControlsPanel=createTogglePanel("controls"),toggleKeyboardPanel=createTogglePanel("keyboard"),toggleMousePanel=createTogglePanel("mouse"),toggleSharePanel=createTogglePanel("share"),handleLayoutModeChange=(e,t)=>{const o=e.querySelectorAll(SELECTORS.layoutButton);for(const e of o)e.classList.remove("button__group-item--active"),e.setAttribute("aria-pressed","false");t.classList.add("button__group-item--active"),t.setAttribute("aria-pressed","true");const a=t.dataset.layout;carousel.options.layoutMode=a,carousel.updateLayoutClass(),"stack"===a&&(carousel.elements.carousel.style.transform="none"),carousel.animator.updateCarousel()},attachEventListeners=()=>{const e=document.querySelector(SELECTORS.controlsToggle),t=document.querySelector(SELECTORS.keyboardToggle),o=document.querySelector(SELECTORS.mouseToggle),a=document.querySelector(".share__toggle"),l=document.querySelector(SELECTORS.controlsClose),n=document.querySelector(SELECTORS.layoutModeGroup);e?.addEventListener("click",toggleControlsPanel),t?.addEventListener("click",toggleKeyboardPanel),o?.addEventListener("click",toggleMousePanel),a?.addEventListener("click",toggleSharePanel),l?.addEventListener("click",toggleControlsPanel),n?.addEventListener("click",e=>{const t=e.target.closest(SELECTORS.layoutButton);t&&handleLayoutModeChange(n,t)});for(const e of TOGGLEABLE_UI_IDS)elements[e]?.addEventListener("change",reinitializeCarousel)},cacheElements=()=>{elements=TOGGLEABLE_UI_IDS.reduce((e,t)=>(e[t]=document.getElementById(t),e),{}),panelElements=PANELS.reduce((e,t)=>(e[t.name]={panel:document.querySelector(`.${t.panelClass}`),toggle:document.querySelector(`.${t.toggleClass}`),activeClass:`${t.panelClass}--active`},e),{})},moveCounterToNavigation=()=>{const e=carousel.container.querySelector(SELECTORS.nav);if(!e)return;const t=carousel.container.querySelector(SELECTORS.counter);if(!t)return;const o=e.querySelector(SELECTORS.prevBtn);o&&e.insertBefore(t,o.nextSibling)},init=()=>{cacheElements(),carousel=new PeekCarousel(SELECTORS.carousel,buildCarouselOptions()),moveCounterToNavigation(),attachEventListeners()};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",init):init();
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "peek-carousel",
3
+ "version": "1.0.2",
4
+ "description": "A modular carousel library with three layout modes (Stack/Card, Radial, Classic) featuring peek effect, touch/drag support, and full accessibility - inspired by iPhone 17 Pro",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "dist/peek-carousel.js",
10
+ "module": "dist/peek-carousel.esm.js",
11
+ "types": "types/index.d.ts",
12
+ "style": "dist/peek-carousel.css",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/peek-carousel.esm.js",
16
+ "require": "./dist/peek-carousel.js",
17
+ "types": "./types/index.d.ts"
18
+ },
19
+ "./dist/peek-carousel.css": "./dist/peek-carousel.css",
20
+ "./dist/peek-carousel.min.css": "./dist/peek-carousel.min.css"
21
+ },
22
+ "files": [
23
+ "src/",
24
+ "dist/",
25
+ "types/",
26
+ "examples/",
27
+ "README.md",
28
+ "README.en.md",
29
+ "LICENSE"
30
+ ],
31
+ "scripts": {
32
+ "clean": "rm -rf dist",
33
+ "build:js": "rollup -c",
34
+ "build:css": "sass src/styles/peek-carousel.scss dist/peek-carousel.css --no-source-map",
35
+ "build:css:min": "sass src/styles/peek-carousel.scss dist/peek-carousel.min.css --style=compressed --no-source-map",
36
+ "build:example:css": "postcss examples/example.css -o examples/example.min.css",
37
+ "build:example:js": "terser examples/example.js -o examples/example.min.js -c -m",
38
+ "postbuild:css": "postcss dist/peek-carousel.css -o dist/peek-carousel.css",
39
+ "postbuild:css:min": "postcss dist/peek-carousel.min.css -o dist/peek-carousel.min.css",
40
+ "build": "npm run clean && npm-run-all --parallel build:js build:css build:css:min",
41
+ "build:examples": "npm-run-all --parallel build:example:css build:example:js",
42
+ "watch:js": "rollup -c -w",
43
+ "watch:css": "sass --watch src/styles/peek-carousel.scss dist/peek-carousel.css",
44
+ "watch": "npm-run-all --parallel watch:js watch:css",
45
+ "dev": "npm run watch",
46
+ "prepublishOnly": "npm run build",
47
+ "test": "echo \"Error: no test specified\" && exit 1"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/lledellebell/peek-carousel.git"
52
+ },
53
+ "keywords": [
54
+ "carousel",
55
+ "slider",
56
+ "peek",
57
+ "stack",
58
+ "card",
59
+ "swipe",
60
+ "gallery",
61
+ "apple",
62
+ "responsive",
63
+ "touch",
64
+ "drag",
65
+ "mobile"
66
+ ],
67
+ "author": "B <deep.diver.9.tsuki@gmail.com> (https://github.com/lledellebell)",
68
+ "license": "MIT",
69
+ "bugs": {
70
+ "url": "https://github.com/lledellebell/peek-carousel/issues"
71
+ },
72
+ "homepage": "https://github.com/lledellebell/peek-carousel#readme",
73
+ "engines": {
74
+ "node": ">=14.0.0"
75
+ },
76
+ "devDependencies": {
77
+ "@babel/core": "^7.23.0",
78
+ "@babel/preset-env": "^7.23.0",
79
+ "@rollup/plugin-babel": "^6.0.4",
80
+ "@rollup/plugin-node-resolve": "^15.2.3",
81
+ "@rollup/plugin-terser": "^0.4.4",
82
+ "autoprefixer": "^10.4.16",
83
+ "cssnano": "^6.0.1",
84
+ "npm-run-all": "^4.1.5",
85
+ "postcss": "^8.4.31",
86
+ "postcss-cli": "^11.0.0",
87
+ "rollup": "^4.1.4",
88
+ "rollup-plugin-postcss": "^4.0.2",
89
+ "sass": "^1.69.5",
90
+ "terser": "^5.44.1"
91
+ }
92
+ }