@wyxos/vibe 1.5.0 → 1.6.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.
- package/lib/index.js +401 -349
- package/lib/vibe.css +1 -1
- package/package.json +1 -1
- package/src/Masonry.vue +112 -9
package/lib/vibe.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.masonry-container[data-v-
|
|
1
|
+
.masonry-container[data-v-fa62094f]{overflow-anchor:none}.masonry-item[data-v-fa62094f]{will-change:transform,opacity;contain:layout paint;transition:transform var(--masonry-duration, .45s) var(--masonry-ease, cubic-bezier(.22, .61, .36, 1)),opacity var(--masonry-leave-duration, .16s) ease-out var(--masonry-opacity-delay, 0ms);backface-visibility:hidden}.masonry-move[data-v-fa62094f]{transition:transform var(--masonry-duration, .45s) var(--masonry-ease, cubic-bezier(.22, .61, .36, 1))}@media (prefers-reduced-motion: reduce){.masonry-container:not(.force-motion) .masonry-item[data-v-fa62094f],.masonry-container:not(.force-motion) .masonry-move[data-v-fa62094f]{transition-duration:1ms!important}}
|
package/package.json
CHANGED
package/src/Masonry.vue
CHANGED
|
@@ -91,6 +91,10 @@ const props = defineProps({
|
|
|
91
91
|
loadThresholdPx: {
|
|
92
92
|
type: Number,
|
|
93
93
|
default: 200
|
|
94
|
+
},
|
|
95
|
+
autoRefreshOnEmpty: {
|
|
96
|
+
type: Boolean,
|
|
97
|
+
default: false
|
|
94
98
|
}
|
|
95
99
|
})
|
|
96
100
|
|
|
@@ -133,6 +137,7 @@ const masonry = computed<any>({
|
|
|
133
137
|
const columns = ref<number>(7)
|
|
134
138
|
const container = ref<HTMLElement | null>(null)
|
|
135
139
|
const paginationHistory = ref<any[]>([])
|
|
140
|
+
const currentPage = ref<any>(null) // Track the actual current page being displayed
|
|
136
141
|
const isLoading = ref<boolean>(false)
|
|
137
142
|
const containerHeight = ref<number>(0)
|
|
138
143
|
|
|
@@ -294,6 +299,7 @@ defineExpose({
|
|
|
294
299
|
removeAll,
|
|
295
300
|
loadNext,
|
|
296
301
|
loadPage,
|
|
302
|
+
refreshCurrentPage,
|
|
297
303
|
reset,
|
|
298
304
|
init,
|
|
299
305
|
paginationHistory,
|
|
@@ -316,7 +322,12 @@ function refreshLayout(items: any[]) {
|
|
|
316
322
|
if (!container.value) return
|
|
317
323
|
// Developer diagnostics: warn when dimensions are invalid
|
|
318
324
|
checkItemDimensions(items as any[], 'refreshLayout')
|
|
319
|
-
|
|
325
|
+
// Preserve original index before layout reordering
|
|
326
|
+
const itemsWithIndex = items.map((item, index) => ({
|
|
327
|
+
...item,
|
|
328
|
+
originalIndex: item.originalIndex ?? index
|
|
329
|
+
}))
|
|
330
|
+
const content = calculateLayout(itemsWithIndex as any, container.value as HTMLElement, columns.value, layout.value as any)
|
|
320
331
|
calculateHeight(content as any)
|
|
321
332
|
masonry.value = content
|
|
322
333
|
}
|
|
@@ -383,14 +394,16 @@ async function fetchWithRetry<T = any>(fn: () => Promise<T>): Promise<T> {
|
|
|
383
394
|
}
|
|
384
395
|
|
|
385
396
|
async function loadPage(page: number) {
|
|
386
|
-
if (isLoading.value
|
|
387
|
-
|
|
397
|
+
if (isLoading.value) return
|
|
398
|
+
// Starting a new load should clear any previous cancel request
|
|
388
399
|
cancelRequested.value = false
|
|
400
|
+
isLoading.value = true
|
|
389
401
|
try {
|
|
390
402
|
const baseline = (masonry.value as any[]).length
|
|
391
403
|
if (cancelRequested.value) return
|
|
392
404
|
const response = await getContent(page)
|
|
393
405
|
if (cancelRequested.value) return
|
|
406
|
+
currentPage.value = page // Track the current page
|
|
394
407
|
paginationHistory.value.push(response.nextPage)
|
|
395
408
|
await maybeBackfillToTarget(baseline)
|
|
396
409
|
return response
|
|
@@ -403,15 +416,17 @@ async function loadPage(page: number) {
|
|
|
403
416
|
}
|
|
404
417
|
|
|
405
418
|
async function loadNext() {
|
|
406
|
-
if (isLoading.value
|
|
407
|
-
|
|
419
|
+
if (isLoading.value) return
|
|
420
|
+
// Starting a new load should clear any previous cancel request
|
|
408
421
|
cancelRequested.value = false
|
|
422
|
+
isLoading.value = true
|
|
409
423
|
try {
|
|
410
424
|
const baseline = (masonry.value as any[]).length
|
|
411
425
|
if (cancelRequested.value) return
|
|
412
|
-
const
|
|
413
|
-
const response = await getContent(
|
|
426
|
+
const nextPageToLoad = paginationHistory.value[paginationHistory.value.length - 1]
|
|
427
|
+
const response = await getContent(nextPageToLoad)
|
|
414
428
|
if (cancelRequested.value) return
|
|
429
|
+
currentPage.value = nextPageToLoad // Track the current page
|
|
415
430
|
paginationHistory.value.push(response.nextPage)
|
|
416
431
|
await maybeBackfillToTarget(baseline)
|
|
417
432
|
return response
|
|
@@ -423,10 +438,79 @@ async function loadNext() {
|
|
|
423
438
|
}
|
|
424
439
|
}
|
|
425
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Refresh the current page by clearing items and reloading from current page
|
|
443
|
+
* Useful when items are removed and you want to stay on the same page
|
|
444
|
+
*/
|
|
445
|
+
async function refreshCurrentPage() {
|
|
446
|
+
console.log('[Masonry] refreshCurrentPage called, isLoading:', isLoading.value, 'currentPage:', currentPage.value)
|
|
447
|
+
if (isLoading.value) return
|
|
448
|
+
cancelRequested.value = false
|
|
449
|
+
isLoading.value = true
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
// Use the tracked current page
|
|
453
|
+
const pageToRefresh = currentPage.value
|
|
454
|
+
console.log('[Masonry] pageToRefresh:', pageToRefresh)
|
|
455
|
+
|
|
456
|
+
if (pageToRefresh == null) {
|
|
457
|
+
console.warn('[Masonry] No current page to refresh - currentPage:', currentPage.value, 'paginationHistory:', paginationHistory.value)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Clear existing items
|
|
462
|
+
masonry.value = []
|
|
463
|
+
containerHeight.value = 0
|
|
464
|
+
|
|
465
|
+
// Reset pagination history to just the current page
|
|
466
|
+
paginationHistory.value = [pageToRefresh]
|
|
467
|
+
|
|
468
|
+
await nextTick()
|
|
469
|
+
|
|
470
|
+
// Reload the current page
|
|
471
|
+
const response = await getContent(pageToRefresh)
|
|
472
|
+
if (cancelRequested.value) return
|
|
473
|
+
|
|
474
|
+
// Update pagination state
|
|
475
|
+
currentPage.value = pageToRefresh
|
|
476
|
+
paginationHistory.value.push(response.nextPage)
|
|
477
|
+
|
|
478
|
+
// Optionally backfill if needed
|
|
479
|
+
const baseline = (masonry.value as any[]).length
|
|
480
|
+
await maybeBackfillToTarget(baseline)
|
|
481
|
+
|
|
482
|
+
return response
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error('[Masonry] Error refreshing current page:', error)
|
|
485
|
+
throw error
|
|
486
|
+
} finally {
|
|
487
|
+
isLoading.value = false
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
426
491
|
async function remove(item: any) {
|
|
427
492
|
const next = (masonry.value as any[]).filter(i => i.id !== item.id)
|
|
428
493
|
masonry.value = next
|
|
429
494
|
await nextTick()
|
|
495
|
+
|
|
496
|
+
// If all items were removed, either refresh current page or load next based on prop
|
|
497
|
+
console.log('[Masonry] remove - next.length:', next.length, 'paginationHistory.length:', paginationHistory.value.length)
|
|
498
|
+
if (next.length === 0 && paginationHistory.value.length > 0) {
|
|
499
|
+
if (props.autoRefreshOnEmpty) {
|
|
500
|
+
console.log('[Masonry] All items removed, calling refreshCurrentPage')
|
|
501
|
+
await refreshCurrentPage()
|
|
502
|
+
} else {
|
|
503
|
+
console.log('[Masonry] All items removed, calling loadNext and forcing backfill')
|
|
504
|
+
try {
|
|
505
|
+
await loadNext()
|
|
506
|
+
// Force backfill from 0 to ensure viewport is filled
|
|
507
|
+
// Pass baseline=0 and force=true to trigger backfill even if backfillEnabled was temporarily disabled
|
|
508
|
+
await maybeBackfillToTarget(0, true)
|
|
509
|
+
} catch {}
|
|
510
|
+
}
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
|
|
430
514
|
// Commit DOM updates without forcing sync reflow
|
|
431
515
|
await new Promise<void>(r => requestAnimationFrame(() => r()))
|
|
432
516
|
// Start FLIP on next frame
|
|
@@ -441,6 +525,21 @@ async function removeMany(items: any[]) {
|
|
|
441
525
|
const next = (masonry.value as any[]).filter(i => !ids.has(i.id))
|
|
442
526
|
masonry.value = next
|
|
443
527
|
await nextTick()
|
|
528
|
+
|
|
529
|
+
// If all items were removed, either refresh current page or load next based on prop
|
|
530
|
+
if (next.length === 0 && paginationHistory.value.length > 0) {
|
|
531
|
+
if (props.autoRefreshOnEmpty) {
|
|
532
|
+
await refreshCurrentPage()
|
|
533
|
+
} else {
|
|
534
|
+
try {
|
|
535
|
+
await loadNext()
|
|
536
|
+
// Force backfill from 0 to ensure viewport is filled
|
|
537
|
+
await maybeBackfillToTarget(0, true)
|
|
538
|
+
} catch {}
|
|
539
|
+
}
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
444
543
|
// Commit DOM updates without forcing sync reflow
|
|
445
544
|
await new Promise<void>(r => requestAnimationFrame(() => r()))
|
|
446
545
|
// Start FLIP on next frame
|
|
@@ -487,8 +586,8 @@ function onResize() {
|
|
|
487
586
|
let backfillActive = false
|
|
488
587
|
const cancelRequested = ref(false)
|
|
489
588
|
|
|
490
|
-
async function maybeBackfillToTarget(baselineCount: number) {
|
|
491
|
-
if (!props.backfillEnabled) return
|
|
589
|
+
async function maybeBackfillToTarget(baselineCount: number, force = false) {
|
|
590
|
+
if (!force && !props.backfillEnabled) return
|
|
492
591
|
if (backfillActive) return
|
|
493
592
|
if (cancelRequested.value) return
|
|
494
593
|
|
|
@@ -549,7 +648,9 @@ function cancelLoad() {
|
|
|
549
648
|
}
|
|
550
649
|
|
|
551
650
|
function reset() {
|
|
651
|
+
// Cancel ongoing work, then immediately clear cancel so new loads can start
|
|
552
652
|
cancelLoad()
|
|
653
|
+
cancelRequested.value = false
|
|
553
654
|
if (container.value) {
|
|
554
655
|
container.value.scrollTo({
|
|
555
656
|
top: 0,
|
|
@@ -559,6 +660,7 @@ function reset() {
|
|
|
559
660
|
|
|
560
661
|
masonry.value = []
|
|
561
662
|
containerHeight.value = 0
|
|
663
|
+
currentPage.value = props.loadAtPage // Reset current page tracking
|
|
562
664
|
paginationHistory.value = [props.loadAtPage]
|
|
563
665
|
|
|
564
666
|
scrollProgress.value = {
|
|
@@ -586,6 +688,7 @@ const debouncedScrollHandler = debounce(async () => {
|
|
|
586
688
|
const debouncedResizeHandler = debounce(onResize, 200)
|
|
587
689
|
|
|
588
690
|
function init(items: any[], page: any, next: any) {
|
|
691
|
+
currentPage.value = page // Track the initial current page
|
|
589
692
|
paginationHistory.value = [page]
|
|
590
693
|
paginationHistory.value.push(next)
|
|
591
694
|
// Diagnostics: check incoming initial items
|