@sprawlify/svelte 0.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ import type { Bindable, BindableParams } from "@sprawlify/primitives/core";
2
+ export declare function bindable<T>(props: () => BindableParams<T>): Bindable<T>;
3
+ export declare namespace bindable {
4
+ var cleanup: (fn: VoidFunction) => void;
5
+ var ref: <T>(defaultValue: T) => {
6
+ get: () => T;
7
+ set: (next: T) => void;
8
+ };
9
+ }
@@ -0,0 +1,57 @@
1
+ import { identity, isFunction } from "@sprawlify/primitives/utils";
2
+ import { flushSync, onDestroy, untrack } from "svelte";
3
+ export function bindable(props) {
4
+ const initial = props().defaultValue ?? props().value;
5
+ const eq = props().isEqual ?? Object.is;
6
+ let value = $state(initial);
7
+ const controlled = $derived(props().value !== undefined);
8
+ let valueRef = { current: untrack(() => value) };
9
+ let prevValue = { current: undefined };
10
+ $effect.pre(() => {
11
+ const v = controlled ? props().value : value;
12
+ valueRef = { current: v };
13
+ prevValue = { current: v };
14
+ });
15
+ const setValueFn = (v) => {
16
+ const next = isFunction(v) ? v(valueRef.current) : v;
17
+ const prev = prevValue.current;
18
+ if (props().debug) {
19
+ console.log(`[bindable > ${props().debug}] setValue`, { next, prev });
20
+ }
21
+ if (!controlled)
22
+ value = next;
23
+ if (!eq(next, prev)) {
24
+ props().onChange?.(next, prev);
25
+ }
26
+ };
27
+ function get() {
28
+ return (controlled ? props().value : value);
29
+ }
30
+ return {
31
+ initial,
32
+ ref: valueRef,
33
+ get,
34
+ set(val) {
35
+ const exec = props().sync ? flushSync : identity;
36
+ exec(() => setValueFn(val));
37
+ },
38
+ invoke(nextValue, prevValue) {
39
+ props().onChange?.(nextValue, prevValue);
40
+ },
41
+ hash(value) {
42
+ return props().hash?.(value) ?? String(value);
43
+ },
44
+ };
45
+ }
46
+ bindable.cleanup = (fn) => {
47
+ onDestroy(() => fn());
48
+ };
49
+ bindable.ref = (defaultValue) => {
50
+ let value = defaultValue;
51
+ return {
52
+ get: () => value,
53
+ set: (next) => {
54
+ value = next;
55
+ },
56
+ };
57
+ };
@@ -0,0 +1,6 @@
1
+ export { mergeProps } from "./merge-props";
2
+ export { normalizeProps } from "./normalize-props";
3
+ export type { PropTypes } from "./normalize-props";
4
+ export { portal } from "./portal";
5
+ export { reflect } from "./reflect";
6
+ export { useMachine } from "./machine.svelte";
@@ -0,0 +1,5 @@
1
+ export { mergeProps } from "./merge-props";
2
+ export { normalizeProps } from "./normalize-props";
3
+ export { portal } from "./portal";
4
+ export { reflect } from "./reflect";
5
+ export { useMachine } from "./machine.svelte";
@@ -0,0 +1,2 @@
1
+ import type { Machine, MachineSchema, Service } from "@sprawlify/primitives/core";
2
+ export declare function useMachine<T extends MachineSchema>(machine: Machine<T>, userProps: Partial<T["props"]> | (() => Partial<T["props"]>)): Service<T>;
@@ -0,0 +1,260 @@
1
+ import { createScope, INIT_STATE, MachineStatus } from "@sprawlify/primitives/core";
2
+ import { compact, ensure, isFunction, isString, toArray, warn } from "@sprawlify/primitives/utils";
3
+ import { flushSync, onDestroy, onMount } from "svelte";
4
+ import { bindable } from "./bindable.svelte";
5
+ import { useRefs } from "./refs.svelte";
6
+ import { track } from "./track.svelte";
7
+ function access(userProps) {
8
+ if (isFunction(userProps))
9
+ return userProps();
10
+ return userProps;
11
+ }
12
+ export function useMachine(machine, userProps) {
13
+ const scope = $derived.by(() => {
14
+ const { id, ids, getRootNode } = access(userProps);
15
+ return createScope({ id, ids, getRootNode });
16
+ });
17
+ const debug = (...args) => {
18
+ if (machine.debug)
19
+ console.log(...args);
20
+ };
21
+ const props = $derived(machine.props?.({ props: compact(access(userProps)), scope }) ?? access(userProps));
22
+ const prop = useProp(() => props);
23
+ const context = machine.context?.({
24
+ prop,
25
+ bindable: bindable,
26
+ get scope() {
27
+ return scope;
28
+ },
29
+ flush: flush,
30
+ getContext() {
31
+ return ctx;
32
+ },
33
+ getComputed() {
34
+ return computed;
35
+ },
36
+ getRefs() {
37
+ return refs;
38
+ },
39
+ getEvent() {
40
+ return getEvent();
41
+ },
42
+ });
43
+ const ctx = {
44
+ get(key) {
45
+ return context?.[key].get();
46
+ },
47
+ set(key, value) {
48
+ context?.[key].set(value);
49
+ },
50
+ initial(key) {
51
+ return context?.[key].initial;
52
+ },
53
+ hash(key) {
54
+ const current = context?.[key].get();
55
+ return context?.[key].hash(current);
56
+ },
57
+ };
58
+ let effects = new Map();
59
+ const transitionRef = { current: null };
60
+ const previousEventRef = { current: null };
61
+ const eventRef = { current: { type: "" } };
62
+ const getEvent = () => ({
63
+ ...eventRef.current,
64
+ current() {
65
+ return eventRef.current;
66
+ },
67
+ previous() {
68
+ return previousEventRef.current;
69
+ },
70
+ });
71
+ const getState = () => ({
72
+ ...state,
73
+ hasTag(tag) {
74
+ const currentState = state.get();
75
+ return !!machine.states[currentState]?.tags?.includes(tag);
76
+ },
77
+ matches(...values) {
78
+ const currentState = state.get();
79
+ return values.includes(currentState);
80
+ },
81
+ });
82
+ const refs = useRefs(machine.refs?.({ prop, context: ctx }) ?? {});
83
+ const getParams = () => ({
84
+ state: getState(),
85
+ context: ctx,
86
+ event: getEvent(),
87
+ prop,
88
+ send,
89
+ action,
90
+ guard,
91
+ track,
92
+ refs,
93
+ computed,
94
+ flush,
95
+ scope,
96
+ choose,
97
+ });
98
+ const action = (keys) => {
99
+ const strs = isFunction(keys) ? keys(getParams()) : keys;
100
+ if (!strs)
101
+ return;
102
+ const fns = strs.map((s) => {
103
+ const fn = machine.implementations?.actions?.[s];
104
+ if (!fn)
105
+ warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`);
106
+ return fn;
107
+ });
108
+ for (const fn of fns) {
109
+ fn?.(getParams());
110
+ }
111
+ };
112
+ const guard = (str) => {
113
+ if (isFunction(str))
114
+ return str(getParams());
115
+ return machine.implementations?.guards?.[str](getParams());
116
+ };
117
+ const effect = (keys) => {
118
+ const strs = isFunction(keys) ? keys(getParams()) : keys;
119
+ if (!strs)
120
+ return;
121
+ const fns = strs.map((s) => {
122
+ const fn = machine.implementations?.effects?.[s];
123
+ if (!fn)
124
+ warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`);
125
+ return fn;
126
+ });
127
+ const cleanups = [];
128
+ for (const fn of fns) {
129
+ const cleanup = fn?.(getParams());
130
+ if (cleanup)
131
+ cleanups.push(cleanup);
132
+ }
133
+ return () => cleanups.forEach((fn) => fn?.());
134
+ };
135
+ const choose = (transitions) => {
136
+ return toArray(transitions).find((t) => {
137
+ let result = !t.guard;
138
+ if (isString(t.guard))
139
+ result = !!guard(t.guard);
140
+ else if (isFunction(t.guard))
141
+ result = t.guard(getParams());
142
+ return result;
143
+ });
144
+ };
145
+ const computed = (key) => {
146
+ ensure(machine.computed, () => `[zag-js] No computed object found on machine`);
147
+ const fn = machine.computed[key];
148
+ return fn({
149
+ context: ctx,
150
+ event: getEvent(),
151
+ prop,
152
+ refs,
153
+ scope,
154
+ computed: computed,
155
+ });
156
+ };
157
+ const state = bindable(() => ({
158
+ defaultValue: machine.initialState({ prop }),
159
+ onChange(nextState, prevState) {
160
+ // compute effects: exit -> transition -> enter
161
+ // exit effects
162
+ if (prevState) {
163
+ const exitEffects = effects.get(prevState);
164
+ exitEffects?.();
165
+ effects.delete(prevState);
166
+ }
167
+ // exit actions
168
+ if (prevState) {
169
+ action(machine.states[prevState]?.exit);
170
+ }
171
+ // transition actions
172
+ action(transitionRef.current?.actions);
173
+ // enter effect
174
+ const cleanup = effect(machine.states[nextState]?.effects);
175
+ if (cleanup)
176
+ effects.set(nextState, cleanup);
177
+ // root entry actions
178
+ if (prevState === INIT_STATE) {
179
+ action(machine.entry);
180
+ const cleanup = effect(machine.effects);
181
+ if (cleanup)
182
+ effects.set(INIT_STATE, cleanup);
183
+ }
184
+ // enter actions
185
+ action(machine.states[nextState]?.entry);
186
+ },
187
+ }));
188
+ let status = MachineStatus.NotStarted;
189
+ onMount(() => {
190
+ const started = status === MachineStatus.Started;
191
+ status = MachineStatus.Started;
192
+ debug(started ? "rehydrating..." : "initializing...");
193
+ state.invoke(state.initial, INIT_STATE);
194
+ });
195
+ onDestroy(() => {
196
+ debug("unmounting...");
197
+ status = MachineStatus.Stopped;
198
+ effects.forEach((fn) => fn?.());
199
+ effects = new Map();
200
+ transitionRef.current = null;
201
+ action(machine.exit);
202
+ });
203
+ const send = (event) => {
204
+ if (status !== MachineStatus.Started)
205
+ return;
206
+ previousEventRef.current = eventRef.current;
207
+ eventRef.current = event;
208
+ const currentState = state.get();
209
+ // @ts-ignore
210
+ const transitions = machine.states[currentState].on?.[event.type] ?? machine.on?.[event.type];
211
+ const transition = choose(transitions);
212
+ if (!transition)
213
+ return;
214
+ // save current transition
215
+ transitionRef.current = transition;
216
+ const target = transition.target ?? currentState;
217
+ debug("transition", event.type, transition.target || currentState, `(${transition.actions})`);
218
+ const changed = target !== currentState;
219
+ if (changed) {
220
+ // state change is high priority
221
+ state.set(target);
222
+ }
223
+ else if (transition.reenter && !changed) {
224
+ // reenter will re-invoke the current state
225
+ state.invoke(currentState, currentState);
226
+ }
227
+ else {
228
+ // call transition actions
229
+ action(transition.actions);
230
+ }
231
+ };
232
+ machine.watch?.(getParams());
233
+ return {
234
+ get state() {
235
+ return getState();
236
+ },
237
+ send,
238
+ context: ctx,
239
+ prop,
240
+ get scope() {
241
+ return scope;
242
+ },
243
+ refs,
244
+ computed,
245
+ get event() {
246
+ return getEvent();
247
+ },
248
+ getStatus: () => status,
249
+ };
250
+ }
251
+ function useProp(value) {
252
+ return function get(key) {
253
+ return value()[key];
254
+ };
255
+ }
256
+ function flush(fn) {
257
+ flushSync(() => {
258
+ queueMicrotask(() => fn());
259
+ });
260
+ }
@@ -0,0 +1 @@
1
+ export declare function mergeProps(...args: Record<string | symbol, any>[]): Record<string | symbol, any>;
@@ -0,0 +1,32 @@
1
+ import { mergeProps as zagMergeProps } from "@sprawlify/primitives/core";
2
+ import { toStyleString } from "./normalize-props";
3
+ const CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g;
4
+ const serialize = (style) => {
5
+ const res = {};
6
+ let match;
7
+ while ((match = CSS_REGEX.exec(style))) {
8
+ res[match[1]] = match[2];
9
+ }
10
+ return res;
11
+ };
12
+ export function mergeProps(...args) {
13
+ const classNames = [];
14
+ for (const props of args) {
15
+ if (!props)
16
+ continue;
17
+ if ("class" in props && props.class != null) {
18
+ classNames.push(props.class);
19
+ }
20
+ }
21
+ const merged = zagMergeProps(...args);
22
+ if (classNames.length > 0) {
23
+ merged.class = classNames.length === 1 ? classNames[0] : classNames;
24
+ }
25
+ if ("style" in merged) {
26
+ if (typeof merged.style === "string") {
27
+ merged.style = serialize(merged.style);
28
+ }
29
+ merged.style = toStyleString(merged.style);
30
+ }
31
+ return merged;
32
+ }
@@ -0,0 +1,7 @@
1
+ import type { SvelteHTMLElements, HTMLAttributes } from "svelte/elements";
2
+ export type PropTypes = SvelteHTMLElements & {
3
+ element: HTMLAttributes<HTMLElement>;
4
+ style?: HTMLAttributes<HTMLElement>["style"] | undefined;
5
+ };
6
+ export declare function toStyleString(style: Record<string, number | string>): string;
7
+ export declare const normalizeProps: import("@sprawlify/primitives/types").NormalizeProps<PropTypes>;
@@ -0,0 +1,43 @@
1
+ import { createNormalizer } from "@sprawlify/primitives/types";
2
+ const propMap = {
3
+ className: "class",
4
+ defaultChecked: "checked",
5
+ defaultValue: "value",
6
+ htmlFor: "for",
7
+ onBlur: "onfocusout",
8
+ onChange: "oninput",
9
+ onFocus: "onfocusin",
10
+ onDoubleClick: "ondblclick",
11
+ };
12
+ export function toStyleString(style) {
13
+ let string = "";
14
+ for (let key in style) {
15
+ const value = style[key];
16
+ if (value === null || value === undefined)
17
+ continue;
18
+ if (!key.startsWith("--"))
19
+ key = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
20
+ string += `${key}:${value};`;
21
+ }
22
+ return string;
23
+ }
24
+ const preserveKeys = new Set("viewBox,className,preserveAspectRatio,fillRule,clipPath,clipRule,strokeWidth,strokeLinecap,strokeLinejoin,strokeDasharray,strokeDashoffset,strokeMiterlimit".split(","));
25
+ function toSvelteProp(key) {
26
+ if (key in propMap)
27
+ return propMap[key];
28
+ if (preserveKeys.has(key))
29
+ return key;
30
+ return key.toLowerCase();
31
+ }
32
+ function toSveltePropValue(key, value) {
33
+ if (key === "style" && typeof value === "object")
34
+ return toStyleString(value);
35
+ return value;
36
+ }
37
+ export const normalizeProps = createNormalizer((props) => {
38
+ const normalized = {};
39
+ for (const key in props) {
40
+ normalized[toSvelteProp(key)] = toSveltePropValue(key, props[key]);
41
+ }
42
+ return normalized;
43
+ });
@@ -0,0 +1,9 @@
1
+ export interface PortalActionProps {
2
+ disabled?: boolean | undefined;
3
+ container?: HTMLElement | undefined;
4
+ getRootNode?: (() => ShadowRoot | Document | Node) | undefined;
5
+ }
6
+ export declare function portal(node: HTMLElement, props?: PortalActionProps): {
7
+ destroy: () => void;
8
+ update: (props?: PortalActionProps) => void;
9
+ };
@@ -0,0 +1,15 @@
1
+ export function portal(node, props = {}) {
2
+ function update(props = {}) {
3
+ const { container, disabled, getRootNode } = props;
4
+ if (disabled)
5
+ return;
6
+ const doc = getRootNode?.().ownerDocument ?? document;
7
+ const mountNode = container ?? doc.body;
8
+ mountNode.appendChild(node);
9
+ }
10
+ update(props);
11
+ return {
12
+ destroy: () => node.remove(),
13
+ update,
14
+ };
15
+ }
@@ -0,0 +1 @@
1
+ export declare function reflect<T extends Record<string, any>>(obj: () => T): T;
@@ -0,0 +1,11 @@
1
+ const isFunction = (value) => typeof value === "function";
2
+ export function reflect(obj) {
3
+ return new Proxy(obj(), {
4
+ get(_, prop) {
5
+ const target = obj();
6
+ const value = Reflect.get(target, prop);
7
+ // @ts-ignore
8
+ return isFunction(value) ? value.bind(target) : value;
9
+ },
10
+ });
11
+ }
@@ -0,0 +1,4 @@
1
+ export declare function useRefs<T>(refs: T): {
2
+ get<K extends keyof T>(key: K): T[K];
3
+ set<K extends keyof T>(key: K, value: T[K]): void;
4
+ };
@@ -0,0 +1,11 @@
1
+ export function useRefs(refs) {
2
+ const ref = { current: refs };
3
+ return {
4
+ get(key) {
5
+ return ref.current[key];
6
+ },
7
+ set(key, value) {
8
+ ref.current[key] = value;
9
+ },
10
+ };
11
+ }
@@ -0,0 +1 @@
1
+ export declare const track: (deps: any[], effect: VoidFunction) => void;
@@ -0,0 +1,28 @@
1
+ import { isEqual } from "@sprawlify/primitives/utils";
2
+ const access = (value) => {
3
+ if (typeof value === "function")
4
+ return value();
5
+ return value;
6
+ };
7
+ export const track = (deps, effect) => {
8
+ let prevDeps = [];
9
+ let isFirstRun = true;
10
+ $effect(() => {
11
+ if (isFirstRun) {
12
+ prevDeps = deps.map((d) => access(d));
13
+ isFirstRun = false;
14
+ return;
15
+ }
16
+ let changed = false;
17
+ for (let i = 0; i < deps.length; i++) {
18
+ if (!isEqual(prevDeps[i], access(deps[i]))) {
19
+ changed = true;
20
+ break;
21
+ }
22
+ }
23
+ if (changed) {
24
+ prevDeps = deps.map((d) => access(d));
25
+ effect();
26
+ }
27
+ });
28
+ };
@@ -0,0 +1 @@
1
+ export * from "./core";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./core";
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@sprawlify/svelte",
3
+ "version": "0.0.38",
4
+ "type": "module",
5
+ "description": "Svelte wrapper for primitives.",
6
+ "author": "sprawlify <npm@sprawlify.com>",
7
+ "svelte": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "svelte": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "@sprawlify/primitives": "0.0.38"
24
+ },
25
+ "peerDependencies": {
26
+ "svelte": "^5.0.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=24"
30
+ },
31
+ "license": "MIT",
32
+ "scripts": {
33
+ "build": "svelte-kit sync && svelte-package",
34
+ "typecheck": "svelte-check",
35
+ "lint": "eslint src --fix"
36
+ }
37
+ }