move.gl
Version:
Motion and Animation Library for Stylescape.
1,135 lines (1,129 loc) • 37.3 kB
JavaScript
// src/ts/Draggable.ts
var Draggable = class {
/**
* Creates a new Draggable instance.
* @param elementId - The ID of the HTML element to make draggable.
* @throws Error if element or parent element is not found.
*/
constructor(elementId) {
this.isDragging = false;
this.startX = 0;
this.startY = 0;
/**
* Initiates the drag operation.
*/
this.startDrag = (event) => {
const coords = this.getClientCoordinates(event);
this.isDragging = true;
this.startX = coords.clientX - this.element.offsetLeft;
this.startY = coords.clientY - this.element.offsetTop;
event.preventDefault();
};
/**
* Handles the drag movement.
*/
this.drag = (event) => {
if (!this.isDragging) return;
const coords = this.getClientCoordinates(event);
let x = coords.clientX - this.startX;
let y = coords.clientY - this.startY;
x = Math.max(this.boundRect.left, Math.min(x, this.boundRect.right - this.element.offsetWidth));
y = Math.max(this.boundRect.top, Math.min(y, this.boundRect.bottom - this.element.offsetHeight));
this.element.style.left = `${x}px`;
this.element.style.top = `${y}px`;
};
/**
* Stops the drag operation.
*/
this.stopDrag = () => {
this.isDragging = false;
};
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with id "${elementId}" not found`);
}
this.element = element;
const parent = this.element.parentElement;
if (!parent) {
throw new Error("Draggable element must have a parent element");
}
this.boundRect = parent.getBoundingClientRect();
this.attachEventListeners();
}
/**
* Attaches all necessary event listeners for drag functionality.
*/
attachEventListeners() {
this.element.addEventListener("mousedown", this.startDrag);
this.element.addEventListener("touchstart", this.startDrag, { passive: false });
document.addEventListener("mouseup", this.stopDrag);
document.addEventListener("touchend", this.stopDrag);
document.addEventListener("mousemove", this.drag);
document.addEventListener("touchmove", this.drag, { passive: false });
}
/**
* Gets the client coordinates from a mouse or touch event.
*/
getClientCoordinates(event) {
if ("touches" in event && event.touches.length > 0) {
return {
clientX: event.touches[0].clientX,
clientY: event.touches[0].clientY
};
}
return {
clientX: event.clientX,
clientY: event.clientY
};
}
/**
* Removes all event listeners and cleans up.
*/
destroy() {
this.element.removeEventListener("mousedown", this.startDrag);
this.element.removeEventListener("touchstart", this.startDrag);
document.removeEventListener("mouseup", this.stopDrag);
document.removeEventListener("touchend", this.stopDrag);
document.removeEventListener("mousemove", this.drag);
document.removeEventListener("touchmove", this.drag);
}
};
// src/ts/Screensaver.ts
var Screensaver = class {
/**
* Creates a new Screensaver instance.
* @param options - Configuration options for the screensaver.
*/
constructor(options) {
this.screensaverElement = null;
this.videoElement = null;
this.audioElement = null;
this.isActive = false;
/**
* @notice Resets the screensaver timer and stops the screensaver if
* active.
* @dev Called upon user interactions detected by event listeners.
*/
this.resetScreensaver = () => {
if (this.isActive) {
this.stopScreensaver();
}
this.startScreensaverTimeout();
};
/**
* Activates the screensaver, displaying elements and playing media.
*/
this.activateScreensaver = () => {
if (this.screensaverElement) {
this.screensaverElement.style.display = "block";
}
this.videoElement?.play();
this.audioElement?.play();
this.isActive = true;
};
this.options = {
containerId: "screensaver",
videoId: "screensaverVideo",
audioId: "screensaverAudio",
...options
};
this.timeout = options.timeout;
this.initializeElements();
if (options.videoUrl && options.audioUrl) {
this.loadMedia(options.videoUrl, options.audioUrl);
}
this.setupEventListeners();
this.startScreensaverTimeout();
}
/**
* Initializes HTML elements from the DOM.
*/
initializeElements() {
this.screensaverElement = document.getElementById(this.options.containerId);
this.videoElement = document.getElementById(this.options.videoId);
this.audioElement = document.getElementById(this.options.audioId);
}
/**
* Loads media sources into the video and audio elements.
* @param videoUrl - The source URL of the video.
* @param audioUrl - The source URL of the audio.
*/
loadMedia(videoUrl, audioUrl) {
if (this.videoElement) {
this.videoElement.src = videoUrl;
}
if (this.audioElement) {
this.audioElement.src = audioUrl;
}
}
/**
* @notice Sets up event listeners for user interaction to prevent
* screensaver activation.
* @dev Listens for 'mousemove', 'keydown', and 'touchstart' events
* to reset the screensaver timer.
*/
setupEventListeners() {
["mousemove", "keydown", "touchstart"].forEach((event) => {
document.addEventListener(event, this.resetScreensaver);
});
}
/**
* @notice Starts or restarts the screensaver timeout.
* @dev Resets any existing timeout and sets a new timeout to activate
* the screensaver.
*/
startScreensaverTimeout() {
this.stopScreensaver();
this.timeoutId = window.setTimeout(
() => this.activateScreensaver(),
this.timeout
);
}
/**
* Stops the screensaver and hides its elements.
*/
stopScreensaver() {
if (this.screensaverElement) {
this.screensaverElement.style.display = "none";
}
this.videoElement?.pause();
this.audioElement?.pause();
this.isActive = false;
if (this.timeoutId !== void 0) {
clearTimeout(this.timeoutId);
this.timeoutId = void 0;
}
}
/**
* Sets the volume for both video and audio elements.
* @param volume - A number between 0.0 and 1.0 indicating the volume level.
*/
setVolume(volume) {
const clampedVolume = Math.max(0, Math.min(1, volume));
if (this.videoElement) {
this.videoElement.volume = clampedVolume;
}
if (this.audioElement) {
this.audioElement.volume = clampedVolume;
}
}
/**
* Returns whether the screensaver is currently active.
*/
getIsActive() {
return this.isActive;
}
/**
* Cleans up event listeners and stops the screensaver.
*/
destroy() {
this.stopScreensaver();
["mousemove", "keydown", "touchstart"].forEach((event) => {
document.removeEventListener(event, this.resetScreensaver);
});
}
};
// src/ts/Keyboard.ts
var VirtualKeyboard = class {
/**
* @notice Initializes the virtual keyboard with specific input and
* keyboard element IDs.
* @param inputId The ID of the HTML input element to which the keyboard
* will be linked.
* @param keyboardId The ID of the container element where the keyboard
* will be rendered.
*/
constructor(inputId, keyboardId) {
this.keys = {
"default": [
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"],
["a", "s", "d", "f", "g", "h", "j", "k", "l"],
["z", "x", "c", "v", "b", "n", "m", "Backspace"]
],
"shift": [
["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"],
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
["A", "S", "D", "F", "G", "H", "J", "K", "L"],
["Z", "X", "C", "V", "B", "N", "M", "Backspace"]
],
"special": [
["[", "]", "{", "}", "#", "%", "^", "*", "+", "="],
["_", "\\", "|", "~", "<", ">", "\u20AC", "\xA3", "\xA5"],
[".", ",", "?", "!", "'", '"', ":", ";", "Backspace"]
]
};
this.currentMode = "default";
/**
* @notice Handles physical keyboard events and maps them to virtual key
* presses.
* @param event The keyboard event captured from the user"s physical
* keyboard.
*/
this.handlePhysicalKeyPress = (event) => {
const key = event.key;
if (key === "Shift" || key === "CapsLock") {
this.toggleShift();
event.preventDefault();
} else if (key === "Enter" || key === "Tab") {
} else {
this.handleKeyPress(key);
}
};
/**
* @notice Handles touch events on the keyboard element.
* @param event The touch event on the virtual keyboard.
*/
this.handleTouchStart = (event) => {
event.preventDefault();
const keyElement = event.target;
if (keyElement.classList.contains("key")) {
this.handleKeyPress(keyElement.textContent || "");
}
};
this.inputElement = document.getElementById(
inputId
);
this.keyboardElement = document.getElementById(
keyboardId
);
this.renderKeyboard();
this.attachEventListeners();
}
/**
* @notice Renders the keyboard based on the current mode (default, shift,
* or special).
* @dev Dynamically creates HTML for keyboard keys and appends them to the
* keyboardElement.
*/
renderKeyboard() {
this.keyboardElement.innerHTML = "";
this.keys[this.currentMode].forEach((row) => {
const rowElement = document.createElement("div");
rowElement.className = "keyboard__row";
row.forEach((key) => {
const keyElement = document.createElement("div");
keyElement.textContent = key;
keyElement.className = "key";
keyElement.addEventListener(
"click",
() => this.handleKeyPress(key)
);
rowElement.appendChild(keyElement);
});
this.keyboardElement.appendChild(rowElement);
});
}
/**
* @notice Handles key presses on the virtual keyboard.
* @param key The key character or function (like "Backspace") that was
* pressed.
*/
handleKeyPress(key) {
if (key === "Backspace") {
this.inputElement.value = this.inputElement.value.slice(0, -1);
} else if (key === "Shift" || key === "CapsLock") {
this.toggleShift();
} else {
this.inputElement.value += key;
}
}
/**
* @notice Toggles the keyboard between "default" and "shift" modes.
* @dev This method is called when the "Shift" or "CapsLock" key is pressed.
*/
toggleShift() {
this.currentMode = this.currentMode === "default" ? "shift" : "default";
this.renderKeyboard();
}
/**
* @notice Attaches necessary event listeners to handle both physical
* keyboard and touch inputs.
*/
attachEventListeners() {
document.addEventListener("keydown", this.handlePhysicalKeyPress);
this.keyboardElement.addEventListener(
"touchstart",
this.handleTouchStart,
false
);
}
/**
* @notice Switches the keyboard layout to a specified mode.
* @param mode The mode to which the keyboard layout should switch
* ("default", "shift", or "special").
*/
switchMode(mode) {
if (this.keys[mode]) {
this.currentMode = mode;
this.renderKeyboard();
}
}
/**
* Removes all event listeners and cleans up.
*/
destroy() {
document.removeEventListener("keydown", this.handlePhysicalKeyPress);
this.keyboardElement.removeEventListener("touchstart", this.handleTouchStart);
this.keyboardElement.innerHTML = "";
}
};
// src/ts/Gesture.ts
var TouchGestureHandler = class {
/**
* Creates a new TouchGestureHandler instance.
* @param elementId - The ID of the element to attach gesture handling to.
* @param callbacks - Optional callback functions for gesture events.
*/
constructor(elementId, callbacks = {}) {
this.startTouches = null;
this.lastTouches = null;
this.isSwiping = false;
this.isPinching = false;
this.handleTouchStart = (event) => {
if (event.touches.length === 1) {
this.startTouches = Array.from(event.touches);
} else if (event.touches.length > 1) {
this.startTouches = Array.from(event.touches);
this.isPinching = true;
}
};
this.handleTouchMove = (event) => {
if (!this.startTouches) return;
this.lastTouches = Array.from(event.touches);
if (event.touches.length === 1 && !this.isPinching) {
const dx = event.touches[0].clientX - this.startTouches[0].clientX;
const dy = event.touches[0].clientY - this.startTouches[0].clientY;
if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
this.isSwiping = true;
}
} else if (event.touches.length > 1 && this.isPinching && this.startTouches.length > 1) {
const startDistance = this.getDistance(this.startTouches[0], this.startTouches[1]);
const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
const scale = currentDistance / startDistance;
this.callbacks.onPinch?.(scale);
}
};
this.handleTouchEnd = () => {
if (this.isSwiping && this.startTouches && this.lastTouches) {
const dx = this.lastTouches[0].clientX - this.startTouches[0].clientX;
const dy = this.lastTouches[0].clientY - this.startTouches[0].clientY;
const direction = this.getSwipeDirection(dx, dy);
this.callbacks.onSwipe?.(direction, dx, dy);
this.isSwiping = false;
} else if (this.isPinching) {
this.isPinching = false;
} else {
this.callbacks.onTap?.();
}
this.startTouches = null;
this.lastTouches = null;
};
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with id "${elementId}" not found`);
}
this.element = element;
this.callbacks = callbacks;
this.addTouchListeners();
}
addTouchListeners() {
this.element.addEventListener("touchstart", this.handleTouchStart, false);
this.element.addEventListener("touchmove", this.handleTouchMove, false);
this.element.addEventListener("touchend", this.handleTouchEnd, false);
}
/**
* Determines swipe direction based on deltas.
*/
getSwipeDirection(dx, dy) {
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? "right" : "left";
}
return dy > 0 ? "down" : "up";
}
/**
* Calculates the distance between two touch points.
*/
getDistance(touch1, touch2) {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Removes all event listeners and cleans up.
*/
destroy() {
this.element.removeEventListener("touchstart", this.handleTouchStart);
this.element.removeEventListener("touchmove", this.handleTouchMove);
this.element.removeEventListener("touchend", this.handleTouchEnd);
}
};
var AdvancedGestureRecognition = class {
/**
* Creates a new AdvancedGestureRecognition instance.
* @param elementId - The ID of the element to attach gesture handling to.
* @param callbacks - Optional callback functions for gesture events.
*/
constructor(elementId, callbacks = {}) {
this.ongoingTouches = /* @__PURE__ */ new Map();
this.handleGestureStart = (event) => {
this.ongoingTouches.set(event.pointerId, event);
this.callbacks.onGestureStart?.(event);
};
this.handleGestureMove = (event) => {
if (this.ongoingTouches.has(event.pointerId)) {
const startEvent = this.ongoingTouches.get(event.pointerId);
const dx = event.clientX - startEvent.clientX;
const dy = event.clientY - startEvent.clientY;
this.callbacks.onGestureMove?.(dx, dy, event);
}
};
this.handleGestureEnd = (event) => {
this.ongoingTouches.delete(event.pointerId);
this.callbacks.onGestureEnd?.(event);
};
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with id "${elementId}" not found`);
}
this.element = element;
this.callbacks = callbacks;
this.attachEventListeners();
}
attachEventListeners() {
this.element.addEventListener("pointerdown", this.handleGestureStart, { passive: false });
this.element.addEventListener("pointermove", this.handleGestureMove, { passive: false });
this.element.addEventListener("pointerup", this.handleGestureEnd, { passive: false });
this.element.addEventListener("pointercancel", this.handleGestureEnd, { passive: false });
}
/**
* Removes all event listeners and cleans up.
*/
destroy() {
this.element.removeEventListener("pointerdown", this.handleGestureStart);
this.element.removeEventListener("pointermove", this.handleGestureMove);
this.element.removeEventListener("pointerup", this.handleGestureEnd);
this.element.removeEventListener("pointercancel", this.handleGestureEnd);
}
};
// src/ts/VideoOverlay.ts
var TransparentVideoOverlay = class {
/**
* Creates a new TransparentVideoOverlay instance.
* @param videoElementId - The ID of the video element to manage.
* @param options - Optional configuration options.
*/
constructor(videoElementId, options = {}) {
this.videoElement = null;
this.isVisible = false;
const element = document.getElementById(videoElementId);
if (element instanceof HTMLVideoElement) {
this.videoElement = element;
} else {
console.warn(`Element with id "${videoElementId}" is not a video element`);
}
this.fadeTransitionDuration = options.fadeTransitionDuration ?? 500;
this.loop = options.loop ?? true;
if (this.videoElement) {
this.setupVideo();
if (options.initialSource) {
this.changeVideoSource(options.initialSource, false);
}
}
}
/**
* Initializes video settings and event listeners.
*/
setupVideo() {
if (!this.videoElement) return;
if (this.loop) {
this.videoElement.addEventListener("ended", () => {
this.videoElement?.play();
});
}
this.videoElement.addEventListener("loadeddata", () => {
console.log("Video loaded successfully.");
});
this.videoElement.addEventListener("error", (e) => {
console.error("Error loading video:", e);
});
this.videoElement.style.transition = `opacity ${this.fadeTransitionDuration}ms ease`;
}
/**
* Shows the video overlay with a fade-in effect.
*/
showOverlay() {
if (!this.videoElement) return;
this.videoElement.style.display = "block";
this.videoElement.style.opacity = "0";
requestAnimationFrame(() => {
if (this.videoElement) {
this.videoElement.style.opacity = "1";
this.videoElement.play().catch((err) => {
console.warn("Auto-play prevented:", err);
});
}
});
this.isVisible = true;
}
/**
* Hides the video overlay with a fade-out effect.
*/
hideOverlay() {
if (!this.videoElement) return;
this.videoElement.style.opacity = "0";
setTimeout(() => {
if (this.videoElement) {
this.videoElement.style.display = "none";
this.videoElement.pause();
}
}, this.fadeTransitionDuration);
this.isVisible = false;
}
/**
* Toggles the visibility of the video overlay.
*/
toggleOverlay() {
if (this.isVisible) {
this.hideOverlay();
} else {
this.showOverlay();
}
}
/**
* Changes the video source and optionally plays it immediately.
* @param videoUrl - The URL of the new video source.
* @param autoPlay - Whether the video should play immediately after loading.
*/
changeVideoSource(videoUrl, autoPlay = true) {
if (!this.videoElement) return;
this.videoElement.src = videoUrl;
this.videoElement.load();
if (autoPlay) {
this.showOverlay();
}
}
/**
* Gets the visibility state of the overlay.
*/
getIsVisible() {
return this.isVisible;
}
/**
* Cleans up the video overlay instance.
*/
destroy() {
if (this.videoElement) {
this.videoElement.pause();
this.videoElement.src = "";
this.videoElement = null;
}
}
};
function supportsHEVCAlpha() {
const navigator = window.navigator;
const ua = navigator.userAgent.toLowerCase();
const hasMediaCapabilities = !!(navigator.mediaCapabilities && navigator.mediaCapabilities.decodingInfo);
const isSafari = ua.indexOf("safari") !== -1 && ua.indexOf("chrome") === -1 && ua.indexOf("version/") !== -1;
return isSafari && hasMediaCapabilities;
}
function getOptimalVideoSource(hevcSource, webmSource) {
return supportsHEVCAlpha() ? hevcSource : webmSource;
}
// src/ts/LoaderManager.ts
var LoaderManager = class {
// ========================================================================
// Constructor
// ========================================================================
/**
* Creates a new LoaderManager instance.
* @param preloadBuiltins - Whether to preload built-in loaders (default: true)
*/
constructor(preloadBuiltins = true) {
// ========================================================================
// Properties
// ========================================================================
/** Registry of loader configurations */
this.loaders = /* @__PURE__ */ new Map();
/** Active loader instances */
this.activeLoaders = /* @__PURE__ */ new Map();
/** Default CSS variables for customization */
this.defaultVars = {
"--loader-size": "48px",
"--loader-color": "#FFF",
"--loader-accent": "#FF3D00",
"--loader-speed": "1s"
};
if (preloadBuiltins) {
this.registerBuiltinLoaders();
}
}
// ========================================================================
// Public Methods
// ========================================================================
/**
* Registers a loader configuration.
* @param config - The loader configuration to register
* @returns The LoaderManager instance for chaining
*/
register(config) {
this.loaders.set(config.id, config);
return this;
}
/**
* Registers multiple loader configurations.
* @param configs - Array of loader configurations
* @returns The LoaderManager instance for chaining
*/
registerAll(configs) {
configs.forEach((config) => this.register(config));
return this;
}
/**
* Creates and mounts a loader element.
* @param loaderId - ID of the registered loader to create
* @param options - Creation options
* @returns The created loader element
*/
create(loaderId, options = {}) {
const config = this.loaders.get(loaderId);
if (!config) {
throw new Error(`Loader "${loaderId}" not found. Register it first.`);
}
const {
container,
useShadowDOM = true,
className = "",
size,
color,
accentColor
} = options;
const wrapper = document.createElement("div");
wrapper.className = `loader-wrapper ${className}`.trim();
wrapper.setAttribute("data-loader-id", loaderId);
if (size) {
wrapper.style.setProperty("--loader-size", typeof size === "number" ? `${size}px` : size);
}
if (color) {
wrapper.style.setProperty("--loader-color", color);
}
if (accentColor) {
wrapper.style.setProperty("--loader-accent", accentColor);
}
if (useShadowDOM) {
const shadowRoot = wrapper.attachShadow({ mode: "open" });
const styleEl = document.createElement("style");
styleEl.textContent = this.processCSS(config.css, options);
shadowRoot.appendChild(styleEl);
const loaderEl = document.createElement("span");
loaderEl.className = "loader";
if (config.content) {
loaderEl.innerHTML = config.content;
}
shadowRoot.appendChild(loaderEl);
this.activeLoaders.set(wrapper, { id: loaderId, shadowRoot });
} else {
const styleEl = document.createElement("style");
styleEl.textContent = this.scopeCSS(config.css, wrapper, loaderId);
wrapper.appendChild(styleEl);
const loaderEl = document.createElement("span");
loaderEl.className = `loader loader-${loaderId}`;
if (config.content) {
loaderEl.innerHTML = config.content;
}
wrapper.appendChild(loaderEl);
this.activeLoaders.set(wrapper, { id: loaderId });
}
if (container) {
const containerEl = typeof container === "string" ? document.querySelector(container) : container;
containerEl?.appendChild(wrapper);
}
return wrapper;
}
/**
* Creates a full-screen overlay loader.
* @param loaderId - ID of the registered loader
* @param options - Creation options
* @returns The created overlay element
*/
createOverlay(loaderId, options = {}) {
const overlay = document.createElement("div");
overlay.className = "loader-overlay";
overlay.style.cssText = `
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
`;
const loader = this.create(loaderId, { ...options, container: overlay });
document.body.appendChild(overlay);
this.activeLoaders.set(overlay, { id: `overlay-${loaderId}` });
return overlay;
}
/**
* Shows a loader in an existing element, replacing its content.
* @param loaderId - ID of the registered loader
* @param target - Target element or selector
* @param options - Creation options
* @returns The created loader element
*/
showIn(loaderId, target, options = {}) {
const targetEl = typeof target === "string" ? document.querySelector(target) : target;
if (!targetEl) {
throw new Error(`Target element not found: ${target}`);
}
const originalContent = targetEl.innerHTML;
targetEl.setAttribute("data-original-content", originalContent);
targetEl.innerHTML = "";
const loader = this.create(loaderId, { ...options, container: targetEl });
return loader;
}
/**
* Hides a loader and restores original content.
* @param target - Target element or selector
*/
hideIn(target) {
const targetEl = typeof target === "string" ? document.querySelector(target) : target;
if (!targetEl) return;
const originalContent = targetEl.getAttribute("data-original-content");
if (originalContent !== null) {
targetEl.innerHTML = originalContent;
targetEl.removeAttribute("data-original-content");
}
}
/**
* Destroys a loader element.
* @param loader - The loader element to destroy
*/
destroy(loader) {
this.activeLoaders.delete(loader);
loader.remove();
}
/**
* Destroys all active loaders.
*/
destroyAll() {
this.activeLoaders.forEach((_, loader) => this.destroy(loader));
}
/**
* Gets a list of all registered loader IDs.
* @returns Array of loader IDs
*/
getRegisteredLoaders() {
return Array.from(this.loaders.keys());
}
/**
* Checks if a loader is registered.
* @param loaderId - Loader ID to check
* @returns True if registered
*/
has(loaderId) {
return this.loaders.has(loaderId);
}
/**
* Gets the configuration for a registered loader.
* @param loaderId - Loader ID
* @returns The loader configuration or undefined
*/
getConfig(loaderId) {
return this.loaders.get(loaderId);
}
// ========================================================================
// Private Methods
// ========================================================================
/**
* Processes CSS with variable replacements.
*/
processCSS(css, options) {
let processed = css;
processed = processed.replace(/#FFF\b/gi, "var(--loader-color, #FFF)");
processed = processed.replace(/#FF3D00\b/gi, "var(--loader-accent, #FF3D00)");
processed = processed.replace(/48px/g, "var(--loader-size, 48px)");
return processed;
}
/**
* Scopes CSS to a specific element (for non-Shadow DOM usage).
*/
scopeCSS(css, wrapper, loaderId) {
return css.replace(/\.loader/g, `.loader-${loaderId}`);
}
/**
* Registers built-in loader presets.
*/
registerBuiltinLoaders() {
this.register({
id: "spinner",
css: `.loader {
width: 48px;
height: 48px;
border: 5px solid #FFF;
border-bottom-color: #FF3D00;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}`
});
this.register({
id: "spinner-dual",
css: `.loader {
width: 48px;
height: 48px;
border: 5px solid #FFF;
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
rotation {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}`
});
this.register({
id: "dots-bounce",
css: `.loader, .loader:before, .loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
animation-fill-mode: both;
animation: bblFadInOut 1.8s infinite ease-in-out;
}
.loader {
color: #FFF;
font-size: 7px;
position: relative;
text-indent: -9999em;
transform: translateZ(0);
animation-delay: -0.16s;
}
.loader:before, .loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
bblFadInOut {
0%, 80%, 100% { box-shadow: 0 2.5em 0 -1.3em }
40% { box-shadow: 0 2.5em 0 0 }
}`
});
this.register({
id: "dots-flash",
css: `.loader {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #fff;
box-shadow: 32px 0 #fff, -32px 0 #fff;
position: relative;
animation: flash 0.5s ease-out infinite alternate;
}
flash {
0% {
background-color: #FFF2;
box-shadow: 32px 0 #FFF2, -32px 0 #FFF;
}
50% {
background-color: #FFF;
box-shadow: 32px 0 #FFF2, -32px 0 #FFF2;
}
100% {
background-color: #FFF2;
box-shadow: 32px 0 #FFF, -32px 0 #FFF2;
}
}`
});
this.register({
id: "progress-bar",
css: `.loader {
width: 100%;
height: 4.8px;
display: inline-block;
position: relative;
background: rgba(255, 255, 255, 0.15);
overflow: hidden;
}
.loader::after {
content: '';
width: 96px;
height: 4.8px;
background: #FFF;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
animation: hitZak 1s linear infinite alternate;
}
hitZak {
0% { left: 0; transform: translateX(-1%); }
100% { left: 100%; transform: translateX(-99%); }
}`
});
this.register({
id: "progress-fill",
css: `.loader {
width: 100%;
height: 4.8px;
display: inline-block;
position: relative;
background: rgba(255, 255, 255, 0.15);
overflow: hidden;
}
.loader::after {
content: '';
box-sizing: border-box;
width: 0;
height: 4.8px;
background: #FFF;
position: absolute;
top: 0;
left: 0;
animation: animFw 10s linear infinite;
}
animFw {
0% { width: 0; }
100% { width: 100%; }
}`
});
this.register({
id: "pulse",
css: `.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
position: relative;
background: #FFF;
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5);
animation: pulse 1.5s ease-out infinite;
}
pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5); }
100% { box-shadow: 0 0 0 30px rgba(255, 255, 255, 0); }
}`
});
this.register({
id: "square-flip",
css: `.loader {
width: 48px;
height: 48px;
display: inline-block;
position: relative;
background: #FFF;
box-sizing: border-box;
animation: flipX 1s linear infinite;
}
flipX {
0% { transform: perspective(200px) rotateX(0deg) rotateY(0deg); }
50% { transform: perspective(200px) rotateX(-180deg) rotateY(0deg); }
100% { transform: perspective(200px) rotateX(-180deg) rotateY(-180deg); }
}`
});
this.register({
id: "skeleton-card",
css: `.loader {
width: 320px;
height: 150px;
display: block;
margin: auto;
position: relative;
background: #FFF;
box-sizing: border-box;
}
.loader::after {
content: '';
width: calc(100% - 30px);
height: calc(100% - 30px);
top: 15px;
left: 15px;
position: absolute;
background-image:
linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.5) 50%, transparent 80%),
linear-gradient(#DDD 56px, transparent 0),
linear-gradient(#DDD 24px, transparent 0),
linear-gradient(#DDD 18px, transparent 0),
linear-gradient(#DDD 66px, transparent 0);
background-repeat: no-repeat;
background-size: 75px 130px, 55px 56px, 160px 30px, 260px 20px, 290px 56px;
background-position: 0% 0, 0 0, 70px 5px, 70px 38px, 0px 66px;
box-sizing: border-box;
animation: animloader 1s linear infinite;
}
animloader {
0% { background-position: 0% 0, 0 0, 70px 5px, 70px 38px, 0px 66px; }
100% { background-position: 150% 0, 0 0, 70px 5px, 70px 38px, 0px 66px; }
}`
});
this.register({
id: "bars-wave",
css: `.loader {
color: #FFF;
position: relative;
font-size: 11px;
background: #FFF;
animation: escaleY 1s infinite ease-in-out;
width: 1em;
height: 4em;
animation-delay: -0.16s;
}
.loader:before, .loader:after {
content: '';
position: absolute;
top: 0;
left: 2em;
background: #FFF;
width: 1em;
height: 4em;
animation: escaleY 1s infinite ease-in-out;
}
.loader:before {
left: -2em;
animation-delay: -0.32s;
}
escaleY {
0%, 80%, 100% { box-shadow: 0 0; height: 4em; }
40% { box-shadow: 0 -2em; height: 5em; }
}`
});
}
};
var loaderManager = new LoaderManager();
// src/ts/index.ts
var ts_default = {
Draggable,
LoaderManager,
Screensaver,
VirtualKeyboard,
TouchGestureHandler,
AdvancedGestureRecognition,
TransparentVideoOverlay
};
export {
AdvancedGestureRecognition,
Draggable,
LoaderManager,
Screensaver,
TouchGestureHandler,
TransparentVideoOverlay,
VirtualKeyboard,
ts_default as default,
getOptimalVideoSource,
loaderManager,
supportsHEVCAlpha
};
//# sourceMappingURL=index.mjs.map