UNPKG

@kong/splitpanes

Version:

A Vue.js reliable, simple and touch-ready panes splitter / resizer

266 lines (265 loc) 13.7 kB
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 };