@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/vibe.css CHANGED
@@ -1 +1 @@
1
- .masonry-container[data-v-b855ca99]{overflow-anchor:none}.masonry-item[data-v-b855ca99]{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-b855ca99]{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-b855ca99],.masonry-container:not(.force-motion) .masonry-move[data-v-b855ca99]{transition-duration:1ms!important}}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/vibe",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "main": "lib/index.js",
5
5
  "module": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
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
- const content = calculateLayout(items as any, container.value as HTMLElement, columns.value, layout.value as any)
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 || cancelRequested.value) return
387
- isLoading.value = true
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 || cancelRequested.value) return
407
- isLoading.value = true
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 currentPage = paginationHistory.value[paginationHistory.value.length - 1]
413
- const response = await getContent(currentPage)
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