UNPKG

mdui

Version:

实现 material you 设计规范的 Web Components 组件库

207 lines (206 loc) 6.7 kB
import { $ } from '@mdui/jq/$.js'; import '@mdui/jq/methods/children.js'; import '@mdui/jq/methods/css.js'; import '@mdui/jq/methods/get.js'; import { isNodeName } from '@mdui/jq/shared/helper.js'; import { observeResize } from '@mdui/shared/helpers/observeResize.js'; export class LayoutManager { constructor() { this.states = []; } /** * 注册 `<mdui-layout-main>` */ registerMain(element) { this.$main = $(element); } /** * 取消注册 `<mdui-layout-main>` */ unregisterMain() { this.$main = undefined; } /** * 注册新的 `<mdui-layout-item>` */ registerItem(element) { const state = { element }; this.states.push(state); // 监听元素尺寸变化 state.observeResize = observeResize(state.element, () => { this.updateLayout(state.element, { width: this.isNoWidth(state) ? 0 : undefined, }); }); this.items = undefined; this.resort(); // 从头更新布局 this.updateLayout(); } /** * 取消注册 `<mdui-layout-item>` */ unregisterItem(element) { const index = this.states.findIndex((item) => item.element === element); if (index < 0) { return; } // 取消监听尺寸变化 const item = this.states[index]; item.observeResize?.unobserve(); this.items = undefined; // 移除一个元素,并从下一个元素开始更新 this.states.splice(index, 1); if (this.states[index]) { this.updateLayout(this.states[index].element); } } /** * 获取所有 `<mdui-layout-item>` 元素(按在 DOM 中的顺序) */ getItems() { if (!this.items) { const items = this.states.map((state) => state.element); this.items = items.sort((a, b) => { const position = a.compareDocumentPosition(b); if (position & Node.DOCUMENT_POSITION_FOLLOWING) { return -1; } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { return 1; } else { return 0; } }); } return this.items; } /** * 获取 `<mdui-layout-main>` 元素 */ getMain() { return this.$main ? this.$main[0] : undefined; } /** * 获取 `<mdui-layout-item>` 及 `<mdui-layout-main>` 元素 */ getItemsAndMain() { return [...this.getItems(), this.getMain()].filter((i) => i); } /** * 更新 `order` 值,更新完后重新计算布局 */ updateOrder() { this.resort(); this.updateLayout(); } /** * 重新计算布局 * @param element 从哪一个元素开始更新;若未传入参数,则将更新所有元素 * @param size 此次更新中,元素的宽高(仅在此次更新中使用)。若不传则自动计算 */ updateLayout(element, size) { const state = element ? { element, width: size?.width, height: size?.height, } : undefined; const index = state ? this.states.findIndex((v) => v.element === state.element) : 0; if (index < 0) { return; } Object.assign(this.states[index], state); this.states.forEach((currState, currIndex) => { if (currIndex < index) { return; } // @ts-ignore const placement = currState.element.layoutPlacement; // 前一个元素 const prevState = currIndex > 0 ? this.states[currIndex - 1] : undefined; const top = prevState?.top ?? 0; const right = prevState?.right ?? 0; const bottom = prevState?.bottom ?? 0; const left = prevState?.left ?? 0; Object.assign(currState, { top, right, bottom, left }); switch (placement) { case 'top': case 'bottom': currState[placement] += currState.height ?? currState.element.offsetHeight; break; case 'right': case 'left': currState[placement] += (this.isNoWidth(currState) ? 0 : currState.width) ?? currState.element.offsetWidth; break; } currState.height = currState.width = undefined; $(currState.element).css({ position: 'absolute', top: placement === 'bottom' ? null : top, right: placement === 'left' ? null : right, bottom: placement === 'top' ? null : bottom, left: placement === 'right' ? null : left, }); }); // 更新完后,设置 layout-main 的 padding const lastState = this.states[this.states.length - 1]; if (this.$main) { this.$main.css({ paddingTop: lastState.top, paddingRight: lastState.right, paddingBottom: lastState.bottom, paddingLeft: lastState.left, }); } } /** * 按 order 排序,order 相同时,按在 DOM 中的顺序排序 */ resort() { const items = this.getItems(); this.states.sort((a, b) => { const aOrder = a.element.order ?? 0; const bOrder = b.element.order ?? 0; if (aOrder > bOrder) { return 1; } if (aOrder < bOrder) { return -1; } if (items.indexOf(a.element) > items.indexOf(b.element)) { return 1; } if (items.indexOf(a.element) < items.indexOf(b.element)) { return -1; } return 0; }); } /** * 组件宽度是否为 0 * mdui-navigation-drawer 较为特殊,在为模态化时,占据的宽度为 0 */ isNoWidth(state) { return (isNodeName(state.element, 'mdui-navigation-drawer') && // @ts-ignore state.element.isModal); } } const layoutManagerMap = new WeakMap(); /** * 获取 layout 实例 */ export const getLayout = (element) => { if (!layoutManagerMap.has(element)) { layoutManagerMap.set(element, new LayoutManager()); } return layoutManagerMap.get(element); };