163 lines
4.6 KiB
JavaScript
163 lines
4.6 KiB
JavaScript
import { defineComponent, ref, watch, onMounted, onUnmounted, h } from 'vue';
|
|
|
|
const prevW = Symbol("prevW");
|
|
const prevH = Symbol("prevH");
|
|
const FlowLayout = /* @__PURE__ */ defineComponent({
|
|
name: "FlowLayout",
|
|
props: {
|
|
cols: {
|
|
type: Number,
|
|
default: 2
|
|
},
|
|
gap: {
|
|
type: [Number, Array],
|
|
default: 4
|
|
},
|
|
duration: {
|
|
type: [Boolean, Number],
|
|
default: true
|
|
}
|
|
},
|
|
setup(props, { slots }) {
|
|
let sizeObs;
|
|
let childrenObs;
|
|
const layout = ref(null);
|
|
const layouting = ref(false);
|
|
const reLayout = () => {
|
|
if (layouting.value || !layout.value)
|
|
return;
|
|
layouting.value = true;
|
|
requestAnimationFrame(() => {
|
|
try {
|
|
const parent = layout.value;
|
|
const children = Array.from(parent.children);
|
|
const len = children.length;
|
|
const [gapX, gapY] = Array.isArray(props.gap) ? props.gap : [props.gap, props.gap];
|
|
const colWidth = (parent.offsetWidth - (props.cols - 1) * gapX) / props.cols;
|
|
for (const el of children) {
|
|
el.style.width = `${colWidth}px`;
|
|
}
|
|
const hs = Array.from({ length: len }, (_, index) => {
|
|
const el = children[index];
|
|
el[prevW] = el.offsetWidth;
|
|
el[prevH] = el.offsetHeight;
|
|
return el.offsetHeight;
|
|
});
|
|
const columnHs = Array.from({ length: props.cols }, () => 0);
|
|
for (let i = 0; i < children.length; i++) {
|
|
const el = children[i];
|
|
const minColHeight = Math.min(...columnHs);
|
|
const colIndex = columnHs.indexOf(minColHeight);
|
|
const left = colIndex * (colWidth + gapX);
|
|
const top = minColHeight;
|
|
el.style.left = `${left}px`;
|
|
el.style.top = `${top}px`;
|
|
columnHs[colIndex] += hs[i] + gapY;
|
|
}
|
|
parent.style.height = `${Math.max(...columnHs) - gapY}px`;
|
|
} finally {
|
|
if (layout.value) {
|
|
layout.value[prevW] = layout.value.offsetWidth;
|
|
layout.value[prevH] = layout.value.offsetHeight;
|
|
}
|
|
layouting.value = false;
|
|
}
|
|
});
|
|
};
|
|
const sizeListener = () => {
|
|
if (!layout.value)
|
|
return;
|
|
const parent = layout.value;
|
|
sizeObs = new ResizeObserver((els) => {
|
|
for (const { target: el } of els) {
|
|
if (el.offsetWidth !== el[prevW] || el.offsetHeight !== el[prevH]) {
|
|
reLayout();
|
|
}
|
|
}
|
|
});
|
|
sizeObs.observe(parent);
|
|
for (const el of parent.children) {
|
|
sizeObs.observe(el);
|
|
}
|
|
};
|
|
const childrenListener = () => {
|
|
if (!layout.value)
|
|
return;
|
|
const parent = layout.value;
|
|
childrenObs = new MutationObserver((ml) => {
|
|
for (const m of ml) {
|
|
for (const el of m.addedNodes) {
|
|
if (el.nodeType === 1)
|
|
sizeObs.observe(el);
|
|
}
|
|
for (const el of m.removedNodes) {
|
|
if (el.nodeType === 1)
|
|
sizeObs.unobserve(el);
|
|
}
|
|
}
|
|
reLayout();
|
|
});
|
|
childrenObs.observe(parent, { childList: true, subtree: true });
|
|
};
|
|
watch(() => [props.cols, props.gap], reLayout);
|
|
onMounted(() => {
|
|
reLayout();
|
|
sizeListener();
|
|
childrenListener();
|
|
});
|
|
onUnmounted(() => {
|
|
sizeObs.disconnect();
|
|
childrenObs.disconnect();
|
|
});
|
|
return () => {
|
|
const parentStyle = {
|
|
position: "relative",
|
|
display: "block",
|
|
boxSizing: "border-box",
|
|
overflow: "unset !important"
|
|
};
|
|
const childStyle = {
|
|
position: "absolute",
|
|
boxSizing: "border-box",
|
|
...props.duration ? {
|
|
transition: `all ${typeof props.duration === "number" ? `${props.duration}ms` : "350ms"}`
|
|
} : {}
|
|
};
|
|
const children = slots.default?.();
|
|
function setStyle(vnode, style, depath = 1) {
|
|
if (Array.isArray(vnode.children) && depath > 0) {
|
|
vnode.children.forEach((vvnode) => {
|
|
setStyle(vvnode, style, depath - 1);
|
|
});
|
|
} else {
|
|
const props2 = vnode.props = vnode.props || {};
|
|
props2.style = {
|
|
...props2.style,
|
|
...childStyle
|
|
};
|
|
}
|
|
}
|
|
children?.forEach((child) => {
|
|
setStyle(child, childStyle);
|
|
});
|
|
return h(
|
|
"div",
|
|
{
|
|
id: "vue-flow-layout",
|
|
ref: layout,
|
|
style: parentStyle
|
|
},
|
|
children
|
|
);
|
|
};
|
|
}
|
|
});
|
|
|
|
const index = {
|
|
install: (app) => {
|
|
app.component("FlowLayout", FlowLayout);
|
|
}
|
|
};
|
|
|
|
export { FlowLayout, index as default };
|