UNPKG

@zag-js/react

Version:

The react wrapper for zag

372 lines (363 loc) • 10.9 kB
"use client"; 'use strict'; var core = require('@zag-js/core'); var utils = require('@zag-js/utils'); var React = require('react'); var reactDom = require('react-dom'); var types = require('@zag-js/types'); var jsxRuntime = require('react/jsx-runtime'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespace(React); // src/index.ts var useSafeLayoutEffect = typeof globalThis.document !== "undefined" ? React.useLayoutEffect : React.useEffect; // src/bindable.ts function useBindable(props) { const initial = props().value ?? props().defaultValue; const eq = props().isEqual ?? Object.is; const [initialValue] = React.useState(initial); const [value, setValue] = React.useState(initialValue); const controlled = props().value !== void 0; const valueRef = React.useRef(value); valueRef.current = controlled ? props().value : value; const prevValue = React.useRef(valueRef.current); useSafeLayoutEffect(() => { prevValue.current = valueRef.current; }, [value, props().value]); const setFn = (value2) => { const prev = prevValue.current; const next = utils.isFunction(value2) ? value2(prev) : value2; if (props().debug) { console.log(`[bindable > ${props().debug}] setValue`, { next, prev }); } if (!controlled) setValue(next); if (!eq(next, prev)) { props().onChange?.(next, prev); } }; function get() { return controlled ? props().value : value; } return { initial: initialValue, ref: valueRef, get, set(value2) { const exec = props().sync ? reactDom.flushSync : utils.identity; exec(() => setFn(value2)); }, invoke(nextValue, prevValue2) { props().onChange?.(nextValue, prevValue2); }, hash(value2) { return props().hash?.(value2) ?? String(value2); } }; } useBindable.cleanup = (fn) => { React.useEffect(() => fn, []); }; useBindable.ref = (defaultValue) => { const value = React.useRef(defaultValue); return { get: () => value.current, set: (next) => { value.current = next; } }; }; function useRefs(refs) { const ref = React.useRef(refs); return { get(key) { return ref.current[key]; }, set(key, value) { ref.current[key] = value; } }; } var useTrack = (deps, effect) => { const render = React.useRef(false); const called = React.useRef(false); React.useEffect(() => { const mounted = render.current; const run = mounted && called.current; if (run) return effect(); called.current = true; }, [...(deps ?? []).map((d) => typeof d === "function" ? d() : d)]); React.useEffect(() => { render.current = true; return () => { render.current = false; }; }, []); }; // src/machine.ts function useMachine(machine, userProps = {}) { const scope = React.useMemo(() => { const { id, ids, getRootNode } = userProps; return core.createScope({ id, ids, getRootNode }); }, [userProps]); const debug = (...args) => { if (machine.debug) console.log(...args); }; const props = machine.props?.({ props: utils.compact(userProps), scope }) ?? userProps; const prop = useProp(props); const context = machine.context?.({ prop, bindable: useBindable, scope, flush, getContext() { return ctx; }, getComputed() { return computed; }, getRefs() { return refs; } }); const contextRef = useLiveRef(context); const ctx = { get(key) { return contextRef.current?.[key].ref.current; }, set(key, value) { contextRef.current?.[key].set(value); }, initial(key) { return contextRef.current?.[key].initial; }, hash(key) { const current = contextRef.current?.[key].get(); return contextRef.current?.[key].hash(current); } }; const effects = React.useRef(/* @__PURE__ */ new Map()); const transitionRef = React.useRef(null); const previousEventRef = React.useRef(null); const eventRef = React.useRef({ type: "" }); const getEvent = () => ({ ...eventRef.current, current() { return eventRef.current; }, previous() { return previousEventRef.current; } }); const getState = () => ({ ...state, matches(...values) { return values.includes(state.ref.current); }, hasTag(tag) { return !!machine.states[state.ref.current]?.tags?.includes(tag); } }); const refs = useRefs(machine.refs?.({ prop, context: ctx }) ?? {}); const getParams = () => ({ state: getState(), context: ctx, event: getEvent(), prop, send, action, guard, track: useTrack, refs, computed, flush, scope, choose }); const action = (keys) => { const strs = utils.isFunction(keys) ? keys(getParams()) : keys; if (!strs) return; const fns = strs.map((s) => { const fn = machine.implementations?.actions?.[s]; if (!fn) utils.warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`); return fn; }); for (const fn of fns) { fn?.(getParams()); } }; const guard = (str) => { if (utils.isFunction(str)) return str(getParams()); return machine.implementations?.guards?.[str](getParams()); }; const effect = (keys) => { const strs = utils.isFunction(keys) ? keys(getParams()) : keys; if (!strs) return; const fns = strs.map((s) => { const fn = machine.implementations?.effects?.[s]; if (!fn) utils.warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`); return fn; }); const cleanups = []; for (const fn of fns) { const cleanup = fn?.(getParams()); if (cleanup) cleanups.push(cleanup); } return () => cleanups.forEach((fn) => fn?.()); }; const choose = (transitions) => { return utils.toArray(transitions).find((t) => { let result = !t.guard; if (utils.isString(t.guard)) result = !!guard(t.guard); else if (utils.isFunction(t.guard)) result = t.guard(getParams()); return result; }); }; const computed = (key) => { utils.ensure(machine.computed, () => `[zag-js] No computed object found on machine`); const fn = machine.computed[key]; return fn({ context: ctx, event: getEvent(), prop, refs, scope, computed }); }; const state = useBindable(() => ({ defaultValue: machine.initialState({ prop }), onChange(nextState, prevState) { if (prevState) { const exitEffects = effects.current.get(prevState); exitEffects?.(); effects.current.delete(prevState); } if (prevState) { action(machine.states[prevState]?.exit); } action(transitionRef.current?.actions); const cleanup = effect(machine.states[nextState]?.effects); if (cleanup) effects.current.set(nextState, cleanup); if (prevState === core.INIT_STATE) { action(machine.entry); const cleanup2 = effect(machine.effects); if (cleanup2) effects.current.set(core.INIT_STATE, cleanup2); } action(machine.states[nextState]?.entry); } })); const hydratedStateRef = React.useRef(void 0); const statusRef = React.useRef(core.MachineStatus.NotStarted); useSafeLayoutEffect(() => { queueMicrotask(() => { const started = statusRef.current === core.MachineStatus.Started; statusRef.current = core.MachineStatus.Started; debug(started ? "rehydrating..." : "initializing..."); const initialState = hydratedStateRef.current ?? state.initial; state.invoke(initialState, started ? state.get() : core.INIT_STATE); }); const fns = effects.current; const currentState = state.ref.current; return () => { debug("unmounting..."); hydratedStateRef.current = currentState; statusRef.current = core.MachineStatus.Stopped; fns.forEach((fn) => fn?.()); effects.current = /* @__PURE__ */ new Map(); transitionRef.current = null; queueMicrotask(() => { action(machine.exit); }); }; }, []); const getCurrentState = () => { if ("ref" in state) return state.ref.current; return state.get(); }; const send = (event) => { queueMicrotask(() => { if (statusRef.current !== core.MachineStatus.Started) return; previousEventRef.current = eventRef.current; eventRef.current = event; debug("send", event); let currentState = getCurrentState(); const transitions = ( // @ts-ignore machine.states[currentState].on?.[event.type] ?? // @ts-ignore machine.on?.[event.type] ); const transition = choose(transitions); if (!transition) return; transitionRef.current = transition; const target = transition.target ?? currentState; debug("transition", transition); const changed = target !== currentState; if (changed) { reactDom.flushSync(() => state.set(target)); } else if (transition.reenter && !changed) { state.invoke(currentState, currentState); } else { action(transition.actions ?? []); } }); }; machine.watch?.(getParams()); return { state: getState(), send, context: ctx, prop, scope, refs, computed, event: getEvent(), getStatus: () => statusRef.current }; } function useLiveRef(value) { const ref = React.useRef(value); ref.current = value; return ref; } function useProp(value) { const ref = useLiveRef(value); return function get(key) { return ref.current[key]; }; } function flush(fn) { queueMicrotask(() => { reactDom.flushSync(() => fn()); }); } var normalizeProps = types.createNormalizer((v) => v); var Portal = (props) => { const { children, container, disabled, getRootNode } = props; const isServer = typeof window === "undefined"; if (isServer || disabled) return /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Fragment, { children }); const doc = getRootNode?.().ownerDocument ?? document; const mountNode = container?.current ?? doc.body; return /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Fragment, { children: React__namespace.Children.map(children, (child) => reactDom.createPortal(child, mountNode)) }); }; Object.defineProperty(exports, "mergeProps", { enumerable: true, get: function () { return core.mergeProps; } }); exports.Portal = Portal; exports.normalizeProps = normalizeProps; exports.useMachine = useMachine;