@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
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @hortonstudio/main
|
|
2
|
+
|
|
3
|
+
**Version:** 2.0.0
|
|
4
|
+
|
|
5
|
+
Auto-initializing JavaScript library for Webflow sites with animations, accessibility, and utility functions.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/bootstrap.js"></script>
|
|
13
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/main.js"></script>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Local Development:**
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<!-- Local dev with HMR (run: npm run dev) -->
|
|
20
|
+
<script src="http://localhost:5173/public/bootstrap.js"></script>
|
|
21
|
+
<script type="module" src="http://localhost:5173/index.ts"></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
### SPA Mode (Barba.js, Swup, etc.)
|
|
29
|
+
|
|
30
|
+
For single-page applications, add `data-hs-spa="true"` to disable automatic link interception:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script
|
|
34
|
+
type="module"
|
|
35
|
+
src="https://cdn.jsdelivr.net/npm/@hortonstudio/main@2/dist/main.js"
|
|
36
|
+
data-hs-spa="true"
|
|
37
|
+
></script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then dispatch events from your router:
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Example: Barba.js integration
|
|
44
|
+
barba.hooks.before(() => {
|
|
45
|
+
window.dispatchEvent(new CustomEvent('hsmain:transition-exit'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
barba.hooks.after(() => {
|
|
49
|
+
window.dispatchEvent(new CustomEvent('hsmain:transition-enter'));
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## API Usage
|
|
56
|
+
|
|
57
|
+
### Manual Module Loading
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Load a single module
|
|
61
|
+
window.hsmain.load('counter');
|
|
62
|
+
|
|
63
|
+
// With callback
|
|
64
|
+
window.hsmain.push([
|
|
65
|
+
'counter',
|
|
66
|
+
() => {
|
|
67
|
+
console.log('Counter module ready');
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Lifecycle Hooks
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// Execute code after all modules load
|
|
76
|
+
window.hsmain.afterReady(() => {
|
|
77
|
+
console.log('Library fully initialized');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Check module status
|
|
81
|
+
const status = window.hsmain.status('counter');
|
|
82
|
+
console.log(status); // { loaded: true, loading: false }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Cleanup
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// Destroy all modules (useful for SPA navigation)
|
|
89
|
+
window.hsmain.destroy();
|
|
90
|
+
|
|
91
|
+
// Reinitialize everything
|
|
92
|
+
await window.hsmain.reinitialize();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Architecture
|
|
98
|
+
|
|
99
|
+
- **TypeScript:** Full type safety with TypeScript source
|
|
100
|
+
- **Vite Bundled:** Optimized build with code splitting (~24kb gzipped total)
|
|
101
|
+
- **Config-driven:** Centralized configuration in `config.json`
|
|
102
|
+
- **SPA Compatible:** Works with Barba.js, Swup, and other routers
|
|
103
|
+
- **Memory Safe:** Proper cleanup tracking for observers and event handlers
|
|
104
|
+
- **Phase-based Loading:** Optimized initialization sequence
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Module Documentation
|
|
109
|
+
|
|
110
|
+
Each module has its own README with detailed usage instructions:
|
|
111
|
+
|
|
112
|
+
- **Animations:** `src/modules/default/` - Counter, comparison sliders, marquee
|
|
113
|
+
- **Navigation:** `src/modules/default/navbar/` - Dropdowns and mobile menus
|
|
114
|
+
- **Transitions:** `src/modules/transition/` - Page transitions with namespace support
|
|
115
|
+
- **Forms:** `src/modules/structure/functions/form/` - Custom selects and form enhancements
|
|
116
|
+
- **Accessibility:** `src/modules/default/functions/accordion/` - ARIA attributes
|
|
117
|
+
- **Utilities:** `src/modules/structure/` - Year replacement, TOC, pagination, site settings
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
### Build
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm run build
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Type Checking
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm run type-check
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Linting
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run lint
|
|
139
|
+
npm run format
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
ISC
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import tsParser from '@typescript-eslint/parser';
|
|
2
|
+
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
|
3
|
+
import prettierConfig from 'eslint-config-prettier';
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
files: ['**/*.ts'],
|
|
8
|
+
languageOptions: {
|
|
9
|
+
parser: tsParser,
|
|
10
|
+
parserOptions: {
|
|
11
|
+
ecmaVersion: 'latest',
|
|
12
|
+
sourceType: 'module',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
plugins: {
|
|
16
|
+
'@typescript-eslint': tsPlugin,
|
|
17
|
+
},
|
|
18
|
+
rules: {
|
|
19
|
+
...tsPlugin.configs.recommended.rules,
|
|
20
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
21
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
22
|
+
'@typescript-eslint/no-unused-vars': [
|
|
23
|
+
'error',
|
|
24
|
+
{
|
|
25
|
+
argsIgnorePattern: '^_',
|
|
26
|
+
varsIgnorePattern: '^_',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
prettierConfig,
|
|
32
|
+
];
|
package/index.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import config from '@config';
|
|
2
|
+
|
|
3
|
+
const API_NAME = config._global.apiName;
|
|
4
|
+
|
|
5
|
+
const initializeHsMain = async () => {
|
|
6
|
+
if (window[API_NAME] && !Array.isArray(window[API_NAME]) && window[API_NAME].loaded) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Add initialized class to HTML element immediately
|
|
11
|
+
document.documentElement.classList.add(config._global.classes.initialized);
|
|
12
|
+
|
|
13
|
+
const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
|
|
14
|
+
|
|
15
|
+
const moduleMap = {
|
|
16
|
+
normalize: () => import('./src/modules/normalize/normalize.ts'),
|
|
17
|
+
structure: () => import('./src/modules/structure/structure.ts'),
|
|
18
|
+
default: () => import('./src/modules/default/default.ts'),
|
|
19
|
+
animations: () => import('./src/animations/animations.ts'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let scripts = [...document.querySelectorAll(`script[type="module"][src="${import.meta.url}"]`)];
|
|
23
|
+
|
|
24
|
+
if (scripts.length === 0) {
|
|
25
|
+
scripts = [
|
|
26
|
+
...document.querySelectorAll('script[type="module"][src*="@hortonstudio/main"]'),
|
|
27
|
+
].filter((script) => {
|
|
28
|
+
const scriptSrc = script.src;
|
|
29
|
+
const currentSrc = import.meta.url;
|
|
30
|
+
const scriptPackage = scriptSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
|
|
31
|
+
const currentPackage = currentSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
|
|
32
|
+
return (
|
|
33
|
+
scriptPackage &&
|
|
34
|
+
currentPackage &&
|
|
35
|
+
scriptPackage.split('@')[0] === currentPackage.split('@')[0]
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Read settings from script tag attributes
|
|
41
|
+
const settings = {
|
|
42
|
+
isSPA: scripts.some((script) => script.getAttribute('data-hs-spa') === 'true'),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Define 3-phase initialization system
|
|
46
|
+
const PHASES = {
|
|
47
|
+
normalize: {
|
|
48
|
+
modules: ['normalize'],
|
|
49
|
+
parallel: false, // Sequential execution critical for DOM fixes
|
|
50
|
+
},
|
|
51
|
+
structure: {
|
|
52
|
+
modules: ['structure'],
|
|
53
|
+
parallel: false, // Must complete before default
|
|
54
|
+
},
|
|
55
|
+
default: {
|
|
56
|
+
modules: ['default', 'animations'],
|
|
57
|
+
parallel: true, // Run default and animations synchronously
|
|
58
|
+
waitForIx3: true, // Wait for Webflow IX3 before starting
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const loadModule = async (moduleName) => {
|
|
63
|
+
const instance = window[API_NAME];
|
|
64
|
+
if (instance.process.has(moduleName)) {
|
|
65
|
+
return instance.modules[moduleName]?.loading;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
instance.process.add(moduleName);
|
|
69
|
+
|
|
70
|
+
const moduleData = instance.modules[moduleName] || {};
|
|
71
|
+
instance.modules[moduleName] = moduleData;
|
|
72
|
+
|
|
73
|
+
moduleData.loading = new Promise((resolve, reject) => {
|
|
74
|
+
moduleData.resolve = resolve;
|
|
75
|
+
moduleData.reject = reject;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
let destroy;
|
|
79
|
+
try {
|
|
80
|
+
const { init } = await moduleMap[moduleName]();
|
|
81
|
+
const result = await init();
|
|
82
|
+
const resultData = result || {};
|
|
83
|
+
destroy = resultData.destroy;
|
|
84
|
+
const initResult = resultData.result;
|
|
85
|
+
|
|
86
|
+
moduleData.destroy = () => {
|
|
87
|
+
destroy?.();
|
|
88
|
+
instance.process.delete(moduleName);
|
|
89
|
+
};
|
|
90
|
+
moduleData.restart = () => {
|
|
91
|
+
moduleData.destroy?.();
|
|
92
|
+
instance.load(moduleName);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
moduleData.resolve?.(initResult);
|
|
96
|
+
delete moduleData.resolve;
|
|
97
|
+
delete moduleData.reject;
|
|
98
|
+
|
|
99
|
+
return initResult;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
destroy?.();
|
|
102
|
+
moduleData.reject?.(error);
|
|
103
|
+
instance.process.delete(moduleName);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Helper to load and execute a phase
|
|
109
|
+
const executePhase = async (phaseName) => {
|
|
110
|
+
const phase = PHASES[phaseName];
|
|
111
|
+
|
|
112
|
+
// Download all modules in parallel
|
|
113
|
+
await Promise.allSettled(phase.modules.map((name) => moduleMap[name]()));
|
|
114
|
+
|
|
115
|
+
// Execute based on parallel setting
|
|
116
|
+
if (phase.parallel) {
|
|
117
|
+
const initPromises = phase.modules.map((name) => loadModule(name));
|
|
118
|
+
await Promise.allSettled(initPromises);
|
|
119
|
+
} else {
|
|
120
|
+
// Sequential execution
|
|
121
|
+
for (const name of phase.modules) {
|
|
122
|
+
await loadModule(name);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Trigger DOM ready event
|
|
128
|
+
const triggerDomReady = () => {
|
|
129
|
+
const event = new CustomEvent('hsmain:dom-ready');
|
|
130
|
+
window.dispatchEvent(event);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Wait for Webflow IX3 to be ready
|
|
134
|
+
const waitForIx3 = async () => {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
const checkIx3 = () => {
|
|
137
|
+
const htmlElement = document.documentElement;
|
|
138
|
+
const hasIx3 = htmlElement.classList.contains('w-mod-ix3');
|
|
139
|
+
|
|
140
|
+
if (hasIx3) {
|
|
141
|
+
resolve(true);
|
|
142
|
+
} else {
|
|
143
|
+
setTimeout(checkIx3, 50);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
checkIx3();
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Initialize all modules in 3 phases
|
|
151
|
+
const initializeInPhases = async () => {
|
|
152
|
+
// Phase 1: Normalize (critical DOM fixes)
|
|
153
|
+
await executePhase('normalize');
|
|
154
|
+
|
|
155
|
+
// Phase 2: Structure (DOM modifications)
|
|
156
|
+
await executePhase('structure');
|
|
157
|
+
|
|
158
|
+
// Wait for IX3 if needed for Phase 3
|
|
159
|
+
if (PHASES.default.waitForIx3) {
|
|
160
|
+
await waitForIx3();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Phase 3: Default + Transition (parallel with 50ms transition delay)
|
|
164
|
+
await executePhase('default');
|
|
165
|
+
|
|
166
|
+
// Trigger DOM ready after everything initialized
|
|
167
|
+
triggerDomReady();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const readyCallbacks = [];
|
|
171
|
+
|
|
172
|
+
window[API_NAME] = {
|
|
173
|
+
scripts,
|
|
174
|
+
settings,
|
|
175
|
+
modules: {},
|
|
176
|
+
process: new Set(),
|
|
177
|
+
load: loadModule,
|
|
178
|
+
loaded: false,
|
|
179
|
+
push(...items) {
|
|
180
|
+
for (const [moduleName, callback] of items) {
|
|
181
|
+
if (typeof callback === 'function') {
|
|
182
|
+
this.modules[moduleName]?.loading?.then(callback);
|
|
183
|
+
} else {
|
|
184
|
+
this.load(moduleName);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
destroy() {
|
|
189
|
+
for (const moduleName in this.modules) {
|
|
190
|
+
this.modules[moduleName]?.destroy?.();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async reinitialize() {
|
|
194
|
+
// Complete teardown
|
|
195
|
+
this.loaded = false;
|
|
196
|
+
this.destroy();
|
|
197
|
+
this.modules = {};
|
|
198
|
+
this.process = new Set();
|
|
199
|
+
|
|
200
|
+
// Re-scan for modules including in w-embeds
|
|
201
|
+
await this.scanForEmbedModules();
|
|
202
|
+
|
|
203
|
+
// Use same phased initialization flow
|
|
204
|
+
await initializeInPhases();
|
|
205
|
+
|
|
206
|
+
// Wait for IX3 to be ready before marking as loaded
|
|
207
|
+
await waitForIx3();
|
|
208
|
+
this.loaded = true;
|
|
209
|
+
},
|
|
210
|
+
async scanForEmbedModules() {
|
|
211
|
+
const embeds = document.querySelectorAll('.w-embed');
|
|
212
|
+
embeds.forEach((embed) => {
|
|
213
|
+
const scripts = embed.querySelectorAll('script[data-hs-module]');
|
|
214
|
+
scripts.forEach((script) => {
|
|
215
|
+
// Extract module info from data attributes
|
|
216
|
+
const moduleType = script.getAttribute('data-hs-module-type');
|
|
217
|
+
const moduleName = script.getAttribute('data-hs-module');
|
|
218
|
+
|
|
219
|
+
if (moduleType && moduleName) {
|
|
220
|
+
const moduleKey = `data-hs-${moduleType}-${moduleName}`;
|
|
221
|
+
if (moduleMap[moduleKey]) {
|
|
222
|
+
this.load(moduleKey);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
afterReady(callback) {
|
|
229
|
+
if (typeof callback === 'function') {
|
|
230
|
+
if (this.loaded) {
|
|
231
|
+
callback();
|
|
232
|
+
} else {
|
|
233
|
+
readyCallbacks.push(callback);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
status(moduleName) {
|
|
238
|
+
if (moduleName) {
|
|
239
|
+
return {
|
|
240
|
+
loaded: !!this.modules[moduleName],
|
|
241
|
+
loading: this.process.has(moduleName),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
loaded: Object.keys(this.modules),
|
|
246
|
+
loading: [...this.process],
|
|
247
|
+
phases: PHASES,
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const finalize = async () => {
|
|
253
|
+
window[API_NAME].push(...queuedModules);
|
|
254
|
+
|
|
255
|
+
// Run phased initialization
|
|
256
|
+
await initializeInPhases();
|
|
257
|
+
|
|
258
|
+
// Wait for IX3 to be ready before firing callbacks
|
|
259
|
+
await waitForIx3();
|
|
260
|
+
|
|
261
|
+
window[API_NAME].loaded = true;
|
|
262
|
+
readyCallbacks.forEach((callback) => {
|
|
263
|
+
try {
|
|
264
|
+
callback();
|
|
265
|
+
} catch {
|
|
266
|
+
// Silent error handling
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
readyCallbacks.length = 0; // Clear array after execution
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
finalize().catch(() => {});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
initializeHsMain();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hortonstudio/main",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.20",
|
|
4
4
|
"description": "Animation and utility library for client websites",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,23 @@
|
|
|
13
13
|
"author": "Horton Studio",
|
|
14
14
|
"license": "ISC",
|
|
15
15
|
"scripts": {
|
|
16
|
-
"
|
|
16
|
+
"dev": "vite --host",
|
|
17
|
+
"build": "vite build",
|
|
18
|
+
"preview": "vite preview",
|
|
19
|
+
"type-check": "tsc --noEmit",
|
|
20
|
+
"lint": "eslint src/**/*.ts index.ts",
|
|
21
|
+
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
|
22
|
+
"build:strict": "tsc --noEmit && vite build"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
|
26
|
+
"@typescript-eslint/parser": "^8.46.4",
|
|
27
|
+
"eslint": "^9.39.1",
|
|
28
|
+
"eslint-config-prettier": "^10.1.8",
|
|
29
|
+
"prettier": "^3.6.2",
|
|
30
|
+
"terser": "^5.44.1",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"vite": "^7.2.2",
|
|
33
|
+
"vite-plugin-compression": "^0.5.1"
|
|
17
34
|
}
|
|
18
35
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap stub for @hortonstudio/main
|
|
3
|
+
* Initializes the API before the main bundle loads
|
|
4
|
+
* This prevents race conditions and allows users to queue module loads
|
|
5
|
+
*/
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Get API name from config (defaults to 'hsmain')
|
|
10
|
+
const API_NAME = 'hsmain';
|
|
11
|
+
|
|
12
|
+
// Initialize API if not already present
|
|
13
|
+
if (!window[API_NAME]) {
|
|
14
|
+
window[API_NAME] = [];
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animations Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Conditionally loads animation modules based on DOM presence.
|
|
5
|
+
* Uses dynamic imports for tree-shaking - only bundles animations that exist on the page.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Runs synchronously with default phase (Phase 3)
|
|
9
|
+
* - Scans DOM for animation attributes
|
|
10
|
+
* - Dynamically imports only the animations found
|
|
11
|
+
* - Exposes rescan() for SPA page transitions
|
|
12
|
+
*
|
|
13
|
+
* @version 2.0.0
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import config from '@config';
|
|
17
|
+
|
|
18
|
+
export async function init() {
|
|
19
|
+
const activeModules = new Map();
|
|
20
|
+
const CONFIG_ROOT = 'animations';
|
|
21
|
+
const moduleConfig = config[CONFIG_ROOT];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Scan DOM and initialize animations that exist on the page
|
|
25
|
+
*/
|
|
26
|
+
async function scanAndInitialize() {
|
|
27
|
+
const results = [];
|
|
28
|
+
|
|
29
|
+
// Counter Animation
|
|
30
|
+
if (document.querySelector('[data-hs-anim="counter"]')) {
|
|
31
|
+
if (!activeModules.has('counter')) {
|
|
32
|
+
try {
|
|
33
|
+
const { init } = await import('./functions/counter/counter.ts');
|
|
34
|
+
const instance = await init(moduleConfig.counter);
|
|
35
|
+
activeModules.set('counter', instance);
|
|
36
|
+
results.push({ name: 'counter', status: 'loaded' });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[animations] Failed to load counter:', error);
|
|
39
|
+
results.push({ name: 'counter', status: 'failed' });
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
results.push({ name: 'counter', status: 'already-loaded' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add more animation checks here as you create them:
|
|
47
|
+
// if (document.querySelector('[data-hs-hamburger="squeeze"]')) { ... }
|
|
48
|
+
// if (document.querySelector('[data-hs-scroll-reveal]')) { ... }
|
|
49
|
+
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Initial scan on page load
|
|
54
|
+
const initialResults = await scanAndInitialize();
|
|
55
|
+
|
|
56
|
+
// Log what was loaded
|
|
57
|
+
const loaded = initialResults.filter((r) => r.status === 'loaded');
|
|
58
|
+
if (loaded.length > 0) {
|
|
59
|
+
console.log(
|
|
60
|
+
`[animations] Loaded ${loaded.length} animation(s):`,
|
|
61
|
+
loaded.map((r) => r.name).join(', ')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
result: `animations initialized (${loaded.length} active)`,
|
|
67
|
+
rescan: async () => {
|
|
68
|
+
// Rescan for new animations after SPA navigation
|
|
69
|
+
const results = await scanAndInitialize();
|
|
70
|
+
const newlyLoaded = results.filter((r) => r.status === 'loaded');
|
|
71
|
+
if (newlyLoaded.length > 0) {
|
|
72
|
+
console.log(
|
|
73
|
+
`[animations] Rescanned and loaded ${newlyLoaded.length} new animation(s):`,
|
|
74
|
+
newlyLoaded.map((r) => r.name).join(', ')
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
},
|
|
79
|
+
destroy: () => {
|
|
80
|
+
// Destroy all active animation modules
|
|
81
|
+
activeModules.forEach((instance, name) => {
|
|
82
|
+
try {
|
|
83
|
+
if (instance?.destroy) {
|
|
84
|
+
instance.destroy();
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`[animations] Error destroying ${name}:`, error);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
activeModules.clear();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|