UNPKG

jjb-lc-designable

Version:

基于alibaba-designable源码二次封装的表单设计器。

338 lines (334 loc) 11.4 kB
import { calcBoundingRect, calcElementLayout, isHTMLElement, isPointInRect, requestIdle, cancelIdle, globalThisPolyfill, Rect, isRectInRect } from 'jjb-lc-designable/shared'; import { action, define, observable } from 'jjb-lc-formily/reactive'; /** * 视口模型 */ export class Viewport { scrollX = 0; scrollY = 0; width = 0; height = 0; mounted = false; nodeElementsStore = {}; constructor(props) { this.workspace = props.workspace; this.engine = props.engine; this.moveSensitive = props.moveSensitive ?? false; this.moveInsertionType = props.moveInsertionType ?? 'all'; this.viewportElement = props.viewportElement; this.contentWindow = props.contentWindow; this.nodeIdAttrName = props.nodeIdAttrName; this.digestViewport(); this.makeObservable(); this.attachEvents(); } get isScrollLeft() { return this.scrollX === 0; } get isScrollTop() { return this.scrollY === 0; } get isScrollRight() { if (this.isIframe) { return this.width + this.contentWindow.scrollX >= this.contentWindow?.document?.body?.scrollWidth; } else if (this.viewportElement) { return this.viewportElement.offsetWidth + this.scrollX >= this.viewportElement.scrollWidth; } } get isScrollBottom() { if (this.isIframe) { if (!this.contentWindow?.document?.body) return false; return this.height + this.contentWindow.scrollY >= this.contentWindow.document.body.scrollHeight; } else if (this.viewportElement) { if (!this.viewportElement) return false; return this.viewportElement.offsetHeight + this.viewportElement.scrollTop >= this.viewportElement.scrollHeight; } } get viewportRoot() { return this.isIframe ? this.contentWindow?.document?.body : this.viewportElement; } get isMaster() { return this.contentWindow === globalThisPolyfill; } get isIframe() { return !!this.contentWindow?.frameElement && !this.isMaster; } get scrollContainer() { return this.isIframe ? this.contentWindow : this.viewportElement; } get rect() { const viewportElement = this.viewportElement; if (viewportElement) return viewportElement.getBoundingClientRect(); } get innerRect() { const rect = this.rect; return new Rect(0, 0, rect?.width, rect?.height); } get offsetX() { const rect = this.rect; if (!rect) return 0; return rect.x; } get offsetY() { const rect = this.rect; if (!rect) return 0; return rect.y; } get scale() { if (!this.viewportElement) return 1; const clientRect = this.viewportElement.getBoundingClientRect(); const offsetWidth = this.viewportElement.offsetWidth; if (!clientRect.width || !offsetWidth) return 1; return Math.round(clientRect.width / offsetWidth); } get dragScrollXDelta() { return this.scrollX - this.dragStartSnapshot.scrollX; } get dragScrollYDelta() { return this.scrollY - this.dragStartSnapshot.scrollY; } cacheElements() { this.nodeElementsStore = {}; this.viewportRoot?.querySelectorAll(`*[${this.nodeIdAttrName}]`).forEach(element => { const id = element.getAttribute(this.nodeIdAttrName); this.nodeElementsStore[id] = this.nodeElementsStore[id] || []; this.nodeElementsStore[id].push(element); }); } clearCache() { this.nodeElementsStore = {}; } getCurrentData() { const data = {}; if (this.isIframe) { data.scrollX = this.contentWindow?.scrollX || 0; data.scrollY = this.contentWindow?.scrollY || 0; data.width = this.contentWindow?.innerWidth || 0; data.height = this.contentWindow?.innerHeight || 0; } else if (this.viewportElement) { data.scrollX = this.viewportElement?.scrollLeft || 0; data.scrollY = this.viewportElement?.scrollTop || 0; data.width = this.viewportElement?.clientWidth || 0; data.height = this.viewportElement?.clientHeight || 0; } return data; } takeDragStartSnapshot() { this.dragStartSnapshot = this.getCurrentData(); } digestViewport() { Object.assign(this, this.getCurrentData()); } elementFromPoint(point) { if (this.contentWindow?.document) { return this.contentWindow.document.elementFromPoint(point.x, point.y); } } matchViewport(target) { if (this.isIframe) { return target === this.viewportElement || target === this.contentWindow || target === this.contentWindow?.document; } else { return target === this.viewportElement; } } attachEvents() { const engine = this.engine; cancelIdle(this.attachRequest); this.attachRequest = requestIdle(() => { if (!engine) return; if (this.isIframe) { this.workspace.attachEvents(this.contentWindow, this.contentWindow); } else if (isHTMLElement(this.viewportElement)) { this.workspace.attachEvents(this.viewportElement, this.contentWindow); } }); } detachEvents() { if (this.isIframe) { this.workspace.detachEvents(this.contentWindow); this.workspace.detachEvents(this.viewportElement); } else if (this.viewportElement) { this.workspace.detachEvents(this.viewportElement); } } onMount(element, contentWindow) { this.mounted = true; this.viewportElement = element; this.contentWindow = contentWindow; this.attachEvents(); this.digestViewport(); } onUnmount() { this.mounted = false; this.detachEvents(); } isPointInViewport(point, sensitive) { if (!this.rect) return false; if (!this.containsElement(document.elementFromPoint(point.x, point.y))) { return false; } return isPointInRect(point, this.rect, sensitive); } isRectInViewport(rect) { if (!this.rect) return false; if (!this.containsElement(document.elementFromPoint(rect.x, rect.y))) { return false; } return isRectInRect(rect, this.rect); } isPointInViewportArea(point, sensitive) { if (!this.rect) return false; return isPointInRect(point, this.rect, sensitive); } isOffsetPointInViewport(point, sensitive) { if (!this.innerRect) return false; if (!this.containsElement(document.elementFromPoint(point.x, point.y))) return false; return isPointInRect(point, this.innerRect, sensitive); } isOffsetRectInViewport(rect) { if (!this.innerRect) return false; if (!this.containsElement(document.elementFromPoint(rect.x, rect.y))) { return false; } return isRectInRect(rect, this.innerRect); } makeObservable() { define(this, { scrollX: observable.ref, scrollY: observable.ref, width: observable.ref, height: observable.ref, digestViewport: action, viewportElement: observable.ref, contentWindow: observable.ref }); } findElementById(id) { if (!id) return; if (this.nodeElementsStore[id]) return this.nodeElementsStore[id][0]; return this.viewportRoot?.querySelector(`*[${this.nodeIdAttrName}='${id}']`); } findElementsById(id) { if (!id) return []; if (this.nodeElementsStore[id]) return this.nodeElementsStore[id]; return Array.from(this.viewportRoot?.querySelectorAll(`*[${this.nodeIdAttrName}='${id}']`) ?? []); } containsElement(element) { let root = this.viewportElement; if (root === element) return true; return root?.contains(element); } getOffsetPoint(topPoint) { const data = this.getCurrentData(); return { x: topPoint.x - this.offsetX + data.scrollX, y: topPoint.y - this.offsetY + data.scrollY }; } //相对于页面 getElementRect(element) { const rect = element.getBoundingClientRect(); const offsetWidth = element['offsetWidth'] ? element['offsetWidth'] : rect.width; const offsetHeight = element['offsetHeight'] ? element['offsetHeight'] : rect.height; return new Rect(rect.x, rect.y, this.scale !== 1 ? offsetWidth : rect.width, this.scale !== 1 ? offsetHeight : rect.height); } //相对于页面 getElementRectById(id) { const elements = this.findElementsById(id); const rect = calcBoundingRect(elements.map(element => this.getElementRect(element))); if (rect) { if (this.isIframe) { return new Rect(rect.x + this.offsetX, rect.y + this.offsetY, rect.width, rect.height); } else { return new Rect(rect.x, rect.y, rect.width, rect.height); } } } //相对于视口 getElementOffsetRect(element) { const elementRect = element.getBoundingClientRect(); if (elementRect) { if (this.isIframe) { return new Rect(elementRect.x + this.contentWindow.scrollX, elementRect.y + this.contentWindow.scrollY, elementRect.width, elementRect.height); } else { return new Rect((elementRect.x - this.offsetX + this.viewportElement.scrollLeft) / this.scale, (elementRect.y - this.offsetY + this.viewportElement.scrollTop) / this.scale, elementRect.width, elementRect.height); } } } //相对于视口 getElementOffsetRectById(id) { const elements = this.findElementsById(id); if (!elements.length) return; const elementRect = calcBoundingRect(elements.map(element => this.getElementRect(element))); if (elementRect) { if (this.isIframe) { return new Rect(elementRect.x + this.contentWindow.scrollX, elementRect.y + this.contentWindow.scrollY, elementRect.width, elementRect.height); } else { return new Rect((elementRect.x - this.offsetX + this.viewportElement.scrollLeft) / this.scale, (elementRect.y - this.offsetY + this.viewportElement.scrollTop) / this.scale, elementRect.width, elementRect.height); } } } getValidNodeElement(node) { const getNodeElement = node => { if (!node) return; const ele = this.findElementById(node.id); if (ele) { return ele; } else { return getNodeElement(node.parent); } }; return getNodeElement(node); } getChildrenRect(node) { if (!node?.children?.length) return; return calcBoundingRect(node.children.reduce((buf, child) => { const rect = this.getValidNodeRect(child); if (rect) { return buf.concat(rect); } return buf; }, [])); } getChildrenOffsetRect(node) { if (!node?.children?.length) return; return calcBoundingRect(node.children.reduce((buf, child) => { const rect = this.getValidNodeOffsetRect(child); if (rect) { return buf.concat(rect); } return buf; }, [])); } getValidNodeRect(node) { if (!node) return; const rect = this.getElementRectById(node.id); if (node && node === node.root && node.isInOperation) { if (!rect) return this.rect; return calcBoundingRect([this.rect, rect]); } if (rect) { return rect; } else { return this.getChildrenRect(node); } } getValidNodeOffsetRect(node) { if (!node) return; const rect = this.getElementOffsetRectById(node.id); if (node && node === node.root && node.isInOperation) { if (!rect) return this.innerRect; return calcBoundingRect([this.innerRect, rect]); } if (rect) { return rect; } else { return this.getChildrenOffsetRect(node); } } getValidNodeLayout(node) { if (!node) return 'vertical'; if (node.parent?.designerProps?.inlineChildrenLayout) return 'horizontal'; return calcElementLayout(this.findElementById(node.id)); } }