@hortonstudio/main 1.9.4 → 1.9.7
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/autoInit/accessibility/README.md +126 -0
- package/autoInit/accessibility/accessibility.js +56 -0
- package/autoInit/accessibility/functions/blog-remover/README.md +61 -0
- package/autoInit/accessibility/functions/blog-remover/blog-remover.js +31 -0
- package/autoInit/accessibility/functions/click-forwarding/README.md +60 -0
- package/autoInit/accessibility/functions/click-forwarding/click-forwarding.js +82 -0
- package/autoInit/accessibility/functions/convert-to-span/README.md +59 -0
- package/autoInit/accessibility/functions/convert-to-span/convert-to-span.js +70 -0
- package/autoInit/accessibility/functions/custom-values-replacement/README.md +71 -0
- package/autoInit/accessibility/functions/custom-values-replacement/custom-values-replacement.js +102 -0
- package/autoInit/accessibility/functions/dropdown/README.md +212 -0
- package/autoInit/accessibility/functions/dropdown/dropdown.js +167 -0
- package/autoInit/accessibility/functions/list-accessibility/README.md +56 -0
- package/autoInit/accessibility/functions/list-accessibility/list-accessibility.js +23 -0
- package/autoInit/accessibility/functions/prevent-default/README.md +58 -0
- package/autoInit/accessibility/functions/prevent-default/prevent-default.js +58 -0
- package/autoInit/accessibility/functions/remove-list-accessibility/README.md +57 -0
- package/autoInit/accessibility/functions/remove-list-accessibility/remove-list-accessibility.js +68 -0
- package/autoInit/accessibility/functions/text-synchronization/README.md +62 -0
- package/autoInit/accessibility/functions/text-synchronization/text-synchronization.js +101 -0
- package/autoInit/accessibility/functions/toc/README.md +79 -0
- package/autoInit/accessibility/functions/toc/toc.js +191 -0
- package/autoInit/accessibility/functions/year-replacement/README.md +54 -0
- package/autoInit/accessibility/functions/year-replacement/year-replacement.js +43 -0
- package/autoInit/button/README.md +122 -0
- package/autoInit/counter/README.md +274 -0
- package/autoInit/{counter.js → counter/counter.js} +20 -5
- package/autoInit/form/README.md +338 -0
- package/autoInit/{form.js → form/form.js} +44 -29
- package/autoInit/navbar/README.md +366 -0
- package/autoInit/{navbar.js → navbar/navbar.js} +128 -118
- package/autoInit/site-settings/README.md +218 -0
- package/autoInit/smooth-scroll/README.md +386 -0
- package/autoInit/transition/README.md +301 -0
- package/autoInit/{transition.js → transition/transition.js} +13 -2
- package/index.js +8 -8
- package/package.json +1 -1
- package/autoInit/accessibility.js +0 -786
- /package/autoInit/{button.js → button/button.js} +0 -0
- /package/autoInit/{site-settings.js → site-settings/site-settings.js} +0 -0
- /package/autoInit/{smooth-scroll.js → smooth-scroll/smooth-scroll.js} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# **Accessibility System Documentation**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
The accessibility system provides 11 modular functions to enhance website accessibility and functionality. 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. Blog Remover**
|
|
14
|
+
Automatically removes blog wrapper elements that have no blog list content.
|
|
15
|
+
|
|
16
|
+
**Use case:** Clean up empty blog sections when using Webflow CMS conditional visibility.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### **2. List Accessibility**
|
|
21
|
+
Adds proper ARIA `role="list"` and `role="listitem"` to custom-styled lists.
|
|
22
|
+
|
|
23
|
+
**Use case:** Making non-semantic list layouts accessible to screen readers.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### **3. Remove List Accessibility**
|
|
28
|
+
Removes list semantics by stripping ARIA roles and converting `<ul>/<ol>/<li>` to divs.
|
|
29
|
+
|
|
30
|
+
**Use case:** Decorative list layouts that shouldn't be announced as lists.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### **4. Convert to Span**
|
|
35
|
+
Converts most HTML elements to span elements while preserving attributes and content.
|
|
36
|
+
|
|
37
|
+
**Use case:** Removing semantic meaning from purely decorative wrapper elements.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### **5. Year Replacement**
|
|
42
|
+
Replaces `{{year}}` and `{{month}}` placeholders with current year and month.
|
|
43
|
+
|
|
44
|
+
**Use case:** Auto-updating copyright years and date-based content.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### **6. Prevent Default**
|
|
49
|
+
Prevents default behavior on elements including clicks and keyboard activation.
|
|
50
|
+
|
|
51
|
+
**Use case:** Decorative buttons or preventing anchor link scrolling.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### **7. Custom Values Replacement**
|
|
56
|
+
Collects custom name-value pairs and replaces `{{name}}` placeholders throughout the page.
|
|
57
|
+
|
|
58
|
+
**Use case:** Dynamic content replacement for user-defined values.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### **8. Click Forwarding**
|
|
63
|
+
Forwards clicks from decorative wrapper elements to actual interactive trigger elements.
|
|
64
|
+
|
|
65
|
+
**Use case:** Making entire card areas clickable while maintaining semantic structure.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### **9. Text Synchronization**
|
|
70
|
+
Synchronizes text content and aria-labels from original element to multiple match elements in real-time.
|
|
71
|
+
|
|
72
|
+
**Use case:** Keeping duplicate content in sync across multiple locations.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### **10. Table of Contents (TOC)**
|
|
77
|
+
Automatically generates a table of contents from H2 headings with smooth scrolling, focus management, and active state tracking.
|
|
78
|
+
|
|
79
|
+
**Use case:** Auto-generated TOC navigation for blog posts and documentation pages.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### **11. Dropdown**
|
|
84
|
+
Universal dropdown system for FAQ, summary/read-more, and general toggle components. Syncs ARIA with Webflow interactions and optionally updates text content.
|
|
85
|
+
|
|
86
|
+
**Use case:** All dropdown/accordion/toggle patterns with unified ARIA management and optional text swapping.
|
|
87
|
+
|
|
88
|
+
**Note:** This function replaces the legacy `faq-accessibility` and `summary` functions with a single unified system.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## **Documentation**
|
|
93
|
+
|
|
94
|
+
Each function has detailed documentation in its respective folder:
|
|
95
|
+
|
|
96
|
+
- `functions/blog-remover/README.md`
|
|
97
|
+
- `functions/list-accessibility/README.md`
|
|
98
|
+
- `functions/remove-list-accessibility/README.md`
|
|
99
|
+
- `functions/convert-to-span/README.md`
|
|
100
|
+
- `functions/year-replacement/README.md`
|
|
101
|
+
- `functions/prevent-default/README.md`
|
|
102
|
+
- `functions/custom-values-replacement/README.md`
|
|
103
|
+
- `functions/click-forwarding/README.md`
|
|
104
|
+
- `functions/text-synchronization/README.md`
|
|
105
|
+
- `functions/toc/README.md`
|
|
106
|
+
- `functions/dropdown/README.md`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## **How It Works**
|
|
111
|
+
|
|
112
|
+
The accessibility system uses a dynamic loader pattern:
|
|
113
|
+
|
|
114
|
+
1. Main `accessibility.js` imports all function modules
|
|
115
|
+
2. Each function is loaded in parallel via `Promise.all()`
|
|
116
|
+
3. All destroy functions are collected for cleanup
|
|
117
|
+
4. On destroy, all functions are cleaned up properly
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## **Notes**
|
|
122
|
+
|
|
123
|
+
- All functions auto-initialize on page load
|
|
124
|
+
- Each function operates independently
|
|
125
|
+
- Barba.js compatible with proper cleanup
|
|
126
|
+
- No configuration required - works with data attributes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export async function init() {
|
|
2
|
+
// Centralized cleanup tracking
|
|
3
|
+
const cleanup = {
|
|
4
|
+
modules: {},
|
|
5
|
+
destroyFunctions: []
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const functionMap = {
|
|
9
|
+
"blog-remover": () => import("./functions/blog-remover/blog-remover.js"),
|
|
10
|
+
"list-accessibility": () => import("./functions/list-accessibility/list-accessibility.js"),
|
|
11
|
+
"remove-list-accessibility": () => import("./functions/remove-list-accessibility/remove-list-accessibility.js"),
|
|
12
|
+
"convert-to-span": () => import("./functions/convert-to-span/convert-to-span.js"),
|
|
13
|
+
"year-replacement": () => import("./functions/year-replacement/year-replacement.js"),
|
|
14
|
+
"prevent-default": () => import("./functions/prevent-default/prevent-default.js"),
|
|
15
|
+
"custom-values-replacement": () => import("./functions/custom-values-replacement/custom-values-replacement.js"),
|
|
16
|
+
"click-forwarding": () => import("./functions/click-forwarding/click-forwarding.js"),
|
|
17
|
+
"text-synchronization": () => import("./functions/text-synchronization/text-synchronization.js"),
|
|
18
|
+
"toc": () => import("./functions/toc/toc.js"),
|
|
19
|
+
"dropdown": () => import("./functions/dropdown/dropdown.js")
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const loadFunction = async (functionName) => {
|
|
23
|
+
try {
|
|
24
|
+
const { init } = await functionMap[functionName]();
|
|
25
|
+
const result = await init();
|
|
26
|
+
cleanup.modules[functionName] = result;
|
|
27
|
+
if (result && result.destroy) {
|
|
28
|
+
cleanup.destroyFunctions.push(result.destroy);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Failed to load accessibility function: ${functionName}`, error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Load all functions
|
|
38
|
+
const functionPromises = Object.keys(functionMap).map(name => loadFunction(name));
|
|
39
|
+
await Promise.all(functionPromises);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
result: "accessibility initialized",
|
|
43
|
+
destroy: () => {
|
|
44
|
+
// Call all destroy functions
|
|
45
|
+
cleanup.destroyFunctions.forEach(destroyFn => {
|
|
46
|
+
try {
|
|
47
|
+
destroyFn();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error('Error during accessibility cleanup:', error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
cleanup.destroyFunctions.length = 0;
|
|
53
|
+
cleanup.modules = {};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# **Blog Remover**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
Automatically removes blog wrapper elements that have no blog list content. Useful for cleaning up empty blog sections when using Webflow CMS conditional visibility.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## **Required Elements**
|
|
10
|
+
|
|
11
|
+
**Blog Wrapper**
|
|
12
|
+
* data-site-blog="wrapper"
|
|
13
|
+
* data-site-blog-config="delete-if-no-list" (triggers deletion check)
|
|
14
|
+
|
|
15
|
+
**Blog List** *(descendant of wrapper)*
|
|
16
|
+
* data-site-blog="list"
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## **What It Does**
|
|
21
|
+
|
|
22
|
+
1. Finds all `[data-site-blog="wrapper"]` elements
|
|
23
|
+
2. Checks if wrapper has `data-site-blog-config="delete-if-no-list"`
|
|
24
|
+
3. If yes, checks for descendant with `[data-site-blog="list"]`
|
|
25
|
+
4. If no list found, deletes the entire wrapper
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## **Usage Example**
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- Will be deleted if no blog list inside -->
|
|
33
|
+
<div data-site-blog="wrapper" data-site-blog-config="delete-if-no-list">
|
|
34
|
+
<h2>Recent Posts</h2>
|
|
35
|
+
<!-- If Webflow CMS hides the list, wrapper gets removed -->
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Will remain (no delete config) -->
|
|
39
|
+
<div data-site-blog="wrapper">
|
|
40
|
+
<h2>Recent Posts</h2>
|
|
41
|
+
<p>No posts yet.</p>
|
|
42
|
+
</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## **Key Attributes**
|
|
48
|
+
|
|
49
|
+
| Attribute | Purpose |
|
|
50
|
+
| ----- | ----- |
|
|
51
|
+
| `data-site-blog="wrapper"` | Blog container |
|
|
52
|
+
| `data-site-blog-config="delete-if-no-list"` | Enable deletion if empty |
|
|
53
|
+
| `data-site-blog="list"` | Blog list (prevents deletion) |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## **Notes**
|
|
58
|
+
|
|
59
|
+
* One-time operation on page load
|
|
60
|
+
* No cleanup needed
|
|
61
|
+
* Barba.js compatible
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function init() {
|
|
2
|
+
function setupBlogListCleanup() {
|
|
3
|
+
const wrappers = document.querySelectorAll('[data-site-blog="wrapper"]');
|
|
4
|
+
|
|
5
|
+
wrappers.forEach(wrapper => {
|
|
6
|
+
// Check if wrapper has the delete-if-no-list config
|
|
7
|
+
const shouldDelete = wrapper.getAttribute('data-site-blog-config') === 'delete-if-no-list';
|
|
8
|
+
|
|
9
|
+
if (!shouldDelete) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Check if there's a descendant with data-site-blog="list"
|
|
14
|
+
const hasList = wrapper.querySelector('[data-site-blog="list"]') !== null;
|
|
15
|
+
|
|
16
|
+
// Delete wrapper if it doesn't have a list
|
|
17
|
+
if (!hasList) {
|
|
18
|
+
wrapper.remove();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setupBlogListCleanup();
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
result: "blog-remover initialized",
|
|
27
|
+
destroy: () => {
|
|
28
|
+
// No cleanup needed - this is a one-time DOM operation
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# **Click Forwarding**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
Forwards clicks from decorative/wrapper elements to actual interactive trigger elements. Useful for making entire card areas clickable while maintaining semantic button/link.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## **Required Elements**
|
|
10
|
+
|
|
11
|
+
**Clickable Element** *(wrapper users click)*
|
|
12
|
+
* data-hs-a11y="click-trigger-[identifier], clickable"
|
|
13
|
+
|
|
14
|
+
**Trigger Element** *(actual button/link)*
|
|
15
|
+
* data-hs-a11y="click-trigger-[identifier], trigger"
|
|
16
|
+
|
|
17
|
+
**Note:** `[identifier]` must match between clickable and trigger.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## **What It Does**
|
|
22
|
+
|
|
23
|
+
1. Finds all clickable elements
|
|
24
|
+
2. Matches them to trigger elements by identifier
|
|
25
|
+
3. Forwards click events from clickable → trigger
|
|
26
|
+
4. Adds keyboard support (Enter/Space)
|
|
27
|
+
5. Sets `tabindex="0"` and `role="button"` on clickable
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## **Usage Example**
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<div data-hs-a11y="click-trigger-card1, clickable">
|
|
35
|
+
<h3>Card Title</h3>
|
|
36
|
+
<p>Card description...</p>
|
|
37
|
+
<button data-hs-a11y="click-trigger-card1, trigger">
|
|
38
|
+
Learn More
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Result:** Clicking anywhere in the div triggers the button.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## **Key Attributes**
|
|
48
|
+
|
|
49
|
+
| Attribute | Purpose |
|
|
50
|
+
| ----- | ----- |
|
|
51
|
+
| `data-hs-a11y="click-trigger-[id], clickable"` | Wrapper to make clickable |
|
|
52
|
+
| `data-hs-a11y="click-trigger-[id], trigger"` | Actual button/link |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## **Notes**
|
|
57
|
+
|
|
58
|
+
* Identifier must match exactly
|
|
59
|
+
* Event listeners cleaned up on destroy
|
|
60
|
+
* Supports keyboard activation
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function init() {
|
|
2
|
+
const cleanup = {
|
|
3
|
+
handlers: []
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const addHandler = (element, event, handler, options) => {
|
|
7
|
+
element.addEventListener(event, handler, options);
|
|
8
|
+
cleanup.handlers.push({ element, event, handler, options });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function setupClickForwarding(addHandler) {
|
|
12
|
+
// Find all clickable elements (custom styled elements users click)
|
|
13
|
+
const clickableElements = document.querySelectorAll('[data-hs-a11y*="clickable"]');
|
|
14
|
+
|
|
15
|
+
clickableElements.forEach(clickableElement => {
|
|
16
|
+
const attribute = clickableElement.getAttribute('data-hs-a11y');
|
|
17
|
+
|
|
18
|
+
// Parse the attribute: "click-trigger-[identifier], clickable"
|
|
19
|
+
const parts = attribute.split(',').map(part => part.trim());
|
|
20
|
+
|
|
21
|
+
// Find the part with click-trigger and the part with clickable
|
|
22
|
+
const triggerPart = parts.find(part => part.startsWith('click-trigger-'));
|
|
23
|
+
const rolePart = parts.find(part => part === 'clickable');
|
|
24
|
+
|
|
25
|
+
if (!triggerPart || !rolePart) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract identifier from "click-trigger-[identifier]"
|
|
30
|
+
const identifier = triggerPart.replace('click-trigger-', '').trim();
|
|
31
|
+
|
|
32
|
+
// Find the corresponding trigger element
|
|
33
|
+
const triggerSelector = `[data-hs-a11y*="click-trigger-${identifier}"][data-hs-a11y*="trigger"]`;
|
|
34
|
+
const triggerElement = document.querySelector(triggerSelector);
|
|
35
|
+
|
|
36
|
+
if (!triggerElement) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add click event listener to forward clicks
|
|
41
|
+
const clickHandler = (event) => {
|
|
42
|
+
// Prevent default behavior on the clickable element
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
|
|
46
|
+
// Trigger click on the target element
|
|
47
|
+
triggerElement.click();
|
|
48
|
+
};
|
|
49
|
+
addHandler(clickableElement, 'click', clickHandler);
|
|
50
|
+
|
|
51
|
+
// Also handle keyboard events for accessibility
|
|
52
|
+
const keydownHandler = (event) => {
|
|
53
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
triggerElement.click();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
addHandler(clickableElement, 'keydown', keydownHandler);
|
|
60
|
+
|
|
61
|
+
// Ensure clickable element is keyboard accessible
|
|
62
|
+
if (!clickableElement.hasAttribute('tabindex')) {
|
|
63
|
+
clickableElement.setAttribute('tabindex', '0');
|
|
64
|
+
}
|
|
65
|
+
if (!clickableElement.hasAttribute('role')) {
|
|
66
|
+
clickableElement.setAttribute('role', 'button');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setupClickForwarding(addHandler);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
result: "click-forwarding initialized",
|
|
75
|
+
destroy: () => {
|
|
76
|
+
cleanup.handlers.forEach(({ element, event, handler, options }) => {
|
|
77
|
+
element.removeEventListener(event, handler, options);
|
|
78
|
+
});
|
|
79
|
+
cleanup.handlers.length = 0;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# **Convert to Span**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
Converts most HTML elements to span elements while preserving attributes and content. Useful for removing semantic meaning from decorative elements.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## **Required Elements**
|
|
10
|
+
|
|
11
|
+
**Container**
|
|
12
|
+
* data-hs-a11y="convert-span"
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## **What It Does**
|
|
17
|
+
|
|
18
|
+
1. Finds all `[data-hs-a11y="convert-span"]` containers
|
|
19
|
+
2. Converts all descendant elements to `<span>` (except skip list)
|
|
20
|
+
3. Converts container itself to `<span>`
|
|
21
|
+
4. Preserves all attributes except `data-hs-a11y`
|
|
22
|
+
|
|
23
|
+
**Skip List:** `span`, `a`, `button`, `input`, `textarea`, `select`, `img`, `video`, `audio`, `iframe`, `object`, `embed`, `canvas`, `svg`, `form`, `table`, semantic tags, headings, `script`, `style`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## **Usage Example**
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!-- Before -->
|
|
31
|
+
<div data-hs-a11y="convert-span">
|
|
32
|
+
<p>Text here</p>
|
|
33
|
+
<section>Content</section>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- After -->
|
|
37
|
+
<span>
|
|
38
|
+
<span>Text here</span>
|
|
39
|
+
<span>Content</span>
|
|
40
|
+
</span>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Result:** All elements become spans, removing semantic meaning.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## **Key Attributes**
|
|
48
|
+
|
|
49
|
+
| Attribute | Purpose |
|
|
50
|
+
| ----- | ----- |
|
|
51
|
+
| `data-hs-a11y="convert-span"` | Container to convert |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## **Notes**
|
|
56
|
+
|
|
57
|
+
* One-time DOM transformation
|
|
58
|
+
* Skips interactive and media elements
|
|
59
|
+
* Use for purely decorative wrappers
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function init() {
|
|
2
|
+
function setupConvertToSpan() {
|
|
3
|
+
const containers = document.querySelectorAll('[data-hs-a11y="convert-span"]');
|
|
4
|
+
|
|
5
|
+
containers.forEach(container => {
|
|
6
|
+
const skipTags = [
|
|
7
|
+
'span', 'a', 'button', 'input', 'textarea', 'select', 'img', 'video', 'audio',
|
|
8
|
+
'iframe', 'object', 'embed', 'canvas', 'svg', 'form', 'table', 'thead', 'tbody',
|
|
9
|
+
'tr', 'td', 'th', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4',
|
|
10
|
+
'h5', 'h6', 'script', 'style', 'link', 'meta', 'title', 'head', 'html', 'body'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Convert all child elements first
|
|
14
|
+
const elementsToConvert = container.querySelectorAll('*');
|
|
15
|
+
|
|
16
|
+
elementsToConvert.forEach(element => {
|
|
17
|
+
const tagName = element.tagName.toLowerCase();
|
|
18
|
+
|
|
19
|
+
if (!skipTags.includes(tagName)) {
|
|
20
|
+
const newSpan = document.createElement('span');
|
|
21
|
+
|
|
22
|
+
// Copy all attributes except data-hs-a11y
|
|
23
|
+
Array.from(element.attributes).forEach(attr => {
|
|
24
|
+
if (attr.name !== 'data-hs-a11y') {
|
|
25
|
+
newSpan.setAttribute(attr.name, attr.value);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Move all child nodes
|
|
30
|
+
while (element.firstChild) {
|
|
31
|
+
newSpan.appendChild(element.firstChild);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Replace the element
|
|
35
|
+
element.parentNode.replaceChild(newSpan, element);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Convert the container itself to span
|
|
40
|
+
const containerTagName = container.tagName.toLowerCase();
|
|
41
|
+
if (!skipTags.includes(containerTagName)) {
|
|
42
|
+
const newSpan = document.createElement('span');
|
|
43
|
+
|
|
44
|
+
// Copy all attributes except data-hs-a11y
|
|
45
|
+
Array.from(container.attributes).forEach(attr => {
|
|
46
|
+
if (attr.name !== 'data-hs-a11y') {
|
|
47
|
+
newSpan.setAttribute(attr.name, attr.value);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Move all child nodes
|
|
52
|
+
while (container.firstChild) {
|
|
53
|
+
newSpan.appendChild(container.firstChild);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Replace the container
|
|
57
|
+
container.parentNode.replaceChild(newSpan, container);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setupConvertToSpan();
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
result: "convert-to-span initialized",
|
|
66
|
+
destroy: () => {
|
|
67
|
+
// No cleanup needed - this is a one-time DOM operation
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# **Custom Values Replacement**
|
|
2
|
+
|
|
3
|
+
## **Overview**
|
|
4
|
+
|
|
5
|
+
Collects custom name-value pairs from a list and replaces `{{name}}` placeholders throughout the page. Similar to site-settings but for user-defined values.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## **Required Elements**
|
|
10
|
+
|
|
11
|
+
**Custom Values List**
|
|
12
|
+
* data-hs-a11y="custom-values-list"
|
|
13
|
+
|
|
14
|
+
**Name Element** *(within list descendants)*
|
|
15
|
+
* data-hs-a11y="custom-values-name"
|
|
16
|
+
|
|
17
|
+
**Value Element** *(within list descendants)*
|
|
18
|
+
* data-hs-a11y="custom-values-value"
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## **What It Does**
|
|
23
|
+
|
|
24
|
+
1. Finds custom values list
|
|
25
|
+
2. Collects name-value pairs from descendants
|
|
26
|
+
3. Replaces `{{name}}` placeholders in all text nodes
|
|
27
|
+
4. Replaces `{{name}}` placeholders in link hrefs
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## **Usage Example**
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<div data-hs-a11y="custom-values-list" style="display: none;">
|
|
35
|
+
<div>
|
|
36
|
+
<span data-hs-a11y="custom-values-name">support-email</span>
|
|
37
|
+
<span data-hs-a11y="custom-values-value">help@example.com</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<span data-hs-a11y="custom-values-name">phone</span>
|
|
41
|
+
<span data-hs-a11y="custom-values-value">555-1234</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<p>Contact us at {{support-email}} or call {{phone}}</p>
|
|
46
|
+
<a href="mailto:{{support-email}}">Email Us</a>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Result:**
|
|
50
|
+
```html
|
|
51
|
+
<p>Contact us at help@example.com or call 555-1234</p>
|
|
52
|
+
<a href="mailto:help@example.com">Email Us</a>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## **Key Attributes**
|
|
58
|
+
|
|
59
|
+
| Attribute | Purpose |
|
|
60
|
+
| ----- | ----- |
|
|
61
|
+
| `data-hs-a11y="custom-values-list"` | Container for values |
|
|
62
|
+
| `data-hs-a11y="custom-values-name"` | Placeholder name |
|
|
63
|
+
| `data-hs-a11y="custom-values-value"` | Replacement value |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## **Notes**
|
|
68
|
+
|
|
69
|
+
* One-time text replacement
|
|
70
|
+
* List should be hidden with CSS
|
|
71
|
+
* No cleanup needed
|