UNPKG

@zag-js/solid

Version:

The solid.js wrapper for zag

431 lines (425 loc) • 11.9 kB
'use strict'; var keyed = require('@solid-primitives/keyed'); var core = require('@zag-js/core'); var utils = require('@zag-js/utils'); var solidJs = require('solid-js'); var types = require('@zag-js/types'); // src/index.ts function createBindable(props) { const initial = props().value ?? props().defaultValue; const eq = props().isEqual ?? Object.is; const [value, setValue] = solidJs.createSignal(initial); const controlled = solidJs.createMemo(() => props().value != void 0); const valueRef = { current: value() }; const prevValue = { current: void 0 }; solidJs.createEffect(() => { const v = controlled() ? props().value : value(); prevValue.current = v; valueRef.current = v; }); const set = (v) => { const prev = prevValue.current; const next = utils.isFunction(v) ? v(valueRef.current) : v; 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() { const v = controlled() ? props().value : value; return utils.isFunction(v) ? v() : v; } return { initial, ref: valueRef, get, set, invoke(nextValue, prevValue2) { props().onChange?.(nextValue, prevValue2); }, hash(value2) { return props().hash?.(value2) ?? String(value2); } }; } createBindable.cleanup = (fn) => { solidJs.onCleanup(() => fn()); }; createBindable.ref = (defaultValue) => { let value = defaultValue; return { get: () => value, set: (next) => { value = next; } }; }; // src/refs.ts function createRefs(refs) { const ref = { current: refs }; return { get(key) { return ref.current[key]; }, set(key, value) { ref.current[key] = value; } }; } function access(v) { if (utils.isFunction(v)) return v(); return v; } var createTrack = (deps, effect) => { let prevDeps = []; let isFirstRun = true; solidJs.createEffect(() => { if (isFirstRun) { prevDeps = deps.map((d) => access(d)); isFirstRun = false; return; } let changed = false; for (let i = 0; i < deps.length; i++) { if (!utils.isEqual(prevDeps[i], access(deps[i]))) { changed = true; break; } } if (changed) { prevDeps = deps.map((d) => access(d)); effect(); } }); }; // src/machine.ts function useMachine(machine, userProps = {}) { const scope = solidJs.createMemo(() => { const { id, ids, getRootNode } = access2(userProps); return core.createScope({ id, ids, getRootNode }); }); const debug = (...args) => { if (machine.debug) console.log(...args); }; const props = solidJs.createMemo( () => machine.props?.({ props: utils.compact(access2(userProps)), scope: scope() }) ?? access2(userProps) ); const prop = createProp(props); const context = machine.context?.({ prop, bindable: createBindable, get scope() { return scope(); }, flush, getContext() { return ctx; }, getComputed() { return computed; }, getRefs() { return refs; }, getEvent() { return getEvent(); } }); const ctx = { get(key) { return context?.[key].get(); }, set(key, value) { context?.[key].set(value); }, initial(key) { return context?.[key].initial; }, hash(key) { const current = context?.[key].get(); return context?.[key].hash(current); } }; const effects = { current: /* @__PURE__ */ new Map() }; const transitionRef = { current: null }; const previousEventRef = { current: null }; const eventRef = { current: { type: "" } }; const getEvent = () => solidJs.mergeProps(eventRef.current, { current() { return eventRef.current; }, previous() { return previousEventRef.current; } }); const getState = () => solidJs.mergeProps(state, { matches(...values) { const current = state.get(); return values.includes(current); }, hasTag(tag) { const current = state.get(); return !!machine.states[current]?.tags?.includes(tag); } }); const refs = createRefs(machine.refs?.({ prop, context: ctx }) ?? {}); const getParams = () => ({ state: getState(), context: ctx, event: getEvent(), prop, send, action, guard, track: createTrack, refs, computed, flush, get scope() { return 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: eventRef.current, prop, refs, scope: scope(), computed }); }; const state = createBindable(() => ({ 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); } })); let status = core.MachineStatus.NotStarted; solidJs.onMount(() => { const started = status === core.MachineStatus.Started; status = core.MachineStatus.Started; debug(started ? "rehydrating..." : "initializing..."); state.invoke(state.initial, core.INIT_STATE); }); solidJs.onCleanup(() => { debug("unmounting..."); status = core.MachineStatus.Stopped; const fns = effects.current; fns.forEach((fn) => fn?.()); effects.current = /* @__PURE__ */ new Map(); transitionRef.current = null; action(machine.exit); }); const send = (event) => { queueMicrotask(() => { if (status !== core.MachineStatus.Started) return; previousEventRef.current = eventRef.current; eventRef.current = event; let currentState = solidJs.untrack(() => state.get()); 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", event.type, transition.target || currentState, `(${transition.actions})`); const changed = target !== currentState; if (changed) { 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, get scope() { return scope(); }, refs, computed, event: getEvent(), getStatus: () => status }; } function flush(fn) { fn(); } function access2(value) { return utils.isFunction(value) ? value() : value; } function createProp(value) { return function get(key) { return value()[key]; }; } function mergeProps2(...sources) { const target = {}; for (let i = 0; i < sources.length; i++) { let source = sources[i]; if (typeof source === "function") source = source(); if (source) { const descriptors = Object.getOwnPropertyDescriptors(source); for (const key in descriptors) { if (key in target) continue; Object.defineProperty(target, key, { enumerable: true, get() { let e = {}; if (key === "style" || key === "class" || key === "className" || key.startsWith("on")) { for (let i2 = 0; i2 < sources.length; i2++) { let s = sources[i2]; if (typeof s === "function") s = s(); e = core.mergeProps(e, { [key]: (s || {})[key] }); } return e[key]; } for (let i2 = sources.length - 1; i2 >= 0; i2--) { let v, s = sources[i2]; if (typeof s === "function") s = s(); v = (s || {})[key]; if (v !== void 0) return v; } } }); } } } return target; } var eventMap = { onFocus: "onFocusIn", onBlur: "onFocusOut", onDoubleClick: "onDblClick", onChange: "onInput", defaultChecked: "checked", defaultValue: "value", htmlFor: "for", className: "class" }; var format = (v) => v.startsWith("--") ? v : hyphenateStyleName(v); function toSolidProp(prop) { return prop in eventMap ? eventMap[prop] : prop; } var normalizeProps = types.createNormalizer((props) => { const normalized = {}; for (const key in props) { const value = props[key]; if (key === "readOnly" && value === false) { continue; } if (key === "style" && utils.isObject(value)) { normalized["style"] = cssify(value); continue; } if (key === "children") { if (utils.isString(value)) { normalized["textContent"] = value; } continue; } normalized[toSolidProp(key)] = value; } return normalized; }); function cssify(style) { let css = {}; for (const property in style) { const value = style[property]; if (!utils.isString(value) && !utils.isNumber(value)) continue; css[format(property)] = value; } return css; } var uppercasePattern = /[A-Z]/g; var msPattern = /^ms-/; function toHyphenLower(match) { return "-" + match.toLowerCase(); } var cache = {}; function hyphenateStyleName(name) { if (cache.hasOwnProperty(name)) return cache[name]; var hName = name.replace(uppercasePattern, toHyphenLower); return cache[name] = msPattern.test(hName) ? "-" + hName : hName; } Object.defineProperty(exports, "Key", { enumerable: true, get: function () { return keyed.Key; } }); exports.mergeProps = mergeProps2; exports.normalizeProps = normalizeProps; exports.useMachine = useMachine;