UNPKG

@xiaohaih/drag

Version:

拖拽插件, 可通过指令或函数调用来拖拽元素移动

575 lines (574 loc) 22.9 kB
var q = Object.defineProperty; var K = (i, t, e) => t in i ? q(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e; var f = (i, t, e) => K(i, typeof t != "symbol" ? t + "" : t, e); var O = /* @__PURE__ */ ((i) => (i[i.ground = 1e3] = "ground", i[i.sky = 2e3] = "sky", i[i.troposphere = 3e3] = "troposphere", i[i.stratosphere = 4e3] = "stratosphere", i[i.mesosphere = 5e3] = "mesosphere", i[i.thermosphere = 6e3] = "thermosphere", i[i.outerspace = 7e3] = "outerspace", i))(O || {}); function lt(i) { return typeof i == "function"; } function ut(i) { return typeof i == "string"; } function G(i) { return Object.prototype.toString.call(i).slice(8, -1); } function ft(i) { return G(i) === "Object"; } function p(i) { return !!i && i.enable !== !1; } function J() { return /AppleWebKit.*Mobile.*/.test(navigator.userAgent); } function y(i, t, e) { return i == null ? e : typeof i == "string" ? y(t.querySelectorAll(i), t) : i instanceof Node ? [i] : typeof i == "function" ? y(i(t), t) : Array.isArray(i) ? i.reduce((s, r) => { const n = y(r, t); return n && (Array.isArray(n) ? s.push(...n) : s.push(n)), s; }, []) : i instanceof NodeList || i instanceof HTMLCollection ? [...i] : e; } function F(i) { return i.touches ? i.touches[0] || i.changedTouches[0] : i; } function X(i) { return i.offsetParent || document.body || document.documentElement; } function Q(i, t, e) { let s = i; for (; s; ) { if (s === t) return s; if (s === e) return null; s = s.parentElement; } return null; } function W(i) { return { width: i.offsetWidth, height: i.offsetHeight }; } function _(i, t) { return window.getComputedStyle(i, t); } function k(i, t) { t && (i.classList ? i.classList.add(...t.split(" ").filter(Boolean)) : i.className += t); } function R(i, t) { if (!t) return; const e = t.split(" ").filter(Boolean); if (i.classList) i.classList.remove(...e); else { const s = i.className.split(" "); e.forEach((r) => { const n = s.indexOf(r); n !== -1 && s.splice(n, 1); }), i.className = s.join(" "); } } function U(i, t) { const e = { left: 0, right: 0, top: 0, bottom: 0, x: 0, y: 0, width: i[`${t}Width`], height: i[`${t}Height`] }; let s = i; for (; s; ) e.left += s.offsetLeft, e.top += s.offsetTop, s = s.offsetParent; return e.x = e.left, e.y = e.top, e.bottom = e.top + e.height, e.right = e.left + e.width, e; } const A = J(), M = A ? "touchstart" : "mousedown", z = A ? "touchmove" : "mousemove", P = A ? "touchend" : "mouseup"; function Z() { return { name: "BoundaryLimit", sort: O.sky + 30, install(i) { let t = []; i.on("start", (e) => { let s = t.find((n) => n[0] === e.target); s || t.push(s = [e.target, { scrollWidth: 0, scrollHeight: 0 }]); const r = X(e.target); s[1].scrollWidth = r.scrollWidth, s[1].scrollHeight = r.scrollHeight; }), i.on("axisBeforeUpdate", (e, s) => { if (!(s.status && p(s.option.boundaryLimitOptions)) || s.option.boundaryLimitOptions.boundaryLimit === !1) return; const n = t.find((a) => a[0] === e.target); if (!n) return; const { scrollWidth: o, scrollHeight: h } = n[1], { width: c, height: l } = W(e.target); e.x + c > o ? e.x = o - c : e.x < 0 && (e.x = 0), e.y + l > h ? e.y = h - l : e.y < 0 && (e.y = 0); }); } }; } class H { constructor(t) { f(this, "option", { setCursor: V, getCursor: w, cursorOver: "move", cursorMoving: "move" }); /** 应用的插件 */ f(this, "plugins", []); /** 拖拽的节点信息 */ f(this, "operationDom", []); /** 启用状态 */ f(this, "status", !0); /** 元素比例, 当缩放时, 此处会记录缩放比例[宽度百分比, 高度百分比] */ f(this, "ratio", [1, 1]); f(this, "brokerDoms", []); f(this, "_cursorTouch", ""); f(this, "isTouching", !1); f(this, "_cursor", ""); f(this, "isEntering", !1); /** 事件集合 */ f(this, "events", {}); /** 在特定时机收集新增的事件 */ f(this, "eventsScope", []); this.addEventListenerForBrokerDom = this.addEventListenerForBrokerDom.bind(this), this.touchstart = this.touchstart.bind(this), this.down = this.down.bind(this), this.move = this.move.bind(this), this.up = this.up.bind(this), this.mouseenter = this.mouseenter.bind(this), this.mouseleave = this.mouseleave.bind(this), this.setPosition = this.setPosition.bind(this), this.on = this.on.bind(this), this.once = this.once.bind(this), this.off = this.off.bind(this), this.emit = this.emit.bind(this), this.option.disabled && (this.status = !1), t && this.updateOption(t); } /** 更新参数 */ updateOption(t) { return Object.assign(this.option, t), t.eventProxy && this.formatBrokerDom(), (t.target || this.option.target && t.handle) && this.formatDom(), this; } /** 启用拖拽 */ enabled() { return this.status = !0, this.addEventsListener(), this; } /** 禁用拖拽 */ disabled() { return this.status = !1, V(this._cursor), this._cursor = "", this.isTouching = this.isEntering = !1, this.removeEventsListener(), this; } /** 格式化代理元素 */ formatBrokerDom() { this.removeEventListenerForBrokerDoms(); const { eventProxy: t } = this.option; this.brokerDoms = y(t, document) || [], this.addEventListenerForBrokerDoms(); } /** 增加代理元素监听的相关事件 */ addEventListenerForBrokerDoms() { this.brokerDoms.forEach((t) => { t.addEventListener(M, this.addEventListenerForBrokerDom); }); } /** 移除代理元素监听的相关事件 */ removeEventListenerForBrokerDoms() { this.brokerDoms.forEach((t) => { t.removeEventListener(M, this.addEventListenerForBrokerDom); }); } /** 格式化拖拽元素 */ formatDom() { const { target: t, handle: e } = this.option; return this.removeEventsListener(), this.operationDom = [], (this.brokerDoms.length ? this.brokerDoms.reduce((r, n) => (r.push(...y(t, n, [])), r), []) : y(t, document, [])).forEach((r) => { (y(e, r) || [r]).forEach( (o) => this.operationDom.push({ target: r, handle: o, x: 0, y: 0, dragging: !1, // eslint-disable-next-line ts/unbound-method setPosition: this.setPosition }) ); }), this.status && this.addEventsListener(), this; } /** 增加元素监听的相关事件 */ addEventsListener() { this.operationDom.forEach(({ handle: t }) => { t.addEventListener(M, this.touchstart), A || (t.addEventListener("mouseenter", this.mouseenter), t.addEventListener("mouseleave", this.mouseleave)); }); } /** 移除元素监听的相关事件 */ removeEventsListener() { this.operationDom.forEach((t) => { t.dragging && this.up(t, {}), t.handle.removeEventListener(M, this.touchstart), t.handle.removeEventListener("mouseenter", this.mouseenter), t.handle.removeEventListener("mouseleave", this.mouseleave); }); } /** 代理元素的按下事件 */ addEventListenerForBrokerDom(t) { const { target: e } = this.option, { operationDom: s } = this; if (!e || y(e, t.currentTarget, []).every((a, u) => { var g; return a === ((g = s[u]) == null ? void 0 : g.target); })) return; this.formatDom(); const n = this.operationDom.find((a) => Q(t.target, a.target, t.currentTarget)); if (!n) return; const { clientX: o, clientY: h, pageX: c, pageY: l } = F(t); this.down(n, { x: o, y: h, pageX: c, pageY: l }, t); } /** 开始拖拽 - 基于事件 */ touchstart(t) { const e = this.operationDom.find((h) => h.handle === t.currentTarget); if (!e) return; const { clientX: s, clientY: r, pageX: n, pageY: o } = F(t); this.down(e, { x: s, y: r, pageX: n, pageY: o }, t); } /** 拖拽开始 */ down(t, e, s) { s == null || s.preventDefault(), this.isTouching = !0, this.emit("beforeStart", this.getCustomEvent(t, s), this), this._cursorTouch = this.isEntering ? this._cursor : document.body.style.cursor, this.option.setCursor(this.option.getCursor("down", t, this, w)); const { position: r, marginLeft: n, marginTop: o } = _(t.target), h = r === "absolute" || r === "fixed", c = h ? t.target.offsetLeft : 0, l = h ? t.target.offsetTop : 0, { x: a, y: u } = t.target.getBoundingClientRect(); this.ratio = H.getRatioByElement(t.target); const [g, E] = this.ratio; t.dragging = !0, t.clientX = e.x, t.clientY = e.y, t.pageX = e.pageX, t.pageY = e.pageY, t.offsetX = e.pageX - c / g, t.offsetY = e.pageY - l / E, t.offsetInsetX = Math.abs(e.x - a), t.offsetInsetY = Math.abs(e.y - u), t.initialX = c, t.initialY = l, t.ml = n && Number.parseFloat(n) || 0, t.mt = o && Number.parseFloat(o) || 0, t.x = c, t.y = l, k(t.target, this.option.classActive), this.option.classActivated && this.operationDom.forEach((d) => { d.target === t.target ? k(d.target, this.option.classActivated) : R(d.target, this.option.classActivated); }), this.emit("start", this.getCustomEvent(t, s), this); const Y = (d) => { const { clientX: b, clientY: D, pageX: C, pageY: v } = F(d); this.move(t, { x: b, y: D, pageX: C, pageY: v }, d); }, m = (d) => { const { clientX: b, clientY: D, pageX: C, pageY: v } = F(d); this.up(t, { x: b, y: D, pageX: C, pageY: v }, d), window.removeEventListener(z, Y), window.removeEventListener(P, m); }; window.addEventListener(z, Y, { passive: !1 }), window.addEventListener(P, m, { passive: !1 }); } /** 拖拽中 */ move(t, e, s) { if (!this.status) return; s == null || s.preventDefault(), s == null || s.stopImmediatePropagation(), this.emit("beforeMove", this.getCustomEvent(t, s), this), k(t.target, this.option.classMoving), this.option.setCursor(this.option.getCursor("moving", t, this, w)); const [r, n] = this.ratio; t.clientX = e.x, t.clientY = e.y, t.pageX = e.pageX, t.pageY = e.pageY, t.x = (e.pageX - t.offsetX) * r, t.y = (e.pageY - t.offsetY) * n, this.setPosition(t, t.target), this.emit("move", this.getCustomEvent(t, s), this); } /** 拖拽结束 */ up(t, e, s) { if (!this.status) return; this.emit("beforeEnd", this.getCustomEvent(t, s), this), R(t.target, this.option.classActive), R(t.target, this.option.classMoving), this.isTouching = !1; const r = this.isEntering ? "over" : "up"; this.option.setCursor(this.option.getCursor(r, t, this, w) || this._cursorTouch), t.dragging = !1, this.emit("end", this.getCustomEvent(t, s), this); } /** 设置坐标 */ setPosition(t, e) { let s = !1; function r(h, c) { (h.x !== n.x || h.y !== n.y) && (s = !0, n.setPosition(h, c)); } const n = { ...t, setPosition: r }; if (this.emit("axisBeforeUpdate", n, this), s) return; this.option.virtualAxis || (e.style.left = `${n.x - n.ml}px`, e.style.top = `${n.y - n.mt}px`), t.x = n.x, t.y = n.y; const o = this.operationDom.find((h) => h.target === e); o && Object.assign(o, t), this.emit("axisUpdated", t, this); } /** 获取事件传递的信息 */ getCustomEvent(t, e) { return { ...t, native: e }; } /** 鼠标移入事件 */ mouseenter(t) { const e = this.operationDom.find((s) => s.handle === t.currentTarget); e && (this.isEntering = !0, this._cursor = this.isTouching ? this._cursorTouch : document.body.style.cursor, !this.isTouching && this.option.setCursor(this.option.getCursor("over", e, this, w))); } /** 鼠标离开事件 */ mouseleave(t) { const e = this.operationDom.find((s) => s.handle === t.currentTarget); e && (this.isEntering = !1, !this.isTouching && this.option.setCursor(this.option.getCursor("out", e, this, w) || this._cursor)); } /** * 监听事件 * @param {DragCoreEventsNames} eventName 事件名称 * @param {DragCoreEvents[DragCoreEventsNames]} callback 事件回调 * @param {boolean} [once] 是否仅监听一次 */ on(t, e, s) { return this.events[t] || (this.events[t] = []), this.events[t].push([e, s]), this.eventsScope.forEach((r) => { r[t] || (r[t] = []), r[t].push([e, s]); }), this; } /** 一次性监听事件 */ once(t, e) { return this.on(t, e, !0); } /** 重新绑定指定事件集合内的事件 */ rebindEvents(t) { t && Object.entries(t).forEach(([e, s]) => { s.forEach(([r, n]) => { this.off(e, r), this.on(e, r, n); }); }); } /** 移除监听事件 */ off(t, e) { return this.eventsScope.forEach( (s) => s[t] && (e ? s[t] = s[t].filter((r) => r[0] !== e) : delete s[t]) ), this.events[t] ? e ? (this.events[t] = this.events[t].filter((s) => s[0] !== e), this.events[t].length || delete this.events[t], this) : (delete this.events[t], this) : this; } /** 触发监听事件 */ emit(t, ...e) { if (!this.events[t]) return this; let s = 0; return this.events[t].slice().forEach((r, n) => { r[0].apply(null, e), r[1] && (this.events[t].splice(n - s, 1), ++s); }), this; } /** 收集新增的事件 */ getFragmentEvents() { const t = {}; return { get: () => t, run: () => { this.eventsScope.push(t); }, stop: () => { const e = this.eventsScope.indexOf(t); e !== -1 && this.eventsScope.splice(e, 1); } }; } /** 销毁指定事件 */ disposeEvents(t) { t && Object.entries(t).forEach(([e, s]) => s.forEach((r) => this.off(e, r[0]))); } /** * 注册插件 插件默认不会执行, 只在 run 方法后运行 */ use(t) { const e = t(); if (!this.plugins.find((s) => s.name === e.name)) { const { run: s, get: r, stop: n } = this.getFragmentEvents(); if (this.plugins.push(e), s(), e.install(this), n(), e.events = r(), this.plugins.length < 2) return this; this.plugins.sort((o, h) => (o.sort || 0) - (h.sort || 0)), this.plugins.forEach((o) => this.rebindEvents(o.events)); } return this; } /** 移除插件 */ unuse(t) { var s; const e = typeof t == "string" ? this.plugins.findIndex((r) => r.name === t) : t; if (e !== -1) { const [r] = this.plugins.splice(e, 1); r && ((s = r.uninstall) == null || s.call(r, this), this.disposeEvents(r.events)); } return this; } /** 销毁实例 */ destroyed() { this.disabled(), this.plugins.forEach((t) => { var e; (e = t.uninstall) == null || e.call(t, this), this.disposeEvents(t.events), delete t.events; }), this.eventsScope = []; } // 辅助函数 /** 获取元素的比例 */ static getRatioByElement(t) { const { width: e, height: s } = t.getBoundingClientRect(), { offsetWidth: r, offsetHeight: n } = t; return [r / e, n / s]; } } function gt(i) { return new H(i); } const N = { over: "cursorOver", out: "cursorOut", moving: "cursorMoving", down: "cursorDown", up: "cursorUp" }; function w(i, t, e) { if (e.status) return e.option[N[i]]; } function V(i) { typeof i == "string" && (document.body.style.cursor = i); } function tt() { return { name: "Direction", sort: O.sky + 10, install(i) { i.on("axisBeforeUpdate", (t, e) => { if (!(e.status && p(e.option.directionOptions))) return; e.option.directionOptions.orient === "y" ? t.x = t.initialX : t.y = t.initialY; }); } }; } function et() { let i = []; return { name: "Scrolling", sort: O.sky, install(t) { let e = 0; function s(o, h) { if (!(h.status && p(h.option.scrollingOptions))) return; const c = h.option.scrollingOptions, l = i.find((x) => x[0] === o.target); if (!l) return; const [a, u] = h.ratio, { threshold: g = 40, speed: E = 10, scrollOption: Y } = c, m = { ...Y }, { scrollWidth: d, scrollHeight: b, offsetWidth: D, offsetHeight: C, scrollTop: v, scrollLeft: $, clientWidth: j, clientHeight: I } = l[1].scrollContainer; if (d <= D && b <= C) return; const { x: L, y: B } = l[1].scrollContainerRect; o.x = (o.clientX - L - o.offsetInsetX) * a + l[1].x, o.y = (o.clientY - B - o.offsetInsetY) * u + l[1].y; const T = o.clientX - L < g ? E * -1 : L + j / a - o.clientX < g ? E : 0, S = o.clientY - B < g ? E * -1 : B + I / u - o.clientY < g ? E : 0; if (T) { const x = T > 0 ? Math.min($ + T, d - j) : Math.max(0, $ + T); m.left = l[1].x = x, o.x = (o.clientX - L - o.offsetInsetX) * a + x; } if (S) { const x = S > 0 ? Math.min(v + S, b - I) : Math.max(0, v + S); m.top = l[1].y = x, o.y = (o.clientY - B - o.offsetInsetY) * u + x; } (m.left !== void 0 || m.top !== void 0) && l[1].scrollContainer.scrollTo(m), o.setPosition(o, o.target), l[1].clientX = o.clientX, l[1].clientY = o.clientY; } function r(o, h) { if (!(h.status && p(h.option.scrollingOptions))) return; const { scrollMs: c = 100 } = h.option.scrollingOptions; n(), s(o, h), e = setInterval(s, c, o, h); } function n() { clearInterval(e); } t.on("start", (o, h) => { if (!(h.status && p(h.option.scrollingOptions))) return; const { container: c } = h.option.scrollingOptions, l = typeof c == "function" ? c(o) : c || X(o.target); let a = i.find((u) => u[0] === o.target); a || i.push(a = [o.target, { clientX: 0, clientY: 0, x: 0, y: 0, scrollContainerRect: {}, scrollContainer: null }]), Object.assign(a[1], { clientX: o.clientX, clientY: o.clientY, x: l.scrollLeft, y: l.scrollTop, scrollContainerRect: l.getBoundingClientRect(), scrollContainer: l }), r(o, h); }).on("move", r).on("end", (o) => { n(); const h = i.find((c) => c[0] === o.target); h && Object.assign(h[1], { clientX: 0, clientY: 0, x: 0, y: 0 }); }); } }; } function st() { let i = []; return { name: "ShadowFollow", sort: O.thermosphere, install(t) { t.on("start", (e, s) => { if (!(s.status && p(s.option.shadowFollowOptions))) return; const r = s.option.shadowFollowOptions; if (i.find((a) => a[0] === e.target)) return; const o = { createDom: l, append: it, setDomAttrs: rt, ...r }, h = o.createDom(o, e), c = { x: 0, y: 0 }; if (r.fixed) { const a = e.target.getBoundingClientRect(); c.x = a.x, c.y = a.y, Object.assign(h.style, { left: `${a.x - e.ml}px`, top: `${a.y - e.mt}px` }); } else { const { position: a } = _(e.target); Object.assign(h.style, { left: `${e.target.offsetLeft - e.ml}px`, top: `${e.target.offsetTop - e.mt}px` }), a !== "absolute" && Object.assign(c, { x: e.target.offsetLeft, y: e.target.offsetTop }); } if (o.append(h, e), !r.fixed && h.parentElement !== e.target.parentElement) { const a = U(e.target, "offset"), u = U(h, "offset"); u.x !== a.x && (c.x = c.x + (a.x - u.x), h.style.left = `${c.x - e.ml}px`), u.y !== a.y && (c.y = c.y + (a.y - u.y), h.style.top = `${c.y - e.mt}px`); } i.push([e.target, { dom: h, x: c.x, y: c.y }]); function l(a) { const u = e.target.cloneNode(!0); return a.setDomAttrs(u, a, e), u; } }).on("axisBeforeUpdate", (e, s) => { if (!(s.status && p(s.option.shadowFollowOptions))) return; const r = i.find((n) => n[0] === e.target); r && (r[1].dom.style.left = `${e.x + r[1].x - e.ml}px`, r[1].dom.style.top = `${e.y + r[1].y - e.mt}px`); }).on("end", (e, s) => { var o; const r = i.findIndex((h) => h[0] === e.target); if (r === -1) return; const [n] = i.splice(r, 1); (o = n[1].dom.parentElement) == null || o.removeChild(n[1].dom); }); } }; } function it(i, t) { var e; (e = t.target.parentElement) == null || e.appendChild(i); } function rt(i, t, e) { const s = _(e.target).boxSizing === "border-box", r = s ? e.target.offsetWidth : e.target.clientWidth, n = s ? e.target.offsetHeight : e.target.clientHeight; Object.assign(i.style, { opacity: "0.5", pointerEvents: "none", position: t.fixed ? "fixed" : "absolute", width: `${r}px`, height: `${n}px` }), k(i, t.class), t.style && Object.assign(i.style, t.style); } const nt = ["left", "right", "x"], ot = { x: ["left", "right"], y: ["top", "bottom"] }, ht = { x: "width", y: "height" }; function ct() { return { name: "Snap", sort: O.sky + 20, install(i) { let t = []; i.on("start", (e) => { let s = t.find((n) => n[0] === e.target); s || t.push(s = [e.target, { scrollWidth: 0, scrollHeight: 0 }]); const r = X(e.target); s[1].scrollWidth = r.scrollWidth, s[1].scrollHeight = r.scrollHeight; }).on("axisBeforeUpdate", (e, s) => { if (!(s.status && p(s.option.snapOptions))) return; const r = s.option.snapOptions; if (!r.enable || r.forceSnap) return; const n = t.find((g) => g[0] === e.target); if (!n) return; const { orient: o, threshold: h = 10 } = r, c = { width: n[1].scrollWidth, height: n[1].scrollHeight }, l = W(e.target), a = o !== "y", u = o !== "x"; a && (e.x < h ? e.x = 0 : c.width - e.x - l.width < h && (e.x = c.width - l.width)), u && (e.y < h ? e.y = 0 : c.height - e.y - l.height < h && (e.y = c.height - l.height)); }).on("end", (e, s) => { if (!(s.status && p(s.option.snapOptions))) return; const r = s.option.snapOptions; if (!r.forceSnap) return; let n = r.forceSnapOrient || "x"; const o = nt.includes(n) ? "x" : "y", h = ht[o], c = X(e.target).getBoundingClientRect(), l = X(e.target)[o === "x" ? "scrollWidth" : "scrollHeight"], a = W(e.target)[h]; if (n === "x" || n === "y") { const u = ot[n]; n = e[`client${n.toUpperCase()}`] - c[n] > c[h] / 2 ? u[1] : u[0]; } e[o] = n === "left" || n === "top" ? 0 : l - a, e.setPosition(e, e.target); }); } }; } function dt(i) { return new H(i).use(tt).use(Z).use(ct).use(et).use(st); } export { Z as BoundaryLimit, tt as Direction, H as DragCore, et as Scrolling, st as ShadowFollow, ct as Snap, k as addElementClass, M as downEventName, dt as drag, gt as dragCore, U as getBoundingClientRect, _ as getElementStyle, p as getEnableStatus, F as getEvent, J as getMobilePlatStatus, X as getParent, W as getSize, G as getType, lt as isFunction, A as isMobile, ft as isObject, ut as isString, Q as matchForDomTree, z as moveEventName, y as parseDOM, R as removeElementClass, P as upEventName };