@hortonstudio/main 1.2.14 → 1.2.18

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/index.js CHANGED
@@ -1,305 +1,232 @@
1
- // ver 1.2.14
2
-
3
1
  const API_NAME = 'hsmain';
4
2
 
5
- // Main initialization function
6
- const initializeHsMain = () => {
7
- // Handle existing API
8
- if (window[API_NAME] && !Array.isArray(window[API_NAME]) && window[API_NAME].loaded) {
9
- return;
3
+ const initializeHsMain = async () => {
4
+ if (window[API_NAME] && !Array.isArray(window[API_NAME]) && window[API_NAME].loaded) {
5
+ return;
6
+ }
7
+
8
+ const queuedModules = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
9
+
10
+ const animationModules = {
11
+ 'data-hs-anim-text': true,
12
+ 'data-hs-anim-hero': true,
13
+ 'data-hs-anim-transition': true
14
+ };
15
+
16
+ const utilityModules = {
17
+ 'data-hs-util-toc': true,
18
+ 'data-hs-util-progress': true,
19
+ 'data-hs-util-navbar': true
20
+ };
21
+
22
+ const autoInitModules = {
23
+ 'smooth-scroll': true
24
+ };
25
+
26
+ const allDataAttributes = { ...animationModules, ...utilityModules };
27
+
28
+ const waitForWebflow = async () => new Promise(resolve => {
29
+ if (!window.Webflow) window.Webflow = [];
30
+ window.Webflow.push(resolve);
31
+ });
32
+
33
+ const waitForDOMContentLoaded = async () => new Promise(resolve => {
34
+ if (document.readyState === 'loading') {
35
+ document.addEventListener('DOMContentLoaded', resolve);
36
+ } else {
37
+ resolve();
38
+ }
39
+ });
40
+
41
+ const moduleMap = {
42
+ 'data-hs-anim-text': () => import('./animations/text.js'),
43
+ 'data-hs-anim-hero': () => import('./animations/hero.js'),
44
+ 'data-hs-anim-transition': () => import('./animations/transition.js'),
45
+ 'data-hs-util-toc': () => import('./utils/toc.js'),
46
+ 'data-hs-util-progress': () => import('./utils/scroll-progress.js'),
47
+ 'data-hs-util-navbar': () => import('./utils/navbar.js'),
48
+ 'smooth-scroll': () => import('./autoInit/smooth-scroll.js')
49
+ };
50
+
51
+ let scripts = [...document.querySelectorAll(`script[type="module"][src="${import.meta.url}"]`)];
52
+
53
+ if (scripts.length === 0) {
54
+ scripts = [...document.querySelectorAll('script[type="module"][src*="@hortonstudio/main"]')]
55
+ .filter(script => {
56
+ const scriptSrc = script.src;
57
+ const currentSrc = import.meta.url;
58
+ const scriptPackage = scriptSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
59
+ const currentPackage = currentSrc.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
60
+ return scriptPackage && currentPackage &&
61
+ scriptPackage.split('@')[0] === currentPackage.split('@')[0];
62
+ });
63
+ }
64
+
65
+ const loadModule = async (moduleName) => {
66
+ const instance = window[API_NAME];
67
+ if (instance.process.has(moduleName)) {
68
+ return instance.modules[moduleName]?.loading;
10
69
  }
11
70
 
12
- // Store any early API calls
13
- const existingRequests = Array.isArray(window[API_NAME]) ? window[API_NAME] : [];
14
-
15
- // Module definitions
16
- const animationModules = {
17
- "data-hs-anim-text": true,
18
- "data-hs-anim-hero": true,
19
- "data-hs-anim-transition": true
20
- };
21
-
22
- const utilityModules = {
23
- "data-hs-util-toc": true,
24
- "data-hs-util-progress": true,
25
- "data-hs-util-navbar": true
26
- };
27
-
28
- const autoInitModules = {
29
- "smooth-scroll": true
30
- };
31
-
32
- // All available modules
33
- const allModules = { ...animationModules, ...utilityModules };
34
-
35
- // Dynamic module loader (CDN URLs)
36
- const loadModule = async (moduleName) => {
37
- switch (moduleName) {
38
- case "data-hs-anim-text":
39
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-anim-text@1/text.js');
40
- case "data-hs-anim-hero":
41
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-anim-hero@1/hero.js');
42
- case "data-hs-anim-transition":
43
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-anim-transition@1/transition.js');
44
- case "data-hs-util-toc":
45
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-util-toc@1/toc.js');
46
- case "data-hs-util-progress":
47
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-util-progress@1/scroll-progress.js');
48
- case "data-hs-util-navbar":
49
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-util-navbar@1/navbar.js');
50
- case "smooth-scroll":
51
- return import('https://cdn.jsdelivr.net/npm/@hortonstudio/main-smooth-scroll@1/smooth-scroll.js');
52
- default:
53
- throw new Error(`${API_NAME} module "${moduleName}" is not supported.`);
54
- }
55
- };
71
+ instance.process.add(moduleName);
56
72
 
57
- // Webflow ready helper (like Finsweet)
58
- const waitWebflowReady = async () => {
59
- return new Promise((resolve) => {
60
- window.Webflow ||= [];
61
- window.Webflow.push(resolve);
62
- });
63
- };
73
+ const moduleData = instance.modules[moduleName] || {};
74
+ instance.modules[moduleName] = moduleData;
64
75
 
65
- // DOM ready helper (like Finsweet)
66
- const waitDOMReady = async () => {
67
- return new Promise((resolve) => {
68
- if (document.readyState === 'loading') {
69
- document.addEventListener('DOMContentLoaded', resolve);
70
- } else {
71
- resolve();
72
- }
73
- });
74
- };
76
+ moduleData.loading = new Promise((resolve, reject) => {
77
+ moduleData.resolve = resolve;
78
+ moduleData.reject = reject;
79
+ });
75
80
 
76
- // Find script tags (with CDN version redirect support)
77
- let scripts = [...document.querySelectorAll(`script[type="module"][src="${import.meta.url}"]`)];
78
-
79
- // Handle CDN version redirects (e.g., @1 -> @1.2.11)
80
- if (scripts.length === 0) {
81
- const allScripts = [...document.querySelectorAll('script[type="module"][src*="@hortonstudio/main"]')];
82
- scripts = allScripts.filter(script => {
83
- const src = script.src;
84
- const metaUrl = import.meta.url;
85
- // Extract package name and check if they match (ignoring version differences)
86
- const srcPackage = src.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
87
- const metaPackage = metaUrl.match(/@hortonstudio\/main(@[\d.]+)?/)?.[0];
88
- return srcPackage && metaPackage && srcPackage.split('@')[0] === metaPackage.split('@')[0];
89
- });
81
+ try {
82
+ const { init, version } = await moduleMap[moduleName]();
83
+ const result = await init();
84
+ const { result: initResult, destroy } = result || {};
85
+
86
+ moduleData.version = version;
87
+ moduleData.destroy = () => {
88
+ destroy?.();
89
+ instance.process.delete(moduleName);
90
+ };
91
+ moduleData.restart = () => {
92
+ moduleData.destroy?.();
93
+ instance.load(moduleName);
94
+ };
95
+
96
+ moduleData.resolve?.(initResult);
97
+ delete moduleData.resolve;
98
+ delete moduleData.reject;
99
+
100
+ return initResult;
101
+ } catch (error) {
102
+ moduleData.reject?.(error);
103
+ instance.process.delete(moduleName);
104
+ throw error;
90
105
  }
91
-
92
- // Module loading function
93
- const loadHsModule = async (moduleName) => {
94
- const apiInstance = window[API_NAME];
95
-
96
- // Check if already processing
97
- if (apiInstance.process.has(moduleName)) {
98
- return apiInstance.modules[moduleName]?.loading;
106
+ };
107
+
108
+ const readyCallbacks = [];
109
+
110
+ window[API_NAME] = {
111
+ scripts,
112
+ modules: {},
113
+ process: new Set(),
114
+ load: loadModule,
115
+ loaded: false,
116
+ push(...items) {
117
+ for (const [moduleName, callback] of items) {
118
+ if (typeof callback === 'function') {
119
+ this.modules[moduleName]?.loading?.then(callback);
120
+ } else {
121
+ this.load(moduleName);
99
122
  }
100
-
101
- // Add to processing set
102
- apiInstance.process.add(moduleName);
103
-
104
- // Create module object
105
- const moduleObj = apiInstance.modules[moduleName] || {};
106
- apiInstance.modules[moduleName] = moduleObj;
107
-
108
- // Create loading promise
109
- moduleObj.loading = new Promise((resolve, reject) => {
110
- moduleObj.resolve = resolve;
111
- moduleObj.reject = reject;
112
- });
113
-
114
- try {
115
- const { init, version } = await loadModule(moduleName);
116
- const initResult = await init();
117
- const { result, destroy } = initResult || {};
118
-
119
- moduleObj.version = version;
120
-
121
- // Add destroy and restart methods
122
- moduleObj.destroy = () => {
123
- destroy?.();
124
- apiInstance.process.delete(moduleName);
125
- };
126
-
127
- moduleObj.restart = () => {
128
- moduleObj.destroy?.();
129
- return apiInstance.load(moduleName);
130
- };
131
-
132
- moduleObj.resolve?.(result);
133
- delete moduleObj.resolve;
134
- delete moduleObj.reject;
135
-
136
- return result;
137
-
138
- } catch (error) {
139
- moduleObj.reject?.(error);
140
- apiInstance.process.delete(moduleName);
141
- throw error;
123
+ }
124
+ },
125
+ destroy() {
126
+ for (const moduleName in this.modules) {
127
+ this.modules[moduleName]?.destroy?.();
128
+ }
129
+ },
130
+ afterReady(callback) {
131
+ if (typeof callback === 'function') {
132
+ if (this.loaded) {
133
+ callback();
134
+ } else {
135
+ readyCallbacks.push(callback);
142
136
  }
143
- };
144
-
145
- // Store callbacks to run after initialization
146
- const postWebflowCallbacks = [];
137
+ }
138
+ },
139
+ afterWebflowReady(callback) {
140
+ if (typeof callback === 'function') {
141
+ if (this.loaded) {
142
+ callback();
143
+ } else {
144
+ readyCallbacks.push(callback);
145
+ }
146
+ }
147
+ },
148
+ status(moduleName) {
149
+ if (moduleName) {
150
+ return {
151
+ loaded: !!this.modules[moduleName],
152
+ loading: this.process.has(moduleName)
153
+ };
154
+ }
155
+ return {
156
+ loaded: Object.keys(this.modules),
157
+ loading: [...this.process],
158
+ animations: Object.keys(animationModules),
159
+ utilities: Object.keys(utilityModules),
160
+ autoInit: Object.keys(autoInitModules)
161
+ };
162
+ }
163
+ };
147
164
 
148
- // Initialize API
149
- window[API_NAME] = {
150
- scripts,
151
- modules: {},
152
- process: new Set(),
153
- load: loadHsModule,
154
- loaded: false,
155
-
156
- // Push method for queuing
157
- push(...requests) {
158
- for (let [moduleName, callback] of requests) {
159
- if (typeof callback === 'function') {
160
- this.modules[moduleName]?.loading?.then(callback);
161
- } else {
162
- this.load(moduleName);
163
- }
164
- }
165
- },
166
-
167
- // Destroy all modules
168
- destroy() {
169
- for (let moduleName in this.modules) {
170
- this.modules[moduleName]?.destroy?.();
171
- }
172
- },
165
+ const processModules = () => {
166
+ for (const script of scripts) {
167
+ const isAutoDetect = script.getAttribute('data-hs-auto') === 'true';
173
168
 
174
- // API function for scripts to register post-initialization callbacks
175
- afterReady(callback) {
176
- if (typeof callback === 'function') {
177
- if (this.loaded) {
178
- callback();
179
- } else {
180
- postWebflowCallbacks.push(callback);
181
- }
182
- }
183
- },
184
-
185
- // Legacy alias for Webflow compatibility
186
- afterWebflowReady(callback) {
187
- if (typeof callback === 'function') {
188
- if (this.loaded) {
189
- callback();
190
- } else {
191
- postWebflowCallbacks.push(callback);
192
- }
193
- }
194
- },
195
-
196
- // Status method
197
- status(moduleName) {
198
- if (!moduleName) {
199
- return {
200
- loaded: Object.keys(this.modules),
201
- loading: [...this.process],
202
- animations: Object.keys(animationModules),
203
- utilities: Object.keys(utilityModules),
204
- autoInit: Object.keys(autoInitModules)
205
- };
206
- }
207
- return {
208
- loaded: !!this.modules[moduleName],
209
- loading: this.process.has(moduleName)
210
- };
169
+ for (const attrName of Object.keys(allDataAttributes)) {
170
+ if (script.hasAttribute(attrName)) {
171
+ loadModule(attrName);
211
172
  }
212
- };
213
-
214
- // Process modules from script tags
215
- const processScriptModules = () => {
216
- for (const script of scripts) {
217
- // Check for auto mode
218
- const autoMode = script.getAttribute('data-hs-auto') === 'true';
219
-
220
- // Load modules based on script attributes
221
- for (const moduleName of Object.keys(allModules)) {
222
- if (script.hasAttribute(moduleName)) {
223
- loadHsModule(moduleName);
224
- }
225
- }
226
-
227
- // Auto-discovery mode
228
- if (autoMode) {
229
- waitDOMReady().then(() => {
230
- const foundModules = new Set();
231
- const allElements = document.querySelectorAll('*');
232
-
233
- for (const element of allElements) {
234
- for (const attrName of element.getAttributeNames()) {
235
- // Look for data-hs-* attributes
236
- const match = attrName.match(/^(data-hs-(?:anim|util)-[^-=]+)/)?.[1];
237
- if (match && allModules[match]) {
238
- foundModules.add(match);
239
- }
240
- }
241
- }
242
-
243
- for (const moduleName of foundModules) {
244
- loadHsModule(moduleName);
245
- }
246
- });
173
+ }
174
+
175
+ if (isAutoDetect) {
176
+ waitForDOMContentLoaded().then(() => {
177
+ const foundAttributes = new Set();
178
+ const allElements = document.querySelectorAll('*');
179
+
180
+ for (const element of allElements) {
181
+ for (const attrName of element.getAttributeNames()) {
182
+ const match = attrName.match(/^(data-hs-(?:anim|util)-[^-=]+)/)?.[1];
183
+ if (match && allDataAttributes[match]) {
184
+ foundAttributes.add(match);
185
+ }
247
186
  }
248
- }
249
-
250
- // Always load auto-init modules
251
- for (const moduleName of Object.keys(autoInitModules)) {
252
- loadHsModule(moduleName);
253
- }
254
-
255
- // Hide .transition elements if transition module is not loaded
256
- const hasTransition = scripts.some(script => script.hasAttribute('data-hs-anim-transition'));
257
- if (!hasTransition) {
258
- const transitionElements = document.querySelectorAll('.transition');
259
- transitionElements.forEach(element => {
260
- element.style.display = 'none';
261
- });
262
- }
263
- };
187
+ }
264
188
 
265
- // Handle rich text blocks
266
- const richTextBlocks = document.querySelectorAll('.w-richtext');
267
- richTextBlocks.forEach(block => {
268
- const images = block.querySelectorAll('img');
269
- images.forEach(img => {
270
- img.loading = 'eager';
189
+ for (const attrName of foundAttributes) {
190
+ loadModule(attrName);
191
+ }
271
192
  });
272
- });
193
+ }
194
+ }
273
195
 
274
- // Main initialization
275
- const initializeMain = async () => {
276
- // Process script modules
277
- processScriptModules();
278
-
279
- // Wait for Webflow
280
- await waitWebflowReady();
281
-
282
- // Mark as loaded
283
- window[API_NAME].loaded = true;
284
-
285
- // Run all registered post-Webflow callbacks
286
- postWebflowCallbacks.forEach((callback) => {
287
- try {
288
- callback();
289
- } catch (error) {
290
- // Silent fail for callbacks
291
- }
292
- });
293
- };
196
+ for (const moduleName of Object.keys(autoInitModules)) {
197
+ loadModule(moduleName);
198
+ }
294
199
 
295
- // Process any early requests
296
- window[API_NAME].push(...existingRequests);
200
+ if (!scripts.some(script => script.hasAttribute('data-hs-anim-transition'))) {
201
+ document.querySelectorAll('.transition').forEach(element => {
202
+ element.style.display = 'none';
203
+ });
204
+ }
205
+ };
297
206
 
298
- // Start initialization
299
- initializeMain().catch(() => {
300
- // Silent fail for initialization
207
+ document.querySelectorAll('.w-richtext').forEach(richtext => {
208
+ richtext.querySelectorAll('img').forEach(img => {
209
+ img.loading = 'eager';
301
210
  });
211
+ });
212
+
213
+ const finalize = async () => {
214
+ processModules();
215
+ await waitForWebflow();
216
+ window[API_NAME].loaded = true;
217
+ readyCallbacks.forEach(callback => {
218
+ try {
219
+ callback();
220
+ } catch (error) {
221
+ // Silent fail for callbacks
222
+ }
223
+ });
224
+ };
225
+
226
+ window[API_NAME].push(...queuedModules);
227
+ finalize().catch(() => {
228
+ // Silent fail for initialization
229
+ });
302
230
  };
303
231
 
304
- // Run initialization
305
232
  initializeHsMain();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.2.14",
4
- "description": "Core animation and utility library for client websites",
3
+ "version": "1.2.18",
4
+ "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -12,13 +12,7 @@
12
12
  ],
13
13
  "author": "Horton Studio",
14
14
  "license": "ISC",
15
- "dependencies": {
16
- "@hortonstudio/main-anim-hero": "^1.0.0",
17
- "@hortonstudio/main-anim-text": "^1.0.0",
18
- "@hortonstudio/main-anim-transition": "^1.0.0",
19
- "@hortonstudio/main-util-navbar": "^1.0.0",
20
- "@hortonstudio/main-util-toc": "^1.0.0",
21
- "@hortonstudio/main-util-progress": "^1.0.0",
22
- "@hortonstudio/main-smooth-scroll": "^1.0.0"
15
+ "scripts": {
16
+ "test": "echo \"Error: no test specified\" && exit 1"
23
17
  }
24
18
  }