@hortonstudio/main 1.9.6 → 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.
Files changed (40) hide show
  1. package/autoInit/accessibility/README.md +126 -0
  2. package/autoInit/accessibility/accessibility.js +56 -0
  3. package/autoInit/accessibility/functions/blog-remover/README.md +61 -0
  4. package/autoInit/accessibility/functions/blog-remover/blog-remover.js +31 -0
  5. package/autoInit/accessibility/functions/click-forwarding/README.md +60 -0
  6. package/autoInit/accessibility/functions/click-forwarding/click-forwarding.js +82 -0
  7. package/autoInit/accessibility/functions/convert-to-span/README.md +59 -0
  8. package/autoInit/accessibility/functions/convert-to-span/convert-to-span.js +70 -0
  9. package/autoInit/accessibility/functions/custom-values-replacement/README.md +71 -0
  10. package/autoInit/accessibility/functions/custom-values-replacement/custom-values-replacement.js +102 -0
  11. package/autoInit/accessibility/functions/dropdown/README.md +212 -0
  12. package/autoInit/accessibility/functions/dropdown/dropdown.js +167 -0
  13. package/autoInit/accessibility/functions/list-accessibility/README.md +56 -0
  14. package/autoInit/accessibility/functions/list-accessibility/list-accessibility.js +23 -0
  15. package/autoInit/accessibility/functions/prevent-default/README.md +58 -0
  16. package/autoInit/accessibility/functions/prevent-default/prevent-default.js +58 -0
  17. package/autoInit/accessibility/functions/remove-list-accessibility/README.md +57 -0
  18. package/autoInit/accessibility/functions/remove-list-accessibility/remove-list-accessibility.js +68 -0
  19. package/autoInit/accessibility/functions/text-synchronization/README.md +62 -0
  20. package/autoInit/accessibility/functions/text-synchronization/text-synchronization.js +101 -0
  21. package/autoInit/accessibility/functions/toc/README.md +79 -0
  22. package/autoInit/accessibility/functions/toc/toc.js +191 -0
  23. package/autoInit/accessibility/functions/year-replacement/README.md +54 -0
  24. package/autoInit/accessibility/functions/year-replacement/year-replacement.js +43 -0
  25. package/autoInit/button/README.md +122 -0
  26. package/autoInit/counter/README.md +274 -0
  27. package/autoInit/{counter.js → counter/counter.js} +20 -5
  28. package/autoInit/form/README.md +338 -0
  29. package/autoInit/{form.js → form/form.js} +44 -29
  30. package/autoInit/navbar/README.md +366 -0
  31. package/autoInit/site-settings/README.md +218 -0
  32. package/autoInit/smooth-scroll/README.md +386 -0
  33. package/autoInit/transition/README.md +301 -0
  34. package/autoInit/{transition.js → transition/transition.js} +13 -2
  35. package/index.js +7 -7
  36. package/package.json +1 -1
  37. package/autoInit/accessibility.js +0 -786
  38. /package/autoInit/{button.js → button/button.js} +0 -0
  39. /package/autoInit/{site-settings.js → site-settings/site-settings.js} +0 -0
  40. /package/autoInit/{smooth-scroll.js → smooth-scroll/smooth-scroll.js} +0 -0
@@ -0,0 +1,102 @@
1
+ export function init() {
2
+ function setupCustomValuesReplacement() {
3
+ const customValuesList = document.querySelector('[data-hs-a11y="custom-values-list"]');
4
+
5
+ if (!customValuesList) {
6
+ return;
7
+ }
8
+
9
+ // Collect all custom values data
10
+ const customValues = {};
11
+ const descendants = customValuesList.getElementsByTagName('*');
12
+
13
+ Array.from(descendants).forEach(descendant => {
14
+ const nameElement = descendant.querySelector('[data-hs-a11y="custom-values-name"]');
15
+ const valueElement = descendant.querySelector('[data-hs-a11y="custom-values-value"]');
16
+
17
+ if (nameElement && valueElement) {
18
+ const name = nameElement.textContent.trim();
19
+ const value = valueElement.textContent.trim();
20
+
21
+ if (name && value) {
22
+ customValues[name] = value;
23
+ }
24
+ }
25
+ });
26
+
27
+ // If no custom values found, exit early
28
+ if (Object.keys(customValues).length === 0) {
29
+ return;
30
+ }
31
+
32
+ // Replace text content efficiently using TreeWalker
33
+ const walker = document.createTreeWalker(
34
+ document.body,
35
+ NodeFilter.SHOW_TEXT,
36
+ {
37
+ acceptNode: (node) => {
38
+ // Check if any custom value names exist in the text content
39
+ const text = node.textContent;
40
+ for (const name in customValues) {
41
+ if (text.includes(`{{${name}}}`)) {
42
+ return NodeFilter.FILTER_ACCEPT;
43
+ }
44
+ }
45
+ return NodeFilter.FILTER_SKIP;
46
+ }
47
+ }
48
+ );
49
+
50
+ const textNodes = [];
51
+ let node;
52
+ while (node = walker.nextNode()) {
53
+ textNodes.push(node);
54
+ }
55
+
56
+ // Replace text in collected nodes
57
+ textNodes.forEach(textNode => {
58
+ let newText = textNode.textContent;
59
+ let hasChanges = false;
60
+
61
+ for (const name in customValues) {
62
+ const placeholder = `{{${name}}}`;
63
+ if (newText.includes(placeholder)) {
64
+ newText = newText.replace(new RegExp(placeholder, 'g'), customValues[name]);
65
+ hasChanges = true;
66
+ }
67
+ }
68
+
69
+ if (hasChanges) {
70
+ textNode.textContent = newText;
71
+ }
72
+ });
73
+
74
+ // Replace link hrefs
75
+ const links = document.querySelectorAll('a[href]');
76
+ links.forEach(link => {
77
+ let href = link.getAttribute('href');
78
+ let hasChanges = false;
79
+
80
+ for (const name in customValues) {
81
+ const placeholder = `{{${name}}}`;
82
+ if (href.includes(placeholder)) {
83
+ href = href.replace(new RegExp(placeholder, 'g'), customValues[name]);
84
+ hasChanges = true;
85
+ }
86
+ }
87
+
88
+ if (hasChanges) {
89
+ link.setAttribute('href', href);
90
+ }
91
+ });
92
+ }
93
+
94
+ setupCustomValuesReplacement();
95
+
96
+ return {
97
+ result: "custom-values-replacement initialized",
98
+ destroy: () => {
99
+ // No cleanup needed - this is a one-time text replacement
100
+ }
101
+ };
102
+ }
@@ -0,0 +1,212 @@
1
+ # **Dropdown Accessibility**
2
+
3
+ ## **Overview**
4
+
5
+ Universal dropdown system for FAQ, summary/read-more, and general toggle components. Automatically syncs ARIA attributes with Webflow interactions and optionally updates text content.
6
+
7
+ **This function handles all dropdown/accordion/toggle patterns with a single unified system.**
8
+
9
+ ---
10
+
11
+ ## **Required Elements**
12
+
13
+ **Dropdown Wrapper**
14
+ * data-hs-dropdown="wrapper"
15
+ * data-hs-dropdown-text-swap="Close" (text for open state, defaults to "Close")
16
+
17
+ **Dropdown Toggle** *(gets is-active class from Webflow IX)*
18
+ * data-hs-dropdown="toggle"
19
+ * Contains clickable element
20
+
21
+ **Clickable Element**
22
+ * data-site-clickable="element"
23
+ * First child receives ARIA attributes
24
+
25
+ **Text Wrapper** *(optional - controls text update scope)*
26
+ * data-hs-dropdown="text"
27
+ * If present: all text inside swaps
28
+ * If absent: only aria-label updates (visual text stays static)
29
+
30
+ **Dropdown Content** *(expandable area)*
31
+ * data-hs-dropdown="content"
32
+
33
+ ---
34
+
35
+ ## **What It Does**
36
+
37
+ 1. **Monitors Toggle State**: MutationObserver watches for `is-active` class changes
38
+ 2. **Updates ARIA**: Sets `aria-expanded`, `aria-controls`, `aria-hidden`, `role="region"`
39
+ 3. **Text Updates**:
40
+ - **With text wrapper**: Swaps all text nodes + aria-label
41
+ - **Without text wrapper**: Only updates aria-label (visual stays static)
42
+ 4. **Focus Management**: Returns focus to toggle button when closing if focus is inside content
43
+ 5. **Supports Nesting**: Each dropdown manages its own state independently
44
+
45
+ ---
46
+
47
+ ## **Usage Examples**
48
+
49
+ ### **FAQ (No Visual Text Change)**
50
+
51
+ ```html
52
+ <div data-hs-dropdown="wrapper" data-hs-dropdown-text-swap="Close FAQ">
53
+ <div data-hs-dropdown="toggle">
54
+ <div data-site-clickable="element">
55
+ <button>
56
+ <span class="u-sr-only">Open FAQ</span>
57
+ </button>
58
+ </div>
59
+ <h3>What is this product?</h3>
60
+ </div>
61
+ <div data-hs-dropdown="content">
62
+ <p>This is a detailed answer...</p>
63
+ </div>
64
+ </div>
65
+ ```
66
+
67
+ **Result:**
68
+ - Question text stays visible ✅
69
+ - Only aria-label swaps: "Open FAQ" ↔ "Close FAQ"
70
+ - Perfect for FAQ lists
71
+
72
+ ---
73
+
74
+ ### **Summary (Full Text Swap)**
75
+
76
+ ```html
77
+ <div data-hs-dropdown="wrapper" data-hs-dropdown-text-swap="Close">
78
+ <div data-hs-dropdown="toggle">
79
+ <p>View Quick Summary</p>
80
+ <div data-hs-dropdown="text">
81
+ <div data-site-clickable="element">
82
+ <button>View</button>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ <div data-hs-dropdown="content">
87
+ <p>Full summary content...</p>
88
+ </div>
89
+ </div>
90
+ ```
91
+
92
+ **Result:**
93
+ - All text in "text" wrapper swaps: "View" ↔ "Close"
94
+ - aria-label also swaps to match
95
+ - Perfect for read-more toggles
96
+
97
+ ---
98
+
99
+ ### **Product Details (No Text Wrapper)**
100
+
101
+ ```html
102
+ <div data-hs-dropdown="wrapper" data-hs-dropdown-text-swap="Hide Details">
103
+ <div data-hs-dropdown="toggle">
104
+ <div data-site-clickable="element">
105
+ <button>Show Details</button>
106
+ </div>
107
+ </div>
108
+ <div data-hs-dropdown="content">
109
+ <p>Product specifications...</p>
110
+ </div>
111
+ </div>
112
+ ```
113
+
114
+ **Result:**
115
+ - Only aria-label swaps: "Show Details" ↔ "Hide Details"
116
+ - Visual button text stays static
117
+
118
+ ---
119
+
120
+ ### **Nested Dropdowns**
121
+
122
+ ```html
123
+ <div data-hs-dropdown="wrapper" data-hs-dropdown-text-swap="Hide">
124
+ <div data-hs-dropdown="toggle">
125
+ <button>Product Details</button>
126
+ </div>
127
+ <div data-hs-dropdown="content">
128
+ <p>Product info...</p>
129
+
130
+ <!-- Nested FAQ -->
131
+ <div data-hs-dropdown="wrapper" data-hs-dropdown-text-swap="Close FAQ">
132
+ <div data-hs-dropdown="toggle">
133
+ <button>Shipping Questions</button>
134
+ </div>
135
+ <div data-hs-dropdown="content">
136
+ <p>Shipping details...</p>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ ```
142
+
143
+ **Result:**
144
+ - Each dropdown manages its own state
145
+ - Focus returns to parent when parent closes
146
+ - Fully accessible keyboard navigation
147
+
148
+ ---
149
+
150
+ ## **Key Attributes**
151
+
152
+ | Attribute | Purpose |
153
+ | ----- | ----- |
154
+ | `data-hs-dropdown="wrapper"` | Container for entire dropdown |
155
+ | `data-hs-dropdown-text-swap` | Text for open state (default: "Close") |
156
+ | `data-hs-dropdown="toggle"` | Gets is-active from Webflow IX |
157
+ | `data-hs-dropdown="text"` | Optional text update scope |
158
+ | `data-site-clickable="element"` | Contains actual button/link |
159
+ | `data-hs-dropdown="content"` | Expandable content area |
160
+
161
+ ---
162
+
163
+ ## **How It Works**
164
+
165
+ 1. **Initial Setup**: Scans for all `[data-hs-dropdown="wrapper"]` elements
166
+ 2. **ID Generation**: Creates unique IDs for ARIA relationships
167
+ 3. **Text Capture**: Saves original text from text wrapper or clickable
168
+ 4. **State Detection**: Checks for existing `is-active` class on toggle
169
+ 5. **Observer Setup**: MutationObserver watches toggle for class changes
170
+ 6. **State Sync**: When `is-active` changes, updates ARIA and text
171
+ 7. **Focus Management**: Returns focus to button when closing if needed
172
+
173
+ ---
174
+
175
+ ## **Focus Management**
176
+
177
+ When any dropdown closes:
178
+ - If focus is anywhere inside content area
179
+ - Focus automatically returns to toggle button
180
+ - Then ARIA updates apply
181
+ - Prevents keyboard users from losing focus
182
+
183
+ Works for:
184
+ - Buttons inside content ✅
185
+ - Nested dropdowns ✅
186
+ - Form fields inside content ✅
187
+
188
+ ---
189
+
190
+ ## **Notes**
191
+
192
+ * Pattern matches navbar structure
193
+ * Works with Webflow IX for visual state (`is-active`)
194
+ * Text wrapper is optional - use for visual text changes
195
+ * Without text wrapper, only aria-label changes
196
+ * Supports infinite nesting depth
197
+ * Warns in console if multiple clickables found in toggle
198
+ * All observers cleaned up on destroy for Barba.js compatibility
199
+
200
+ ---
201
+
202
+ ## **Common Patterns**
203
+
204
+ ### **When to use text wrapper:**
205
+ - Summary/Read More toggles
206
+ - Buttons that should visually change
207
+ - Any toggle where text swap is visible
208
+
209
+ ### **When NOT to use text wrapper:**
210
+ - FAQ items (question should stay visible)
211
+ - Toggles with static visible labels
212
+ - Dropdowns where only accessible text should change
@@ -0,0 +1,167 @@
1
+ export function init() {
2
+ const cleanup = {
3
+ observers: []
4
+ };
5
+
6
+ const addObserver = (observer) => cleanup.observers.push(observer);
7
+
8
+ function setupDropdownAccessibility(addObserver) {
9
+ const dropdownWrappers = document.querySelectorAll('[data-hs-dropdown="wrapper"]');
10
+
11
+ dropdownWrappers.forEach((wrapper, index) => {
12
+ const toggle = wrapper.querySelector('[data-hs-dropdown="toggle"]');
13
+ const content = wrapper.querySelector('[data-hs-dropdown="content"]');
14
+ const textSwapValue = wrapper.getAttribute('data-hs-dropdown-text-swap') || 'Close';
15
+
16
+ if (!toggle || !content) {
17
+ return;
18
+ }
19
+
20
+ // Check for multiple clickables and warn
21
+ const clickables = toggle.querySelectorAll('[data-site-clickable="element"]');
22
+ if (clickables.length > 1) {
23
+ console.warn('[dropdown-accessibility] Multiple clickables found in toggle. Using first one only.', toggle);
24
+ }
25
+
26
+ const clickable = clickables[0];
27
+ if (!clickable) {
28
+ return;
29
+ }
30
+
31
+ const button = clickable.children[0];
32
+ if (!button) {
33
+ return;
34
+ }
35
+
36
+ // Generate unique IDs
37
+ const buttonId = `hs-dropdown-btn-${index}`;
38
+ const contentId = `hs-dropdown-content-${index}`;
39
+
40
+ // Check if text wrapper exists
41
+ const textWrapper = toggle.querySelector('[data-hs-dropdown="text"]');
42
+
43
+ // Capture original text
44
+ let originalText = '';
45
+ if (textWrapper) {
46
+ // Get text from text wrapper
47
+ const walker = document.createTreeWalker(
48
+ textWrapper,
49
+ NodeFilter.SHOW_TEXT,
50
+ null,
51
+ false
52
+ );
53
+
54
+ let firstTextNode = walker.nextNode();
55
+ while (firstTextNode && !firstTextNode.textContent.trim()) {
56
+ firstTextNode = walker.nextNode();
57
+ }
58
+
59
+ originalText = firstTextNode ? firstTextNode.textContent.trim() : textWrapper.textContent.trim();
60
+ } else {
61
+ // Get text from clickable for aria-label fallback
62
+ const walker = document.createTreeWalker(
63
+ clickable,
64
+ NodeFilter.SHOW_TEXT,
65
+ null,
66
+ false
67
+ );
68
+
69
+ let firstTextNode = walker.nextNode();
70
+ while (firstTextNode && !firstTextNode.textContent.trim()) {
71
+ firstTextNode = walker.nextNode();
72
+ }
73
+
74
+ originalText = firstTextNode ? firstTextNode.textContent.trim() : clickable.textContent.trim();
75
+ }
76
+
77
+ // Function to update text nodes
78
+ function updateText(newText) {
79
+ if (textWrapper) {
80
+ // Update all text nodes in text wrapper
81
+ const walker = document.createTreeWalker(
82
+ textWrapper,
83
+ NodeFilter.SHOW_TEXT,
84
+ null,
85
+ false
86
+ );
87
+
88
+ const textNodes = [];
89
+ let node;
90
+ while (node = walker.nextNode()) {
91
+ if (node.textContent.trim()) {
92
+ textNodes.push(node);
93
+ }
94
+ }
95
+
96
+ textNodes.forEach(textNode => {
97
+ textNode.textContent = newText;
98
+ });
99
+ }
100
+
101
+ // Always update aria-label
102
+ button.setAttribute('aria-label', newText);
103
+ }
104
+
105
+ // Set initial IDs and ARIA attributes
106
+ button.setAttribute('id', buttonId);
107
+ content.setAttribute('id', contentId);
108
+ content.setAttribute('role', 'region');
109
+ content.setAttribute('aria-labelledby', buttonId);
110
+
111
+ button.setAttribute('aria-controls', contentId);
112
+
113
+ // Function to check if dropdown is open
114
+ function isDropdownOpen() {
115
+ return toggle.classList.contains('is-active');
116
+ }
117
+
118
+ // Update ARIA states based on current visual state
119
+ function updateARIAStates() {
120
+ const isOpen = isDropdownOpen();
121
+ const wasOpen = button.getAttribute("aria-expanded") === "true";
122
+
123
+ // If closing and focus is inside content, return focus first
124
+ if (wasOpen && !isOpen && content.contains(document.activeElement)) {
125
+ button.focus();
126
+ }
127
+
128
+ // Update ARIA attributes
129
+ button.setAttribute("aria-expanded", isOpen ? "true" : "false");
130
+ content.setAttribute("aria-hidden", isOpen ? "false" : "true");
131
+
132
+ // Update text and aria-label
133
+ if (isOpen) {
134
+ updateText(textSwapValue);
135
+ } else {
136
+ updateText(originalText);
137
+ }
138
+ }
139
+
140
+ // Set initial state based on existing is-active class
141
+ updateARIAStates();
142
+
143
+ // Monitor for class changes on toggle
144
+ const observer = new MutationObserver(() => {
145
+ updateARIAStates();
146
+ });
147
+
148
+ observer.observe(toggle, {
149
+ attributes: true,
150
+ attributeFilter: ['class']
151
+ });
152
+
153
+ addObserver(observer);
154
+ });
155
+ }
156
+
157
+ setupDropdownAccessibility(addObserver);
158
+
159
+ return {
160
+ result: "dropdown initialized",
161
+ destroy: () => {
162
+ // Disconnect all observers
163
+ cleanup.observers.forEach(obs => obs.disconnect());
164
+ cleanup.observers.length = 0;
165
+ }
166
+ };
167
+ }
@@ -0,0 +1,56 @@
1
+ # **List Accessibility**
2
+
3
+ ## **Overview**
4
+
5
+ Adds proper ARIA roles to custom-styled lists that aren't using semantic HTML list elements.
6
+
7
+ ---
8
+
9
+ ## **Required Elements**
10
+
11
+ **List Container**
12
+ * data-hs-a11y="list"
13
+
14
+ **List Items**
15
+ * data-hs-a11y="list-item"
16
+
17
+ ---
18
+
19
+ ## **What It Does**
20
+
21
+ 1. Finds all `[data-hs-a11y="list"]` elements
22
+ 2. Adds `role="list"` to each
23
+ 3. Finds all `[data-hs-a11y="list-item"]` elements
24
+ 4. Adds `role="listitem"` to each
25
+
26
+ ---
27
+
28
+ ## **Usage Example**
29
+
30
+ ```html
31
+ <!-- Custom styled list -->
32
+ <div data-hs-a11y="list">
33
+ <div data-hs-a11y="list-item">Item 1</div>
34
+ <div data-hs-a11y="list-item">Item 2</div>
35
+ <div data-hs-a11y="list-item">Item 3</div>
36
+ </div>
37
+ ```
38
+
39
+ **Result:** Screen readers announce as a list with 3 items.
40
+
41
+ ---
42
+
43
+ ## **Key Attributes**
44
+
45
+ | Attribute | Purpose |
46
+ | ----- | ----- |
47
+ | `data-hs-a11y="list"` | List container |
48
+ | `data-hs-a11y="list-item"` | Individual list items |
49
+
50
+ ---
51
+
52
+ ## **Notes**
53
+
54
+ * Use when not using `<ul>/<ol>/<li>` semantic HTML
55
+ * ARIA roles remain after initialization
56
+ * No cleanup needed
@@ -0,0 +1,23 @@
1
+ export function init() {
2
+ function setupListAccessibility() {
3
+ const listElements = document.querySelectorAll('[data-hs-a11y="list"]');
4
+ const listItemElements = document.querySelectorAll('[data-hs-a11y="list-item"]');
5
+
6
+ listElements.forEach(element => {
7
+ element.setAttribute('role', 'list');
8
+ });
9
+
10
+ listItemElements.forEach(element => {
11
+ element.setAttribute('role', 'listitem');
12
+ });
13
+ }
14
+
15
+ setupListAccessibility();
16
+
17
+ return {
18
+ result: "list-accessibility initialized",
19
+ destroy: () => {
20
+ // No cleanup needed - ARIA attributes remain on elements
21
+ }
22
+ };
23
+ }
@@ -0,0 +1,58 @@
1
+ # **Prevent Default**
2
+
3
+ ## **Overview**
4
+
5
+ Prevents default behavior on elements, including clicks and keyboard activation. Useful for decorative buttons or preventing anchor scrolling.
6
+
7
+ ---
8
+
9
+ ## **Required Elements**
10
+
11
+ **Element to Disable**
12
+ * data-hs-a11y="prevent-default"
13
+
14
+ ---
15
+
16
+ ## **What It Does**
17
+
18
+ 1. Finds all `[data-hs-a11y="prevent-default"]` elements
19
+ 2. Prevents click events
20
+ 3. Prevents Enter/Space keyboard activation
21
+ 4. For anchor links with `#` hrefs:
22
+ - Removes href attribute
23
+ - Sets `role="button"`
24
+ - Sets `tabindex="0"`
25
+
26
+ ---
27
+
28
+ ## **Usage Example**
29
+
30
+ ```html
31
+ <!-- Decorative button (no action) -->
32
+ <button data-hs-a11y="prevent-default">
33
+ Disabled Action
34
+ </button>
35
+
36
+ <!-- Anchor link without scroll -->
37
+ <a href="#" data-hs-a11y="prevent-default">
38
+ Click me (won't scroll)
39
+ </a>
40
+ ```
41
+
42
+ **Result:** Elements receive focus but perform no action when clicked or activated.
43
+
44
+ ---
45
+
46
+ ## **Key Attributes**
47
+
48
+ | Attribute | Purpose |
49
+ | ----- | ----- |
50
+ | `data-hs-a11y="prevent-default"` | Element to disable |
51
+
52
+ ---
53
+
54
+ ## **Notes**
55
+
56
+ * Event listeners cleaned up on destroy
57
+ * Useful with Webflow IX for custom behaviors
58
+ * Anchor links converted to role="button"
@@ -0,0 +1,58 @@
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 setupPreventDefault(addHandler) {
12
+ const elements = document.querySelectorAll('[data-hs-a11y="prevent-default"]');
13
+
14
+ elements.forEach(element => {
15
+ // Prevent click
16
+ const clickHandler = (e) => {
17
+ e.preventDefault();
18
+ e.stopPropagation();
19
+ return false;
20
+ };
21
+ addHandler(element, 'click', clickHandler);
22
+
23
+ // Prevent keyboard activation
24
+ const keydownHandler = (e) => {
25
+ if (e.key === 'Enter' || e.key === ' ') {
26
+ e.preventDefault();
27
+ e.stopPropagation();
28
+ return false;
29
+ }
30
+ };
31
+ addHandler(element, 'keydown', keydownHandler);
32
+
33
+ // Additional prevention for anchor links
34
+ if (element.tagName.toLowerCase() === 'a') {
35
+ // Remove or modify href to prevent scroll
36
+ const originalHref = element.getAttribute('href');
37
+ if (originalHref && (originalHref === '#' || originalHref.startsWith('#'))) {
38
+ element.setAttribute('data-original-href', originalHref);
39
+ element.removeAttribute('href');
40
+ element.setAttribute('role', 'button');
41
+ element.setAttribute('tabindex', '0');
42
+ }
43
+ }
44
+ });
45
+ }
46
+
47
+ setupPreventDefault(addHandler);
48
+
49
+ return {
50
+ result: "prevent-default initialized",
51
+ destroy: () => {
52
+ cleanup.handlers.forEach(({ element, event, handler, options }) => {
53
+ element.removeEventListener(event, handler, options);
54
+ });
55
+ cleanup.handlers.length = 0;
56
+ }
57
+ };
58
+ }