{"version":3,"file":"image-viewer2.mjs","sources":["../../../../../../packages/components/image-viewer/src/image-viewer.vue"],"sourcesContent":["<template>\n  <hl-teleport to=\"body\" :disabled=\"!teleported\">\n    <transition name=\"viewer-fade\">\n      <div\n        ref=\"wrapper\"\n        :tabindex=\"-1\"\n        :class=\"namespace\"\n        :style=\"{ zIndex }\"\n      >\n        <div\n          class=\"image-viewer-mask\"\n          @click.self=\"hideOnClickModal && hide()\"\n        ></div>\n\n        <!-- ACTIONS -->\n        <hl-group gap=\"var(--sm)\" align=\"items-middle items-around\" class=\"image-viewer-tools\">\n          <hl-group v-if=\"$slots['tools-before']\" class=\"tools-group\">\n            <slot name=\"tools-before\" :index=\"activeIndex\"></slot>\n          </hl-group>\n          <hl-group gap=\"var(--xs)\" class=\"tools-group\">\n            <hl-icon @click=\"handleActions('zoomOut')\">\n              <system-zoom-out />\n            </hl-icon>\n            <hl-icon @click=\"handleActions('zoomIn')\">\n              <system-zoom-in />\n            </hl-icon>\n            <hl-icon name=\"Refresh\" @click=\"handleActions('anticlockwise')\">\n              <system-refresh />\n            </hl-icon>\n            <hl-icon @click=\"toggleMode\">\n              <system-maximize v-if=\"mode.icon === 'Maximize'\" />\n              <system-minimize v-if=\"mode.icon === 'Minimize'\" />\n            </hl-icon>\n          </hl-group>\n          <hl-group v-if=\"!isSingle\" gap=\"var(--xs)\" class=\"tools-group\">\n            <hl-icon :class=\"{ 'is-disabled': !infinite && isFirst }\" @click=\"prev\">\n              <system-arrow-left />\n            </hl-icon>\n            <hl-icon :class=\"{ 'is-disabled': !infinite && isLast }\" @click=\"next\">\n              <system-arrow-right />\n            </hl-icon>\n          </hl-group>\n          <hl-group class=\"tools-group\">\n            <hl-icon @click=\"hide\">\n              <system-close />\n            </hl-icon>\n          </hl-group>\n          <hl-group v-if=\"$slots['tools-after']\" class=\"tools-group\">\n            <slot name=\"tools-after\" :index=\"activeIndex\"></slot>\n          </hl-group>\n        </hl-group>\n        <!-- CANVAS -->\n        <div class=\"image-viewer-canvas\">\n          <img\n            v-for=\"(url, i) in urlList\"\n            v-show=\"i === activeIndex\"\n            :ref=\"(el) => (imgRefs[i] = el)\"\n            :key=\"url\"\n            :src=\"url\"\n            :style=\"imgStyle\"\n            class=\"image-viewer-img\"\n            :crossorigin=\"crossorigin\"\n            @load=\"handleImgLoad\"\n            @error=\"handleImgError\"\n            @mousedown=\"handleMouseDown\"\n          >\n        </div>\n        <slot></slot>\n      </div>\n    </transition>\n  </hl-teleport>\n</template>\n\n<script lang='ts'>\nimport { defineComponent, computed, ref, onMounted, watch, nextTick, effectScope, shallowRef } from 'vue'\nimport { useEventListener } from '@vueuse/core'\nimport { throttle } from 'lodash-unified'\nimport { keysOf } from '@hongluan-ui/utils'\nimport { EVENT_CODE } from '@hongluan-ui/constants'\nimport { useLocale, useNamespace, useZIndex } from '@hongluan-ui/hooks'\nimport HlIcon from '@hongluan-ui/components/icon'\nimport HlGroup from '@hongluan-ui/components/group'\nimport HlTeleport from '@hongluan-ui/components/teleport'\nimport {\n  SystemClose,\n  SystemArrowLeft,\n  SystemArrowRight,\n  SystemZoomIn,\n  SystemZoomOut,\n  SystemRefresh,\n  SystemMaximize,\n  SystemMinimize,\n} from '@hongluan-ui/components/system-icon'\nimport { imageViewerProps } from './image-viewer'\n\nimport type { CSSProperties } from 'vue'\nimport type { ImageViewerAction, ImageViewerMode } from './image-viewer'\n\nconst modes: Record<'CONTAIN' | 'ORIGINAL', ImageViewerMode> = {\n  CONTAIN: {\n    name: 'contain',\n    icon: 'Maximize',\n  },\n  ORIGINAL: {\n    name: 'original',\n    icon: 'Minimize',\n  },\n}\n\nexport default defineComponent({\n  name: 'ImageViewer',\n  components: {\n    HlIcon,\n    HlGroup,\n    HlTeleport,\n    SystemClose,\n    SystemArrowLeft,\n    SystemArrowRight,\n    SystemZoomIn,\n    SystemZoomOut,\n    SystemRefresh,\n    SystemMaximize,\n    SystemMinimize,\n  },\n  props: imageViewerProps,\n  emits: ['close', 'switch', 'rotate'],\n\n  setup(props, { emit }) {\n    const { namespace } = useNamespace('image-viewer')\n    const { nextZIndex } = useZIndex()\n\n    const { t } = useLocale()\n    const wrapper = ref<HTMLDivElement>()\n    const imgRefs = ref<HTMLImageElement[]>([])\n\n    const scopeEventListener = effectScope()\n\n    const loading = ref(true)\n    const activeIndex = ref(props.initialIndex)\n    const mode = shallowRef<ImageViewerMode>(modes.CONTAIN)\n\n    const transform = ref({\n      scale: 1,\n      deg: 0,\n      offsetX: 0,\n      offsetY: 0,\n      enableTransition: false,\n    })\n    const zIndex = ref(props.zIndex ?? nextZIndex())\n\n    const isSingle = computed(() => {\n      const { urlList } = props\n      return urlList.length <= 1\n    })\n\n    const isFirst = computed(() => {\n      return activeIndex.value === 0\n    })\n\n    const isLast = computed(() => {\n      return activeIndex.value === props.urlList.length - 1\n    })\n\n    const currentImg = computed(() => {\n      return props.urlList[activeIndex.value]\n    })\n\n    const imgStyle = computed(() => {\n      const { scale, deg, offsetX, offsetY, enableTransition } = transform.value\n      let translateX = offsetX / scale\n      let translateY = offsetY / scale\n\n      const radian = (deg * Math.PI) / 180\n      const cosRadian = Math.cos(radian)\n      const sinRadian = Math.sin(radian)\n      translateX = translateX * cosRadian + translateY * sinRadian\n      translateY = translateY * cosRadian - (offsetX / scale) * sinRadian\n\n      const style: CSSProperties = {\n        transform: `scale(${scale}) rotate(${deg}deg) translate(${translateX}px, ${translateY}px)`,\n        transition: enableTransition ? 'transform .3s' : '',\n      }\n      if (mode.value.name === modes.CONTAIN.name) {\n        style.maxWidth = style.maxHeight = '100%'\n      }\n      return style\n    })\n\n    function hide() {\n      unregisterEventListener()\n      emit('close')\n    }\n\n    function registerEventListener() {\n      const keydownHandler = throttle((e: KeyboardEvent) => {\n        switch (e.code) {\n          // ESC\n          case EVENT_CODE.esc:\n            hide()\n            break\n          // SPACE\n          case EVENT_CODE.space:\n            toggleMode()\n            break\n          // LEFT_ARROW\n          case EVENT_CODE.left:\n            prev()\n            break\n          // UP_ARROW\n          case EVENT_CODE.up:\n            handleActions('zoomIn')\n            break\n          // RIGHT_ARROW\n          case EVENT_CODE.right:\n            next()\n            break\n          // DOWN_ARROW\n          case EVENT_CODE.down:\n            handleActions('zoomOut')\n            break\n        }\n      })\n\n      const mousewheelHandler = throttle((e: WheelEvent) => {\n        const delta = e.deltaY || e.deltaX\n        handleActions(delta < 0 ? 'zoomIn' : 'zoomOut', {\n          zoomRate: props.zoomRate,\n          enableTransition: false,\n        })\n      })\n\n\n      scopeEventListener.run(() => {\n        useEventListener(document, 'keydown', keydownHandler)\n        useEventListener(document, 'wheel', mousewheelHandler)\n      })\n    }\n\n    function unregisterEventListener() {\n      scopeEventListener.stop()\n    }\n\n    function handleImgLoad() {\n      loading.value = false\n    }\n\n    function handleImgError(e: Event) {\n      loading.value = false\n      ;(e.target as HTMLImageElement).alt = t('hl.image.error')\n    }\n\n    function handleMouseDown(e: MouseEvent) {\n      if (loading.value || e.button !== 0 || !wrapper.value) return\n      transform.value.enableTransition = false\n\n      const { offsetX, offsetY } = transform.value\n      const startX = e.pageX\n      const startY = e.pageY\n\n      const dragHandler = throttle((ev: MouseEvent) => {\n        transform.value = {\n          ...transform.value,\n          offsetX: offsetX + ev.pageX - startX,\n          offsetY: offsetY + ev.pageY - startY,\n        }\n      })\n      const removeMousemove = useEventListener(\n        document,\n        'mousemove',\n        dragHandler,\n      )\n      useEventListener(document, 'mouseup', () => {\n        removeMousemove()\n      })\n\n      e.preventDefault()\n    }\n\n    function reset() {\n      transform.value = {\n        scale: 1,\n        deg: 0,\n        offsetX: 0,\n        offsetY: 0,\n        enableTransition: false,\n      }\n    }\n\n    function toggleMode() {\n      if (loading.value) return\n\n      const modeNames = keysOf(modes)\n      const modeValues = Object.values(modes)\n      const currentMode = mode.value.name\n      const index = modeValues.findIndex(i => i.name === currentMode)\n      const nextIndex = (index + 1) % modeNames.length\n      mode.value = modes[modeNames[nextIndex]]\n\n      reset()\n    }\n\n    function setActiveItem(index: number) {\n      const len = props.urlList.length\n      activeIndex.value = (index + len) % len\n    }\n\n    function prev() {\n      if (isFirst.value && !props.infinite) return\n      setActiveItem(activeIndex.value - 1)\n    }\n\n    function next() {\n      if (isLast.value && !props.infinite) return\n      setActiveItem(activeIndex.value + 1)\n    }\n\n    function handleActions(action: ImageViewerAction, options = {}) {\n      if (loading.value) return\n      const { minScale, maxScale } = props\n      const { zoomRate, rotateDeg, enableTransition } = {\n        zoomRate: props.zoomRate,\n        rotateDeg: 90,\n        enableTransition: true,\n        ...options,\n      }\n      switch (action) {\n        case 'zoomOut':\n          if (transform.value.scale > minScale) {\n            transform.value.scale = Number.parseFloat(\n              (transform.value.scale / zoomRate).toFixed(3),\n            )\n          }\n          break\n        case 'zoomIn':\n          if (transform.value.scale < maxScale) {\n            transform.value.scale = Number.parseFloat(\n              (transform.value.scale * zoomRate).toFixed(3),\n            )\n          }\n          break\n        case 'clockwise':\n          transform.value.deg += rotateDeg\n          emit('rotate', transform.value.deg)\n          break\n        case 'anticlockwise':\n          transform.value.deg -= rotateDeg\n          emit('rotate', transform.value.deg)\n          break\n      }\n      transform.value.enableTransition = enableTransition\n    }\n\n    watch(currentImg, () => {\n      nextTick(() => {\n        const $img = imgRefs.value[0]\n        if (!$img?.complete) {\n          loading.value = true\n        }\n      })\n    })\n\n    watch(activeIndex, val => {\n      reset()\n      emit('switch', val)\n    })\n\n    onMounted(() => {\n      registerEventListener()\n      // add tabindex then wrapper can be focusable via Javascript\n      // focus wrapper so arrow key can't cause inner scroll behavior underneath\n      wrapper.value?.focus?.()\n    })\n\n    return {\n      namespace,\n      activeIndex,\n      wrapper,\n      imgRefs,\n      isSingle,\n      isFirst,\n      isLast,\n      currentImg,\n      imgStyle,\n      mode,\n      zIndex,\n      handleActions,\n      prev,\n      next,\n      hide,\n      toggleMode,\n      setActiveItem,\n      handleImgLoad,\n      handleImgError,\n      handleMouseDown,\n    }\n  },\n})\n</script>\n"],"names":["_withCtx","_withDirectives"],"mappings":"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"}