[State Machine] Zag-js
import { createMachine } from "@zag-js/core";
type MachineState = {
value: "idle" | "focused";
};
type MachineContext = {
value: string[];
focusedIndex: number;
name?: string;
readonly isCompleted: boolean;
onCompleted?: (value: string[]) => void;
};
export type MachineOptions = {
value?: string[];
onCompleted?: (value: string[]) => void;
numOfFields: number;
name?: string;
};
function getNextValue(eventValue: string, currentValue: string) {
let nextValue = eventValue;
if (currentValue[0] === eventValue[0]) {
// "2", "29" => "9"
nextValue = eventValue[1];
} else if (currentValue[0] === eventValue[1]) {
// "2", "92" => "9"
nextValue = eventValue[0];
}
// "2", "22" => "2"
return nextValue;
}
export function machine(options: MachineOptions) {
const { numOfFields, name, ...restOptions } = options;
return createMachine<MachineContext, MachineState>(
{
id: "pin-input",
initial: "idle",
context: {
name: name ?? "pin-input",
value: Array.from<string>({ length: numOfFields }).fill(""),
focusedIndex: -1,
...restOptions,
},
computed: {
isCompleted(context) {
return context.value.every((val) => val !== "");
},
},
watch: {
focusedIndex: ["executeFocus"],
isCompleted: ["invokeOnCompleted"],
},
states: {
idle: {
on: {
FOCUS: {
target: "focused",
actions: ["setFocusedIndex"],
},
LABEL_CLICK: {
actions: ["focusFirstInput"],
},
},
},
focused: {
on: {
BLUR: {
target: "idle",
actions: ["clearFocusedIndex"],
},
INPUT: {
actions: ["setFocusedValue", "focusNextInput"],
},
BACKSPACE: {
actions: ["clearFocusedValue", "focusPreviousInput"],
},
PASTE: {
actions: ["setPastedValue", "focusLastEmptyInput"],
},
},
},
},
},
{
actions: {
setFocusedIndex(context, event) {
context.focusedIndex = event.index;
},
clearFocusedValue(context) {
context.value[context.focusedIndex] = "";
},
clearFocusedIndex(context, event) {
context.focusedIndex = -1;
},
setFocusedValue(context, event) {
// should only allow latest input value
const nextValue = getNextValue(
event.value,
context.value[context.focusedIndex]
);
context.value[context.focusedIndex] = nextValue;
},
setPastedValue(context, event) {
const pastedValue: string[] = event.value
.split("")
.slice(0, context.value.length);
pastedValue.forEach((value, index) => {
context.value[index] = value;
});
},
focusFirstInput(context) {
context.focusedIndex = 0;
},
focusNextInput(context, event) {
const nextIndex = Math.min(event.index + 1, context.value.length - 1);
context.focusedIndex = nextIndex;
},
focusPreviousInput(context, event) {
const prevIndex = Math.max(context.focusedIndex - 1, 0);
context.focusedIndex = prevIndex;
},
focusLastEmptyInput(context) {
const index = context.value.findIndex((value) => value === "");
const lastIndex = context.value.length - 1;
context.focusedIndex = index === -1 ? lastIndex : index;
},
executeFocus(context) {
const inpugGroup = document.querySelector("[data-part=input-group]");
if (!inpugGroup || context.focusedIndex < 0) return;
const inputElements = Array.from<HTMLInputElement>(
document.querySelectorAll("[data-part=input]")
);
const input = inputElements[context.focusedIndex];
// avoid mutli dom mutation problem
// schedule for next event
requestAnimationFrame(() => {
input?.focus();
});
},
invokeOnCompleted(context) {
if (!context.isCompleted) return;
context.onCompleted?.(Array.from(context.value));
},
},
}
);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2020-02-19 【逻辑思维】排中律:生存还是毁灭,只能选一个
2020-02-19 【逻辑思维】矛盾律:谁给理发师理发
2020-02-19 [AST Babel] Create a simple babel plugin
2020-02-19 [Angular 8 Unit testing] Testing a smart component with service injection -- 1
2019-02-19 [React] Ensure all React useEffect Effects Run Synchronously in Tests with react-testing-library
2019-02-19 [Functional Programming] Write a simple version of Maybe
2019-02-19 [JS Compose] 3. Use chain for composable error handling with nested Eithers (flatMap)