0
# Utility Functions
1
2
Helper functions for property merging, memoization, and scope management essential for UI component development.
3
4
## Capabilities
5
6
### Property Merging
7
8
Intelligently merges multiple props objects with special handling for event handlers, CSS classes, and styles.
9
10
```typescript { .api }
11
/**
12
* Merges multiple props objects with smart handling for events, classes, and styles
13
* @param args - Variable number of props objects to merge
14
* @returns Merged props object with combined functionality
15
*/
16
function mergeProps<T extends Props>(...args: T[]): UnionToIntersection<TupleTypes<T[]>>;
17
18
interface Props {
19
[key: string]: any;
20
}
21
22
// Utility types for mergeProps
23
type TupleTypes<T extends any[]> = T[number];
24
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
25
```
26
27
**Special Merging Behavior:**
28
29
- **Event Handlers**: Functions starting with "on" are combined to call all handlers in sequence
30
- **CSS Classes**: `className` and `class` properties are concatenated with spaces
31
- **Styles**: Style objects/strings are merged, with later values overriding earlier ones
32
- **Other Props**: Later values override earlier values, undefined values are ignored
33
34
**Usage Examples:**
35
36
```typescript
37
import { mergeProps } from "@zag-js/core";
38
39
// Basic prop merging
40
const baseProps = { className: "base", disabled: false };
41
const variantProps = { className: "primary", size: "large" };
42
const merged = mergeProps(baseProps, variantProps);
43
// Result: { className: "base primary", disabled: false, size: "large" }
44
45
// Event handler combination
46
const buttonProps = {
47
onClick: () => console.log("Button clicked"),
48
className: "button"
49
};
50
const trackingProps = {
51
onClick: () => console.log("Event tracked"),
52
className: "tracked"
53
};
54
const combined = mergeProps(buttonProps, trackingProps);
55
// Result: onClick calls both functions, className is "button tracked"
56
57
// Style merging
58
const baseStyles = {
59
style: { color: "blue", fontSize: "14px" },
60
className: "base"
61
};
62
const overrideStyles = {
63
style: { color: "red", fontWeight: "bold" },
64
className: "override"
65
};
66
const styledProps = mergeProps(baseStyles, overrideStyles);
67
// Result: style is { color: "red", fontSize: "14px", fontWeight: "bold" }
68
69
// CSS string style merging
70
const cssStringProps = {
71
style: "color: blue; font-size: 14px;",
72
className: "css-string"
73
};
74
const cssObjectProps = {
75
style: { color: "red", fontWeight: "bold" },
76
className: "css-object"
77
};
78
const mixedStyles = mergeProps(cssStringProps, cssObjectProps);
79
// Styles are properly merged regardless of string vs object format
80
```
81
82
### Memoization
83
84
Creates memoized functions with dependency tracking for performance optimization.
85
86
```typescript { .api }
87
/**
88
* Creates a memoized function with dependency tracking
89
* @param getDeps - Function to extract dependencies from arguments
90
* @param fn - Function to memoize (called only when dependencies change)
91
* @param opts - Optional configuration with change callback
92
* @returns Memoized function that caches results based on dependency equality
93
*/
94
function memo<TDeps extends any[], TDepArgs, TResult>(
95
getDeps: (depArgs: TDepArgs) => [...TDeps],
96
fn: (...args: NoInfer<[...TDeps]>) => TResult,
97
opts?: {
98
onChange?: ((result: TResult) => void) | undefined;
99
}
100
): (depArgs: TDepArgs) => TResult;
101
102
type NoInfer<T> = [T][T extends any ? 0 : never];
103
```
104
105
**Usage Examples:**
106
107
```typescript
108
import { memo } from "@zag-js/core";
109
110
// Basic memoization
111
const expensiveCalculation = memo(
112
// Extract dependencies
113
({ numbers, multiplier }: { numbers: number[], multiplier: number }) => [numbers, multiplier],
114
// Function to memoize
115
(numbers: number[], multiplier: number) => {
116
console.log("Calculating sum..."); // Only logs when deps change
117
return numbers.reduce((sum, n) => sum + n, 0) * multiplier;
118
}
119
);
120
121
const result1 = expensiveCalculation({ numbers: [1, 2, 3], multiplier: 2 }); // Calculates
122
const result2 = expensiveCalculation({ numbers: [1, 2, 3], multiplier: 2 }); // Uses cache
123
// result1 === result2, calculation only ran once
124
125
// Memoization with change callback
126
const memoizedFormatter = memo(
127
({ value, locale }: { value: number, locale: string }) => [value, locale],
128
(value: number, locale: string) => {
129
return new Intl.NumberFormat(locale).format(value);
130
},
131
{
132
onChange: (formatted) => console.log(`Formatted: ${formatted}`)
133
}
134
);
135
136
// Complex object memoization
137
interface RenderProps {
138
items: Array<{ id: string, name: string }>;
139
filter: string;
140
sortBy: 'name' | 'id';
141
}
142
143
const memoizedFilter = memo(
144
({ items, filter, sortBy }: RenderProps) => [items, filter, sortBy],
145
(items, filter, sortBy) => {
146
return items
147
.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
148
.sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
149
}
150
);
151
```
152
153
### Scope Creation
154
155
Creates scope objects for DOM operations and element queries within a specific root node context.
156
157
```typescript { .api }
158
/**
159
* Creates a scope object for DOM operations and element queries
160
* @param props - Configuration with id, ids mapping, and root node accessor
161
* @returns Scope object with DOM utility methods
162
*/
163
function createScope(props: Pick<Scope, "id" | "ids" | "getRootNode">): Scope;
164
165
interface Scope {
166
/** Scope identifier */
167
id?: string | undefined;
168
/** ID mappings for scoped element access */
169
ids?: Record<string, any> | undefined;
170
/** Get the root node (document, shadow root, or custom node) */
171
getRootNode(): ShadowRoot | Document | Node;
172
/** Get element by ID within the scope */
173
getById<T extends Element = HTMLElement>(id: string): T | null;
174
/** Get currently active element within the scope */
175
getActiveElement(): HTMLElement | null;
176
/** Check if the given element is the active element */
177
isActiveElement(elem: HTMLElement | null): boolean;
178
/** Get the document associated with the scope */
179
getDoc(): typeof document;
180
/** Get the window associated with the scope */
181
getWin(): typeof window;
182
}
183
```
184
185
**Usage Examples:**
186
187
```typescript
188
import { createScope } from "@zag-js/core";
189
190
// Basic scope creation
191
const documentScope = createScope({
192
id: "my-component",
193
getRootNode: () => document
194
});
195
196
// Access DOM elements
197
const button = documentScope.getById<HTMLButtonElement>("submit-btn");
198
const activeEl = documentScope.getActiveElement();
199
const isSubmitActive = documentScope.isActiveElement(button);
200
201
// Shadow DOM scope
202
const shadowScope = createScope({
203
id: "shadow-component",
204
getRootNode: () => shadowRoot
205
});
206
207
// Scoped ID mapping
208
const scopedIds = {
209
trigger: "dialog-trigger",
210
content: "dialog-content",
211
overlay: "dialog-overlay"
212
};
213
214
const dialogScope = createScope({
215
id: "dialog",
216
ids: scopedIds,
217
getRootNode: () => document
218
});
219
220
// Use scoped IDs
221
const trigger = dialogScope.getById(dialogScope.ids.trigger);
222
const content = dialogScope.getById(dialogScope.ids.content);
223
224
// Component integration example
225
interface ComponentProps {
226
id?: string;
227
getRootNode?: () => Document | ShadowRoot;
228
}
229
230
function createComponentScope({ id = "component", getRootNode }: ComponentProps) {
231
return createScope({
232
id,
233
getRootNode: getRootNode ?? (() => document),
234
ids: {
235
root: `${id}-root`,
236
content: `${id}-content`,
237
trigger: `${id}-trigger`
238
}
239
});
240
}
241
242
// Usage in machine context
243
const scope = createComponentScope({ id: "tabs" });
244
const tablist = scope.getById<HTMLDivElement>(scope.ids.root);
245
const doc = scope.getDoc();
246
const win = scope.getWin();
247
```
248
249
### Internal CSS Utilities
250
251
Internal utilities used by mergeProps for CSS and class name handling.
252
253
```typescript { .api }
254
/**
255
* Concatenates CSS class names, filtering out falsy values
256
* @param args - Array of class name strings (undefined values ignored)
257
* @returns Single space-separated class name string
258
*/
259
declare function clsx(...args: (string | undefined)[]): string;
260
261
/**
262
* Merges CSS styles from objects or strings
263
* @param a - First style (object or CSS string)
264
* @param b - Second style (object or CSS string)
265
* @returns Merged style object or CSS string
266
*/
267
declare function css(
268
a: Record<string, string> | string | undefined,
269
b: Record<string, string> | string | undefined
270
): Record<string, string> | string;
271
272
/**
273
* Parses CSS string into style object
274
* @param style - CSS string with property: value pairs
275
* @returns Object with CSS properties as keys
276
*/
277
declare function serialize(style: string): Record<string, string>;
278
```
279
280
**Note**: These are internal utilities used by `mergeProps` and are not typically used directly. They handle the complex logic of merging different CSS formats.