CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zag-js--core

A minimal implementation of xstate fsm for UI machines

Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Helper functions for property merging, memoization, and scope management essential for UI component development.

Capabilities

Property Merging

Intelligently merges multiple props objects with special handling for event handlers, CSS classes, and styles.

/**
 * Merges multiple props objects with smart handling for events, classes, and styles
 * @param args - Variable number of props objects to merge
 * @returns Merged props object with combined functionality
 */
function mergeProps<T extends Props>(...args: T[]): UnionToIntersection<TupleTypes<T[]>>;

interface Props {
  [key: string]: any;
}

// Utility types for mergeProps
type TupleTypes<T extends any[]> = T[number];
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

Special Merging Behavior:

  • Event Handlers: Functions starting with "on" are combined to call all handlers in sequence
  • CSS Classes: className and class properties are concatenated with spaces
  • Styles: Style objects/strings are merged, with later values overriding earlier ones
  • Other Props: Later values override earlier values, undefined values are ignored

Usage Examples:

import { mergeProps } from "@zag-js/core";

// Basic prop merging
const baseProps = { className: "base", disabled: false };
const variantProps = { className: "primary", size: "large" };
const merged = mergeProps(baseProps, variantProps);
// Result: { className: "base primary", disabled: false, size: "large" }

// Event handler combination
const buttonProps = {
  onClick: () => console.log("Button clicked"),
  className: "button"
};
const trackingProps = {
  onClick: () => console.log("Event tracked"),
  className: "tracked"
};
const combined = mergeProps(buttonProps, trackingProps);
// Result: onClick calls both functions, className is "button tracked"

// Style merging
const baseStyles = {
  style: { color: "blue", fontSize: "14px" },
  className: "base"
};
const overrideStyles = {
  style: { color: "red", fontWeight: "bold" },
  className: "override"
};
const styledProps = mergeProps(baseStyles, overrideStyles);
// Result: style is { color: "red", fontSize: "14px", fontWeight: "bold" }

// CSS string style merging
const cssStringProps = {
  style: "color: blue; font-size: 14px;",
  className: "css-string"
};
const cssObjectProps = {
  style: { color: "red", fontWeight: "bold" },
  className: "css-object"  
};
const mixedStyles = mergeProps(cssStringProps, cssObjectProps);
// Styles are properly merged regardless of string vs object format

Memoization

Creates memoized functions with dependency tracking for performance optimization.

/**
 * Creates a memoized function with dependency tracking
 * @param getDeps - Function to extract dependencies from arguments
 * @param fn - Function to memoize (called only when dependencies change)
 * @param opts - Optional configuration with change callback
 * @returns Memoized function that caches results based on dependency equality
 */
function memo<TDeps extends any[], TDepArgs, TResult>(
  getDeps: (depArgs: TDepArgs) => [...TDeps],
  fn: (...args: NoInfer<[...TDeps]>) => TResult,
  opts?: {
    onChange?: ((result: TResult) => void) | undefined;
  }
): (depArgs: TDepArgs) => TResult;

type NoInfer<T> = [T][T extends any ? 0 : never];

Usage Examples:

import { memo } from "@zag-js/core";

// Basic memoization
const expensiveCalculation = memo(
  // Extract dependencies
  ({ numbers, multiplier }: { numbers: number[], multiplier: number }) => [numbers, multiplier],
  // Function to memoize
  (numbers: number[], multiplier: number) => {
    console.log("Calculating sum..."); // Only logs when deps change
    return numbers.reduce((sum, n) => sum + n, 0) * multiplier;
  }
);

const result1 = expensiveCalculation({ numbers: [1, 2, 3], multiplier: 2 }); // Calculates
const result2 = expensiveCalculation({ numbers: [1, 2, 3], multiplier: 2 }); // Uses cache
// result1 === result2, calculation only ran once

// Memoization with change callback
const memoizedFormatter = memo(
  ({ value, locale }: { value: number, locale: string }) => [value, locale],
  (value: number, locale: string) => {
    return new Intl.NumberFormat(locale).format(value);
  },
  {
    onChange: (formatted) => console.log(`Formatted: ${formatted}`)
  }
);

// Complex object memoization
interface RenderProps {
  items: Array<{ id: string, name: string }>;
  filter: string;
  sortBy: 'name' | 'id';
}

const memoizedFilter = memo(
  ({ items, filter, sortBy }: RenderProps) => [items, filter, sortBy],
  (items, filter, sortBy) => {
    return items
      .filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
      .sort((a, b) => a[sortBy].localeCompare(b[sortBy]));
  }
);

Scope Creation

Creates scope objects for DOM operations and element queries within a specific root node context.

/**
 * Creates a scope object for DOM operations and element queries
 * @param props - Configuration with id, ids mapping, and root node accessor
 * @returns Scope object with DOM utility methods
 */
function createScope(props: Pick<Scope, "id" | "ids" | "getRootNode">): Scope;

interface Scope {
  /** Scope identifier */
  id?: string | undefined;
  /** ID mappings for scoped element access */
  ids?: Record<string, any> | undefined;
  /** Get the root node (document, shadow root, or custom node) */
  getRootNode(): ShadowRoot | Document | Node;
  /** Get element by ID within the scope */
  getById<T extends Element = HTMLElement>(id: string): T | null;
  /** Get currently active element within the scope */
  getActiveElement(): HTMLElement | null;
  /** Check if the given element is the active element */
  isActiveElement(elem: HTMLElement | null): boolean;
  /** Get the document associated with the scope */
  getDoc(): typeof document;
  /** Get the window associated with the scope */
  getWin(): typeof window;
}

Usage Examples:

import { createScope } from "@zag-js/core";

// Basic scope creation
const documentScope = createScope({
  id: "my-component",
  getRootNode: () => document
});

// Access DOM elements
const button = documentScope.getById<HTMLButtonElement>("submit-btn");
const activeEl = documentScope.getActiveElement();
const isSubmitActive = documentScope.isActiveElement(button);

// Shadow DOM scope
const shadowScope = createScope({
  id: "shadow-component",
  getRootNode: () => shadowRoot
});

// Scoped ID mapping
const scopedIds = {
  trigger: "dialog-trigger",
  content: "dialog-content", 
  overlay: "dialog-overlay"
};

const dialogScope = createScope({
  id: "dialog",
  ids: scopedIds,
  getRootNode: () => document
});

// Use scoped IDs
const trigger = dialogScope.getById(dialogScope.ids.trigger);
const content = dialogScope.getById(dialogScope.ids.content);

// Component integration example
interface ComponentProps {
  id?: string;
  getRootNode?: () => Document | ShadowRoot;
}

function createComponentScope({ id = "component", getRootNode }: ComponentProps) {
  return createScope({
    id,
    getRootNode: getRootNode ?? (() => document),
    ids: {
      root: `${id}-root`,
      content: `${id}-content`,
      trigger: `${id}-trigger`
    }
  });
}

// Usage in machine context
const scope = createComponentScope({ id: "tabs" });
const tablist = scope.getById<HTMLDivElement>(scope.ids.root);
const doc = scope.getDoc();
const win = scope.getWin();

Internal CSS Utilities

Internal utilities used by mergeProps for CSS and class name handling.

/**
 * Concatenates CSS class names, filtering out falsy values
 * @param args - Array of class name strings (undefined values ignored)
 * @returns Single space-separated class name string
 */
declare function clsx(...args: (string | undefined)[]): string;

/**
 * Merges CSS styles from objects or strings
 * @param a - First style (object or CSS string)
 * @param b - Second style (object or CSS string)  
 * @returns Merged style object or CSS string
 */
declare function css(
  a: Record<string, string> | string | undefined,
  b: Record<string, string> | string | undefined
): Record<string, string> | string;

/**
 * Parses CSS string into style object
 * @param style - CSS string with property: value pairs
 * @returns Object with CSS properties as keys
 */
declare function serialize(style: string): Record<string, string>;

Note: These are internal utilities used by mergeProps and are not typically used directly. They handle the complex logic of merging different CSS formats.

Install with Tessl CLI

npx tessl i tessl/npm-zag-js--core

docs

index.md

machine-creation.md

type-system.md

utilities.md

tile.json