Generic type manipulation helpers for advanced TypeScript patterns, useful for building type-safe drag and drop interfaces and utility functions.
Extracts function argument types as a tuple. Useful for creating wrapper functions that need to preserve exact parameter types.
/**
* Extracts function argument types as a tuple
* @template T - Function type to extract arguments from
*/
type Arguments<T> = T extends (...args: infer U) => any ? U : never;Usage Examples:
import { type Arguments } from "@dnd-kit/utilities";
// Extract arguments from existing function
function createDragHandler(
element: HTMLElement,
options: { threshold: number; axis?: "x" | "y" },
callback: (event: MouseEvent) => void
) {
// Implementation here
}
// Create a type for the arguments
type DragHandlerArgs = Arguments<typeof createDragHandler>;
// Result: [HTMLElement, { threshold: number; axis?: "x" | "y" }, (event: MouseEvent) => void]
// Use in wrapper function
function createEnhancedDragHandler(...args: DragHandlerArgs) {
console.log("Creating enhanced drag handler with args:", args);
return createDragHandler(...args);
}
// Generic wrapper factory
function createWrapper<T extends (...args: any[]) => any>(
originalFn: T
): (...args: Arguments<T>) => ReturnType<T> {
return (...args: Arguments<T>) => {
console.log("Calling function with args:", args);
return originalFn(...args);
};
}
// Event handler argument extraction
type MouseEventHandler = (event: MouseEvent) => void;
type MouseEventArgs = Arguments<MouseEventHandler>;
// Result: [MouseEvent]
function createEventProxy<T extends (...args: any[]) => any>(
handler: T
): (...args: Arguments<T>) => void {
return (...args: Arguments<T>) => {
// Perform validation, logging, etc.
console.log("Event proxy called with:", args);
handler(...args);
};
}Makes all properties and nested properties required, removing all optional flags recursively through the type hierarchy.
/**
* Makes all properties and nested properties required
* @template T - Type to make deeply required
*/
type DeepRequired<T> = {
[K in keyof T]-?: Required<T[K]>;
};Usage Examples:
import { type DeepRequired } from "@dnd-kit/utilities";
// Original interface with optional properties
interface DragConfig {
element?: HTMLElement;
options?: {
threshold?: number;
axis?: "x" | "y";
constraints?: {
minX?: number;
maxX?: number;
minY?: number;
maxY?: number;
};
};
callbacks?: {
onStart?: (event: MouseEvent) => void;
onMove?: (event: MouseEvent) => void;
onEnd?: (event: MouseEvent) => void;
};
}
// Make all properties required
type RequiredDragConfig = DeepRequired<DragConfig>;
/* Result:
{
element: HTMLElement;
options: {
threshold: number;
axis: "x" | "y";
constraints: {
minX: number;
maxX: number;
minY: number;
maxY: number;
};
};
callbacks: {
onStart: (event: MouseEvent) => void;
onMove: (event: MouseEvent) => void;
onEnd: (event: MouseEvent) => void;
};
}
*/
// Function that requires complete configuration
function initializeDragSystem(config: RequiredDragConfig) {
// All properties are guaranteed to exist
config.element.addEventListener("mousedown", config.callbacks.onStart);
// No need for optional chaining
const threshold = config.options.threshold;
const constraints = config.options.constraints;
console.log("Drag system initialized with complete config");
}
// Default configuration provider
function createDefaultDragConfig(): RequiredDragConfig {
return {
element: document.createElement("div"),
options: {
threshold: 5,
axis: "x",
constraints: {
minX: 0,
maxX: window.innerWidth,
minY: 0,
maxY: window.innerHeight
}
},
callbacks: {
onStart: () => console.log("Drag started"),
onMove: () => console.log("Drag moved"),
onEnd: () => console.log("Drag ended")
}
};
}
// Merge partial config with defaults
function mergeDragConfig(
partial: DragConfig,
defaults: RequiredDragConfig
): RequiredDragConfig {
return {
element: partial.element ?? defaults.element,
options: {
threshold: partial.options?.threshold ?? defaults.options.threshold,
axis: partial.options?.axis ?? defaults.options.axis,
constraints: {
minX: partial.options?.constraints?.minX ?? defaults.options.constraints.minX,
maxX: partial.options?.constraints?.maxX ?? defaults.options.constraints.maxX,
minY: partial.options?.constraints?.minY ?? defaults.options.constraints.minY,
maxY: partial.options?.constraints?.maxY ?? defaults.options.constraints.maxY
}
},
callbacks: {
onStart: partial.callbacks?.onStart ?? defaults.callbacks.onStart,
onMove: partial.callbacks?.onMove ?? defaults.callbacks.onMove,
onEnd: partial.callbacks?.onEnd ?? defaults.callbacks.onEnd
}
};
}Extracts the type of the first argument from a function. Useful for creating utilities that work with the primary parameter of callback functions.
/**
* Extracts the type of the first argument from a function
* @template T - Function type to extract first argument from
*/
type FirstArgument<T> = T extends (firstArg: infer U, ...args: Array<any>) => any
? U
: never;Usage Examples:
import { type FirstArgument } from "@dnd-kit/utilities";
// Extract first argument type from event handlers
type MouseEventHandler = (event: MouseEvent, extra: string) => void;
type MouseEventType = FirstArgument<MouseEventHandler>;
// Result: MouseEvent
type DragHandler = (data: { x: number; y: number }, element: HTMLElement) => void;
type DragData = FirstArgument<DragHandler>;
// Result: { x: number; y: number }
// Create type-safe event proxy
function createEventProxy<T extends (event: any, ...args: any[]) => any>(
handler: T
): (event: FirstArgument<T>) => void {
return (event: FirstArgument<T>) => {
// Validate or transform the event
console.log("Proxying event:", event);
handler(event, "additional", "arguments");
};
}
// React component prop extraction
interface ButtonProps {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDoubleClick: (event: React.MouseEvent<HTMLButtonElement>, target: HTMLElement) => void;
}
type ClickEvent = FirstArgument<ButtonProps['onClick']>;
// Result: React.MouseEvent<HTMLButtonElement>
type DoubleClickEvent = FirstArgument<ButtonProps['onDoubleClick']>;
// Result: React.MouseEvent<HTMLButtonElement>
// Generic callback wrapper
function createCallbackWrapper<T extends (first: any, ...rest: any[]) => any>(
callback: T,
middleware: (data: FirstArgument<T>) => FirstArgument<T>
): T {
return ((...args: any[]) => {
const [first, ...rest] = args;
const processedFirst = middleware(first);
return callback(processedFirst, ...rest);
}) as T;
}
// Usage with drag handlers
const originalDragHandler = (data: { x: number, y: number }, element: HTMLElement) => {
console.log("Dragging to:", data);
};
const wrappedDragHandler = createCallbackWrapper(
originalDragHandler,
(data) => ({
x: Math.round(data.x),
y: Math.round(data.y)
})
);
// Event handler factory
function createEventHandler<T extends (event: any, ...args: any[]) => any>(
handler: T,
validator: (event: FirstArgument<T>) => boolean
): T {
return ((...args: any[]) => {
const [event, ...rest] = args;
if (validator(event)) {
return handler(event, ...rest);
}
console.warn("Event validation failed");
}) as T;
}
// Create validated mouse handler
const validatedMouseHandler = createEventHandler(
(event: MouseEvent) => console.log("Valid mouse event"),
(event) => event instanceof MouseEvent && event.button === 0
);Creates a type by excluding specified keys from another type. Useful for creating modified versions of interfaces or for implementing omit-like functionality.
/**
* Creates a type by excluding specified keys from T
* @template T - Source type
* @template K - Keys to exclude from T
*/
type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;Usage Examples:
import { type Without } from "@dnd-kit/utilities";
// Original interface
interface FullDragConfig {
element: HTMLElement;
threshold: number;
axis: "x" | "y" | "both";
onStart: (event: MouseEvent) => void;
onMove: (event: MouseEvent) => void;
onEnd: (event: MouseEvent) => void;
disabled: boolean;
}
// Create type without event handlers
type DragConfigWithoutEvents = Without<FullDragConfig, 'onStart' | 'onMove' | 'onEnd'>;
/* Result:
{
element: HTMLElement;
threshold: number;
axis: "x" | "y" | "both";
disabled: boolean;
}
*/
// Create type without element (for configuration only)
type DragOptions = Without<FullDragConfig, 'element'>;
/* Result:
{
threshold: number;
axis: "x" | "y" | "both";
onStart: (event: MouseEvent) => void;
onMove: (event: MouseEvent) => void;
onEnd: (event: MouseEvent) => void;
disabled: boolean;
}
*/
// Function that accepts partial configuration
function createDragElement(
element: HTMLElement,
config: Without<FullDragConfig, 'element'>
) {
const fullConfig: FullDragConfig = {
element,
...config
};
return initializeDrag(fullConfig);
}
// Create immutable update functions
function updateDragConfig<K extends keyof FullDragConfig>(
config: FullDragConfig,
key: K,
value: FullDragConfig[K]
): FullDragConfig {
return { ...config, [key]: value };
}
// Create configuration builder
class DragConfigBuilder {
private config: Partial<FullDragConfig> = {};
element(el: HTMLElement): DragConfigBuilder {
this.config.element = el;
return this;
}
threshold(val: number): DragConfigBuilder {
this.config.threshold = val;
return this;
}
// Method that returns config without builder methods
build(): Without<FullDragConfig, never> {
return this.config as FullDragConfig;
}
// Get partial config for validation
getPartial(): Without<FullDragConfig, 'element'> | Partial<FullDragConfig> {
return this.config;
}
}
// React props manipulation
interface ComponentProps {
className?: string;
style?: React.CSSProperties;
onClick?: (event: React.MouseEvent) => void;
onDrag?: (event: React.DragEvent) => void;
children: React.ReactNode;
disabled?: boolean;
}
// Create props without event handlers for styling-only component
type StyleOnlyProps = Without<ComponentProps, 'onClick' | 'onDrag'>;
function StyleOnlyComponent(props: StyleOnlyProps) {
return <div className={props.className} style={props.style}>{props.children}</div>;
}
// Create props without children for wrapper component
type WrapperProps = Without<ComponentProps, 'children'>;
function WrapperComponent(props: WrapperProps) {
return (
<div {...props}>
<div>Wrapper content</div>
</div>
);
}
// API response type manipulation
interface APIResponse {
data: any;
status: number;
headers: Record<string, string>;
timestamp: string;
requestId: string;
}
// Client-side type without server metadata
type ClientResponse = Without<APIResponse, 'headers' | 'requestId'>;
function processClientResponse(response: ClientResponse) {
// Only access client-relevant properties
console.log("Status:", response.status);
console.log("Data:", response.data);
console.log("Time:", response.timestamp);
}