@hortonstudio/main 1.9.9 → 1.9.10

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.
@@ -1,8 +1,8 @@
1
- # **Before/After Image Comparison**
1
+ # **Before/After Image Comparison Documentation**
2
2
 
3
3
  ## **Overview**
4
4
 
5
- Interactive before/after image comparison utility with multiple viewing modes, slider controls, and carousel functionality. Supports keyboard navigation, touch gestures, and accessibility features.
5
+ Interactive before/after image comparison utility with multiple viewing modes, slider controls, and carousel functionality. Features full keyboard navigation, touch gestures, ARIA live announcements, and Barba.js compatibility.
6
6
 
7
7
  **Note:** This utility is loaded via `data-hs-util-ba` attribute on the script tag in index.js.
8
8
 
@@ -13,47 +13,129 @@ Interactive before/after image comparison utility with multiple viewing modes, s
13
13
  - **Three Viewing Modes**: Before, After, and Split (slider)
14
14
  - **Multiple Items**: Carousel navigation with arrows and pagination
15
15
  - **Slider Control**: Draggable slider with click-to-position
16
- - **Keyboard Support**: Arrow keys for navigation and slider control
17
- - **Touch Gestures**: Full mobile touch support
18
- - **Accessibility**: ARIA attributes and keyboard navigation
16
+ - **Keyboard Support**: Full arrow key navigation and slider control
17
+ - **Touch Gestures**: Complete mobile touch support
18
+ - **Accessibility**: Live regions, proper ARIA attributes, semantic roles
19
+ - **Memory Safe**: WeakMap-based references, proper cleanup for Barba.js
20
+ - **Console Warnings**: Helpful warnings for missing required elements
19
21
 
20
22
  ---
21
23
 
22
- ## **Required Elements**
24
+ ## **Required Structure**
23
25
 
24
- **Wrapper**
25
- * data-hs-ba="wrapper"
26
- * Container for entire before/after instance
26
+ ### **Wrapper** *(required)*
27
27
 
28
- **Image Wrapper**
29
- * data-hs-ba="image-wrapper"
30
- * Container for before/after images
31
- * Focusable for keyboard slider control
28
+ **Attribute:** `data-hs-ba="wrapper"`
32
29
 
33
- **Before Image**
34
- * data-hs-ba="image-before"
35
- * Background/base image layer
30
+ **What it does:** Main container for entire before/after instance. Automatically receives:
31
+ - `role="region"`
32
+ - `aria-roledescription="image comparison carousel"`
33
+ - `aria-label` with keyboard instructions
34
+ - Live region for announcing state changes
36
35
 
37
- **After Image**
38
- * data-hs-ba="image-after"
39
- * Foreground image with clip-path
36
+ ---
37
+
38
+ ### **Image Wrapper** *(required)*
40
39
 
41
- **Slider** *(optional)*
42
- * data-hs-ba="slider"
43
- * Draggable handle for split view
40
+ **Attribute:** `data-hs-ba="image-wrapper"`
44
41
 
45
- **Mode Buttons** *(optional)*
46
- * data-hs-ba="mode-before"
47
- * data-hs-ba="mode-after"
48
- * data-hs-ba="mode-split"
42
+ **What it does:** Container for before/after images. Made focusable for keyboard slider control.
43
+ - `tabindex="0"` for keyboard focus
44
+ - `role="img"` for semantics
45
+ - Arrow keys adjust slider position when focused (in split mode)
46
+
47
+ ---
49
48
 
50
- **Navigation** *(optional, for multiple items)*
51
- * data-hs-ba="left" - Previous item
52
- * data-hs-ba="right" - Next item
49
+ ### **Before Image** *(required)*
53
50
 
54
- **Pagination** *(optional)*
55
- * data-hs-ba="pagination"
56
- * Container with template dot (first child)
51
+ **Attribute:** `data-hs-ba="image-before"`
52
+
53
+ **What it does:** Background/base image layer. Always visible behind the after image.
54
+
55
+ ---
56
+
57
+ ### **After Image** *(required)*
58
+
59
+ **Attribute:** `data-hs-ba="image-after"`
60
+
61
+ **What it does:** Foreground image with clip-path animation. Reveals/hides based on mode and slider position.
62
+
63
+ ---
64
+
65
+ ### **Slider** *(optional)*
66
+
67
+ **Attribute:** `data-hs-ba="slider"`
68
+
69
+ **What it does:** Draggable handle for split view mode. Hidden in before/after modes.
70
+ - Supports mouse and touch dragging
71
+ - Click anywhere on image wrapper to reposition
72
+
73
+ ---
74
+
75
+ ### **Mode Buttons** *(optional)*
76
+
77
+ **Attributes:**
78
+ - `data-hs-ba="mode-before"`
79
+ - `data-hs-ba="mode-split"`
80
+ - `data-hs-ba="mode-after"`
81
+
82
+ **What they do:** Switch between viewing modes. Must use Global/Button component pattern:
83
+
84
+ **Typical element layout:**
85
+
86
+ 1. Mode Wrapper (data-hs-ba="mode-before/split/after")
87
+ 1. Style Element (has data-button-style attribute as descendant)
88
+ 2. Clickable Wrapper (data-site-clickable="element")
89
+ 1. Actual Button Element (first child)
90
+
91
+ **What JavaScript does:**
92
+ - Finds `[data-site-clickable="element"]` and gets `.children[0]` for actual button
93
+ - Adds descriptive `aria-label` ("Switch to before view", etc.)
94
+ - Manages `aria-pressed` state (true/false)
95
+ - Finds descendant with `data-button-style` and updates between "primary"/"secondary"
96
+ - Announces mode changes via live region
97
+
98
+ ---
99
+
100
+ ### **Navigation Arrows** *(optional, for multiple items)*
101
+
102
+ **Attributes:**
103
+ - `data-hs-ba="left-arrow"` - Previous item
104
+ - `data-hs-ba="right-arrow"` - Next item
105
+
106
+ **What they do:** Navigate between carousel items. Must use Global/Clickable component pattern:
107
+
108
+ **Typical element layout:**
109
+
110
+ 1. Arrow Wrapper (data-hs-ba="left-arrow" or "right-arrow")
111
+ 1. Clickable Wrapper (data-site-clickable="element")
112
+ 1. Link or Button Element (first child)
113
+
114
+ **What JavaScript does:**
115
+ - Finds `[data-site-clickable="element"]` and gets `.children[0]`
116
+ - Adds `aria-label` ("Previous image" or "Next image")
117
+ - Loops through items infinitely
118
+ - Announces slide changes via live region
119
+
120
+ ---
121
+
122
+ ### **Pagination** *(optional)*
123
+
124
+ **Attribute:** `data-hs-ba="pagination"`
125
+
126
+ **What it does:** Displays dots for each item. Requires template dot as first child.
127
+
128
+ **Typical element layout:**
129
+
130
+ 1. Pagination Container (data-hs-ba="pagination")
131
+ 1. Template Dot (first child, will be cloned)
132
+
133
+ **What JavaScript does:**
134
+ - Clones first child for each item
135
+ - Adds `role="group"` and `aria-label="Image pagination"` to container
136
+ - Adds `role="button"`, `tabindex="0"`, `aria-label` to each dot
137
+ - Manages `is-active` class and `aria-current="true"` state
138
+ - Keyboard accessible (Enter/Space to activate)
57
139
 
58
140
  ---
59
141
 
@@ -71,10 +153,30 @@ Interactive before/after image comparison utility with multiple viewing modes, s
71
153
  </div>
72
154
  </div>
73
155
 
74
- <!-- Mode buttons -->
75
- <button data-hs-ba="mode-before">Before</button>
76
- <button data-hs-ba="mode-split">Split</button>
77
- <button data-hs-ba="mode-after">After</button>
156
+ <!-- Mode buttons (using Global/Button components) -->
157
+ <div data-hs-ba="mode-before" data-site-button="wrapper">
158
+ <div data-button-style="secondary">
159
+ <div data-site-clickable="element">
160
+ <button type="button">Before</button>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <div data-hs-ba="mode-split" data-site-button="wrapper">
166
+ <div data-button-style="primary">
167
+ <div data-site-clickable="element">
168
+ <button type="button">Split</button>
169
+ </div>
170
+ </div>
171
+ </div>
172
+
173
+ <div data-hs-ba="mode-after" data-site-button="wrapper">
174
+ <div data-button-style="secondary">
175
+ <div data-site-clickable="element">
176
+ <button type="button">After</button>
177
+ </div>
178
+ </div>
179
+ </div>
78
180
  </div>
79
181
  ```
80
182
 
@@ -87,34 +189,47 @@ Interactive before/after image comparison utility with multiple viewing modes, s
87
189
  <!-- Item 1 -->
88
190
  <div>
89
191
  <div data-hs-ba="image-wrapper">
90
- <img data-hs-ba="image-before" src="before-1.jpg">
91
- <img data-hs-ba="image-after" src="after-1.jpg">
192
+ <img data-hs-ba="image-before" src="before-1.jpg" alt="Before">
193
+ <img data-hs-ba="image-after" src="after-1.jpg" alt="After">
92
194
  <div data-hs-ba="slider"></div>
93
195
  </div>
94
- <button data-hs-ba="mode-before">Before</button>
95
- <button data-hs-ba="mode-split">Split</button>
96
- <button data-hs-ba="mode-after">After</button>
196
+
197
+ <!-- Mode buttons for this item -->
198
+ <div data-hs-ba="mode-before" data-site-button="wrapper">...</div>
199
+ <div data-hs-ba="mode-split" data-site-button="wrapper">...</div>
200
+ <div data-hs-ba="mode-after" data-site-button="wrapper">...</div>
97
201
  </div>
98
202
 
99
203
  <!-- Item 2 -->
100
204
  <div>
101
205
  <div data-hs-ba="image-wrapper">
102
- <img data-hs-ba="image-before" src="before-2.jpg">
103
- <img data-hs-ba="image-after" src="after-2.jpg">
206
+ <img data-hs-ba="image-before" src="before-2.jpg" alt="Before">
207
+ <img data-hs-ba="image-after" src="after-2.jpg" alt="After">
104
208
  <div data-hs-ba="slider"></div>
105
209
  </div>
106
- <button data-hs-ba="mode-before">Before</button>
107
- <button data-hs-ba="mode-split">Split</button>
108
- <button data-hs-ba="mode-after">After</button>
210
+
211
+ <!-- Mode buttons for this item -->
212
+ <div data-hs-ba="mode-before" data-site-button="wrapper">...</div>
213
+ <div data-hs-ba="mode-split" data-site-button="wrapper">...</div>
214
+ <div data-hs-ba="mode-after" data-site-button="wrapper">...</div>
109
215
  </div>
110
216
 
111
217
  <!-- Navigation -->
112
- <button data-hs-ba="left">Previous</button>
113
- <button data-hs-ba="right">Next</button>
218
+ <div data-hs-ba="left-arrow">
219
+ <div data-site-clickable="element">
220
+ <a href="#">Previous</a>
221
+ </div>
222
+ </div>
223
+
224
+ <div data-hs-ba="right-arrow">
225
+ <div data-site-clickable="element">
226
+ <a href="#">Next</a>
227
+ </div>
228
+ </div>
114
229
 
115
230
  <!-- Pagination -->
116
231
  <div data-hs-ba="pagination">
117
- <div class="dot"></div> <!-- Template -->
232
+ <button class="dot"></button> <!-- Template -->
118
233
  </div>
119
234
  </div>
120
235
  ```
@@ -127,29 +242,41 @@ Interactive before/after image comparison utility with multiple viewing modes, s
127
242
  - Shows only the before image
128
243
  - Hides slider
129
244
  - Clips after image to 100% (invisible)
245
+ - Announces "Before view" to screen readers
130
246
 
131
247
  ### **After Mode**
132
248
  - Shows only the after image
133
249
  - Hides slider
134
250
  - Clips after image to 0% (fully visible)
251
+ - Announces "After view" to screen readers
135
252
 
136
253
  ### **Split Mode** *(default)*
137
254
  - Shows both images side by side
138
255
  - Slider visible and draggable
139
256
  - Default split at 50%
140
- - Clicking split mode button resets to 50%
257
+ - Clicking split mode button when already in split mode resets to 50%
258
+ - Announces "Split view" to screen readers
141
259
 
142
260
  ---
143
261
 
144
262
  ## **Keyboard Navigation**
145
263
 
146
264
  ### **Main Wrapper Focus:**
147
- - `Arrow Left/Right/Up/Down`: Navigate between items
265
+ - `Arrow Left/Right/Up/Down`: Navigate between carousel items
266
+ - Focus returns to wrapper after slide change
148
267
 
149
268
  ### **Image Wrapper Focus:**
150
- - `Arrow Left`: Move slider left (in split mode)
151
- - `Arrow Right`: Move slider right (in split mode)
152
- - Step size: 5% per press (configurable)
269
+ - `Arrow Left`: Move slider left 5% (in split mode only)
270
+ - `Arrow Right`: Move slider right 5% (in split mode only)
271
+ - Step size configurable via `keyboardStep` config
272
+
273
+ ### **Mode Buttons:**
274
+ - `Enter` or `Space`: Activate button
275
+ - `aria-pressed` state reflects active mode
276
+
277
+ ### **Pagination Dots:**
278
+ - `Tab`: Navigate between dots
279
+ - `Enter` or `Space`: Jump to that slide
153
280
 
154
281
  ---
155
282
 
@@ -200,44 +327,194 @@ window.hsmain.utilBeforeAfter.updateConfig({
200
327
 
201
328
  ---
202
329
 
330
+ ## **Key Attributes Summary**
331
+
332
+ | Attribute | Purpose | Required | Element Type |
333
+ | ----- | ----- | ----- | ----- |
334
+ | `data-hs-ba="wrapper"` | Main container | **Required** | Wrapper div |
335
+ | `data-hs-ba="image-wrapper"` | Images container | **Required** | Wrapper div |
336
+ | `data-hs-ba="image-before"` | Before image | **Required** | `<img>` |
337
+ | `data-hs-ba="image-after"` | After image | **Required** | `<img>` |
338
+ | `data-hs-ba="slider"` | Slider handle | Optional | Wrapper div |
339
+ | `data-hs-ba="mode-before"` | Before button wrapper | Optional | Wrapper div |
340
+ | `data-hs-ba="mode-split"` | Split button wrapper | Optional | Wrapper div |
341
+ | `data-hs-ba="mode-after"` | After button wrapper | Optional | Wrapper div |
342
+ | `data-hs-ba="left-arrow"` | Previous button wrapper | Optional | Wrapper div |
343
+ | `data-hs-ba="right-arrow"` | Next button wrapper | Optional | Wrapper div |
344
+ | `data-hs-ba="pagination"` | Pagination container | Optional | Wrapper div |
345
+ | `data-site-clickable="element"` | Interactive element wrapper | Required for buttons | Wrapper div |
346
+
347
+ ---
348
+
203
349
  ## **Accessibility Features**
204
350
 
205
- - Wrapper has `role="application"` with descriptive aria-label
206
- - Image wrapper has `role="img"` and keyboard instructions
207
- - Pagination dots have proper ARIA attributes
208
- - All interactive elements are keyboard accessible
209
- - Focus management between items
210
- - Slider position persists across item navigation
351
+ ### **ARIA Live Regions**
352
+ - Dynamically created live region for announcements
353
+ - Announces mode changes: "Before view", "After view", "Split view"
354
+ - Announces slide changes: "Image 2 of 3"
355
+ - Uses `aria-live="polite"` and `aria-atomic="true"`
356
+
357
+ ### **Semantic Roles**
358
+ - Wrapper: `role="region"` with `aria-roledescription="image comparison carousel"`
359
+ - Image wrapper: `role="img"` with descriptive `aria-label`
360
+ - Pagination: `role="group"` with `aria-label="Image pagination"`
361
+ - Dots: `role="button"` with `aria-current` state
362
+
363
+ ### **Button States**
364
+ - Mode buttons: `aria-pressed="true"` for active, `"false"` for inactive
365
+ - Mode buttons: Descriptive `aria-label` for each mode
366
+ - Arrow buttons: `aria-label="Previous image"` / `"Next image"`
367
+ - Pagination dots: `aria-current="true"` for active page
368
+
369
+ ### **Focus Management**
370
+ - Wrapper and image wrapper are keyboard focusable
371
+ - Tab key navigates through all interactive elements
372
+ - Focus remains on controls during interaction
373
+
374
+ ### **Memory Safety**
375
+ - Uses WeakMap for storing DOM references (prevents memory leaks)
376
+ - Proper cleanup on destroy for Barba.js page transitions
377
+ - Global event handlers set up once at module level
211
378
 
212
379
  ---
213
380
 
214
381
  ## **Touch Support**
215
382
 
216
- - Drag slider handle on touch devices
217
- - Click/tap image wrapper to position slider
218
- - Touch-friendly navigation buttons
219
- - Prevents default scroll during drag
383
+ - **Drag slider handle**: Touch and drag to adjust position
384
+ - **Click/tap to position**: Click/tap anywhere on image wrapper to move slider
385
+ - **Touch-friendly controls**: All buttons and dots are touch accessible
386
+ - **Prevents scroll**: Prevents default scroll behavior during drag
387
+
388
+ ---
389
+
390
+ ## **Console Warnings**
391
+
392
+ The utility provides helpful warnings for missing required elements:
393
+
394
+ - `[hs-before-after] Wrapper element has no child items`
395
+ - `[hs-before-after] Missing required element in item X: data-hs-ba="image-wrapper"`
396
+ - `[hs-before-after] Missing required element in item X: data-hs-ba="image-before"`
397
+ - `[hs-before-after] Missing required element in item X: data-hs-ba="image-after"`
398
+
399
+ Optional elements (slider, mode buttons, navigation, pagination) do not trigger warnings.
400
+
401
+ ---
402
+
403
+ ## **Barba.js Compatibility**
404
+
405
+ The utility is fully compatible with Barba.js page transitions:
406
+
407
+ ### **On Destroy:**
408
+ 1. Resets global drag state
409
+ 2. Removes dynamically created live region elements
410
+ 3. Clears cached elements (WeakMap and Map)
411
+ 4. Removes all event listeners via clone-and-replace
412
+ 5. Strips accessibility attributes from wrappers
413
+ 6. Removes `data-initialized` markers
414
+ 7. Cleans up window API
415
+
416
+ ### **On Reinitialize:**
417
+ 1. Finds fresh wrappers on new page
418
+ 2. Creates new instances
419
+ 3. Reuses existing global event handlers (no duplicates)
420
+ 4. Applies all accessibility features to new elements
421
+
422
+ ### **Global Event Handlers:**
423
+ - Mouse/touch drag handlers attached once at module level
424
+ - Shared across all page transitions
425
+ - Only execute when `globalDragInstance` exists
426
+ - No memory leaks or duplicate listeners
220
427
 
221
428
  ---
222
429
 
223
430
  ## **How It Works**
224
431
 
225
- 1. **Initialization**: Finds all `[data-hs-ba="wrapper"]` elements
226
- 2. **Instance Creation**: Each wrapper becomes independent instance
227
- 3. **Carousel Setup**: Multiple items get navigation and pagination
228
- 4. **Mode Management**: Buttons control image visibility via clip-path
229
- 5. **Slider Dragging**: Mouse/touch events update clip-path in real-time
230
- 6. **Keyboard Control**: Arrow keys navigate items and adjust slider
231
- 7. **State Persistence**: Slider position maintained across item changes
432
+ 1. **Module Load**: Global event handlers attach to document (once)
433
+ 2. **Initialization**: Finds all `[data-hs-ba="wrapper"]` elements not yet initialized
434
+ 3. **Instance Creation**: Each wrapper becomes independent instance
435
+ 4. **Live Region**: Creates hidden ARIA live region for announcements
436
+ 5. **Accessibility Setup**: Adds all ARIA attributes and semantic roles
437
+ 6. **Carousel Setup**: Multiple items get navigation and pagination
438
+ 7. **Mode Management**: Buttons control image visibility via clip-path
439
+ 8. **Slider Dragging**: Global handlers update clip-path via `globalDragInstance`
440
+ 9. **Keyboard Control**: Arrow keys navigate items and adjust slider
441
+ 10. **State Persistence**: Slider position maintained across item changes
442
+ 11. **Cleanup**: Destroy removes all traces for clean Barba.js transitions
443
+
444
+ ---
445
+
446
+ ## **Clickable Element Pattern**
447
+
448
+ **All interactive elements** (mode buttons, arrows) must follow this pattern:
449
+
450
+ **For Buttons:**
451
+ ```html
452
+ <div data-hs-ba="mode-split">
453
+ <div data-button-style="primary"> <!-- Found via querySelector -->
454
+ <div data-site-clickable="element">
455
+ <button type="button">Split</button> <!-- .children[0] -->
456
+ </div>
457
+ </div>
458
+ </div>
459
+ ```
460
+
461
+ **For Links:**
462
+ ```html
463
+ <div data-hs-ba="left-arrow">
464
+ <div data-site-clickable="element">
465
+ <a href="#">Previous</a> <!-- .children[0] -->
466
+ </div>
467
+ </div>
468
+ ```
469
+
470
+ **Why:** JavaScript queries `[data-site-clickable="element"]` then gets `.children[0]` to find the actual interactive element. For mode buttons, it separately finds descendants with `[data-button-style]` for style updates. This allows wrapping for styling while maintaining consistent selection.
471
+
472
+ ---
473
+
474
+ ## **Common Issues**
475
+
476
+ **Mode buttons not switching styles:**
477
+ 1. Ensure `data-button-style` attribute exists on a descendant element
478
+ 2. Verify button is first child of `[data-site-clickable="element"]`
479
+ 3. Check console for warnings about missing elements
480
+
481
+ **Arrow buttons not working:**
482
+ 1. Ensure using `data-hs-ba="left-arrow"` and `data-hs-ba="right-arrow"` (not "left"/"right")
483
+ 2. Verify `[data-site-clickable="element"]` wrapper exists
484
+ 3. Check that first child is the actual link/button element
485
+
486
+ **Slider not draggable:**
487
+ 1. Verify `data-hs-ba="image-wrapper"` exists
488
+ 2. Check that slider has `data-hs-ba="slider"` attribute
489
+ 3. Ensure in split mode (slider hidden in before/after modes)
490
+
491
+ **Pagination dots not generated:**
492
+ 1. Ensure at least one template child exists in pagination container
493
+ 2. Check that wrapper has multiple items (single item hides pagination)
494
+ 3. Verify `data-hs-ba="pagination"` is on correct element
495
+
496
+ **Console warnings appearing:**
497
+ 1. Only required elements trigger warnings: wrapper, image-wrapper, image-before, image-after
498
+ 2. Optional elements (slider, buttons, navigation) never trigger warnings
499
+ 3. Fix missing elements or ignore if intentionally omitted
500
+
501
+ **Barba.js transitions not working:**
502
+ 1. Ensure `data-initialized` attribute is being removed on destroy
503
+ 2. Check that destroy function is being called
504
+ 3. Verify global event handlers not causing issues (they're safe to persist)
232
505
 
233
506
  ---
234
507
 
235
508
  ## **Notes**
236
509
 
237
510
  - Each wrapper is an independent instance
238
- - Slider position persists when navigating items
511
+ - Slider position persists when navigating items across the carousel
512
+ - Mode state persists when navigating items
239
513
  - Pagination automatically generated from items
240
- - First pagination dot used as template
514
+ - First pagination child used as template (cloned for each item)
241
515
  - Supports any number of items (1+)
242
- - Cleanup on destroy for Barba.js compatibility
243
- - Caches DOM queries for performance
516
+ - Single item hides navigation and pagination automatically
517
+ - Using Global/Button and Global/Clickable components is recommended
518
+ - Complete cleanup on destroy for Barba.js compatibility
519
+ - WeakMap caching prevents memory leaks
520
+ - Global drag handlers shared efficiently across instances and page transitions