[XState] Nested State

When dragging, we want to have two modes, one in 'normal' mode, another one is 'locked', we enter the locked mode by holde on 'shift' key. 

To achieve this, we need to use nested state:

复制代码
    dragging: {
      initial: "normal",
      states: {
        normal: {
          on: {
            "keydown.shift": "locked",
          },
        },
        locked: { 
          on: {
            // overwrite mousemove actions to move only
            // on x-axis
            mousemove: {
              actions: assignOnlyX,
            },
            "keyup.shift": {
              target: "normal",
            },
          },
        },
      },
复制代码
  • When entering the dragging, we want to use 'normal' as initial state.
  • Then we defined two nested state: 'normal' and 'locked'
  • When 'shift' key is down, we go from 'normal' to 'locked', when its up, we back from 'locked' to 'normal'.
  • We also need to overwirte mousemove state's action, to only move the box in x-axis.

Full code:

复制代码
import { createMachine, assign, interpret } from "xstate";

const elBox = document.querySelector("#box");
const elBody = document.body;

const assignPoint = assign({
  px: (context, event) => event.clientX,
  py: (context, event) => event.clientY,
});

const assignPosition = assign({
  x: (context, event) => {
    return context.x + context.dx;
  },
  y: (context, event) => {
    return context.y + context.dy;
  },
  dx: 0,
  dy: 0,
  px: 0,
  py: 0,
});

const assignDelta = assign({
  dx: (context, event) => {
    return event.clientX - context.px;
  },
  dy: (context, event) => {
    return event.clientY - context.py;
  },
});

const resetPosition = assign({
  dx: 0,
  dy: 0,
  px: 0,
  py: 0,
});

const assignOnlyX = assign({
  dx: (context, event) => {
    return event.clientX - context.px;
  },
});

const dragDropMachine = createMachine({
  initial: "idle",
  context: {
    x: 0,
    y: 0,
    dx: 0,
    dy: 0,
    px: 0,
    py: 0,
  },
  states: {
    idle: {
      on: {
        mousedown: {
          actions: assignPoint,
          target: "dragging", // dragging.locked
        },
      },
    },
    dragging: {
      initial: "normal",
      states: {
        normal: {
          on: {
            "keydown.shift": "locked",
          },
        },
        locked: { 
          on: {
            // overwrite mousemove actions to move only
            // on x-axis
            mousemove: {
              actions: assignOnlyX,
            },
            "keyup.shift": {
              target: "normal",
            },
          },
        },
      },
      on: {
        mousemove: {
          actions: assignDelta,
          internal: false,
        },
        mouseup: {
          actions: [assignPosition],
          target: "idle",
        },
        "keyup.escape": {
          target: "idle",
          actions: resetPosition,
        },
      },
    },
  },
});

const service = interpret(dragDropMachine);

service.onTransition((state) => {
  elBox.dataset.state = state.toStrings().join(" ");

  if (state.changed) {
    elBox.style.setProperty("--dx", state.context.dx);
    elBox.style.setProperty("--dy", state.context.dy);
    elBox.style.setProperty("--x", state.context.x);
    elBox.style.setProperty("--y", state.context.y);
  }
});

service.start();

elBox.addEventListener("mousedown", (event) => {
  service.send(event);
});

elBody.addEventListener("mousemove", (event) => {
  service.send(event);
});

elBody.addEventListener("mouseup", (event) => {
  service.send(event);
});

elBody.addEventListener("keyup", (e) => {
  if (e.key === "Escape") {
    service.send("keyup.escape");
  }

  if (e.key === "Shift") {
    service.send("keyup.shift");
  }
});

elBody.addEventListener("keydown", (e) => {
  if (e.key === "Shift") {
    console.log("shift is down");
    service.send("keydown.shift");
  }
});
复制代码

 

posted @   Zhentiw  阅读(187)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2018-07-31 [Angular] New in V6.1
2018-07-31 [Javascript] JavaScript赋值时的传值与传址
2017-07-31 [React Intl] Use Webpack to Conditionally Include an Intl Polyfill for Older Browsers
2017-07-31 [React Intl] Use a react-intl Higher Order Component to format messages
2017-07-31 [React Intl] Get locale value from intl injector
2017-07-31 [React Intl] Render Content Based on a Number using react-intl FormattedMessage (plural)
2014-07-31 [Node.js]32. Level 7: Working with Lists -- Redis
点击右上角即可分享
微信分享提示