@wyxos/vibe 1.6.29 → 2.0.2

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 (43) hide show
  1. package/README.md +29 -287
  2. package/lib/index.cjs +1 -0
  3. package/lib/index.js +795 -1791
  4. package/lib/logo-dark.svg +36 -36
  5. package/lib/logo-light.svg +29 -29
  6. package/lib/logo.svg +32 -32
  7. package/lib/manifest.json +1 -1
  8. package/package.json +82 -96
  9. package/LICENSE +0 -21
  10. package/lib/vibe.css +0 -1
  11. package/lib/vite.svg +0 -1
  12. package/src/App.vue +0 -35
  13. package/src/Masonry.vue +0 -1030
  14. package/src/archive/App.vue +0 -96
  15. package/src/archive/InfiniteMansonry.spec.ts +0 -10
  16. package/src/archive/InfiniteMasonry.vue +0 -218
  17. package/src/assets/vue.svg +0 -1
  18. package/src/calculateLayout.ts +0 -194
  19. package/src/components/CodeTabs.vue +0 -158
  20. package/src/components/MasonryItem.vue +0 -499
  21. package/src/components/examples/BasicExample.vue +0 -46
  22. package/src/components/examples/CustomItemExample.vue +0 -87
  23. package/src/components/examples/HeaderFooterExample.vue +0 -79
  24. package/src/components/examples/ManualInitExample.vue +0 -78
  25. package/src/components/examples/SwipeModeExample.vue +0 -40
  26. package/src/createMasonryTransitions.ts +0 -176
  27. package/src/main.ts +0 -6
  28. package/src/masonryUtils.ts +0 -96
  29. package/src/pages.json +0 -36402
  30. package/src/router/index.ts +0 -20
  31. package/src/style.css +0 -32
  32. package/src/types.ts +0 -101
  33. package/src/useMasonryDimensions.ts +0 -59
  34. package/src/useMasonryItems.ts +0 -231
  35. package/src/useMasonryLayout.ts +0 -164
  36. package/src/useMasonryPagination.ts +0 -539
  37. package/src/useMasonryScroll.ts +0 -61
  38. package/src/useMasonryVirtualization.ts +0 -140
  39. package/src/useSwipeMode.ts +0 -233
  40. package/src/utils/errorHandler.ts +0 -8
  41. package/src/views/Examples.vue +0 -323
  42. package/src/views/Home.vue +0 -321
  43. package/toggle-link.mjs +0 -92
@@ -1,140 +0,0 @@
1
- import { ref, computed, nextTick, type Ref, type ComputedRef } from 'vue'
2
- import { calculateColumnHeights } from './masonryUtils'
3
-
4
- export interface UseMasonryVirtualizationOptions {
5
- masonry: Ref<any[]>
6
- container: Ref<HTMLElement | null>
7
- columns: Ref<number>
8
- virtualBufferPx: number
9
- loadThresholdPx: number
10
- handleScroll: (precomputedHeights?: number[]) => void
11
- }
12
-
13
- export function useMasonryVirtualization(options: UseMasonryVirtualizationOptions) {
14
- const {
15
- masonry,
16
- container,
17
- columns,
18
- virtualBufferPx,
19
- loadThresholdPx
20
- } = options
21
-
22
- // Use a ref for handleScroll so it can be updated after initialization
23
- const handleScrollRef = ref<(precomputedHeights?: number[]) => void>(options.handleScroll)
24
-
25
- // Virtualization viewport state
26
- const viewportTop = ref(0)
27
- const viewportHeight = ref(0)
28
- const VIRTUAL_BUFFER_PX = virtualBufferPx
29
-
30
- // Gate transitions during virtualization-only DOM churn
31
- const virtualizing = ref(false)
32
-
33
- // Scroll progress tracking
34
- const scrollProgress = ref<{ distanceToTrigger: number; isNearTrigger: boolean }>({
35
- distanceToTrigger: 0,
36
- isNearTrigger: false
37
- })
38
-
39
- // Visible window of items (virtualization)
40
- const visibleMasonry = computed(() => {
41
- const top = viewportTop.value - VIRTUAL_BUFFER_PX
42
- const bottom = viewportTop.value + viewportHeight.value + VIRTUAL_BUFFER_PX
43
- const items = masonry.value as any[]
44
- if (!items || items.length === 0) return [] as any[]
45
-
46
- // Filter items that have valid positions and are within viewport
47
- const visible = items.filter((it: any) => {
48
- // If item doesn't have position yet, include it (will be filtered once layout is calculated)
49
- if (typeof it.top !== 'number' || typeof it.columnHeight !== 'number') {
50
- return true // Include items without positions to avoid hiding them prematurely
51
- }
52
- const itemTop = it.top
53
- const itemBottom = it.top + it.columnHeight
54
- return itemBottom >= top && itemTop <= bottom
55
- })
56
-
57
- // Log if we're filtering out items (for debugging)
58
- if (import.meta.env.DEV && items.length > 0 && visible.length === 0 && viewportHeight.value > 0) {
59
- const itemsWithPositions = items.filter((it: any) =>
60
- typeof it.top === 'number' && typeof it.columnHeight === 'number'
61
- )
62
- }
63
-
64
- return visible
65
- })
66
-
67
- function updateScrollProgress(precomputedHeights?: number[]) {
68
- if (!container.value) return
69
-
70
- const { scrollTop, clientHeight } = container.value
71
- const visibleBottom = scrollTop + clientHeight
72
-
73
- const columnHeights = precomputedHeights ?? calculateColumnHeights(masonry.value as any, columns.value)
74
- const tallest = columnHeights.length ? Math.max(...columnHeights) : 0
75
- const threshold = typeof loadThresholdPx === 'number' ? loadThresholdPx : 200
76
- const triggerPoint = threshold >= 0
77
- ? Math.max(0, tallest - threshold)
78
- : Math.max(0, tallest + threshold)
79
-
80
- const distanceToTrigger = Math.max(0, triggerPoint - visibleBottom)
81
- const isNearTrigger = distanceToTrigger <= 100
82
-
83
- scrollProgress.value = {
84
- distanceToTrigger: Math.round(distanceToTrigger),
85
- isNearTrigger
86
- }
87
- }
88
-
89
- async function updateViewport() {
90
- if (container.value) {
91
- const scrollTop = container.value.scrollTop
92
- const clientHeight = container.value.clientHeight || window.innerHeight
93
- // Ensure viewportHeight is never 0 (fallback to window height if container height is 0)
94
- const safeClientHeight = clientHeight > 0 ? clientHeight : window.innerHeight
95
- viewportTop.value = scrollTop
96
- viewportHeight.value = safeClientHeight
97
- // Log when scroll handler runs (helpful for debugging viewport issues)
98
- if (import.meta.env.DEV) {
99
- console.log('[Masonry] scroll: viewport updated', {
100
- scrollTop,
101
- clientHeight: safeClientHeight,
102
- itemsCount: masonry.value.length,
103
- visibleItemsCount: visibleMasonry.value.length
104
- })
105
- }
106
- }
107
- // Gate transitions for virtualization-only DOM changes
108
- virtualizing.value = true
109
- await nextTick()
110
- await nextTick()
111
- virtualizing.value = false
112
-
113
- const heights = calculateColumnHeights(masonry.value as any, columns.value)
114
- handleScrollRef.value(heights as any)
115
- updateScrollProgress(heights)
116
- }
117
-
118
- function reset() {
119
- viewportTop.value = 0
120
- viewportHeight.value = 0
121
- virtualizing.value = false
122
- scrollProgress.value = {
123
- distanceToTrigger: 0,
124
- isNearTrigger: false
125
- }
126
- }
127
-
128
- return {
129
- viewportTop,
130
- viewportHeight,
131
- virtualizing,
132
- scrollProgress,
133
- visibleMasonry,
134
- updateScrollProgress,
135
- updateViewport,
136
- reset,
137
- handleScroll: handleScrollRef
138
- }
139
- }
140
-
@@ -1,233 +0,0 @@
1
- import { computed, ref, type Ref, type ComputedRef } from 'vue'
2
-
3
- export interface UseSwipeModeOptions {
4
- useSwipeMode: ComputedRef<boolean>
5
- masonry: Ref<any[]>
6
- isLoading: Ref<boolean>
7
- loadNext: () => Promise<any>
8
- loadPage: (page: any) => Promise<any>
9
- paginationHistory: Ref<any[]>
10
- }
11
-
12
- export interface UseSwipeModeReturn {
13
- // State
14
- currentSwipeIndex: Ref<number>
15
- swipeOffset: Ref<number>
16
- isDragging: Ref<boolean>
17
- swipeContainer: Ref<HTMLElement | null>
18
-
19
- // Computed
20
- currentItem: ComputedRef<any | null>
21
- nextItem: ComputedRef<any | null>
22
- previousItem: ComputedRef<any | null>
23
-
24
- // Functions
25
- handleTouchStart: (e: TouchEvent) => void
26
- handleTouchMove: (e: TouchEvent) => void
27
- handleTouchEnd: (e: TouchEvent) => void
28
- handleMouseDown: (e: MouseEvent) => void
29
- handleMouseMove: (e: MouseEvent) => void
30
- handleMouseUp: (e: MouseEvent) => void
31
- goToNextItem: () => void
32
- goToPreviousItem: () => void
33
- snapToCurrentItem: () => void
34
- handleWindowResize: () => void
35
- reset: () => void
36
- }
37
-
38
- export function useSwipeMode(options: UseSwipeModeOptions): UseSwipeModeReturn {
39
- const { useSwipeMode, masonry, isLoading, loadNext, loadPage, paginationHistory } = options
40
-
41
- // Swipe mode state
42
- const currentSwipeIndex = ref<number>(0)
43
- const swipeOffset = ref<number>(0)
44
- const isDragging = ref<boolean>(false)
45
- const dragStartY = ref<number>(0)
46
- const dragStartOffset = ref<number>(0)
47
- const swipeContainer = ref<HTMLElement | null>(null)
48
-
49
- // Get current item index for swipe mode
50
- const currentItem = computed(() => {
51
- if (!useSwipeMode.value || masonry.value.length === 0) return null
52
- const index = Math.max(0, Math.min(currentSwipeIndex.value, masonry.value.length - 1))
53
- return masonry.value[index] || null
54
- })
55
-
56
- // Get next/previous items for preloading in swipe mode
57
- const nextItem = computed(() => {
58
- if (!useSwipeMode.value || !currentItem.value) return null
59
- const nextIndex = currentSwipeIndex.value + 1
60
- if (nextIndex >= masonry.value.length) return null
61
- return masonry.value[nextIndex] || null
62
- })
63
-
64
- const previousItem = computed(() => {
65
- if (!useSwipeMode.value || !currentItem.value) return null
66
- const prevIndex = currentSwipeIndex.value - 1
67
- if (prevIndex < 0) return null
68
- return masonry.value[prevIndex] || null
69
- })
70
-
71
- function snapToCurrentItem() {
72
- if (!swipeContainer.value) return
73
-
74
- // Use container height for swipe mode instead of window height
75
- const viewportHeight = swipeContainer.value.clientHeight
76
- swipeOffset.value = -currentSwipeIndex.value * viewportHeight
77
- }
78
-
79
- function goToNextItem() {
80
- if (!nextItem.value) {
81
- // Try to load next page
82
- loadNext()
83
- return
84
- }
85
-
86
- currentSwipeIndex.value++
87
- snapToCurrentItem()
88
-
89
- // Preload next item if we're near the end
90
- if (currentSwipeIndex.value >= masonry.value.length - 5) {
91
- loadNext()
92
- }
93
- }
94
-
95
- function goToPreviousItem() {
96
- if (!previousItem.value) return
97
-
98
- currentSwipeIndex.value--
99
- snapToCurrentItem()
100
- }
101
-
102
- // Swipe gesture handlers
103
- function handleTouchStart(e: TouchEvent) {
104
- if (!useSwipeMode.value) return
105
- isDragging.value = true
106
- dragStartY.value = e.touches[0].clientY
107
- dragStartOffset.value = swipeOffset.value
108
- e.preventDefault()
109
- }
110
-
111
- function handleTouchMove(e: TouchEvent) {
112
- if (!useSwipeMode.value || !isDragging.value) return
113
- const deltaY = e.touches[0].clientY - dragStartY.value
114
- swipeOffset.value = dragStartOffset.value + deltaY
115
- e.preventDefault()
116
- }
117
-
118
- function handleTouchEnd(e: TouchEvent) {
119
- if (!useSwipeMode.value || !isDragging.value) return
120
- isDragging.value = false
121
-
122
- const deltaY = swipeOffset.value - dragStartOffset.value
123
- const threshold = 100 // Minimum swipe distance to trigger navigation
124
-
125
- if (Math.abs(deltaY) > threshold) {
126
- if (deltaY > 0 && previousItem.value) {
127
- // Swipe down - go to previous
128
- goToPreviousItem()
129
- } else if (deltaY < 0 && nextItem.value) {
130
- // Swipe up - go to next
131
- goToNextItem()
132
- } else {
133
- // Snap back
134
- snapToCurrentItem()
135
- }
136
- } else {
137
- // Snap back if swipe wasn't far enough
138
- snapToCurrentItem()
139
- }
140
-
141
- e.preventDefault()
142
- }
143
-
144
- // Mouse drag handlers for desktop testing
145
- function handleMouseDown(e: MouseEvent) {
146
- if (!useSwipeMode.value) return
147
- isDragging.value = true
148
- dragStartY.value = e.clientY
149
- dragStartOffset.value = swipeOffset.value
150
- e.preventDefault()
151
- }
152
-
153
- function handleMouseMove(e: MouseEvent) {
154
- if (!useSwipeMode.value || !isDragging.value) return
155
- const deltaY = e.clientY - dragStartY.value
156
- swipeOffset.value = dragStartOffset.value + deltaY
157
- e.preventDefault()
158
- }
159
-
160
- function handleMouseUp(e: MouseEvent) {
161
- if (!useSwipeMode.value || !isDragging.value) return
162
- isDragging.value = false
163
-
164
- const deltaY = swipeOffset.value - dragStartOffset.value
165
- const threshold = 100
166
-
167
- if (Math.abs(deltaY) > threshold) {
168
- if (deltaY > 0 && previousItem.value) {
169
- goToPreviousItem()
170
- } else if (deltaY < 0 && nextItem.value) {
171
- goToNextItem()
172
- } else {
173
- snapToCurrentItem()
174
- }
175
- } else {
176
- snapToCurrentItem()
177
- }
178
-
179
- e.preventDefault()
180
- }
181
-
182
- // Watch for container/window resize to update swipe mode
183
- function handleWindowResize() {
184
- // If switching from swipe to masonry, reset swipe state
185
- if (!useSwipeMode.value && currentSwipeIndex.value > 0) {
186
- currentSwipeIndex.value = 0
187
- swipeOffset.value = 0
188
- }
189
-
190
- // If switching to swipe mode, ensure we have items loaded
191
- if (useSwipeMode.value && masonry.value.length === 0 && !isLoading.value) {
192
- loadPage(paginationHistory.value[0] as any)
193
- }
194
-
195
- // Re-snap to current item on resize to adjust offset
196
- if (useSwipeMode.value) {
197
- snapToCurrentItem()
198
- }
199
- }
200
-
201
- function reset() {
202
- currentSwipeIndex.value = 0
203
- swipeOffset.value = 0
204
- isDragging.value = false
205
- }
206
-
207
- return {
208
- // State
209
- currentSwipeIndex,
210
- swipeOffset,
211
- isDragging,
212
- swipeContainer,
213
-
214
- // Computed
215
- currentItem,
216
- nextItem,
217
- previousItem,
218
-
219
- // Functions
220
- handleTouchStart,
221
- handleTouchMove,
222
- handleTouchEnd,
223
- handleMouseDown,
224
- handleMouseMove,
225
- handleMouseUp,
226
- goToNextItem,
227
- goToPreviousItem,
228
- snapToCurrentItem,
229
- handleWindowResize,
230
- reset
231
- }
232
- }
233
-
@@ -1,8 +0,0 @@
1
- /**
2
- * Utility function to normalize errors to Error instances
3
- * Ensures consistent error handling throughout the application
4
- */
5
- export function normalizeError(error: unknown): Error {
6
- return error instanceof Error ? error : new Error(String(error))
7
- }
8
-
@@ -1,323 +0,0 @@
1
- <template>
2
- <div class="min-h-screen bg-slate-50 pt-20">
3
- <div class="max-w-7xl mx-auto px-4 py-8">
4
- <div class="flex gap-8">
5
- <!-- Side Menu -->
6
- <aside class="hidden lg:block w-64 flex-shrink-0 sticky top-24 h-fit">
7
- <nav class="bg-white rounded-lg shadow-sm p-4 border border-slate-200">
8
- <h3 class="text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">Examples</h3>
9
- <ul class="space-y-2">
10
- <li v-for="example in examples" :key="example.id">
11
- <a :href="`#${example.id}`" @click.prevent="scrollTo(example.id)"
12
- class="block px-3 py-2 text-sm rounded-md transition-colors" :class="activeSection === example.id
13
- ? 'bg-blue-50 text-blue-600 font-medium'
14
- : 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'">
15
- {{ example.title }}
16
- </a>
17
- </li>
18
- </ul>
19
- </nav>
20
- </aside>
21
-
22
- <!-- Main Content -->
23
- <main class="flex-1 min-w-0">
24
- <div class="space-y-16">
25
- <!-- Basic Example -->
26
- <section id="basic" ref="basicRef" class="scroll-mt-24">
27
- <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
28
- <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
29
- <h2 class="text-2xl font-bold text-slate-800">Basic Usage</h2>
30
- <p class="text-slate-600 mt-1">Simple masonry grid with default MasonryItem component</p>
31
- </div>
32
-
33
- <!-- Live Example -->
34
- <div class="p-6 bg-slate-50">
35
- <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
36
- <BasicExample />
37
- </div>
38
- </div>
39
-
40
- <!-- Code Tabs -->
41
- <div class="px-6 pb-6">
42
- <CodeTabs vue='<script setup>
43
- import { ref } from "vue";
44
- import { Masonry } from "@wyxos/vibe";
45
-
46
- const items = ref([]);
47
-
48
- async function getPage(page) {
49
- const response = await fetch(`/api/items?page=${page}`);
50
- const data = await response.json();
51
- return {
52
- items: data.items,
53
- nextPage: page + 1
54
- };
55
- }
56
- </script>
57
-
58
- <template>
59
- <Masonry
60
- v-model:items="items"
61
- :get-page="getPage"
62
- :load-at-page="1"
63
- />
64
- </template>' />
65
- </div>
66
- </div>
67
- </section>
68
-
69
- <!-- Manual Init Example -->
70
- <section id="manual-init" ref="manualInitRef" class="scroll-mt-24">
71
- <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
72
- <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
73
- <h2 class="text-2xl font-bold text-slate-800">Manual Init CTA</h2>
74
- <p class="text-slate-600 mt-1">Delay initialization until the user clicks a call-to-action</p>
75
- </div>
76
-
77
- <!-- Live Example -->
78
- <div class="p-6 bg-slate-50">
79
- <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
80
- <ManualInitExample />
81
- </div>
82
- </div>
83
-
84
- <!-- Code Tabs -->
85
- <div class="px-6 pb-6">
86
- <CodeTabs vue='<script setup>
87
- import { ref } from "vue";
88
- import { Masonry } from "@wyxos/vibe";
89
-
90
- const items = ref([]);
91
- const masonryRef = ref(null);
92
-
93
- async function getPage(page) {
94
- const response = await fetch(`/api/items?page=${page}`);
95
- const data = await response.json();
96
- return {
97
- items: data.items,
98
- nextPage: page + 1
99
- };
100
- }
101
-
102
- function handleInit() {
103
- masonryRef.value?.loadPage(1);
104
- }
105
- </script>
106
-
107
- <template>
108
- <button @click="handleInit">Load gallery</button>
109
-
110
- <Masonry
111
- ref="masonryRef"
112
- v-model:items="items"
113
- :get-page="getPage"
114
- :load-at-page="1"
115
- init="manual"
116
- />
117
- </template>' />
118
- </div>
119
- </div>
120
- </section>
121
-
122
- <!-- Custom Masonry Item Example -->
123
- <section id="custom-item" ref="customItemRef" class="scroll-mt-24">
124
- <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
125
- <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
126
- <h2 class="text-2xl font-bold text-slate-800">Custom Masonry Item</h2>
127
- <p class="text-slate-600 mt-1">Customize item rendering with scoped slots</p>
128
- </div>
129
-
130
- <!-- Live Example -->
131
- <div class="p-6 bg-slate-50">
132
- <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
133
- <CustomItemExample />
134
- </div>
135
- </div>
136
-
137
- <!-- Code Tabs -->
138
- <div class="px-6 pb-6">
139
- <CodeTabs vue='<script setup>
140
- import { ref } from "vue";
141
- import { Masonry } from "@wyxos/vibe";
142
-
143
- const items = ref([]);
144
-
145
- async function getPage(page) {
146
- const response = await fetch(`/api/items?page=${page}`);
147
- const data = await response.json();
148
- return {
149
- items: data.items,
150
- nextPage: page + 1
151
- };
152
- }
153
- </script>
154
-
155
- <template>
156
- <Masonry
157
- v-model:items="items"
158
- :get-page="getPage"
159
- :load-at-page="1"
160
- >
161
- <template #item="{ item, remove }">
162
- <div class="custom-card">
163
- <img :src="item.src" :alt="item.title" />
164
- <div class="overlay">
165
- <h3>{{ item.title }}</h3>
166
- <button @click="remove">Remove</button>
167
- </div>
168
- </div>
169
- </template>
170
- </Masonry>
171
- </template>' />
172
- </div>
173
- -->
174
- </div>
175
- </section>
176
-
177
- <!-- Header & Footer Example -->
178
- <section id="header-footer" ref="headerFooterRef" class="scroll-mt-24">
179
- <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
180
- <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
181
- <h2 class="text-2xl font-bold text-slate-800">Header & Footer</h2>
182
- <p class="text-slate-600 mt-1">
183
- Use `layout.header` and `layout.footer` with slots to add per-item UI like badges, titles and
184
- actions.
185
- </p>
186
- </div>
187
-
188
- <!-- Live Example -->
189
- <div class="p-6 bg-slate-50">
190
- <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
191
- <HeaderFooterExample />
192
- </div>
193
- </div>
194
-
195
- <!-- Code Tabs omitted for this example to keep docs build-safe.
196
- See the Header & Footer example component for full code. -->
197
- </div>
198
- </section>
199
-
200
- <!-- Swipe Mode Example -->
201
- <section id="swipe-mode" ref="swipeModeRef" class="scroll-mt-24">
202
- <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
203
- <div class="px-6 py-4 border-b border-slate-200 bg-slate-50">
204
- <h2 class="text-2xl font-bold text-slate-800">Swipe Mode</h2>
205
- <p class="text-slate-600 mt-1">Vertical swipe feed for mobile devices</p>
206
- </div>
207
-
208
- <!-- Live Example -->
209
- <div class="p-6 bg-slate-50">
210
- <div class="bg-white rounded-lg border border-slate-200 p-4" style="height: 500px;">
211
- <SwipeModeExample />
212
- </div>
213
- </div>
214
-
215
- <!-- Code Tabs -->
216
- <div class="px-6 pb-6">
217
- <CodeTabs vue='<script setup>
218
- import { ref } from "vue";
219
- import { Masonry } from "@wyxos/vibe";
220
-
221
- const items = ref([]);
222
-
223
- async function getPage(page) {
224
- const response = await fetch(`/api/items?page=${page}`);
225
- const data = await response.json();
226
- return {
227
- items: data.items,
228
- nextPage: page + 1
229
- };
230
- }
231
- </script>
232
-
233
- <template>
234
- <!-- Auto mode: switches to swipe on mobile -->
235
- <Masonry
236
- v-model:items="items"
237
- :get-page="getPage"
238
- layout-mode="auto"
239
- :mobile-breakpoint="768"
240
- />
241
-
242
- <!-- Force swipe mode on all devices -->
243
- <Masonry
244
- v-model:items="items"
245
- :get-page="getPage"
246
- layout-mode="swipe"
247
- />
248
- </template>' />
249
- </div>
250
- </div>
251
- </section>
252
- </div>
253
- </main>
254
- </div>
255
- </div>
256
- </div>
257
- </template>
258
-
259
- <script setup lang="ts">
260
- // @ts-nocheck - documentation page with inline string code samples that Vue/TS can't statically analyze cleanly
261
- import { ref, onMounted, onUnmounted } from 'vue'
262
- import CodeTabs from '../components/CodeTabs.vue'
263
- import BasicExample from '../components/examples/BasicExample.vue'
264
- import ManualInitExample from '../components/examples/ManualInitExample.vue'
265
- import CustomItemExample from '../components/examples/CustomItemExample.vue'
266
- import SwipeModeExample from '../components/examples/SwipeModeExample.vue'
267
- import HeaderFooterExample from '../components/examples/HeaderFooterExample.vue'
268
-
269
- const examples = [
270
- { id: 'basic', title: 'Basic Usage' },
271
- { id: 'manual-init', title: 'Manual Init CTA' },
272
- { id: 'custom-item', title: 'Custom Masonry Item' },
273
- { id: 'header-footer', title: 'Header & Footer' },
274
- { id: 'swipe-mode', title: 'Swipe Mode' }
275
- ]
276
-
277
- const activeSection = ref('basic')
278
- const basicRef = ref<HTMLElement | null>(null)
279
- const manualInitRef = ref<HTMLElement | null>(null)
280
- const customItemRef = ref<HTMLElement | null>(null)
281
- const headerFooterRef = ref<HTMLElement | null>(null)
282
- const swipeModeRef = ref<HTMLElement | null>(null)
283
-
284
- function scrollTo(id: string) {
285
- const element = document.getElementById(id)
286
- if (element) {
287
- element.scrollIntoView({ behavior: 'smooth', block: 'start' })
288
- activeSection.value = id
289
- }
290
- }
291
-
292
- function updateActiveSection() {
293
- const sections = [
294
- { id: 'basic', ref: basicRef },
295
- { id: 'manual-init', ref: manualInitRef },
296
- { id: 'custom-item', ref: customItemRef },
297
- { id: 'header-footer', ref: headerFooterRef },
298
- { id: 'swipe-mode', ref: swipeModeRef }
299
- ]
300
-
301
- const scrollPosition = window.scrollY + 100
302
-
303
- for (let i = sections.length - 1; i >= 0; i--) {
304
- const section = sections[i]
305
- if (section.ref.value) {
306
- const top = section.ref.value.offsetTop
307
- if (scrollPosition >= top) {
308
- activeSection.value = section.id
309
- break
310
- }
311
- }
312
- }
313
- }
314
-
315
- onMounted(() => {
316
- window.addEventListener('scroll', updateActiveSection)
317
- updateActiveSection()
318
- })
319
-
320
- onUnmounted(() => {
321
- window.removeEventListener('scroll', updateActiveSection)
322
- })
323
- </script>