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