or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmachine-creation.mdtype-system.mdutilities.md

utilities.mddocs/

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.