Skip to content

Commit 46fd1e9

Browse files
committed
refactor: number input
1 parent 54d025a commit 46fd1e9

File tree

12 files changed

+725
-668
lines changed

12 files changed

+725
-668
lines changed

MIGRATION.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Migration Guide
22

3+
- Initial and controlled values are now handled in the machine.
4+
5+
- `defaultOpen` and `open`
6+
- `defaultValue` and `value`
7+
38
- Pagination
49

510
- `api.setCount` is removed in favor of explicitly setting the `count` prop.
@@ -17,6 +22,7 @@
1722
longer returns a tuple of `[state, send]`.
1823

1924
- Removed `useActor` hook in favor of `useMachine` everywhere.
25+
- Removed `open.controlled` in favor of `defaultOpen` and `open` props.
2026

2127
## Avatar
2228

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import type { BaseSchema, GuardFn, MachineConfig } from "./types"
1+
import type { BaseSchema, GuardFn, MachineConfig, Params, Transition } from "./types"
22

33
export function createGuards<T extends BaseSchema>() {
44
return {
5-
and: (...guards: Array<GuardFn | T["guard"]>) => {
5+
and: (...guards: Array<GuardFn<T> | T["guard"]>) => {
66
return function andGuard(params: any) {
77
return guards.every((str) => params.guard(str))
88
}
99
},
10-
or: (...guards: Array<GuardFn | T["guard"]>) => {
10+
or: (...guards: Array<GuardFn<T> | T["guard"]>) => {
1111
return function orGuard(params: any) {
1212
return guards.some((str) => params.guard(str))
1313
}
1414
},
15-
not: (guard: GuardFn | T["guard"]) => {
15+
not: (guard: GuardFn<T> | T["guard"]) => {
1616
return function notGuard(params: any) {
1717
return !params.guard(guard)
1818
}
@@ -23,3 +23,17 @@ export function createGuards<T extends BaseSchema>() {
2323
export function createMachine<T extends BaseSchema>(config: MachineConfig<T>) {
2424
return config
2525
}
26+
27+
export function setup<T extends BaseSchema>() {
28+
return {
29+
guards: createGuards<T>(),
30+
createMachine: (config: MachineConfig<T>) => {
31+
return createMachine(config)
32+
},
33+
choose: (transitions: Transition<T> | Transition<T>[]) => {
34+
return function chooseFn({ choose }: Params<T>) {
35+
return choose(transitions)?.actions
36+
}
37+
},
38+
}
39+
}

packages/core/src/types.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ interface ContextParams<T extends Dict> {
1818
flush: (fn: VoidFunction) => void
1919
}
2020

21-
interface PropFn<T extends Dict> {
21+
export interface PropFn<T extends Dict> {
2222
<K extends keyof T["props"]>(key: K): T["props"][K]
2323
}
2424

25-
interface ComputedFn<T extends Dict> {
25+
export interface ComputedFn<T extends Dict> {
2626
<K extends keyof T["computed"]>(key: K): T["computed"][K]
2727
}
2828

@@ -86,7 +86,6 @@ export type EventObject = EventType<{ type: string }>
8686

8787
export interface Params<T extends Dict> {
8888
prop: PropFn<T>
89-
bindable: BindableFn
9089
action: (action: T["action"][]) => void
9190
context: BindableContext<T>
9291
refs: BindableRefs<T>
@@ -100,16 +99,24 @@ export interface Params<T extends Dict> {
10099
computed: ComputedFn<T>
101100
scope: Scope
102101
state: Bindable<T["state"]>
102+
choose: ChooseFn<T>
103+
guard: (key: T["guard"] | GuardFn<T>) => boolean | undefined
103104
}
104105

105-
export type GuardFn = (params: any) => boolean
106+
export type GuardFn<T extends Dict> = (params: Params<T>) => boolean
106107

107-
interface Transition<T extends Dict> {
108+
export interface Transition<T extends Dict> {
108109
target?: T["state"]
109110
actions?: T["action"][]
110-
guard?: T["guard"] | GuardFn
111+
guard?: T["guard"] | GuardFn<T>
111112
}
112113

114+
type MaybeArray<T> = T | T[]
115+
116+
export type ChooseFn<T extends Dict> = (
117+
transitions: MaybeArray<Omit<Transition<T>, "target">>,
118+
) => Transition<T> | undefined
119+
113120
interface PropsParams<T extends Dict> {
114121
props: Partial<T["props"]>
115122
scope: Scope
@@ -120,6 +127,10 @@ interface RefsParams<T extends Dict> {
120127
context: BindableContext<T>
121128
}
122129

130+
export type ActionsOrFn<T extends Dict> = T["action"][] | ((params: Params<T>) => T["action"][] | undefined)
131+
132+
export type EffectsOrFn<T extends Dict> = T["effect"][] | ((params: Params<T>) => T["effect"][] | undefined)
133+
123134
export interface MachineConfig<T extends Dict> {
124135
props?: (params: PropsParams<T>) => T["props"]
125136
context?: (params: ContextParams<T>) => {
@@ -129,9 +140,9 @@ export interface MachineConfig<T extends Dict> {
129140
[K in keyof T["computed"]]: (params: ComputedParams<T>) => T["computed"][K]
130141
}
131142
initialState: (params: { prop: PropFn<T> }) => T["state"]
132-
entry?: T["action"][]
133-
exit?: T["action"][]
134-
effects?: T["effect"][]
143+
entry?: ActionsOrFn<T>
144+
exit?: ActionsOrFn<T>
145+
effects?: EffectsOrFn<T>
135146
refs?: (params: RefsParams<T>) => T["refs"]
136147
dom?: (params: Params<T>) => T["dom"]
137148
watch?: (params: Params<T>) => void
@@ -141,9 +152,9 @@ export interface MachineConfig<T extends Dict> {
141152
states: {
142153
[K in T["state"]]: {
143154
tags?: T["tag"][]
144-
entry?: T["action"][]
145-
exit?: T["action"][]
146-
effects?: T["effect"][]
155+
entry?: ActionsOrFn<T>
156+
exit?: ActionsOrFn<T>
157+
effects?: EffectsOrFn<T>
147158
on?: {
148159
[E in T["event"]["type"]]?: Transition<T> | Array<Transition<T>>
149160
}

packages/frameworks/react/src/machine.ts

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
import type { BaseSchema, Bindable, BindableContext, BindableRefs, GuardFn, MachineConfig, Service } from "@zag-js/core"
1+
import type {
2+
ActionsOrFn,
3+
BaseSchema,
4+
Bindable,
5+
BindableContext,
6+
BindableRefs,
7+
ChooseFn,
8+
ComputedFn,
9+
EffectsOrFn,
10+
GuardFn,
11+
MachineConfig,
12+
Params,
13+
Service,
14+
} from "@zag-js/core"
215
import { createScope } from "@zag-js/core"
316
import { isFunction, isString, toArray, warn } from "@zag-js/utils"
417
import { useLayoutEffect, useMemo, useRef } from "react"
@@ -59,7 +72,7 @@ export function useMachine<T extends BaseSchema>(
5972

6073
const refs: BindableRefs<T> = useRefs(machine.refs?.({ prop, context: ctx }) ?? {})
6174

62-
const getParams = (): any => ({
75+
const getParams = (): Params<T> => ({
6376
state,
6477
context: ctx,
6578
event: {
@@ -76,9 +89,12 @@ export function useMachine<T extends BaseSchema>(
7689
computed,
7790
flush,
7891
scope,
92+
choose,
7993
})
8094

81-
const action = (strs: T["action"][]) => {
95+
const action = (keys: ActionsOrFn<T> | undefined) => {
96+
const strs = isFunction(keys) ? keys(getParams()) : keys
97+
if (!strs) return
8298
const fns = strs.map((s) => {
8399
const fn = machine.implementations?.actions?.[s]
84100
if (!fn) warn(`[zag-js] No implementation found for action "${JSON.stringify(s)}"`)
@@ -89,12 +105,14 @@ export function useMachine<T extends BaseSchema>(
89105
}
90106
}
91107

92-
const guard = (str: T["guard"] | GuardFn) => {
108+
const guard = (str: T["guard"] | GuardFn<T>) => {
93109
if (isFunction(str)) return str(getParams())
94110
return machine.implementations?.guards?.[str](getParams())
95111
}
96112

97-
const effect = (strs: T["effect"][]) => {
113+
const effect = (keys: EffectsOrFn<T> | undefined) => {
114+
const strs = isFunction(keys) ? keys(getParams()) : keys
115+
if (!strs) return
98116
const fns = strs.map((s) => {
99117
const fn = machine.implementations?.effects?.[s]
100118
if (!fn) warn(`[zag-js] No implementation found for effect "${JSON.stringify(s)}"`)
@@ -108,17 +126,28 @@ export function useMachine<T extends BaseSchema>(
108126
return () => cleanups.forEach((fn) => fn?.())
109127
}
110128

111-
const computed = (key: keyof T["computed"]) => {
112-
return machine.computed?.[key]({
113-
context: ctx as any,
114-
event: eventRef.current,
115-
prop,
116-
refs,
117-
scope,
118-
computed: computed as any,
129+
const choose: ChooseFn<T> = (transitions) => {
130+
return toArray(transitions).find((t) => {
131+
let result = !t.guard
132+
if (isString(t.guard)) result = !!guard(t.guard)
133+
else if (isFunction(t.guard)) result = t.guard(getParams())
134+
return result
119135
})
120136
}
121137

138+
const computed: ComputedFn<T> = (key) => {
139+
return (
140+
machine.computed?.[key]({
141+
context: ctx as any,
142+
event: eventRef.current,
143+
prop,
144+
refs,
145+
scope,
146+
computed: computed as any,
147+
}) ?? ({} as any)
148+
)
149+
}
150+
122151
const state = useBindable(() => ({
123152
defaultValue: machine.initialState({ prop }),
124153
onChange(nextState, prevState) {
@@ -134,27 +163,27 @@ export function useMachine<T extends BaseSchema>(
134163
// exit actions
135164
if (prevState) {
136165
// @ts-ignore
137-
action(machine.states[prevState]?.exit ?? [])
166+
action(machine.states[prevState]?.exit)
138167
}
139168

140169
// transition actions
141-
action(transitionRef.current?.actions ?? [])
170+
action(transitionRef.current?.actions)
142171

143172
// enter effect
144173
// @ts-ignore
145-
const cleanup = effect(machine.states[nextState]?.effects ?? [])
146-
effects.current.set(nextState as string, cleanup)
174+
const cleanup = effect(machine.states[nextState]?.effects)
175+
if (cleanup) effects.current.set(nextState as string, cleanup)
147176

148177
// root entry actions
149178
if (prevState === "__init__") {
150-
action(machine.entry ?? [])
151-
const cleanup = effect(machine.effects ?? [])
152-
effects.current.set("__init__", cleanup)
179+
action(machine.entry)
180+
const cleanup = effect(machine.effects)
181+
if (cleanup) effects.current.set("__init__", cleanup)
153182
}
154183

155184
// enter actions
156185
// @ts-ignore
157-
action(machine.states[nextState]?.entry ?? [])
186+
action(machine.states[nextState]?.entry)
158187
},
159188
}))
160189

@@ -165,7 +194,7 @@ export function useMachine<T extends BaseSchema>(
165194
fns.forEach((fn) => fn?.())
166195
effects.current = new Map()
167196
transitionRef.current = null
168-
action(machine.exit ?? [])
197+
action(machine.exit)
169198
}
170199
}, [])
171200

@@ -187,13 +216,7 @@ export function useMachine<T extends BaseSchema>(
187216
// @ts-ignore
188217
machine.on?.[event.type]
189218

190-
const transition = toArray(transitions).find((t) => {
191-
let result = !t.guard
192-
if (isString(t.guard)) result = !!guard(t.guard)
193-
else if (isFunction(t.guard)) result = t.guard(getParams())
194-
return result
195-
})
196-
219+
const transition = choose(transitions)
197220
if (!transition) return
198221

199222
// save current transition

packages/machines/number-input/src/cursor.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
/**
2-
* Credits: https://github.com/react-component/input-number/blob/master/src/hooks/useCursor.ts
3-
*/
4-
51
interface Selection {
62
start?: number | undefined
73
end?: number | undefined
@@ -10,8 +6,8 @@ interface Selection {
106
afterTxt?: string | undefined
117
}
128

13-
export function recordCursor(inputEl: HTMLInputElement): Selection | undefined {
14-
if (inputEl.ownerDocument.activeElement !== inputEl) return
9+
export function recordCursor(inputEl: HTMLInputElement | null): Selection | undefined {
10+
if (!inputEl || inputEl.ownerDocument.activeElement !== inputEl) return
1511
try {
1612
const { selectionStart: start, selectionEnd: end, value } = inputEl
1713
const beforeTxt = value.substring(0, start!)
@@ -26,8 +22,8 @@ export function recordCursor(inputEl: HTMLInputElement): Selection | undefined {
2622
} catch {}
2723
}
2824

29-
export function restoreCursor(inputEl: HTMLInputElement, selection: Selection | undefined) {
30-
if (inputEl.ownerDocument.activeElement !== inputEl) return
25+
export function restoreCursor(inputEl: HTMLInputElement | null, selection: Selection | undefined) {
26+
if (!inputEl || inputEl.ownerDocument.activeElement !== inputEl) return
3127

3228
if (!selection) {
3329
inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length)

packages/machines/number-input/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ export { anatomy } from "./number-input.anatomy"
22
export { connect } from "./number-input.connect"
33
export { machine } from "./number-input.machine"
44
export type {
5-
MachineApi as Api,
6-
UserDefinedContext as Context,
5+
NumberInputApi as Api,
6+
NumberInputProps as Props,
7+
NumberInputService as Service,
78
ElementIds,
89
FocusChangeDetails,
910
IntlTranslations,
10-
Service,
1111
ValueChangeDetails,
1212
ValueInvalidDetails,
1313
} from "./number-input.types"

0 commit comments

Comments
 (0)