zag-ripple 0.0.1

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.
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "zag-ripple",
3
+ "version": "0.0.1",
4
+ "description": "RippleJS Adapter for Zag JS",
5
+ "keywords": [
6
+ "js",
7
+ "machine",
8
+ "xstate",
9
+ "statechart",
10
+ "component",
11
+ "zag-js",
12
+ "RippleJS",
13
+ "ripple"
14
+ ],
15
+ "author": "Abraham Aremu <anubra266@gmail.com>",
16
+ "homepage": "https://github.com/anubra266/zag-ripple#readme",
17
+ "license": "MIT",
18
+ "main": "src/index.ripple",
19
+ "repository": "https://github.com/anubra266/zag-ripple/tree/main/packages/zag-ripple",
20
+ "sideEffects": false,
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/anubra266i/zag-ripple/issues"
26
+ },
27
+ "scripts": {
28
+ "test": "echo \"No test specified\""
29
+ },
30
+ "dependencies": {
31
+ "@zag-js/core": "^1.3.3",
32
+ "@zag-js/types": "latest",
33
+ "@zag-js/utils": "latest"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "~5.6.2"
37
+ }
38
+ }
@@ -0,0 +1,68 @@
1
+ import { identity, isFunction } from "@zag-js/utils"
2
+ import { track, effect, flushSync, untrack } from "ripple"
3
+
4
+ export function createBindable(props) {
5
+ const initial = props().defaultValue ?? props().value
6
+ const eq = props().isEqual ?? Object.is
7
+
8
+ let value = track(initial)
9
+ let controlled = track(() => props().value !== undefined)
10
+
11
+ let valueRef = { current: untrack(() => @value) }
12
+ let prevValue = { current: undefined }
13
+
14
+ effect(() => {
15
+ const v = @controlled ? props().value : @value
16
+ valueRef = { current: v }
17
+ prevValue = { current: v }
18
+ })
19
+
20
+ const setValueFn = (v) => {
21
+ const next = isFunction(v) ? v(valueRef.current) : v
22
+ const prev = prevValue.current
23
+ if (props().debug) {
24
+ console.log(`[bindable > ${props().debug}] setValue`, { next, prev })
25
+ }
26
+
27
+ if (!@controlled) {
28
+ @value = next;
29
+ }
30
+ if (!eq(next, prev)) {
31
+ props().onChange?.(next, prev)
32
+ }
33
+ }
34
+
35
+ function get(): T {
36
+ return @controlled ? props().value : @value
37
+ }
38
+
39
+ return {
40
+ initial,
41
+ ref: valueRef,
42
+ get,
43
+ set(val) {
44
+ const exec = props().sync ? flushSync : identity
45
+ exec(() => setValueFn(val))
46
+ },
47
+ invoke: (nextValue, prevValue) => {
48
+ props().onChange?.(nextValue, prevValue)
49
+ },
50
+ hash: (value) => {
51
+ return props().hash?.(value) ?? String(value)
52
+ },
53
+ }
54
+ }
55
+
56
+ createBindable.cleanup = (_fn: VoidFunction) => {
57
+ effect(() => fn, [])
58
+ }
59
+
60
+ createBindable.ref = (defaultValue: any) => {
61
+ let value = defaultValue
62
+ return {
63
+ get: () => value,
64
+ set: (next) => {
65
+ value = next
66
+ },
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ export { mergeProps } from "@zag-js/core"
2
+ export * from "./normalize-props.ripple"
3
+ export * from "./machine.ripple"
@@ -0,0 +1,293 @@
1
+ import type {
2
+ ActionsOrFn,
3
+ BindableContext,
4
+ ChooseFn,
5
+ ComputedFn,
6
+ EffectsOrFn,
7
+ GuardFn,
8
+ Machine,
9
+ MachineSchema,
10
+ Params,
11
+ Service,
12
+ } from "@zag-js/core"
13
+ import { createScope, INIT_STATE, MachineStatus } from "@zag-js/core"
14
+ import { compact, ensure, isFunction, isString, toArray, warn } from "@zag-js/utils"
15
+ import { track, effect, flushSync } from "ripple"
16
+ import { createBindable } from "./bindable.ripple"
17
+ import { createRefs } from "./refs.ripple"
18
+ import { createTrack } from "./track.ripple"
19
+
20
+ function access(userProps) {
21
+ if (isFunction(userProps)) return userProps()
22
+ return userProps
23
+ }
24
+
25
+ export function useMachine(
26
+ machine,
27
+ userProps = {},
28
+ ) {
29
+ let scope = track(() => {
30
+ const { id, ids, getRootNode } = access(userProps)
31
+ return createScope({ id, ids, getRootNode })
32
+ })
33
+
34
+ const debug = (...args: any[]) => {
35
+ if (machine.debug) console.log(...args)
36
+ }
37
+
38
+ let props = track(() =>
39
+ machine.props?.({ props: compact(access(userProps)), scope: @scope }) ?? access(userProps)
40
+ )
41
+
42
+ const prop = useProp(() => @props)
43
+
44
+ const context = machine.context?.({
45
+ prop,
46
+ bindable: createBindable,
47
+ get scope() {
48
+ return @scope
49
+ },
50
+ flush: flush,
51
+ getContext() {
52
+ return ctx
53
+ },
54
+ getComputed() {
55
+ return computed
56
+ },
57
+ getRefs() {
58
+ return refs
59
+ },
60
+ getEvent() {
61
+ return getEvent()
62
+ },
63
+ })
64
+
65
+ const ctx = {
66
+ get(key) {
67
+ return context?.[key]?.get()
68
+ },
69
+ set(key, value) {
70
+ context?.[key]?.set(value)
71
+ },
72
+ initial(key) {
73
+ return context?.[key]?.initial
74
+ },
75
+ hash(key) {
76
+ const current = context?.[key]?.get()
77
+ return context?.[key]?.hash(current)
78
+ },
79
+ }
80
+
81
+ let effects = new Map()
82
+ let transitionRef = { current: null }
83
+
84
+ let previousEventRef = { current: null }
85
+ let eventRef = { current: { type: "" } }
86
+
87
+ const getEvent = () => ({
88
+ ...eventRef.current,
89
+ current() {
90
+ return eventRef.current
91
+ },
92
+ previous() {
93
+ return previousEventRef.current
94
+ },
95
+ })
96
+
97
+ const getState = () => ({
98
+ ...state,
99
+ matches(...values) {
100
+ const current = state.get()
101
+ return values.includes(current)
102
+ },
103
+ hasTag(tag) {
104
+ const current = state.get()
105
+ return !!machine.states[current]?.tags?.includes(tag)
106
+ },
107
+ })
108
+
109
+ const refs = createRefs(machine.refs?.({ prop, context: ctx }) ?? {})
110
+
111
+ const getParams = () => ({
112
+ state: getState(),
113
+ context: ctx,
114
+ event: getEvent(),
115
+ prop,
116
+ send,
117
+ action,
118
+ guard,
119
+ track: createTrack,
120
+ refs,
121
+ computed,
122
+ flush,
123
+ scope: @scope,
124
+ choose,
125
+ })
126
+
127
+ const action = (keys) => {
128
+ const strs = isFunction(keys) ? keys(getParams()) : keys
129
+ if (!strs) return
130
+ const fns = strs.map((s) => {
131
+ const fn = machine.implementations?.actions?.[s]
132
+ if (!fn) warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`)
133
+ return fn
134
+ })
135
+ for (const fn of fns) {
136
+ fn?.(getParams())
137
+ }
138
+ }
139
+
140
+ const guard = (str) => {
141
+ if (isFunction(str)) return str(getParams())
142
+ return machine.implementations?.guards?.[str](getParams())
143
+ }
144
+
145
+ const effectFn = (keys) => {
146
+ const strs = isFunction(keys) ? keys(getParams()) : keys
147
+ if (!strs) return
148
+ const fns = strs.map((s) => {
149
+ const fn = machine.implementations?.effects?.[s]
150
+ if (!fn) warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`)
151
+ return fn
152
+ })
153
+ const cleanups = []
154
+ for (const fn of fns) {
155
+ const cleanup = fn?.(getParams())
156
+ if (cleanup) cleanups.push(cleanup)
157
+ }
158
+ return () => cleanups.forEach((fn) => fn?.())
159
+ }
160
+
161
+ const choose = (transitions) => {
162
+ return toArray(transitions).find((t) => {
163
+ let result = !t.guard
164
+ if (isString(t.guard)) result = !!guard(t.guard)
165
+ else if (isFunction(t.guard)) result = t.guard(getParams())
166
+ return result
167
+ })
168
+ }
169
+
170
+ const computed = (key) => {
171
+ ensure(machine.computed, () => `[zag-js] No computed object found on machine`)
172
+ const fn = machine.computed[key]
173
+ return fn({
174
+ context: ctx,
175
+ event: getEvent(),
176
+ prop,
177
+ refs,
178
+ scope: @scope,
179
+ computed: computed,
180
+ })
181
+ }
182
+
183
+ const state = createBindable(() => ({
184
+ defaultValue: machine.initialState({ prop }),
185
+ onChange(nextState: string, prevState: string | undefined) {
186
+ // compute effects: exit -> transition -> enter
187
+
188
+ // exit effects
189
+ if (prevState) {
190
+ const exitEffects = effects.get(prevState)
191
+ exitEffects?.()
192
+ effects.delete(prevState)
193
+ }
194
+
195
+ // exit actions
196
+ if (prevState && prevState !== INIT_STATE) {
197
+ action(machine.states[prevState]?.exit)
198
+ }
199
+
200
+ // transition actions
201
+ if (transitionRef.current?.actions) {
202
+ action(transitionRef.current.actions)
203
+ }
204
+
205
+ // enter effects
206
+ const cleanup = effectFn( machine.states[nextState]?.effects)
207
+ if (cleanup) effects.set(nextState, cleanup)
208
+
209
+ // root entry actions
210
+ if (prevState === INIT_STATE) {
211
+ action(machine.entry)
212
+ const cleanup = effectFn(machine.effects)
213
+ if (cleanup) effects.set(INIT_STATE, cleanup)
214
+ }
215
+
216
+ // enter actions
217
+ action(machine.states[nextState]?.entry)
218
+ },
219
+ }))
220
+
221
+ let status = MachineStatus.NotStarted
222
+
223
+ // Initialize machine immediately - RippleJS components run imperatively
224
+ status = MachineStatus.Started
225
+ debug("initializing...")
226
+ const initialState = machine.initialState({ prop })
227
+ state.set(initialState)
228
+
229
+ const send = (event) => {
230
+ if (status !== MachineStatus.Started) return
231
+
232
+ previousEventRef.current = eventRef.current
233
+ eventRef.current = event
234
+
235
+ let currentState = state.get()
236
+
237
+ const transitions =
238
+ machine.states[currentState]?.on?.[event.type] ??
239
+ machine.on?.[event.type]
240
+
241
+ const transition = choose(transitions)
242
+ if (!transition) return
243
+
244
+ // save current transition
245
+ transitionRef.current = transition
246
+ const target = transition.target ?? currentState
247
+
248
+ debug("transition", event.type, transition.target || currentState, `(${transition.actions})`)
249
+
250
+ const changed = target !== currentState
251
+ if (changed) {
252
+ state.set(target)
253
+ } else if (transition.reenter && !changed) {
254
+ // reenter will re-invoke the current state
255
+ state.invoke?.(currentState, currentState)
256
+ } else {
257
+ // call transition actions
258
+ action(transition.actions ?? [])
259
+ }
260
+ }
261
+
262
+ machine.watch?.(getParams())
263
+
264
+ return {
265
+ get state() {
266
+ return getState()
267
+ },
268
+ send,
269
+ context: ctx,
270
+ prop,
271
+ get scope() {
272
+ return @scope
273
+ },
274
+ refs,
275
+ computed,
276
+ get event() {
277
+ return getEvent()
278
+ },
279
+ getStatus: () => status,
280
+ }
281
+ }
282
+
283
+ function useProp(value) {
284
+ return function get(key) {
285
+ return value()[key]
286
+ }
287
+ }
288
+
289
+ function flush(fn: VoidFunction) {
290
+ flushSync(() => {
291
+ queueMicrotask(() => fn())
292
+ })
293
+ }
@@ -0,0 +1,6 @@
1
+ import { createNormalizer } from "@zag-js/types"
2
+
3
+
4
+
5
+ // Simple prop normalizer for RippleJS - just pass through
6
+ export const normalizeProps = createNormalizer((props) => props)
@@ -0,0 +1,13 @@
1
+ import { track } from "ripple";
2
+
3
+ export function createRefs(refs) {
4
+ const ref = { current: refs }
5
+ return {
6
+ get(key) {
7
+ return ref.current[key]
8
+ },
9
+ set(key, value) {
10
+ ref.current[key] = value
11
+ },
12
+ }
13
+ }
@@ -0,0 +1,31 @@
1
+ import { isEqual, isFunction } from "@zag-js/utils";
2
+ import { effect, track } from "ripple";
3
+
4
+ const access = (v) => {
5
+ if (isFunction(v)) return v()
6
+ return v;
7
+ };
8
+
9
+ export const createTrack = (deps: any[], effectCallback: VoidFunction ) => {
10
+ let prevDeps: any[] = [];
11
+ let isFirstRun = true;
12
+
13
+ effect(() => {
14
+ if (isFirstRun) {
15
+ prevDeps = deps.map(d => access(d));
16
+ isFirstRun = false;
17
+ return;
18
+ }
19
+ let changed = false;
20
+ for (let i = 0; i < deps.length; i++) {
21
+ if (!isEqual(prevDeps[i], access(deps[i]))) {
22
+ changed = true;
23
+ break;
24
+ }
25
+ }
26
+ if (changed) {
27
+ prevDeps = deps.map(d => access(d));
28
+ effectCallback();
29
+ }
30
+ });
31
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "node_modules/.cache/.tsbuildinfo"
5
+ }
6
+ }