@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
JavaScript
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
};