@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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Transition Module
|
|
3
|
+
* Enhanced with namespace support and SPA/MPA compatibility
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Backward compatible with old single-trigger system
|
|
7
|
+
* - Namespace-specific transitions (data-hs-transition-namespace)
|
|
8
|
+
* - Separate exit/enter triggers with custom timing (optional)
|
|
9
|
+
* - MPA: Auto link interception (default)
|
|
10
|
+
* - SPA: Event-based integration (set data-hs-spa="true" on script tag)
|
|
11
|
+
*
|
|
12
|
+
* @version 2.0.0
|
|
13
|
+
*/
|
|
14
|
+
import fullConfig from '@config';
|
|
15
|
+
import { querySelector, querySelectorAll, globalConfig } from '@utils';
|
|
16
|
+
|
|
17
|
+
const API_NAME = globalConfig.apiName;
|
|
18
|
+
|
|
19
|
+
export async function init(transitionConfig) {
|
|
20
|
+
const config = transitionConfig || fullConfig.default?.transition;
|
|
21
|
+
|
|
22
|
+
// Check if transition element exists - skip if not found
|
|
23
|
+
const transitionElement = querySelector(config, 'element');
|
|
24
|
+
if (!transitionElement) {
|
|
25
|
+
console.log('[transition] No transition element found, skipping initialization');
|
|
26
|
+
return {
|
|
27
|
+
result: 'transition skipped - no element found',
|
|
28
|
+
destroy: () => {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get current namespace (if any)
|
|
33
|
+
const namespaceAttr = config.attributes.elements.namespace.primary;
|
|
34
|
+
const currentNamespace = transitionElement.getAttribute(namespaceAttr) || null;
|
|
35
|
+
|
|
36
|
+
// Check if running in SPA mode
|
|
37
|
+
const isSPA = window[API_NAME]?.settings?.isSPA || false;
|
|
38
|
+
|
|
39
|
+
// Store event handlers and timeouts for cleanup
|
|
40
|
+
const eventHandlers = new Map();
|
|
41
|
+
let delayTimeout = null;
|
|
42
|
+
let navTimeout = null;
|
|
43
|
+
|
|
44
|
+
const cleanup = () => {
|
|
45
|
+
// Clear any pending timeouts
|
|
46
|
+
if (delayTimeout) clearTimeout(delayTimeout);
|
|
47
|
+
if (navTimeout) clearTimeout(navTimeout);
|
|
48
|
+
|
|
49
|
+
// Remove all event listeners
|
|
50
|
+
eventHandlers.forEach((handler, event) => {
|
|
51
|
+
if (event === 'click' || event === 'pageshow' || event === 'resize') {
|
|
52
|
+
const target = event === 'click' ? document : window;
|
|
53
|
+
target.removeEventListener(event, handler);
|
|
54
|
+
} else {
|
|
55
|
+
window.removeEventListener(event, handler);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
eventHandlers.clear();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Find trigger element for specific namespace and type (exit or enter)
|
|
63
|
+
* Falls back to default trigger (no namespace) if namespace-specific not found
|
|
64
|
+
* Also supports legacy single-trigger system for backward compatibility
|
|
65
|
+
*/
|
|
66
|
+
const findTrigger = (type: 'exit' | 'enter', namespace: string | null) => {
|
|
67
|
+
// Try new system: separate exit/enter triggers
|
|
68
|
+
const allTriggers = querySelectorAll(config, type === 'exit' ? 'exitTrigger' : 'enterTrigger');
|
|
69
|
+
|
|
70
|
+
if (allTriggers.length > 0) {
|
|
71
|
+
// If namespace specified, try to find namespace-specific trigger first
|
|
72
|
+
if (namespace) {
|
|
73
|
+
const namespaceTrigger = Array.from(allTriggers).find(
|
|
74
|
+
(trigger) => trigger.getAttribute(namespaceAttr) === namespace
|
|
75
|
+
);
|
|
76
|
+
if (namespaceTrigger) return namespaceTrigger;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fall back to default trigger (no namespace attribute)
|
|
80
|
+
const defaultTrigger = Array.from(allTriggers).find(
|
|
81
|
+
(trigger) => !trigger.hasAttribute(namespaceAttr)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (defaultTrigger) return defaultTrigger;
|
|
85
|
+
return allTriggers[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Legacy fallback: look for old single trigger system
|
|
89
|
+
// This maintains backward compatibility with existing sites
|
|
90
|
+
const legacyTriggers = document.querySelectorAll('[data-hs-transition="trigger"]');
|
|
91
|
+
if (legacyTriggers.length > 0) {
|
|
92
|
+
// If namespace specified, try to find namespace-specific trigger
|
|
93
|
+
if (namespace) {
|
|
94
|
+
const namespaceTrigger = Array.from(legacyTriggers).find(
|
|
95
|
+
(trigger) => trigger.getAttribute(namespaceAttr) === namespace
|
|
96
|
+
);
|
|
97
|
+
if (namespaceTrigger) return namespaceTrigger;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fall back to first legacy trigger
|
|
101
|
+
return legacyTriggers[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get timing value from trigger element or transition element (legacy)
|
|
109
|
+
*/
|
|
110
|
+
const getTiming = (
|
|
111
|
+
trigger: Element | null,
|
|
112
|
+
type: 'time' | 'delay',
|
|
113
|
+
direction: 'exit' | 'enter'
|
|
114
|
+
): number => {
|
|
115
|
+
if (!trigger) return 0;
|
|
116
|
+
|
|
117
|
+
// Try new system: timing attributes on trigger element
|
|
118
|
+
const attrName =
|
|
119
|
+
direction === 'exit'
|
|
120
|
+
? type === 'time'
|
|
121
|
+
? config.attributes.elements.exitTime.primary
|
|
122
|
+
: config.attributes.elements.exitDelay.primary
|
|
123
|
+
: type === 'time'
|
|
124
|
+
? config.attributes.elements.enterTime.primary
|
|
125
|
+
: config.attributes.elements.enterDelay.primary;
|
|
126
|
+
|
|
127
|
+
let value = trigger.getAttribute(attrName);
|
|
128
|
+
if (value) return parseFloat(value) * 1000;
|
|
129
|
+
|
|
130
|
+
// Legacy fallback: timing on transition element
|
|
131
|
+
if (direction === 'exit' && type === 'time') {
|
|
132
|
+
value = transitionElement.getAttribute('data-hs-exit-time');
|
|
133
|
+
if (value) return parseFloat(value) * 1000;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (direction === 'enter' && type === 'delay') {
|
|
137
|
+
value = transitionElement.getAttribute('data-hs-delay');
|
|
138
|
+
if (value) return parseFloat(value) * 1000;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return 0;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Play exit animation (when leaving page)
|
|
146
|
+
*/
|
|
147
|
+
const playExitAnimation = (namespace: string | null = currentNamespace): Promise<void> => {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const exitTrigger = findTrigger('exit', namespace);
|
|
150
|
+
if (!exitTrigger) {
|
|
151
|
+
resolve();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const exitTime = getTiming(exitTrigger, 'time', 'exit');
|
|
156
|
+
const exitDelay = getTiming(exitTrigger, 'delay', 'exit');
|
|
157
|
+
|
|
158
|
+
const triggerAnimation = () => {
|
|
159
|
+
if (typeof Webflow !== 'undefined') {
|
|
160
|
+
Webflow.push(() => {
|
|
161
|
+
(exitTrigger as HTMLElement).click();
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
(exitTrigger as HTMLElement).click();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Resolve after animation completes
|
|
168
|
+
setTimeout(resolve, exitTime);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (exitDelay > 0) {
|
|
172
|
+
setTimeout(triggerAnimation, exitDelay);
|
|
173
|
+
} else {
|
|
174
|
+
triggerAnimation();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Play enter animation (when arriving on page)
|
|
181
|
+
*/
|
|
182
|
+
const playEnterAnimation = (namespace: string | null = currentNamespace): Promise<void> => {
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
const enterTrigger = findTrigger('enter', namespace);
|
|
185
|
+
if (!enterTrigger) {
|
|
186
|
+
resolve();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const enterTime = getTiming(enterTrigger, 'time', 'enter');
|
|
191
|
+
const enterDelay = getTiming(enterTrigger, 'delay', 'enter');
|
|
192
|
+
|
|
193
|
+
// Check if first load (legacy behavior)
|
|
194
|
+
const isFirstLoad = !sessionStorage.getItem('transition-loaded');
|
|
195
|
+
|
|
196
|
+
const triggerAnimation = () => {
|
|
197
|
+
if (typeof Webflow !== 'undefined') {
|
|
198
|
+
Webflow.push(() => {
|
|
199
|
+
(enterTrigger as HTMLElement).click();
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
(enterTrigger as HTMLElement).click();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Mark as loaded for session
|
|
206
|
+
if (isFirstLoad) {
|
|
207
|
+
sessionStorage.setItem('transition-loaded', 'true');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Resolve after animation completes
|
|
211
|
+
setTimeout(resolve, enterTime);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Only apply delay on first load (legacy behavior)
|
|
215
|
+
if (isFirstLoad && enterDelay > 0) {
|
|
216
|
+
delayTimeout = setTimeout(triggerAnimation, enterDelay);
|
|
217
|
+
} else {
|
|
218
|
+
triggerAnimation();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Always listen for custom events (works for both MPA and SPA)
|
|
224
|
+
const handleExitEvent = () => playExitAnimation();
|
|
225
|
+
const handleEnterEvent = () => playEnterAnimation();
|
|
226
|
+
|
|
227
|
+
eventHandlers.set('hsmain:transition-exit', handleExitEvent);
|
|
228
|
+
eventHandlers.set('hsmain:transition-enter', handleEnterEvent);
|
|
229
|
+
window.addEventListener('hsmain:transition-exit', handleExitEvent);
|
|
230
|
+
window.addEventListener('hsmain:transition-enter', handleEnterEvent);
|
|
231
|
+
|
|
232
|
+
// MPA Mode: Intercept link clicks
|
|
233
|
+
if (!isSPA) {
|
|
234
|
+
const linkClickHandler = (e: MouseEvent) => {
|
|
235
|
+
const link = (e.target as Element).closest('a[href]') as HTMLAnchorElement;
|
|
236
|
+
|
|
237
|
+
if (
|
|
238
|
+
link &&
|
|
239
|
+
link.hostname === window.location.hostname &&
|
|
240
|
+
link.getAttribute('href') &&
|
|
241
|
+
link.getAttribute('href')!.indexOf('#') === -1 &&
|
|
242
|
+
link.getAttribute('target') !== '_blank'
|
|
243
|
+
) {
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
const href = link.href;
|
|
246
|
+
|
|
247
|
+
// Play exit animation, then navigate
|
|
248
|
+
playExitAnimation().then(() => {
|
|
249
|
+
window.location.href = href;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
eventHandlers.set('click', linkClickHandler);
|
|
255
|
+
document.addEventListener('click', linkClickHandler);
|
|
256
|
+
|
|
257
|
+
// Handle back button navigation
|
|
258
|
+
const pageShowHandler = (event: PageTransitionEvent) => {
|
|
259
|
+
if (event.persisted) {
|
|
260
|
+
window.location.reload();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
eventHandlers.set('pageshow', pageShowHandler);
|
|
265
|
+
window.addEventListener('pageshow', pageShowHandler);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Hide transition on window resize (prevents visual glitches)
|
|
269
|
+
const resizeHandler = () => {
|
|
270
|
+
if (transitionElement) {
|
|
271
|
+
(transitionElement as HTMLElement).style.display = 'none';
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
eventHandlers.set('resize', resizeHandler);
|
|
276
|
+
window.addEventListener('resize', resizeHandler);
|
|
277
|
+
|
|
278
|
+
// On DOM ready, play enter animation
|
|
279
|
+
const domReadyHandler = () => {
|
|
280
|
+
playEnterAnimation();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
eventHandlers.set(`${API_NAME}:dom-ready`, domReadyHandler);
|
|
284
|
+
window.addEventListener(`${API_NAME}:dom-ready`, domReadyHandler, { once: true });
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
result: 'transition initialized',
|
|
288
|
+
destroy: cleanup,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# **Normalize System Documentation**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
The normalize module provides functions that modify DOM structure before any visual changes occur. These are critical functions that run in Phase 1 (normalize) of the phased initialization system.
|
|
6
|
+
|
|
7
|
+
**Purpose**: Ensure DOM structure is normalized and consistent before visual-dom modules run, preventing layout shifts and ensuring reliable selectors.
|
|
8
|
+
|
|
9
|
+
**Phase**: Normalize (Phase 1) - runs first, before all other modules
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## **Functions**
|
|
14
|
+
|
|
15
|
+
### **1. Clickable**
|
|
16
|
+
|
|
17
|
+
Normalizes clickable wrappers by keeping either `<button>` or `<a>` element based on href validity.
|
|
18
|
+
|
|
19
|
+
**Use case:** Designer includes both button and link in wrapper. Script automatically keeps the semantically correct one based on whether there's a valid link destination.
|
|
20
|
+
|
|
21
|
+
**Sub-functions:**
|
|
22
|
+
|
|
23
|
+
- **normalize**: Removes duplicate button/link elements, keeps the correct one
|
|
24
|
+
|
|
25
|
+
**Performance:** ~30-50ms for 100 wrappers (with 1s timeout safety)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## **Documentation**
|
|
30
|
+
|
|
31
|
+
Each function has detailed documentation in its respective folder:
|
|
32
|
+
|
|
33
|
+
- `functions/clickable/README.md`
|
|
34
|
+
- `functions/clickable/functions/normalize/README.md`
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## **How It Works**
|
|
39
|
+
|
|
40
|
+
The normalize system uses a dynamic loader pattern:
|
|
41
|
+
|
|
42
|
+
1. Main `normalize.js` imports all function modules
|
|
43
|
+
2. Each function is loaded in parallel via `Promise.allSettled()`
|
|
44
|
+
3. All destroy functions are collected for cleanup
|
|
45
|
+
4. On destroy, all functions are cleaned up properly
|
|
46
|
+
|
|
47
|
+
**Initialization Flow**:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Page Load
|
|
51
|
+
↓
|
|
52
|
+
Phase 1: Normalize (normalize module) ← WE ARE HERE
|
|
53
|
+
↓
|
|
54
|
+
Phase 2: Visual-DOM (structure + form)
|
|
55
|
+
↓
|
|
56
|
+
Phase 3: Transition reveals page
|
|
57
|
+
↓
|
|
58
|
+
Phase 4: Default modules (accessibility, navbar, etc.)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## **Phased Initialization**
|
|
64
|
+
|
|
65
|
+
### **Why Phase 1?**
|
|
66
|
+
|
|
67
|
+
These functions modify the DOM structure in fundamental ways:
|
|
68
|
+
|
|
69
|
+
- Clickable removes elements from DOM
|
|
70
|
+
- Other normalize functions might add/remove/restructure elements
|
|
71
|
+
- Must happen BEFORE visual modules select elements
|
|
72
|
+
|
|
73
|
+
**Without phasing**: Visual modules might select wrong elements or fail to find elements
|
|
74
|
+
|
|
75
|
+
**With phasing**: DOM structure is normalized → visual modules always find correct elements
|
|
76
|
+
|
|
77
|
+
### **Phase Characteristics**
|
|
78
|
+
|
|
79
|
+
- **Timing**: Runs first, before all other modules
|
|
80
|
+
- **Parallelization**: All functions load/execute in parallel within Phase 1
|
|
81
|
+
- **Resilience**: Uses `Promise.allSettled` - individual failures don't block phase
|
|
82
|
+
- **Timeout**: 1s timeout per module (safety net for stuck DOM operations)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## **Barba.js / SPA Compatibility**
|
|
87
|
+
|
|
88
|
+
The normalize system is fully compatible with Barba.js and other SPA frameworks:
|
|
89
|
+
|
|
90
|
+
### **v2.0.0 Improvements:**
|
|
91
|
+
|
|
92
|
+
- **Resilient loading with Promise.allSettled** - Individual function failures won't break other functions
|
|
93
|
+
- **Graceful error handling** - Failed functions are logged but don't prevent successful ones from working
|
|
94
|
+
- **Complete cleanup on destroy** - All functions properly cleaned up for page transitions
|
|
95
|
+
- **Phased initialization** - Same clean flow on page load and SPA transitions
|
|
96
|
+
|
|
97
|
+
### **Resilient Loading:**
|
|
98
|
+
|
|
99
|
+
- If one function fails (missing element, error, etc.), others continue working
|
|
100
|
+
- Logs helpful warnings showing which functions succeeded/failed
|
|
101
|
+
- System remains functional even with partial failures
|
|
102
|
+
|
|
103
|
+
### **On Destroy:**
|
|
104
|
+
|
|
105
|
+
1. Calls destroy() on all successfully loaded functions
|
|
106
|
+
2. Errors during cleanup are caught and logged
|
|
107
|
+
3. Cleanup tracking arrays are reset
|
|
108
|
+
4. Safe for Barba.js page transitions
|
|
109
|
+
|
|
110
|
+
### **On Reinitialize:**
|
|
111
|
+
|
|
112
|
+
1. Attempts to load all functions again
|
|
113
|
+
2. Works like fresh page load on new DOM
|
|
114
|
+
3. Independent function loading ensures maximum resilience
|
|
115
|
+
4. Runs in same Phase 1 timing for consistent UX
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## **Performance**
|
|
120
|
+
|
|
121
|
+
**Expected load time**: ~20-50ms total
|
|
122
|
+
|
|
123
|
+
Individual timing varies by function:
|
|
124
|
+
|
|
125
|
+
- Clickable normalization: ~30-50ms (DOM manipulation for 100 elements)
|
|
126
|
+
|
|
127
|
+
**Total Phase 1 timing**: All normalize functions in parallel, typically completes in 30-50ms
|
|
128
|
+
|
|
129
|
+
**1s timeout**: Safety net for stuck operations, ensures Phase 2 starts even if normalize hangs
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## **Adding New Normalize Functions**
|
|
134
|
+
|
|
135
|
+
To add a new normalize function:
|
|
136
|
+
|
|
137
|
+
1. **Create function folder:**
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
autoInit/normalize/functions/my-function/
|
|
141
|
+
my-function.js
|
|
142
|
+
config.js
|
|
143
|
+
README.md
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
2. **Update normalize.js functionMap:**
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const functionMap = {
|
|
150
|
+
clickable: () => import('./functions/clickable/clickable.js'),
|
|
151
|
+
'my-function': () => import('./functions/my-function/my-function.js'),
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
3. **Follow the pattern:**
|
|
156
|
+
- Export `init()` function that returns `{ result, destroy }`
|
|
157
|
+
- Export `version` constant
|
|
158
|
+
- Use Promise.allSettled for resilient loading
|
|
159
|
+
- Clean up properly in destroy()
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## **Notes**
|
|
164
|
+
|
|
165
|
+
- All functions auto-initialize on page load
|
|
166
|
+
- Each function operates independently
|
|
167
|
+
- Barba.js compatible with proper cleanup
|
|
168
|
+
- No configuration required at normalize level - each function uses its own config
|
|
169
|
+
- Runs in Phase 1 (normalize) of initialization system
|
|
170
|
+
- Uses Promise.allSettled for maximum reliability
|
|
171
|
+
- Part of v2.0.0 phased initialization architecture
|
|
172
|
+
- 1s timeout per module ensures Phase 2 starts even if normalize hangs
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# **Clickable System Documentation**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
The clickable system provides modular functions to manage clickable elements and their behaviors. Each function operates independently and can be customized through data attributes.
|
|
6
|
+
|
|
7
|
+
**Note:** This module auto-initializes and loads all functions on page load.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## **Functions**
|
|
12
|
+
|
|
13
|
+
### **1. Normalize**
|
|
14
|
+
|
|
15
|
+
Normalizes clickable wrappers by keeping either `<button>` or `<a>` element based on href validity.
|
|
16
|
+
|
|
17
|
+
**Use case:** Designer includes both button and link in wrapper. Script automatically keeps the semantically correct one based on whether there's a valid link destination.
|
|
18
|
+
|
|
19
|
+
**How it works:**
|
|
20
|
+
|
|
21
|
+
- If `<a>` has valid href (not empty, not "#") → keep link, remove button
|
|
22
|
+
- Otherwise → keep button, remove link
|
|
23
|
+
- Kept element gets `data-site-clickable="button"` for reliable selection by other modules
|
|
24
|
+
|
|
25
|
+
**Performance:** ~30-50ms for 100 wrappers
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## **Documentation**
|
|
30
|
+
|
|
31
|
+
Each function has detailed documentation in its respective folder:
|
|
32
|
+
|
|
33
|
+
- `functions/normalize/README.md`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## **How It Works**
|
|
38
|
+
|
|
39
|
+
The clickable system uses a dynamic loader pattern:
|
|
40
|
+
|
|
41
|
+
1. Main `clickable.js` imports all function modules
|
|
42
|
+
2. Each function is loaded in parallel via `Promise.allSettled()`
|
|
43
|
+
3. All destroy functions are collected for cleanup
|
|
44
|
+
4. On destroy, all functions are cleaned up properly
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## **Barba.js / SPA Compatibility**
|
|
49
|
+
|
|
50
|
+
The clickable system is fully compatible with Barba.js and other SPA frameworks:
|
|
51
|
+
|
|
52
|
+
### **v2.0.0 Improvements:**
|
|
53
|
+
|
|
54
|
+
- **Resilient loading with Promise.allSettled** - Individual function failures won't break other functions
|
|
55
|
+
- **Graceful error handling** - Failed functions are logged but don't prevent successful ones from working
|
|
56
|
+
- **Complete cleanup on destroy** - All functions properly cleaned up for page transitions
|
|
57
|
+
|
|
58
|
+
### **Resilient Loading:**
|
|
59
|
+
|
|
60
|
+
- If one clickable function fails (missing element, error, etc.), others continue working
|
|
61
|
+
- Logs helpful warnings showing which functions succeeded/failed
|
|
62
|
+
- System remains functional even with partial failures
|
|
63
|
+
|
|
64
|
+
### **On Destroy:**
|
|
65
|
+
|
|
66
|
+
1. Calls destroy() on all successfully loaded functions
|
|
67
|
+
2. Errors during cleanup are caught and logged
|
|
68
|
+
3. Cleanup tracking arrays are reset
|
|
69
|
+
4. Safe for Barba.js page transitions
|
|
70
|
+
|
|
71
|
+
### **On Reinitialize:**
|
|
72
|
+
|
|
73
|
+
1. Attempts to load all functions again
|
|
74
|
+
2. Works like fresh page load on new DOM
|
|
75
|
+
3. Independent function loading ensures maximum resilience
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## **Notes**
|
|
80
|
+
|
|
81
|
+
- All functions auto-initialize on page load
|
|
82
|
+
- Each function operates independently
|
|
83
|
+
- Barba.js compatible with proper cleanup
|
|
84
|
+
- No configuration required - works with data attributes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clickable Orchestrator
|
|
3
|
+
* Manages clickable wrapper normalization
|
|
4
|
+
*
|
|
5
|
+
* Uses static imports and passes config down to functions
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
import { init as normalizeInit } from './functions/normalize/normalize.ts';
|
|
9
|
+
|
|
10
|
+
export async function init(clickableConfig) {
|
|
11
|
+
const cleanup = { destroyFunctions: [] };
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Load normalize function
|
|
15
|
+
const normalizeResult = await normalizeInit(clickableConfig.normalize);
|
|
16
|
+
if (normalizeResult?.destroy) cleanup.destroyFunctions.push(normalizeResult.destroy);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
result: 'clickable initialized',
|
|
20
|
+
destroy: () => {
|
|
21
|
+
cleanup.destroyFunctions.forEach((destroyFn) => {
|
|
22
|
+
try {
|
|
23
|
+
destroyFn();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('[clickable] Error during cleanup:', error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
cleanup.destroyFunctions.length = 0;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('[clickable] Initialization failed:', error);
|
|
33
|
+
// Cleanup any partial initialization
|
|
34
|
+
cleanup.destroyFunctions.forEach((fn) => {
|
|
35
|
+
try {
|
|
36
|
+
fn();
|
|
37
|
+
} catch (cleanupError) {
|
|
38
|
+
console.error('[clickable] Error during error cleanup:', cleanupError);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|