hy-masonry
Version:
Animated Masonry Layout as Web Component - Living organisms that morph and breathe
1,030 lines (1,029 loc) • 29.8 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const hyMasonry$1 = "";
class AnimationEngine {
constructor() {
__publicField(this, "animations", /* @__PURE__ */ new Map());
__publicField(this, "animationFrame", null);
__publicField(this, "isRunning", false);
__publicField(this, "lastTime", 0);
this.start();
}
/**
* Add animation to an item
*/
addAnimation(itemId, animationType, options = {}) {
const animationFunction = this.createAnimation(animationType, options);
this.animations.set(itemId, animationFunction);
}
/**
* Remove animation from an item
*/
removeAnimation(itemId) {
this.animations.delete(itemId);
}
/**
* Create animation function based on type
*/
createAnimation(type, options) {
switch (type) {
case "breathing":
const breathing = new BreathingAnimation(options);
return (element, deltaTime) => breathing.update(element, deltaTime);
case "pulse":
const pulse = new PulseAnimation(options);
return (element, deltaTime) => pulse.update(element, deltaTime);
case "float":
const float = new FloatAnimation(options);
return (element, deltaTime) => float.update(element, deltaTime);
default:
return (_element, _deltaTime) => {
};
}
}
/**
* Start animation loop
*/
start() {
if (this.isRunning)
return;
this.isRunning = true;
this.lastTime = performance.now();
this.animate();
}
/**
* Stop animation loop
*/
stop() {
this.isRunning = false;
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
this.animationFrame = null;
}
}
/**
* Main animation loop
*/
animate() {
if (!this.isRunning)
return;
const currentTime = performance.now();
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
this.animations.forEach((animation, itemId) => {
const element = document.querySelector(`[data-item-id="${itemId}"]`);
if (element) {
animation(element, deltaTime);
}
});
this.animationFrame = requestAnimationFrame(() => this.animate());
}
/**
* Get animation count
*/
getAnimationCount() {
return this.animations.size;
}
/**
* Clear all animations
*/
clear() {
this.animations.clear();
}
}
class BreathingAnimation {
constructor(options = {}) {
__publicField(this, "speed");
__publicField(this, "intensity");
__publicField(this, "phase");
this.speed = options.speed || 2e3;
this.intensity = options.intensity || 0.05;
this.phase = options.phase || 0;
}
update(element, deltaTime) {
const scale = 1 + Math.sin(this.phase) * this.intensity;
element.style.transform = `scale(${scale})`;
this.phase += deltaTime / this.speed * Math.PI * 2;
}
}
class PulseAnimation {
constructor(options = {}) {
__publicField(this, "speed");
__publicField(this, "phase");
this.speed = options.speed || 1500;
this.phase = options.phase || 0;
}
update(element, deltaTime) {
const opacity = 0.8 + Math.sin(this.phase) * 0.2;
element.style.opacity = opacity.toString();
this.phase += deltaTime / this.speed * Math.PI * 2;
}
}
class FloatAnimation {
constructor(options = {}) {
__publicField(this, "speed");
__publicField(this, "intensity");
__publicField(this, "phase");
this.speed = options.speed || 3e3;
this.intensity = options.intensity || 5;
this.phase = options.phase || 0;
}
update(element, deltaTime) {
const translateY = Math.sin(this.phase) * this.intensity;
element.style.transform = `translateY(${translateY}px)`;
this.phase += deltaTime / this.speed * Math.PI * 2;
}
}
class SizeCalculator {
/**
* Parse size string (e.g., "2x3") into dimensions
*/
static parseSize(sizeString) {
const [cols, rows] = sizeString.split("x").map(Number);
return { cols: cols || 1, rows: rows || 1 };
}
/**
* Calculate actual dimensions for a grid item
*/
static calculateDimensions(size, containerWidth, gap, columns) {
const { cols, rows } = this.parseSize(size);
const availableWidth = containerWidth - gap * (columns - 1);
const itemWidth = availableWidth / columns;
const actualWidth = itemWidth * cols + gap * (cols - 1);
const actualHeight = itemWidth * rows;
return { width: actualWidth, height: actualHeight };
}
/**
* Morph size between original and target
*/
static morphSize(originalSize, targetSize, progress) {
const original = this.parseSize(originalSize);
const target = this.parseSize(targetSize);
const cols = original.cols + (target.cols - original.cols) * progress;
const rows = original.rows + (target.rows - original.rows) * progress;
return `${Math.round(cols)}x${Math.round(rows)}`;
}
/**
* Validate size string format
*/
static isValidSize(size) {
const pattern = /^\d+x\d+$/;
return pattern.test(size);
}
/**
* Get minimum size for an item
*/
static getMinSize(size) {
const { cols, rows } = this.parseSize(size);
return `${Math.max(1, Math.floor(cols / 2))}x${Math.max(1, Math.floor(rows / 2))}`;
}
/**
* Get maximum size for an item
*/
static getMaxSize(size) {
const { cols, rows } = this.parseSize(size);
return `${cols * 2}x${rows * 2}`;
}
/**
* Calculate grid position for an item
*/
static calculateGridPosition(_itemIndex, _columns, columnHeights) {
const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
return {
x: shortestColumn,
y: columnHeights[shortestColumn]
};
}
/**
* Update column heights after placing an item
*/
static updateColumnHeights(columnHeights, columnIndex, itemHeight, gap) {
const newHeights = [...columnHeights];
newHeights[columnIndex] += itemHeight + gap;
return newHeights;
}
}
class MorphingManager {
constructor() {
__publicField(this, "morphingItems", /* @__PURE__ */ new Map());
__publicField(this, "originalSizes", /* @__PURE__ */ new Map());
__publicField(this, "morphAnimationFrame", null);
__publicField(this, "isRunning", false);
this.start();
}
/**
* Morph item to new size
*/
morphItem(itemId, targetSize, duration = 500) {
const element = document.querySelector(`[data-item-id="${itemId}"]`);
if (!element)
return;
const currentSize = element.getAttribute("data-size") || "1x1";
const originalSize = this.originalSizes.get(itemId) || currentSize;
const morphingState = {
originalSize,
targetSize,
duration,
startTime: Date.now(),
progress: 0,
update: function(currentTime) {
this.progress = Math.min((currentTime - this.startTime) / this.duration, 1);
return this.progress;
},
isComplete: function() {
return this.progress >= 1;
}
};
this.morphingItems.set(itemId, morphingState);
element.classList.add("morphing");
this.dispatchEvent("item:morph", { itemId, targetSize, duration });
}
/**
* Reset item to original size
*/
resetItem(itemId, duration = 500) {
const originalSize = this.originalSizes.get(itemId);
if (originalSize) {
this.morphItem(itemId, originalSize, duration);
}
}
/**
* Check if item is currently morphing
*/
isMorphing(itemId) {
return this.morphingItems.has(itemId);
}
/**
* Store original size for an item
*/
storeOriginalSize(itemId, size) {
this.originalSizes.set(itemId, size);
}
/**
* Start morphing animation loop
*/
start() {
if (this.isRunning)
return;
this.isRunning = true;
this.animate();
}
/**
* Stop morphing animation loop
*/
stop() {
this.isRunning = false;
if (this.morphAnimationFrame) {
cancelAnimationFrame(this.morphAnimationFrame);
this.morphAnimationFrame = null;
}
}
/**
* Main morphing animation loop
*/
animate() {
if (!this.isRunning)
return;
const currentTime = Date.now();
const completedItems = [];
this.morphingItems.forEach((state, itemId) => {
const element = document.querySelector(`[data-item-id="${itemId}"]`);
if (!element) {
completedItems.push(itemId);
return;
}
const progress = state.update(currentTime);
const morphedSize = SizeCalculator.morphSize(
state.originalSize,
state.targetSize,
progress
);
element.setAttribute("data-size", morphedSize);
element.style.gridColumn = `span ${SizeCalculator.parseSize(morphedSize).cols}`;
element.style.gridRow = `span ${SizeCalculator.parseSize(morphedSize).rows}`;
if (state.isComplete()) {
completedItems.push(itemId);
element.classList.remove("morphing");
this.dispatchEvent("item:morph-end", { itemId, finalSize: state.targetSize });
}
});
completedItems.forEach((itemId) => {
this.morphingItems.delete(itemId);
});
this.morphAnimationFrame = requestAnimationFrame(() => this.animate());
}
/**
* Dispatch custom event
*/
dispatchEvent(eventName, detail) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
}
/**
* Get morphing items count
*/
getMorphingCount() {
return this.morphingItems.size;
}
/**
* Clear all morphing states
*/
clear() {
this.morphingItems.clear();
}
/**
* Get morphing state for an item
*/
getMorphingState(itemId) {
return this.morphingItems.get(itemId);
}
}
class InteractionHandler {
constructor(config = {}) {
__publicField(this, "longPressTimers", /* @__PURE__ */ new Map());
__publicField(this, "touchStartPositions", /* @__PURE__ */ new Map());
__publicField(this, "longPressDelay", 500);
__publicField(this, "touchEnabled", true);
this.longPressDelay = config.longPressDelay || 500;
this.touchEnabled = config.touchEnabled !== false;
}
/**
* Setup event listeners for an item
*/
setupItemListeners(item, element) {
if (item.hoverable !== false) {
element.addEventListener("mouseenter", (e) => this.handleMouseEnter(e, item));
element.addEventListener("mouseleave", (e) => this.handleMouseLeave(e, item));
}
if (item.clickable !== false) {
element.addEventListener("click", (e) => this.handleClick(e, item));
}
if (this.touchEnabled) {
element.addEventListener("touchstart", (e) => this.handleTouchStart(e, item));
element.addEventListener("touchend", (e) => this.handleTouchEnd(e, item));
element.addEventListener("touchmove", (e) => this.handleTouchMove(e, item));
}
element.addEventListener("keydown", (e) => this.handleKeyDown(e, item));
element.setAttribute("tabindex", "0");
}
/**
* Handle mouse enter
*/
handleMouseEnter(event, item) {
const element = event.target;
element.classList.add("hover");
if (item.morphOnHover && item.morphSize) {
this.dispatchMorphEvent(element, item.morphSize, item.morphDuration || 300);
}
if (item.onHover) {
item.onHover(event, item);
}
this.dispatchEvent("item:hover", { item, element });
}
/**
* Handle mouse leave
*/
handleMouseLeave(event, item) {
const element = event.target;
element.classList.remove("hover");
if (item.morphOnHover && item.morphSize) {
const originalSize = element.getAttribute("data-original-size") || item.size;
this.dispatchMorphEvent(element, originalSize, item.morphDuration || 300);
}
this.dispatchEvent("item:hover-end", { item, element });
}
/**
* Handle click
*/
handleClick(event, item) {
if (item.onClick) {
item.onClick(event, item);
}
this.dispatchEvent("item:click", { item, event });
}
/**
* Handle touch start
*/
handleTouchStart(event, item) {
const touch = event.touches[0];
const element = event.target;
const itemId = element.getAttribute("data-item-id");
if (itemId) {
this.touchStartPositions.set(itemId, { x: touch.clientX, y: touch.clientY });
const timer = window.setTimeout(() => {
this.handleLongPress(event, item);
}, this.longPressDelay);
this.longPressTimers.set(itemId, timer);
}
}
/**
* Handle touch end
*/
handleTouchEnd(event, _item) {
const element = event.target;
const itemId = element.getAttribute("data-item-id");
if (itemId) {
const timer = this.longPressTimers.get(itemId);
if (timer) {
clearTimeout(timer);
this.longPressTimers.delete(itemId);
}
this.touchStartPositions.delete(itemId);
}
}
/**
* Handle touch move
*/
handleTouchMove(event, _item) {
const element = event.target;
const itemId = element.getAttribute("data-item-id");
if (itemId) {
const startPos = this.touchStartPositions.get(itemId);
if (startPos) {
const touch = event.touches[0];
const deltaX = Math.abs(touch.clientX - startPos.x);
const deltaY = Math.abs(touch.clientY - startPos.y);
if (deltaX > 10 || deltaY > 10) {
const timer = this.longPressTimers.get(itemId);
if (timer) {
clearTimeout(timer);
this.longPressTimers.delete(itemId);
}
}
}
}
}
/**
* Handle long press
*/
handleLongPress(event, item) {
const element = event.target;
if (item.morphOnLongPress && item.morphSize) {
this.dispatchMorphEvent(element, item.morphSize, item.morphDuration || 500);
}
if (item.onLongPress) {
item.onLongPress(event, item);
}
this.dispatchEvent("item:longpress", { item, element });
}
/**
* Handle key down
*/
handleKeyDown(event, item) {
const element = event.target;
switch (event.key) {
case "Enter":
case " ":
event.preventDefault();
this.handleClick(event, item);
break;
case "Escape":
if (item.morphOnHover && item.morphSize) {
const originalSize = element.getAttribute("data-original-size") || item.size;
this.dispatchMorphEvent(element, originalSize, item.morphDuration || 300);
}
break;
}
}
/**
* Dispatch morph event
*/
dispatchMorphEvent(element, targetSize, duration) {
const event = new CustomEvent("item:morph-request", {
detail: {
element,
targetSize,
duration
},
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
}
/**
* Dispatch custom event
*/
dispatchEvent(eventName, detail) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
}
/**
* Clean up event listeners
*/
cleanupItemListeners(element) {
const itemId = element.getAttribute("data-item-id");
if (itemId) {
const timer = this.longPressTimers.get(itemId);
if (timer) {
clearTimeout(timer);
this.longPressTimers.delete(itemId);
}
this.touchStartPositions.delete(itemId);
}
}
/**
* Get active timers count
*/
getActiveTimersCount() {
return this.longPressTimers.size;
}
/**
* Clear all timers
*/
clearAllTimers() {
this.longPressTimers.forEach((timer) => clearTimeout(timer));
this.longPressTimers.clear();
this.touchStartPositions.clear();
}
}
class HyMasonry extends HTMLElement {
constructor() {
super();
__publicField(this, "config");
__publicField(this, "items", /* @__PURE__ */ new Map());
__publicField(this, "animationEngine");
__publicField(this, "morphingManager");
__publicField(this, "interactionHandler");
__publicField(this, "layout", null);
__publicField(this, "resizeObserver", null);
__publicField(this, "intersectionObserver", null);
__publicField(this, "container", null);
this.config = this.getDefaultConfig();
this.animationEngine = new AnimationEngine();
this.morphingManager = new MorphingManager();
this.interactionHandler = new InteractionHandler({
longPressDelay: this.config.longPressDelay,
touchEnabled: this.config.touchEnabled
});
}
/**
* Get default configuration
*/
getDefaultConfig() {
return {
columns: 6,
gap: 20,
padding: 20,
animation: true,
animationDuration: 300,
breathingEffect: true,
breathingSpeed: 2e3,
morphing: true,
morphDuration: 500,
hoverMorph: true,
longPressMorph: true,
responsive: true,
breakpoints: {
mobile: { columns: 2, gap: 12 },
tablet: { columns: 4, gap: 16 },
desktop: { columns: 6, gap: 20 }
},
longPressDelay: 500,
touchEnabled: true,
theme: "default",
borderRadius: 12,
borderWidth: 0,
shadow: true,
throttleResize: 100,
useTransform: true,
hardwareAcceleration: true
};
}
/**
* Read configuration from HTML attributes
*/
readAttributes() {
const columns = this.getAttribute("columns");
if (columns) {
this.config.columns = parseInt(columns);
}
const gap = this.getAttribute("gap");
if (gap) {
this.config.gap = parseInt(gap);
}
const padding = this.getAttribute("padding");
if (padding) {
this.config.padding = parseInt(padding);
}
const borderWidth = this.getAttribute("border-width");
if (borderWidth) {
this.config.borderWidth = parseInt(borderWidth);
}
const borderRadius = this.getAttribute("border-radius");
if (borderRadius) {
this.config.borderRadius = parseInt(borderRadius);
}
}
/**
* Connected callback - when element is added to DOM
*/
connectedCallback() {
this.readAttributes();
this.setupComponent();
this.setupEventListeners();
this.setupObservers();
this.applyTheme();
this.refresh();
}
/**
* Disconnected callback - when element is removed from DOM
*/
disconnectedCallback() {
this.cleanup();
}
/**
* Attribute changed callback
*/
static get observedAttributes() {
return ["columns", "gap", "padding", "border-width", "border-radius"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue)
return;
switch (name) {
case "columns":
this.config.columns = newValue ? parseInt(newValue) : 4;
break;
case "gap":
this.config.gap = newValue ? parseInt(newValue) : 16;
break;
case "padding":
this.config.padding = newValue ? parseInt(newValue) : 20;
break;
case "border-width":
this.config.borderWidth = newValue ? parseInt(newValue) : 0;
break;
case "border-radius":
this.config.borderRadius = newValue ? parseInt(newValue) : 8;
break;
}
this.setupComponent();
this.refresh();
}
/**
* Setup component structure
*/
setupComponent() {
this.container = document.createElement("div");
this.container.className = "masonry-container";
this.appendChild(this.container);
this.style.setProperty("--columns", this.config.columns?.toString() || "4");
this.style.setProperty("--gap", `${this.config.gap}px`);
this.style.setProperty("--padding", `${this.config.padding}px`);
this.style.setProperty("--border-radius", `${this.config.borderRadius}px`);
this.style.setProperty("--border-width", `${this.config.borderWidth}px`);
this.style.setProperty("--transition", `${this.config.animationDuration}ms ease`);
this.style.setProperty("--breathing-speed", `${this.config.breathingSpeed}ms`);
this.style.setProperty("--morph-duration", `${this.config.morphDuration}ms`);
}
/**
* Setup event listeners
*/
setupEventListeners() {
this.addEventListener("item:morph-request", (event) => {
const customEvent = event;
const { element, targetSize, duration } = customEvent.detail;
const itemId = element.getAttribute("data-item-id");
if (itemId) {
this.morphingManager.morphItem(itemId, targetSize, duration);
}
});
const throttledRefresh = this.throttle(() => {
this.refresh();
}, this.config.throttleResize || 100);
window.addEventListener("resize", throttledRefresh);
}
/**
* Setup observers
*/
setupObservers() {
this.resizeObserver = new ResizeObserver(() => {
this.refresh();
});
this.resizeObserver.observe(this);
this.intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
} else {
entry.target.classList.remove("visible");
}
});
},
{ threshold: 0.1 }
);
}
/**
* Apply theme
*/
applyTheme() {
const themes = {
default: {
"--bg-color": "#ffffff",
"--text-color": "#000000",
"--border-color": "#333333",
"--shadow": "0 2px 8px rgba(0, 0, 0, 0.1)",
"--hover-shadow": "0 4px 16px rgba(0, 0, 0, 0.15)",
"--hover-scale": "1.02"
},
dark: {
"--bg-color": "#1a1a1a",
"--text-color": "#ffffff",
"--border-color": "#333333",
"--shadow": "0 2px 8px rgba(0, 0, 0, 0.3)",
"--hover-shadow": "0 4px 16px rgba(0, 0, 0, 0.4)",
"--hover-scale": "1.02"
},
minimal: {
"--bg-color": "#fafafa",
"--text-color": "#333333",
"--border-color": "#e5e5e5",
"--shadow": "none",
"--hover-shadow": "0 2px 8px rgba(0, 0, 0, 0.1)",
"--hover-scale": "1.01"
},
glass: {
"--bg-color": "rgba(255, 255, 255, 0.1)",
"--text-color": "#ffffff",
"--border-color": "rgba(255, 255, 255, 0.2)",
"--shadow": "0 8px 32px rgba(31, 38, 135, 0.37)",
"--hover-shadow": "0 12px 40px rgba(31, 38, 135, 0.5)",
"--hover-scale": "1.05"
},
neon: {
"--bg-color": "#000000",
"--text-color": "#00ff00",
"--border-color": "#00ff00",
"--shadow": "0 0 10px rgba(0, 255, 0, 0.5)",
"--hover-shadow": "0 0 20px rgba(0, 255, 0, 0.8)",
"--hover-scale": "1.1"
},
custom: {
"--bg-color": "#ffffff",
"--text-color": "#000000",
"--border-color": "#e0e0e0",
"--shadow": "0 2px 8px rgba(0, 0, 0, 0.1)",
"--hover-shadow": "0 4px 16px rgba(0, 0, 0, 0.15)",
"--hover-scale": "1.02"
}
};
const themeName = this.config.theme || "default";
const theme = themes[themeName] || themes.default;
Object.entries(theme).forEach(([property, value]) => {
this.style.setProperty(property, value);
});
}
/**
* Add item to masonry
*/
addItem(item) {
this.items.set(item.id, item);
this.createItemElement(item);
this.refresh();
this.dispatchCustomEvent("item:added", { item });
}
/**
* Remove item from masonry
*/
removeItem(itemId) {
const item = this.items.get(itemId);
if (item) {
this.items.delete(itemId);
const element = this.querySelector(`[data-item-id="${itemId}"]`);
if (element) {
this.interactionHandler.cleanupItemListeners(element);
element.remove();
}
this.refresh();
this.dispatchCustomEvent("item:removed", { item });
}
}
/**
* Update item
*/
updateItem(itemId, newConfig) {
const item = this.items.get(itemId);
if (item) {
const updatedItem = { ...item, ...newConfig };
this.items.set(itemId, updatedItem);
this.updateItemElement(itemId, updatedItem);
this.refresh();
this.dispatchCustomEvent("item:updated", { item: updatedItem });
}
}
/**
* Create item element
*/
createItemElement(item) {
if (!this.container)
return;
const element = document.createElement("div");
element.className = "masonry-item";
element.setAttribute("data-item-id", item.id);
element.setAttribute("data-size", item.size);
element.setAttribute("data-original-size", item.size);
if (item.backgroundColor) {
element.style.backgroundColor = item.backgroundColor;
}
if (item.textColor) {
element.style.color = item.textColor;
}
if (item.image) {
element.style.backgroundImage = `url(${item.image})`;
element.style.backgroundSize = "cover";
element.style.backgroundPosition = "center";
}
if (item.content) {
element.innerHTML = item.content;
}
this.interactionHandler.setupItemListeners(item, element);
if (this.config.animation && item.animationType && item.animationType !== "none") {
this.animationEngine.addAnimation(item.id, item.animationType, {
speed: this.config.breathingSpeed,
intensity: 0.05
});
}
this.morphingManager.storeOriginalSize(item.id, item.size);
if (this.intersectionObserver) {
this.intersectionObserver.observe(element);
}
this.container.appendChild(element);
}
/**
* Update item element
*/
updateItemElement(itemId, item) {
const element = this.querySelector(`[data-item-id="${itemId}"]`);
if (!element)
return;
element.setAttribute("data-size", item.size);
element.setAttribute("data-original-size", item.size);
if (item.backgroundColor) {
element.style.backgroundColor = item.backgroundColor;
}
if (item.textColor) {
element.style.color = item.textColor;
}
if (item.image) {
element.style.backgroundImage = `url(${item.image})`;
}
if (item.content) {
element.innerHTML = item.content;
}
if (this.config.animation && item.animationType && item.animationType !== "none") {
this.animationEngine.addAnimation(item.id, item.animationType);
} else {
this.animationEngine.removeAnimation(item.id);
}
}
/**
* Refresh layout
*/
refresh() {
if (!this.container)
return;
const containerWidth = this.container.offsetWidth;
const columns = this.config.columns || 4;
const gap = this.config.gap || 16;
const columnHeights = new Array(columns).fill(0);
const items = Array.from(this.container.children);
items.forEach((element) => {
const size = element.getAttribute("data-size") || "1x1";
const { cols, rows } = SizeCalculator.parseSize(size);
const shortestColumn = columnHeights.indexOf(Math.min(...columnHeights));
const itemWidth = (containerWidth - gap * (columns - 1)) / columns;
const itemHeight = itemWidth * rows;
element.style.gridColumn = `span ${cols}`;
element.style.gridRow = `span ${rows}`;
columnHeights[shortestColumn] += itemHeight + gap;
});
this.layout = {
columns,
gap,
padding: this.config.padding || 20,
containerWidth,
containerHeight: Math.max(...columnHeights),
itemCount: items.length,
columnHeights
};
this.dispatchCustomEvent("layout:updated", { layout: this.layout });
}
/**
* Morph item
*/
morphItem(itemId, newSize, duration) {
this.morphingManager.morphItem(itemId, newSize, duration || this.config.morphDuration || 500);
}
/**
* Reset item to original size
*/
resetItem(itemId) {
this.morphingManager.resetItem(itemId);
}
/**
* Get item by ID
*/
getItem(itemId) {
return this.items.get(itemId);
}
/**
* Get all items
*/
getItems() {
return Array.from(this.items.values());
}
/**
* Get layout information
*/
getLayoutInfo() {
return this.layout;
}
/**
* Update configuration
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.setupComponent();
this.applyTheme();
this.refresh();
}
/**
* Throttle function
*/
throttle(func, delay) {
let timeout = null;
return (...args) => {
if (timeout)
return;
timeout = window.setTimeout(() => {
func.apply(this, args);
timeout = null;
}, delay);
};
}
/**
* Dispatch custom event
*/
dispatchCustomEvent(eventName, detail) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
cancelable: true
});
this.dispatchEvent(event);
}
/**
* Cleanup resources
*/
cleanup() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
this.animationEngine.stop();
this.morphingManager.stop();
this.interactionHandler.clearAllTimers();
}
/**
* Destroy component
*/
destroy() {
this.cleanup();
this.items.clear();
if (this.container) {
this.container.innerHTML = "";
}
}
}
customElements.define("hy-masonry", HyMasonry);
const hyMasonry = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
HyMasonry
}, Symbol.toStringTag, { value: "Module" }));
if (typeof window !== "undefined" && typeof customElements !== "undefined") {
Promise.resolve().then(() => hyMasonry);
}
export {
AnimationEngine,
HyMasonry,
InteractionHandler,
MorphingManager,
SizeCalculator,
HyMasonry as default
};
//# sourceMappingURL=hy-masonry.esm.js.map