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