@fy-/fws-vue 2.4.3 → 2.4.4

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.
@@ -18,13 +18,20 @@ import {
18
18
  useWindowSize,
19
19
  } from '@vueuse/core'
20
20
  import { computed, h, nextTick, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from 'vue'
21
+ import { useRoute } from 'vue-router'
21
22
  import { useEventBus } from '../../composables/event-bus'
23
+ import { useServerRouter } from '../../stores/serverRouter'
22
24
  import { ClientOnly } from '../ssr/ClientOnly'
23
25
  import DefaultPaging from './DefaultPaging.vue'
24
26
 
25
27
  // Core state
26
28
  const isGalleryOpen = ref<boolean>(false)
27
29
  const eventBus = useEventBus()
30
+ const route = useRoute()
31
+ const history = useServerRouter()
32
+ // Cross-page navigation: set while a prev/next page change is in flight
33
+ const pendingPageFlip = ref<'next' | 'prev' | null>(null)
34
+ let pendingPageFlipTimeout: number | null = null
28
35
  const sidePanel = ref<boolean>(true)
29
36
  const showControls = ref<boolean>(true)
30
37
  const isFullscreen = ref<boolean>(false)
@@ -322,6 +329,13 @@ function setModal(value: boolean) {
322
329
  clearTimeout(controlsTimeout)
323
330
  controlsTimeout = null
324
331
  }
332
+
333
+ // Abort any in-flight cross-page navigation
334
+ pendingPageFlip.value = null
335
+ if (pendingPageFlipTimeout) {
336
+ clearTimeout(pendingPageFlipTimeout)
337
+ pendingPageFlipTimeout = null
338
+ }
325
339
  }
326
340
  isGalleryOpen.value = value
327
341
  showControls.value = true
@@ -343,12 +357,60 @@ const openGalleryImage = useDebounceFn((index: number | undefined) => {
343
357
  })
344
358
  }, 50)
345
359
 
360
+ // Cross-page navigation: push the new page into the route query, exactly like
361
+ // DefaultPaging does. The consumer's paging watchers react to the route change
362
+ // and refetch, replacing `images` — the watcher below then lands the index.
363
+ function goToGalleryPage(page: number) {
364
+ const newQuery = { ...route.query }
365
+ newQuery.page = page.toString()
366
+ history.push({
367
+ path: history.currentRoute.path,
368
+ query: newQuery,
369
+ })
370
+ }
371
+
372
+ function startPageFlip(dir: 'next' | 'prev', page: number) {
373
+ pendingPageFlip.value = dir
374
+ // Safety: if the refetch never lands (network error), unlock navigation
375
+ if (pendingPageFlipTimeout) clearTimeout(pendingPageFlipTimeout)
376
+ pendingPageFlipTimeout = window.setTimeout(() => {
377
+ pendingPageFlip.value = null
378
+ }, 5000)
379
+ goToGalleryPage(page)
380
+ }
381
+
382
+ // When the images array is replaced while a page flip is pending, land on the
383
+ // first image (next) or last image (prev) of the new page. Default pre-flush
384
+ // watcher: runs before render, so the old index never flashes.
385
+ watch(
386
+ () => props.images,
387
+ (newImages) => {
388
+ if (!pendingPageFlip.value) return
389
+ if (newImages && newImages.length > 0) {
390
+ modelValue.value = pendingPageFlip.value === 'next' ? 0 : newImages.length - 1
391
+ }
392
+ pendingPageFlip.value = null
393
+ if (pendingPageFlipTimeout) {
394
+ clearTimeout(pendingPageFlipTimeout)
395
+ pendingPageFlipTimeout = null
396
+ }
397
+ nextTick(() => {
398
+ updateImageSizes()
399
+ })
400
+ },
401
+ )
402
+
346
403
  // Navigation functions
347
404
  function goNextImage() {
348
405
  direction.value = 'next'
406
+ if (pendingPageFlip.value) return // page change in flight — ignore extra input
349
407
  if (modelValue.value < props.images.length - 1) {
350
408
  modelValue.value++
351
409
  }
410
+ else if (props.paging && props.paging.page_no < props.paging.page_max) {
411
+ // Last image of the page → go to the first image of the next page
412
+ startPageFlip('next', props.paging.page_no + 1)
413
+ }
352
414
  else {
353
415
  modelValue.value = 0
354
416
  }
@@ -361,9 +423,14 @@ function goNextImage() {
361
423
 
362
424
  function goPrevImage() {
363
425
  direction.value = 'prev'
426
+ if (pendingPageFlip.value) return // page change in flight — ignore extra input
364
427
  if (modelValue.value > 0) {
365
428
  modelValue.value--
366
429
  }
430
+ else if (props.paging && props.paging.page_no > 1) {
431
+ // First image of the page → go to the last image of the previous page
432
+ startPageFlip('prev', props.paging.page_no - 1)
433
+ }
367
434
  else {
368
435
  modelValue.value = props.images.length - 1 > 0 ? props.images.length - 1 : 0
369
436
  }
@@ -599,6 +666,10 @@ onUnmounted(() => {
599
666
  clearTimeout(fullscreenResizeTimeout)
600
667
  }
601
668
 
669
+ if (pendingPageFlipTimeout) {
670
+ clearTimeout(pendingPageFlipTimeout)
671
+ }
672
+
602
673
  // Ensure we exit fullscreen mode on unmount
603
674
  if (isFullscreen.value) {
604
675
  exitFullscreen().catch(() => {})
@@ -698,7 +769,7 @@ onUnmounted(() => {
698
769
  leave-to-class="opacity-0"
699
770
  >
700
771
  <div
701
- v-if="showControls && images.length > 1"
772
+ v-if="showControls && (images.length > 1 || (paging && paging.page_max > 1))"
702
773
  class="absolute left-0 z-40 h-full flex items-center px-2 md:px-4"
703
774
  >
704
775
  <button
@@ -772,7 +843,7 @@ onUnmounted(() => {
772
843
  leave-to-class="opacity-0"
773
844
  >
774
845
  <div
775
- v-if="showControls && images.length > 1"
846
+ v-if="showControls && (images.length > 1 || (paging && paging.page_max > 1))"
776
847
  class="absolute right-0 z-40 h-full flex items-center px-2 md:px-4"
777
848
  :class="{ 'lg:mr-64': sidePanel }"
778
849
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",