@hortonstudio/main 1.4.0 → 1.4.1
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/animations/text.js +32 -5
- package/autoInit/accessibility.js +184 -1
- package/autoInit/navbar.js +11 -1
- package/index.js +1 -3
- package/package.json +1 -1
- package/autoInit/custom-values.js +0 -266
package/animations/text.js
CHANGED
@@ -176,7 +176,9 @@ const CharSplitAnimations = {
|
|
176
176
|
scrollTrigger: {
|
177
177
|
trigger: textElement,
|
178
178
|
start: config.charSplit.start,
|
179
|
-
invalidateOnRefresh:
|
179
|
+
invalidateOnRefresh: true,
|
180
|
+
toggleActions: "play none none none",
|
181
|
+
once: true,
|
180
182
|
},
|
181
183
|
onComplete: () => {
|
182
184
|
if (textElement.splitTextInstance) {
|
@@ -247,7 +249,9 @@ const WordSplitAnimations = {
|
|
247
249
|
scrollTrigger: {
|
248
250
|
trigger: textElement,
|
249
251
|
start: config.wordSplit.start,
|
250
|
-
invalidateOnRefresh:
|
252
|
+
invalidateOnRefresh: true,
|
253
|
+
toggleActions: "play none none none",
|
254
|
+
once: true,
|
251
255
|
},
|
252
256
|
onComplete: () => {
|
253
257
|
if (textElement.splitTextInstance) {
|
@@ -318,7 +322,9 @@ const LineSplitAnimations = {
|
|
318
322
|
scrollTrigger: {
|
319
323
|
trigger: textElement,
|
320
324
|
start: config.lineSplit.start,
|
321
|
-
invalidateOnRefresh:
|
325
|
+
invalidateOnRefresh: true,
|
326
|
+
toggleActions: "play none none none",
|
327
|
+
once: true,
|
322
328
|
},
|
323
329
|
onComplete: () => {
|
324
330
|
if (textElement.splitTextInstance) {
|
@@ -377,7 +383,9 @@ const AppearAnimations = {
|
|
377
383
|
scrollTrigger: {
|
378
384
|
trigger: element,
|
379
385
|
start: config.appear.start,
|
380
|
-
invalidateOnRefresh:
|
386
|
+
invalidateOnRefresh: true,
|
387
|
+
toggleActions: "play none none none",
|
388
|
+
once: true,
|
381
389
|
},
|
382
390
|
});
|
383
391
|
|
@@ -430,7 +438,9 @@ const RevealAnimations = {
|
|
430
438
|
scrollTrigger: {
|
431
439
|
trigger: element,
|
432
440
|
start: config.reveal.start,
|
433
|
-
invalidateOnRefresh:
|
441
|
+
invalidateOnRefresh: true,
|
442
|
+
toggleActions: "play none none none",
|
443
|
+
once: true,
|
434
444
|
},
|
435
445
|
});
|
436
446
|
|
@@ -525,6 +535,23 @@ export async function init() {
|
|
525
535
|
};
|
526
536
|
window.addEventListener("resize", resizeHandler);
|
527
537
|
|
538
|
+
// Add page load handler for proper ScrollTrigger refresh timing
|
539
|
+
const handlePageLoad = () => {
|
540
|
+
setTimeout(() => {
|
541
|
+
try {
|
542
|
+
ScrollTrigger.refresh();
|
543
|
+
} catch (error) {
|
544
|
+
console.warn("Error refreshing ScrollTrigger on page load:", error);
|
545
|
+
}
|
546
|
+
}, 100);
|
547
|
+
};
|
548
|
+
|
549
|
+
if (document.readyState === 'complete') {
|
550
|
+
handlePageLoad();
|
551
|
+
} else {
|
552
|
+
window.addEventListener('load', handlePageLoad);
|
553
|
+
}
|
554
|
+
|
528
555
|
// Initialize API with proper checks
|
529
556
|
if (!window[API_NAME]) {
|
530
557
|
window[API_NAME] = {};
|
@@ -3,7 +3,190 @@ export function init() {
|
|
3
3
|
// Stats accessibility has been moved to counter.js
|
4
4
|
|
5
5
|
function setupGeneralAccessibility() {
|
6
|
-
|
6
|
+
setupListAccessibility();
|
7
|
+
setupFAQAccessibility();
|
8
|
+
setupConvertToSpan();
|
9
|
+
setupYearReplacement();
|
10
|
+
setupPreventDefault();
|
11
|
+
}
|
12
|
+
|
13
|
+
function setupListAccessibility() {
|
14
|
+
const listElements = document.querySelectorAll('[data-hs-a11y="list"]');
|
15
|
+
const listItemElements = document.querySelectorAll('[data-hs-a11y="list-item"]');
|
16
|
+
|
17
|
+
listElements.forEach(element => {
|
18
|
+
element.setAttribute('role', 'list');
|
19
|
+
element.removeAttribute('data-hs-a11y');
|
20
|
+
});
|
21
|
+
|
22
|
+
listItemElements.forEach(element => {
|
23
|
+
element.setAttribute('role', 'listitem');
|
24
|
+
element.removeAttribute('data-hs-a11y');
|
25
|
+
});
|
26
|
+
}
|
27
|
+
|
28
|
+
function setupFAQAccessibility() {
|
29
|
+
const faqContainers = document.querySelectorAll('[data-hs-a11y="faq"]');
|
30
|
+
|
31
|
+
faqContainers.forEach((container, index) => {
|
32
|
+
const button = container.querySelector('button');
|
33
|
+
const contentWrapper = button.parentElement.nextElementSibling;
|
34
|
+
|
35
|
+
const buttonId = `faq-button-${index}`;
|
36
|
+
const contentId = `faq-content-${index}`;
|
37
|
+
|
38
|
+
button.setAttribute('id', buttonId);
|
39
|
+
button.setAttribute('aria-expanded', 'false');
|
40
|
+
button.setAttribute('aria-controls', contentId);
|
41
|
+
|
42
|
+
contentWrapper.setAttribute('id', contentId);
|
43
|
+
contentWrapper.setAttribute('aria-hidden', 'true');
|
44
|
+
contentWrapper.setAttribute('role', 'region');
|
45
|
+
contentWrapper.setAttribute('aria-labelledby', buttonId);
|
46
|
+
|
47
|
+
if (contentWrapper.style.height !== '0px') {
|
48
|
+
button.setAttribute('aria-expanded', 'true');
|
49
|
+
contentWrapper.setAttribute('aria-hidden', 'false');
|
50
|
+
}
|
51
|
+
|
52
|
+
function toggleFAQ() {
|
53
|
+
const isOpen = button.getAttribute('aria-expanded') === 'true';
|
54
|
+
|
55
|
+
button.setAttribute('aria-expanded', !isOpen);
|
56
|
+
contentWrapper.setAttribute('aria-hidden', isOpen);
|
57
|
+
}
|
58
|
+
|
59
|
+
button.addEventListener('click', toggleFAQ);
|
60
|
+
|
61
|
+
container.removeAttribute('data-hs-a11y');
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
65
|
+
function setupConvertToSpan() {
|
66
|
+
const containers = document.querySelectorAll('[data-hs-a11y="convert-span"]');
|
67
|
+
|
68
|
+
containers.forEach(container => {
|
69
|
+
const skipTags = [
|
70
|
+
'span', 'a', 'button', 'input', 'textarea', 'select', 'img', 'video', 'audio',
|
71
|
+
'iframe', 'object', 'embed', 'canvas', 'svg', 'form', 'table', 'thead', 'tbody',
|
72
|
+
'tr', 'td', 'th', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4',
|
73
|
+
'h5', 'h6', 'script', 'style', 'link', 'meta', 'title', 'head', 'html', 'body'
|
74
|
+
];
|
75
|
+
|
76
|
+
// Convert all child elements first
|
77
|
+
const elementsToConvert = container.querySelectorAll('*');
|
78
|
+
|
79
|
+
elementsToConvert.forEach(element => {
|
80
|
+
const tagName = element.tagName.toLowerCase();
|
81
|
+
|
82
|
+
if (!skipTags.includes(tagName)) {
|
83
|
+
const newSpan = document.createElement('span');
|
84
|
+
|
85
|
+
// Copy all attributes except data-hs-a11y
|
86
|
+
Array.from(element.attributes).forEach(attr => {
|
87
|
+
if (attr.name !== 'data-hs-a11y') {
|
88
|
+
newSpan.setAttribute(attr.name, attr.value);
|
89
|
+
}
|
90
|
+
});
|
91
|
+
|
92
|
+
// Move all child nodes
|
93
|
+
while (element.firstChild) {
|
94
|
+
newSpan.appendChild(element.firstChild);
|
95
|
+
}
|
96
|
+
|
97
|
+
// Replace the element
|
98
|
+
element.parentNode.replaceChild(newSpan, element);
|
99
|
+
}
|
100
|
+
});
|
101
|
+
|
102
|
+
// Convert the container itself to span
|
103
|
+
const containerTagName = container.tagName.toLowerCase();
|
104
|
+
if (!skipTags.includes(containerTagName)) {
|
105
|
+
const newSpan = document.createElement('span');
|
106
|
+
|
107
|
+
// Copy all attributes except data-hs-a11y
|
108
|
+
Array.from(container.attributes).forEach(attr => {
|
109
|
+
if (attr.name !== 'data-hs-a11y') {
|
110
|
+
newSpan.setAttribute(attr.name, attr.value);
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
// Move all child nodes
|
115
|
+
while (container.firstChild) {
|
116
|
+
newSpan.appendChild(container.firstChild);
|
117
|
+
}
|
118
|
+
|
119
|
+
// Replace the container
|
120
|
+
container.parentNode.replaceChild(newSpan, container);
|
121
|
+
} else {
|
122
|
+
// Just remove the attribute if container shouldn't be converted
|
123
|
+
container.removeAttribute('data-hs-a11y');
|
124
|
+
}
|
125
|
+
});
|
126
|
+
}
|
127
|
+
|
128
|
+
function setupYearReplacement() {
|
129
|
+
const currentYear = new Date().getFullYear().toString();
|
130
|
+
const walker = document.createTreeWalker(
|
131
|
+
document.body,
|
132
|
+
NodeFilter.SHOW_TEXT,
|
133
|
+
{
|
134
|
+
acceptNode: (node) => {
|
135
|
+
return node.textContent.includes('{{year}}')
|
136
|
+
? NodeFilter.FILTER_ACCEPT
|
137
|
+
: NodeFilter.FILTER_SKIP;
|
138
|
+
}
|
139
|
+
}
|
140
|
+
);
|
141
|
+
|
142
|
+
const textNodes = [];
|
143
|
+
let node;
|
144
|
+
while (node = walker.nextNode()) {
|
145
|
+
textNodes.push(node);
|
146
|
+
}
|
147
|
+
|
148
|
+
textNodes.forEach(textNode => {
|
149
|
+
const newText = textNode.textContent.replace(/\{\{year\}\}/gi, currentYear);
|
150
|
+
if (newText !== textNode.textContent) {
|
151
|
+
textNode.textContent = newText;
|
152
|
+
}
|
153
|
+
});
|
154
|
+
}
|
155
|
+
|
156
|
+
function setupPreventDefault() {
|
157
|
+
const elements = document.querySelectorAll('[data-hs-a11y="prevent-default"]');
|
158
|
+
|
159
|
+
elements.forEach(element => {
|
160
|
+
// Prevent click
|
161
|
+
element.addEventListener('click', (e) => {
|
162
|
+
e.preventDefault();
|
163
|
+
e.stopPropagation();
|
164
|
+
return false;
|
165
|
+
});
|
166
|
+
|
167
|
+
// Prevent keyboard activation
|
168
|
+
element.addEventListener('keydown', (e) => {
|
169
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
170
|
+
e.preventDefault();
|
171
|
+
e.stopPropagation();
|
172
|
+
return false;
|
173
|
+
}
|
174
|
+
});
|
175
|
+
|
176
|
+
// Additional prevention for anchor links
|
177
|
+
if (element.tagName.toLowerCase() === 'a') {
|
178
|
+
// Remove or modify href to prevent scroll
|
179
|
+
const originalHref = element.getAttribute('href');
|
180
|
+
if (originalHref && (originalHref === '#' || originalHref.startsWith('#'))) {
|
181
|
+
element.setAttribute('data-original-href', originalHref);
|
182
|
+
element.removeAttribute('href');
|
183
|
+
element.setAttribute('role', 'button');
|
184
|
+
element.setAttribute('tabindex', '0');
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
element.removeAttribute('data-hs-a11y');
|
189
|
+
});
|
7
190
|
}
|
8
191
|
|
9
192
|
setupGeneralAccessibility();
|
package/autoInit/navbar.js
CHANGED
@@ -363,6 +363,16 @@ function setupDynamicDropdowns() {
|
|
363
363
|
}
|
364
364
|
});
|
365
365
|
|
366
|
+
toggle.addEventListener("click", function(e) {
|
367
|
+
if (e.isTrusted) {
|
368
|
+
// This is a real user click - prevent it
|
369
|
+
e.preventDefault();
|
370
|
+
e.stopPropagation();
|
371
|
+
return false;
|
372
|
+
}
|
373
|
+
// Programmatic clicks (from hover/keyboard) proceed normally
|
374
|
+
});
|
375
|
+
|
366
376
|
document.addEventListener("click", function (e) {
|
367
377
|
if (!wrapper.contains(e.target) && isOpen) {
|
368
378
|
closeDropdown();
|
@@ -726,7 +736,7 @@ function setupMobileMenuButton() {
|
|
726
736
|
|
727
737
|
// Cleanup function for window.mobileMenuState
|
728
738
|
if (typeof window !== "undefined") {
|
729
|
-
window.addEventListener("
|
739
|
+
window.addEventListener("pagehide", () => {
|
730
740
|
if (window.mobileMenuState) {
|
731
741
|
delete window.mobileMenuState;
|
732
742
|
}
|
package/index.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
// Version:1.4.
|
1
|
+
// Version:1.4.1
|
2
2
|
|
3
3
|
const API_NAME = "hsmain";
|
4
4
|
|
@@ -31,7 +31,6 @@ const initializeHsMain = async () => {
|
|
31
31
|
navbar: true,
|
32
32
|
accessibility: true,
|
33
33
|
counter: true,
|
34
|
-
"custom-values": true,
|
35
34
|
form: true,
|
36
35
|
};
|
37
36
|
|
@@ -55,7 +54,6 @@ const initializeHsMain = async () => {
|
|
55
54
|
navbar: () => import("./autoInit/navbar.js"),
|
56
55
|
accessibility: () => import("./autoInit/accessibility.js"),
|
57
56
|
counter: () => import("./autoInit/counter.js"),
|
58
|
-
"custom-values": () => import("./autoInit/custom-values.js"),
|
59
57
|
form: () => import("./autoInit/form.js"),
|
60
58
|
};
|
61
59
|
|
package/package.json
CHANGED
@@ -1,266 +0,0 @@
|
|
1
|
-
export function init() {
|
2
|
-
const customValues = new Map();
|
3
|
-
let isInitialized = false;
|
4
|
-
|
5
|
-
// Configuration for performance optimization
|
6
|
-
const config = {
|
7
|
-
// Attributes to search for placeholders
|
8
|
-
searchAttributes: [
|
9
|
-
'href', 'src', 'alt', 'title', 'aria-label', 'data-src',
|
10
|
-
'data-href', 'action', 'placeholder', 'value'
|
11
|
-
],
|
12
|
-
// Elements to exclude from search for performance
|
13
|
-
excludeSelectors: [
|
14
|
-
'script', 'style', 'meta', 'link', 'title', 'head',
|
15
|
-
'[data-hs-custom="list"]', '[data-hs-custom="name"]', '[data-hs-custom="value"]'
|
16
|
-
],
|
17
|
-
// Phone number formatting options
|
18
|
-
phoneFormatting: {
|
19
|
-
// Attributes that should use tel: format
|
20
|
-
telAttributes: ['href'],
|
21
|
-
// Pattern to detect phone numbers (matches various formats)
|
22
|
-
phonePattern: /^[\+]?[1-9]?[\d\s\-\(\)\.]{7,15}$/,
|
23
|
-
// Default country code (US/Canada)
|
24
|
-
defaultCountryCode: '+1',
|
25
|
-
// Clean phone for tel: links (remove all non-digits except +)
|
26
|
-
cleanForTel: (phone) => {
|
27
|
-
const cleaned = phone.replace(/[^\d+]/g, '');
|
28
|
-
// If no country code, add default
|
29
|
-
if (!cleaned.startsWith('+')) {
|
30
|
-
return config.phoneFormatting.defaultCountryCode + cleaned;
|
31
|
-
}
|
32
|
-
return cleaned;
|
33
|
-
},
|
34
|
-
// Format for display (keep original formatting)
|
35
|
-
formatForDisplay: (phone) => phone
|
36
|
-
}
|
37
|
-
};
|
38
|
-
|
39
|
-
// Detect if a value looks like a phone number
|
40
|
-
function isPhoneNumber(value) {
|
41
|
-
return config.phoneFormatting.phonePattern.test(value.trim());
|
42
|
-
}
|
43
|
-
|
44
|
-
// Format value based on context (attribute vs text content)
|
45
|
-
function formatValueForContext(value, isAttribute, attributeName) {
|
46
|
-
if (isPhoneNumber(value)) {
|
47
|
-
// For href attributes, clean the phone number (no tel: prefix)
|
48
|
-
if (isAttribute && config.phoneFormatting.telAttributes.includes(attributeName)) {
|
49
|
-
return config.phoneFormatting.cleanForTel(value);
|
50
|
-
} else {
|
51
|
-
// For display, keep original formatting
|
52
|
-
return config.phoneFormatting.formatForDisplay(value);
|
53
|
-
}
|
54
|
-
}
|
55
|
-
return value;
|
56
|
-
}
|
57
|
-
|
58
|
-
// Extract custom values from data attributes
|
59
|
-
function extractCustomValues() {
|
60
|
-
const customList = document.querySelector('[data-hs-custom="list"]');
|
61
|
-
if (!customList) {
|
62
|
-
return false;
|
63
|
-
}
|
64
|
-
|
65
|
-
const nameElements = customList.querySelectorAll('[data-hs-custom="name"]');
|
66
|
-
const valueElements = customList.querySelectorAll('[data-hs-custom="value"]');
|
67
|
-
|
68
|
-
// Build mapping from name/value pairs
|
69
|
-
for (let i = 0; i < Math.min(nameElements.length, valueElements.length); i++) {
|
70
|
-
const name = nameElements[i].textContent.trim();
|
71
|
-
const value = valueElements[i].textContent.trim();
|
72
|
-
|
73
|
-
if (name && value) {
|
74
|
-
// Store with lowercase key for case-insensitive matching
|
75
|
-
const key = `{{${name.toLowerCase()}}}`;
|
76
|
-
customValues.set(key, value);
|
77
|
-
}
|
78
|
-
}
|
79
|
-
|
80
|
-
return customValues.size > 0;
|
81
|
-
}
|
82
|
-
|
83
|
-
// Replace placeholders in text content
|
84
|
-
function replaceInText(text, isAttribute = false, attributeName = null) {
|
85
|
-
if (!text || typeof text !== 'string') return text;
|
86
|
-
|
87
|
-
let result = text;
|
88
|
-
|
89
|
-
customValues.forEach((value, placeholder) => {
|
90
|
-
// Create case-insensitive regex for exact placeholder match
|
91
|
-
const regex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
92
|
-
const matches = text.match(regex);
|
93
|
-
if (matches) {
|
94
|
-
// Format value based on context (phone numbers get special treatment)
|
95
|
-
const formattedValue = formatValueForContext(value, isAttribute, attributeName);
|
96
|
-
result = result.replace(regex, formattedValue);
|
97
|
-
}
|
98
|
-
});
|
99
|
-
|
100
|
-
return result;
|
101
|
-
}
|
102
|
-
|
103
|
-
// Replace placeholders in all attributes of an element
|
104
|
-
function replaceInAttributes(element) {
|
105
|
-
config.searchAttributes.forEach(attr => {
|
106
|
-
const value = element.getAttribute(attr);
|
107
|
-
if (value) {
|
108
|
-
const newValue = replaceInText(value, true, attr);
|
109
|
-
if (newValue !== value) {
|
110
|
-
element.setAttribute(attr, newValue);
|
111
|
-
}
|
112
|
-
}
|
113
|
-
});
|
114
|
-
}
|
115
|
-
|
116
|
-
// Check if element should be excluded from processing
|
117
|
-
function shouldExcludeElement(element) {
|
118
|
-
return config.excludeSelectors.some(selector => {
|
119
|
-
return element.matches(selector);
|
120
|
-
});
|
121
|
-
}
|
122
|
-
|
123
|
-
// Process text nodes for placeholder replacement
|
124
|
-
function processTextNodes(element) {
|
125
|
-
const walker = document.createTreeWalker(
|
126
|
-
element,
|
127
|
-
NodeFilter.SHOW_TEXT,
|
128
|
-
{
|
129
|
-
acceptNode: (node) => {
|
130
|
-
// Skip if parent element should be excluded
|
131
|
-
if (shouldExcludeElement(node.parentElement)) {
|
132
|
-
return NodeFilter.FILTER_REJECT;
|
133
|
-
}
|
134
|
-
|
135
|
-
// Only process text nodes with placeholder patterns
|
136
|
-
return node.textContent.includes('{{') && node.textContent.includes('}}')
|
137
|
-
? NodeFilter.FILTER_ACCEPT
|
138
|
-
: NodeFilter.FILTER_SKIP;
|
139
|
-
}
|
140
|
-
}
|
141
|
-
);
|
142
|
-
|
143
|
-
const textNodes = [];
|
144
|
-
let node;
|
145
|
-
while (node = walker.nextNode()) {
|
146
|
-
textNodes.push(node);
|
147
|
-
}
|
148
|
-
|
149
|
-
// Replace placeholders in collected text nodes
|
150
|
-
textNodes.forEach(textNode => {
|
151
|
-
const originalText = textNode.textContent;
|
152
|
-
const newText = replaceInText(originalText);
|
153
|
-
if (newText !== originalText) {
|
154
|
-
textNode.textContent = newText;
|
155
|
-
}
|
156
|
-
});
|
157
|
-
}
|
158
|
-
|
159
|
-
// Process all elements for attribute replacement
|
160
|
-
function processElements(container) {
|
161
|
-
const elements = container.querySelectorAll('*');
|
162
|
-
|
163
|
-
elements.forEach(element => {
|
164
|
-
if (!shouldExcludeElement(element)) {
|
165
|
-
replaceInAttributes(element);
|
166
|
-
}
|
167
|
-
});
|
168
|
-
}
|
169
|
-
|
170
|
-
// Main replacement function
|
171
|
-
function performReplacements() {
|
172
|
-
if (customValues.size === 0) return;
|
173
|
-
|
174
|
-
// Process text content
|
175
|
-
processTextNodes(document.body);
|
176
|
-
|
177
|
-
// Process element attributes
|
178
|
-
processElements(document.body);
|
179
|
-
|
180
|
-
// Also check document root attributes
|
181
|
-
replaceInAttributes(document.documentElement);
|
182
|
-
}
|
183
|
-
|
184
|
-
// Remove the custom values list from DOM
|
185
|
-
function cleanupCustomList() {
|
186
|
-
const customList = document.querySelector('[data-hs-custom="list"]');
|
187
|
-
if (customList) {
|
188
|
-
customList.remove();
|
189
|
-
}
|
190
|
-
}
|
191
|
-
|
192
|
-
// Handle dynamic content with MutationObserver
|
193
|
-
function setupDynamicContentHandler() {
|
194
|
-
const observer = new MutationObserver((mutations) => {
|
195
|
-
let hasNewContent = false;
|
196
|
-
|
197
|
-
mutations.forEach((mutation) => {
|
198
|
-
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
199
|
-
mutation.addedNodes.forEach((node) => {
|
200
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
201
|
-
// Check if new content contains placeholders
|
202
|
-
const hasPlaceholders = node.textContent.includes('{{') && node.textContent.includes('}}');
|
203
|
-
const hasAttributePlaceholders = config.searchAttributes.some(attr => {
|
204
|
-
const value = node.getAttribute?.(attr);
|
205
|
-
return value && value.includes('{{') && value.includes('}}');
|
206
|
-
});
|
207
|
-
|
208
|
-
if (hasPlaceholders || hasAttributePlaceholders) {
|
209
|
-
hasNewContent = true;
|
210
|
-
}
|
211
|
-
}
|
212
|
-
});
|
213
|
-
}
|
214
|
-
});
|
215
|
-
|
216
|
-
if (hasNewContent && customValues.size > 0) {
|
217
|
-
// Debounce replacements for performance
|
218
|
-
clearTimeout(observer.timeout);
|
219
|
-
observer.timeout = setTimeout(() => {
|
220
|
-
performReplacements();
|
221
|
-
}, 100);
|
222
|
-
}
|
223
|
-
});
|
224
|
-
|
225
|
-
observer.observe(document.body, {
|
226
|
-
childList: true,
|
227
|
-
subtree: true
|
228
|
-
});
|
229
|
-
|
230
|
-
return observer;
|
231
|
-
}
|
232
|
-
|
233
|
-
// Initialize the custom values system
|
234
|
-
function initializeCustomValues() {
|
235
|
-
if (isInitialized) return;
|
236
|
-
|
237
|
-
// Extract custom values from data attributes
|
238
|
-
const hasCustomValues = extractCustomValues();
|
239
|
-
|
240
|
-
if (hasCustomValues) {
|
241
|
-
// Perform initial replacements
|
242
|
-
performReplacements();
|
243
|
-
|
244
|
-
// Clean up the custom list
|
245
|
-
cleanupCustomList();
|
246
|
-
|
247
|
-
// Set up dynamic content handling
|
248
|
-
setupDynamicContentHandler();
|
249
|
-
|
250
|
-
isInitialized = true;
|
251
|
-
|
252
|
-
return {
|
253
|
-
result: `custom-values initialized with ${customValues.size} replacements`,
|
254
|
-
count: customValues.size
|
255
|
-
};
|
256
|
-
} else {
|
257
|
-
return {
|
258
|
-
result: 'custom-values initialized (no custom values found)',
|
259
|
-
count: 0
|
260
|
-
};
|
261
|
-
}
|
262
|
-
}
|
263
|
-
|
264
|
-
// Initialize on page load
|
265
|
-
return initializeCustomValues();
|
266
|
-
}
|