@zag-js/solid
Version:
The solid.js wrapper for zag
431 lines (425 loc) • 11.9 kB
JavaScript
;
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;