@posva/splitpanes
Version:
A Vue.js reliable, simple and touch-ready panes splitter / resizer
312 lines (311 loc) • 14 kB
JavaScript
import { defineComponent as G, useSlots as fe, ref as k, computed as h, watch as A, onMounted as X, onBeforeUnmount as Y, provide as g, createElementBlock as W, openBlock as I, normalizeClass as pe, renderSlot as J, nextTick as T, inject as E, getCurrentInstance as me, normalizeStyle as de, unref as j } from "vue";
const he = /* @__PURE__ */ G({
__name: "splitpanes",
props: {
horizontal: { type: Boolean, default: !1 },
pushOtherPanes: { type: Boolean, default: !0 },
maximizePanes: { type: Boolean, default: !0 },
// Maximize pane on splitter double click/tap.
rtl: { type: Boolean, default: !1 },
// Right to left direction.
firstSplitter: { type: Boolean, default: !1 }
},
emits: [
"ready",
"resize",
"resized",
"pane-click",
"pane-maximize",
"pane-add",
"pane-remove",
"splitter-click",
"splitter-dblclick"
],
setup(L, { emit: x }) {
const y = x, c = L;
fe();
const l = k([]), w = h(
() => l.value.reduce((e, n) => (e[~~n.id] = n) && e, {})
), z = h(() => l.value.length), f = k(null), S = k(!1), v = k({
mouseDown: !1,
dragging: !1,
activeSplitter: null,
cursorOffset: 0
// Cursor offset within the splitter.
}), d = k({
// Used to detect double click on touch devices.
splitter: null,
timeoutId: null
}), _ = h(() => ({
[`splitpanes splitpanes--${c.horizontal ? "horizontal" : "vertical"}`]: !0,
"splitpanes--dragging": v.value.dragging
}));
let P = () => {
};
function R(e) {
e.addEventListener("mousemove", M, { passive: !1 }), e.addEventListener("mouseup", u), "ontouchstart" in window && (e.addEventListener("touchmove", M, { passive: !1 }), e.addEventListener("touchend", u)), P = () => {
e.removeEventListener("mousemove", M), e.removeEventListener("mouseup", u), "ontouchstart" in window && (e.removeEventListener("touchmove", M), e.removeEventListener("touchend", u)), P = () => {
};
};
}
const N = (e, n) => {
const t = e.target.closest(".splitpanes__splitter");
if (t) {
const { left: i, top: a } = t.getBoundingClientRect(), { clientX: o, clientY: s } = "touches" in e ? e.touches[0] : e;
v.value.cursorOffset = c.horizontal ? s - a : o - i;
}
R(e.target.ownerDocument || document), v.value.mouseDown = !0, v.value.activeSplitter = n;
}, M = (e) => {
v.value.mouseDown && (e.preventDefault(), v.value.dragging = !0, requestAnimationFrame(() => {
Z(Q(e)), m("resize", { event: e }, !0);
}));
}, u = (e) => {
v.value.dragging && m("resized", { event: e }, !0), v.value.mouseDown = !1, v.value.activeSplitter = null, setTimeout(() => {
v.value.dragging = !1, P();
}, 100);
}, C = (e, n) => {
"ontouchstart" in window && (e.preventDefault(), d.value.splitter === n ? (clearTimeout(d.value.timeoutId), d.value.timeoutId = null, B(e, n), d.value.splitter = null) : (d.value.splitter = n, d.value.timeoutId = setTimeout(() => d.value.splitter = null, 500))), v.value.dragging || m("splitter-click", { event: e, index: n }, !0);
}, B = (e, n) => {
if (m("splitter-dblclick", { event: e, index: n }, !0), c.maximizePanes) {
let t = 0;
l.value = l.value.map((i, a) => (i.size = a === n ? i.max : i.min, a !== n && (t += i.min), i)), l.value[n].size -= t, m("pane-maximize", {
event: e,
index: n,
pane: l.value[n]
}), m("resized", { event: e, index: n }, !0);
}
}, K = (e, n) => {
m("pane-click", {
event: e,
index: w.value[n].index,
pane: w.value[n]
});
}, Q = (e) => {
var a;
const n = (a = f.value) == null ? void 0 : a.getBoundingClientRect(), { clientX: t, clientY: i } = "ontouchstart" in window && e.touches ? e.touches[0] : e;
return {
x: t - (c.horizontal ? 0 : v.value.cursorOffset) - n.left,
y: i - (c.horizontal ? v.value.cursorOffset : 0) - n.top
};
}, V = (e) => {
e = e[c.horizontal ? "y" : "x"];
const n = f.value[c.horizontal ? "clientHeight" : "clientWidth"];
return c.rtl && !c.horizontal && (e = n - e), e * 100 / n;
}, Z = (e) => {
const n = v.value.activeSplitter;
let t = {
prevPanesSize: U(n),
nextPanesSize: b(n),
prevReachedMinPanes: 0,
nextReachedMinPanes: 0
};
const i = 0 + (c.pushOtherPanes ? 0 : t.prevPanesSize), a = 100 - (c.pushOtherPanes ? 0 : t.nextPanesSize), o = Math.max(Math.min(V(e), a), i);
let s = [n, n + 1], r = l.value[s[0]], p = l.value[s[1]];
if (!r || !p) return;
const F = r.max < 100 && o >= r.max + t.prevPanesSize, ve = p.max < 100 && o <= 100 - (p.max + b(n + 1));
if (F || ve) {
F ? (r.size = r.max, p.size = Math.max(100 - r.max - t.prevPanesSize - t.nextPanesSize, 0)) : (r.size = Math.max(
100 - p.max - t.prevPanesSize - b(n + 1),
0
), p.size = p.max);
return;
}
if (c.pushOtherPanes) {
const H = ee(t, o);
if (!H) return;
({ sums: t, panesToResize: s } = H), r = l.value[s[0]], p = l.value[s[1]];
}
r && (r.size = Math.min(
Math.max(o - t.prevPanesSize - t.prevReachedMinPanes, r.min),
r.max
)), p && (p.size = Math.min(
Math.max(100 - o - t.nextPanesSize - t.nextReachedMinPanes, p.min),
p.max
));
}, ee = (e, n) => {
const t = v.value.activeSplitter, i = [t, t + 1];
let a = l.value[i[0]], o = l.value[i[1]];
return a && n < e.prevPanesSize + a.min && (i[0] = ne(t).index, e.prevReachedMinPanes = 0, i[0] < t && l.value.forEach((s, r) => {
r > i[0] && r <= t && (s.size = s.min, e.prevReachedMinPanes += s.min);
}), e.prevPanesSize = U(i[0]), i[0] === void 0) ? (e.prevReachedMinPanes = 0, l.value[0].size = l.value[0].min, l.value.forEach((s, r) => {
r > 0 && r <= t && (s.size = s.min, e.prevReachedMinPanes += s.min);
}), o.size = 100 - e.prevReachedMinPanes - l.value[0].min - e.prevPanesSize - e.nextPanesSize, null) : n > 100 - e.nextPanesSize - l.value[i[1]].min && (i[1] = ie(t).index, e.nextReachedMinPanes = 0, i[1] > t + 1 && l.value.forEach((s, r) => {
r > t && r < i[1] && (s.size = s.min, e.nextReachedMinPanes += s.min);
}), e.nextPanesSize = b(i[1] - 1), i[1] === void 0) ? (e.nextReachedMinPanes = 0, l.value.forEach((s, r) => {
r < z.value - 1 && r >= t + 1 && (s.size = s.min, e.nextReachedMinPanes += s.min);
}), l.value[i[0]].size = 100 - e.prevPanesSize - b(i[0] - 1), null) : { sums: e, panesToResize: i };
}, U = (e) => l.value.reduce((n, t, i) => n + (i < e ? t.size : 0), 0), b = (e) => l.value.reduce((n, t, i) => n + (i > e + 1 ? t.size : 0), 0), ne = (e) => [...l.value].reverse().find((t) => t.index < e && t.size > t.min) || {}, ie = (e) => l.value.find((t) => t.index > e + 1 && t.size > t.min) || {}, te = () => {
var n;
const e = Array.from(((n = f.value) == null ? void 0 : n.children) || []);
for (const t of e) {
const i = t.classList.contains("splitpanes__pane"), a = t.classList.contains("splitpanes__splitter");
!i && !a && (t.remove(), console.warn(
"Splitpanes: Only <pane> elements are allowed at the root of <splitpanes>. One of your DOM nodes was removed."
));
}
}, $ = (e, n, t = !1) => {
const i = e - 1, a = document.createElement("div");
a.classList.add("splitpanes__splitter"), t || (a.onmousedown = (o) => N(o, i), typeof window < "u" && "ontouchstart" in window && (a.ontouchstart = (o) => N(o, i)), a.onclick = (o) => C(o, i + 1)), a.ondblclick = (o) => B(o, i + 1), n.parentNode.insertBefore(a, n);
}, ae = (e) => {
e.onmousedown = void 0, e.onclick = void 0, e.ondblclick = void 0, e.remove();
}, O = () => {
var t;
const e = Array.from(((t = f.value) == null ? void 0 : t.children) || []);
for (const i of e)
i.className.includes("splitpanes__splitter") && ae(i);
let n = 0;
for (const i of e)
i.className.includes("splitpanes__pane") && (!n && c.firstSplitter ? $(n, i, !0) : n && $(n, i), n++);
}, se = ({ uid: e, ...n }) => {
const t = w.value[e];
for (const [i, a] of Object.entries(n)) t[i] = a;
}, le = (e) => {
var t;
let n = -1;
Array.from(((t = f.value) == null ? void 0 : t.children) || []).some((i) => (i.className.includes("splitpanes__pane") && n++, i.isSameNode(e.el))), l.value.splice(n, 0, { ...e, index: n }), l.value.forEach((i, a) => i.index = a), S.value && T(() => {
O(), q({ addedPane: l.value[n] }), m("pane-add", { pane: l.value[n] });
});
}, oe = (e) => {
const n = l.value.findIndex((i) => i.id === e);
l.value[n].el = null;
const t = l.value.splice(n, 1)[0];
l.value.forEach((i, a) => i.index = a), T(() => {
O(), m("pane-remove", { pane: t }), q({ removedPane: { ...t } });
});
}, q = (e = {}) => {
!e.addedPane && !e.removedPane ? ue() : l.value.some((n) => n.givenSize !== null || n.min || n.max < 100) ? ce(e) : re(), S.value && m("resized");
}, re = () => {
const e = 100 / z.value;
let n = 0;
const t = [], i = [];
for (const a of l.value)
a.size = Math.max(Math.min(e, a.max), a.min), n -= a.size, a.size >= a.max && t.push(a.id), a.size <= a.min && i.push(a.id);
n > 0.1 && D(n, t, i);
}, ue = () => {
let e = 100;
const n = [], t = [];
let i = 0;
for (const o of l.value)
e -= o.size, o.givenSize !== null && i++, o.size >= o.max && n.push(o.id), o.size <= o.min && t.push(o.id);
let a = 100;
if (e > 0.1) {
for (const o of l.value)
o.givenSize === null && (o.size = Math.max(
Math.min(e / (z.value - i), o.max),
o.min
)), a -= o.size;
a > 0.1 && D(a, n, t);
}
}, ce = ({ addedPane: e, removedPane: n } = {}) => {
let t = 100 / z.value, i = 0;
const a = [], o = [];
((e == null ? void 0 : e.givenSize) ?? null) !== null && (t = (100 - e.givenSize) / (z.value - 1));
for (const s of l.value)
i -= s.size, s.size >= s.max && a.push(s.id), s.size <= s.min && o.push(s.id);
if (!(Math.abs(i) < 0.1)) {
for (const s of l.value)
(e == null ? void 0 : e.givenSize) !== null && (e == null ? void 0 : e.id) === s.id || (s.size = Math.max(Math.min(t, s.max), s.min)), i -= s.size, s.size >= s.max && a.push(s.id), s.size <= s.min && o.push(s.id);
i > 0.1 && D(i, a, o);
}
}, D = (e, n, t) => {
let i;
e > 0 ? i = e / (z.value - n.length) : i = e / (z.value - t.length), l.value.forEach((a, o) => {
if (e > 0 && !n.includes(a.id)) {
const s = Math.max(Math.min(a.size + i, a.max), a.min), r = s - a.size;
e -= r, a.size = s;
} else if (!t.includes(a.id)) {
const s = Math.max(Math.min(a.size + i, a.max), a.min), r = s - a.size;
e -= r, a.size = s;
}
}), Math.abs(e) > 0.1 && T(() => {
S.value && console.warn("Splitpanes: Could not resize panes correctly due to their constraints.");
});
}, m = (e, n = void 0, t = !1) => {
const i = (n == null ? void 0 : n.index) ?? v.value.activeSplitter ?? null;
y(e, {
...n,
...i !== null && { index: i },
...t && i !== null && {
prevPane: l.value[i - (c.firstSplitter ? 1 : 0)],
nextPane: l.value[i + (c.firstSplitter ? 0 : 1)]
},
panes: l.value.map((a) => ({
min: a.min,
max: a.max,
size: a.size
}))
});
};
return A(
() => c.firstSplitter,
() => O()
), X(() => {
te(), O(), q(), m("ready"), S.value = !0;
}), Y(() => S.value = !1), g("panes", l), g("indexedPanes", w), g(
"horizontal",
h(() => c.horizontal)
), g("requestUpdate", se), g("onPaneAdd", le), g("onPaneRemove", oe), g("onPaneClick", K), (e, n) => (I(), W("div", {
class: pe(_.value),
ref_key: "containerEl",
ref: f
}, [
J(e.$slots, "default")
], 2));
}
}), xe = /* @__PURE__ */ G({
__name: "pane",
props: {
size: { type: [Number, String] },
minSize: { type: [Number, String], default: 0 },
maxSize: { type: [Number, String], default: 100 }
},
setup(L) {
var M;
const x = L, y = E("requestUpdate"), c = E("onPaneAdd"), l = E("horizontal"), w = E("onPaneRemove"), z = E("onPaneClick"), f = (M = me()) == null ? void 0 : M.uid, S = E("indexedPanes"), v = h(() => S.value[f]), d = k(null), _ = h(() => {
const u = isNaN(x.size) || x.size === void 0 ? 0 : parseFloat(x.size);
return Math.max(Math.min(u, R.value), P.value);
}), P = h(() => {
const u = parseFloat(x.minSize);
return isNaN(u) ? 0 : u;
}), R = h(() => {
const u = parseFloat(x.maxSize);
return isNaN(u) ? 100 : u;
}), N = h(() => {
var u;
return `${l.value ? "height" : "width"}: ${(u = v.value) == null ? void 0 : u.size}%`;
});
return A(
() => _.value,
(u) => y({ uid: f, size: u })
), A(
() => P.value,
(u) => y({ uid: f, min: u })
), A(
() => R.value,
(u) => y({ uid: f, max: u })
), X(() => {
c({
id: f,
el: d.value,
min: P.value,
max: R.value,
// The given size (useful to know the user intention).
givenSize: x.size === void 0 ? null : _.value,
size: _.value
// The computed current size at any time.
});
}), Y(() => w(f)), (u, C) => (I(), W("div", {
ref_key: "paneEl",
ref: d,
class: "splitpanes__pane",
onClick: C[0] || (C[0] = (B) => j(z)(B, j(f))),
style: de(N.value)
}, [
J(u.$slots, "default")
], 4));
}
});
export {
xe as Pane,
he as Splitpanes
};