mdui
Version:
实现 material you 设计规范的 Web Components 组件库
207 lines (206 loc) • 6.7 kB
JavaScript
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);
};