@hortonstudio/main 1.9.10 → 1.9.20
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.
- package/.prettierrc +8 -0
- package/README.md +146 -0
- package/eslint.config.js +32 -0
- package/index.ts +275 -0
- package/package.json +19 -2
- package/public/bootstrap.js +16 -0
- package/src/animations/animations.ts +93 -0
- package/src/animations/functions/counter/counter.ts +137 -0
- package/src/config.json +570 -0
- package/src/config.ts +105 -0
- package/src/modules/default/README.md +167 -0
- package/src/modules/default/default.ts +71 -0
- package/{autoInit → src/modules/default/functions}/accessibility/README.md +44 -12
- package/src/modules/default/functions/accessibility/accessibility.ts +54 -0
- package/src/modules/default/functions/accordion/README.md +451 -0
- package/src/modules/default/functions/accordion/accordion.ts +189 -0
- package/src/modules/default/functions/comparison/comparison.ts +424 -0
- package/src/modules/default/functions/marquee/marquee.ts +206 -0
- package/src/modules/default/functions/navbar/README.md +393 -0
- package/src/modules/default/functions/navbar/functions/arrow-navigation/arrow-navigation.ts +183 -0
- package/src/modules/default/functions/navbar/functions/dropdown/dropdown.ts +313 -0
- package/src/modules/default/functions/navbar/functions/menu/menu.ts +315 -0
- package/src/modules/default/functions/navbar/navbar.ts +51 -0
- package/{autoInit → src/modules/default/functions}/smooth-scroll/README.md +45 -14
- package/{autoInit/smooth-scroll/smooth-scroll.js → src/modules/default/functions/smooth-scroll/smooth-scroll.ts} +33 -38
- package/{autoInit → src/modules/default/functions}/transition/README.md +59 -32
- package/src/modules/default/functions/transition/transition.ts +290 -0
- package/src/modules/normalize/README.md +172 -0
- package/src/modules/normalize/functions/clickable/README.md +84 -0
- package/src/modules/normalize/functions/clickable/clickable.ts +43 -0
- package/src/modules/normalize/functions/clickable/functions/normalize/README.md +213 -0
- package/src/modules/normalize/functions/clickable/functions/normalize/normalize.ts +68 -0
- package/src/modules/normalize/functions/dupe/README.md +405 -0
- package/src/modules/normalize/functions/dupe/dupe.ts +197 -0
- package/src/modules/normalize/functions/sync/sync.ts +378 -0
- package/src/modules/normalize/normalize.ts +58 -0
- package/src/modules/structure/README.md +190 -0
- package/src/modules/structure/functions/form/README.md +94 -0
- package/src/modules/structure/functions/form/form.ts +54 -0
- package/src/modules/structure/functions/form/functions/honeypot/README.md +77 -0
- package/src/modules/structure/functions/form/functions/honeypot/honeypot.ts +37 -0
- package/src/modules/structure/functions/form/functions/range/README.md +410 -0
- package/src/modules/structure/functions/form/functions/range/range.ts +92 -0
- package/src/modules/structure/functions/form/functions/select/README.md +393 -0
- package/src/modules/structure/functions/form/functions/select/functions/custom-select/custom-select.ts +637 -0
- package/src/modules/structure/functions/form/functions/select/functions/states/states.ts +118 -0
- package/src/modules/structure/functions/form/functions/select/select.ts +48 -0
- package/src/modules/structure/functions/form/functions/test/test.ts +132 -0
- package/src/modules/structure/functions/pagination/README.md +527 -0
- package/src/modules/structure/functions/pagination/pagination.ts +493 -0
- package/src/modules/structure/functions/site-settings/README.md +395 -0
- package/src/modules/structure/functions/site-settings/site-settings.ts +158 -0
- package/{autoInit/accessibility → src/modules/structure}/functions/toc/README.md +18 -15
- package/{autoInit/accessibility/functions/toc/toc.js → src/modules/structure/functions/toc/functions/heading-links/heading-links.ts} +43 -63
- package/src/modules/structure/functions/toc/functions/progress-bar/progress-bar.ts +101 -0
- package/src/modules/structure/functions/toc/toc.ts +35 -0
- package/{autoInit/accessibility → src/modules/structure}/functions/year-replacement/README.md +7 -6
- package/src/modules/structure/functions/year-replacement/year-replacement.ts +59 -0
- package/src/modules/structure/structure.ts +59 -0
- package/src/utils/attributeSelector.ts +78 -0
- package/src/utils/cssVariables.ts +24 -0
- package/src/utils/gsap.ts +198 -0
- package/src/utils/heightAnimator.ts +130 -0
- package/src/utils/modalManager.ts +150 -0
- package/src/utils.ts +54 -0
- package/tsconfig.json +24 -0
- package/vite.config.js +45 -0
- package/.claude/settings.local.json +0 -70
- package/archive/hero.js +0 -794
- package/archive/modal.js +0 -80
- package/archive/text.js +0 -628
- package/autoInit/accessibility/accessibility.js +0 -53
- package/autoInit/accessibility/functions/blog-remover/README.md +0 -61
- package/autoInit/accessibility/functions/blog-remover/blog-remover.js +0 -31
- package/autoInit/accessibility/functions/click-forwarding/README.md +0 -60
- package/autoInit/accessibility/functions/click-forwarding/click-forwarding.js +0 -82
- package/autoInit/accessibility/functions/dropdown/README.md +0 -212
- package/autoInit/accessibility/functions/dropdown/dropdown.js +0 -167
- package/autoInit/accessibility/functions/list-accessibility/README.md +0 -56
- package/autoInit/accessibility/functions/list-accessibility/list-accessibility.js +0 -23
- package/autoInit/accessibility/functions/pagination/README.md +0 -428
- package/autoInit/accessibility/functions/pagination/pagination.js +0 -359
- package/autoInit/accessibility/functions/text-synchronization/README.md +0 -62
- package/autoInit/accessibility/functions/text-synchronization/text-synchronization.js +0 -101
- package/autoInit/accessibility/functions/year-replacement/year-replacement.js +0 -43
- package/autoInit/button/README.md +0 -122
- package/autoInit/button/button.js +0 -51
- package/autoInit/counter/README.md +0 -274
- package/autoInit/counter/counter.js +0 -185
- package/autoInit/form/README.md +0 -338
- package/autoInit/form/form.js +0 -374
- package/autoInit/navbar/README.md +0 -366
- package/autoInit/navbar/navbar.js +0 -786
- package/autoInit/site-settings/README.md +0 -218
- package/autoInit/site-settings/site-settings.js +0 -134
- package/autoInit/transition/transition.js +0 -116
- package/index.js +0 -305
- package/utils/before-after/README.md +0 -520
- package/utils/before-after/before-after.js +0 -653
- package/utils/css-animations/buttons/main/bgbasic/btn-main-bgbasic.html +0 -10
- package/utils/css-animations/buttons/main/bgfill/btn-main-bgfill.html +0 -29
- package/utils/css-animations/buttons/navbar/bgbasic/navbar-main-bgbasic.html +0 -17
- package/utils/css-animations/buttons/navbar/bgbasic/navbar-menu-bgbasic.html +0 -16
- package/utils/css-animations/buttons/navbar/bgfill/navbar-main-bgfill.html +0 -46
- package/utils/css-animations/buttons/navbar/bgfill/navbar-menu-bgfill.html +0 -39
- package/utils/css-animations/buttons/navbar/color/navbar-announce-color.html +0 -5
- package/utils/css-animations/buttons/navbar/color/navbar-main-color.html +0 -7
- package/utils/css-animations/buttons/navbar/color/navbar-menu-color.html +0 -7
- package/utils/css-animations/buttons/navbar/double-slide/navbar-announce-double-slide.html +0 -40
- package/utils/css-animations/buttons/navbar/double-slide/navbar-main-double-slide.html +0 -77
- package/utils/css-animations/buttons/navbar/scale/navbar-announce-scale.html +0 -6
- package/utils/css-animations/buttons/navbar/scale/navbar-main-scale.html +0 -9
- package/utils/css-animations/buttons/navbar/scale/navbar-menu-scale.html +0 -8
- package/utils/css-animations/buttons/navbar/underline/navbar-announce-underline.html +0 -32
- package/utils/css-animations/buttons/navbar/underline/navbar-main-underline.html +0 -56
- package/utils/css-animations/buttons/text/color/text-footer-color.html +0 -5
- package/utils/css-animations/buttons/text/color/text-main-color.html +0 -5
- package/utils/css-animations/buttons/text/double-slide/text-main-double-slide.html +0 -56
- package/utils/css-animations/buttons/text/scale/text-footer-scale.html +0 -6
- package/utils/css-animations/buttons/text/scale/text-main-scale.html +0 -6
- package/utils/css-animations/buttons/text/underline/text-footer-underline.html +0 -45
- package/utils/css-animations/buttons/text/underline/text-main-underline.html +0 -58
- package/utils/css-animations/cards/card-clickable.html +0 -11
- package/utils/css-animations/defaults.html +0 -69
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination System
|
|
3
|
+
*
|
|
4
|
+
* Handles paginated lists with controls, counters, and dot navigation.
|
|
5
|
+
* Supports infinite looping, responsive layouts, and accessibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { querySelectorAll, querySelector, getSelector, globalConfig, cssVariables } from '@utils';
|
|
9
|
+
|
|
10
|
+
// Module-scoped config (set during init)
|
|
11
|
+
let moduleConfig = null;
|
|
12
|
+
|
|
13
|
+
export function init(config) {
|
|
14
|
+
// Store config at module scope for helper functions
|
|
15
|
+
moduleConfig = config;
|
|
16
|
+
|
|
17
|
+
const cleanup = {
|
|
18
|
+
observers: [],
|
|
19
|
+
handlers: [],
|
|
20
|
+
liveRegions: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Initialize all pagination containers
|
|
24
|
+
try {
|
|
25
|
+
querySelectorAll(config, 'wrapper').forEach((container) => {
|
|
26
|
+
try {
|
|
27
|
+
const instance = initPaginationInstance(container, cleanup);
|
|
28
|
+
if (!instance) {
|
|
29
|
+
console.warn('[hs-pagination] Failed to initialize container', container);
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('[hs-pagination] Error initializing container:', error, container);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('[hs-pagination] Critical error during initialization:', error);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
result: 'pagination initialized',
|
|
41
|
+
destroy: () => {
|
|
42
|
+
try {
|
|
43
|
+
// Remove all live regions
|
|
44
|
+
cleanup.liveRegions.forEach((liveRegion) => {
|
|
45
|
+
try {
|
|
46
|
+
if (liveRegion.parentNode) {
|
|
47
|
+
liveRegion.parentNode.removeChild(liveRegion);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('[hs-pagination] Error removing live region:', error);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
cleanup.liveRegions.length = 0;
|
|
54
|
+
|
|
55
|
+
// Disconnect all observers
|
|
56
|
+
cleanup.observers.forEach((obs) => {
|
|
57
|
+
try {
|
|
58
|
+
obs.disconnect();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('[hs-pagination] Error disconnecting observer:', error);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
cleanup.observers.length = 0;
|
|
64
|
+
|
|
65
|
+
// Remove all event listeners
|
|
66
|
+
cleanup.handlers.forEach(({ element, event, handler }) => {
|
|
67
|
+
try {
|
|
68
|
+
element.removeEventListener(event, handler);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('[hs-pagination] Error removing event listener:', error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
cleanup.handlers.length = 0;
|
|
74
|
+
|
|
75
|
+
// Clear module config
|
|
76
|
+
moduleConfig = null;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('[hs-pagination] Critical error during cleanup:', error);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function initPaginationInstance(container, cleanup) {
|
|
85
|
+
const list = querySelector(moduleConfig, 'list', container);
|
|
86
|
+
if (!list) {
|
|
87
|
+
console.warn('[hs-pagination] Missing required element: data-hs-pagination="list"');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const wrapper = list.parentElement;
|
|
92
|
+
if (!wrapper) return null;
|
|
93
|
+
|
|
94
|
+
const elements = {
|
|
95
|
+
controls: querySelector(moduleConfig, 'controls', container),
|
|
96
|
+
counter: querySelector(moduleConfig, 'counter', container),
|
|
97
|
+
dotsWrap: querySelector(moduleConfig, 'dots', container),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Early exit for infinite mode - no controls means no pagination
|
|
101
|
+
if (!elements.controls) {
|
|
102
|
+
return { initialized: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Find next/previous buttons using clickable pattern
|
|
106
|
+
const nextClickable = querySelector(moduleConfig, 'next', container);
|
|
107
|
+
const prevClickable = querySelector(moduleConfig, 'previous', container);
|
|
108
|
+
|
|
109
|
+
const clickableSelector = getSelector(globalConfig.clickable, 'button');
|
|
110
|
+
const nextBtn = nextClickable?.querySelector(clickableSelector) || nextClickable;
|
|
111
|
+
const prevBtn = prevClickable?.querySelector(clickableSelector) || prevClickable;
|
|
112
|
+
|
|
113
|
+
if (!nextBtn || !prevBtn) {
|
|
114
|
+
console.warn('[hs-pagination] Missing required navigation buttons');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
elements.nextBtn = nextBtn;
|
|
119
|
+
elements.prevBtn = prevBtn;
|
|
120
|
+
|
|
121
|
+
// Add ARIA attributes to buttons
|
|
122
|
+
elements.nextBtn.setAttribute('aria-label', 'Go to next page');
|
|
123
|
+
elements.prevBtn.setAttribute('aria-label', 'Go to previous page');
|
|
124
|
+
if (elements.counter) {
|
|
125
|
+
elements.counter.setAttribute('aria-live', 'polite');
|
|
126
|
+
elements.counter.setAttribute('aria-label', 'Current page');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Parse configuration from new attributes
|
|
130
|
+
const desktopItems = parseInt(elements.controls?.getAttribute('data-hs-pagination-show')) || 6;
|
|
131
|
+
const mobileItems =
|
|
132
|
+
parseInt(elements.controls?.getAttribute('data-hs-pagination-show-mobile')) || desktopItems;
|
|
133
|
+
|
|
134
|
+
const isMobileLayout = () => {
|
|
135
|
+
const stateValue = getComputedStyle(list).getPropertyValue(cssVariables.state).trim();
|
|
136
|
+
return stateValue === globalConfig.cssVars.state.values.active;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const allItems = Array.from(list.children);
|
|
140
|
+
const totalItems = allItems.length;
|
|
141
|
+
if (!totalItems) return null;
|
|
142
|
+
|
|
143
|
+
const state = {
|
|
144
|
+
totalPages: 1,
|
|
145
|
+
currentIndex: 1,
|
|
146
|
+
currentPage: 1,
|
|
147
|
+
isAnimating: false,
|
|
148
|
+
itemsPerPage: desktopItems,
|
|
149
|
+
dotTemplates: { active: null, inactive: null },
|
|
150
|
+
};
|
|
151
|
+
let wrapperChildren = [];
|
|
152
|
+
|
|
153
|
+
// Create live region for announcements
|
|
154
|
+
const liveRegion = document.createElement('div');
|
|
155
|
+
liveRegion.className = 'sr-only';
|
|
156
|
+
liveRegion.setAttribute('aria-live', 'assertive');
|
|
157
|
+
liveRegion.setAttribute('aria-atomic', 'true');
|
|
158
|
+
liveRegion.style.cssText =
|
|
159
|
+
'position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;';
|
|
160
|
+
container.appendChild(liveRegion);
|
|
161
|
+
cleanup.liveRegions.push(liveRegion);
|
|
162
|
+
|
|
163
|
+
const updateCounter = () => {
|
|
164
|
+
if (elements.counter) {
|
|
165
|
+
elements.counter.textContent = `${state.currentPage} / ${state.totalPages}`;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const announcePageChange = () => {
|
|
170
|
+
liveRegion.textContent = `Page ${state.currentPage} of ${state.totalPages}`;
|
|
171
|
+
setTimeout(() => (liveRegion.textContent = ''), 1000);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const manageFocus = () => {
|
|
175
|
+
wrapperChildren.forEach((page, index) => {
|
|
176
|
+
page[index === state.currentIndex ? 'removeAttribute' : 'setAttribute']('inert', '');
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const updateHeight = () => {
|
|
181
|
+
const targetPage = wrapperChildren[state.currentIndex];
|
|
182
|
+
if (targetPage && targetPage.offsetHeight !== undefined) {
|
|
183
|
+
wrapper.style.height = targetPage.offsetHeight + 'px';
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const initializeDots = () => {
|
|
188
|
+
if (!elements.dotsWrap) return;
|
|
189
|
+
|
|
190
|
+
// Find existing dots as templates
|
|
191
|
+
const existingDots = Array.from(elements.dotsWrap.children);
|
|
192
|
+
if (!existingDots.length) return;
|
|
193
|
+
|
|
194
|
+
// Identify active and inactive templates
|
|
195
|
+
const activeDot = existingDots.find((dot) => dot.classList.contains('is-active'));
|
|
196
|
+
const inactiveDot = existingDots.find((dot) => !dot.classList.contains('is-active'));
|
|
197
|
+
|
|
198
|
+
// Store templates (use same template for both if only one exists)
|
|
199
|
+
state.dotTemplates.active = activeDot
|
|
200
|
+
? activeDot.cloneNode(true)
|
|
201
|
+
: existingDots[0].cloneNode(true);
|
|
202
|
+
state.dotTemplates.inactive = inactiveDot
|
|
203
|
+
? inactiveDot.cloneNode(true)
|
|
204
|
+
: existingDots[0].cloneNode(true);
|
|
205
|
+
|
|
206
|
+
// Clear existing dots
|
|
207
|
+
elements.dotsWrap.innerHTML = '';
|
|
208
|
+
|
|
209
|
+
// Add pagination accessibility attributes
|
|
210
|
+
elements.dotsWrap.setAttribute('role', 'group');
|
|
211
|
+
elements.dotsWrap.setAttribute('aria-label', 'Page navigation');
|
|
212
|
+
|
|
213
|
+
// Create dots for each page
|
|
214
|
+
for (let i = 1; i <= state.totalPages; i++) {
|
|
215
|
+
const dot = (i === 1 ? state.dotTemplates.active : state.dotTemplates.inactive).cloneNode(
|
|
216
|
+
true
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Add accessibility attributes
|
|
220
|
+
dot.setAttribute('role', 'button');
|
|
221
|
+
dot.setAttribute('tabindex', '0');
|
|
222
|
+
dot.setAttribute('aria-label', `Go to page ${i}`);
|
|
223
|
+
dot.setAttribute('aria-current', i === 1 ? 'page' : 'false');
|
|
224
|
+
dot.setAttribute('data-page', i);
|
|
225
|
+
|
|
226
|
+
// Set initial active state
|
|
227
|
+
if (i === 1) {
|
|
228
|
+
dot.classList.add('is-active');
|
|
229
|
+
} else {
|
|
230
|
+
dot.classList.remove('is-active');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Click handler
|
|
234
|
+
const clickHandler = () => navigateToPage(i);
|
|
235
|
+
dot.addEventListener('click', clickHandler);
|
|
236
|
+
cleanup.handlers.push({ element: dot, event: 'click', handler: clickHandler });
|
|
237
|
+
|
|
238
|
+
// Keyboard handler
|
|
239
|
+
const keyHandler = (e) => {
|
|
240
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
navigateToPage(i);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
dot.addEventListener('keydown', keyHandler);
|
|
246
|
+
cleanup.handlers.push({ element: dot, event: 'keydown', handler: keyHandler });
|
|
247
|
+
|
|
248
|
+
elements.dotsWrap.appendChild(dot);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const updateActiveDot = () => {
|
|
253
|
+
if (!elements.dotsWrap) return;
|
|
254
|
+
|
|
255
|
+
const dots = Array.from(elements.dotsWrap.children);
|
|
256
|
+
dots.forEach((dot, index) => {
|
|
257
|
+
const dotPage = index + 1;
|
|
258
|
+
if (dotPage === state.currentPage) {
|
|
259
|
+
dot.classList.add('is-active');
|
|
260
|
+
dot.setAttribute('aria-current', 'page');
|
|
261
|
+
} else {
|
|
262
|
+
dot.classList.remove('is-active');
|
|
263
|
+
dot.setAttribute('aria-current', 'false');
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const initializePagination = (forceItemsPerPage = null) => {
|
|
269
|
+
const currentIsMobile = isMobileLayout();
|
|
270
|
+
state.itemsPerPage = forceItemsPerPage || (currentIsMobile ? mobileItems : desktopItems);
|
|
271
|
+
state.totalPages = Math.ceil(totalItems / state.itemsPerPage);
|
|
272
|
+
|
|
273
|
+
// Remove old dot event handlers to prevent memory leaks
|
|
274
|
+
if (elements.dotsWrap) {
|
|
275
|
+
cleanup.handlers = cleanup.handlers.filter(({ element }) => {
|
|
276
|
+
return !elements.dotsWrap.contains(element);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Clean up previous page lists
|
|
281
|
+
Array.from(wrapper.children).forEach((child) => {
|
|
282
|
+
if (child !== list) wrapper.removeChild(child);
|
|
283
|
+
});
|
|
284
|
+
list.innerHTML = '';
|
|
285
|
+
allItems.forEach((item) => list.appendChild(item));
|
|
286
|
+
|
|
287
|
+
// Single page case
|
|
288
|
+
if (state.totalPages <= 1) {
|
|
289
|
+
if (elements.controls) {
|
|
290
|
+
if (elements.controls.contains(document.activeElement)) document.activeElement.blur();
|
|
291
|
+
elements.controls.style.display = 'none';
|
|
292
|
+
elements.controls.setAttribute('aria-hidden', 'true');
|
|
293
|
+
}
|
|
294
|
+
if (elements.dotsWrap) {
|
|
295
|
+
elements.dotsWrap.style.display = 'none';
|
|
296
|
+
elements.dotsWrap.setAttribute('aria-hidden', 'true');
|
|
297
|
+
}
|
|
298
|
+
Object.assign(state, { totalPages: 1, currentIndex: 1, currentPage: 1, isAnimating: false });
|
|
299
|
+
wrapper.style.cssText = `transform: translateX(0%); height: ${list.offsetHeight}px;`;
|
|
300
|
+
wrapperChildren = [list];
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Show controls and dots
|
|
305
|
+
if (elements.controls) {
|
|
306
|
+
elements.controls.style.display = '';
|
|
307
|
+
elements.controls.removeAttribute('aria-hidden');
|
|
308
|
+
}
|
|
309
|
+
if (elements.dotsWrap) {
|
|
310
|
+
elements.dotsWrap.style.display = '';
|
|
311
|
+
elements.dotsWrap.removeAttribute('aria-hidden');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Create page lists
|
|
315
|
+
const pageLists = Array.from({ length: state.totalPages }, (_, page) => {
|
|
316
|
+
const pageList = list.cloneNode(false);
|
|
317
|
+
const startIndex = page * state.itemsPerPage;
|
|
318
|
+
const endIndex = Math.min(startIndex + state.itemsPerPage, totalItems);
|
|
319
|
+
allItems
|
|
320
|
+
.slice(startIndex, endIndex)
|
|
321
|
+
.forEach((item) => pageList.appendChild(item.cloneNode(true)));
|
|
322
|
+
return pageList;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Insert cloned pages for infinite loop
|
|
326
|
+
wrapper.insertBefore(pageLists[pageLists.length - 1].cloneNode(true), list);
|
|
327
|
+
pageLists.slice(1).forEach((page) => wrapper.appendChild(page));
|
|
328
|
+
wrapper.appendChild(pageLists[0].cloneNode(true));
|
|
329
|
+
|
|
330
|
+
// Populate original list with first page
|
|
331
|
+
list.innerHTML = '';
|
|
332
|
+
Array.from(pageLists[0].children).forEach((item) => list.appendChild(item));
|
|
333
|
+
|
|
334
|
+
Object.assign(state, { currentIndex: 1, currentPage: 1, isAnimating: false });
|
|
335
|
+
wrapperChildren = Array.from(wrapper.children);
|
|
336
|
+
wrapper.style.transform = 'translateX(-100%)';
|
|
337
|
+
|
|
338
|
+
updateCounter();
|
|
339
|
+
updateHeight();
|
|
340
|
+
manageFocus();
|
|
341
|
+
initializeDots();
|
|
342
|
+
return state.totalPages;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
let currentLayoutIsMobile = isMobileLayout();
|
|
346
|
+
|
|
347
|
+
const checkLayoutChange = () => {
|
|
348
|
+
// Prevent resize during animation
|
|
349
|
+
if (state.isAnimating) return;
|
|
350
|
+
|
|
351
|
+
const newIsMobile = isMobileLayout();
|
|
352
|
+
if (newIsMobile !== currentLayoutIsMobile) {
|
|
353
|
+
currentLayoutIsMobile = newIsMobile;
|
|
354
|
+
initializePagination();
|
|
355
|
+
} else {
|
|
356
|
+
updateHeight();
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const resizeObserver = new ResizeObserver(checkLayoutChange);
|
|
361
|
+
resizeObserver.observe(wrapper);
|
|
362
|
+
cleanup.observers.push(resizeObserver);
|
|
363
|
+
|
|
364
|
+
initializePagination();
|
|
365
|
+
|
|
366
|
+
const navigateToPage = (targetPage) => {
|
|
367
|
+
if (state.isAnimating || state.totalPages <= 1 || targetPage === state.currentPage) return;
|
|
368
|
+
state.isAnimating = true;
|
|
369
|
+
|
|
370
|
+
state.currentIndex = targetPage;
|
|
371
|
+
state.currentPage = targetPage;
|
|
372
|
+
|
|
373
|
+
updateCounter();
|
|
374
|
+
announcePageChange();
|
|
375
|
+
updateHeight();
|
|
376
|
+
updateActiveDot();
|
|
377
|
+
|
|
378
|
+
if (isMobileLayout() && elements.controls) {
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
const controlsBottom =
|
|
381
|
+
elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
|
|
382
|
+
const clearance = 5 * 16; // 5rem in pixels
|
|
383
|
+
const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
|
|
384
|
+
window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
|
|
385
|
+
}, 50);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
|
|
389
|
+
|
|
390
|
+
let transitionTimeout = null;
|
|
391
|
+
|
|
392
|
+
const handleTransitionEnd = () => {
|
|
393
|
+
clearTimeout(transitionTimeout);
|
|
394
|
+
wrapper.removeEventListener('transitionend', handleTransitionEnd);
|
|
395
|
+
|
|
396
|
+
updateCounter();
|
|
397
|
+
announcePageChange();
|
|
398
|
+
updateHeight();
|
|
399
|
+
manageFocus();
|
|
400
|
+
updateActiveDot();
|
|
401
|
+
state.isAnimating = false;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Safety timeout in case transitionend never fires
|
|
405
|
+
transitionTimeout = setTimeout(handleTransitionEnd, 1000);
|
|
406
|
+
|
|
407
|
+
wrapper.addEventListener('transitionend', handleTransitionEnd);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const navigate = (direction) => {
|
|
411
|
+
if (state.isAnimating || state.totalPages <= 1) return;
|
|
412
|
+
state.isAnimating = true;
|
|
413
|
+
state.currentIndex += direction;
|
|
414
|
+
|
|
415
|
+
state.currentPage =
|
|
416
|
+
state.currentIndex > state.totalPages
|
|
417
|
+
? 1
|
|
418
|
+
: state.currentIndex < 1
|
|
419
|
+
? state.totalPages
|
|
420
|
+
: state.currentIndex;
|
|
421
|
+
|
|
422
|
+
updateCounter();
|
|
423
|
+
announcePageChange();
|
|
424
|
+
updateHeight();
|
|
425
|
+
updateActiveDot();
|
|
426
|
+
|
|
427
|
+
if (isMobileLayout() && elements.controls) {
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
const controlsBottom =
|
|
430
|
+
elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
|
|
431
|
+
const clearance = 5 * 16; // 5rem in pixels
|
|
432
|
+
const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
|
|
433
|
+
window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
|
|
434
|
+
}, 50);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
|
|
438
|
+
|
|
439
|
+
let transitionTimeout = null;
|
|
440
|
+
|
|
441
|
+
const handleTransitionEnd = () => {
|
|
442
|
+
clearTimeout(transitionTimeout);
|
|
443
|
+
wrapper.removeEventListener('transitionend', handleTransitionEnd);
|
|
444
|
+
|
|
445
|
+
if (state.currentIndex > state.totalPages) {
|
|
446
|
+
state.currentIndex = 1;
|
|
447
|
+
state.currentPage = 1;
|
|
448
|
+
// Disable transition for instant jump
|
|
449
|
+
wrapper.style.transition = 'none';
|
|
450
|
+
wrapper.style.transform = 'translateX(-100%)';
|
|
451
|
+
// Force reflow to apply the instant jump
|
|
452
|
+
wrapper.offsetHeight;
|
|
453
|
+
// Re-enable CSS transition
|
|
454
|
+
wrapper.style.transition = '';
|
|
455
|
+
} else if (state.currentIndex < 1) {
|
|
456
|
+
state.currentIndex = state.totalPages;
|
|
457
|
+
state.currentPage = state.totalPages;
|
|
458
|
+
// Disable transition for instant jump
|
|
459
|
+
wrapper.style.transition = 'none';
|
|
460
|
+
wrapper.style.transform = `translateX(${-state.totalPages * 100}%)`;
|
|
461
|
+
// Force reflow to apply the instant jump
|
|
462
|
+
wrapper.offsetHeight;
|
|
463
|
+
// Re-enable CSS transition
|
|
464
|
+
wrapper.style.transition = '';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
updateCounter();
|
|
468
|
+
announcePageChange();
|
|
469
|
+
updateHeight();
|
|
470
|
+
manageFocus();
|
|
471
|
+
updateActiveDot();
|
|
472
|
+
state.isAnimating = false;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Safety timeout in case transitionend never fires
|
|
476
|
+
transitionTimeout = setTimeout(handleTransitionEnd, 1000);
|
|
477
|
+
|
|
478
|
+
wrapper.addEventListener('transitionend', handleTransitionEnd);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const nextHandler = () => navigate(1);
|
|
482
|
+
const prevHandler = () => navigate(-1);
|
|
483
|
+
|
|
484
|
+
elements.nextBtn.addEventListener('click', nextHandler);
|
|
485
|
+
elements.prevBtn.addEventListener('click', prevHandler);
|
|
486
|
+
|
|
487
|
+
cleanup.handlers.push(
|
|
488
|
+
{ element: elements.nextBtn, event: 'click', handler: nextHandler },
|
|
489
|
+
{ element: elements.prevBtn, event: 'click', handler: prevHandler }
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
return { initialized: true };
|
|
493
|
+
}
|