UNPKG

vue-fantable

Version:
1,917 lines (1,822 loc) 441 kB
import { inject, ref, computed, watch, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, renderSlot, createTextVNode, toDisplayString, provide, createVNode, withDirectives, resolveDirective, h, nextTick, createApp, shallowRef, triggerRef, vModelText, resolveComponent, isVNode, defineComponent } from 'vue'; function debounce(func, wait, immediate) { let timeout; return function () { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { timeout = null; func.apply(context, args); }, wait); }; } /** * This method is like `clone` except that it recursively clones `value`. * Object inheritance is preserved. * * @since 1.0.0 * @category Lang * @param {*} value The value to recursively clone. * @returns {*} Returns the deep cloned value. * @see clone * @example * * const objects = [{ 'a': 1 }, { 'b': 2 }] * * const deep = cloneDeep(objects) * console.log(deep[0] === objects[0]) * // => false */ function deepClone(receive) { // console.log(typeof receive) // if (structuredClone) { // return structuredClone(receive) // } return goClone(receive); } function goClone(receive) { if (typeof receive !== 'object' || receive === null) { return receive; } const result = Array.isArray(receive) ? [] : {}; if (Array.isArray(receive)) { receive.forEach(item => { if (typeof item === 'object' && item !== null) { const afterDeep = goClone(item); result.push(afterDeep); } else { result.push(item); } }); } else { Object.keys(receive).forEach(item => { if (typeof receive[item] !== 'object') { result[item] = receive[item]; } else { result[item] = goClone(receive[item]); } }); } return result; } /* * @hasValue * @desc has value * @param {array} arr */ function hasValue(rec) { return rec !== null && rec !== undefined; } /* * @isEmptyArray * @desc is empty array * @param {array} arr */ function isEmptyArray(arr) { return !(Array.isArray(arr) && arr.length > 0); } /* * @isEmptyValue * @desc is empty value * @param {array} arr */ function isEmptyValue(value) { return !(value !== '' && value !== undefined && value !== null); } /* * @isDefined * @desc is defined * @param {any} val */ function isDefined(val) { return val !== undefined && val !== null; } /* * @isFunction * @desc is function * @param {any} val */ function isFunction$1(val) { return typeof val === 'function'; } /* * @isBoolean * @desc is boolean * @param {any} val */ function isBoolean(val) { return typeof val === 'boolean'; } /* * @isNumber * @desc is number * @param {any} val */ function isNumber(val) { return typeof val === 'number'; } /* * @getValByUnit * @desc get value by unit * @param {number|string} width - 宽度 */ function getValByUnit(width) { return typeof width === 'number' ? width + 'px' : width; } /* * @scrollTo * @desc element scrollTo https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollTo * @param {element} el - element * @param {object} option - scroll option */ function scrollTo(el, option) { if (isFunction$1(el.scrollTo)) { el.scrollTo(option); } else { const { top, left } = option; el.scrollTop = top; el.scrollLeft = left; } } // prefix const PREFIX_CLS$6 = 've-checkbox-'; // comps name const COMPS_NAME$9 = { VE_CHECKBOX: 'VeCheckbox', VE_CHECKBOX_GROUP: 'VeCheckboxGroup' }; /* * @clsName * @desc get class name * @param {string} cls - class */ function clsName$7(cls) { return PREFIX_CLS$6 + cls; } // prefix // comps name const COMPS_NAME$8 = { VE_CHECKBOX: 'VeCheckbox', VE_CHECKBOX_GROUP: 'VeCheckboxGroup' }; const GROUP_SYMBOL = Symbol('groupSymbol'); const GROUP_MODEL_VALUE = Symbol('groupModelValue'); var _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const __default__$1 = { name: COMPS_NAME$9.VE_CHECKBOX, }; const _sfc_main$1 = /*@__PURE__*/Object.assign(__default__$1, { props: { // 当前 checkbox 选中状态,实现 v-model modelValue: { type: [String, Number, Boolean], default: null, }, label: { type: [String], default: null, }, // is disabled checked disabled: Boolean, // partial selection effect indeterminate: Boolean, // 是否是可控组件 isControlled: { type: Boolean, default: false, }, // isControlled 为true 时生效 isSelected: { type: Boolean, default: false, }, }, emits: ['checkedChange', 'update:modelValue'], setup(__props, { expose: __expose, emit: __emit }) { __expose(); const fatherGroup = inject(GROUP_SYMBOL, 'default'); const fatherGroupValue = inject(GROUP_MODEL_VALUE, {}); const props = __props; const emit = __emit; const model = ref(false); initModel(); // get label content function initModel() { if (hasValue(props.modelValue)) { model.value = props.modelValue; } else if (isCheckBoxGroup()) { model.value = fatherGroupValue.modelValue.includes(props.label); } } const checkboxClass = computed(() => { const checked = clsName$7('checked'); const disabled = clsName$7('disabled'); const indeterminate = clsName$7('indeterminate'); const content = clsName$7('content'); const result = { [content]: true, [checked]: internalIsSelected.value, [disabled]: props.disabled, [indeterminate]: props.indeterminate, }; if (isCheckBoxGroup()) { result[checked] = fatherGroupValue.modelValue.includes(props.label); } return result }); // 是否横向显示还是纵向显示 const checkboxStyle = computed(() => { const displayState = fatherGroupValue.isVerticalShow; return { display: displayState ? 'block' : 'inline-block', } }); // 是否选中 const internalIsSelected = computed(() => { return props.isControlled ? props.isSelected : model.value }); watch(() => props.modelValue, () => { if (!props.disabled) { model.value = props.modelValue; } }); // is checkbox group function isCheckBoxGroup() { return typeof fatherGroup === 'function' } // checkbox change function onCheckboxChange(event) { if (props.disabled) { return false } const isChecked = event.target.checked; // if (!props.isControlled) { // emit('input', isChecked) // } // emit(EMIT_EVENTS.ON_CHECKED_CHANGE, isChecked) emit('checkedChange', isChecked); emit('update:modelValue', isChecked); if (isCheckBoxGroup()) { // update parent comp:checkbox-group fatherGroup(props.label, isChecked); } } const __returned__ = { fatherGroup, fatherGroupValue, props, emit, model, initModel, checkboxClass, checkboxStyle, internalIsSelected, isCheckBoxGroup, onCheckboxChange, get hasValue() { return hasValue }, get clsName() { return clsName$7 }, get COMPS_NAME() { return COMPS_NAME$9 }, get GROUP_SYMBOL() { return GROUP_SYMBOL }, get GROUP_MODEL_VALUE() { return GROUP_MODEL_VALUE }, computed, inject, watch, ref }; Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }); return __returned__ } }); const _hoisted_1$1 = ["checked", "value"]; function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createElementBlock("label", { class: "ve-checkbox", style: normalizeStyle($setup.checkboxStyle) }, [ createElementVNode("span", { class: normalizeClass($setup.checkboxClass) }, [ createElementVNode("input", { checked: $setup.internalIsSelected, class: normalizeClass($setup.clsName('input')), type: "checkbox", value: $props.label, onChange: $setup.onCheckboxChange }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1$1), createElementVNode("span", { class: normalizeClass($setup.clsName('inner')) }, null, 2 /* CLASS */) ], 2 /* CLASS */), createElementVNode("span", { class: normalizeClass($setup.clsName('label')) }, [ renderSlot(_ctx.$slots, "default", {}, () => [ createTextVNode(toDisplayString($props.label), 1 /* TEXT */) ]) ], 2 /* CLASS */) ], 4 /* STYLE */)) } var VeCheckbox = /*#__PURE__*/_export_sfc(_sfc_main$1, [['render',_sfc_render$1],['__file',"E:\\Full-stuck-study\\3-Vue\\vue-fantable\\packages\\ve-checkbox\\src\\index.vue"]]); VeCheckbox.install = function (Vue) { Vue.component('FanCheckbox', VeCheckbox); Vue.component(VeCheckbox.name, VeCheckbox); }; const __default__ = { name: COMPS_NAME$8.VE_CHECKBOX_GROUP }; const _sfc_main = /*@__PURE__*/Object.assign(__default__, { props: { modelValue: { type: Array, default() { return [] }, }, // 是否垂直排列显示(当时checkbox组时生效) isVerticalShow: { type: Boolean, default: false, }, }, emits: ['update:modelValue', 'change'], setup(__props, { expose: __expose, emit: __emit }) { __expose(); // import { getChildCompsByName } from '@P/src/utils/index' const emit = __emit; const updateValueInject = (label, value) => { if (value) { if (!props.modelValue.includes(label)) { const newValue = props.modelValue.slice(); newValue.push(label); emit('update:modelValue', newValue); } } else { if (props.modelValue.includes(label)) { const newValue = props.modelValue.filter(item => item !== label); emit('update:modelValue', newValue); } } }; const props = __props; provide(GROUP_SYMBOL, updateValueInject); provide(GROUP_MODEL_VALUE, props); // 更新子组件选中状态 // watch(() => props.modelValue, (newVal) => { // if (value) { // if (!props.modelValue.includes(label)) { // const newValue = props.modelValue.slice().push(label) // emit('update:modelValue', newValue) // } // } else { // if (props.modelValue.includes(label)) { // const newValue = props.modelValue.filter(item => item !== label) // emit('update:modelValue', newValue) // } // } // }) const __returned__ = { emit, updateValueInject, props, get GROUP_SYMBOL() { return GROUP_SYMBOL }, get COMPS_NAME() { return COMPS_NAME$8 }, get GROUP_MODEL_VALUE() { return GROUP_MODEL_VALUE }, provide }; Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }); return __returned__ } }); const _hoisted_1 = { class: "ve-checkbox-group" }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createElementBlock("div", _hoisted_1, [ renderSlot(_ctx.$slots, "default") ])) } var VeCheckboxGroup = /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"E:\\Full-stuck-study\\3-Vue\\vue-fantable\\packages\\ve-checkbox-group\\src\\index.vue"]]); VeCheckboxGroup.install = function (Vue) { Vue.component('FanCheckboxGroup', VeCheckboxGroup); Vue.component(VeCheckboxGroup.name, VeCheckboxGroup); }; // prefix const PREFIX_CLS$5 = 've-contextmenu-'; // comps name const COMPS_NAME$7 = { VE_CONTEXTMENU: 'VeContextmenu' }; // init data const INIT_DATA = { // PARENT_ID: 0, PARENT_DEEP: -1 }; // contextmenu types const CONTEXTMENU_NODE_TYPES$1 = { // Separator SEPARATOR: 'SEPARATOR' }; // instance methods // export const INSTANCE_METHODS = { // HIDE_CONTEXTMENU: 'hideContextmenu', // } /* * @clsName * @desc get class name * @param {string} cls - class */ function clsName$6(cls) { return PREFIX_CLS$5 + cls; } // prefix // comps name const COMPS_NAME$6 = { VE_ICON: 'VeIcon' }; // icon name const ICON_NAMES = { FILTER: 'filter', DOUBLE_RIGHT_ARROW: 'double-right-arrow', DOUBLE_LEFT_ARROW: 'double-left-arrow', TOP_ARROW: 'top-arrow', RIGHT_ARROW: 'right-arrow', BOTTOM_ARROW: 'bottom-arrow', LEFT_ARROW: 'left-arrow', SORT_TOP_ARROW: 'sort-top-arrow', SORT_BOTTOM_ARROW: 'sort-bottom-arrow', SEARCH: 'search' }; var ArrowUp = { name: 'IconArrowUp', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M13 30L25 18L37 30", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var ArrowDown = { name: 'IconArrowDown', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M36 18L24 30L12 18", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var ArrowLeft = { name: 'IconArrowLeft', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M31 36L19 24L31 12", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var ArrowRight = { name: 'IconArrowRight', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M19 12L31 24L19 36", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var CaretDownFill = { name: 'IconCaretDownFill', setup() { return () => createVNode("svg", { "xmlns": "http://www.w3.org/2000/svg", "class": "icon icon-tabler icon-tabler-caret-down-filled", "width": "1em", "height": "1em", "viewBox": "0 0 24 24", "stroke-width": "2", "stroke": "currentColor", "fill": "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, [createVNode("path", { "stroke": "none", "d": "M0 0h24v24H0z", "fill": "none" }, null), createVNode("path", { "d": "M18 9c.852 0 1.297 .986 .783 1.623l-.076 .084l-6 6a1 1 0 0 1 -1.32 .083l-.094 -.083l-6 -6l-.083 -.094l-.054 -.077l-.054 -.096l-.017 -.036l-.027 -.067l-.032 -.108l-.01 -.053l-.01 -.06l-.004 -.057v-.118l.005 -.058l.009 -.06l.01 -.052l.032 -.108l.027 -.067l.07 -.132l.065 -.09l.073 -.081l.094 -.083l.077 -.054l.096 -.054l.036 -.017l.067 -.027l.108 -.032l.053 -.01l.06 -.01l.057 -.004l12.059 -.002z", "stroke-width": "0", "fill": "currentColor" }, null)]); } }; var CaretUpFill = { name: 'IconCaretUpFill', setup() { return () => createVNode("svg", { "xmlns": "http://www.w3.org/2000/svg", "class": "icon icon-tabler icon-tabler-caret-up-filled", "width": "1em", "height": "1em", "viewBox": "0 0 24 24", "stroke-width": "2", "stroke": "currentColor", "fill": "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, [createVNode("path", { "stroke": "none", "d": "M0 0h24v24H0z", "fill": "none" }, null), createVNode("path", { "d": "M11.293 7.293a1 1 0 0 1 1.32 -.083l.094 .083l6 6l.083 .094l.054 .077l.054 .096l.017 .036l.027 .067l.032 .108l.01 .053l.01 .06l.004 .057l.002 .059l-.002 .059l-.005 .058l-.009 .06l-.01 .052l-.032 .108l-.027 .067l-.07 .132l-.065 .09l-.073 .081l-.094 .083l-.077 .054l-.096 .054l-.036 .017l-.067 .027l-.108 .032l-.053 .01l-.06 .01l-.057 .004l-.059 .002h-12c-.852 0 -1.297 -.986 -.783 -1.623l.076 -.084l6 -6z", "stroke-width": "0", "fill": "currentColor" }, null)]); } }; var DoubleArrowLeft = { name: 'IconDoubleArrowLeft', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M24 36L12 24L24 12", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null), createVNode("path", { "d": "M36 36L24 24L36 12", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var DoubleArrowRight = { name: 'IconDoubleArrowRight', setup() { return () => createVNode("svg", { "width": "1em", "height": "1em", "viewBox": "0 0 48 48", "fill": "none", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("path", { "d": "M12 12L24 24L12 36", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null), createVNode("path", { "d": "M24 12L36 24L24 36", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, null)]); } }; var FilterVue = { name: 'IconFilter', setup() { return () => createVNode("svg", { "xmlns": "http://www.w3.org/2000/svg", "class": "icon icon-tabler icon-tabler-filter", "width": "1em", "height": "1em", "viewBox": "0 0 24 24", "stroke-width": "1", "stroke": "currentColor", "fill": "none", "stroke-linecap": "round" }, [createVNode("path", { "stroke": "none", "d": "M0 0h24v24H0z", "fill": "none" }, null), createVNode("path", { "d": "M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227z" }, null)]); } }; var SearchVue = { name: 'IconSearch', setup() { return () => createVNode("svg", { "xmlns": "http://www.w3.org/2000/svg", "class": "icon icon-tabler icon-tabler-search", "width": "1em", "height": "1em", "viewBox": "0 0 24 24", "stroke-width": "2", "stroke": "currentColor", "fill": "none", "stroke-linecap": "round", "stroke-linejoin": "round" }, [createVNode("path", { "stroke": "none", "d": "M0 0h24v24H0z", "fill": "none" }, null), createVNode("path", { "d": "M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" }, null), createVNode("path", { "d": "M21 21l-6 -6" }, null)]); } }; var VeIcon = { name: COMPS_NAME$6.VE_ICON, props: { // icon name name: { type: String, required: true }, color: { type: String, default: null }, size: { type: [Number, String], default: '' } }, computed: { // icon style iconStyle() { const { color, size } = this; const result = { color, 'font-size': getValByUnit(size) }; return result; }, iconNameVue() { const { name } = this; // from @P/src/utils/constant ICON_NAMES const map = { filter: FilterVue, 'double-right-arrow': DoubleArrowRight, 'double-left-arrow': DoubleArrowLeft, 'right-arrow': ArrowRight, 'top-arrow': ArrowUp, 'bottom-arrow': ArrowDown, 'left-arrow': ArrowLeft, 'sort-top-arrow': CaretUpFill, 'sort-bottom-arrow': CaretDownFill, search: SearchVue }; return map[name]; } }, methods: {}, render() { const { iconStyle } = this; return createVNode("span", { "style": iconStyle }, [createVNode(this.iconNameVue, null, null)]); } }; VeIcon.install = function (Vue) { Vue.component('FanIcon', VeIcon); Vue.component(VeIcon.name, VeIcon); }; /* fork from: https://github.com/ElemeFE/element */ const trim = function (string) { return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); }; // add class function addClass(el, cls) { if (!el) return; let curClass = el.className; const classes = (cls || '').split(' '); for (let i = 0, j = classes.length; i < j; i++) { const clsName = classes[i]; if (!clsName) continue; if (el.classList) { el.classList.add(clsName); } else if (!hasClass(el, clsName)) { curClass += ' ' + clsName; } } if (!el.classList) { el.className = curClass; } } // remove class function removeClass(el, cls) { if (!el || !cls) return; const classes = cls.split(' '); let curClass = ' ' + el.className + ' '; for (let i = 0, j = classes.length; i < j; i++) { const clsName = classes[i]; if (!clsName) continue; if (el.classList) { el.classList.remove(clsName); } else if (hasClass(el, clsName)) { curClass = curClass.replace(' ' + clsName + ' ', ' '); } } if (!el.classList) { el.className = trim(curClass); } } // has class function hasClass(el, cls) { if (!el || !cls) return false; if (cls.indexOf(' ') !== -1) { throw new Error('className should not contain space.'); } if (el.classList) { return el.classList.contains(cls); } else { return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; } } /* 获取当前元素的偏移(相对于整个document) * offsetTop:元素最顶端距离文档顶端的距离,包含滚动条 * offsetleft:元素最左侧距离文档左侧的距离,包含滚动条 * left:元素最左侧距离文档左侧的距离,不包含滚动条 * top:元素最顶端距离文档顶端的距离,不包含滚动条 * right:元素最右侧距离文档右侧的距离,不包含滚动条 * bottom:元素最底端距离文档底端的距离,不包含滚动条 * right2:元素最左侧距离文档右侧的距离,不包含滚动条 * bottom2:元素最底端距离文档最底部的距离,不包含滚动条 * */ function getViewportOffset(triggerEl) { const doc = document.documentElement; const box = typeof triggerEl.getBoundingClientRect !== 'undefined' ? triggerEl.getBoundingClientRect() : 0; const scrollLeft = (window.scrollX || doc.scrollLeft) - (doc.clientLeft || 0); const scrollTop = (window.scrollY || doc.scrollTop) - (doc.clientTop || 0); const offsetLeft = box.left + window.scrollX; const offsetTop = box.top + window.scrollY; const left = offsetLeft - scrollLeft; const top = offsetTop - scrollTop; return { offsetTop, offsetLeft, left, top, right: doc.clientWidth - box.width - left, bottom: doc.clientHeight - box.height - top, right2: doc.clientWidth - left, bottom2: doc.clientHeight - top }; } /* 获取当前元素的偏移(相对于外层容器) * offsetTop:元素最顶端距离文档顶端的距离,包含滚动条 * offsetleft:元素最左侧距离文档左侧的距离,包含滚动条 * left:元素最左侧距离文档左侧的距离,不包含滚动条 * top:元素最顶端距离文档顶端的距离,不包含滚动条 * right:元素最右侧距离文档右侧的距离,不包含滚动条 * bottom:元素最底端距离文档底端的距离,不包含滚动条 * right2:元素最左侧距离文档右侧的距离,不包含滚动条 * bottom2:元素最底端距离文档最底部的距离,不包含滚动条 * */ function getViewportOffsetWithinContainer(triggerEl, containerEl) { const { offsetTop: tElOffsetTop, offsetLeft: tElOffsetLeft, left: tElLef, top: tElTop, right: tElRight, bottom: tElBottom, right2: tElRight2, bottom2: tElBottom2 } = getViewportOffset(triggerEl); const { offsetTop: cElOffsetTop, offsetLeft: cElOffsetLeft, left: cElLef, top: cElTop, right: cElRight, bottom: cElBottom, right2: cElRight2, bottom2: cElBottom2 } = getViewportOffset(containerEl); return { offsetTop: tElOffsetTop - cElOffsetTop, offsetLeft: tElOffsetLeft - cElOffsetLeft, left: tElLef - cElLef, top: tElTop - cElTop, right: tElRight - cElRight, bottom: tElBottom - cElBottom, right2: tElRight2 - cElRight2, bottom2: tElBottom2 - cElBottom2 }; } /* 获取鼠标相对于文档的坐标 * left:鼠标点击位置距离文档左侧的距离,包含滚动条 * top: 鼠标点击位置距离文档顶端的距离,包含滚动条 * right:鼠标点击位置距离文档右侧的距离,不包含滚动条 * bottom:鼠标点击位置距离文档底端的距离,不包含滚动条 * */ function getMousePosition(event) { let x = 0; let y = 0; const doc = document.documentElement; const body = document.body; if (window.scrollY) { x = window.scrollX; y = window.scrollY; } else { x = (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); y = (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } x += event.clientX; y += event.clientY; const right = doc.clientWidth - event.clientX; const bottom = doc.clientHeight - event.clientY; return { left: x, top: y, right, bottom }; } /** * Returns caret position in text input. * * @author https://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea * @param {HTMLElement} el An element to check. * @returns {number} */ function getCaretPosition(el) { const rootDocument = document; if (el.selectionStart) { return el.selectionStart; } else if (rootDocument.selection) { // IE8 el.focus(); const r = rootDocument.selection.createRange(); if (r === null) { return 0; } const re = el.createTextRange(); const rc = re.duplicate(); re.moveToBookmark(r.getBookmark()); rc.setEndPoint('EndToStart', re); return rc.text.length; } return 0; } /** * Sets caret position in text input. * * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/ * @param {Element} element An element to process. * @param {number} pos The selection start position. * @param {number} endPos The selection end position. */ function setCaretPosition(element, pos, endPos) { if (endPos === undefined) { endPos = pos; } if (element.setSelectionRange) { element.focus(); try { element.setSelectionRange(pos, endPos); } catch (err) { const elementParent = element.parentNode; const parentDisplayValue = elementParent.style.display; elementParent.style.display = 'block'; element.setSelectionRange(pos, endPos); elementParent.style.display = parentDisplayValue; } } } /** * Generate a non duplicate ID */ function getRandomId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); } /* events outside desc: 绑定元素触发的事件不在指定事件中,将会触发。此指令可替代 clickoutside */ var eventsOutside = { mounted(el, binding, vNode) { const { events, callback } = binding.value; if (Array.isArray(events) && events.length && typeof callback === 'function') { const handler = e => { if (!el.contains(e.target) && el !== e.target) { callback(e); } else { return false; } }; el.__eventsOutside__ = handler; events.forEach(eventName => { document.addEventListener(eventName, handler, true); }); } else { const compName = vNode.context.name; console.error(`[events-outside] Please provided 'events' and 'callback' in ${compName}`); } }, unmounted(el, binding, vNode) { const { events } = binding.value; events.forEach(eventName => { document.removeEventListener(eventName, el.__eventsOutside__, true); }); el.__eventsOutside__ = null; } }; var VeContextmenu = { name: COMPS_NAME$7.VE_CONTEXTMENU, directives: { 'events-outside': eventsOutside }, props: { options: { type: Array, required: true }, // eventTarget: contextmenu event will register on it eventTarget: { type: [String, HTMLElement], required: true } }, emits: ['nodeClick'], data() { return { internalOptions: [], panelOptions: [], // event target element eventTargetEl: '', // root contextmenu id rootContextmenuId: '', /* is children panels clicked 如果点击了则不关闭 panels */ isChildrenPanelsClicked: false, /* is panel right direction 决定了子 panel 默认展示方向 */ isPanelRightDirection: true, /* is panels remove 防止hover后菜单被移除,仍然显示子集菜单的问题 */ isPanelsEmptyed: true }; }, computed: { // active menus ids activeMenuIds() { const { panelOptions } = this; return panelOptions.map(x => x.parentId); } }, watch: { options: { handler(val) { if (Array.isArray(val) && val.length > 0) { /* 如果配置项修改,则重新销毁并创建 */ this.removeOrEmptyPanels(true); this.rootContextmenuId = this.getRandomIdWithPrefix(); this.createInternalOptions(); this.createPanelOptions({ options: this.internalOptions }); this.resetContextmenu(); this.addRootContextmenuPanelToBody(); } }, immediate: true }, eventTarget: { handler(val) { if (val) { this.registerContextmenuEvent(); } }, immediate: true } }, created() { this.debounceCreatePanelByHover = debounce(this.createPanelByHover, 300); }, mounted() { this.addRootContextmenuPanelToBody(); }, unmounted() { this.removeContextmenuEvent(); this.removeOrEmptyPanels(true); }, methods: { // get random id getRandomIdWithPrefix() { return clsName$6(getRandomId()); }, // has children hasChildren(option) { return Array.isArray(option.children) && option.children.length; }, // get panel option by menu id getPanelOptionByMenuId(options, menuId) { for (let i = 0; i < options.length; i++) { if (options[i].id === menuId) { return options[i].children; } if (options[i].children) { const panelOption = this.getPanelOptionByMenuId(options[i].children, menuId); if (panelOption) return panelOption; } } }, // get parent contextmenu panel element getParentContextmenuPanelEl(contextmenuPanelId) { let result; const { panelOptions } = this; const panelIndex = panelOptions.findIndex(x => x.parentId === contextmenuPanelId); if (panelIndex > 0) { // preview panel's panelId const parentPanelId = panelOptions[panelIndex - 1].parentId; result = document.querySelector(`#${parentPanelId}`); } return result; }, // create panel by hover createPanelByHover({ event, menu }) { const { internalOptions, panelOptions } = this; // 如果被移除则不创建 if (this.isPanelsEmptyed) { return false; } // has already exists if (panelOptions.findIndex(x => x.parentId === menu.id) > -1) { return false; } // remove panels // 移除 panel 深度大于等于当前悬浮菜单的。从后往前删除 const deletePanelDeeps = panelOptions.filter(x => x.parentDeep >= menu.deep).map(x => x.parentDeep).reverse(); if (deletePanelDeeps.length) { for (let i = deletePanelDeeps.length - 1; i >= 0; i--) { const delIndex = panelOptions.findIndex(x => x.parentDeep === deletePanelDeeps[i]); if (delIndex > -1) { this.panelOptions.splice(delIndex, 1); } } } const panelOption = this.getPanelOptionByMenuId(internalOptions, menu.id); if (panelOption) { this.createPanelOptions({ options: panelOption, currentMenu: menu }); this.$nextTick(() => { this.addContextmenuPanelToBody({ contextmenuId: menu.id }); this.showContextmenuPanel({ event, contextmenuId: menu.id }); }); } }, // create panels option createPanelOptions({ options, currentMenu }) { const { hasChildren, rootContextmenuId } = this; if (Array.isArray(options)) { // const menus = options.map(option => { return { hasChildren: hasChildren(option), ...option }; }); this.panelOptions.push({ parentId: currentMenu ? currentMenu.id : rootContextmenuId, parentDeep: currentMenu ? currentMenu.deep : INIT_DATA.PARENT_DEEP, menus }); } }, // create internal options recursion createInternalOptionsRecursion(options, deep = 0) { options.id = this.getRandomIdWithPrefix(); options.deep = deep; deep++; if (Array.isArray(options.children)) { options.children.map(option => { return this.createInternalOptionsRecursion(option, deep); }); } return options; }, // create internal options createInternalOptions() { this.internalOptions = deepClone(this.options).map(option => { return this.createInternalOptionsRecursion(option); }); }, // show root contextmenu panel showRootContextmenuPanel(event) { event.preventDefault(); const { rootContextmenuId } = this; if (rootContextmenuId) { // refresh contextmenu this.resetContextmenu(); this.showContextmenuPanel({ event, contextmenuId: rootContextmenuId, isRootContextmenu: true }); this.isPanelsEmptyed = false; } }, // show contextmenu panel showContextmenuPanel({ event, contextmenuId, isRootContextmenu }) { const { getParentContextmenuPanelEl } = this; const contextmenuPanelEl = document.querySelector(`#${contextmenuId}`); if (contextmenuPanelEl) { // remove first contextmenuPanelEl.innerHTML = ''; contextmenuPanelEl.appendChild(this.$refs[contextmenuId]); contextmenuPanelEl.style.position = 'absolute'; contextmenuPanelEl.classList.add(clsName$6('popper')); const { width: currentPanelWidth, height: currentPanelHeight } = contextmenuPanelEl.getBoundingClientRect(); if (isRootContextmenu) { const { left: clickLeft, top: clickTop, right: clickRight, bottom: clickBottom } = getMousePosition(event); let panelX = 0; let panelY = 0; // 右方宽度够显示 if (clickRight >= currentPanelWidth) { panelX = clickLeft; this.isPanelRightDirection = true; } else { // 右方宽度不够显示在鼠标点击左方 panelX = clickLeft - currentPanelWidth; this.isPanelRightDirection = false; } // 下方高度够显示 if (clickBottom >= currentPanelHeight) { panelY = clickTop; } else { // 下方高度不够显示在鼠标点击上方 panelY = clickTop - currentPanelHeight; } contextmenuPanelEl.style.left = panelX + 'px'; contextmenuPanelEl.style.top = panelY + 'px'; } else { const parentContextmenuPanelEl = getParentContextmenuPanelEl(contextmenuId); if (parentContextmenuPanelEl) { const { left: parentPanelLeft, right: parentPanelRight } = getViewportOffset(parentContextmenuPanelEl); const { top: clickTop, bottom: clickBottom } = getMousePosition(event); const { width: parentPanelWidth } = parentContextmenuPanelEl.getBoundingClientRect(); let panelX = 0; let panelY = 0; // 如果默认展示在右方向 if (this.isPanelRightDirection) { // 右方宽度够显示 if (parentPanelRight >= currentPanelWidth) { panelX = parentPanelLeft + parentPanelWidth; } else { // 右方宽度不够显示在鼠标点击左方 panelX = parentPanelLeft - parentPanelWidth; } } else { // 如果默认展示在左方向 // 左方宽度够显示 if (parentPanelLeft >= currentPanelWidth) { panelX = parentPanelLeft - parentPanelWidth; } else { // 左方宽度不够显示在鼠标点击右方 panelX = parentPanelLeft + parentPanelWidth; } } // 下方高度够显示 if (clickBottom >= currentPanelHeight) { panelY = clickTop; } else { // 下方高度不够显示在鼠标点击上方 panelY = clickTop - currentPanelHeight; } contextmenuPanelEl.style.left = panelX + 'px'; contextmenuPanelEl.style.top = panelY + 'px'; } } } }, // empty contextmenu panels emptyContextmenuPanels() { // wait for children panel clicked by setTimeout // 如果点击的是非 root panel 不关闭 setTimeout(() => { if (this.isChildrenPanelsClicked) { this.isChildrenPanelsClicked = false; } else { this.removeOrEmptyPanels(); this.isPanelsEmptyed = true; } }); }, // remove or empty panels removeOrEmptyPanels(isRemove) { const { panelOptions } = this; panelOptions.forEach(panelOption => { const contextmenuPanelEl = document.querySelector(`#${panelOption.parentId}`); if (contextmenuPanelEl) { if (isRemove) { contextmenuPanelEl.remove(); } else { contextmenuPanelEl.innerHTML = ''; } } }); }, // reset contextmeny resetContextmenu() { this.panelOptions = []; this.createPanelOptions({ options: this.internalOptions }); }, // add context menu panel to body addContextmenuPanelToBody({ contextmenuId }) { const contextmenuPanelEl = document.querySelector(`#${contextmenuId}`); if (contextmenuPanelEl) { return false; } else { const containerEl = document.createElement('div'); containerEl.setAttribute('id', contextmenuId); document.body.appendChild(containerEl); } }, // add root contextmenu panel to body addRootContextmenuPanelToBody() { if (this.rootContextmenuId) { this.addContextmenuPanelToBody({ contextmenuId: this.rootContextmenuId }); } }, // register contextmenu event registerContextmenuEvent() { const { eventTarget } = this; if (typeof eventTarget === 'string' && eventTarget.length > 0) { this.eventTargetEl = document.querySelector(eventTarget); } else { this.eventTargetEl = eventTarget; } if (this.eventTargetEl) { // contextmenu is on the current element this.eventTargetEl.addEventListener('contextmenu', this.showRootContextmenuPanel); } }, // unregister contextmen event removeContextmenuEvent() { if (this.eventTargetEl) { this.eventTargetEl.removeEventListener('contextmenu', this.showRootContextmenuPanel); } }, // hide contextmenu // INSTANCE_METHODS.HIDE_CONTEXTMENU hideContextmenu() { this.emptyContextmenuPanels(); } }, render() { const { panelOptions, activeMenuIds, hasChildren, emptyContextmenuPanels, debounceCreatePanelByHover } = this; const contextmenuProps = { class: ['ve-contextmenu'], style: { display: 'none' } }; return createVNode("div", contextmenuProps, [panelOptions.map((panelOption, panelIndex) => { const contextmenuPanelProps = { ref: panelOption.parentId, class: clsName$6('panel'), onClick: () => { if (panelIndex !== 0) { this.isChildrenPanelsClicked = true; } }, onContextmenu: e => { e.preventDefault(); } }; return withDirectives(createVNode("div", contextmenuPanelProps, [createVNode("ul", { "class": clsName$6('list') }, [panelOption.menus.map(menu => { let contextmenuNodeProps; if (menu.type !== CONTEXTMENU_NODE_TYPES$1.SEPARATOR) { const nodeActive = clsName$6('node-active'); const nodeDisable = clsName$6('node-disabled'); contextmenuNodeProps = { class: { [clsName$6('node')]: true, [nodeActive]: activeMenuIds.includes(menu.id), [nodeDisable]: menu.disabled }, onMouseover: event => { // disable if (!menu.disabled) { debounceCreatePanelByHover({ event, menu }); } }, onClick: () => { if (!menu.disabled && !hasChildren(menu)) { // EMIT_EVENTS.ON_NODE_CLICK, this.$emit('nodeClick', menu.type); setTimeout(() => { emptyContextmenuPanels(); }, 50); } } }; } else { // separator contextmenuNodeProps = { class: { [clsName$6('node-separator')]: true } }; } if (menu.type !== CONTEXTMENU_NODE_TYPES$1.SEPARATOR) { return createVNode("li", contextmenuNodeProps, [createVNode("span", { "class": clsName$6('node-label') }, [menu.label]), menu.hasChildren && createVNode(VeIcon, { "class": clsName$6('node-icon-postfix'), "name": ICON_NAMES.RIGHT_ARROW }, null)]); } else { return createVNode("li", contextmenuNodeProps, null); } })])]), [[resolveDirective("events-outside"), { events: ['click'], callback: e => { // only for root panel if (panelIndex === 0) { emptyContextmenuPanels(); } } }]]); })]); } }; VeContextmenu.install = function (Vue) { Vue.component('FanContextmenu', VeContextmenu); Vue.component(VeContextmenu.name, VeContextmenu); }; var clickoutside = { mounted(el, binding, vNode) { if (typeof binding.value !== 'function') { let msg = `in [clickoutside] directives, provided expression '${binding.expression}' is not a function `; const compName = vNode.context.name; if (compName) { msg += `in ${compName}`; } console.error(msg); } const handler = e => { if (!el.contains(e.target) && el !== e.target) { binding.value(e); } else { return false; } }; el.__clickOutSide__ = handler; document.addEventListener('click', handler, true); }, unmounted(el) { document.removeEventListener('click', el.__clickOutSide__, true); el.__clickOutSide__ = null; } }; // prefix const PREFIX_CLS$4 = 've-radio-'; // comps name const COMPS_NAME$5 = { VE_RADIO: 'VeRadio' }; /* * @clsName * @desc get class name * @param {string} cls - class */ function clsName$5(cls) { return PREFIX_CLS$4 + cls; } var VeRadio = { name: COMPS_NAME$5.VE_RADIO, props: { // 当前 checkbox 选中状态,实现 v-model modelValue: { type: [String, Number, Boolean], default: null }, label: { type: String, default: null }, // is disabled checked disabled: Boolean, // 是否是可控组件 isControlled: { type: Boolean, default: false }, // isControlled 为true 时生效 isSelected: { type: Boolean, default: false } }, emits: ['radioChange', 'update:modelValue'], data() { return { // 当前checkbox 选中状态 model: this.modelValue }; }, computed: { radioClass() { const disableState = this.disabled; const disabled = clsName$5('disabled'); return [clsName$5('container'), { [clsName$5('checked')]: this.internalIsSelected, [disabled]: disableState }]; }, // 是否选中 internalIsSelected() { return this.isControlled ? this.isSelected : this.model; } }, watch: { modelValue() { this.updateModelBySingle(); } }, created() { this.initModel(); }, methods: { // checked change initModel() { if (hasValue(this.modelValue)) { this.internalOptions = [].concat(this.modelValue); } }, checkedChange(event) { if (this.disabled) { return false; } const isChecked = event.target.checked; if (!this.isControlled) { this.$emit('update:modelValue', isChecked); } // this.$emit(EMIT_EVENTS.ON_RADIO_CHANGE, isChecked) this.$emit('radioChange', isChecked); }, // get label content getLabelContent() { const { label, $slots } = this; return label || $slots.default; }, // 通过单选更新 model updateModelBySingle() { if (!this.disabled) { this.model = this.modelValue; } } }, render() { const { label, radioClass, checkedChange, getLabelContent, internalIsSelected } = this; return createVNode("label", { "class": 've-radio' }, [createVNode("span", { "class": radioClass }, [createVNode("input", { "checked": internalIsSelected, "class": clsName$5('input'), "type": "radio", "value": label, "onChange": checkedChange }, null), createVNode("span", { "class": clsName$5('inner') }, null)]), createVNode("span", { "class": clsName$5('label') }, [getLabelContent()])]); } }; VeRadio.install = function (Vue) { Vue.component('FanRadio', VeRadio); Vue.component(VeRadio.name, VeRadio); }; // emit events // comps name const COMPS_NAME$4 = { VE_DROPDOWN: 'VeDropdown' }; /* * @clsName * @desc get class name * @param {string} cls - class */ function clsName$4(cls) { return 've-dropdown-' + cls; } var VeDropdown = { name: COMPS_NAME$4.VE_DROPDOWN, directives: { 'click-outside': clickoutside }, props: { // 如果是 select 组件将特殊处理 isSelect: { type: Boolean, default: false }, showOperation: { type: Boolean, default: false }, width: { type: Number, default: 90 }, // select 的最大宽度(超出隐藏) maxWidth: { type: Number, default: 0 }, // max height maxHeight: { type: Number, default: 1000 }, // 如果为true 会包含 checkbox isMultiple: { type: Boolean, default: false }, // 用户传入 v-model 的值 [{value/label/selected}] modelValue: { type: [Array], default: null }, // 文本居中方式 left|center|right textAlign: { type: String, default: 'left' }, // 是否支持输入input isInput: { type: Boolean, default: false }, // confirm filter text confirmFilterText: { type: String, default: '' }, // confirm filter text resetFilterText: { type: String, default: '' }, // hide by single selection item click hideByItemClick: { type: Boolean, default: false }, // is show radio when single selection showRadio: { type: Boolean, default: false }, // 当 isControlled=true ,visible 生效 visible: { type: Boolean, default: false }, // is controlled isControlled: { type: Boolean, default: false }, // is custom content isCustomContent: { type: Boolean, default: false }, // instance between dropdown items and trigger element defaultInstance: { type: Number, default: 5 }, // popper append to element popperAppendTo: { type: [String, HTMLElement], default: function () { return document.body; } }, /* before visible change 如果返回false 则阻止显示或者关闭 */ beforeVisibleChange: { type: Function, default: null } }, emits: ['update:modelValue', 'dropdownVisibleChange', 'filterConfirm', 'filterReset', 'itemSelectChange'], data() { return { internalVisible: false, internalOptions: [], inputValue: '', // 是否显示触发器被点击了(被点击将忽略 clickOutside 事件) isDropdownShowTriggerClicked: false, // root id rootId: '', // dropdown items panel id dropdownItemsPanelId: '', // 弹出被添加到的目标元素 popperAppendToEl: null, // 弹出被添加到的目标元素标签名称 appendToElTagName: null }; }, computed: { // is dropdown visible isDropdownVisible() { return this.isControlled ? this.visible : this.internalVisible; }, // 获取最大宽度(不设置则是无穷大) getMaxWidth() { let result = Infinity; const maxWidth = this.maxWidth; const width = this.width; if (maxWidth && maxWidth > 0 && maxWidth > width) { result = maxWidth; } return result; }, // selected labels selectedLabels() { return this.internalOptions.filter(x => x.selected).map(x => { if (x.selected) { return x.label; } return null; }); }, // operation buttons class operationFilterClass() { let result = null; result = { [clsName$4('filter-disable')]: this.selectedLabels.length === 0 }; return result; }, // dropdown items class dropdownItemsClass() { const ddShow = clsName$4('dd-show'); return { [clsName$4('dd')]: true, [ddShow]: this.isDropdownVisible }; } }, watch: { modelValue() { this.initModel(); }, visible: { handler(visible) { const { isControlled, showDropDown, hideDropDown } = this; // deal after mounted hook setTimeout(() => { if (isControlled) { if (visible) { showDropDown(); } else { hideDropDown(); } } }); }, immediate: true } }, created() { this.initModel(); }, mounted() { this.addRootElementToElement(); this.$nextTick(() => { const targetEl = this.appendToElTagName