@hortonstudio/main 1.9.11 → 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/{autoInit/accessibility → src/modules/structure}/functions/pagination/README.md +147 -72
- package/{autoInit/accessibility/functions/pagination/pagination.js → src/modules/structure/functions/pagination/pagination.ts} +98 -50
- package/{autoInit → src/modules/structure/functions}/site-settings/README.md +57 -27
- package/{autoInit/site-settings/site-settings.js → src/modules/structure/functions/site-settings/site-settings.ts} +36 -32
- 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/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/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
|
@@ -5,35 +5,55 @@
|
|
|
5
5
|
* Supports infinite looping, responsive layouts, and accessibility.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
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
|
+
|
|
9
17
|
const cleanup = {
|
|
10
18
|
observers: [],
|
|
11
|
-
handlers: []
|
|
19
|
+
handlers: [],
|
|
20
|
+
liveRegions: [],
|
|
12
21
|
};
|
|
13
22
|
|
|
14
23
|
// Initialize all pagination containers
|
|
15
24
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.warn('[hs-pagination] Failed to initialize container', container);
|
|
22
|
-
}
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.error('[hs-pagination] Error initializing container:', error, container);
|
|
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);
|
|
25
30
|
}
|
|
26
|
-
})
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('[hs-pagination] Error initializing container:', error, container);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
27
35
|
} catch (error) {
|
|
28
36
|
console.error('[hs-pagination] Critical error during initialization:', error);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
return {
|
|
32
|
-
result:
|
|
40
|
+
result: 'pagination initialized',
|
|
33
41
|
destroy: () => {
|
|
34
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
|
+
|
|
35
55
|
// Disconnect all observers
|
|
36
|
-
cleanup.observers.forEach(obs => {
|
|
56
|
+
cleanup.observers.forEach((obs) => {
|
|
37
57
|
try {
|
|
38
58
|
obs.disconnect();
|
|
39
59
|
} catch (error) {
|
|
@@ -51,15 +71,18 @@ export function init() {
|
|
|
51
71
|
}
|
|
52
72
|
});
|
|
53
73
|
cleanup.handlers.length = 0;
|
|
74
|
+
|
|
75
|
+
// Clear module config
|
|
76
|
+
moduleConfig = null;
|
|
54
77
|
} catch (error) {
|
|
55
78
|
console.error('[hs-pagination] Critical error during cleanup:', error);
|
|
56
79
|
}
|
|
57
|
-
}
|
|
80
|
+
},
|
|
58
81
|
};
|
|
59
82
|
}
|
|
60
83
|
|
|
61
84
|
function initPaginationInstance(container, cleanup) {
|
|
62
|
-
const list =
|
|
85
|
+
const list = querySelector(moduleConfig, 'list', container);
|
|
63
86
|
if (!list) {
|
|
64
87
|
console.warn('[hs-pagination] Missing required element: data-hs-pagination="list"');
|
|
65
88
|
return null;
|
|
@@ -69,9 +92,9 @@ function initPaginationInstance(container, cleanup) {
|
|
|
69
92
|
if (!wrapper) return null;
|
|
70
93
|
|
|
71
94
|
const elements = {
|
|
72
|
-
controls:
|
|
73
|
-
counter:
|
|
74
|
-
dotsWrap:
|
|
95
|
+
controls: querySelector(moduleConfig, 'controls', container),
|
|
96
|
+
counter: querySelector(moduleConfig, 'counter', container),
|
|
97
|
+
dotsWrap: querySelector(moduleConfig, 'dots', container),
|
|
75
98
|
};
|
|
76
99
|
|
|
77
100
|
// Early exit for infinite mode - no controls means no pagination
|
|
@@ -79,11 +102,13 @@ function initPaginationInstance(container, cleanup) {
|
|
|
79
102
|
return { initialized: false };
|
|
80
103
|
}
|
|
81
104
|
|
|
82
|
-
// Find next/previous buttons using
|
|
83
|
-
const nextClickable =
|
|
84
|
-
const prevClickable =
|
|
85
|
-
|
|
86
|
-
const
|
|
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;
|
|
87
112
|
|
|
88
113
|
if (!nextBtn || !prevBtn) {
|
|
89
114
|
console.warn('[hs-pagination] Missing required navigation buttons');
|
|
@@ -103,11 +128,12 @@ function initPaginationInstance(container, cleanup) {
|
|
|
103
128
|
|
|
104
129
|
// Parse configuration from new attributes
|
|
105
130
|
const desktopItems = parseInt(elements.controls?.getAttribute('data-hs-pagination-show')) || 6;
|
|
106
|
-
const mobileItems =
|
|
131
|
+
const mobileItems =
|
|
132
|
+
parseInt(elements.controls?.getAttribute('data-hs-pagination-show-mobile')) || desktopItems;
|
|
107
133
|
|
|
108
134
|
const isMobileLayout = () => {
|
|
109
|
-
const
|
|
110
|
-
return
|
|
135
|
+
const stateValue = getComputedStyle(list).getPropertyValue(cssVariables.state).trim();
|
|
136
|
+
return stateValue === globalConfig.cssVars.state.values.active;
|
|
111
137
|
};
|
|
112
138
|
|
|
113
139
|
const allItems = Array.from(list.children);
|
|
@@ -120,7 +146,7 @@ function initPaginationInstance(container, cleanup) {
|
|
|
120
146
|
currentPage: 1,
|
|
121
147
|
isAnimating: false,
|
|
122
148
|
itemsPerPage: desktopItems,
|
|
123
|
-
dotTemplates: { active: null, inactive: null }
|
|
149
|
+
dotTemplates: { active: null, inactive: null },
|
|
124
150
|
};
|
|
125
151
|
let wrapperChildren = [];
|
|
126
152
|
|
|
@@ -129,8 +155,10 @@ function initPaginationInstance(container, cleanup) {
|
|
|
129
155
|
liveRegion.className = 'sr-only';
|
|
130
156
|
liveRegion.setAttribute('aria-live', 'assertive');
|
|
131
157
|
liveRegion.setAttribute('aria-atomic', 'true');
|
|
132
|
-
liveRegion.style.cssText =
|
|
158
|
+
liveRegion.style.cssText =
|
|
159
|
+
'position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;';
|
|
133
160
|
container.appendChild(liveRegion);
|
|
161
|
+
cleanup.liveRegions.push(liveRegion);
|
|
134
162
|
|
|
135
163
|
const updateCounter = () => {
|
|
136
164
|
if (elements.counter) {
|
|
@@ -140,7 +168,7 @@ function initPaginationInstance(container, cleanup) {
|
|
|
140
168
|
|
|
141
169
|
const announcePageChange = () => {
|
|
142
170
|
liveRegion.textContent = `Page ${state.currentPage} of ${state.totalPages}`;
|
|
143
|
-
setTimeout(() => liveRegion.textContent = '', 1000);
|
|
171
|
+
setTimeout(() => (liveRegion.textContent = ''), 1000);
|
|
144
172
|
};
|
|
145
173
|
|
|
146
174
|
const manageFocus = () => {
|
|
@@ -164,12 +192,16 @@ function initPaginationInstance(container, cleanup) {
|
|
|
164
192
|
if (!existingDots.length) return;
|
|
165
193
|
|
|
166
194
|
// Identify active and inactive templates
|
|
167
|
-
const activeDot = existingDots.find(dot => dot.classList.contains('is-active'));
|
|
168
|
-
const inactiveDot = existingDots.find(dot => !dot.classList.contains('is-active'));
|
|
195
|
+
const activeDot = existingDots.find((dot) => dot.classList.contains('is-active'));
|
|
196
|
+
const inactiveDot = existingDots.find((dot) => !dot.classList.contains('is-active'));
|
|
169
197
|
|
|
170
198
|
// Store templates (use same template for both if only one exists)
|
|
171
|
-
state.dotTemplates.active = activeDot
|
|
172
|
-
|
|
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);
|
|
173
205
|
|
|
174
206
|
// Clear existing dots
|
|
175
207
|
elements.dotsWrap.innerHTML = '';
|
|
@@ -180,7 +212,9 @@ function initPaginationInstance(container, cleanup) {
|
|
|
180
212
|
|
|
181
213
|
// Create dots for each page
|
|
182
214
|
for (let i = 1; i <= state.totalPages; i++) {
|
|
183
|
-
const dot = (i === 1 ? state.dotTemplates.active : state.dotTemplates.inactive).cloneNode(
|
|
215
|
+
const dot = (i === 1 ? state.dotTemplates.active : state.dotTemplates.inactive).cloneNode(
|
|
216
|
+
true
|
|
217
|
+
);
|
|
184
218
|
|
|
185
219
|
// Add accessibility attributes
|
|
186
220
|
dot.setAttribute('role', 'button');
|
|
@@ -244,11 +278,11 @@ function initPaginationInstance(container, cleanup) {
|
|
|
244
278
|
}
|
|
245
279
|
|
|
246
280
|
// Clean up previous page lists
|
|
247
|
-
Array.from(wrapper.children).forEach(child => {
|
|
281
|
+
Array.from(wrapper.children).forEach((child) => {
|
|
248
282
|
if (child !== list) wrapper.removeChild(child);
|
|
249
283
|
});
|
|
250
284
|
list.innerHTML = '';
|
|
251
|
-
allItems.forEach(item => list.appendChild(item));
|
|
285
|
+
allItems.forEach((item) => list.appendChild(item));
|
|
252
286
|
|
|
253
287
|
// Single page case
|
|
254
288
|
if (state.totalPages <= 1) {
|
|
@@ -282,18 +316,20 @@ function initPaginationInstance(container, cleanup) {
|
|
|
282
316
|
const pageList = list.cloneNode(false);
|
|
283
317
|
const startIndex = page * state.itemsPerPage;
|
|
284
318
|
const endIndex = Math.min(startIndex + state.itemsPerPage, totalItems);
|
|
285
|
-
allItems
|
|
319
|
+
allItems
|
|
320
|
+
.slice(startIndex, endIndex)
|
|
321
|
+
.forEach((item) => pageList.appendChild(item.cloneNode(true)));
|
|
286
322
|
return pageList;
|
|
287
323
|
});
|
|
288
324
|
|
|
289
325
|
// Insert cloned pages for infinite loop
|
|
290
326
|
wrapper.insertBefore(pageLists[pageLists.length - 1].cloneNode(true), list);
|
|
291
|
-
pageLists.slice(1).forEach(page => wrapper.appendChild(page));
|
|
327
|
+
pageLists.slice(1).forEach((page) => wrapper.appendChild(page));
|
|
292
328
|
wrapper.appendChild(pageLists[0].cloneNode(true));
|
|
293
329
|
|
|
294
330
|
// Populate original list with first page
|
|
295
331
|
list.innerHTML = '';
|
|
296
|
-
Array.from(pageLists[0].children).forEach(item => list.appendChild(item));
|
|
332
|
+
Array.from(pageLists[0].children).forEach((item) => list.appendChild(item));
|
|
297
333
|
|
|
298
334
|
Object.assign(state, { currentIndex: 1, currentPage: 1, isAnimating: false });
|
|
299
335
|
wrapperChildren = Array.from(wrapper.children);
|
|
@@ -341,14 +377,14 @@ function initPaginationInstance(container, cleanup) {
|
|
|
341
377
|
|
|
342
378
|
if (isMobileLayout() && elements.controls) {
|
|
343
379
|
setTimeout(() => {
|
|
344
|
-
const controlsBottom =
|
|
380
|
+
const controlsBottom =
|
|
381
|
+
elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
|
|
345
382
|
const clearance = 5 * 16; // 5rem in pixels
|
|
346
383
|
const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
|
|
347
384
|
window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
|
|
348
385
|
}, 50);
|
|
349
386
|
}
|
|
350
387
|
|
|
351
|
-
wrapper.style.transition = 'transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
|
352
388
|
wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
|
|
353
389
|
|
|
354
390
|
let transitionTimeout = null;
|
|
@@ -356,7 +392,6 @@ function initPaginationInstance(container, cleanup) {
|
|
|
356
392
|
const handleTransitionEnd = () => {
|
|
357
393
|
clearTimeout(transitionTimeout);
|
|
358
394
|
wrapper.removeEventListener('transitionend', handleTransitionEnd);
|
|
359
|
-
wrapper.style.transition = '';
|
|
360
395
|
|
|
361
396
|
updateCounter();
|
|
362
397
|
announcePageChange();
|
|
@@ -377,8 +412,12 @@ function initPaginationInstance(container, cleanup) {
|
|
|
377
412
|
state.isAnimating = true;
|
|
378
413
|
state.currentIndex += direction;
|
|
379
414
|
|
|
380
|
-
state.currentPage =
|
|
381
|
-
|
|
415
|
+
state.currentPage =
|
|
416
|
+
state.currentIndex > state.totalPages
|
|
417
|
+
? 1
|
|
418
|
+
: state.currentIndex < 1
|
|
419
|
+
? state.totalPages
|
|
420
|
+
: state.currentIndex;
|
|
382
421
|
|
|
383
422
|
updateCounter();
|
|
384
423
|
announcePageChange();
|
|
@@ -387,14 +426,14 @@ function initPaginationInstance(container, cleanup) {
|
|
|
387
426
|
|
|
388
427
|
if (isMobileLayout() && elements.controls) {
|
|
389
428
|
setTimeout(() => {
|
|
390
|
-
const controlsBottom =
|
|
429
|
+
const controlsBottom =
|
|
430
|
+
elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
|
|
391
431
|
const clearance = 5 * 16; // 5rem in pixels
|
|
392
432
|
const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
|
|
393
433
|
window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
|
|
394
434
|
}, 50);
|
|
395
435
|
}
|
|
396
436
|
|
|
397
|
-
wrapper.style.transition = 'transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
|
398
437
|
wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
|
|
399
438
|
|
|
400
439
|
let transitionTimeout = null;
|
|
@@ -402,16 +441,27 @@ function initPaginationInstance(container, cleanup) {
|
|
|
402
441
|
const handleTransitionEnd = () => {
|
|
403
442
|
clearTimeout(transitionTimeout);
|
|
404
443
|
wrapper.removeEventListener('transitionend', handleTransitionEnd);
|
|
405
|
-
wrapper.style.transition = '';
|
|
406
444
|
|
|
407
445
|
if (state.currentIndex > state.totalPages) {
|
|
408
446
|
state.currentIndex = 1;
|
|
409
447
|
state.currentPage = 1;
|
|
448
|
+
// Disable transition for instant jump
|
|
449
|
+
wrapper.style.transition = 'none';
|
|
410
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 = '';
|
|
411
455
|
} else if (state.currentIndex < 1) {
|
|
412
456
|
state.currentIndex = state.totalPages;
|
|
413
457
|
state.currentPage = state.totalPages;
|
|
458
|
+
// Disable transition for instant jump
|
|
459
|
+
wrapper.style.transition = 'none';
|
|
414
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 = '';
|
|
415
465
|
}
|
|
416
466
|
|
|
417
467
|
updateCounter();
|
|
@@ -441,5 +491,3 @@ function initPaginationInstance(container, cleanup) {
|
|
|
441
491
|
|
|
442
492
|
return { initialized: true };
|
|
443
493
|
}
|
|
444
|
-
|
|
445
|
-
export const version = "1.0.0";
|
|
@@ -10,20 +10,20 @@ The site settings system collects dynamic values from designated elements and re
|
|
|
10
10
|
|
|
11
11
|
### **Required Elements**
|
|
12
12
|
|
|
13
|
-
**Settings Wrapper**
|
|
13
|
+
**Settings Wrapper** _(container for all settings lists)_
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
- data-site-settings-element="wrapper"
|
|
16
16
|
|
|
17
|
-
**Settings List**
|
|
17
|
+
**Settings List** _(can have multiple lists inside wrapper)_
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
- data-site-settings-element="list"
|
|
20
20
|
|
|
21
|
-
**Setting Element**
|
|
21
|
+
**Setting Element** _(individual setting with a name and value)_
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
- data-site-settings="name"
|
|
24
|
+
- The attribute value is the placeholder name (e.g., "company", "email", "phone")
|
|
25
|
+
- The element's text content is used for text replacements
|
|
26
|
+
- The element's href attribute (if present) is used for link replacements
|
|
27
27
|
|
|
28
28
|
**Typical element layout:**
|
|
29
29
|
|
|
@@ -48,18 +48,20 @@ The site settings system collects dynamic values from designated elements and re
|
|
|
48
48
|
### **Text vs Href Behavior**
|
|
49
49
|
|
|
50
50
|
**Text replacement** (in page content):
|
|
51
|
+
|
|
51
52
|
- **First choice:** Use text value
|
|
52
53
|
- **Fallback:** Use href value if no text
|
|
53
54
|
|
|
54
55
|
**Href replacement** (in link attributes):
|
|
56
|
+
|
|
55
57
|
- **With dedicated href:** Completely replace entire href (fixes Webflow prefix issue)
|
|
56
58
|
- **Without dedicated href:** Normal placeholder replacement with text value
|
|
57
59
|
|
|
58
60
|
### **When It Runs**
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
- Runs once on page load
|
|
63
|
+
- Runs again on Barba.js page transitions (via `reinitialize()`)
|
|
64
|
+
- No ongoing monitoring or event listeners
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
@@ -67,11 +69,11 @@ The site settings system collects dynamic values from designated elements and re
|
|
|
67
69
|
|
|
68
70
|
### **Optional Hide Attribute**
|
|
69
71
|
|
|
70
|
-
**Hide Element**
|
|
72
|
+
**Hide Element** _(removes content conditionally before extracting text)_
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
- data-site-settings-hide="class-name"
|
|
75
|
+
- Can specify multiple classes separated by spaces
|
|
76
|
+
- Element is removed if it has ANY of the specified classes
|
|
75
77
|
|
|
76
78
|
### **Example Use Case**
|
|
77
79
|
|
|
@@ -150,6 +152,7 @@ For dynamic content where certain parts should be hidden based on CMS conditions
|
|
|
150
152
|
```
|
|
151
153
|
|
|
152
154
|
**Result:**
|
|
155
|
+
|
|
153
156
|
```html
|
|
154
157
|
<h1>Welcome to Compass Facilities</h1>
|
|
155
158
|
<p>Contact us at info@compass.com or call (555) 123-4567</p>
|
|
@@ -170,6 +173,7 @@ For dynamic content where certain parts should be hidden based on CMS conditions
|
|
|
170
173
|
```
|
|
171
174
|
|
|
172
175
|
**Result:**
|
|
176
|
+
|
|
173
177
|
```html
|
|
174
178
|
<!-- Entire href replaced with dedicated href value -->
|
|
175
179
|
<a href="mailto:info@compass.com">Email Us</a>
|
|
@@ -178,6 +182,7 @@ For dynamic content where certain parts should be hidden based on CMS conditions
|
|
|
178
182
|
```
|
|
179
183
|
|
|
180
184
|
**Why this matters:** Webflow often adds `https://` prefix by default. Without complete href replacement:
|
|
185
|
+
|
|
181
186
|
- `https://tel:+15551234567` ❌ (broken)
|
|
182
187
|
- `tel:+15551234567` ✅ (works)
|
|
183
188
|
|
|
@@ -196,6 +201,7 @@ When setting has only text (no href), normal placeholder replacement occurs:
|
|
|
196
201
|
```
|
|
197
202
|
|
|
198
203
|
**Result:**
|
|
204
|
+
|
|
199
205
|
```html
|
|
200
206
|
<a href="https://compassfacilities.com">Visit Site</a>
|
|
201
207
|
```
|
|
@@ -234,13 +240,13 @@ All settings are collected regardless of which list they're in.
|
|
|
234
240
|
|
|
235
241
|
## **Key Attributes Summary**
|
|
236
242
|
|
|
237
|
-
| Attribute
|
|
238
|
-
|
|
|
239
|
-
| `data-site-settings-element="wrapper"` | Settings container
|
|
240
|
-
| `data-site-settings-element="list"`
|
|
241
|
-
| `data-site-settings="name"`
|
|
242
|
-
| `href` attribute
|
|
243
|
-
| `data-site-settings-hide="class"`
|
|
243
|
+
| Attribute | Purpose | Required On |
|
|
244
|
+
| -------------------------------------- | ----------------------------- | ---------------------------------- |
|
|
245
|
+
| `data-site-settings-element="wrapper"` | Settings container | Wrapper div |
|
|
246
|
+
| `data-site-settings-element="list"` | Settings list | List div (at least one) |
|
|
247
|
+
| `data-site-settings="name"` | Setting with name | Individual setting elements |
|
|
248
|
+
| `href` attribute | Link URL for href replacement | Optional (on link settings) |
|
|
249
|
+
| `data-site-settings-hide="class"` | Conditional hide | Optional (inside setting elements) |
|
|
244
250
|
|
|
245
251
|
---
|
|
246
252
|
|
|
@@ -248,21 +254,23 @@ All settings are collected regardless of which list they're in.
|
|
|
248
254
|
|
|
249
255
|
**Format:** `{{name}}`
|
|
250
256
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
- Double curly braces
|
|
258
|
+
- Name must match the `data-site-settings` attribute value exactly
|
|
259
|
+
- Case-sensitive
|
|
260
|
+
- Works in text content and link hrefs
|
|
255
261
|
|
|
256
262
|
---
|
|
257
263
|
|
|
258
264
|
## **Replacement Logic**
|
|
259
265
|
|
|
260
266
|
### **For Text Content (`{{name}}` in page text):**
|
|
267
|
+
|
|
261
268
|
1. If setting has text value → use text
|
|
262
269
|
2. If no text but has href value → use href
|
|
263
270
|
3. If neither → skip replacement
|
|
264
271
|
|
|
265
272
|
### **For Link Hrefs (`{{name}}` in href attribute):**
|
|
273
|
+
|
|
266
274
|
1. If setting has href value → **completely replace entire href**
|
|
267
275
|
2. If no href but has text value → normal placeholder replacement
|
|
268
276
|
3. If neither → skip replacement
|
|
@@ -272,6 +280,7 @@ All settings are collected regardless of which list they're in.
|
|
|
272
280
|
## **Common Use Cases**
|
|
273
281
|
|
|
274
282
|
### **Phone Number**
|
|
283
|
+
|
|
275
284
|
```html
|
|
276
285
|
<!-- Setting with both formatted display and tel: link -->
|
|
277
286
|
<a data-site-settings="phone" href="tel:+15551234567">(555) 123-4567</a>
|
|
@@ -286,6 +295,7 @@ All settings are collected regardless of which list they're in.
|
|
|
286
295
|
```
|
|
287
296
|
|
|
288
297
|
### **Email Address**
|
|
298
|
+
|
|
289
299
|
```html
|
|
290
300
|
<!-- Setting -->
|
|
291
301
|
<a data-site-settings="email" href="mailto:contact@example.com">contact@example.com</a>
|
|
@@ -300,6 +310,7 @@ All settings are collected regardless of which list they're in.
|
|
|
300
310
|
```
|
|
301
311
|
|
|
302
312
|
### **Social Media**
|
|
313
|
+
|
|
303
314
|
```html
|
|
304
315
|
<!-- Setting -->
|
|
305
316
|
<a data-site-settings="facebook" href="https://facebook.com/mypage">@mypage</a>
|
|
@@ -326,6 +337,25 @@ All settings are collected regardless of which list they're in.
|
|
|
326
337
|
|
|
327
338
|
---
|
|
328
339
|
|
|
340
|
+
## **v2.0.0 Improvements**
|
|
341
|
+
|
|
342
|
+
### **JSON Config System**
|
|
343
|
+
|
|
344
|
+
- Added config.js with all attribute definitions
|
|
345
|
+
- Future-proof: Easy to add aliases or change attribute patterns
|
|
346
|
+
- Self-documenting: All supported attributes in one place
|
|
347
|
+
|
|
348
|
+
### **Barba.js / SPA Compatibility**
|
|
349
|
+
|
|
350
|
+
The site-settings system is fully compatible with Barba.js and other SPA frameworks:
|
|
351
|
+
|
|
352
|
+
- Works on fresh page load and reinitialize
|
|
353
|
+
- No destroy needed (one-time DOM operation)
|
|
354
|
+
- Automatically replaces placeholders on new pages
|
|
355
|
+
- Works like fresh page load on new DOM
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
329
359
|
## **Common Issues**
|
|
330
360
|
|
|
331
361
|
**Placeholder not replacing:**
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { querySelector, querySelectorAll, getSelector } from '@utils';
|
|
2
|
+
|
|
3
|
+
export function init(config) {
|
|
2
4
|
function setupSiteSettings() {
|
|
3
|
-
const wrapper =
|
|
5
|
+
const wrapper = querySelector(config, 'wrapper');
|
|
4
6
|
|
|
5
7
|
if (!wrapper) {
|
|
6
8
|
return;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
// Find all lists inside the wrapper
|
|
10
|
-
const lists =
|
|
12
|
+
const lists = querySelectorAll(config, 'list', wrapper);
|
|
11
13
|
|
|
12
14
|
if (lists.length === 0) {
|
|
13
15
|
return;
|
|
@@ -16,26 +18,30 @@ export function init() {
|
|
|
16
18
|
// Collect all site settings data
|
|
17
19
|
const siteSettings = {};
|
|
18
20
|
|
|
19
|
-
lists.forEach(list => {
|
|
21
|
+
lists.forEach((list) => {
|
|
20
22
|
// Find all descendants with data-site-settings attribute
|
|
21
|
-
const
|
|
23
|
+
const settingSelector = getSelector(config, 'setting');
|
|
24
|
+
const settingElements = list.querySelectorAll(settingSelector);
|
|
22
25
|
|
|
23
|
-
settingElements.forEach(element => {
|
|
26
|
+
settingElements.forEach((element) => {
|
|
24
27
|
// Clone the element to avoid modifying the original DOM
|
|
25
28
|
const clonedElement = element.cloneNode(true);
|
|
26
29
|
|
|
27
30
|
// Find and remove elements with data-site-settings-hide that match their classes
|
|
28
|
-
const
|
|
31
|
+
const hideSelector = getSelector(config, 'hide');
|
|
32
|
+
const hideElements = clonedElement.querySelectorAll(hideSelector);
|
|
29
33
|
|
|
30
|
-
hideElements.forEach(hideElement => {
|
|
31
|
-
const
|
|
34
|
+
hideElements.forEach((hideElement) => {
|
|
35
|
+
const hideAttr =
|
|
36
|
+
config.attributes.elements.hide.primary.match(/data-site-settings-hide/)[0];
|
|
37
|
+
const hideValue = hideElement.getAttribute(hideAttr);
|
|
32
38
|
|
|
33
39
|
if (hideValue) {
|
|
34
40
|
// Split by spaces to get all class names to check
|
|
35
|
-
const classesToHide = hideValue.split(' ').filter(c => c.trim());
|
|
41
|
+
const classesToHide = hideValue.split(' ').filter((c) => c.trim());
|
|
36
42
|
|
|
37
43
|
// Check if element has any of the classes
|
|
38
|
-
const hasMatchingClass = classesToHide.some(className =>
|
|
44
|
+
const hasMatchingClass = classesToHide.some((className) =>
|
|
39
45
|
hideElement.classList.contains(className)
|
|
40
46
|
);
|
|
41
47
|
|
|
@@ -46,7 +52,9 @@ export function init() {
|
|
|
46
52
|
}
|
|
47
53
|
});
|
|
48
54
|
|
|
49
|
-
const
|
|
55
|
+
const settingAttr =
|
|
56
|
+
config.attributes.elements.setting.primary.match(/data-site-settings/)[0];
|
|
57
|
+
const settingName = element.getAttribute(settingAttr);
|
|
50
58
|
const settingText = clonedElement.textContent.trim();
|
|
51
59
|
const settingHref = element.getAttribute('href');
|
|
52
60
|
|
|
@@ -54,7 +62,7 @@ export function init() {
|
|
|
54
62
|
if (settingName && (settingText || settingHref)) {
|
|
55
63
|
siteSettings[settingName] = {
|
|
56
64
|
text: settingText || null,
|
|
57
|
-
href: settingHref || null
|
|
65
|
+
href: settingHref || null,
|
|
58
66
|
};
|
|
59
67
|
}
|
|
60
68
|
});
|
|
@@ -66,31 +74,27 @@ export function init() {
|
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
// Replace text content efficiently using TreeWalker
|
|
69
|
-
const walker = document.createTreeWalker(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for (const name in siteSettings) {
|
|
77
|
-
if (text.includes(`{{${name}}}`)) {
|
|
78
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
79
|
-
}
|
|
77
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
|
78
|
+
acceptNode: (node) => {
|
|
79
|
+
// Check if any site setting names exist in the text content
|
|
80
|
+
const text = node.textContent;
|
|
81
|
+
for (const name in siteSettings) {
|
|
82
|
+
if (text.includes(`{{${name}}}`)) {
|
|
83
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
80
84
|
}
|
|
81
|
-
return NodeFilter.FILTER_SKIP;
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
return NodeFilter.FILTER_SKIP;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
85
89
|
|
|
86
90
|
const textNodes = [];
|
|
87
91
|
let node;
|
|
88
|
-
while (node = walker.nextNode()) {
|
|
92
|
+
while ((node = walker.nextNode())) {
|
|
89
93
|
textNodes.push(node);
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
// Replace text in collected nodes
|
|
93
|
-
textNodes.forEach(textNode => {
|
|
97
|
+
textNodes.forEach((textNode) => {
|
|
94
98
|
let newText = textNode.textContent;
|
|
95
99
|
let hasChanges = false;
|
|
96
100
|
|
|
@@ -114,7 +118,7 @@ export function init() {
|
|
|
114
118
|
|
|
115
119
|
// Replace link hrefs
|
|
116
120
|
const links = document.querySelectorAll('a[href]');
|
|
117
|
-
links.forEach(link => {
|
|
121
|
+
links.forEach((link) => {
|
|
118
122
|
let href = link.getAttribute('href');
|
|
119
123
|
let hasChanges = false;
|
|
120
124
|
|
|
@@ -146,9 +150,9 @@ export function init() {
|
|
|
146
150
|
setupSiteSettings();
|
|
147
151
|
|
|
148
152
|
return {
|
|
149
|
-
result:
|
|
153
|
+
result: 'site-settings initialized',
|
|
150
154
|
destroy: () => {
|
|
151
155
|
// No cleanup needed - this module only modifies DOM during init
|
|
152
|
-
}
|
|
156
|
+
},
|
|
153
157
|
};
|
|
154
158
|
}
|