UNPKG

@zag-js/preact

Version:

The preact wrapper for zag

425 lines (416 loc) 12.3 kB
'use strict'; var core = require('@zag-js/core'); var compat = require('preact/compat'); var hooks = require('preact/hooks'); var types = require('@zag-js/types'); // src/index.ts // ../../utilities/core/src/array.ts function toArray(v) { if (v == null) return []; return Array.isArray(v) ? v : [v]; } // ../../utilities/core/src/equal.ts var isArrayLike = (value) => value?.constructor.name === "Array"; var isArrayEqual = (a, b) => { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!isEqual(a[i], b[i])) return false; } return true; }; var isEqual = (a, b) => { if (Object.is(a, b)) return true; if (a == null && b != null || a != null && b == null) return false; if (typeof a?.isEqual === "function" && typeof b?.isEqual === "function") { return a.isEqual(b); } if (typeof a === "function" && typeof b === "function") { return a.toString() === b.toString(); } if (isArrayLike(a) && isArrayLike(b)) { return isArrayEqual(Array.from(a), Array.from(b)); } if (!(typeof a === "object") || !(typeof b === "object")) return false; const keys = Object.keys(b ?? /* @__PURE__ */ Object.create(null)); const length = keys.length; for (let i = 0; i < length; i++) { const hasKey = Reflect.has(a, keys[i]); if (!hasKey) return false; } for (let i = 0; i < length; i++) { const key = keys[i]; if (!isEqual(a[key], b[key])) return false; } return true; }; // ../../utilities/core/src/guard.ts var isString = (v) => typeof v === "string"; var isFunction = (v) => typeof v === "function"; var fnToString = Function.prototype.toString; fnToString.call(Object); // ../../utilities/core/src/warning.ts function warn(...a) { const m = a.length === 1 ? a[0] : a[1]; const c = a.length === 2 ? a[0] : true; if (c && process.env.NODE_ENV !== "production") { console.warn(m); } } function ensure(c, m) { if (c == null) throw new Error(m()); } var identity = (v) => v(); function useBindable(props) { const initial = props().value ?? props().defaultValue; const eq = props().isEqual ?? isEqual; const [initialValue] = hooks.useState(initial); const [value, setValue] = hooks.useState(initialValue); const controlled = props().value !== void 0; const valueRef = hooks.useRef(value); valueRef.current = controlled ? props().value : value; const prevValue = hooks.useRef(valueRef.current); hooks.useLayoutEffect(() => { prevValue.current = valueRef.current; }, [value, props().value]); const setFn = (value2) => { const prev = prevValue.current; const next = 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 ? compat.flushSync : identity; exec(() => setFn(value2)); }, invoke(nextValue, prevValue2) { props().onChange?.(nextValue, prevValue2); }, hash(value2) { return props().hash?.(value2) ?? String(value2); } }; } useBindable.cleanup = (fn) => { hooks.useLayoutEffect(() => fn, []); }; useBindable.ref = (defaultValue) => { const value = hooks.useRef(defaultValue); return { get: () => value.current, set: (next) => { value.current = next; } }; }; function useRefs(refs) { const ref = hooks.useRef(refs); return { get(key) { return ref.current[key]; }, set(key, value) { ref.current[key] = value; } }; } var useTrack = (deps, effect) => { const render = hooks.useRef(false); const called = hooks.useRef(false); hooks.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)]); hooks.useEffect(() => { render.current = true; return () => { render.current = false; }; }, []); }; // src/machine.ts function useMachine(machine, userProps = {}) { const scope = hooks.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: 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; }, getEvent() { return getEvent(); } }); const contextRef = useLiveRef(context); const ctx = { get(key) { return contextRef.current?.[key].get(); }, 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 = hooks.useRef(/* @__PURE__ */ new Map()); const transitionRef = hooks.useRef(null); const previousEventRef = hooks.useRef(null); const eventRef = hooks.useRef({ type: "" }); const refs = useRefs(machine.refs?.({ prop, context: ctx }) ?? {}); const getEvent = () => ({ ...eventRef.current, current() { return eventRef.current; }, previous() { return previousEventRef.current; } }); const getState = () => ({ ...state, hasTag(tag) { const currentState = state.get(); return !!machine.states[currentState]?.tags?.includes(tag); }, matches(...values) { const currentState = state.get(); return values.includes(currentState); } }); const getParams = () => ({ state: getState(), context: ctx, event: getEvent(), prop, send, action, guard, track: useTrack, refs, computed, flush, scope, choose }); const action = (keys) => { const strs = isFunction(keys) ? keys(getParams()) : keys; if (!strs) return; const fns = strs.map((s) => { const fn = machine.implementations?.actions?.[s]; if (!fn) warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`); return fn; }); for (const fn of fns) { fn?.(getParams()); } }; const guard = (str) => { if (isFunction(str)) return str(getParams()); return machine.implementations?.guards?.[str](getParams()); }; const effect = (keys) => { const strs = isFunction(keys) ? keys(getParams()) : keys; if (!strs) return; const fns = strs.map((s) => { const fn = machine.implementations?.effects?.[s]; if (!fn) 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 toArray(transitions).find((t) => { let result = !t.guard; if (isString(t.guard)) result = !!guard(t.guard); else if (isFunction(t.guard)) result = t.guard(getParams()); return result; }); }; const computed = (key) => { 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 = hooks.useRef(void 0); const statusRef = hooks.useRef(core.MachineStatus.NotStarted); hooks.useLayoutEffect(() => { 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; 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; 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; const changed = target !== currentState; if (changed) { compat.flushSync(() => state.set(target)); } 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 = hooks.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(() => { compat.flushSync(() => fn()); }); } var eventMap = { onFocus: "onfocusin", onBlur: "onfocusout", onDoubleClick: "onDblClick", onChange: "onInput", defaultChecked: "checked", defaultValue: "value" }; function toPreactProp(prop) { return prop in eventMap ? eventMap[prop] : prop; } var normalizeProps = types.createNormalizer((props) => { const normalized = {}; for (const key in props) { normalized[toPreactProp(key)] = props[key]; } return normalized; }); var Portal = (props) => { const { children, container, disabled, getRootNode } = props; const [, forceUpdate] = compat.useReducer((c) => c + 1, 0); compat.useLayoutEffect(() => { forceUpdate({}); }, []); const isServer = typeof window === "undefined"; if (isServer || disabled) return /* @__PURE__ */ React.createElement(React.Fragment, null, children); const doc = getRootNode?.().ownerDocument ?? document; const mountNode = container?.current ?? doc.body; return /* @__PURE__ */ React.createElement(React.Fragment, null, compat.Children.map(children, (child) => { return compat.createPortal(/* @__PURE__ */ React.createElement(React.Fragment, null, child), mountNode); })); }; Object.defineProperty(exports, "mergeProps", { enumerable: true, get: function () { return core.mergeProps; } }); exports.Portal = Portal; exports.normalizeProps = normalizeProps; exports.useMachine = useMachine;