UNPKG

vxe-pc-ui

Version:
592 lines (591 loc) 25.2 kB
import { defineComponent, ref, h, reactive, provide, computed, watch, nextTick, onMounted, onUnmounted, onActivated } from 'vue'; import { getConfig, getIcon, createEvent, globalEvents, globalResize, renderEmptyElement } from '../../ui'; import { getSlotVNs } from '../../ui/src/vn'; import { toCssUnit, isScale, addClass, removeClass } from '../../ui/src/dom'; import { getGlobalDefaultConfig } from '../../ui/src/utils'; import { errLog } from '../../ui/src/log'; import XEUtils from 'xe-utils'; export default defineComponent({ name: 'VxeSplit', props: { width: [Number, String], height: [Number, String], vertical: { type: Boolean, default: () => getConfig().split.vertical }, border: { type: Boolean, default: () => getConfig().split.border }, padding: { type: Boolean, default: () => getConfig().split.padding }, resize: { type: Boolean, default: () => getConfig().split.resize }, items: Array, itemConfig: Object, barConfig: Object, actionConfig: Object }, emits: [ 'action-dblclick', 'action-click', 'toggle-expand', 'resize-start', 'resize-drag', 'resize-end' ], setup(props, context) { const { emit, slots } = context; const xID = XEUtils.uniqueId(); const refElem = ref(); const reactData = reactive({ staticItems: [], itemList: [] }); const internalData = {}; const computeItemOpts = computed(() => { return Object.assign({}, getConfig().split.itemConfig, props.itemConfig); }); const computeBarOpts = computed(() => { return Object.assign({}, getConfig().split.barConfig, props.barConfig); }); const computeActionOpts = computed(() => { return Object.assign({}, getConfig().split.actionConfig, props.actionConfig); }); const computeIsFoldNext = computed(() => { const actionOpts = computeActionOpts.value; return actionOpts.direction === 'next'; }); const computeVisibleItems = computed(() => { return reactData.itemList.filter(item => item.isVisible); }); const computeBarStyle = computed(() => { const barOpts = computeBarOpts.value; const { width, height } = barOpts; const stys = {}; if (height) { stys.height = toCssUnit(height); } if (width) { stys.width = toCssUnit(width); } return stys; }); const computeMaps = { computeItemOpts, computeBarOpts, computeActionOpts, computeIsFoldNext }; const refMaps = { refElem }; const $xeSplit = { xID, props, context, reactData, internalData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $split: $xeSplit }, params)); }; const callSlot = (slotFunc, params) => { if (slotFunc) { if (XEUtils.isString(slotFunc)) { slotFunc = slots[slotFunc] || null; } if (XEUtils.isFunction(slotFunc)) { return getSlotVNs(slotFunc(params)); } } return []; }; const getDefaultActionIcon = (item) => { const { vertical } = props; const { showAction, isExpand } = item; const isFoldNext = computeIsFoldNext.value; const topIcon = 'SPLIT_TOP_ACTION'; const bottomIcon = 'SPLIT_BOTTOM_ACTION'; const leftIcon = 'SPLIT_LEFT_ACTION'; const rightIcon = 'SPLIT_RIGHT_ACTION'; if (showAction) { let iconName = ''; if (isFoldNext) { if (vertical) { iconName = isExpand ? bottomIcon : topIcon; } else { iconName = isExpand ? rightIcon : leftIcon; } } else { if (vertical) { iconName = isExpand ? topIcon : bottomIcon; } else { iconName = isExpand ? leftIcon : rightIcon; } } if (iconName) { return getIcon()[iconName]; } } return ''; }; const reset = () => { const { itemList } = reactData; itemList.forEach(item => { item.isExpand = true; item.isVisible = true; item.foldHeight = 0; item.foldWidth = 0; item.resizeHeight = 0; item.resizeWidth = 0; }); return nextTick(); }; const handleLoadItem = (list, isReset) => { const { staticItems } = reactData; const itemDef = { isVisible: true, isExpand: true, renderWidth: 0, resizeWidth: 0, foldWidth: 0, renderHeight: 0, resizeHeight: 0, foldHeight: 0 }; reactData.itemList = list.map(item => { if (item.slots) { XEUtils.each(item.slots, (func) => { if (!XEUtils.isFunction(func)) { if (!slots[func]) { errLog('vxe.error.notSlot', [func]); } } }); } return Object.assign({}, isReset ? null : itemDef, item, isReset ? itemDef : null, { id: XEUtils.uniqueId() }); }); if (staticItems.length) { errLog('vxe.error.errConflicts', ['<vxe-split-pane ...>', 'items']); } return recalculate(); }; const loadItem = (list) => { return handleLoadItem(list || [], false); }; const reloadItem = (list) => { return handleLoadItem(list || [], true); }; const recalculate = () => { return nextTick().then(() => { const { vertical } = props; const { itemList } = reactData; const el = refElem.value; if (!el) { return; } const wrapperWidth = el.clientWidth; const wrapperHeight = el.clientHeight; if (!wrapperWidth || !wrapperHeight) { return; } const itemOpts = computeItemOpts.value; const allMinWidth = XEUtils.toNumber(itemOpts.minWidth); const allMinHeight = XEUtils.toNumber(itemOpts.minHeight); const residueItems = []; if (vertical) { let countHeight = 0; itemList.forEach(item => { const { height } = item; let itemHeight = 0; if (height) { if (isScale(height)) { itemHeight = wrapperHeight * XEUtils.toNumber(height); } else { itemHeight = XEUtils.toNumber(height); } item.renderHeight = itemHeight; } else { residueItems.push(item); } countHeight += itemHeight; }); if (residueItems.length) { const reMeanHeight = (wrapperHeight - countHeight) / residueItems.length; residueItems.forEach(item => { item.renderHeight = Math.max(XEUtils.toNumber(getGlobalDefaultConfig(item.minHeight, allMinHeight)), reMeanHeight); }); } } else { let countWidth = 0; itemList.forEach(item => { const { width } = item; let itemWidth = 0; if (width) { if (isScale(width)) { itemWidth = wrapperWidth * XEUtils.toNumber(width); } else { itemWidth = XEUtils.toNumber(width); } item.renderWidth = itemWidth; } else { residueItems.push(item); } countWidth += itemWidth; }); if (residueItems.length) { const reMeanWidth = (wrapperWidth - countWidth) / residueItems.length; residueItems.forEach(item => { item.renderWidth = Math.max(XEUtils.toNumber(getGlobalDefaultConfig(item.minWidth, allMinWidth)), reMeanWidth); }); } } }); }; const dragEvent = (evnt) => { const { resize, vertical } = props; const { itemList } = reactData; if (!resize) { return; } evnt.preventDefault(); const barEl = evnt.currentTarget; const handleEl = barEl.parentElement; const el = refElem.value; if (!el) { return; } const itemId = handleEl.getAttribute('itemid'); const itemIndex = XEUtils.findIndexOf(itemList, item => item.id === itemId); const item = itemList[itemIndex]; if (!item) { return; } if (!item.isExpand) { return; } const isFoldNext = computeIsFoldNext.value; const itemOpts = computeItemOpts.value; const allMinWidth = XEUtils.toNumber(itemOpts.minWidth); const allMinHeight = XEUtils.toNumber(itemOpts.minHeight); const targetItem = itemList[itemIndex + (isFoldNext ? 1 : -1)]; const targetItemEl = targetItem ? el.querySelector(`.vxe-split-pane[itemid="${targetItem.id}"]`) : null; const currItemEl = item ? el.querySelector(`.vxe-split-pane[itemid="${item.id}"]`) : null; const targetWidth = targetItemEl ? targetItemEl.clientWidth : 0; const currWidth = currItemEl ? currItemEl.clientWidth : 0; const targetHeight = targetItemEl ? targetItemEl.clientHeight : 0; const currHeight = currItemEl ? currItemEl.clientHeight : 0; const targetMinWidth = XEUtils.toNumber(targetItem ? getGlobalDefaultConfig(targetItem.minWidth, allMinWidth) : allMinWidth); const currMinWidth = XEUtils.toNumber(getGlobalDefaultConfig(item.minWidth, allMinWidth)); const targetMinHeight = XEUtils.toNumber(targetItem ? getGlobalDefaultConfig(targetItem.minHeight, allMinHeight) : allMinHeight); const currMinHeight = XEUtils.toNumber(getGlobalDefaultConfig(item.minHeight, allMinHeight)); const disX = evnt.clientX; const disY = evnt.clientY; addClass(el, 'is--drag'); document.onmousemove = (evnt) => { evnt.preventDefault(); if (vertical) { const offsetTop = isFoldNext ? (disY - evnt.clientY) : (evnt.clientY - disY); if (offsetTop > 0) { if (targetItem) { if (currHeight - offsetTop >= currMinHeight) { const reHeight = currHeight - offsetTop; targetItem.resizeHeight = targetHeight + offsetTop; item.resizeHeight = reHeight; dispatchEvent('resize-drag', { item, name: item.name, offsetHeight: offsetTop, resizeHeight: reHeight, offsetWidth: 0, resizeWidth: 0 }, evnt); } } } else { if (targetItem) { if (targetHeight + offsetTop >= targetMinHeight) { const reHeight = currHeight - offsetTop; targetItem.resizeHeight = targetHeight + offsetTop; item.resizeHeight = reHeight; dispatchEvent('resize-drag', { item, name: item.name, offsetHeight: offsetTop, resizeHeight: reHeight, offsetWidth: 0, resizeWidth: 0 }, evnt); } } } } else { const offsetLeft = isFoldNext ? (disX - evnt.clientX) : (evnt.clientX - disX); if (offsetLeft > 0) { if (targetItem) { if (currWidth - offsetLeft >= currMinWidth) { const reWidth = currWidth - offsetLeft; targetItem.resizeWidth = targetWidth + offsetLeft; item.resizeWidth = reWidth; dispatchEvent('resize-drag', { item, name: item.name, offsetHeight: 0, resizeHeight: 0, offsetWidth: offsetLeft, resizeWidth: reWidth }, evnt); } } } else { if (targetItem) { if (targetWidth + offsetLeft >= targetMinWidth) { const reWidth = currWidth - offsetLeft; targetItem.resizeWidth = targetWidth + offsetLeft; item.resizeWidth = reWidth; dispatchEvent('resize-drag', { item, name: item.name, offsetHeight: 0, resizeHeight: 0, offsetWidth: offsetLeft, resizeWidth: reWidth }, evnt); } } } } }; document.onmouseup = (evnt) => { document.onmousemove = null; document.onmouseup = null; removeClass(el, 'is--drag'); dispatchEvent('resize-end', { item, name: item.name, resizeHeight: item.resizeHeight, resizeWidth: item.resizeWidth }, evnt); recalculate(); }; dispatchEvent('resize-start', { item, name: item.name }, evnt); }; const handleItemActionEvent = (evnt) => { const el = refElem.value; if (!el) { return; } const { vertical } = props; const { itemList } = reactData; const isFoldNext = computeIsFoldNext.value; const btnEl = evnt.currentTarget; const handleEl = btnEl.parentElement; const itemId = handleEl.getAttribute('itemid'); const itemIndex = XEUtils.findIndexOf(itemList, item => item.id === itemId); const item = itemList[itemIndex]; const targetItem = itemList[itemIndex + (isFoldNext ? 1 : -1)]; if (item) { const { showAction, isExpand } = item; if (showAction) { if (vertical) { if (targetItem) { targetItem.isVisible = !isExpand; targetItem.foldHeight = 0; item.isExpand = !isExpand; item.isVisible = true; item.foldHeight = isExpand ? (targetItem.resizeHeight || targetItem.renderHeight) + (item.resizeHeight || item.renderHeight) : 0; } } else { if (targetItem) { targetItem.isVisible = !isExpand; targetItem.foldWidth = 0; item.isExpand = !isExpand; item.isVisible = true; item.foldWidth = isExpand ? (targetItem.resizeWidth || targetItem.renderWidth) + (item.resizeWidth || item.renderWidth) : 0; } } dispatchEvent('toggle-expand', { item, name: item.name, targetItem, targetName: targetItem ? targetItem.name : '', expanded: item.isExpand }, evnt); recalculate(); } } }; const handleActionDblclickEvent = (evnt) => { const { itemList } = reactData; const actionOpts = computeActionOpts.value; const btnEl = evnt.currentTarget; const handleEl = btnEl.parentElement; const itemId = handleEl.getAttribute('itemid'); const itemIndex = XEUtils.findIndexOf(itemList, item => item.id === itemId); const item = itemList[itemIndex]; if (actionOpts.trigger === 'dblclick') { handleItemActionEvent(evnt); } dispatchEvent('action-dblclick', { item, name: item ? item.name : '' }, evnt); }; const handleActionClickEvent = (evnt) => { const { itemList } = reactData; const actionOpts = computeActionOpts.value; const btnEl = evnt.currentTarget; const handleEl = btnEl.parentElement; const itemId = handleEl.getAttribute('itemid'); const itemIndex = XEUtils.findIndexOf(itemList, item => item.id === itemId); const item = itemList[itemIndex]; if (actionOpts.trigger !== 'dblclick') { handleItemActionEvent(evnt); } dispatchEvent('action-click', { item, name: item ? item.name : '' }, evnt); }; const handleGlobalResizeEvent = () => { recalculate(); }; const splitMethods = { dispatchEvent, recalculate, reset, loadItem, reloadItem }; const splitPrivateMethods = {}; Object.assign($xeSplit, splitMethods, splitPrivateMethods); const renderHandleBar = (item) => { const barStyle = computeBarStyle.value; const actionOpts = computeActionOpts.value; const isFoldNext = computeIsFoldNext.value; const { id, isExpand, showAction } = item; return h('div', { itemid: id, class: ['vxe-split-pane-handle', isFoldNext ? 'to--next' : 'to--prev'] }, [ h('div', { class: 'vxe-split-pane-handle-bar', style: barStyle, onMousedown: dragEvent }), showAction ? h('span', { class: 'vxe-split-pane-action-btn', onDblclick: handleActionDblclickEvent, onClick: handleActionClickEvent }, [ h('i', { class: (isExpand ? actionOpts.openIcon : actionOpts.closeIcon) || getDefaultActionIcon(item) }) ]) : renderEmptyElement($xeSplit) ]); }; const renderItems = () => { const { border, padding, resize, vertical } = props; const { itemList } = reactData; const visibleItems = computeVisibleItems.value; const isFoldNext = computeIsFoldNext.value; const itemVNs = []; itemList.forEach((item, index) => { const { id, name, slots, renderHeight, resizeHeight, foldHeight, renderWidth, resizeWidth, foldWidth, isVisible, isExpand } = item; const defaultSlot = slots ? slots.default : null; const stys = {}; const itemWidth = isVisible ? (foldWidth || resizeWidth || renderWidth) : 0; const itemHeight = isVisible ? (foldHeight || resizeHeight || renderHeight) : 0; // 当只剩下一个可视区自动占用 100% if (vertical) { if (itemHeight) { stys.height = visibleItems.length === 1 ? '100%' : toCssUnit(itemHeight); } } else { if (itemWidth) { stys.width = visibleItems.length === 1 ? '100%' : toCssUnit(itemWidth); } } itemVNs.push(h('div', { itemid: id, class: ['vxe-split-pane', vertical ? 'is--vertical' : 'is--horizontal', { 'is--resize': resize, 'is--padding': padding, 'is--border': border, 'is--height': itemHeight, 'is--width': itemWidth, 'is--fill': isVisible && !itemHeight && !itemWidth, 'is--handle': index > 0, 'is--expand': isExpand, 'is--hidden': !isVisible }], style: stys }, [ index && !isFoldNext ? renderHandleBar(item) : renderEmptyElement($xeSplit), h('div', { itemid: id, class: 'vxe-split-pane--wrapper' }, [ h('div', { class: 'vxe-split-pane--inner' }, defaultSlot ? callSlot(defaultSlot, { name, isVisible, isExpand }) : []) ]), isFoldNext && index < itemList.length - 1 ? renderHandleBar(item) : renderEmptyElement($xeSplit) ])); }); return h('div', { class: 'vxe-split-wrapper' }, itemVNs); }; const renderVN = () => { const { vertical, width, height } = props; const defaultSlot = slots.default; const stys = {}; if (height) { stys.height = toCssUnit(height); } if (width) { stys.width = toCssUnit(width); } return h('div', { ref: refElem, class: ['vxe-split', vertical ? 'is--vertical' : 'is--horizontal'], style: stys }, [ h('div', { class: 'vxe-split-slots' }, defaultSlot ? defaultSlot({}) : []), renderItems() ]); }; const itemFlag = ref(0); watch(() => props.items ? props.items.length : -1, () => { itemFlag.value++; }); watch(() => props.items, () => { itemFlag.value++; }); watch(itemFlag, () => { loadItem(props.items || []); }); watch(() => reactData.staticItems, (val) => { if (props.items && props.items.length) { errLog('vxe.error.errConflicts', ['<vxe-split-pane ...>', 'items']); } reactData.itemList = val; recalculate(); }); let resizeObserver; onMounted(() => { nextTick(() => { recalculate(); }); const el = refElem.value; if (el) { resizeObserver = globalResize.create(() => { recalculate(); }); resizeObserver.observe(el); } globalEvents.on($xeSplit, 'resize', handleGlobalResizeEvent); }); onUnmounted(() => { if (resizeObserver) { resizeObserver.disconnect(); } globalEvents.off($xeSplit, 'resize'); }); onActivated(() => { recalculate(); }); if (props.items) { loadItem(props.items); } provide('$xeSplit', $xeSplit); $xeSplit.renderVN = renderVN; return $xeSplit; }, render() { return this.renderVN(); } });