UNPKG

@zag-js/splitter

Version:

Core logic for the splitter widget implemented as a state machine

207 lines (205 loc) • 8.27 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/utils/registry.ts var registry_exports = {}; __export(registry_exports, { SplitterRegistry: () => SplitterRegistry, registry: () => registry }); module.exports = __toCommonJS(registry_exports); var import_dom_query = require("@zag-js/dom-query"); var import_intersects = require("./intersects.js"); var import_stacking_order = require("./stacking-order.js"); var SplitterRegistry = class { constructor(options = {}) { __publicField(this, "handles", /* @__PURE__ */ new Map()); __publicField(this, "state", { activeHandleIds: /* @__PURE__ */ new Set(), isPointerDown: false }); __publicField(this, "listenerAttached", false); __publicField(this, "options"); __publicField(this, "handlePointerMove", (event) => { if (this.state.isPointerDown) return; const pointerType = this.getPointerType(event); const intersecting = this.findHitHandles(event.clientX, event.clientY, pointerType); const newActiveIds = new Set(intersecting.map((h) => h.id)); const changed = newActiveIds.size !== this.state.activeHandleIds.size || [...newActiveIds].some((id) => !this.state.activeHandleIds.has(id)); if (changed) { this.state.activeHandleIds = newActiveIds; this.updateCursor(intersecting); } }); __publicField(this, "handlePointerDown", (event) => { const pointerType = this.getPointerType(event); const intersecting = this.findIntersectingHandles(event.clientX, event.clientY, pointerType, event.target); if (intersecting.length > 0) { this.state.isPointerDown = true; this.state.activeHandleIds = new Set(intersecting.map((h) => h.id)); const point = { x: event.clientX, y: event.clientY }; intersecting.forEach((handle) => { handle.onActivate(point); }); this.updateCursor(intersecting); } }); __publicField(this, "handlePointerUp", (_event) => { if (this.state.isPointerDown) { this.state.isPointerDown = false; this.handles.forEach((handle) => { if (this.state.activeHandleIds.has(handle.id)) { handle.onDeactivate(); } }); this.state.activeHandleIds.clear(); this.clearGlobalCursor(); } }); __publicField(this, "globalCursorId", "splitter-registry-cursor"); this.options = { nonce: options.nonce ?? "", hitAreaMargins: { coarse: options.hitAreaMargins?.coarse ?? 15, fine: options.hitAreaMargins?.fine ?? 5 } }; } register(data) { this.handles.set(data.id, data); this.attachGlobalListeners(); return () => { this.handles.delete(data.id); this.state.activeHandleIds.delete(data.id); if (this.handles.size === 0) { this.detachGlobalListeners(); } }; } attachGlobalListeners() { if (this.listenerAttached) return; this.doc.addEventListener("pointermove", this.handlePointerMove, true); this.doc.addEventListener("pointerdown", this.handlePointerDown, true); this.doc.addEventListener("pointerup", this.handlePointerUp, true); this.listenerAttached = true; } detachGlobalListeners() { if (!this.listenerAttached) return; this.doc.removeEventListener("pointermove", this.handlePointerMove, true); this.doc.removeEventListener("pointerdown", this.handlePointerDown, true); this.doc.removeEventListener("pointerup", this.handlePointerUp, true); this.listenerAttached = false; } getPointerType(event) { return event.pointerType === "touch" || event.pointerType === "pen" ? "coarse" : "fine"; } get doc() { const firstHandle = this.handles.values().next().value; return (0, import_dom_query.getDocument)(firstHandle?.element); } /** * Fast hit-test: only checks pointer proximity to handles (no stacking order). * Used for pointermove cursor feedback. */ findHitHandles(x, y, pointerType) { const intersecting = []; const margin = this.options.hitAreaMargins[pointerType]; this.handles.forEach((handle) => { const rect = handle.element.getBoundingClientRect(); const hit = x >= rect.left - margin && x <= rect.right + margin && y >= rect.top - margin && y <= rect.bottom + margin; if (hit) intersecting.push(handle); }); return intersecting; } /** * Full intersection check: hit-test + stacking order verification. * Used for pointerdown activation where correctness matters. */ findIntersectingHandles(x, y, pointerType, eventTarget) { const hits = this.findHitHandles(x, y, pointerType); const targetElement = (0, import_dom_query.isElement)(eventTarget) ? eventTarget : null; if (!targetElement || !(0, import_dom_query.contains)(this.doc, targetElement)) return hits; return hits.filter((handle) => { const dragHandleElement = handle.element; if (targetElement === dragHandleElement || (0, import_dom_query.contains)(dragHandleElement, targetElement) || (0, import_dom_query.contains)(targetElement, dragHandleElement)) { return true; } try { if ((0, import_stacking_order.compareStackingOrder)(targetElement, dragHandleElement) > 0) { const dragHandleRect = dragHandleElement.getBoundingClientRect(); let currentElement = targetElement; while (currentElement) { if (currentElement.contains(dragHandleElement)) break; const currentRect = currentElement.getBoundingClientRect(); if ((0, import_intersects.intersects)((0, import_intersects.toRect)(currentRect), (0, import_intersects.toRect)(dragHandleRect), true)) { return false; } currentElement = (0, import_dom_query.getParentElement)(currentElement); } } } catch { } return true; }); } updateCursor(intersecting) { if (intersecting.length === 0) { this.clearGlobalCursor(); return; } const hasHorizontal = intersecting.some((h) => h.orientation === "horizontal"); const hasVertical = intersecting.some((h) => h.orientation === "vertical"); let cursor = "default"; if (hasHorizontal && hasVertical) { cursor = "move"; } else if (hasHorizontal) { cursor = "ew-resize"; } else if (hasVertical) { cursor = "ns-resize"; } this.setGlobalCursor(cursor); } setGlobalCursor(cursor) { const doc = this.doc; let styleEl = doc.getElementById(this.globalCursorId); const textContent = `* { cursor: ${cursor} !important; }`; if (styleEl) { styleEl.textContent = textContent; } else { styleEl = doc.createElement("style"); styleEl.id = this.globalCursorId; styleEl.textContent = textContent; if (this.options.nonce) { styleEl.nonce = this.options.nonce; } doc.head.appendChild(styleEl); } } clearGlobalCursor() { const styleEl = this.doc.getElementById(this.globalCursorId); styleEl?.remove(); } }; var registry = (opts = {}) => new SplitterRegistry(opts); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SplitterRegistry, registry });