jekyll-theme-zer0 0.16.0 → 0.17.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -10
- data/README.md +7 -7
- data/_data/authors.yml +2 -2
- data/_data/features.yml +676 -0
- data/_data/navigation/README.md +54 -0
- data/_data/navigation/about.yml +7 -5
- data/_data/navigation/docs.yml +77 -31
- data/_data/navigation/home.yml +4 -3
- data/_data/navigation/main.yml +16 -7
- data/_data/navigation/quickstart.yml +4 -2
- data/_includes/components/js-cdn.html +2 -2
- data/_includes/landing/landing-install-cards.html +8 -8
- data/_includes/landing/landing-quick-links.html +4 -4
- data/_includes/navigation/breadcrumbs.html +29 -6
- data/_includes/navigation/nav-tree.html +181 -0
- data/_includes/navigation/navbar.html +262 -9
- data/_includes/navigation/sidebar-left.html +22 -23
- data/_layouts/default.html +1 -1
- data/_layouts/landing.html +1 -1
- data/_layouts/notebook.html +4 -4
- data/_layouts/root.html +2 -2
- data/_sass/core/_nav-tree.scss +145 -0
- data/_sass/custom.scss +3 -0
- data/assets/js/modules/navigation/config.js +149 -0
- data/assets/js/modules/navigation/focus.js +189 -0
- data/assets/js/modules/navigation/gestures.js +179 -0
- data/assets/js/modules/navigation/index.js +227 -0
- data/assets/js/modules/navigation/keyboard.js +237 -0
- data/assets/js/modules/navigation/scroll-spy.js +219 -0
- data/assets/js/modules/navigation/sidebar-state.js +267 -0
- data/assets/js/modules/navigation/smooth-scroll.js +153 -0
- data/scripts/migrate-nav-modes.sh +146 -0
- metadata +15 -3
- data/assets/js/sidebar.js +0 -511
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ===================================================================
|
|
3
|
+
* NAVIGATION CONFIG - Shared Configuration for Navigation Modules
|
|
4
|
+
* ===================================================================
|
|
5
|
+
*
|
|
6
|
+
* File: config.js
|
|
7
|
+
* Path: assets/js/modules/navigation/config.js
|
|
8
|
+
* Purpose: Centralized configuration for all navigation modules
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { config } from './config.js';
|
|
12
|
+
* const toc = document.querySelector(config.selectors.toc);
|
|
13
|
+
*
|
|
14
|
+
* ===================================================================
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const config = {
|
|
18
|
+
// ===================================================================
|
|
19
|
+
// DOM SELECTORS
|
|
20
|
+
// ===================================================================
|
|
21
|
+
selectors: {
|
|
22
|
+
// Table of Contents
|
|
23
|
+
toc: '#TableOfContents',
|
|
24
|
+
tocLinks: '#TableOfContents a',
|
|
25
|
+
tocContainer: '.bd-toc .offcanvas-body',
|
|
26
|
+
|
|
27
|
+
// Sidebars
|
|
28
|
+
leftSidebar: '#bdSidebar',
|
|
29
|
+
rightSidebar: '#tocContents',
|
|
30
|
+
|
|
31
|
+
// Content areas
|
|
32
|
+
mainContent: '.bd-content',
|
|
33
|
+
|
|
34
|
+
// Navigation elements
|
|
35
|
+
navTree: '.nav-tree',
|
|
36
|
+
navTreeToggle: '[data-nav-toggle]',
|
|
37
|
+
|
|
38
|
+
// Offcanvas
|
|
39
|
+
offcanvas: '.offcanvas'
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// ===================================================================
|
|
43
|
+
// SCROLL SPY SETTINGS
|
|
44
|
+
// ===================================================================
|
|
45
|
+
scrollSpy: {
|
|
46
|
+
// Root margin for Intersection Observer
|
|
47
|
+
// Negative values account for fixed headers
|
|
48
|
+
rootMargin: '-80px 0px -80px 0px',
|
|
49
|
+
|
|
50
|
+
// Intersection threshold levels
|
|
51
|
+
threshold: [0, 0.25, 0.5, 0.75, 1]
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// ===================================================================
|
|
55
|
+
// SMOOTH SCROLL SETTINGS
|
|
56
|
+
// ===================================================================
|
|
57
|
+
smoothScroll: {
|
|
58
|
+
// Offset for fixed headers when scrolling to anchor
|
|
59
|
+
offset: 80,
|
|
60
|
+
|
|
61
|
+
// Scroll behavior
|
|
62
|
+
behavior: 'smooth'
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// ===================================================================
|
|
66
|
+
// KEYBOARD SHORTCUTS
|
|
67
|
+
// ===================================================================
|
|
68
|
+
keyboard: {
|
|
69
|
+
// Enable/disable keyboard shortcuts
|
|
70
|
+
enabled: true,
|
|
71
|
+
|
|
72
|
+
// Key mappings
|
|
73
|
+
keys: {
|
|
74
|
+
previousSection: '[',
|
|
75
|
+
nextSection: ']',
|
|
76
|
+
search: '/',
|
|
77
|
+
toggleSidebar: 'b',
|
|
78
|
+
toggleToc: 't'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// ===================================================================
|
|
83
|
+
// SWIPE GESTURES
|
|
84
|
+
// ===================================================================
|
|
85
|
+
gestures: {
|
|
86
|
+
// Enable/disable swipe gestures
|
|
87
|
+
enabled: true,
|
|
88
|
+
|
|
89
|
+
// Minimum distance (px) for swipe to register
|
|
90
|
+
threshold: 50,
|
|
91
|
+
|
|
92
|
+
// Edge detection zone (px) for sidebar swipes
|
|
93
|
+
edgeZone: 50
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// ===================================================================
|
|
97
|
+
// SIDEBAR STATE PERSISTENCE
|
|
98
|
+
// ===================================================================
|
|
99
|
+
state: {
|
|
100
|
+
// localStorage key prefix
|
|
101
|
+
storagePrefix: 'zer0-nav-',
|
|
102
|
+
|
|
103
|
+
// Keys for different state values
|
|
104
|
+
keys: {
|
|
105
|
+
expandedNodes: 'expanded-nodes',
|
|
106
|
+
sidebarOpen: 'sidebar-open',
|
|
107
|
+
tocOpen: 'toc-open'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// ===================================================================
|
|
112
|
+
// DEBOUNCE/THROTTLE TIMINGS
|
|
113
|
+
// ===================================================================
|
|
114
|
+
timing: {
|
|
115
|
+
debounceDelay: 100,
|
|
116
|
+
scrollDebounce: 50
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// ===================================================================
|
|
120
|
+
// BREAKPOINTS (match Bootstrap 5)
|
|
121
|
+
// ===================================================================
|
|
122
|
+
breakpoints: {
|
|
123
|
+
sm: 576,
|
|
124
|
+
md: 768,
|
|
125
|
+
lg: 992,
|
|
126
|
+
xl: 1200,
|
|
127
|
+
xxl: 1400
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if viewport is below a breakpoint
|
|
133
|
+
* @param {string} breakpoint - Breakpoint name (sm, md, lg, xl, xxl)
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
export function isBelowBreakpoint(breakpoint) {
|
|
137
|
+
return window.innerWidth < config.breakpoints[breakpoint];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if viewport is at or above a breakpoint
|
|
142
|
+
* @param {string} breakpoint - Breakpoint name (sm, md, lg, xl, xxl)
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
export function isAtOrAboveBreakpoint(breakpoint) {
|
|
146
|
+
return window.innerWidth >= config.breakpoints[breakpoint];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default config;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ===================================================================
|
|
3
|
+
* FOCUS MANAGER - Accessibility Enhancements
|
|
4
|
+
* ===================================================================
|
|
5
|
+
*
|
|
6
|
+
* File: focus.js
|
|
7
|
+
* Path: assets/js/modules/navigation/focus.js
|
|
8
|
+
* Purpose: Focus management for accessibility in navigation components
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Returns focus to trigger element when offcanvas closes
|
|
12
|
+
* - Manages focus trap in modal/offcanvas contexts
|
|
13
|
+
* - Provides focus ring styling hooks
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { FocusManager } from './focus.js';
|
|
17
|
+
* const focus = new FocusManager();
|
|
18
|
+
*
|
|
19
|
+
* ===================================================================
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { config } from './config.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get all elements safely
|
|
26
|
+
* @param {string} selector - CSS selector
|
|
27
|
+
* @returns {NodeList}
|
|
28
|
+
*/
|
|
29
|
+
function getElements(selector) {
|
|
30
|
+
try {
|
|
31
|
+
return document.querySelectorAll(selector);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class FocusManager {
|
|
38
|
+
constructor() {
|
|
39
|
+
this._boundHandleHidden = this._handleOffcanvasHidden.bind(this);
|
|
40
|
+
this._init();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initialize focus management
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
_init() {
|
|
48
|
+
// Handle focus return when offcanvas closes
|
|
49
|
+
const offcanvasElements = getElements(config.selectors.offcanvas);
|
|
50
|
+
|
|
51
|
+
offcanvasElements.forEach(offcanvas => {
|
|
52
|
+
offcanvas.addEventListener('hidden.bs.offcanvas', this._boundHandleHidden);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Add focus-visible polyfill behavior
|
|
56
|
+
this._setupFocusVisible();
|
|
57
|
+
|
|
58
|
+
console.log(`FocusManager: Initialized with ${offcanvasElements.length} offcanvas elements`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handle offcanvas hidden event - return focus to trigger
|
|
63
|
+
* @private
|
|
64
|
+
* @param {Event} event
|
|
65
|
+
*/
|
|
66
|
+
_handleOffcanvasHidden(event) {
|
|
67
|
+
const offcanvas = event.target;
|
|
68
|
+
this.returnFocus(offcanvas);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Return focus to the trigger element that opened an offcanvas
|
|
73
|
+
* @param {Element} offcanvas - The offcanvas element
|
|
74
|
+
*/
|
|
75
|
+
returnFocus(offcanvas) {
|
|
76
|
+
const triggerId = offcanvas.id;
|
|
77
|
+
|
|
78
|
+
// Find the trigger button that opened this offcanvas
|
|
79
|
+
const trigger = document.querySelector(
|
|
80
|
+
`[data-bs-target="#${triggerId}"], [href="#${triggerId}"]`
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (trigger) {
|
|
84
|
+
// Small delay to ensure offcanvas animation completes
|
|
85
|
+
requestAnimationFrame(() => {
|
|
86
|
+
trigger.focus();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Setup focus-visible behavior for keyboard users
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_setupFocusVisible() {
|
|
96
|
+
// Add class to body when user is navigating with keyboard
|
|
97
|
+
let hadKeyboardEvent = false;
|
|
98
|
+
|
|
99
|
+
document.addEventListener('keydown', (e) => {
|
|
100
|
+
if (e.key === 'Tab') {
|
|
101
|
+
hadKeyboardEvent = true;
|
|
102
|
+
document.body.classList.add('keyboard-nav');
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
document.addEventListener('mousedown', () => {
|
|
107
|
+
hadKeyboardEvent = false;
|
|
108
|
+
document.body.classList.remove('keyboard-nav');
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Focus the first focusable element within a container
|
|
114
|
+
* @param {Element} container
|
|
115
|
+
*/
|
|
116
|
+
focusFirst(container) {
|
|
117
|
+
const focusable = container.querySelector(
|
|
118
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
119
|
+
);
|
|
120
|
+
if (focusable) {
|
|
121
|
+
focusable.focus();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Focus the last focusable element within a container
|
|
127
|
+
* @param {Element} container
|
|
128
|
+
*/
|
|
129
|
+
focusLast(container) {
|
|
130
|
+
const focusables = container.querySelectorAll(
|
|
131
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
132
|
+
);
|
|
133
|
+
if (focusables.length > 0) {
|
|
134
|
+
focusables[focusables.length - 1].focus();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Trap focus within a container (for modals/offcanvas)
|
|
140
|
+
* @param {Element} container
|
|
141
|
+
* @returns {Function} Cleanup function to remove trap
|
|
142
|
+
*/
|
|
143
|
+
trapFocus(container) {
|
|
144
|
+
const focusableElements = container.querySelectorAll(
|
|
145
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const firstFocusable = focusableElements[0];
|
|
149
|
+
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
150
|
+
|
|
151
|
+
const handleKeydown = (e) => {
|
|
152
|
+
if (e.key !== 'Tab') return;
|
|
153
|
+
|
|
154
|
+
if (e.shiftKey) {
|
|
155
|
+
// Shift + Tab
|
|
156
|
+
if (document.activeElement === firstFocusable) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
lastFocusable.focus();
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
// Tab
|
|
162
|
+
if (document.activeElement === lastFocusable) {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
firstFocusable.focus();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
container.addEventListener('keydown', handleKeydown);
|
|
170
|
+
|
|
171
|
+
// Return cleanup function
|
|
172
|
+
return () => {
|
|
173
|
+
container.removeEventListener('keydown', handleKeydown);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Cleanup event listeners
|
|
179
|
+
*/
|
|
180
|
+
destroy() {
|
|
181
|
+
const offcanvasElements = getElements(config.selectors.offcanvas);
|
|
182
|
+
offcanvasElements.forEach(offcanvas => {
|
|
183
|
+
offcanvas.removeEventListener('hidden.bs.offcanvas', this._boundHandleHidden);
|
|
184
|
+
});
|
|
185
|
+
console.log('FocusManager: Destroyed');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default FocusManager;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ===================================================================
|
|
3
|
+
* SWIPE GESTURES - Mobile Offcanvas Control
|
|
4
|
+
* ===================================================================
|
|
5
|
+
*
|
|
6
|
+
* File: gestures.js
|
|
7
|
+
* Path: assets/js/modules/navigation/gestures.js
|
|
8
|
+
* Purpose: Touch gesture support for sidebar navigation on mobile
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Swipe from left edge to open left sidebar
|
|
12
|
+
* - Swipe from right edge to open TOC sidebar
|
|
13
|
+
* - Configurable threshold and edge zones
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { SwipeGestures } from './gestures.js';
|
|
17
|
+
* const gestures = new SwipeGestures();
|
|
18
|
+
*
|
|
19
|
+
* ===================================================================
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { config, isBelowBreakpoint } from './config.js';
|
|
23
|
+
|
|
24
|
+
export class SwipeGestures {
|
|
25
|
+
constructor() {
|
|
26
|
+
if (!config.gestures.enabled) {
|
|
27
|
+
console.log('SwipeGestures: Disabled via config');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.startX = 0;
|
|
32
|
+
this.startY = 0;
|
|
33
|
+
this.distX = 0;
|
|
34
|
+
this.distY = 0;
|
|
35
|
+
|
|
36
|
+
// Bound handlers for cleanup
|
|
37
|
+
this._boundTouchStart = this._handleTouchStart.bind(this);
|
|
38
|
+
this._boundTouchMove = this._handleTouchMove.bind(this);
|
|
39
|
+
this._boundTouchEnd = this._handleTouchEnd.bind(this);
|
|
40
|
+
|
|
41
|
+
this._init();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Initialize touch event listeners
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
_init() {
|
|
49
|
+
document.addEventListener('touchstart', this._boundTouchStart, { passive: true });
|
|
50
|
+
document.addEventListener('touchmove', this._boundTouchMove, { passive: true });
|
|
51
|
+
document.addEventListener('touchend', this._boundTouchEnd);
|
|
52
|
+
|
|
53
|
+
console.log('SwipeGestures: Initialized');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Handle touch start
|
|
58
|
+
* @private
|
|
59
|
+
* @param {TouchEvent} event
|
|
60
|
+
*/
|
|
61
|
+
_handleTouchStart(event) {
|
|
62
|
+
const touch = event.touches[0];
|
|
63
|
+
this.startX = touch.clientX;
|
|
64
|
+
this.startY = touch.clientY;
|
|
65
|
+
this.distX = 0;
|
|
66
|
+
this.distY = 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handle touch move
|
|
71
|
+
* @private
|
|
72
|
+
* @param {TouchEvent} event
|
|
73
|
+
*/
|
|
74
|
+
_handleTouchMove(event) {
|
|
75
|
+
if (!this.startX || !this.startY) return;
|
|
76
|
+
|
|
77
|
+
const touch = event.touches[0];
|
|
78
|
+
this.distX = touch.clientX - this.startX;
|
|
79
|
+
this.distY = touch.clientY - this.startY;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle touch end
|
|
84
|
+
* @private
|
|
85
|
+
* @param {TouchEvent} event
|
|
86
|
+
*/
|
|
87
|
+
_handleTouchEnd(event) {
|
|
88
|
+
const { threshold, edgeZone } = config.gestures;
|
|
89
|
+
|
|
90
|
+
// Check if swipe distance meets threshold
|
|
91
|
+
if (Math.abs(this.distX) < threshold) {
|
|
92
|
+
this._reset();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Only handle horizontal swipes (not vertical scroll)
|
|
97
|
+
if (Math.abs(this.distX) > Math.abs(this.distY)) {
|
|
98
|
+
if (this.distX > 0) {
|
|
99
|
+
this._handleSwipeRight();
|
|
100
|
+
} else {
|
|
101
|
+
this._handleSwipeLeft();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._reset();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Handle swipe right (open left sidebar)
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
_handleSwipeRight() {
|
|
113
|
+
const { edgeZone } = config.gestures;
|
|
114
|
+
|
|
115
|
+
// Only if swipe started from left edge
|
|
116
|
+
if (this.startX > edgeZone) return;
|
|
117
|
+
|
|
118
|
+
// Only on mobile
|
|
119
|
+
if (!isBelowBreakpoint('lg')) return;
|
|
120
|
+
|
|
121
|
+
const leftSidebar = document.querySelector(config.selectors.leftSidebar);
|
|
122
|
+
if (leftSidebar && typeof bootstrap !== 'undefined') {
|
|
123
|
+
const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(leftSidebar);
|
|
124
|
+
bsOffcanvas.show();
|
|
125
|
+
|
|
126
|
+
document.dispatchEvent(new CustomEvent('navigation:swipe', {
|
|
127
|
+
detail: { direction: 'right', sidebar: 'left' }
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle swipe left (open right/TOC sidebar)
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
_handleSwipeLeft() {
|
|
137
|
+
const { edgeZone } = config.gestures;
|
|
138
|
+
const windowWidth = window.innerWidth;
|
|
139
|
+
|
|
140
|
+
// Only if swipe started from right edge
|
|
141
|
+
if (this.startX < windowWidth - edgeZone) return;
|
|
142
|
+
|
|
143
|
+
// Only on mobile
|
|
144
|
+
if (!isBelowBreakpoint('lg')) return;
|
|
145
|
+
|
|
146
|
+
const rightSidebar = document.querySelector(config.selectors.rightSidebar);
|
|
147
|
+
if (rightSidebar && typeof bootstrap !== 'undefined') {
|
|
148
|
+
const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(rightSidebar);
|
|
149
|
+
bsOffcanvas.show();
|
|
150
|
+
|
|
151
|
+
document.dispatchEvent(new CustomEvent('navigation:swipe', {
|
|
152
|
+
detail: { direction: 'left', sidebar: 'toc' }
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Reset touch tracking state
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
_reset() {
|
|
162
|
+
this.startX = 0;
|
|
163
|
+
this.startY = 0;
|
|
164
|
+
this.distX = 0;
|
|
165
|
+
this.distY = 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Cleanup event listeners
|
|
170
|
+
*/
|
|
171
|
+
destroy() {
|
|
172
|
+
document.removeEventListener('touchstart', this._boundTouchStart);
|
|
173
|
+
document.removeEventListener('touchmove', this._boundTouchMove);
|
|
174
|
+
document.removeEventListener('touchend', this._boundTouchEnd);
|
|
175
|
+
console.log('SwipeGestures: Destroyed');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default SwipeGestures;
|