@hortonstudio/main 1.7.1 → 1.7.3

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.
@@ -61,7 +61,9 @@
61
61
  "Bash(npx prettier:*)",
62
62
  "Bash(npx prettier:*)",
63
63
  "Bash(npx prettier:*)",
64
- "Bash(git stash:*)"
64
+ "Bash(git stash:*)",
65
+ "Bash(npm uninstall:*)",
66
+ "Read(//Users/devan/.claude/commands/**)"
65
67
  ],
66
68
  "deny": []
67
69
  }
@@ -453,12 +453,6 @@ function setupMenuButton() {
453
453
 
454
454
  document.body.classList.add("u-overflow-hidden");
455
455
 
456
- document
457
- .querySelectorAll('[data-hs-nav="modal-blur"]')
458
- .forEach((element) => {
459
- element.classList.add('is-active');
460
- });
461
-
462
456
  menuButton.setAttribute("aria-expanded", "true");
463
457
  menuButton.setAttribute("aria-label", "Close navigation menu");
464
458
 
@@ -487,12 +481,6 @@ function setupMenuButton() {
487
481
 
488
482
  document.body.classList.remove("u-overflow-hidden");
489
483
 
490
- document
491
- .querySelectorAll('[data-hs-nav="modal-blur"]')
492
- .forEach((element) => {
493
- element.classList.remove('is-active');
494
- });
495
-
496
484
  if (menu.contains(document.activeElement)) {
497
485
  menuButton.focus();
498
486
  }
@@ -551,12 +539,6 @@ function setupMenuDisplayObserver() {
551
539
 
552
540
  // Toggle modal effects only when menu is visible AND menu is open
553
541
  document.body.classList.toggle("u-overflow-hidden", shouldShowModal);
554
-
555
- document
556
- .querySelectorAll('[data-hs-nav="modal-blur"]')
557
- .forEach((element) => {
558
- element.classList.toggle('is-active', shouldShowModal);
559
- });
560
542
  }
561
543
 
562
544
  const displayObserver = new ResizeObserver(handleDisplayChange);
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // Version:1.7.1
1
+ // Version:1.7.3
2
2
  const API_NAME = "hsmain";
3
3
 
4
4
  const initializeHsMain = async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/utils/slider.js CHANGED
@@ -56,7 +56,8 @@ const initPaginationInstance = (originalList) => {
56
56
  nextBtn: container.querySelector('[data-hs-slider="pagination-next"]'),
57
57
  prevBtn: container.querySelector('[data-hs-slider="pagination-previous"]'),
58
58
  counter: container.querySelector('[data-hs-slider="pagination-counter"]'),
59
- controls: container.querySelector('[data-hs-slider="pagination-controls"]')
59
+ controls: container.querySelector('[data-hs-slider="pagination-controls"]'),
60
+ dotsWrap: container.querySelector('[data-hs-slider="dots-wrap"]')
60
61
  };
61
62
 
62
63
  if (!elements.nextBtn || !elements.prevBtn) return;
@@ -69,16 +70,119 @@ const initPaginationInstance = (originalList) => {
69
70
  }
70
71
 
71
72
  const config = originalList.getAttribute('data-hs-config') || '';
72
- const desktopItems = parseInt(config.match(/show-(\d+)(?!-mobile)/)?.[1]) || 6;
73
- const mobileItems = parseInt(config.match(/show-(\d+)-mobile/)?.[1]) || desktopItems;
73
+ const configOptions = config.split(', ').map(opt => opt.trim());
74
+ const isInfiniteMode = configOptions.includes('infinite');
74
75
 
75
- const isMobileLayout = () => getComputedStyle(originalList).display === 'flex';
76
+ const desktopItems = !isInfiniteMode ? (parseInt(config.match(/show-(\d+)(?!-mobile)/)?.[1]) || 6) : 0;
77
+ const mobileItems = !isInfiniteMode ? (parseInt(config.match(/show-(\d+)-mobile/)?.[1]) || desktopItems) : 0;
78
+
79
+
80
+ // Early exit for infinite mode - disable pagination completely
81
+ if (isInfiniteMode) {
82
+ if (elements.controls) {
83
+ elements.controls.style.display = 'none';
84
+ elements.controls.setAttribute('aria-hidden', 'true');
85
+ }
86
+ if (elements.dotsWrap) {
87
+ elements.dotsWrap.style.display = 'none';
88
+ elements.dotsWrap.setAttribute('aria-hidden', 'true');
89
+ }
90
+ return;
91
+ }
92
+
93
+ const isMobileLayout = () => {
94
+ const breakpoint = getComputedStyle(originalList).getPropertyValue('--data-hs-break').trim().replace(/"/g, '');
95
+ return breakpoint === 'mobile';
96
+ };
76
97
  const allItems = Array.from(originalList.children);
77
98
  const totalItems = allItems.length;
78
99
  if (!totalItems) return;
79
100
 
80
101
  const state = { totalPages: 1, currentIndex: 1, currentPage: 1, isAnimating: false, itemsPerPage: desktopItems };
81
102
  let wrapperChildren = [];
103
+ let dotTemplates = { active: null, inactive: null };
104
+
105
+ const initializeDots = () => {
106
+ if (!elements.dotsWrap) {
107
+ return;
108
+ }
109
+
110
+ // Find template dots
111
+ const existingDots = Array.from(elements.dotsWrap.children);
112
+
113
+ // Identify active and inactive templates
114
+ existingDots.forEach((dot) => {
115
+ if (dot.classList.contains('is-active')) {
116
+ dotTemplates.active = dot.cloneNode(true);
117
+ } else {
118
+ dotTemplates.inactive = dot.cloneNode(true);
119
+ }
120
+ });
121
+
122
+ // Clear existing dots
123
+ elements.dotsWrap.innerHTML = '';
124
+
125
+ // Handle single page or no pages case
126
+ if (state.totalPages <= 1) {
127
+ elements.dotsWrap.style.display = 'none';
128
+ elements.dotsWrap.setAttribute('aria-hidden', 'true');
129
+ return;
130
+ }
131
+
132
+ // Show dots container
133
+ elements.dotsWrap.style.display = '';
134
+ elements.dotsWrap.removeAttribute('aria-hidden');
135
+
136
+ // Create dots for each page
137
+ for (let i = 1; i <= state.totalPages; i++) {
138
+ const template = i === 1 ? dotTemplates.active : dotTemplates.inactive;
139
+ if (template) {
140
+ const dot = template.cloneNode(true);
141
+ dot.setAttribute('data-page', i);
142
+ dot.setAttribute('aria-label', `Go to page ${i}`);
143
+ dot.style.cursor = 'pointer';
144
+
145
+ // Add click handler for dot navigation
146
+ dot.addEventListener('click', (e) => {
147
+ e.preventDefault();
148
+ const targetPage = parseInt(dot.getAttribute('data-page'));
149
+ navigateToPage(targetPage);
150
+ });
151
+
152
+ // Add keyboard support
153
+ dot.setAttribute('tabindex', '0');
154
+ dot.setAttribute('role', 'button');
155
+ dot.addEventListener('keydown', (e) => {
156
+ if (e.key === 'Enter' || e.key === ' ') {
157
+ e.preventDefault();
158
+ const targetPage = parseInt(dot.getAttribute('data-page'));
159
+ navigateToPage(targetPage);
160
+ }
161
+ });
162
+
163
+ elements.dotsWrap.appendChild(dot);
164
+ }
165
+ }
166
+ };
167
+
168
+ const updateActiveDot = () => {
169
+ if (!elements.dotsWrap || state.totalPages <= 1) return;
170
+
171
+ const dots = Array.from(elements.dotsWrap.children);
172
+
173
+ dots.forEach((dot, index) => {
174
+ const pageNumber = index + 1;
175
+ const isActive = pageNumber === state.currentPage;
176
+
177
+ if (isActive) {
178
+ dot.classList.add('is-active');
179
+ dot.setAttribute('aria-current', 'page');
180
+ } else {
181
+ dot.classList.remove('is-active');
182
+ dot.removeAttribute('aria-current');
183
+ }
184
+ });
185
+ };
82
186
 
83
187
  const initializePagination = (forceItemsPerPage = null) => {
84
188
  const currentIsMobile = isMobileLayout();
@@ -97,6 +201,10 @@ const initPaginationInstance = (originalList) => {
97
201
  elements.controls.style.display = 'none';
98
202
  elements.controls.setAttribute('aria-hidden', 'true');
99
203
  }
204
+ if (elements.dotsWrap) {
205
+ elements.dotsWrap.style.display = 'none';
206
+ elements.dotsWrap.setAttribute('aria-hidden', 'true');
207
+ }
100
208
  Object.assign(state, { totalPages: 1, currentIndex: 1, currentPage: 1, isAnimating: false });
101
209
  wrapper.style.cssText = `transform: translateX(0%); height: ${originalList.offsetHeight}px;`;
102
210
  wrapperChildren = [originalList];
@@ -107,6 +215,10 @@ const initPaginationInstance = (originalList) => {
107
215
  elements.controls.style.display = '';
108
216
  elements.controls.removeAttribute('aria-hidden');
109
217
  }
218
+ if (elements.dotsWrap) {
219
+ elements.dotsWrap.style.display = '';
220
+ elements.dotsWrap.removeAttribute('aria-hidden');
221
+ }
110
222
 
111
223
  const pageLists = Array.from({ length: state.totalPages }, (_, page) => {
112
224
  const pageList = originalList.cloneNode(false);
@@ -130,6 +242,7 @@ const initPaginationInstance = (originalList) => {
130
242
  updateCounter();
131
243
  updateHeight();
132
244
  manageFocus();
245
+ initializeDots();
133
246
  return state.totalPages;
134
247
  };
135
248
  const liveRegion = document.createElement('div');
@@ -170,6 +283,46 @@ const initPaginationInstance = (originalList) => {
170
283
  resizeObserver.observe(wrapper);
171
284
  initializePagination();
172
285
 
286
+ const navigateToPage = (targetPage) => {
287
+ if (state.isAnimating || state.totalPages <= 1 || targetPage === state.currentPage) return;
288
+ state.isAnimating = true;
289
+
290
+ // For multi-page jumps, we still use the same transition but update the index directly
291
+ state.currentIndex = targetPage;
292
+ state.currentPage = targetPage;
293
+
294
+ updateCounter();
295
+ announcePageChange();
296
+ updateHeight();
297
+ updateActiveDot();
298
+
299
+ if (isMobileLayout() && elements.controls) {
300
+ setTimeout(() => {
301
+ const controlsBottom = elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
302
+ const clearance = 5 * 16; // 5rem in pixels
303
+ const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
304
+ window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
305
+ }, 50);
306
+ }
307
+
308
+ wrapper.style.transition = 'transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
309
+ wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
310
+
311
+ const handleTransitionEnd = () => {
312
+ wrapper.removeEventListener('transitionend', handleTransitionEnd);
313
+ wrapper.style.transition = '';
314
+
315
+ updateCounter();
316
+ announcePageChange();
317
+ updateHeight();
318
+ manageFocus();
319
+ updateActiveDot();
320
+ state.isAnimating = false;
321
+ };
322
+
323
+ wrapper.addEventListener('transitionend', handleTransitionEnd);
324
+ };
325
+
173
326
  const navigate = (direction) => {
174
327
  if (state.isAnimating || state.totalPages <= 1) return;
175
328
  state.isAnimating = true;
@@ -181,6 +334,16 @@ const initPaginationInstance = (originalList) => {
181
334
  updateCounter();
182
335
  announcePageChange();
183
336
  updateHeight();
337
+ updateActiveDot();
338
+
339
+ if (isMobileLayout() && elements.controls) {
340
+ setTimeout(() => {
341
+ const controlsBottom = elements.controls.getBoundingClientRect().bottom + window.pageYOffset;
342
+ const clearance = 5 * 16; // 5rem in pixels
343
+ const targetScrollPosition = controlsBottom - window.innerHeight + clearance;
344
+ window.scrollTo({ top: targetScrollPosition, behavior: 'smooth' });
345
+ }, 50);
346
+ }
184
347
 
185
348
  wrapper.style.transition = 'transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
186
349
  wrapper.style.transform = `translateX(${-state.currentIndex * 100}%)`;
@@ -203,6 +366,7 @@ const initPaginationInstance = (originalList) => {
203
366
  announcePageChange();
204
367
  updateHeight();
205
368
  manageFocus();
369
+ updateActiveDot();
206
370
  state.isAnimating = false;
207
371
  };
208
372