@hortonstudio/main 1.2.35 → 1.4.0
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/.claude/settings.local.json +22 -1
- package/TEMP-before-after-attributes.md +158 -0
- package/animations/hero.js +741 -611
- package/animations/text.js +505 -317
- package/animations/transition.js +36 -21
- package/autoInit/accessibility.js +7 -67
- package/autoInit/counter.js +338 -0
- package/autoInit/custom-values.js +266 -0
- package/autoInit/form.js +471 -0
- package/autoInit/modal.js +43 -38
- package/autoInit/navbar.js +484 -371
- package/autoInit/smooth-scroll.js +86 -84
- package/index.js +140 -88
- package/package.json +1 -1
- package/utils/before-after.js +279 -146
- package/utils/scroll-progress.js +26 -21
- package/utils/toc.js +73 -66
- package/CLAUDE.md +0 -45
- package/debug-version.html +0 -37
@@ -1,89 +1,91 @@
|
|
1
|
-
const API_NAME =
|
1
|
+
const API_NAME = "hsmain";
|
2
2
|
|
3
3
|
export async function init() {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
}
|
9
|
-
});
|
10
|
-
|
11
|
-
// Disable CSS smooth scrolling
|
12
|
-
document.documentElement.style.scrollBehavior = 'auto';
|
13
|
-
document.body.style.scrollBehavior = 'auto';
|
14
|
-
|
15
|
-
// Check if user prefers reduced motion
|
16
|
-
function prefersReducedMotion() {
|
17
|
-
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
18
|
-
}
|
19
|
-
|
20
|
-
function getScrollOffset() {
|
21
|
-
const offsetValue = getComputedStyle(document.documentElement)
|
22
|
-
.getPropertyValue('--misc--scroll-offset').trim();
|
23
|
-
return parseInt(offsetValue) || 0;
|
24
|
-
}
|
25
|
-
|
26
|
-
// Smooth scroll to element with offset
|
27
|
-
function scrollToElement(target, offset = 0) {
|
28
|
-
if (!target) return;
|
29
|
-
|
30
|
-
// Skip animation if user prefers reduced motion
|
31
|
-
if (prefersReducedMotion()) {
|
32
|
-
const targetPosition = target.getBoundingClientRect().top + window.scrollY - offset;
|
33
|
-
window.scrollTo(0, targetPosition);
|
34
|
-
target.setAttribute('tabindex', '-1');
|
35
|
-
target.focus({ preventScroll: true });
|
36
|
-
return;
|
37
|
-
}
|
38
|
-
|
39
|
-
gsap.to(window, {
|
40
|
-
duration: 1,
|
41
|
-
scrollTo: {
|
42
|
-
y: target,
|
43
|
-
offsetY: offset
|
44
|
-
},
|
45
|
-
ease: "power2.out",
|
46
|
-
onComplete: function() {
|
47
|
-
target.setAttribute('tabindex', '-1');
|
48
|
-
target.focus({ preventScroll: true });
|
49
|
-
}
|
50
|
-
});
|
4
|
+
const api = window[API_NAME];
|
5
|
+
api.afterWebflowReady(() => {
|
6
|
+
if (typeof $ !== "undefined") {
|
7
|
+
$(document).off("click.wf-scroll");
|
51
8
|
}
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
9
|
+
});
|
10
|
+
|
11
|
+
// Disable CSS smooth scrolling
|
12
|
+
document.documentElement.style.scrollBehavior = "auto";
|
13
|
+
document.body.style.scrollBehavior = "auto";
|
14
|
+
|
15
|
+
// Check if user prefers reduced motion
|
16
|
+
function prefersReducedMotion() {
|
17
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
18
|
+
}
|
19
|
+
|
20
|
+
function getScrollOffset() {
|
21
|
+
const offsetValue = getComputedStyle(document.documentElement)
|
22
|
+
.getPropertyValue("--misc--scroll-offset")
|
23
|
+
.trim();
|
24
|
+
return parseInt(offsetValue) || 0;
|
25
|
+
}
|
26
|
+
|
27
|
+
// Smooth scroll to element with offset
|
28
|
+
function scrollToElement(target, offset = 0) {
|
29
|
+
if (!target) return;
|
30
|
+
|
31
|
+
// Skip animation if user prefers reduced motion
|
32
|
+
if (prefersReducedMotion()) {
|
33
|
+
const targetPosition =
|
34
|
+
target.getBoundingClientRect().top + window.scrollY - offset;
|
35
|
+
window.scrollTo(0, targetPosition);
|
36
|
+
target.setAttribute("tabindex", "-1");
|
37
|
+
target.focus({ preventScroll: true });
|
38
|
+
return;
|
61
39
|
}
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
40
|
+
|
41
|
+
gsap.to(window, {
|
42
|
+
duration: 1,
|
43
|
+
scrollTo: {
|
44
|
+
y: target,
|
45
|
+
offsetY: offset,
|
46
|
+
},
|
47
|
+
ease: "power2.out",
|
48
|
+
onComplete: function () {
|
49
|
+
target.setAttribute("tabindex", "-1");
|
50
|
+
target.focus({ preventScroll: true });
|
51
|
+
},
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
// Handle anchor link clicks and keyboard activation
|
56
|
+
function handleAnchorClicks() {
|
57
|
+
document.addEventListener("click", handleAnchorActivation);
|
58
|
+
document.addEventListener("keydown", function (e) {
|
59
|
+
if (e.key === "Enter" || e.key === " ") {
|
60
|
+
handleAnchorActivation(e);
|
61
|
+
}
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
65
|
+
function handleAnchorActivation(e) {
|
66
|
+
const link = e.target.closest('a[href^="#"]');
|
67
|
+
if (!link) return;
|
68
|
+
|
69
|
+
const href = link.getAttribute("href");
|
70
|
+
if (!href || href === "#") return;
|
71
|
+
|
72
|
+
const targetId = href.substring(1);
|
73
|
+
const targetElement = document.getElementById(targetId);
|
74
|
+
|
75
|
+
if (targetElement) {
|
76
|
+
e.preventDefault();
|
77
|
+
if (history.replaceState) {
|
78
|
+
history.replaceState(null, null, `#${targetElement.id}`);
|
79
|
+
}
|
80
|
+
const offset = getScrollOffset();
|
81
|
+
scrollToElement(targetElement, offset);
|
81
82
|
}
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
}
|
83
|
+
}
|
84
|
+
|
85
|
+
// Initialize anchor link handling
|
86
|
+
handleAnchorClicks();
|
87
|
+
|
88
|
+
return {
|
89
|
+
result: "autoInit-smooth-scroll initialized",
|
90
|
+
};
|
91
|
+
}
|
package/index.js
CHANGED
@@ -1,73 +1,90 @@
|
|
1
|
-
// Version:1.
|
1
|
+
// Version:1.4.0
|
2
2
|
|
3
|
-
const API_NAME =
|
3
|
+
const API_NAME = "hsmain";
|
4
4
|
|
5
5
|
const initializeHsMain = async () => {
|
6
|
-
if (
|
6
|
+
if (
|
7
|
+
window[API_NAME] &&
|
8
|
+
!Array.isArray(window[API_NAME]) &&
|
9
|
+
window[API_NAME].loaded
|
10
|
+
) {
|
7
11
|
return;
|
8
12
|
}
|
9
13
|
|
10
14
|
const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
|
11
15
|
|
12
16
|
const animationModules = {
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
"data-hs-anim-text": true,
|
18
|
+
"data-hs-anim-hero": true,
|
19
|
+
"data-hs-anim-transition": true,
|
16
20
|
};
|
17
21
|
|
18
22
|
const utilityModules = {
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
"data-hs-util-toc": true,
|
24
|
+
"data-hs-util-progress": true,
|
25
|
+
"data-hs-util-ba": true,
|
22
26
|
};
|
23
27
|
|
24
28
|
const autoInitModules = {
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
"smooth-scroll": true,
|
30
|
+
modal: true,
|
31
|
+
navbar: true,
|
32
|
+
accessibility: true,
|
33
|
+
counter: true,
|
34
|
+
"custom-values": true,
|
35
|
+
form: true,
|
29
36
|
};
|
30
37
|
|
31
38
|
const allDataAttributes = { ...animationModules, ...utilityModules };
|
32
39
|
|
33
|
-
const waitForWebflow = async () =>
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
const waitForDOMContentLoaded = async () => new Promise(resolve => {
|
39
|
-
if (document.readyState === 'loading') {
|
40
|
-
document.addEventListener('DOMContentLoaded', resolve);
|
41
|
-
} else {
|
42
|
-
resolve();
|
43
|
-
}
|
44
|
-
});
|
40
|
+
const waitForWebflow = async () =>
|
41
|
+
new Promise((resolve) => {
|
42
|
+
if (!window.Webflow) window.Webflow = [];
|
43
|
+
window.Webflow.push(resolve);
|
44
|
+
});
|
45
45
|
|
46
46
|
const moduleMap = {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
47
|
+
"data-hs-anim-text": () => import("./animations/text.js"),
|
48
|
+
"data-hs-anim-hero": () => import("./animations/hero.js"),
|
49
|
+
"data-hs-anim-transition": () => import("./animations/transition.js"),
|
50
|
+
"data-hs-util-toc": () => import("./utils/toc.js"),
|
51
|
+
"data-hs-util-progress": () => import("./utils/scroll-progress.js"),
|
52
|
+
"data-hs-util-ba": () => import("./utils/before-after.js"),
|
53
|
+
"smooth-scroll": () => import("./autoInit/smooth-scroll.js"),
|
54
|
+
modal: () => import("./autoInit/modal.js"),
|
55
|
+
navbar: () => import("./autoInit/navbar.js"),
|
56
|
+
accessibility: () => import("./autoInit/accessibility.js"),
|
57
|
+
counter: () => import("./autoInit/counter.js"),
|
58
|
+
"custom-values": () => import("./autoInit/custom-values.js"),
|
59
|
+
form: () => import("./autoInit/form.js"),
|
57
60
|
};
|
58
61
|
|
59
|
-
let scripts = [
|
60
|
-
|
62
|
+
let scripts = [
|
63
|
+
...document.querySelectorAll(
|
64
|
+
`script[type="module"][src="${import.meta.url}"]`,
|
65
|
+
),
|
66
|
+
];
|
67
|
+
|
61
68
|
if (scripts.length === 0) {
|
62
|
-
scripts = [
|
63
|
-
.
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
scripts = [
|
70
|
+
...document.querySelectorAll(
|
71
|
+
'script[type="module"][src*="@hortonstudio/main"]',
|
72
|
+
),
|
73
|
+
].filter((script) => {
|
74
|
+
const scriptSrc = script.src;
|
75
|
+
const currentSrc = import.meta.url;
|
76
|
+
const scriptPackage = scriptSrc.match(
|
77
|
+
/@hortonstudio\/main(@[\d.]+)?/,
|
78
|
+
)?.[0];
|
79
|
+
const currentPackage = currentSrc.match(
|
80
|
+
/@hortonstudio\/main(@[\d.]+)?/,
|
81
|
+
)?.[0];
|
82
|
+
return (
|
83
|
+
scriptPackage &&
|
84
|
+
currentPackage &&
|
85
|
+
scriptPackage.split("@")[0] === currentPackage.split("@")[0]
|
86
|
+
);
|
87
|
+
});
|
71
88
|
}
|
72
89
|
|
73
90
|
const loadModule = async (moduleName) => {
|
@@ -121,9 +138,12 @@ const initializeHsMain = async () => {
|
|
121
138
|
process: new Set(),
|
122
139
|
load: loadModule,
|
123
140
|
loaded: false,
|
141
|
+
barbaEnabled: false,
|
142
|
+
embedScriptManager: null,
|
143
|
+
barbaManager: null,
|
124
144
|
push(...items) {
|
125
145
|
for (const [moduleName, callback] of items) {
|
126
|
-
if (typeof callback ===
|
146
|
+
if (typeof callback === "function") {
|
127
147
|
this.modules[moduleName]?.loading?.then(callback);
|
128
148
|
} else {
|
129
149
|
this.load(moduleName);
|
@@ -135,8 +155,61 @@ const initializeHsMain = async () => {
|
|
135
155
|
this.modules[moduleName]?.destroy?.();
|
136
156
|
}
|
137
157
|
},
|
158
|
+
async reinitialize() {
|
159
|
+
// Complete teardown and reinit
|
160
|
+
this.destroy();
|
161
|
+
this.modules = {};
|
162
|
+
this.process = new Set();
|
163
|
+
|
164
|
+
// Re-scan for modules including in w-embeds
|
165
|
+
await this.scanForEmbedModules();
|
166
|
+
processModules();
|
167
|
+
},
|
168
|
+
async scanForEmbedModules() {
|
169
|
+
const embeds = document.querySelectorAll(".w-embed");
|
170
|
+
embeds.forEach((embed) => {
|
171
|
+
const scripts = embed.querySelectorAll("script[data-hs-module]");
|
172
|
+
scripts.forEach((script) => {
|
173
|
+
// Extract module info from data attributes
|
174
|
+
const moduleType = script.getAttribute("data-hs-module-type");
|
175
|
+
const moduleName = script.getAttribute("data-hs-module");
|
176
|
+
|
177
|
+
if (moduleType && moduleName) {
|
178
|
+
const moduleKey = `data-hs-${moduleType}-${moduleName}`;
|
179
|
+
if (moduleMap[moduleKey]) {
|
180
|
+
this.load(moduleKey);
|
181
|
+
}
|
182
|
+
}
|
183
|
+
});
|
184
|
+
});
|
185
|
+
},
|
186
|
+
async setBarbaMode(enabled) {
|
187
|
+
this.barbaEnabled = enabled;
|
188
|
+
if (enabled && !this.barbaManager) {
|
189
|
+
try {
|
190
|
+
const module = await import("./barba/barbaManager.js");
|
191
|
+
this.barbaManager = module.initializeBarba();
|
192
|
+
if (!this.barbaManager) {
|
193
|
+
}
|
194
|
+
} catch (error) {
|
195
|
+
this.barbaEnabled = false;
|
196
|
+
}
|
197
|
+
}
|
198
|
+
},
|
199
|
+
enableBarba() {
|
200
|
+
return this.setBarbaMode(true);
|
201
|
+
},
|
202
|
+
async initEmbedScriptManager() {
|
203
|
+
if (!this.embedScriptManager) {
|
204
|
+
const { EmbedScriptManager } = await import(
|
205
|
+
"./barba/embedScriptManager.js"
|
206
|
+
);
|
207
|
+
this.embedScriptManager = new EmbedScriptManager();
|
208
|
+
}
|
209
|
+
return this.embedScriptManager;
|
210
|
+
},
|
138
211
|
afterReady(callback) {
|
139
|
-
if (typeof callback ===
|
212
|
+
if (typeof callback === "function") {
|
140
213
|
if (this.loaded) {
|
141
214
|
callback();
|
142
215
|
} else {
|
@@ -145,7 +218,7 @@ const initializeHsMain = async () => {
|
|
145
218
|
}
|
146
219
|
},
|
147
220
|
afterWebflowReady(callback) {
|
148
|
-
if (typeof callback ===
|
221
|
+
if (typeof callback === "function") {
|
149
222
|
if (this.loaded) {
|
150
223
|
callback();
|
151
224
|
} else {
|
@@ -157,7 +230,7 @@ const initializeHsMain = async () => {
|
|
157
230
|
if (moduleName) {
|
158
231
|
return {
|
159
232
|
loaded: !!this.modules[moduleName],
|
160
|
-
loading: this.process.has(moduleName)
|
233
|
+
loading: this.process.has(moduleName),
|
161
234
|
};
|
162
235
|
}
|
163
236
|
return {
|
@@ -165,76 +238,55 @@ const initializeHsMain = async () => {
|
|
165
238
|
loading: [...this.process],
|
166
239
|
animations: Object.keys(animationModules),
|
167
240
|
utilities: Object.keys(utilityModules),
|
168
|
-
autoInit: Object.keys(autoInitModules)
|
241
|
+
autoInit: Object.keys(autoInitModules),
|
242
|
+
barbaEnabled: this.barbaEnabled,
|
169
243
|
};
|
170
|
-
}
|
244
|
+
},
|
171
245
|
};
|
172
246
|
|
173
247
|
const processModules = () => {
|
174
248
|
for (const script of scripts) {
|
175
|
-
const isAutoDetect = script.getAttribute('data-hs-auto') === 'true';
|
176
|
-
|
177
249
|
for (const attrName of Object.keys(allDataAttributes)) {
|
178
250
|
if (script.hasAttribute(attrName)) {
|
179
251
|
loadModule(attrName);
|
180
252
|
}
|
181
253
|
}
|
182
|
-
|
183
|
-
if (isAutoDetect) {
|
184
|
-
waitForDOMContentLoaded().then(() => {
|
185
|
-
const foundAttributes = new Set();
|
186
|
-
const allElements = document.querySelectorAll('*');
|
187
|
-
|
188
|
-
for (const element of allElements) {
|
189
|
-
for (const attrName of element.getAttributeNames()) {
|
190
|
-
const match = attrName.match(/^(data-hs-(?:anim|util)-[^-=]+)/)?.[1];
|
191
|
-
if (match && allDataAttributes[match]) {
|
192
|
-
foundAttributes.add(match);
|
193
|
-
}
|
194
|
-
}
|
195
|
-
}
|
196
|
-
|
197
|
-
for (const attrName of foundAttributes) {
|
198
|
-
loadModule(attrName);
|
199
|
-
}
|
200
|
-
});
|
201
|
-
}
|
202
254
|
}
|
203
255
|
|
204
256
|
for (const moduleName of Object.keys(autoInitModules)) {
|
205
257
|
loadModule(moduleName);
|
206
258
|
}
|
207
259
|
|
208
|
-
if (
|
209
|
-
|
210
|
-
|
260
|
+
if (
|
261
|
+
!scripts.some((script) => script.hasAttribute("data-hs-anim-transition"))
|
262
|
+
) {
|
263
|
+
document.querySelectorAll(".transition").forEach((element) => {
|
264
|
+
element.style.display = "none";
|
211
265
|
});
|
212
266
|
}
|
213
267
|
};
|
214
268
|
|
215
|
-
document.querySelectorAll(
|
216
|
-
richtext.querySelectorAll(
|
217
|
-
img.loading =
|
269
|
+
document.querySelectorAll(".w-richtext").forEach((richtext) => {
|
270
|
+
richtext.querySelectorAll("img").forEach((img) => {
|
271
|
+
img.loading = "eager";
|
218
272
|
});
|
219
273
|
});
|
220
274
|
|
221
275
|
const finalize = async () => {
|
222
276
|
processModules();
|
223
277
|
await waitForWebflow();
|
278
|
+
|
224
279
|
window[API_NAME].loaded = true;
|
225
|
-
readyCallbacks.forEach(callback => {
|
280
|
+
readyCallbacks.forEach((callback) => {
|
226
281
|
try {
|
227
282
|
callback();
|
228
|
-
} catch (error) {
|
229
|
-
// Silent fail for callbacks
|
230
|
-
}
|
283
|
+
} catch (error) {}
|
231
284
|
});
|
285
|
+
readyCallbacks.length = 0; // Clear array after execution
|
232
286
|
};
|
233
287
|
|
234
288
|
window[API_NAME].push(...queuedModules);
|
235
|
-
finalize().catch(() => {
|
236
|
-
// Silent fail for initialization
|
237
|
-
});
|
289
|
+
finalize().catch(() => {});
|
238
290
|
};
|
239
291
|
|
240
|
-
initializeHsMain();
|
292
|
+
initializeHsMain();
|