UNPKG

@opentiny/vue-renderless

Version:

An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.

680 lines (679 loc) 21.6 kB
import { __spreadValues } from "../chunk-G2ADBYYC.js"; import { on, off } from "@opentiny/utils"; import { KEY_CODE } from "@opentiny/utils"; import { PopupManager } from "@opentiny/utils"; import { xss } from "@opentiny/utils"; const mousewheelEventName = "mousewheel"; const rafThrottle = (fn) => { let locked = false; return function(...args) { if (locked) { return; } locked = true; window.requestAnimationFrame(() => { fn.apply(this, args); locked = false; }); }; }; const hide = ({ props, api, state }) => () => { api.deviceSupportUninstall(); props.onClose(); state.showImageViewer = false; }; const deviceSupportInstall = ({ state, api, mode }) => () => { on(window, "resize", api.initPage); state.urlList = state.urlList.map((subItem) => { let subItemObj = {}; let lastSlashIndex = ""; if (typeof subItem === "string") { subItem = api.filterImageUrl(subItem); if (state.isThumbnail || state.isMenuView) { lastSlashIndex = subItem.lastIndexOf("/"); state.fileName = subItem.substring(lastSlashIndex + 1); subItemObj.url = subItem; subItemObj.name = state.fileName; return subItemObj; } else { lastSlashIndex = subItem.lastIndexOf("/"); state.fileName = subItem.substring(lastSlashIndex + 1); return { url: subItem, name: state.fileName }; } } else if (typeof subItem === "object" && subItem !== null) { subItem.url = api.filterImageUrl(subItem.url); if (!subItem.name) { lastSlashIndex = subItem.url.lastIndexOf("/"); state.fileName = subItem.url.substring(lastSlashIndex + 1); subItem.name = state.fileName; } return subItem; } else { return null; } }); state._keyDownHandler = rafThrottle((event) => { const keyCode = event.keyCode; switch (keyCode) { case KEY_CODE.Escape: api.hide(); break; case KEY_CODE.Space: api.toggleMode(); break; case KEY_CODE.ArrowLeft: api.prev(); break; case KEY_CODE.ArrowUp: api.handleActions("zoomIn"); break; case KEY_CODE.ArrowRight: api.next(); break; case KEY_CODE.ArrowDown: api.handleActions("zoomOut"); break; default: break; } }); state._mouseWheelHandler = rafThrottle((event) => { const delta = event.wheelDelta ? event.wheelDelta : -event.detail; if (delta > 0) { api.handleActions("zoomIn", { zoomRate: 0.015, enableTransition: false }); } else { api.handleActions("zoomOut", { zoomRate: 0.015, enableTransition: false }); } }); on(document, "keydown", state._keyDownHandler); mode !== "mobile-first" && on(document, mousewheelEventName, state._mouseWheelHandler); }; const deviceSupportUninstall = ({ state, mode }) => () => { off(document, "keydown", state._keyDownHandler); mode !== "mobile-first" && off(document, mousewheelEventName, state._mouseWheelHandler); state._keyDownHandler = null; state._mouseWheelHandler = null; }; const handleImgLoad = (state) => () => state.loading = false; const handleImgError = ({ state, t }) => (event) => { state.loading = false; event.target.alt = t("ui.imageViewer.loadErrorAlt"); }; const handleMouseDown = (state) => (event) => { if (state.loading || event.button !== 0) { return; } const { offsetX, offsetY } = state.transform; const startX = event.pageX; const startY = event.pageY; state._dragHandler = rafThrottle((event2) => { state.transform.offsetX = offsetX + event2.pageX - startX; state.transform.offsetY = offsetY + event2.pageY - startY; }); on(document, "mousemove", state._dragHandler); state._removeDrag = () => off(document, "mousemove", state._dragHandler); if (state._clearMouse) { state._clearMouse(); state._clearMouse = void 0; } on(document, "mouseup", state._removeDrag); on(document, "mouseleave", state._removeDrag); state._clearMouse = () => { off(document, "mouseup", state._removeDrag); off(document, "mouseleave", state._removeDrag); }; event.preventDefault(); }; const reset = (state) => () => state.transform = { scale: 1, deg: 0, offsetX: 0, offsetY: 0, enableTransition: false }; const toggleMode = ({ state, constants, api }) => () => { if (state.loading) { return; } const MODE = constants.MODE; const modeNames = Object.keys(MODE); const modeValues = []; modeNames.forEach((key) => { modeValues.push(MODE[key]); }); let index = -1; modeValues.forEach((item, inx) => { if (item.name === state.mode.name) { index = inx; } }); const nextIndex = (index + 1) % modeNames.length; state.mode = MODE[modeNames[nextIndex]]; api.reset(); }; const prev = ({ state, api, vm }) => () => { if (state.isFirst && !state.infinite) { return; } const len = state.urlList.length; let prevElement = ""; state.index = (state.index - 1 + len) % len; api.activeItems(state.index); if (state.isThumbnail) { prevElement = vm.$refs[`isThumbnail_${state.index}`][0] || vm.$refs[`isThumbnail_${state.index}`]; } else if (state.isMenuView) { prevElement = vm.$refs[`isMenuView_${state.index}`][0] || vm.$refs[`isMenuView_${state.index}`]; } if (state.index === 1) { state.isThumbnail && vm.$refs.isThumbnailContent && (vm.$refs.isThumbnailContent.scrollTop = 0); state.isMenuView && vm.$refs.isMenuViewContent && (vm.$refs.isMenuViewContent.scrollTop = 0); state.scrollTop = 0; } else if (state.index === state.urlList.length - 1) { api.getLastPrev(prevElement); } else if (state.index === state.urlList.length - 2) { } else if (state.index === state.urlList.length - 3) { state.scrollTop = prevElement.offsetHeight; } else { api.getDefaultPrev(prevElement); } }; const getLastPrev = ({ state, vm }) => (prevElement) => { state.isThumbnail && vm.$refs.isThumbnailContent && (vm.$refs.isThumbnailContent.scrollTop = prevElement.offsetTop); state.isMenuView && vm.$refs.isMenuViewContent && (vm.$refs.isMenuViewContent.scrollTop = prevElement.offsetTop); state.scrollTop = prevElement.offsetTop; }; const getDefaultPrev = ({ state, vm }) => (prevElement) => { if (state.scrollTop <= prevElement.offsetHeight) { state.scrollTop = prevElement.offsetTop - state.scrollTop - (state.isThumbnail ? state.thumbnailTop : state.menuTop); } else { state.scrollTop = state.scrollTop - prevElement.offsetHeight - (state.isThumbnail ? state.thumbnailTop : state.menuTop); } state.isThumbnail && vm.$refs.isThumbnailContent && (vm.$refs.isThumbnailContent.scrollTop = state.scrollTop); state.isMenuView && vm.$refs.isMenuViewContent.scrollTop && (vm.$refs.isMenuViewContent.scrollTop = state.scrollTop); }; const next = ({ state, api, vm }) => () => { if (state.isLast && !state.infinite) { return; } const len = state.urlList.length; let element = ""; state.index = (state.index + 1) % len; api.activeItems(state.index); if (state.isThumbnail) { element = vm.$refs[`isThumbnail_${state.index}`][0] || vm.$refs[`isThumbnail_${state.index}`]; } else if (state.isMenuView) { element = vm.$refs[`isMenuView_${state.index}`][0] || vm.$refs[`isMenuView_${state.index}`]; } state.centerIndex = api.getCenterPosition(element) - 1; let slientIndex = -1; if (state.centerIndex > state.index) { slientIndex = state.index; } if (state.index === 0) { state.isThumbnail && vm.$refs.isThumbnailContent && (vm.$refs.isThumbnailContent.scrollTop = 0); state.isMenuView && vm.$refs.isMenuViewContent && (vm.$refs.isMenuViewContent.scrollTop = 0); state.scrollTop = 0; } else if (state.index === slientIndex) { } else { if (state.isThumbnail) { vm.$refs.isThumbnailContent && (vm.$refs.isThumbnailContent.scrollTop = state.scrollTop); state.scrollTop = state.scrollTop + element.offsetHeight + state.thumbnailTop; } else if (state.isMenuView) { vm.$refs.isMenuViewContent && (vm.$refs.isMenuViewContent.scrollTop = state.scrollTop); state.scrollTop = state.scrollTop + element.offsetHeight + state.menuTop; } } }; const getCenterPosition = ({ state, vm }) => (element) => { let contentHeight = 0; let eleHeight = 0; if (state.isThumbnail && vm.$refs.isThumbnailContent) { contentHeight = vm.$refs.isThumbnailContent.getBoundingClientRect().height; eleHeight = element.getBoundingClientRect().height + parseFloat(getComputedStyle(element).marginBottom) - 0; return Math.ceil(contentHeight / eleHeight) / 2; } else if (vm.$refs.isMenuViewContent && vm.$refs.isMenuViewContent) { contentHeight = vm.$refs.isMenuViewContent.getBoundingClientRect().height; eleHeight = element.getBoundingClientRect().height + parseFloat(getComputedStyle(element).marginTop) - 0; return Math.ceil(contentHeight / eleHeight) / 2; } }; const handleActions = (state, props, emit) => (action, options = {}) => { const { zoomRate, rotateDeg, enableTransition } = __spreadValues({ zoomRate: 0.2, rotateDeg: 90, enableTransition: true }, options); const { transform } = state; let lastSlashIndex = ""; let imageUrl = []; lastSlashIndex = state.currentImg.lastIndexOf("/"); let cutName = state.currentImg.substring(lastSlashIndex + 1); if (action === "delImage") { if (typeof state.urlList[0] === "string") { imageUrl = state.urlList; } else if (typeof state.urlList[0] === "object" && state.urlList[0] !== null) { cutName = state.urlList[state.index].name; state.urlList.forEach((item) => { imageUrl.push(item.url); }); } if (imageUrl.includes(state.currentImg)) { if (state.index === state.urlList.length - 1) { state.urlList.splice(state.index, 1); state.index = state.urlList.length - 1; } else { state.urlList.splice(state.index, 1); } } state.imageName = cutName; emit("delete", state.imageName); } if (state.loading) { return; } if (action === "zoomOut") { if (transform.scale > 0.2) { transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3)); } } else if (action === "zoomIn") { transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3)); } else if (action === "clocelise") { transform.deg += rotateDeg; } else if (action === "anticlocelise") { transform.deg -= rotateDeg; } transform.enableTransition = enableTransition; }; const computedIsSingle = (props) => () => props.urlList.length <= 1; const computedIsFirst = (state) => () => state.index === 0; const computedIsLast = ({ state, props }) => () => state.index === props.urlList.length - 1; const computedCurrentImg = ({ state, api }) => () => { if (typeof state.urlList[0] === "string") { return api.filterImageUrl(state.urlList[state.index]); } else if (typeof state.urlList[0] === "object" && state.urlList[0] !== null) { return api.filterImageUrl(state.urlList[state.index].url); } }; const computedImgStyle = ({ state, constants }) => () => { const { offsetX, offsetY, scale, deg, enableTransition } = state.transform; const transition = enableTransition ? "transform .3s" : ""; const style = { transform: `scale(${scale}) rotate(${deg}deg)`, transition, "margin-top": `${offsetY}px`, "margin-left": `${offsetX}px` }; if (JSON.stringify(state.mode) === JSON.stringify(constants.MODE.CONTAIN)) { style.maxWidth = style.maxHeight = "100%"; } return style; }; const watchVisible = (state) => (value) => state.previewVisible = value; const handleVisible = ({ state, emit, props }) => () => { state.transform.scale = 1; state.transform.deg = 0; setTimeout(() => { if (props.startPosition > 0) { state.index = (props.startPosition - 1 + state.urlList.length) % state.urlList.length; state.imageTransform = state.index * state.imageItemWidth; state.imageTransformSize = -state.index * state.imageItemWidth; } else { state.index = 0; state.imageTransform = state.index * state.imageItemWidth; state.imageTransformSize = -state.index * state.imageItemWidth; } }, 300); emit("update:preview-visible", false); emit("close", state.index, state.urlList[state.index]); }; const getImageWidth = ({ state, parent, props, vm, mode }) => () => { let imageW = 0; const len = state.urlList.length; if (mode === "mobile-first") { if (state.isThumbnail && state.isImagePreview) { imageW = vm.$refs.thumbnailCanvasBox.offsetWidth; } else { imageW = vm.$refs.canvasBox && vm.$refs.canvasBox.offsetWidth; } state.imageList = vm.$refs.viewerItem; } else if (mode === "mobile") { imageW = parent.$el.querySelector(".tiny-mobile-image-viewer__canvas").offsetWidth; state.imageList = parent.$el.querySelectorAll(".tiny-mobile-image-viewer__item"); } else { imageW = parent.$el.querySelector(".tiny-image-viewer__canvas").offsetWidth; state.imageList = parent.$el.querySelectorAll(".tiny-image-viewer__img"); } state.imageItemWidth = imageW; state.imageAllWidth = state.urlList.length * imageW; if (mode === "mobile") { if (props.startPosition > 0) { state.index = props.startPosition; state.imageTransition = 0; const transformX = state.index * state.imageItemWidth; state.imageTransform = transformX; state.imageTransformSize = -transformX; } if (state.index === 0 && props.deleteButton && state.delete) { state.imageTransition = 0; const transformX = state.index * state.imageItemWidth; state.imageTransform = transformX; state.imageTransformSize = -transformX; } } setTimeout(() => { state.imageTransition = 300; }, 0); if (props.startPosition === 0) { state.arrowStyle = "N"; } if (props.startPosition === len - 1) { state.arrowStyle = "Y"; } }; const swipeLeft = ({ state, emit }) => () => { if (state.isLast && !state.infinite) { return; } const len = state.urlList.length; if (state.index >= state.urlList.length - 2) { state.arrowStyle = "Y"; } else { state.arrowStyle = null; } if (state.imageTransform === state.imageAllWidth) { state.imageTransformSize = state.imageTransform = 0; state.imageList[0].style.transform = null; return; } if (state.imageTransform === state.imageAllWidth - state.imageItemWidth && state.index === state.urlList.length - 1) { return; } state.index = (state.index + 1) % len; const transformX = state.index * state.imageItemWidth; state.imageTransform = transformX; state.imageTransformSize = -transformX; emit("change", state.index, state.urlList[state.index]); }; const swipeRight = ({ state, emit }) => () => { if (state.isFirst && !state.infinite) { return; } const len = state.urlList.length; if (state.index <= 1) { state.arrowStyle = "N"; } else { state.arrowStyle = null; } if (state.imageTransform === 0 && state.index === 0) { return; } state.index = (state.index - 1 + len) % len; const transformX = state.index * state.imageItemWidth; state.imageTransform = transformX; state.imageTransformSize = -transformX; emit("change", state.index, state.urlList[state.index]); }; const handleDelete = ({ api, emit, state }) => () => { if (state.urlList.length <= 1) { state.delete = false; return; } state.delete = true; const currenIndex = state.index; const urlList = state.urlList; urlList.splice(currenIndex, 1); state.urlList = urlList; state.index = 0; api.getImageWidth(); emit("newImageList", state.urlList, currenIndex); }; const langClick = (state) => () => { if (window.navigator.msSaveOrOpenBlob) { let bstr = atob(state.currentImg.split(",")[1]); let n = bstr.length; let u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } let blob = new Blob([u8arr]); window.navigator.msSaveOrOpenBlob(blob, "img.png"); } else { const a = document.createElement("a"); a.style = "dispaly:none"; a.href = state.currentImg + "?response-content-type=application/octet-stream"; a.setAttribute("download", "img"); a.setAttribute("target", "downloadFile"); document.body.appendChild(a); a.click(); document.body.removeChild(a); } }; const touchstart = ({ state, mode, api }) => (e) => { state.firstX = e.targetTouches[0].clientX; if (mode === "mobile-first") { state.time = setTimeout(() => { if (!state.isImagePreview) { api.langClick(); } else { state.boxVisibility = true; } }, 1e3); } const touches = e.touches; const events = touches[0]; const events2 = touches[1]; preventDefault(e); state.pageX = events.pageX; state.pageY = events.pageY; state.moveable = true; if (events2) { state.pageX2 = events2.pageX; state.pageY2 = events2.pageY; } state.originScale = state.scale || 1; }; const preventDefault = (event, isStopPropagation) => { if (typeof event.cancelable !== "boolean" || event.cancelable) { event.preventDefault(); } if (isStopPropagation) { event.stopPropagation(); } }; const touchmove = (state) => (event) => { if (!state.moveable) { return; } preventDefault(event); const touches = event.touches; const events = touches[0]; const events2 = touches[1]; if (events2) { if (!state.pageX2) { state.pageX2 = events2.pageX; } if (!state.pageY2) { state.pageY2 = events2.pageY; } const getDistance = (start, stop) => Math.hypot(stop.x - start.x, stop.y - start.y); const zoom = getDistance( { x: events.pageX, y: events.pageY }, { x: events2.pageX, y: events2.pageY } ) / getDistance( { x: state.pageX, y: state.pageY }, { x: state.pageX2, y: state.pageY2 } ); let newScale = state.originScale * zoom; if (newScale > 3) { newScale = 3; } state.scale = newScale; state.transform.scale = newScale; } clearTimeout(state.time); }; const touchend = (state) => (e) => { let moveX = 0; state.endX = e.changedTouches[0].clientX; moveX = state.endX - state.firstX; if (!state.boxVisibility) { if (moveX === 0) { state.isImagePreview = false; state.hiddenThumbnail = false; } } state.moveable = false; state.pageX2 = 0; state.pageY2 = 0; clearTimeout(state.time); }; const computeZIndex = ({ constants, props }) => () => props.zIndex === constants.DEFAULT_POPPER_ZINDEX || props.zIndex < 1 ? PopupManager.nextZIndex() : props.zIndex; const activeItems = (state) => (i) => { state.index = i; state.currentIndex = i; }; const imagePreview = (state) => (i) => { state.index = i; state.mobileCurrentIndex && (state.isImagePreview = true); }; const initPage = ({ state, nextTick, api }) => () => { state.isImagePreview = false; state.hiddenThumbnail = false; nextTick(() => { api.getImageWidth(); }); }; const beforeDestroy = ({ api, state }) => () => { off(window, "resize", api.initPage); if (state._clearMouse) { state._clearMouse(); state._clearMouse = void 0; } if (state._dragHandler) { state._dragHandler = void 0; } if (state._removeDrag) { state._removeDrag = void 0; } }; const itemClick = ({ state, vm, nextTick }) => (itemData) => { if (state.isThumbnail) { state.showFlag = 1; } else if (state.isMenuView) { state.showFlag = 2; } switch (itemData) { case "1": state.isThumbnail = false; state.isMenuView = false; break; case "2": state.isThumbnail = true; state.isMenuView = false; nextTick(() => { state.currentIndex = 0; state.index = 0; vm.$refs.isThumbnailContent.scrollTop = 0; }); break; case "3": state.isThumbnail = false; state.isMenuView = true; nextTick(() => { state.currentIndex = 0; state.index = 0; vm.$refs.isMenuViewContent.scrollTop = 0; }); break; case "4": if (state.showFlag === 1) { state.isThumbnail = true; } else if (state.showFlag === 2) { state.isMenuView = true; } break; default: break; } }; const selectOption = ({ state, api }) => (item, index) => { switch (index) { case 0: api.langClick(); break; case 1: state.isThumbnail = true; state.isImagePreview = false; break; case 2: api.handleActions("delImage"); api.getImageWidth(); break; default: break; } }; const filterImageUrl = () => (imageUrl) => { const isBase64 = /^data:image\/(png|jpg|jpeg|gif);base64,([a-zA-Z0-9+/]+={0,2})/; return isBase64.test(imageUrl) ? imageUrl : xss.filterUrl(imageUrl); }; export { activeItems, beforeDestroy, computeZIndex, computedCurrentImg, computedImgStyle, computedIsFirst, computedIsLast, computedIsSingle, deviceSupportInstall, deviceSupportUninstall, filterImageUrl, getCenterPosition, getDefaultPrev, getImageWidth, getLastPrev, handleActions, handleDelete, handleImgError, handleImgLoad, handleMouseDown, handleVisible, hide, imagePreview, initPage, itemClick, langClick, next, prev, rafThrottle, reset, selectOption, swipeLeft, swipeRight, toggleMode, touchend, touchmove, touchstart, watchVisible };