@wyxos/vibe 1.6.25 → 1.6.27
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.
- package/README.md +287 -254
- package/lib/index.js +1201 -1115
- package/lib/vibe.css +1 -1
- package/package.json +1 -1
- package/src/Masonry.vue +159 -188
- package/src/components/MasonryItem.vue +501 -434
- package/src/components/examples/BasicExample.vue +2 -1
- package/src/components/examples/CustomItemExample.vue +2 -1
- package/src/components/examples/HeaderFooterExample.vue +79 -78
- package/src/components/examples/ManualInitExample.vue +78 -0
- package/src/components/examples/SwipeModeExample.vue +2 -1
- package/src/{useMasonryTransitions.ts → createMasonryTransitions.ts} +6 -6
- package/src/useMasonryItems.ts +234 -218
- package/src/useMasonryLayout.ts +4 -0
- package/src/useMasonryPagination.ts +465 -342
- package/src/views/Examples.vue +80 -32
- package/src/views/Home.vue +321 -321
|
@@ -1,78 +1,79 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Masonry
|
|
3
|
-
v-model:items="items"
|
|
4
|
-
:get-
|
|
5
|
-
:load-at-page="1"
|
|
6
|
-
:layout="layout"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import
|
|
42
|
-
import
|
|
43
|
-
import
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<Masonry
|
|
3
|
+
v-model:items="items"
|
|
4
|
+
:get-page="getPage"
|
|
5
|
+
:load-at-page="1"
|
|
6
|
+
:layout="layout"
|
|
7
|
+
init="auto"
|
|
8
|
+
>
|
|
9
|
+
<template #item-header="{ item }">
|
|
10
|
+
<div class="h-full flex items-center justify-between px-3">
|
|
11
|
+
<div class="flex items-center gap-2">
|
|
12
|
+
<div class="w-6 h-6 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center shadow-sm">
|
|
13
|
+
<i :class="item.type === 'video' ? 'fas fa-video text-[10px] text-slate-500' : 'fas fa-image text-[10px] text-slate-500'"></i>
|
|
14
|
+
</div>
|
|
15
|
+
<span class="text-xs font-medium text-slate-700">#{{ String(item.id).split('-')[0] }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
<span v-if="item.title" class="text-[11px] text-slate-600 truncate max-w-[160px]">{{ item.title }}</span>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<template #item-footer="{ item, remove }">
|
|
22
|
+
<div class="h-full flex items-center justify-between px-3">
|
|
23
|
+
<div class="flex items-center gap-2">
|
|
24
|
+
<button
|
|
25
|
+
v-if="remove"
|
|
26
|
+
class="px-2.5 py-1 rounded-full bg-white/90 text-slate-700 text-[11px] shadow-sm hover:bg-red-500 hover:text-white transition-colors"
|
|
27
|
+
@click.stop="remove(item)"
|
|
28
|
+
>
|
|
29
|
+
Remove
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="text-[11px] text-slate-600">
|
|
33
|
+
{{ item.width }}×{{ item.height }}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
</Masonry>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import { ref } from 'vue'
|
|
42
|
+
import Masonry from '../../Masonry.vue'
|
|
43
|
+
import fixture from '../../pages.json'
|
|
44
|
+
import type { MasonryItem, GetPageResult } from '../../types'
|
|
45
|
+
|
|
46
|
+
const items = ref<MasonryItem[]>([])
|
|
47
|
+
|
|
48
|
+
const layout = {
|
|
49
|
+
sizes: { base: 1, sm: 2, md: 3, lg: 4 },
|
|
50
|
+
gutterX: 10,
|
|
51
|
+
gutterY: 10,
|
|
52
|
+
header: 36,
|
|
53
|
+
footer: 40
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const getPage = async (page: number): Promise<GetPageResult> => {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
|
|
60
|
+
|
|
61
|
+
if (!pageData) {
|
|
62
|
+
resolve({
|
|
63
|
+
items: [],
|
|
64
|
+
nextPage: null
|
|
65
|
+
})
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
resolve({
|
|
70
|
+
items: pageData.items,
|
|
71
|
+
nextPage: page < (fixture as any[]).length ? page + 1 : null
|
|
72
|
+
})
|
|
73
|
+
}, 300)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative h-full">
|
|
3
|
+
<div
|
|
4
|
+
v-if="!isInitialized"
|
|
5
|
+
class="absolute inset-0 z-10 flex items-center justify-center bg-slate-900/40 backdrop-blur-sm"
|
|
6
|
+
>
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
class="inline-flex items-center gap-2 rounded-full bg-white px-5 py-2 text-sm font-semibold text-slate-900 shadow-sm ring-1 ring-slate-200 transition hover:-translate-y-0.5 hover:shadow-md"
|
|
10
|
+
@click="handleInit"
|
|
11
|
+
>
|
|
12
|
+
Load gallery
|
|
13
|
+
<span class="text-slate-400">-></span>
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<Masonry
|
|
18
|
+
ref="masonryRef"
|
|
19
|
+
v-model:items="items"
|
|
20
|
+
:get-page="getPage"
|
|
21
|
+
:load-at-page="1"
|
|
22
|
+
:layout="layout"
|
|
23
|
+
init="manual"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { ref } from 'vue'
|
|
30
|
+
import Masonry from '../../Masonry.vue'
|
|
31
|
+
import fixture from '../../pages.json'
|
|
32
|
+
import type { MasonryItem, GetPageResult } from '../../types'
|
|
33
|
+
|
|
34
|
+
const items = ref<MasonryItem[]>([])
|
|
35
|
+
const isInitialized = ref(false)
|
|
36
|
+
const masonryRef = ref<InstanceType<typeof Masonry> | null>(null)
|
|
37
|
+
|
|
38
|
+
const layout = {
|
|
39
|
+
sizes: { base: 1, sm: 2, md: 3, lg: 4 },
|
|
40
|
+
gutterX: 10,
|
|
41
|
+
gutterY: 10
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getPage = async (page: number): Promise<GetPageResult> => {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
const pageData = (fixture as any[])[page - 1] as { items: MasonryItem[] } | undefined
|
|
48
|
+
|
|
49
|
+
if (!pageData) {
|
|
50
|
+
resolve({
|
|
51
|
+
items: [],
|
|
52
|
+
nextPage: null
|
|
53
|
+
})
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolve({
|
|
58
|
+
items: pageData.items,
|
|
59
|
+
nextPage: page < (fixture as any[]).length ? page + 1 : null
|
|
60
|
+
})
|
|
61
|
+
}, 300)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleInit() {
|
|
66
|
+
if (isInitialized.value) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isInitialized.value = true
|
|
71
|
+
masonryRef.value?.loadPage(1)
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Factory for masonry item transitions (typed)
|
|
3
3
|
* Optimized for large item arrays by skipping DOM operations for items outside viewport
|
|
4
4
|
*/
|
|
5
|
-
export function
|
|
5
|
+
export function createMasonryTransitions(
|
|
6
6
|
refs: { container?: any; masonry?: any },
|
|
7
7
|
opts?: { leaveDurationMs?: number; virtualizing?: { value: boolean } }
|
|
8
8
|
) {
|
|
@@ -80,7 +80,7 @@ export function useMasonryTransitions(
|
|
|
80
80
|
function onBeforeEnter(el: HTMLElement) {
|
|
81
81
|
const left = parseInt(el.dataset.left || '0', 10)
|
|
82
82
|
const top = parseInt(el.dataset.top || '0', 10)
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
// Skip animation during virtualization
|
|
85
85
|
if (opts?.virtualizing?.value) {
|
|
86
86
|
el.style.transition = 'none'
|
|
@@ -89,7 +89,7 @@ export function useMasonryTransitions(
|
|
|
89
89
|
el.style.removeProperty('--masonry-opacity-delay')
|
|
90
90
|
return
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
el.style.opacity = '0'
|
|
94
94
|
el.style.transform = `translate3d(${left}px, ${top + 10}px, 0) scale(0.985)`
|
|
95
95
|
}
|
|
@@ -141,8 +141,8 @@ export function useMasonryTransitions(
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// Prefer explicit option, fallback to CSS variable for safety
|
|
144
|
-
const fromOpts = typeof opts?.leaveDurationMs === 'number' ? opts!.leaveDurationMs : NaN
|
|
145
|
-
let leaveMs = Number.isFinite(fromOpts) && fromOpts > 0 ? fromOpts : NaN
|
|
144
|
+
const fromOpts = typeof opts?.leaveDurationMs === 'number' ? opts!.leaveDurationMs : Number.NaN
|
|
145
|
+
let leaveMs = Number.isFinite(fromOpts) && fromOpts > 0 ? fromOpts : Number.NaN
|
|
146
146
|
if (!Number.isFinite(leaveMs)) {
|
|
147
147
|
const cs = getComputedStyle(el)
|
|
148
148
|
const varVal = cs.getPropertyValue('--masonry-leave-duration') || ''
|