Comprehensive set of utility functions for type checking, event handling, DOM manipulation, and mathematical operations. These utilities are re-exported from the @cropper/utils package and provide essential functionality for building cropping applications.
Functions for runtime type validation and checking.
/**
* Check if a value is a string
* @param value - Value to check
* @returns True if value is a string
*/
function isString(value: unknown): value is string;
/**
* Check if a value is a number
* @param value - Value to check
* @returns True if value is a number (not NaN)
*/
function isNumber(value: unknown): value is number;
/**
* Check if a value is a positive number
* @param value - Value to check
* @returns True if value is a positive number
*/
function isPositiveNumber(value: unknown): value is number;
/**
* Check if a value is undefined
* @param value - Value to check
* @returns True if value is undefined
*/
function isUndefined(value: unknown): value is undefined;
/**
* Check if a value is an object
* @param value - Value to check
* @returns True if value is an object (not null, array, or function)
*/
function isObject(value: unknown): value is object;
/**
* Check if a value is a plain object
* @param value - Value to check
* @returns True if value is a plain object (created by {} or new Object())
*/
function isPlainObject(value: unknown): value is object;
/**
* Check if a value is a function
* @param value - Value to check
* @returns True if value is a function
*/
function isFunction(value: unknown): value is Function;
/**
* Check if a node is a DOM element
* @param node - Node to check
* @returns True if node is an Element
*/
function isElement(node: unknown): node is Element;
/**
* Check if a value is NaN
* @param value - Value to check
* @returns True if value is NaN
*/
function isNaN(value: unknown): boolean;Usage Examples:
import { isString, isNumber, isElement, isFunction } from 'cropperjs';
// Type checking
if (isString(value)) {
console.log('String length:', value.length);
}
if (isNumber(scale) && scale > 0) {
image.$zoom(scale);
}
if (isElement(container)) {
new Cropper(imageElement, { container });
}
if (isFunction(callback)) {
callback(result);
}
// Validation examples
function validateCropperOptions(options) {
if (!isObject(options)) {
throw new Error('Options must be an object');
}
if (options.container && !isElement(options.container) && !isString(options.container)) {
throw new Error('Container must be an element or selector string');
}
}Functions for converting between different string formats.
/**
* Convert camelCase to kebab-case
* @param value - CamelCase string to convert
* @returns Kebab-case string
*/
function toKebabCase(value: string): string;
/**
* Convert kebab-case to camelCase
* @param value - Kebab-case string to convert
* @returns CamelCase string
*/
function toCamelCase(value: string): string;Usage Examples:
import { toKebabCase, toCamelCase } from 'cropperjs';
// Convert CSS property names
const cssProperty = toKebabCase('backgroundColor'); // 'background-color'
const jsProperty = toCamelCase('font-size'); // 'fontSize'
// Convert HTML attributes
const htmlAttribute = toKebabCase('contentEditable'); // 'content-editable'
const domProperty = toCamelCase('data-theme-color'); // 'dataThemeColor'
// Common conversions
toKebabCase('themeColor'); // 'theme-color'
toKebabCase('initialCoverage'); // 'initial-coverage'
toCamelCase('cropper-canvas'); // 'cropperCanvas'
toCamelCase('scale-step'); // 'scaleStep'Comprehensive event management utilities for DOM interactions.
/**
* Add event listener to target
* @param target - Event target (element, document, window, etc.)
* @param types - Event type(s) (space-separated for multiple)
* @param listener - Event listener function or object
* @param options - Event listener options
*/
function on(
target: EventTarget,
types: string,
listener: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions
): void;
/**
* Remove event listener from target
* @param target - Event target
* @param types - Event type(s) (space-separated for multiple)
* @param listener - Event listener function or object
* @param options - Event listener options
*/
function off(
target: EventTarget,
types: string,
listener: EventListenerOrEventListenerObject,
options?: EventListenerOptions
): void;
/**
* Add one-time event listener
* @param target - Event target
* @param types - Event type(s) (space-separated for multiple)
* @param listener - Event listener function or object
* @param options - Event listener options
*/
function once(
target: EventTarget,
types: string,
listener: EventListenerOrEventListenerObject,
options?: AddEventListenerOptions
): void;
/**
* Dispatch custom event on target
* @param target - Event target
* @param type - Event type
* @param detail - Event detail data
* @param options - Event options
* @returns True if event was not cancelled
*/
function emit(
target: EventTarget,
type: string,
detail?: unknown,
options?: EventInit
): boolean;Usage Examples:
import { on, off, once, emit } from 'cropperjs';
const canvas = document.querySelector('cropper-canvas');
const image = document.querySelector('cropper-image');
// Add single event listener
on(canvas, 'action', (event) => {
console.log('Action:', event.detail.action);
});
// Add multiple event listeners
on(image, 'load error', (event) => {
console.log('Image event:', event.type);
});
// Add with options
on(canvas, 'wheel', handleWheel, { passive: false });
// One-time listener
once(image, 'ready', () => {
console.log('Image is ready');
});
// Remove listener
const handleClick = (event) => console.log('Clicked');
on(canvas, 'click', handleClick);
off(canvas, 'click', handleClick);
// Emit custom events
emit(canvas, 'customaction', {
action: 'crop',
selection: { x: 50, y: 30, width: 200, height: 150 }
});
// Event delegation example
on(document, 'action', (event) => {
if (event.target.matches('cropper-selection')) {
console.log('Selection action:', event.detail);
}
});Mathematical functions for geometry calculations and matrix transformations.
/**
* Get element offset relative to document
* @param element - Element to get offset for
* @returns Object with left and top offset values
*/
function getOffset(element: Element): { left: number; top: number };
/**
* Convert angle to radians
* @param angle - Angle value (number in degrees, or string with unit)
* @returns Angle in radians
*/
function toAngleInRadian(angle: number | string): number;
/**
* Adjust sizes for aspect ratio constraints
* @param data - Size data object
* @param type - Adjustment type
* @returns Adjusted width and height
*/
function getAdjustedSizes(
data: { width: number; height: number; aspectRatio?: number },
type?: string
): { width: number; height: number };
/**
* Multiply transformation matrices
* @param matrix - Base transformation matrix [a, b, c, d, e, f]
* @param args - Additional matrices to multiply
* @returns Resulting transformation matrix
*/
function multiplyMatrices(matrix: number[], ...args: number[][]): number[];Usage Examples:
import { getOffset, toAngleInRadian, getAdjustedSizes, multiplyMatrices } from 'cropperjs';
// Get element position
const canvas = document.querySelector('cropper-canvas');
const offset = getOffset(canvas);
console.log('Canvas position:', offset.left, offset.top);
// Convert angles
const radians45 = toAngleInRadian(45); // π/4
const radiansFromString = toAngleInRadian('90deg'); // π/2
const radiansFromTurn = toAngleInRadian('0.25turn'); // π/2
// Adjust sizes for aspect ratio
const originalSize = { width: 300, height: 200, aspectRatio: 16/9 };
const adjustedSize = getAdjustedSizes(originalSize);
console.log('Adjusted size:', adjustedSize); // { width: 300, height: 168.75 }
// Matrix multiplication for transformations
const scaleMatrix = [1.5, 0, 0, 1.5, 0, 0]; // Scale by 1.5
const rotateMatrix = [0.707, -0.707, 0.707, 0.707, 0, 0]; // Rotate 45°
const translateMatrix = [1, 0, 0, 1, 50, 30]; // Translate (50, 30)
const combinedMatrix = multiplyMatrices(scaleMatrix, rotateMatrix, translateMatrix);
console.log('Combined transform:', combinedMatrix);
// Practical usage in image transformation
function applyComplexTransform(image, scale, rotation, offsetX, offsetY) {
const scaleMatrix = [scale, 0, 0, scale, 0, 0];
const rotationRadians = toAngleInRadian(rotation);
const cos = Math.cos(rotationRadians);
const sin = Math.sin(rotationRadians);
const rotateMatrix = [cos, -sin, sin, cos, 0, 0];
const translateMatrix = [1, 0, 0, 1, offsetX, offsetY];
const finalMatrix = multiplyMatrices(scaleMatrix, rotateMatrix, translateMatrix);
image.$setTransform(finalMatrix);
}Utilities for DOM manipulation and asynchronous operations.
/**
* Defer execution to next DOM update cycle
* @param context - Optional context object
* @param callback - Optional callback function
* @returns Promise that resolves after next DOM update
*/
function nextTick(context?: unknown, callback?: Function): Promise<void>;
/**
* Get root document for an element
* @param element - Element to get root document for
* @returns Document, DocumentFragment, or null
*/
function getRootDocument(element: Element): Document | DocumentFragment | null;Usage Examples:
import { nextTick, getRootDocument } from 'cropperjs';
// Defer DOM updates
async function updateSelection(selection) {
selection.$change(100, 100, 200, 150);
// Wait for DOM to update before measuring
await nextTick();
const bounds = selection.getBoundingClientRect();
console.log('Updated bounds:', bounds);
}
// With callback style
nextTick(null, () => {
console.log('DOM has been updated');
});
// Get root document for cross-frame compatibility
const canvas = document.querySelector('cropper-canvas');
const rootDoc = getRootDocument(canvas);
if (rootDoc) {
// Use root document for event handling
on(rootDoc, 'keydown', handleKeydown);
}
// Shadow DOM compatibility
function getDocumentFromElement(element) {
const rootDoc = getRootDocument(element);
return rootDoc || document;
}Runtime environment detection for cross-browser compatibility.
/**
* Whether running in browser environment
*/
const IS_BROWSER: boolean;
/**
* Window object reference (safe for server-side rendering)
*/
const WINDOW: any;
/**
* Whether device supports touch events
*/
const IS_TOUCH_DEVICE: boolean;
/**
* Whether browser supports pointer events
*/
const HAS_POINTER_EVENT: boolean;Usage Examples:
import { IS_BROWSER, WINDOW, IS_TOUCH_DEVICE, HAS_POINTER_EVENT } from 'cropperjs';
// Check if running in browser
if (IS_BROWSER) {
const cropper = new Cropper('#image');
} else {
console.log('Server-side rendering detected');
}
// Safe window usage
if (WINDOW) {
WINDOW.addEventListener('resize', handleResize);
}
// Touch vs mouse handling
if (IS_TOUCH_DEVICE) {
canvas.scaleStep = 0.2; // Larger steps for touch
} else {
canvas.scaleStep = 0.1; // Finer control for mouse
}
// Event type selection
const eventTypes = HAS_POINTER_EVENT ?
'pointerdown pointermove pointerup' :
'mousedown mousemove mouseup touchstart touchmove touchend';
on(canvas, eventTypes, handleInteraction);Utility functions may throw errors for invalid inputs:
import { isString, toAngleInRadian, getOffset } from 'cropperjs';
// Type checking with error handling
function validateAndProcess(value) {
if (!isString(value)) {
throw new TypeError('Expected string value');
}
return value.toUpperCase();
}
// Angle conversion with validation
try {
const radians = toAngleInRadian('invalid-angle');
} catch (error) {
console.error('Invalid angle format:', error.message);
}
// Element operations with null checks
const element = document.querySelector('#maybe-missing');
if (element) {
const offset = getOffset(element);
console.log('Offset:', offset);
} else {
console.error('Element not found');
}Frequently used combinations of utility functions:
import {
isElement, isString, isNumber, isFunction,
on, off, emit, nextTick,
getOffset, toAngleInRadian, multiplyMatrices
} from 'cropperjs';
// Input validation pattern
function validateCropperInput(element, options = {}) {
if (isString(element)) {
element = document.querySelector(element);
}
if (!isElement(element)) {
throw new Error('Invalid element provided');
}
if (options.container) {
if (isString(options.container)) {
options.container = document.querySelector(options.container);
}
if (!isElement(options.container)) {
throw new Error('Invalid container provided');
}
}
return { element, options };
}
// Event management pattern
class EventManager {
constructor(target) {
this.target = target;
this.listeners = new Map();
}
add(type, listener, options) {
on(this.target, type, listener, options);
this.listeners.set(type, { listener, options });
}
remove(type) {
const entry = this.listeners.get(type);
if (entry) {
off(this.target, type, entry.listener, entry.options);
this.listeners.delete(type);
}
}
removeAll() {
for (const [type] of this.listeners) {
this.remove(type);
}
}
}
// Transform calculation pattern
function calculateComplexTransform(operations) {
let matrix = [1, 0, 0, 1, 0, 0]; // Identity matrix
for (const op of operations) {
let opMatrix;
switch (op.type) {
case 'scale':
opMatrix = [op.x, 0, 0, op.y || op.x, 0, 0];
break;
case 'rotate':
const angle = toAngleInRadian(op.angle);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
opMatrix = [cos, -sin, sin, cos, 0, 0];
break;
case 'translate':
opMatrix = [1, 0, 0, 1, op.x, op.y];
break;
}
if (opMatrix) {
matrix = multiplyMatrices(matrix, opMatrix);
}
}
return matrix;
}