UNPKG

@base-framework/ui

Version:

This is a UI package that adds components and atoms that use Tailwind CSS and a theme based on Shadcn.

534 lines (533 loc) 15.1 kB
var m = Object.defineProperty; var x = (l, t, e) => t in l ? m(l, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : l[t] = e; var f = (l, t, e) => x(l, typeof t != "symbol" ? t + "" : t, e); import { base as r } from "@base-framework/base"; class v { /** * Creates an instance of ElementScaler. * * @constructor * @param {HTMLElement} element - The DOM element to scale. */ constructor(t) { this.element = t, this.elementBoundingBox = null, this.container = t.parentNode, this.containerSize = { width: 0, height: 0, top: 0, left: 0 }, this.setup(); } /** * Initializes the scaling behavior by removing margins * and triggering a resize check. * * @returns {void} */ setup() { this.removeMargin(), this.resize(); } /** * Removes all margin from the element (top/right/bottom/left). * * @returns {void} */ removeMargin() { this.element.style.margin = "0px 0px 0px 0px"; } /** * Gets the current bounding box of the element. * * @returns {DOMRect|null} The bounding box or null if not set. */ getSize() { return this.elementBoundingBox; } /** * Calculates and caches the bounding client rect for the element. * * @returns {void} */ setBoundingBox() { this.elementBoundingBox = this.element.getBoundingClientRect(); } /** * Applies translation and scaling (width/height) to the element. * * @param {number} x - The horizontal position (left). * @param {number} y - The vertical position (top). * @param {number} scale - Scale factor (e.g., 1.0 = 100%, 0.5 = 50%). * @returns {{width: number, height: number}} The new width and height after scaling. */ transform(t, e, s) { const n = this.element, i = n.style; i.top = e + "px", i.left = t + "px"; const o = n.naturalWidth * s, c = n.naturalHeight * s; return i.width = o + "px", i.height = c + "px", { width: o, height: c }; } /** * Updates internal bounding boxes for both the element and its container. * * @returns {void} */ resize() { this.setBoundingBox(), this.containerSize = this.container.getBoundingClientRect(); } } class y { /** * Creates an EventController instance. * * @constructor * @param {object} parent - The parent object (ImageScaler) that handles actions. * @param {HTMLElement} container - The DOM element to attach events to. */ constructor(t, e) { /** * Tracks and measures distances between touches for pinch gestures. */ f(this, "pinchTracker", { /** @type {number|null} */ previousDistance: null, /** @type {number|null} */ currentDistance: null, /** * Calculates Euclidean distance between two points. * * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @returns {number} */ distance(t, e, s, n) { return Math.sqrt( (t - s) * (t - s) + (e - n) * (e - n) ); }, /** * If currentDistance is set, store it as previousDistance. * * @returns {void} */ setPreviousDistance() { this.currentDistance !== null && (this.previousDistance = this.currentDistance); }, /** * Sets the current distance between two touch points. * * @param {object} touch1 * @param {object} touch2 * @returns {void} */ setCurrentDistance(t, e) { this.currentDistance = this.distance(t.x, t.y, e.x, e.y); }, /** * Updates currentDistance and keeps track of the previous distance. * * @param {object} touch1 * @param {object} touch2 * @returns {number} The updated current distance. */ updateCurrentDistance(t, e) { return this.setPreviousDistance(), this.setCurrentDistance(t, e), this.currentDistance; }, /** * Determines the scale direction (zoom in/out) based on distance changes. * * @param {object} touch1 * @param {object} touch2 * @returns {number} 1 for zoom in, -1 for zoom out, 0 if below threshold. */ getScale(t, e) { let s = 0; const n = this.updateCurrentDistance(t, e), i = this.previousDistance; return i === null || Math.abs(n - i) < 2 || (n > i ? s = 1 : n < i && (s = -1)), s; }, /** * Resets the distance measurements. * * @returns {void} */ reset() { this.previousDistance = null, this.currentDistance = null; } }); this.parent = t, this.container = e, this.pointer = { x: 0, y: 0, status: "up" }, this.setup(); } /** * Initializes event setup. * * @returns {void} */ setup() { this.setupEvents(); } /** * Removes all event listeners. * * @returns {void} */ remove() { this.removeEvents(); } /** * Creates and binds all required event listeners. * * @returns {void} */ setupEvents() { const t = this.container, e = r.bind(this, this.pointerMove), s = r.bind(this, this.pointerUp), n = r.bind(this, this.pointerDown), i = r.bind(this, this.wheel), o = r.bind(this, this.resize); this.addEvents = function() { r.on(["mousemove", "touchmove"], t, e), r.on(["mouseup", "mouseout", "touchend", "touchcancel"], t, s), r.on(["mousedown", "touchstart"], t, n), r.onMouseWheel(i, t, !0), r.on("resize", globalThis, o); }, this.addEvents(), this.removeEvents = function() { r.off(["mousemove", "touchmove"], t, e), r.off(["mouseup", "mouseout", "touchend", "touchcancel"], t, s), r.off(["mousedown", "touchstart"], t, n), r.offMouseWheel(i, t), r.off("resize", globalThis, o); }; } /** * Handles mouse wheel or pinch events. * * @param {number} delta - The wheel direction (positive or negative). * @param {Event} e - The associated event. * @returns {void} */ wheel(t, e) { this.parent.callAction("pinch", e, t); } /** * Extracts the position from mouse or touch events and updates `this.pointer`. * * @param {Event} e - The event object. * @returns {void} */ getEventPosition(t) { let e, s; const n = t.touches; if (n && n.length) { const i = n[0]; e = i.clientX, s = i.clientY; } else e = t.clientX, s = t.clientY; this.pointer.x = e, this.pointer.y = s; } /** * Called when the pointer goes down (mouse/touch). * * @param {Event} e - The associated event. * @returns {void} */ pointerDown(t) { this.getEventPosition(t), this.pointer.status = "down", this.isGesture(t) === !1 && this.parent.callAction("pointerDown", t); } /** * Called when the pointer is released. * * @param {Event} e - The associated event. * @returns {void} */ pointerUp(t) { this.pointer.status = "up", this.parent.callAction("pointerUp", t), this.pinchTracker.reset(); } /** * Handles window resize actions. * * @returns {void} */ resize() { this.parent.resize(); } /** * Extracts all touches from the event object. * * @param {Event} e - The touch event. * @returns {Array<object>} Array of touch points. */ getTouches(t) { const e = [], s = t.touches; if (s && s.length) for (let n = 0; n < s.length; n++) e.push(s[n]); return e; } /** * Calculates the midpoint (center) between two sets of coordinates. * * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @returns {{x: number, y: number}} The center coordinates. */ getCenter(t, e, s, n) { return { x: (t + s) / 2, y: (e + n) / 2 }; } /** * Attempts a pinch gesture if two touches are present. * * @param {Event} e - The touch event. * @returns {void} */ pinch(t) { const e = this.getTouches(t); if (e.length === 2) { this.pointer.status = "down"; const s = e[0], n = e[1], i = this.getPosition(s.clientX, s.clientY), o = this.getPosition(n.clientX, n.clientY); this.centerMousePinch(s, n); const c = this.pinchTracker.getScale(i, o); this.parent.callAction("pinch", t, c); } } /** * Creates a coordinate object from x/y. * * @param {number} eX * @param {number} eY * @returns {{x: number, y: number}} */ getPosition(t, e) { return { x: parseInt(String(t)), y: parseInt(String(e)) }; } /** * Moves pointer coordinates to the midpoint of two touches for pinch usage. * * @param {Touch} touch1 * @param {Touch} touch2 * @returns {void} */ centerMousePinch(t, e) { const s = this.getCenter( t.clientX, t.clientY, e.clientX, e.clientY ), n = this.pointer; n.x = s.x, n.y = s.y; } /** * Called on a rotate gesture (currently not used). * * @param {Event} e - The associated event. * @returns {void} */ rotate(t) { this.pointer.status = "down", this.parent.callAction("rotate", t); } /** * Checks if the event is a multi-touch gesture. If yes, performs pinch logic. * * @param {Event} e - The event object. * @returns {boolean} True if it was a gesture (pinch); false otherwise. */ isGesture(t) { let e = !1; const s = t.touches; return s && s.length > 1 && (t.preventDefault(), this.pinch(t), e = !0), e; } /** * Called when the pointer moves (mouse/touch) but might also detect pinch. * * @param {Event} e - The associated event. * @returns {void} */ pointerMove(t) { this.getEventPosition(t), this.isGesture(t) === !1 && this.parent.callAction("pointerMove", t); } } class B { /** * Creates an instance of ImageScaler. * * @constructor * @param {HTMLImageElement} element - The image element to scale. */ constructor(t) { /** @type {number} Minimum allowed scale factor. */ f(this, "minScale", 0.2); this.elementScaler = new v(t), this.scale = this.getImageScale(t), this.panning = !1, this.events = null, this.start = { x: 0, y: 0 }, this.delta = { x: 0, y: 0 }, this.setup(); } /** * Initializes event handling and centers the image. * * @returns {void} */ setup() { this.setupEvents(), this.center(); } /** * Removes all event bindings. * * @returns {void} */ remove() { this.events.remove(); } /** * Invokes a method on this class by name, passing event/data. * * @param {string} action - The method name to call. * @param {Event} e - The associated event object. * @param {*} [data] - Additional data passed to the method. * @returns {void} */ callAction(t, e, s) { this[t](e, s); } /** * Sets up the event controller for the image. * * @returns {void} */ setupEvents() { this.events = new y(this, this.elementScaler.element); } /** * Calculates an initial scale based on the element's offsetWidth vs. naturalWidth. * * @param {HTMLImageElement} element - The image element. * @returns {number} The initial scale factor. */ getImageScale(t) { return t.offsetWidth / t.naturalWidth; } /** * Gets the offset position of the pointer, adjusted by scale and delta. * * @param {Event} e - The associated event object. * @returns {{x: number, y: number}} The pointer offset without scale. */ getOffset(t) { const e = this.scale, s = this.delta, n = this.getPointerPosition(); return { x: (n.x - s.x) / e, y: (n.y - s.y) / e }; } /** * Transforms the element (x, y, scale) and then re-centers it if needed. * * @param {number} x - The new left offset. * @param {number} y - The new top offset. * @param {number} scale - The scale factor. * @returns {void} */ scaleElement(t, e, s) { const n = this.elementScaler.transform(t, e, s); this.center(n.width, n.height); } /** * Attempts to center the scaled element within its container, respecting boundaries. * * @param {number} [width] - Width of the scaled element. * @param {number} [height] - Height of the scaled element. * @returns {void} */ center(t, e) { const s = this.elementScaler, n = s.containerSize, i = s.elementBoundingBox, o = this.delta; t = t || i.width, e = e || i.height; let c, h; const u = n.width; if (t < u) c = u / 2 - t / 2, c = c > 0 ? c : 0; else { c = o.x; const a = t + o.x; a < u && (c = a + (u - a) - t), o.x > 0 && (c = 0); } const p = n.height; if (e < p) h = p / 2 - e / 2, h = h > 0 ? h : 0; else { h = o.y; const a = e + o.y; a < p && (h = a + (p - a) - e), o.y > 0 && (h = 0); } const d = s.element.style; d.left = c + "px", d.top = h + "px", this.delta = { x: c, y: h }; } /** * Updates the current scale (zoom) value based on scroll delta. * * @param {number} delta - Positive = zoom in, negative = zoom out. * @returns {number} The updated scale factor. */ updateScale(t) { let e = this.scale; return t !== 0 && (e = t > 0 ? this.scale *= 1.05 : this.scale /= 1.05), e <= this.minScale && (this.scale = this.minScale), this.scale; } /** * Returns the pointer position relative to the container. * * @returns {{x: number, y: number}} The pointer coordinates. */ getPointerPosition() { const t = this.elementScaler.containerSize, e = this.events.pointer; return { x: e.x - t.left, y: e.y - t.top }; } /** * Called when the user presses down on the pointer (mouse/touch). * * @param {Event} e - The associated event object. * @returns {void} */ pointerDown(t) { const e = this.delta, s = this.getPointerPosition(); this.start = { x: s.x - e.x, y: s.y - e.y }, this.panning = !0; } /** * Called when the user moves the pointer (mouse/touch). * * @param {Event} e - The associated event object. * @returns {void} */ pointerMove(t) { if (t.preventDefault(), !this.panning) return; const e = this.getPointerPosition(), s = this.delta, n = this.start; s.x = e.x - n.x, s.y = e.y - n.y, this.scaleElement(s.x, s.y, this.scale); } /** * Called when the user releases the pointer (mouse/touch). * * @param {Event} e - The associated event object. * @returns {void} */ pointerUp(t) { this.panning = !1; } /** * Recalculates container/element bounding sizes, e.g., on window resize. * * @returns {void} */ resize() { this.elementScaler.resize(); } /** * Called on pinch gesture (usually from a wheel or multi-touch). * * @param {Event} e - The associated event. * @param {number} delta - Positive = zoom in, negative = zoom out. * @returns {void} */ pinch(t, e) { const s = this.getOffset(t); t.preventDefault(); const n = this.updateScale(e), i = this.getPointerPosition(), o = this.delta; o.x = i.x - s.x * n, o.y = i.y - s.y * n, this.scaleElement(o.x, o.y, n); } } export { B as I };