From c1434951fb581a69b578fddac2fd6bdd17ed956c Mon Sep 17 00:00:00 2001 From: Taylor Lodge Date: Wed, 10 Jan 2024 11:22:54 +1300 Subject: [PATCH] feat(core): xstate v5 support Updates xstate-tree to be compatible with xstate v5 Closes #58 BREAKING CHANGE: v1 style builders (buildView etc) have been removed, along with a handful of testing utilities and a behavioural change with children and final states. They will no longer be automatically from views and instead must be manually stopped using `stopChild` --- .github/workflows/xstate-tree.yml | 2 - examples/todomvc/App.tsx | 155 ++--- examples/todomvc/App.typegen.ts | 74 +-- examples/todomvc/Todo.tsx | 229 ++++--- examples/todomvc/Todo.typegen.ts | 88 ++- package-lock.json | 618 ++++++++++++++---- package.json | 13 +- src/builders.tsx | 248 +------ src/index.ts | 7 +- src/lazy.spec.tsx | 108 +-- src/lazy.tsx | 80 +-- .../handleLocationChange.ts | 2 +- src/routing/providers.tsx | 5 +- src/test-app/AppMachine.tsx | 69 +- src/test-app/OtherMachine.tsx | 12 +- src/test-app/TodoMachine.tsx | 57 +- src/test-app/TodosMachine.tsx | 245 ++++--- .../itWorksWithoutRouting.integration.tsx | 4 +- .../tests/removingChildActor.integration.tsx | 2 +- src/testingUtilities.tsx | 183 +----- src/tests/actionsGetUpdatedSelectors.spec.tsx | 56 +- src/tests/asyncRouteRedirects.spec.tsx | 27 +- src/types.ts | 161 ++--- src/useService.ts | 100 +-- src/utils.ts | 31 +- src/xstateTree.spec.tsx | 170 ++--- src/xstateTree.tsx | 228 +++---- tsconfig.json | 3 +- xstate-tree.api.md | 137 +--- 29 files changed, 1387 insertions(+), 1727 deletions(-) diff --git a/.github/workflows/xstate-tree.yml b/.github/workflows/xstate-tree.yml index d33e4ac..92e8fc6 100644 --- a/.github/workflows/xstate-tree.yml +++ b/.github/workflows/xstate-tree.yml @@ -14,8 +14,6 @@ jobs: run: npm run lint - name: Run Tests run: npm run test - - name: Test examples - run: npm run test-examples - name: Build run: npm run build - name: API Extractor diff --git a/examples/todomvc/App.tsx b/examples/todomvc/App.tsx index e304cb1..f2ac1a3 100644 --- a/examples/todomvc/App.tsx +++ b/examples/todomvc/App.tsx @@ -5,10 +5,15 @@ import { RoutingEvent, createXStateTreeMachine, } from "@koordinates/xstate-tree"; -import { assign } from "@xstate/immer"; import React from "react"; import { map } from "rxjs/operators"; -import { createMachine, type ActorRefFrom, spawn } from "xstate"; +import { + type ActorRefFrom, + setup, + assign, + assertEvent, + fromEventObservable, +} from "xstate"; import { TodoMachine } from "./Todo"; import { todos$, type Todo } from "./models"; @@ -16,96 +21,92 @@ import { activeTodos, allTodos, completedTodos } from "./routes"; type Context = { todos: Todo[]; - actors: Record>; + actors: ActorRefFrom[]; filter: "all" | "active" | "completed"; }; type Events = | { type: "SYNC_TODOS"; todos: Todo[] } - | RoutingEvent - | RoutingEvent - | RoutingEvent; + | RoutingEvent; const TodosSlot = multiSlot("Todos"); const machine = /** @xstate-layout N4IgpgJg5mDOIC5QBcD2FUFoCGAHXAdAMaoB2EArkWgE4DEiouqsAlsq2YyAB6ICMAFgBMBAJwTBANgkBWAOwAOWQGYpwgDQgAngMEEp-RYNn8FABmHmxMlQF87WtBhz46AZQCaAOQDCAfQAVAHkAEWD3bmY2Di4kXkRMeSkCEWTzRUUbYUV5c1ktXQRMflKCMyUxQXk1Y2FShyd0LDxcDwAJYIB1fwBBX0CASQA1AFEgsIiolnZOUm4+BBUagitzFWFZasVzczMpQsThFeVFFV35fjzLQUaQZxa3d06e3oAZN4nwyPjo2bjQIt+FJFKsxNYxLIbLJNoIxIpDsUVFDUsJBGcVGIrPxjrdHPdmq42s9uv5fMEALIABTeo0Co1CXymvxmsXm8UWJWsBB2ljUVThe2EBx0iWRYlR6JUmOxuIc+NI6Dg3AeROIZEo1FQNGmMTmC0SslBVRqIJE-HywsEgkRVwIlWqan40tM-DuqtaBEVgWa8BZeoBCQQV1EUhUFSyjrNNtFwZs4kjpysKkUwjU7sJnoAFthYD6MH6mKz9RzDeYCDCwxGTfzbfH4VUk+tU+n8R78Lr-uzAYkLeW0lIMll1Ll8ojMGiUhGzkIU2JWw4gA */ - createMachine( - { - context: { todos: [], actors: {}, filter: "all" }, - tsTypes: {} as import("./App.typegen").Typegen0, - schema: { context: {} as Context, events: {} as Events }, - predictableActionArguments: true, - invoke: { - src: "syncTodos", - id: "syncTodos", - }, - id: "todo-app", - initial: "conductor", - on: { - SYNC_TODOS: { - actions: "syncTodos", - target: ".conductor", - }, - SHOW_ACTIVE_TODOS: { - actions: "setFilter", - }, - SHOW_ALL_TODOS: { - actions: "setFilter", + setup({ + types: { context: {} as Context, events: {} as Events }, + actions: { + syncTodos: assign({ + todos: ({ event: e }) => { + assertEvent(e, "SYNC_TODOS"); + + return e.todos; }, - SHOW_COMPLETED_TODOS: { - actions: "setFilter", + actors: ({ event, spawn }) => { + assertEvent(event, "SYNC_TODOS"); + + return event.todos.map((todo) => + spawn("TodoMachine", { input: todo, id: TodosSlot.getId(todo.id) }) + ); }, + }), + setFilter: assign({ + filter: ({ event: e }) => + e.type === "SHOW_ACTIVE_TODOS" + ? "active" + : e.type === "SHOW_COMPLETED_TODOS" + ? "completed" + : "all", + }), + }, + guards: { + hasTodos: ({ context }) => context.todos.length > 0, + }, + actors: { + TodoMachine, + syncTodos: fromEventObservable(() => { + return todos$.pipe( + map((todos): Events => ({ type: "SYNC_TODOS", todos })) + ); + }), + }, + }).createMachine({ + context: { todos: [], actors: [], filter: "all" }, + invoke: { + src: "syncTodos", + id: "syncTodos", + }, + id: "todo-app", + initial: "conductor", + on: { + SYNC_TODOS: { + actions: "syncTodos", + target: ".conductor", }, - states: { - conductor: { - always: [ - { - cond: "hasTodos", - target: "hasTodos", - }, - { - target: "noTodos", - }, - ], - }, - noTodos: {}, - hasTodos: {}, + SHOW_ACTIVE_TODOS: { + actions: "setFilter", }, - }, - { - actions: { - syncTodos: assign((ctx, e) => { - ctx.todos = e.todos; - - ctx.todos.forEach((todo) => { - if (!ctx.actors[todo.id]) { - ctx.actors[todo.id] = spawn( - TodoMachine.withContext({ ...TodoMachine.context, todo }), - TodosSlot.getId(todo.id) - ); - } - }); - }), - setFilter: assign((ctx, e) => { - ctx.filter = - e.type === "SHOW_ACTIVE_TODOS" - ? "active" - : e.type === "SHOW_COMPLETED_TODOS" - ? "completed" - : "all"; - }), + SHOW_ALL_TODOS: { + actions: "setFilter", }, - guards: { - hasTodos: (ctx) => ctx.todos.length > 0, + SHOW_COMPLETED_TODOS: { + actions: "setFilter", }, - services: { - syncTodos: () => { - return todos$.pipe( - map((todos): Events => ({ type: "SYNC_TODOS", todos })) - ); - }, + }, + states: { + conductor: { + always: [ + { + guard: "hasTodos", + target: "hasTodos", + }, + { + target: "noTodos", + }, + ], }, - } - ); + noTodos: {}, + hasTodos: {}, + }, + }); export const TodoApp = createXStateTreeMachine(machine, { selectors({ ctx, inState }) { diff --git a/examples/todomvc/App.typegen.ts b/examples/todomvc/App.typegen.ts index ee65043..b1f162f 100644 --- a/examples/todomvc/App.typegen.ts +++ b/examples/todomvc/App.typegen.ts @@ -1,40 +1,36 @@ -// This file was automatically generated. Edits will be overwritten -export interface Typegen0 { - "@@xstate/typegen": true; - internalEvents: { - "": { type: "" }; - "done.invoke.syncTodos": { - type: "done.invoke.syncTodos"; - data: unknown; - __tip: "See the XState TS docs to learn how to strongly type this."; - }; - "error.platform.syncTodos": { - type: "error.platform.syncTodos"; - data: unknown; - }; - "xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - syncTodos: "done.invoke.syncTodos"; - }; - missingImplementations: { - actions: never; - services: never; - guards: never; - delays: never; - }; - eventsCausingActions: { - setFilter: "SHOW_ACTIVE_TODOS" | "SHOW_ALL_TODOS" | "SHOW_COMPLETED_TODOS"; - syncTodos: "SYNC_TODOS"; - }; - eventsCausingServices: { - syncTodos: "xstate.init"; - }; - eventsCausingGuards: { - hasTodos: ""; - }; - eventsCausingDelays: {}; - matchesStates: "conductor" | "hasTodos" | "noTodos"; - tags: never; -} + // This file was automatically generated. Edits will be overwritten + + export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + "done.invoke.syncTodos": { type: "done.invoke.syncTodos"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." }; +"error.platform.syncTodos": { type: "error.platform.syncTodos"; data: unknown }; +"xstate.init": { type: "xstate.init" }; + }; + invokeSrcNameMap: { + "syncTodos": "done.invoke.syncTodos"; + }; + missingImplementations: { + actions: "setFilter" | "syncTodos"; + delays: never; + guards: never; + services: "syncTodos"; + }; + eventsCausingActions: { + "setFilter": "SHOW_ACTIVE_TODOS" | "SHOW_ALL_TODOS" | "SHOW_COMPLETED_TODOS"; +"syncTodos": "SYNC_TODOS"; + }; + eventsCausingDelays: { + + }; + eventsCausingGuards: { + + }; + eventsCausingServices: { + "syncTodos": "xstate.init"; + }; + matchesStates: "conductor" | "hasTodos" | "noTodos"; + tags: never; + } + \ No newline at end of file diff --git a/examples/todomvc/Todo.tsx b/examples/todomvc/Todo.tsx index 7f76f16..daed94f 100644 --- a/examples/todomvc/Todo.tsx +++ b/examples/todomvc/Todo.tsx @@ -4,14 +4,28 @@ import { RoutingEvent, type PickEvent, } from "@koordinates/xstate-tree"; -import { assign } from "@xstate/immer"; import React from "react"; import { map, filter } from "rxjs/operators"; -import { createMachine } from "xstate"; +import { assertEvent, assign, fromEventObservable, setup } from "xstate"; + +import { assert } from "../../src/utils"; import { Todo, todos$ } from "./models"; import { activeTodos, allTodos, completedTodos } from "./routes"; +const syncTodo$ = fromEventObservable(({ input }: { input: string }) => + todos$.pipe( + map( + (todos) => + ({ + type: "SYNC_TODO", + todo: todos.find((todo) => todo.id === input)!, + } as const) + ), + filter((e) => e.todo !== undefined) + ) +); + type Context = { todo: Todo; editedText: string }; type Events = | { type: "EDIT" } @@ -25,126 +39,126 @@ type Events = const machine = /** @xstate-layout N4IgpgJg5mDOIC5QBcD2FUDoAWBLCEYAdgMQDKAEgPIDqA+gIIAyTdAKlQCJVmKgAOqWLmS5URPiAAeiABwBmACyZFigIwAmNQFYNATgDsANkPaANCACeibQAZZmWRtu3tag2r0aji2-IC+-hZoGDj4hKSUtIwAwmwAkgBqAKLsXDySgsKi4pIyCCbKXmqysmqKGooG2vLyFtYIBgYOGna2Ggbyrs46gcHoWHgExOTU9DFUALIACkzJbMmcady8SCBZImISa-lG8mqYGkeybraKJ54G9YhKRn0gIVgAbrjCAEYANmCYL2AA7iRFvE2JkhJtcjtECU9CoFPJtHo9p49O1rghtEYNIdPDo9PITh1DPdHj9XrhPt9ICISBxuHQgQtOKDsls8lDSipyvI9G4jC49IiNGimrZDqY1EjSp1FMSBqT3l9MFTkIDOMC6GQAKoAIUmwMZzPB21A+XOWL58g03J5LiM5isNnOmBc5Q0siMzVkKLuQQecpeCspEGpDLoMQoDAAcgBxRaGnLG6SIc4HWTGIx7DO2TS2HxovZYvFKI57M48vSy0IB8lfUbRBhxJKpWkZNYbBNsxpeRxp5wotPuNRqfNaTBKaoVeR8moCyvPMkUuvjKazeaLZatgRgjuQrvyTB2eQedSKPRpj3C1SHDMGdSyXOtIyyOfkACakZiG-jrN3dhh8J0c4zkULo3DRTQXxbOhOGSOYDTbbcfxNGwrgdBBbkg9IwxXOD1xiOYGAAJTjBCWQhZD0VQhojgMTABQFAc7FPJQZXuIh0DgSQSSGCJv3IpMEAFIwxxA294WqSonzRfEsQlD0j1qL1dCaOd5Rrb5fj+PjE3yAxRX2ExnCMHRbwUIxhXxa8PQ9bRmk0U9VOrCklWDZBtM7CoYXogU-ExNNswsg4XBdLxb1aWdfRJJyvnc3dFGEgyvFzEzzinNEqmEl0KlvTxT20bRVMIL5kEgWKKOLFQH0MsKET0NFvG0Oj6OMVRWg8AJIoGMqBKOMcJSS4zbNS8y0O7bzqjTeL3U6QJAiAA */ - createMachine( - { - context: { editedText: "" } as Context, - tsTypes: {} as import("./Todo.typegen").Typegen0, - schema: { context: {} as Context, events: {} as Events }, - predictableActionArguments: true, - invoke: { - src: "syncTodo", - id: "syncTodo", - }, - id: "todo", - on: { - SYNC_TODO: { - actions: "syncTodo", + setup({ + types: { context: {} as Context, events: {} as Events, input: {} as Todo }, + actions: { + syncTodo: assign({ + todo: ({ event: e }) => { + assertEvent(e, "SYNC_TODO"); + + return e.todo; }, - TODO_DELETED: { - cond: "isThisTodo", - target: ".deleted", + }), + submitTodo: ({ context: ctx }) => { + broadcast({ + type: "TODO_EDITED", + id: ctx.todo.id, + text: ctx.editedText, + }); + }, + setEditedText: assign({ + editedText: ({ context: ctx }) => { + return ctx.todo.text; }, - TODO_COMPLETED_CLEARED: { - cond: "isCompleted", - target: ".deleted", + }), + updateEditedText: assign({ + editedText: ({ event: e }) => { + assertEvent(e, "EDIT_CHANGED"); + return e.text; }, + }), + }, + guards: { + isThisTodo: ({ context: ctx, event: e }) => { + assert("id" in e); + return ctx.todo.id === e.id; }, - initial: "visible", - states: { - hidden: { - on: { - SHOW_ALL_TODOS: { - target: "visible", - }, - SHOW_ACTIVE_TODOS: { - cond: "isNotCompleted", - target: "visible", - }, - SHOW_COMPLETED_TODOS: { - cond: "isCompleted", - target: "visible", - }, + isNotCompleted: ({ context: ctx }) => !ctx.todo.completed, + isCompleted: ({ context: ctx }) => ctx.todo.completed, + }, + actors: { + syncTodo: syncTodo$, + }, + }).createMachine({ + context: ({ input }) => ({ editedText: "", todo: input }), + invoke: { + src: "syncTodo", + input: ({ context }) => context.todo.id, + }, + id: "todo", + on: { + SYNC_TODO: { + actions: "syncTodo", + }, + TODO_DELETED: { + guard: "isThisTodo", + target: ".deleted", + }, + TODO_COMPLETED_CLEARED: { + guard: "isCompleted", + target: ".deleted", + }, + }, + initial: "visible", + states: { + hidden: { + on: { + SHOW_ALL_TODOS: { + target: "visible", + }, + SHOW_ACTIVE_TODOS: { + guard: "isNotCompleted", + target: "visible", + }, + SHOW_COMPLETED_TODOS: { + guard: "isCompleted", + target: "visible", }, }, - visible: { - initial: "view", - states: { - view: { - on: { - EDIT: { - cond: "isNotCompleted", - target: "edit", - }, - }, - }, - edit: { - entry: "setEditedText", - on: { - TODO_EDITED: { - cond: "isThisTodo", - target: "view", - }, - EDIT_SUBMITTED: { - actions: "submitTodo", - }, - EDIT_CHANGED: { - actions: "updateEditedText", - }, + }, + visible: { + initial: "view", + states: { + view: { + on: { + EDIT: { + guard: "isNotCompleted", + target: "edit", }, }, }, - on: { - SHOW_ACTIVE_TODOS: { - cond: "isCompleted", - target: "hidden", - }, - SHOW_COMPLETED_TODOS: { - cond: "isNotCompleted", - target: "hidden", + edit: { + entry: "setEditedText", + on: { + TODO_EDITED: { + guard: "isThisTodo", + target: "view", + }, + EDIT_SUBMITTED: { + actions: "submitTodo", + }, + EDIT_CHANGED: { + actions: "updateEditedText", + }, }, }, }, - deleted: { - type: "final", - }, - }, - }, - { - actions: { - syncTodo: assign((ctx, e) => { - ctx.todo = e.todo; - }), - submitTodo: (ctx) => { - broadcast({ - type: "TODO_EDITED", - id: ctx.todo.id, - text: ctx.editedText, - }); + on: { + SHOW_ACTIVE_TODOS: { + guard: "isCompleted", + target: "hidden", + }, + SHOW_COMPLETED_TODOS: { + guard: "isNotCompleted", + target: "hidden", + }, }, - setEditedText: assign((ctx) => { - ctx.editedText = ctx.todo.text; - }), - updateEditedText: assign((ctx, e) => { - ctx.editedText = e.text; - }), }, - guards: { - isThisTodo: (ctx, e) => ctx.todo.id === e.id, - isNotCompleted: (ctx) => !ctx.todo.completed, - isCompleted: (ctx) => ctx.todo.completed, + deleted: { + type: "final", }, - services: { - syncTodo: (ctx) => - todos$.pipe( - map((todos) => ({ - type: "SYNC_TODO", - todo: todos.find((todo) => todo.id === ctx.todo.id), - })), - filter((e) => e.todo !== undefined) - ), - }, - } - ); + }, + }); export const TodoMachine = createXStateTreeMachine(machine, { selectors({ ctx, inState }) { @@ -186,6 +200,7 @@ export const TodoMachine = createXStateTreeMachine(machine, { selectors: { completed, editedText, text, editing, viewing }, actions, }) { + console.log("From Todo view"); return (
  • = 16.8.0 < 19.0.0", - "xstate": ">= 4.20 < 5.0.0", + "xstate": "^5.x", "zod": "^3.x" } }, @@ -951,6 +950,19 @@ "node": ">=8" } }, + "node_modules/@commitlint/load/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@commitlint/message": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-17.0.0.tgz", @@ -1221,6 +1233,42 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -1903,6 +1951,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@microsoft/tsdoc": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz", @@ -3128,6 +3189,12 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -3156,18 +3223,19 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", - "integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.30.5", - "@typescript-eslint/type-utils": "5.30.5", - "@typescript-eslint/utils": "5.30.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "regexpp": "^3.2.0", + "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, @@ -3188,6 +3256,65 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -3375,6 +3502,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.5.tgz", "integrity": "sha512-NJ6F+YHHFT/30isRe2UTmIGGAiXKckCyMnIV58cE3JkHmaD6e5zyEYm5hBDv0Wbin+IC0T1FWJpD3YqHUG/Ydg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/types": "5.30.5", "@typescript-eslint/visitor-keys": "5.30.5" @@ -3388,12 +3516,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz", - "integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.30.5", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3413,11 +3542,81 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/types": { "version": "5.30.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.5.tgz", "integrity": "sha512-kZ80w/M2AvsbRvOr3PjaNh6qEW1LFqs2pLdo2s5R38B2HYXG8Z0PP48/4+j1QHJFL3ssHIbJ4odPRS8PlHrFfw==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -3493,17 +3692,19 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.5.tgz", - "integrity": "sha512-o4SSUH9IkuA7AYIfAvatldovurqTAHrfzPApOZvdUq01hHojZojCFXx06D/aFpKCgWbMPRdJBWAC3sWp3itwTA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.30.5", - "@typescript-eslint/types": "5.30.5", - "@typescript-eslint/typescript-estree": "5.30.5", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "semver": "^7.3.7" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3516,14 +3717,44 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.5.tgz", - "integrity": "sha512-qGTc7QZC801kbYjAr4AgdOfnokpwStqyhSbiQvqGBLixniAKyH+ib2qXIVo4P9NgGzwyfD9I0nlJN7D91E1VpQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.30.5", - "@typescript-eslint/visitor-keys": "5.30.5", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3543,22 +3774,33 @@ } } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "eslint": ">=5" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@typescript-eslint/visitor-keys": { @@ -3566,6 +3808,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.5.tgz", "integrity": "sha512-D+xtGo9HUMELzWIUqcQc0p2PO4NyvTrgIOK/VnSH083+8sq0tiLozNRKuLarwHYGRuA6TVBQSuuLwJUDWd3aaA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/types": "5.30.5", "eslint-visitor-keys": "^3.3.0" @@ -3583,6 +3826,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3608,34 +3852,20 @@ "vite": "^3.0.0" } }, - "node_modules/@xstate/immer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@xstate/immer/-/immer-0.3.1.tgz", - "integrity": "sha512-YE+KY08IjEEmXo6XKKpeSGW4j9LfcXw+5JVixLLUO3fWQ3M95joWJ40VtGzx0w0zQSzoCNk8NgfvwWBGSbIaTA==", - "dev": true, - "peerDependencies": { - "immer": "^9.0.6", - "xstate": "^4.29.0" - } - }, "node_modules/@xstate/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@xstate/react/-/react-3.0.0.tgz", - "integrity": "sha512-KHSCfwtb8gZ7QH2luihvmKYI+0lcdHQOmGNRUxUEs4zVgaJCyd8csCEmwPsudpliLdUmyxX2pzUBojFkINpotw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@xstate/react/-/react-4.0.2.tgz", + "integrity": "sha512-t5Xau/H8nS9nlowV+72RneqaQ/wqkQ8C/8ij4gEvHcoCQptc2REgZkjOpV74xyTabUZnUeYwdDTsMmz5ot3S+Q==", "dev": true, "dependencies": { - "use-isomorphic-layout-effect": "^1.0.0", + "use-isomorphic-layout-effect": "^1.1.2", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { - "@xstate/fsm": "^2.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "xstate": "^4.31.0" + "xstate": "^5.4.1" }, "peerDependenciesMeta": { - "@xstate/fsm": { - "optional": true - }, "xstate": { "optional": true } @@ -6948,6 +7178,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -9915,6 +10151,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -17925,16 +18167,16 @@ } }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/uglify-js": { @@ -18384,9 +18626,9 @@ "dev": true }, "node_modules/xstate": { - "version": "4.33.6", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.33.6.tgz", - "integrity": "sha512-A5R4fsVKADWogK2a43ssu8Fz1AF077SfrKP1ZNyDBD8lNa/l4zfR//Luofp5GSWehOQr36Jp0k2z7b+sH2ivyg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.4.1.tgz", + "integrity": "sha512-1DDT047hsTCSgm3BwLyb4sXQDmTdbLutc4ni0MLACgd2yX6vij35pnNNf1F4U6kQ8njx6NxQ6wpLjlh1lqs0NQ==", "dev": true, "funding": { "type": "opencollective", @@ -19172,6 +19414,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true } } }, @@ -19373,6 +19621,29 @@ "dev": true, "optional": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -19937,6 +20208,12 @@ "requires": { "path-parse": "^1.0.6" } + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true } } }, @@ -20944,6 +21221,12 @@ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", "dev": true }, + "@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -20972,22 +21255,55 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", - "integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.30.5", - "@typescript-eslint/type-utils": "5.30.5", - "@typescript-eslint/utils": "5.30.5", + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "regexpp": "^3.2.0", + "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -21100,27 +21416,69 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.5.tgz", "integrity": "sha512-NJ6F+YHHFT/30isRe2UTmIGGAiXKckCyMnIV58cE3JkHmaD6e5zyEYm5hBDv0Wbin+IC0T1FWJpD3YqHUG/Ydg==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/types": "5.30.5", "@typescript-eslint/visitor-keys": "5.30.5" } }, "@typescript-eslint/type-utils": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz", - "integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.30.5", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } } }, "@typescript-eslint/types": { "version": "5.30.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.5.tgz", "integrity": "sha512-kZ80w/M2AvsbRvOr3PjaNh6qEW1LFqs2pLdo2s5R38B2HYXG8Z0PP48/4+j1QHJFL3ssHIbJ4odPRS8PlHrFfw==", - "dev": true + "dev": true, + "peer": true }, "@typescript-eslint/typescript-estree": { "version": "5.20.0", @@ -21162,27 +21520,45 @@ } }, "@typescript-eslint/utils": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.5.tgz", - "integrity": "sha512-o4SSUH9IkuA7AYIfAvatldovurqTAHrfzPApOZvdUq01hHojZojCFXx06D/aFpKCgWbMPRdJBWAC3sWp3itwTA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, "requires": { + "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.30.5", - "@typescript-eslint/types": "5.30.5", - "@typescript-eslint/typescript-estree": "5.30.5", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" + "semver": "^7.3.7" }, "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true + }, "@typescript-eslint/typescript-estree": { - "version": "5.30.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.5.tgz", - "integrity": "sha512-qGTc7QZC801kbYjAr4AgdOfnokpwStqyhSbiQvqGBLixniAKyH+ib2qXIVo4P9NgGzwyfD9I0nlJN7D91E1VpQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.30.5", - "@typescript-eslint/visitor-keys": "5.30.5", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21190,14 +21566,21 @@ "tsutils": "^3.21.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, "requires": { - "eslint-visitor-keys": "^2.0.0" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true } } }, @@ -21206,6 +21589,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.5.tgz", "integrity": "sha512-D+xtGo9HUMELzWIUqcQc0p2PO4NyvTrgIOK/VnSH083+8sq0tiLozNRKuLarwHYGRuA6TVBQSuuLwJUDWd3aaA==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/types": "5.30.5", "eslint-visitor-keys": "^3.3.0" @@ -21215,7 +21599,8 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true + "dev": true, + "peer": true } } }, @@ -21234,20 +21619,13 @@ "react-refresh": "^0.14.0" } }, - "@xstate/immer": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@xstate/immer/-/immer-0.3.1.tgz", - "integrity": "sha512-YE+KY08IjEEmXo6XKKpeSGW4j9LfcXw+5JVixLLUO3fWQ3M95joWJ40VtGzx0w0zQSzoCNk8NgfvwWBGSbIaTA==", - "dev": true, - "requires": {} - }, "@xstate/react": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@xstate/react/-/react-3.0.0.tgz", - "integrity": "sha512-KHSCfwtb8gZ7QH2luihvmKYI+0lcdHQOmGNRUxUEs4zVgaJCyd8csCEmwPsudpliLdUmyxX2pzUBojFkINpotw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@xstate/react/-/react-4.0.2.tgz", + "integrity": "sha512-t5Xau/H8nS9nlowV+72RneqaQ/wqkQ8C/8ij4gEvHcoCQptc2REgZkjOpV74xyTabUZnUeYwdDTsMmz5ot3S+Q==", "dev": true, "requires": { - "use-isomorphic-layout-effect": "^1.0.0", + "use-isomorphic-layout-effect": "^1.1.2", "use-sync-external-store": "^1.0.0" } }, @@ -23693,6 +24071,12 @@ "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", @@ -25988,6 +26372,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -31779,9 +32169,9 @@ "dev": true }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true }, "uglify-js": { @@ -32097,9 +32487,9 @@ "dev": true }, "xstate": { - "version": "4.33.6", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.33.6.tgz", - "integrity": "sha512-A5R4fsVKADWogK2a43ssu8Fz1AF077SfrKP1ZNyDBD8lNa/l4zfR//Luofp5GSWehOQr36Jp0k2z7b+sH2ivyg==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.4.1.tgz", + "integrity": "sha512-1DDT047hsTCSgm3BwLyb4sXQDmTdbLutc4ni0MLACgd2yX6vij35pnNNf1F4U6kQ8njx6NxQ6wpLjlh1lqs0NQ==", "dev": true }, "xtend": { diff --git a/package.json b/package.json index 07cd412..6d17312 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,9 @@ "@types/react": "^17.0.29", "@types/react-dom": "^18.0.6", "@types/testing-library__jest-dom": "^5.14.1", - "@typescript-eslint/eslint-plugin": "^5.30.5", + "@typescript-eslint/eslint-plugin": "^5.57.0", "@vitejs/plugin-react": "^2.1.0", - "@xstate/immer": "^0.3.1", - "@xstate/react": "^3.0.0", + "@xstate/react": "^4.0.2", "classnames": "^2.3.1", "cz-conventional-changelog": "^3.3.0", "eslint": "^7.32.0", @@ -66,15 +65,15 @@ "todomvc-app-css": "^2.4.2", "todomvc-common": "^1.0.5", "ts-jest": "^28.0.5", - "typescript": "^4.7.3", + "typescript": "5.0.2", "vite": "^3.1.3", "vite-tsconfig-paths": "^3.5.0", - "xstate": "^4.33.0" + "xstate": "^5.4.1" }, "peerDependencies": { - "@xstate/react": "^3.x", + "@xstate/react": "^4.x", "react": ">= 16.8.0 < 19.0.0", - "xstate": ">= 4.20 < 5.0.0", + "xstate": "^5.x", "zod": "^3.x" }, "scripts": { diff --git a/src/builders.tsx b/src/builders.tsx index 7ecdb9d..68a828b 100644 --- a/src/builders.tsx +++ b/src/builders.tsx @@ -1,212 +1,20 @@ import React from "react"; import { - type EventObject, - type StateMachine, + setup, type AnyStateMachine, type ContextFrom, - type EventFrom, - type InterpreterFrom, - type AnyFunction, + AnyStateNodeConfig, createMachine, - StateNodeConfig, } from "xstate"; import { AnyRoute } from "./routing"; import { Slot, singleSlot } from "./slots"; import { - AnyActions, - AnySelector, - CanHandleEvent, - MatchesFrom, - OutputFromSelector, - V1Selectors as LegacySelectors, - V2BuilderMeta, - ViewProps, - XStateTreeMachineMetaV1, - XstateTreeMachineStateSchemaV1, - XstateTreeMachineStateSchemaV2, AnyXstateTreeMachine, + V2BuilderMeta, + XstateTreeMachine, } from "./types"; -/** - * @public - * - * Factory function for selectors. The selectors function is passed three arguments: - * - `ctx` - the current context of the machines state - * - `canHandleEvent` - a function that can be used to determine if the machine can handle a - * given event, by simulating sending the event and seeing if a stat change would happen. - * Handles guards - * - `inState` - equivalent to xstates `state.matches`, allows checking if the machine is in a given state - * - * The resulting selector function has memoization. It will return the same value until the - * machine's state changes or the machine's context changes - * @param machine - The machine to create the selectors for - * @param selectors - The selector function - * @returns The selectors - ready to be passed to {@link buildActions} - * @deprecated use {@link createXStateTreeMachine} instead - */ -export function buildSelectors< - TMachine extends AnyStateMachine, - TSelectors, - TContext = ContextFrom ->( - __machine: TMachine, - selectors: ( - ctx: TContext, - canHandleEvent: CanHandleEvent, - inState: MatchesFrom, - __currentState: never - ) => TSelectors -): LegacySelectors< - TContext, - EventFrom, - TSelectors, - MatchesFrom -> { - let lastState: never | undefined = undefined; - let lastCachedResult: TSelectors | undefined = undefined; - let lastCtxRef: TContext | undefined = undefined; - - return ( - ctx: TContext, - canHandleEvent: CanHandleEvent, - inState: MatchesFrom, - currentState - ) => { - // Handles caching to ensure stable references to selector results - // Only re-run the selector if - // * The reference to the context object has changed (the context object should never be mutated) - // * The last state we ran the selectors in has changed. This is to ensure `canHandleEvent` and `inState` calls aren't stale - if ( - lastCtxRef === ctx && - lastState === currentState && - lastCachedResult !== undefined - ) { - return lastCachedResult; - } else { - const result = selectors(ctx, canHandleEvent, inState, currentState); - lastCtxRef = ctx; - lastCachedResult = result; - lastState = currentState; - - return result; - } - }; -} - -/** - * @public - * - * Factory function for actions. The actions function is passed two arguments: - * - `send` - the interpreters send function, which can be used to send events to the machine - * - `selectors` - the output of the selectors function from {@link buildSelectors} - * - * The resulting action function will only be called once per invocation of a machine. - * The selectors are passed in as a proxy to always read the latest selector value - * - * @param machine - The machine to create the actions for - * @param selectors - The selectors function - * @param actions - The action function - * @returns The actions function - ready to be passed to {@link buildView} - * @deprecated use {@link createXStateTreeMachine} instead - * */ -export function buildActions< - TMachine extends AnyStateMachine, - TActions, - TSelectors, - TSend = InterpreterFrom["send"] ->( - __machine: TMachine, - __selectors: TSelectors, - actions: (send: TSend, selectors: OutputFromSelector) => TActions -): (send: TSend, selectors: OutputFromSelector) => TActions { - return actions; -} - -/** - * @public - * - * Factory function for views. The view is passed four props: - * - `slots` - the slots object, which can be used to render the children of the view invoked by the machine - * - `actions` - the output of the actions function from {@link buildActions} - * - `selectors` - the output of the selectors function from {@link buildSelectors} - * - `inState` - equivalent to xstates `state.matches`, allows checking if the machine is in a given state - * - * The resulting view is wrapped in React.memo, it will re-render when the actions or selectors reference changes - * - * @param machine - The machine to create the view for - * @param selectors - The selectors function from {@link buildSelectors} - * @param actions - The actions function from {@link buildActions} - * @param slots - The array of slots that can be rendered by the view - * @param view - The view function - * @returns The React view - * @deprecated use {@link createXStateTreeMachine} instead - */ -export function buildView< - TMachine extends AnyStateMachine, - TEvent extends EventObject, - TActions, - TSelectors extends AnySelector, - TSlots extends readonly Slot[] = [], - TMatches extends AnyFunction = MatchesFrom, - TViewProps = ViewProps< - OutputFromSelector, - TActions, - TSlots, - TMatches - >, - TSend = (send: TEvent) => void ->( - __machine: TMachine, - __selectors: TSelectors, - __actions: ( - send: TSend, - selectors: OutputFromSelector - ) => TActions, - __slots: TSlots, - view: React.ComponentType -): React.ComponentType { - return React.memo(view) as unknown as React.ComponentType; -} - -/** - * @public - * - * staples xstate machine and xstate-tree metadata together into an xstate-tree machine - * - * @param machine - The machine to staple the selectors/actions/slots/view to - * @param metadata - The xstate-tree metadata to staple to the machine - * @returns The xstate-tree machine, ready to be invoked by other xstate-machines or used with `buildRootComponent` - * @deprecated use {@link createXStateTreeMachine} instead - */ -export function buildXStateTreeMachine< - TMachine extends AnyStateMachine, - TSelectors extends AnySelector, - TActions extends AnyActions ->( - machine: TMachine, - meta: XStateTreeMachineMetaV1 -): StateMachine< - ContextFrom, - XstateTreeMachineStateSchemaV1, - EventFrom, - any, - any, - any, - any -> { - const copiedMeta = { ...meta }; - copiedMeta.xstateTreeMachine = true; - machine.config.meta = { - ...machine.config.meta, - ...copiedMeta, - builderVersion: 1, - }; - machine.meta = { ...machine.meta, ...copiedMeta, builderVersion: 1 }; - - return machine; -} - /** * @public * Creates an xstate-tree machine from an xstate-machine @@ -229,41 +37,19 @@ export function createXStateTreeMachine< >( machine: TMachine, options: V2BuilderMeta -): StateMachine< - ContextFrom, - XstateTreeMachineStateSchemaV2< - TMachine, - TSelectorsOutput, - TActionsOutput, - TSlots - >, - EventFrom, - any, - any, - any, - any -> { +): XstateTreeMachine { const selectors = options.selectors ?? (({ ctx }) => ctx); const actions = options.actions ?? (() => ({})); - const xstateTreeMeta = { - selectors, - actions, - View: options.View, - slots: options.slots ?? [], - }; - machine.meta = { - ...machine.meta, - ...xstateTreeMeta, - builderVersion: 2, - }; - machine.config.meta = { - ...machine.config.meta, - ...xstateTreeMeta, - builderVersion: 2, + const machineWithMeta = machine as unknown as XstateTreeMachine; + machineWithMeta._xstateTree = { + selectors: selectors as any, + actions: actions as any, + View: options.View as any, + slots: (options.slots ?? []) as any, }; - return machine; + return machineWithMeta; } /** @@ -310,14 +96,12 @@ export function buildRoutingMachine( ...acc, [event]: { invoke: { - src: (_ctx, e) => { - return mappings[e.type as TRoutes[number]["event"]]; - }, + src: event, id: contentSlot.getId(), }, }, }; - }, {} as Record>); + }, {} as Record); const mappingsToEvents = Object.keys(mappings).reduce( (acc, event) => ({ @@ -328,7 +112,9 @@ export function buildRoutingMachine( }), {} ); - const machine = createMachine({ + const machine = setup({ + actors: mappings, + }).createMachine({ on: { ...mappingsToEvents, }, diff --git a/src/index.ts b/src/index.ts index 8d71f1d..a2fb714 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,12 +2,7 @@ export * from "./builders"; export * from "./slots"; export { broadcast, buildRootComponent, onBroadcast } from "./xstateTree"; export * from "./types"; -export { - buildTestRootComponent, - buildViewProps, - genericSlotsTestingDummy, - slotTestingDummyFactory, -} from "./testingUtilities"; +export { genericSlotsTestingDummy } from "./testingUtilities"; export { Link, type RoutingEvent, diff --git a/src/lazy.spec.tsx b/src/lazy.spec.tsx index 8a4bb77..e06fabc 100644 --- a/src/lazy.spec.tsx +++ b/src/lazy.spec.tsx @@ -3,12 +3,7 @@ import React from "react"; import { createMachine } from "xstate"; import "@testing-library/jest-dom"; -import { - buildActions, - buildSelectors, - buildView, - buildXStateTreeMachine, -} from "./builders"; +import { createXStateTreeMachine } from "./builders"; import { lazy } from "./lazy"; import { singleSlot } from "./slots"; import { buildRootComponent } from "./xstateTree"; @@ -18,7 +13,7 @@ describe("lazy", () => { const promiseFactory = () => new Promise(() => void 0); const lazyMachine = lazy(promiseFactory); - expect(lazyMachine.meta.xstateTreeMachine).toBe(true); + expect(lazyMachine._xstateTree).toBeDefined(); }); it("renders null by default when loading", () => { @@ -53,16 +48,10 @@ describe("lazy", () => { idle: {}, }, }); - const lazySelectors = buildSelectors(machine, (ctx) => ctx); - const lazyActions = buildActions(machine, lazySelectors, () => ({})); - const lazyView = buildView(machine, lazySelectors, lazyActions, [], () => { - return

    loaded

    ; - }); - const lazyMachine = buildXStateTreeMachine(machine, { - actions: lazyActions, - selectors: lazySelectors, - slots: [], - view: lazyView, + const lazyMachine = createXStateTreeMachine(machine, { + View() { + return

    loaded

    ; + }, }); const lazyMachinePromise = lazy(() => { return new Promise((res) => { @@ -75,37 +64,24 @@ describe("lazy", () => { const lazyMachineSlot = singleSlot("lazy"); const rootMachine = createMachine({ initial: "idle", + id: "lazy-root", states: { idle: { invoke: { id: lazyMachineSlot.getId(), - src: () => { - console.log("root"); - return lazyMachinePromise; - }, + src: lazyMachinePromise, }, }, }, }); const slots = [lazyMachineSlot]; - const selectors = buildSelectors(rootMachine, (ctx) => ctx); - const actions = buildActions(rootMachine, selectors, () => ({})); - const view = buildView( - rootMachine, - selectors, - actions, - slots, - ({ slots }) => { - return ; - } - ); const Root = buildRootComponent( - buildXStateTreeMachine(rootMachine, { - actions, - selectors, + createXStateTreeMachine(rootMachine, { slots, - view, + View({ slots }) { + return ; + }, }) ); @@ -115,87 +91,65 @@ describe("lazy", () => { }); it("invokes the xstate-tree machine returned by the promise with the context specified in withContext", async () => { - const machine = createMachine<{ foo: string; baz: string }>({ + const machine = createMachine({ + types: { + context: {} as { foo: string; baz: string }, + input: {} as { foo: string }, + }, id: "lazy-test", initial: "idle", - context: { - foo: "bar", + context: ({ input }) => ({ + foo: input.foo, baz: "floople", - }, + }), states: { idle: {}, }, }); - const lazySelectors = buildSelectors(machine, (ctx) => ctx); - const lazyActions = buildActions(machine, lazySelectors, () => ({})); - const lazyView = buildView( - machine, - lazySelectors, - lazyActions, - [], - ({ selectors }) => { + const lazyMachine = createXStateTreeMachine(machine, { + View: ({ selectors }) => { return (

    {selectors.foo} {selectors.baz}

    ); - } - ); - const lazyMachine = buildXStateTreeMachine(machine, { - actions: lazyActions, - selectors: lazySelectors, - slots: [], - view: lazyView, + }, }); const lazyMachinePromise = lazy( () => { - return new Promise((res) => { + return new Promise((res) => { setTimeout(() => { res(lazyMachine); }); }); }, { - withContext: () => ({ - foo: "qux", - }), + input: { foo: "qux" }, } ); const lazyMachineSlot = singleSlot("lazy"); const rootMachine = createMachine({ initial: "idle", + id: "lazy-root", states: { idle: { invoke: { id: lazyMachineSlot.getId(), - src: () => { - return lazyMachinePromise; - }, + src: lazyMachinePromise, }, }, }, }); const slots = [lazyMachineSlot]; - const selectors = buildSelectors(rootMachine, (ctx) => ctx); - const actions = buildActions(rootMachine, selectors, () => ({})); - const view = buildView( - rootMachine, - selectors, - actions, - slots, - ({ slots }) => { - return ; - } - ); const Root = buildRootComponent( - buildXStateTreeMachine(rootMachine, { - actions, - selectors, + createXStateTreeMachine(rootMachine, { slots, - view, + View({ slots }) { + return ; + }, }) ); diff --git a/src/lazy.tsx b/src/lazy.tsx index a9d8db1..bf4b5d5 100644 --- a/src/lazy.tsx +++ b/src/lazy.tsx @@ -1,26 +1,11 @@ import React from "react"; -import { - AnyStateMachine, - createMachine, - DoneEvent, - StateMachine, -} from "xstate"; +import { AnyStateMachine, assign, setup, fromPromise, InputFrom } from "xstate"; -import { - buildActions, - buildSelectors, - buildView, - buildXStateTreeMachine, -} from "./builders"; +import { createXStateTreeMachine } from "./builders"; import { singleSlot } from "./slots"; +import { AnyXstateTreeMachine } from "./types"; -type Context = {}; -type Events = any; -type States = - | { value: "loading"; context: Context } - | { value: "rendering"; context: Context }; - -type Options = { +type Options = { /** * Displayed while the promise is resolving, defaults to returning null */ @@ -29,7 +14,7 @@ type Options = { * Allows you to specify an overriden context when the machine is invoked * Automatically supplies the machines default context so only requires a partial of overrides */ - withContext?: () => Partial; + input?: InputFrom; }; /** * @public @@ -41,55 +26,46 @@ type Options = { * @param options - configure loading component and context to invoke machine with * @returns an xstate-tree machine that wraps the promise, invoking the resulting machine when it resolves */ -export function lazy( +export function lazy( factory: () => Promise, - { - Loader = () => null, - withContext = () => ({}), - }: Options = {} -): StateMachine { + { Loader = () => null, input }: Options = {} +): AnyXstateTreeMachine { const loadedMachineSlot = singleSlot("loadedMachine"); const slots = [loadedMachineSlot]; - const machine = createMachine({ + const machine = setup({}).createMachine({ initial: "loading", + context: {}, states: { loading: { invoke: { - src: () => factory, - onDone: "rendering", - }, - }, - rendering: { - invoke: { - id: loadedMachineSlot.getId(), - src: (_ctx, e: DoneEvent) => { - return e.data.withContext({ ...e.data.context, ...withContext() }); + src: fromPromise(factory), + onDone: { + target: "rendering", + actions: assign({ + loadedMachine: ({ spawn, event }) => + spawn(event.output, { + id: loadedMachineSlot.getId(), + input, + }), + }), }, }, }, + rendering: {}, }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, (_send, _selectors) => {}); - const view = buildView( - machine, - selectors, - actions, + return createXStateTreeMachine(machine, { slots, - ({ slots, inState }) => { - if (inState("loading")) { + selectors({ inState }) { + return { loading: inState("loading") }; + }, + View({ selectors, slots }) { + if (selectors.loading) { return ; } return ; - } - ); - - return buildXStateTreeMachine(machine, { - actions, - selectors, - slots, - view, + }, }); } diff --git a/src/routing/handleLocationChange/handleLocationChange.ts b/src/routing/handleLocationChange/handleLocationChange.ts index 2bdc0d8..c4ce21b 100644 --- a/src/routing/handleLocationChange/handleLocationChange.ts +++ b/src/routing/handleLocationChange/handleLocationChange.ts @@ -20,7 +20,7 @@ export function handleLocationChange( path: string, search: string, meta?: Record -): { events: RoutingEvent[]; matchedRoute: AnyRoute } | undefined { +): { events: RoutingEvent[]; matchedRoute: AnyRoute } | undefined { console.debug("[xstate-tree] Matching routes", basePath, path, search, meta); const match = matchRoute(routes, basePath, path, search); console.debug("[xstate-tree] Match result", match); diff --git a/src/routing/providers.tsx b/src/routing/providers.tsx index 92666f4..59c626d 100644 --- a/src/routing/providers.tsx +++ b/src/routing/providers.tsx @@ -1,10 +1,11 @@ import React from "react"; import { createContext, MutableRefObject, useContext } from "react"; +import { AnyRoute } from "./createRoute"; import { RoutingEvent } from "./routingEvent"; type Context = { - activeRouteEvents?: MutableRefObject[]>; + activeRouteEvents?: MutableRefObject[]>; }; export const RoutingContext = createContext(undefined); @@ -57,7 +58,7 @@ export function TestRoutingContext({ activeRouteEvents, children, }: { - activeRouteEvents: RoutingEvent[]; + activeRouteEvents: RoutingEvent[]; children: React.ReactNode; }) { return ( diff --git a/src/test-app/AppMachine.tsx b/src/test-app/AppMachine.tsx index e244284..e63dacb 100644 --- a/src/test-app/AppMachine.tsx +++ b/src/test-app/AppMachine.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { createMachine } from "xstate"; +import { setup } from "xstate"; import { buildRootComponent, @@ -22,44 +22,43 @@ const OtherMachine = () => import("./OtherMachine").then(({ OtherMachine }) => OtherMachine); const AppMachine = /** @xstate-layout N4IgpgJg5mDOIC5QEMAOqDEBxA8gfQAkcBZAUUVFQHtYBLAF1qoDsKQAPRAFgCYAaEAE9EPLgEYAdAE4ZUgBxcAzFxkBWGQDYAvloFpMuPAGVSAFVMBJAHJYjbanUYs2nBLwHCEY1QHZpsnxV1KUV1HV0QZioIODZ9CQB3ZAZaZigAMSoAJwAlKgBXejB7GhTnJA5uP1VFKQAGDVUNRTrWuQ9EOSl-GTlAn00B7Qj4+miaEscmVgrXHh4fDoRRVR6ZBrqZH3U5HT10CSp6AAswLKMAYyywMBnKUqc7yuWFpbEeDTXGppkxMTlhvtUJMyk9XP8lsMdEA */ - createMachine( - { - tsTypes: {} as import("./AppMachine.typegen").Typegen0, - schema: { context: {} as Context, events: {} as Events }, - id: "app", - initial: "waitingForRoute", - on: { - GO_HOME: { - cond: (_ctx, e) => e.meta.indexEvent ?? false, - target: ".todos", - }, - GO_SETTINGS: { - target: ".otherScreen", - }, + setup({ + types: { + context: {} as Context, + events: {} as Events, + }, + actors: { + TodosMachine: TodosMachine, + OtherMachine: lazy(OtherMachine), + }, + }).createMachine({ + id: "app", + initial: "waitingForRoute", + on: { + GO_HOME: { + guard: ({ event: e }) => e.meta.indexEvent ?? false, + target: ".todos", }, - states: { - waitingForRoute: {}, - todos: { - invoke: { - src: "TodosMachine", - id: ScreenSlot.getId(), - }, + GO_SETTINGS: { + target: ".otherScreen", + }, + }, + states: { + waitingForRoute: {}, + todos: { + invoke: { + src: "TodosMachine", + id: ScreenSlot.getId(), }, - otherScreen: { - invoke: { - src: "OtherMachine", - id: ScreenSlot.getId(), - }, + }, + otherScreen: { + invoke: { + src: "OtherMachine", + id: ScreenSlot.getId(), }, }, }, - { - services: { - TodosMachine: TodosMachine, - OtherMachine: lazy(OtherMachine), - }, - } - ); + }); export const BuiltAppMachine = createXStateTreeMachine(AppMachine, { slots, @@ -97,7 +96,7 @@ export const BuiltAppMachine = createXStateTreeMachine(AppMachine, { }, }); -export const App = buildRootComponent(BuiltAppMachine as any, { +export const App = buildRootComponent(BuiltAppMachine, { history, basePath: "", routes: [homeRoute, settingsRoute], diff --git a/src/test-app/OtherMachine.tsx b/src/test-app/OtherMachine.tsx index 0536c5c..9e8b31c 100644 --- a/src/test-app/OtherMachine.tsx +++ b/src/test-app/OtherMachine.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { createMachine } from "xstate"; +import { setup } from "xstate"; import { createXStateTreeMachine, PickEvent } from "../"; import { RoutingEvent } from "../routing"; @@ -15,11 +15,11 @@ declare global { type Events = | PickEvent<"DO_THE_THING" | "GO_TO_DO_THE_THING_STATE"> | RoutingEvent; -type States = { - value: "awaitingRoute"; - context: any; -}; -const machine = createMachine({ +const machine = setup({ + types: { + events: {} as Events, + }, +}).createMachine({ id: "other", initial: "awaitingRoute", states: { diff --git a/src/test-app/TodoMachine.tsx b/src/test-app/TodoMachine.tsx index c71bab8..4fa8519 100644 --- a/src/test-app/TodoMachine.tsx +++ b/src/test-app/TodoMachine.tsx @@ -1,9 +1,9 @@ // ignore file coverage import cx from "classnames"; import React from "react"; -import { createMachine, assign, sendParent } from "xstate"; +import { setup, assign, sendParent } from "xstate"; -import { Slot, PickEvent, createXStateTreeMachine } from ".."; +import { PickEvent, createXStateTreeMachine } from ".."; import { UnmountingTest } from "./unmountingTestFixture"; @@ -39,50 +39,49 @@ type Events = | "VIEW_ALL_TODOS" | "UPDATE_ALL_TODOS" >; -type State = - | { value: "idle"; context: Context } - | { value: "editing"; context: Context } - | { value: "hidden"; context: Context } - | { value: "removed"; context: Context }; -const slots: Slot[] = []; -const TodoMachine = createMachine({ - context: { - todo: "", - completed: false, - edittedTodo: "", - id: "", +const TodoMachine = setup({ + types: { + context: {} as Context, + events: {} as Events, + input: {} as { todo: string; id: string; completed?: boolean }, }, +}).createMachine({ + context: ({ input }) => ({ + ...input, + completed: input.completed ?? false, + edittedTodo: "", + }), initial: "idle", on: { UPDATE_ALL_TODOS: { - actions: assign({ completed: (_ctx, e) => e.completed }), + actions: assign({ completed: ({ event: e }) => e.completed }), }, CLEAR_COMPLETED: { - cond: (ctx) => ctx.completed, - target: "removed", + guard: ({ context: ctx }) => ctx.completed, + target: ".removed", }, }, states: { idle: { entry: assign({ - edittedTodo: (ctx) => { + edittedTodo: ({ context: ctx }) => { return ctx.todo; }, }), on: { TOGGLE_CLICKED: { - actions: assign({ completed: (ctx) => !ctx.completed }), + actions: assign({ completed: ({ context: ctx }) => !ctx.completed }), }, REMOVE: "removed", START_EDITING: "editing", VIEW_COMPLETED_TODOS: { target: "hidden", - cond: (ctx) => !ctx.completed, + guard: ({ context: ctx }) => !ctx.completed, }, VIEW_ACTIVE_TODOS: { target: "hidden", - cond: (ctx) => ctx.completed, + guard: ({ context: ctx }) => ctx.completed, }, }, }, @@ -91,15 +90,15 @@ const TodoMachine = createMachine({ EDITTING_CANCELLED: "idle", EDITTED_TODO_UPDATED: { actions: assign({ - edittedTodo: (_ctx, e) => e.updatedText, + edittedTodo: ({ event: e }) => e.updatedText, }), }, EDITTING_FINISHED: [ { target: "idle", - cond: (ctx) => ctx.edittedTodo.trim().length > 0, + guard: ({ context: ctx }) => ctx.edittedTodo.trim().length > 0, actions: assign({ - todo: (ctx) => ctx.edittedTodo.trim(), + todo: ({ context: ctx }) => ctx.edittedTodo.trim(), }), }, { @@ -113,16 +112,19 @@ const TodoMachine = createMachine({ VIEW_ALL_TODOS: "idle", VIEW_COMPLETED_TODOS: { target: "idle", - cond: (ctx) => ctx.completed, + guard: ({ context: ctx }) => ctx.completed, }, VIEW_ACTIVE_TODOS: { target: "idle", - cond: (ctx) => !ctx.completed, + guard: ({ context: ctx }) => !ctx.completed, }, }, }, removed: { - entry: sendParent((ctx) => ({ type: "REMOVE_TODO", id: ctx.id })), + entry: sendParent(({ context: ctx }) => ({ + type: "REMOVE_TODO", + id: ctx.id, + })), type: "final", }, }, @@ -205,7 +207,6 @@ const BoundTodoMachine = createXStateTreeMachine(TodoMachine, {
  • ); }, - slots, }); export { BoundTodoMachine as TodoMachine }; diff --git a/src/test-app/TodosMachine.tsx b/src/test-app/TodosMachine.tsx index c841857..003c779 100644 --- a/src/test-app/TodosMachine.tsx +++ b/src/test-app/TodosMachine.tsx @@ -1,21 +1,18 @@ import cx from "classnames"; import React from "react"; import { - createMachine, + setup, assign, - spawn, - DoneInvokeEvent, ActorRefFrom, + assertEvent, + fromPromise, + stopChild, } from "xstate"; import { broadcast, multiSlot, createXStateTreeMachine } from "../"; -import { assert } from "../utils"; import { TodoMachine } from "./TodoMachine"; -enum Actions { - inputChanged = "inputChanged", -} type Context = { todos: ActorRefFrom[]; newTodo: string; @@ -28,151 +25,147 @@ type Events = | { type: "ALL_SELECTED" } | { type: "ACTIVE_SELECTED" } | { type: "COMPLETED_SELECTED" }; -type State = - | { value: "loadingTodos"; context: Context } - | { value: "noTodos"; context: Context } - | { value: "haveTodos"; context: Context } - | { value: "haveTodos.all"; context: Context } - | { value: "haveTodos.active"; context: Context } - | { value: "haveTodos.completed"; context: Context }; const TodosSlot = multiSlot("Todos"); const slots = [TodosSlot]; const lastId = 1; -const TodosMachine = createMachine( - { - id: "todos", - context: { - todos: [], - newTodo: "", - }, - initial: "loadingTodos", - on: { - TODO_INPUT_CHANGED: { - actions: Actions.inputChanged, - }, - CREATE_TODO: { - actions: assign({ - newTodo: () => "", - todos: (ctx) => { - const id = String(lastId + 1); +const TodosMachine = setup({ + types: { + context: {} as Context, + events: {} as Events, + }, + actions: { + inputChanged: assign({ + newTodo: ({ event: e }) => { + assertEvent(e, "TODO_INPUT_CHANGED"); - return [ - ...ctx.todos, - spawn( - TodoMachine.withContext({ - todo: ctx.newTodo.trim(), - completed: false, - id, - edittedTodo: "", - }), - TodosSlot.getId(id) - ), - ]; - }, - }) as any, - cond: (ctx) => ctx.newTodo.trim().length > 0, - target: "chooseCorrectState", + return e.val; }, + }), + }, + actors: { + fetchTodos: fromPromise( + () => + new Promise<{ todo: string; id: string; completed: boolean }[]>( + (res) => { + res([ + { todo: "foo", id: "100", completed: false }, + { todo: "bar", id: "200", completed: true }, + ]); + } + ) + ), + TodoMachine, + }, +}).createMachine({ + id: "todos", + context: { + todos: [], + newTodo: "", + }, + initial: "loadingTodos", + on: { + TODO_INPUT_CHANGED: { + actions: "inputChanged", }, - states: { - loadingTodos: { - invoke: { - src: () => - new Promise((res) => { - res([ - { todo: "foo", id: "100", completed: false }, - { todo: "bar", id: "200", completed: true }, - ]); - }), - onDone: { - actions: assign({ - todos: ( - ctx: Context, - e: DoneInvokeEvent< - { todo: string; id: string; completed: boolean }[] - > - ) => { - const foo = [ - ...ctx.todos, - ...e.data.map((todo) => - spawn( - TodoMachine.withContext({ ...todo, edittedTodo: "" }), - TodosSlot.getId(String(todo.id)) - ) - ), - ]; + CREATE_TODO: { + actions: assign({ + newTodo: () => "", + todos: ({ context: ctx, spawn }) => { + const id = String(lastId + 1); - return foo; - }, - }) as any, - target: "chooseCorrectState", - }, + return [ + ...ctx.todos, + spawn("TodoMachine", { + id: TodosSlot.getId(id), + input: { todo: ctx.newTodo, id }, + }), + ]; + }, + }), + guard: ({ context: ctx }) => ctx.newTodo.trim().length > 0, + target: ".chooseCorrectState", + }, + }, + states: { + loadingTodos: { + invoke: { + src: "fetchTodos", + onDone: { + actions: assign({ + todos: ({ context: ctx, event: e, spawn }) => { + return [ + ...ctx.todos, + ...e.output.map((todo) => + spawn("TodoMachine", { + id: TodosSlot.getId(String(todo.id)), + input: todo, + }) + ), + ]; + }, + }), + target: "chooseCorrectState", }, }, - chooseCorrectState: { - always: [ - { target: "haveTodos.hist", cond: (ctx) => ctx.todos.length > 0 }, - { target: "noTodos" }, - ], - }, - haveTodos: { - on: { - REMOVE_TODO: { - actions: assign({ - todos: (ctx, e) => { + }, + chooseCorrectState: { + always: [ + { + target: "haveTodos.hist", + guard: ({ context: ctx }) => ctx.todos.length > 0, + }, + { target: "noTodos" }, + ], + }, + haveTodos: { + on: { + REMOVE_TODO: { + actions: [ + stopChild(({ event }) => TodosSlot.getId(String(event.id))), + assign({ + todos: ({ context: ctx, event: e }) => { return ctx.todos.filter( - (todoActor) => todoActor.state.context.id !== e.id + (todoActor) => todoActor.getSnapshot().context.id !== e.id ); }, }), - target: "chooseCorrectState", - }, - CLEAR_COMPLETED: "chooseCorrectState", - ALL_SELECTED: ".all", - ACTIVE_SELECTED: ".active", - COMPLETED_SELECTED: ".completed", + ], + target: "chooseCorrectState", + }, + CLEAR_COMPLETED: "chooseCorrectState", + ALL_SELECTED: ".all", + ACTIVE_SELECTED: ".active", + COMPLETED_SELECTED: ".completed", + }, + initial: "all", + states: { + hist: { + type: "history", + }, + all: { + entry: () => broadcast({ type: "VIEW_ALL_TODOS" }), }, - initial: "all", - states: { - hist: { - type: "history", - }, - all: { - entry: () => broadcast({ type: "VIEW_ALL_TODOS" }), - }, - active: { - entry: () => broadcast({ type: "VIEW_ACTIVE_TODOS" }), - }, - completed: { - entry: () => broadcast({ type: "VIEW_COMPLETED_TODOS" }), - }, + active: { + entry: () => broadcast({ type: "VIEW_ACTIVE_TODOS" }), + }, + completed: { + entry: () => broadcast({ type: "VIEW_COMPLETED_TODOS" }), }, }, - noTodos: {}, }, + noTodos: {}, }, - { - actions: { - [Actions.inputChanged]: assign({ - newTodo: (_ctx, e) => { - assert(e.type === "TODO_INPUT_CHANGED"); - - return e.val; - }, - }), - }, - } -); +}); const BuiltTodosMachine = createXStateTreeMachine(TodosMachine, { selectors({ ctx, inState }) { return { todoInput: ctx.newTodo, allCompleted: ctx.todos.every( - (todoActor) => todoActor.state.context.completed + (todoActor) => todoActor.getSnapshot().context.completed ), uncompletedCount: ctx.todos.filter( - (todoActor) => !todoActor.state.context.completed + (todoActor) => !todoActor.getSnapshot().context.completed ).length, loading: inState("loadingTodos"), haveTodos: inState("haveTodos"), diff --git a/src/test-app/tests/itWorksWithoutRouting.integration.tsx b/src/test-app/tests/itWorksWithoutRouting.integration.tsx index 6ab4009..460dccc 100644 --- a/src/test-app/tests/itWorksWithoutRouting.integration.tsx +++ b/src/test-app/tests/itWorksWithoutRouting.integration.tsx @@ -20,10 +20,10 @@ const child = createXStateTreeMachine(childMachine, { }); const childSlot = singleSlot("Child"); -const rootMachine = createMachine({ +const rootMachine = createMachine({ initial: "idle", invoke: { - src: () => child, + src: child, id: childSlot.getId(), }, states: { diff --git a/src/test-app/tests/removingChildActor.integration.tsx b/src/test-app/tests/removingChildActor.integration.tsx index b959d0d..63d22d2 100644 --- a/src/test-app/tests/removingChildActor.integration.tsx +++ b/src/test-app/tests/removingChildActor.integration.tsx @@ -6,7 +6,7 @@ import { delay } from "../../utils"; import { App } from "../AppMachine"; describe("removing an existing child actor", () => { - it("remotes the child actor from the existing multi-slot view when it is stopped", async () => { + it("it stops rendering the actor when it is stopped", async () => { const { getAllByTestId } = render(); await delay(50); diff --git a/src/testingUtilities.tsx b/src/testingUtilities.tsx index fc9c519..71486cf 100644 --- a/src/testingUtilities.tsx +++ b/src/testingUtilities.tsx @@ -1,56 +1,5 @@ // ignore file coverage -import { useMachine } from "@xstate/react"; -import React, { JSXElementConstructor, useEffect } from "react"; -import { TinyEmitter } from "tiny-emitter"; -import { - StateMachine, - createMachine, - AnyStateMachine, - ContextFrom, - EventFrom, -} from "xstate"; - -import { buildXStateTreeMachine } from "./builders"; -import { - XstateTreeMachineStateSchemaV1, - GlobalEvents, - ViewProps, - AnySelector, - AnyActions, - XstateTreeMachineStateSchemaV2, -} from "./types"; -import { difference, PropsOf } from "./utils"; -import { emitter, recursivelySend, XstateTreeView } from "./xstateTree"; - -/** - * @public - * - * Creates a dummy machine that just renders the supplied string - useful for rendering xstate-tree views in isolation - * - * @param name - the string to render in the machines view - * @returns a dummy machine that renders a div containing the supplied string - */ -export function slotTestingDummyFactory(name: string) { - return buildXStateTreeMachine( - createMachine({ - id: name, - initial: "idle", - states: { - idle: {}, - }, - }), - { - actions: () => ({}), - selectors: () => ({}), - slots: [], - view: () => ( -
    -

    {name}

    -
    - ), - } - ); -} +import React from "react"; /** * @public @@ -71,133 +20,3 @@ export const genericSlotsTestingDummy = new Proxy( }, } ) as any; - -type InferViewProps = T extends ViewProps< - infer TSelectors, - infer TActions, - never, - infer TMatches -> - ? { - selectors: TSelectors; - actions: TActions; - inState: (state: Parameters[0]) => TMatches; - } - : never; - -/** - * @public - * - * Aids in type inference for creating props objects for xstate-tree views. - * - * @param view - The view to create props for - * @param props - The actions/selectors props to pass to the view - * @returns An object with the view's selectors, actions, and inState function props - */ -export function buildViewProps< - C extends keyof JSX.IntrinsicElements | JSXElementConstructor ->( - _view: C, - props: Pick>, "actions" | "selectors"> -): InferViewProps> { - return { - ...props, - inState: (testState: any) => (state: any) => - state === testState || testState.startsWith(state), - } as InferViewProps>; -} - -/** - * @public - * - * Sets up a root component for use in an \@xstate/test model backed by \@testing-library/react for the component - * - * The logger argument should just be a simple function which forwards the arguments to console.log, - * this is needed because Wallaby.js only displays console logs in tests that come from source code, not library code, - * so any logs from inside this file don't show up in the test explorer - * - * The returned object has a `rootComponent` property and a function, `awaitTransition`, that returns a Promise - * when called that is resolved the next time the underlying machine transitions. This can be used in the \@xstate/test - * model to ensure after an event action is executed the test in the next state doesn't run until after the machine transitions - * - * It also delays for 5ms to ensure any React re-rendering happens in response to the state transition - */ -export function buildTestRootComponent< - TMachine extends AnyStateMachine, - TSelectors extends AnySelector, - TActions extends AnyActions, - TContext = ContextFrom ->( - machine: StateMachine< - TContext, - | XstateTreeMachineStateSchemaV1 - | XstateTreeMachineStateSchemaV2, - EventFrom - >, - logger: typeof console.log -) { - if (!machine.meta) { - throw new Error("Root machine has no meta"); - } - if ( - (machine.meta.builderVersion === 1 && !machine.meta.view) || - (machine.meta.builderVersion === 2 && !machine.meta.View) - ) { - throw new Error("Root machine has no associated view"); - } - const onChangeEmitter = new TinyEmitter(); - - function addTransitionListener(listener: () => void) { - onChangeEmitter.once("transition", listener); - } - - return { - rootComponent: function XstateTreeRootComponent() { - const [_, __, interpreter] = useMachine(machine, { devTools: true }); - - useEffect(() => { - function handler(event: GlobalEvents) { - recursivelySend(interpreter, event); - } - function changeHandler(ctx: TContext, oldCtx: TContext | undefined) { - logger("onChange: ", difference(oldCtx, ctx)); - onChangeEmitter.emit("changed", ctx); - } - function onEventHandler(e: any) { - logger("onEvent", e); - } - function onTransitionHandler(s: any) { - logger("State: ", s.value); - onChangeEmitter.emit("transition"); - } - - interpreter.onChange(changeHandler); - interpreter.onEvent(onEventHandler); - interpreter.onTransition(onTransitionHandler); - - emitter.on("event", handler); - - return () => { - emitter.off("event", handler); - interpreter.off(changeHandler); - interpreter.off(onEventHandler); - interpreter.off(onTransitionHandler); - }; - }, [interpreter]); - - if (!interpreter.initialized) { - return null; - } - - return ; - }, - addTransitionListener, - awaitTransition() { - return new Promise((res) => { - addTransitionListener(() => { - setTimeout(res, 50); - }); - }); - }, - }; -} diff --git a/src/tests/actionsGetUpdatedSelectors.spec.tsx b/src/tests/actionsGetUpdatedSelectors.spec.tsx index 842f7fe..26b0bec 100644 --- a/src/tests/actionsGetUpdatedSelectors.spec.tsx +++ b/src/tests/actionsGetUpdatedSelectors.spec.tsx @@ -1,14 +1,9 @@ import { render } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; -import { assign, createMachine } from "xstate"; +import { assign, setup } from "xstate"; -import { - buildActions, - buildSelectors, - buildView, - buildXStateTreeMachine, -} from "../builders"; +import { createXStateTreeMachine } from "../builders"; import { delay } from "../utils"; import { buildRootComponent } from "../xstateTree"; @@ -16,13 +11,15 @@ describe("actions accessing selectors", () => { let actionsCallCount = 0; type Events = { type: "SET_COUNT"; count: number }; type Context = { count: number }; - const machine = createMachine({ + const machine = setup({ + types: { events: {} as Events, context: {} as Context }, + }).createMachine({ context: { count: 0, }, on: { SET_COUNT: { - actions: assign({ count: (_, event) => event.count }), + actions: assign({ count: ({ event }) => event.count }), }, }, initial: "foo", @@ -31,34 +28,21 @@ describe("actions accessing selectors", () => { }, }); - const selectors = buildSelectors(machine, (ctx) => ({ count: ctx.count })); - const actions = buildActions(machine, selectors, (send, selectors) => { - actionsCallCount++; - return { - incrementCount() { - send({ type: "SET_COUNT", count: selectors.count + 1 }); - }, - }; - }); - - const view = buildView( - machine, - selectors, - actions, - [], - ({ selectors, actions }) => { - return ( - - ); - } - ); - const Root = buildRootComponent( - buildXStateTreeMachine(machine, { - actions, - selectors, - slots: [], - view, + createXStateTreeMachine(machine, { + actions({ selectors, send }) { + actionsCallCount++; + return { + incrementCount() { + send({ type: "SET_COUNT", count: selectors.count + 1 }); + }, + }; + }, + View: ({ selectors, actions }) => { + return ( + + ); + }, }) ); diff --git a/src/tests/asyncRouteRedirects.spec.tsx b/src/tests/asyncRouteRedirects.spec.tsx index 37b3b61..9908a53 100644 --- a/src/tests/asyncRouteRedirects.spec.tsx +++ b/src/tests/asyncRouteRedirects.spec.tsx @@ -1,18 +1,14 @@ import { render } from "@testing-library/react"; -import { assign } from "@xstate/immer"; import { createMemoryHistory } from "history"; import React from "react"; -import { createMachine } from "xstate"; +import { setup, assign } from "xstate"; import { z } from "zod"; import { buildRootComponent, - buildActions, buildCreateRoute, - buildSelectors, - buildView, - buildXStateTreeMachine, XstateTreeHistory, + createXStateTreeMachine, } from "../"; import { delay } from "../utils"; @@ -55,13 +51,17 @@ describe("async route redirects", () => { event: "GO_TO_CHILD", }); - const machine = createMachine({ + const machine = setup({ + types: { context: {} as { bar?: string } }, + }).createMachine({ context: {}, initial: "idle", on: { GO_TO_REDIRECT: { - actions: assign((ctx, e) => { - ctx.bar = e.params.bar; + actions: assign({ + bar: ({ event: e }) => { + return e.params.bar; + }, }), }, }, @@ -69,14 +69,11 @@ describe("async route redirects", () => { idle: {}, }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, () => ({})); - const view = buildView(machine, selectors, actions, [], ({ selectors }) => ( -

    {selectors.bar}

    - )); const Root = buildRootComponent( - buildXStateTreeMachine(machine, { actions, selectors, view, slots: [] }), + createXStateTreeMachine(machine, { + View: ({ selectors }) =>

    {selectors.bar}

    , + }), { basePath: "/", history: hist, diff --git a/src/types.ts b/src/types.ts index 837f91a..2129750 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,71 +1,17 @@ import { type History } from "history"; import React from "react"; import type { - AnyFunction, + ActorRefFrom, AnyStateMachine, ContextFrom, EventFrom, - InterpreterFrom, - StateFrom, - StateMachine, + IsNever, + SnapshotFrom, + StateValue, } from "xstate"; import { Slot, GetSlotNames } from "./slots"; -/** - * @public - */ -export type XStateTreeMachineMetaV1< - TMachine extends AnyStateMachine, - TSelectors, - TActions extends AnyActions, - TSlots extends readonly Slot[] = Slot[] -> = { - slots: TSlots; - view: React.ComponentType< - ViewProps< - OutputFromSelector, - ReturnType, - TSlots, - MatchesFrom - > - >; - selectors: TSelectors; - actions: TActions; - xstateTreeMachine?: true; -}; - -/** - * @public - */ -export type XstateTreeMachineStateSchemaV1< - TMachine extends AnyStateMachine, - TSelectors extends AnySelector, - TActions extends AnyActions -> = { - meta: XStateTreeMachineMetaV1 & { - builderVersion: 1; - }; -}; - -/** - * @public - */ -export type ViewProps< - TSelectors, - TActions, - TSlots extends readonly Slot[], - TMatches extends AnyFunction -> = { - slots: Record, React.ComponentType>; - actions: TActions; - selectors: TSelectors; - /** - * @deprecated see https://github.com/koordinates/xstate-tree/issues/33 use `inState` in the selector function instead - */ - inState: TMatches; -}; - declare global { /** * @@ -110,53 +56,52 @@ export type XstateTreeHistory = History<{ previousUrl?: string; }>; -/** - * @public - */ -export type V1Selectors = ( - ctx: TContext, - canHandleEvent: (e: TEvent) => boolean, - inState: TMatches, - __currentState: never -) => TSelectors; +type Values = T[keyof T]; +type WithParentPath< + TCurrent extends string, + TParentPath extends string +> = `${TParentPath extends "" ? "" : `${TParentPath}.`}${TCurrent}`; + +type ToStatePaths< + TStateValue extends StateValue, + TParentPath extends string = "" +> = TStateValue extends string + ? WithParentPath + : IsNever extends true + ? never + : + | WithParentPath + | Values<{ + [K in keyof TStateValue & string]?: ToStatePaths< + NonNullable, + WithParentPath + >; + }>; /** * @internal */ -export type MatchesFrom = StateFrom["matches"]; - -/** - * @public - */ -export type OutputFromSelector = T extends V1Selectors< - any, - any, - infer O, - any -> - ? O - : never; +export type MatchesFrom = ( + value: ToStatePaths["value"]> +) => boolean; /** - * @public + * @internal */ -export type AnySelector = V1Selectors; +export type XstateTreeMachineInjection = { + _xstateTree: XstateTreeMachineStateSchemaV2; +}; /** * @public */ -export type AnyActions = (send: any, selectors: any) => any; +export type XstateTreeMachine = TMachine & + XstateTreeMachineInjection; /** * @public */ -export type AnyXstateTreeMachine = StateMachine< - any, - | XstateTreeMachineStateSchemaV1 - | XstateTreeMachineStateSchemaV2, - any ->; - +export type AnyXstateTreeMachine = XstateTreeMachine; /** * @internal */ @@ -182,7 +127,7 @@ export type Actions< TSelectorsOutput, TOut > = (args: { - send: InterpreterFrom["send"]; + send: ActorRefFrom["send"]; selectors: TSelectorsOutput; }) => TOut; @@ -222,13 +167,7 @@ export type XstateTreeMachineStateSchemaV2< TSelectorsOutput = ContextFrom, TActionsOutput = Record, TSlots extends readonly Slot[] = Slot[] -> = { - meta: Required< - V2BuilderMeta & { - builderVersion: 2; - } - >; -}; +> = Required>; /** * @public @@ -236,12 +175,13 @@ export type XstateTreeMachineStateSchemaV2< * Retrieves the selector return type from the xstate-tree machine */ export type SelectorsFrom = - TMachine extends StateMachine - ? TMeta extends { meta: { selectors: infer TOut } } - ? TOut extends (...args: any) => any - ? ReturnType - : never - : never + TMachine["_xstateTree"] extends XstateTreeMachineStateSchemaV2< + any, + infer TOut, + any, + any + > + ? TOut : never; /** @@ -250,10 +190,11 @@ export type SelectorsFrom = * Retrieves the actions return type from the xstate-tree machine */ export type ActionsFrom = - TMachine extends StateMachine - ? TMeta extends { meta: { actions: infer TOut } } - ? TOut extends (...args: any) => any - ? ReturnType - : never - : never + TMachine["_xstateTree"] extends XstateTreeMachineStateSchemaV2< + any, + any, + infer TOut, + any + > + ? TOut : never; diff --git a/src/useService.ts b/src/useService.ts index c6774c8..02b8491 100644 --- a/src/useService.ts +++ b/src/useService.ts @@ -1,8 +1,13 @@ import { useState, useRef, useEffect } from "react"; -import { EventObject, Interpreter, InterpreterFrom, AnyState } from "xstate"; +import { + EventObject, + ActorRefFrom, + AnyMachineSnapshot, + AnyActorRef, + SnapshotFrom, +} from "xstate"; -import { AnyXstateTreeMachine, XstateTreeMachineStateSchemaV1 } from "./types"; -import { isEqual } from "./utils"; +import { AnyXstateTreeMachine } from "./types"; /** * @public @@ -29,11 +34,18 @@ export function loggingMetaOptions( * @internal */ export function useService< - TInterpreter extends InterpreterFrom ->(service: TInterpreter) { - const [current, setCurrent] = useState(service.state); - const [children, setChildren] = useState(service.children); - const childrenRef = useRef(new Map()); + TInterpreter extends ActorRefFrom +>( + service: TInterpreter +): readonly [ + current: SnapshotFrom, + children: Record +] { + const [current, setCurrent] = useState(service.getSnapshot()); + const [children, setChildren] = useState>( + service.getSnapshot().children + ); + const childrenRef = useRef>({}); useEffect(() => { childrenRef.current = children; @@ -44,16 +56,11 @@ export function useService< // Set to current service state as there is a possibility // of a transition occurring between the initial useState() // initialization and useEffect() commit. - setCurrent(service.state); - setChildren(service.children); - const listener = function (state: AnyState) { - if (state.changed) { - setCurrent(state); - - if (!isEqual(childrenRef.current, service.children)) { - setChildren(new Map(service.children)); - } - } + setCurrent(service.getSnapshot()); + setChildren(service.getSnapshot().children); + const listener = function (snapshot: AnyMachineSnapshot) { + setCurrent(snapshot); + setChildren(service.getSnapshot().children); }; const sub = service.subscribe(listener); return function () { @@ -62,61 +69,6 @@ export function useService< }, [service, setChildren] ); - useEffect(() => { - function handler(event: EventObject) { - if (event.type.includes("done")) { - const idOfFinishedChild = event.type.split(".")[2]; - childrenRef.current.delete(idOfFinishedChild); - setChildren(new Map(childrenRef.current)); - } - - console.debug( - `[xstate-tree] ${service.id} handling event`, - (service.machine.meta as any)?.xstateTree?.ignoredEvents?.has( - event.type - ) - ? event.type - : event - ); - } - - let prevState: undefined | AnyState = undefined; - function transitionHandler(state: AnyState) { - const ignoreContext: string[] | undefined = (service.machine.meta as any) - ?.xstateTree?.ignoreContext; - const context = ignoreContext ? "[context omitted]" : state.context; - if (prevState) { - console.debug( - `[xstate-tree] ${service.id} transitioning from`, - prevState.value, - "to", - state.value, - context - ); - } else { - console.debug( - `[xstate-tree] ${service.id} transitioning to ${state.value}`, - context - ); - } - - prevState = state; - } - - service.onEvent(handler); - service.onTransition(transitionHandler); - - return () => { - service.off(handler); - service.off(transitionHandler); - }; - }, [service, setChildren]); - return [ - current, - children as unknown as Map< - string | number, - Interpreter, any, any> - >, - ] as const; + return [current, children] as const; } diff --git a/src/utils.ts b/src/utils.ts index fb185bb..4727eea 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,4 @@ import { ComponentPropsWithRef, JSXElementConstructor } from "react"; -import { Interpreter, StateMachine } from "xstate"; export type PropsOf< C extends keyof JSX.IntrinsicElements | JSXElementConstructor @@ -52,18 +51,6 @@ export function assert(value: unknown, msg?: string): asserts value { } } -export type StateMachineToInterpreter = T extends StateMachine< - infer TContext, - infer TSchema, - infer TEvents, - infer TState, - any, - any, - any -> - ? Interpreter - : never; - export function isLikelyPageLoad(): boolean { // without performance API, we can't tell if this is a page load if (typeof performance === "undefined") { @@ -173,3 +160,21 @@ export function mergeMeta(meta: Record) { return acc; }, {}); } +function getCircularReplacer() { + const seen = new WeakSet(); + return (key: string, value: any) => { + if (typeof value === "object" && value !== null) { + if (seen.has(value)) { + // Circular reference found, discard key + return; + } + // Store value in our set + seen.add(value); + } + return value; + }; +} + +export function toJSON(value: unknown): T { + return JSON.parse(JSON.stringify(value, getCircularReplacer())); +} diff --git a/src/xstateTree.spec.tsx b/src/xstateTree.spec.tsx index 68dbb99..0cda00f 100644 --- a/src/xstateTree.spec.tsx +++ b/src/xstateTree.spec.tsx @@ -1,16 +1,9 @@ import { render } from "@testing-library/react"; -import { assign } from "@xstate/immer"; import { createMemoryHistory } from "history"; import React from "react"; -import { createMachine, interpret } from "xstate"; +import { setup, createActor, assign } from "xstate"; -import { - buildXStateTreeMachine, - buildView, - buildSelectors, - buildActions, - createXStateTreeMachine, -} from "./builders"; +import { createXStateTreeMachine, viewToMachine } from "./builders"; import { singleSlot } from "./slots"; import { delay } from "./utils"; import { @@ -23,13 +16,13 @@ describe("xstate-tree", () => { describe("a machine with a guarded event that triggers external side effects in an action", () => { it("does not execute the side effects of events passed to canHandleEvent", async () => { const sideEffect = jest.fn(); - const machine = createMachine({ + const machine = setup({}).createMachine({ initial: "a", states: { a: { on: { SWAP: { - cond: () => true, + guard: () => true, // Don't do this. There is a reason why assign actions should be pure. // but it triggers the issue actions: assign(() => { @@ -64,26 +57,19 @@ describe("xstate-tree", () => { describe("machines that don't have any visible change after initializing", () => { it("still renders the machines view", async () => { const renderCallback = jest.fn(); - const machine = createMachine({ + const machine = setup({}).createMachine({ initial: "a", states: { a: {}, }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, () => ({})); - const view = buildView(machine, selectors, actions, [], () => { - renderCallback(); - - return null; - }); + const XstateTreeMachine = createXStateTreeMachine(machine, { + View: () => { + renderCallback(); - const XstateTreeMachine = buildXStateTreeMachine(machine, { - actions, - selectors, - slots: [], - view, + return null; + }, }); const Root = buildRootComponent(XstateTreeMachine); @@ -96,7 +82,7 @@ describe("xstate-tree", () => { describe("selectors & action references do not change, state does change", () => { it("re-renders the view for the machine", async () => { const renderCallback = jest.fn(); - const machine = createMachine({ + const machine = setup({}).createMachine({ context: { foo: 1 }, initial: "a", states: { @@ -109,37 +95,30 @@ describe("xstate-tree", () => { }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, () => ({})); - const view = buildView(machine, selectors, actions, [], () => { - renderCallback(); - - return null; - }); + const XstateTreeMachine = createXStateTreeMachine(machine, { + View: () => { + renderCallback(); - const XstateTreeMachine = buildXStateTreeMachine(machine, { - actions, - selectors, - slots: [], - view, + return null; + }, }); const Root = buildRootComponent(XstateTreeMachine); const { rerender } = render(); await delay(10); rerender(); - expect(renderCallback).toHaveBeenCalledTimes(1); + expect(renderCallback).toHaveBeenCalledTimes(2); broadcast({ type: "SWAP" } as any as never); await delay(10); rerender(); - expect(renderCallback).toHaveBeenCalledTimes(2); + expect(renderCallback).toHaveBeenCalledTimes(5); }); }); describe("broadcasting event with handler raising error", () => { it("does not bubble the error up", () => { - const machine = createMachine({ + const machine = setup({}).createMachine({ context: { foo: 1 }, initial: "a", states: { @@ -156,17 +135,8 @@ describe("xstate-tree", () => { }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, () => ({})); - const view = buildView(machine, selectors, actions, [], () => { - return null; - }); - - const XstateTreeMachine = buildXStateTreeMachine(machine, { - actions, - selectors, - slots: [], - view, + const XstateTreeMachine = createXStateTreeMachine(machine, { + View: () => null, }); const Root = buildRootComponent(XstateTreeMachine); @@ -181,7 +151,7 @@ describe("xstate-tree", () => { it("sends the event to machines after the machine that errored handling it", () => { const childMachineHandler = jest.fn(); const slots = [singleSlot("child")]; - const childMachine = createMachine({ + const childMachine = setup({}).createMachine({ context: { foo: 2 }, initial: "a", states: { @@ -197,14 +167,16 @@ describe("xstate-tree", () => { b: {}, }, }); - const machine = createMachine({ + const machine = setup({ + actors: { + childMachine, + }, + }).createMachine({ context: { foo: 1 }, initial: "a", invoke: { id: slots[0].getId(), - src: () => { - return childMachine; - }, + src: "childMachine", }, states: { a: { @@ -220,17 +192,8 @@ describe("xstate-tree", () => { }, }); - const selectors = buildSelectors(machine, (ctx) => ctx); - const actions = buildActions(machine, selectors, () => ({})); - const view = buildView(machine, selectors, actions, [], () => { - return null; - }); - - const XstateTreeMachine = buildXStateTreeMachine(machine, { - actions, - selectors, - slots: [], - view, + const XstateTreeMachine = createXStateTreeMachine(machine, { + View: () => null, }); const Root = buildRootComponent(XstateTreeMachine); @@ -243,7 +206,7 @@ describe("xstate-tree", () => { }); it("passes the current states meta into the v2 selector functions", async () => { - const machine = createMachine({ + const machine = setup({}).createMachine({ id: "test-selectors-meta", initial: "idle", states: { @@ -270,9 +233,27 @@ describe("xstate-tree", () => { expect(await findByText("bar")).toBeTruthy(); }); + it("allows rendering nested roots", () => { + const childRoot = viewToMachine(() =>

    Child

    ); + const ChildRoot = buildRootComponent(childRoot); + const rootMachine = viewToMachine(() => { + return ( + <> +

    Root

    + + + ); + }); + const Root = buildRootComponent(rootMachine); + + const { getByText } = render(); + getByText("Root"); + getByText("Child"); + }); + describe("getMultiSlotViewForChildren", () => { it("memoizes correctly", () => { - const machine = createMachine({ + const machine = setup({}).createMachine({ id: "test", initial: "idle", states: { @@ -280,21 +261,30 @@ describe("xstate-tree", () => { }, }); - const interpreter1 = interpret(machine).start(); - const interpreter2 = interpret(machine).start(); + const interpreter1 = createActor(machine).start(); + const interpreter2 = createActor(machine).start(); - const view1 = getMultiSlotViewForChildren(interpreter1, "ignored"); - const view2 = getMultiSlotViewForChildren(interpreter2, "ignored"); + try { + const view1 = getMultiSlotViewForChildren(interpreter1, "ignored"); + const view2 = getMultiSlotViewForChildren(interpreter2, "ignored"); - expect(view1).not.toBe(view2); - expect(view1).toBe(getMultiSlotViewForChildren(interpreter1, "ignored")); - expect(view2).toBe(getMultiSlotViewForChildren(interpreter2, "ignored")); + expect(view1).not.toBe(view2); + expect(view1).toBe( + getMultiSlotViewForChildren(interpreter1, "ignored") + ); + expect(view2).toBe( + getMultiSlotViewForChildren(interpreter2, "ignored") + ); + } finally { + interpreter1.stop(); + interpreter2.stop(); + } }); }); describe("rendering a root inside of a root", () => { it("throws an error during rendering if both are routing roots", async () => { - const machine = createMachine({ + const machine = setup({}).createMachine({ id: "test", initial: "idle", states: { @@ -338,31 +328,11 @@ describe("xstate-tree", () => { }); it("does not throw an error if either or one are a routing root", async () => { - const machine = createMachine({ - id: "test", - initial: "idle", - states: { - idle: {}, - }, - }); - - const RootMachine = createXStateTreeMachine(machine, { - View() { - return

    I am root

    ; - }, - }); + const RootMachine = viewToMachine(() =>

    I am root

    ); const Root = buildRootComponent(RootMachine); - const Root2Machine = createXStateTreeMachine(machine, { - View() { - return ; - }, - }); - const Root2 = buildRootComponent(Root2Machine, { - basePath: "/", - history: createMemoryHistory(), - routes: [], - }); + const Root2Machine = viewToMachine(() => ); + const Root2 = buildRootComponent(Root2Machine); const { rerender } = render(); rerender(); diff --git a/src/xstateTree.tsx b/src/xstateTree.tsx index c3bbb5b..e1f710d 100644 --- a/src/xstateTree.tsx +++ b/src/xstateTree.tsx @@ -1,4 +1,4 @@ -import { useMachine } from "@xstate/react"; +import { useActor } from "@xstate/react"; import memoize from "fast-memoize"; import React, { useCallback, @@ -9,12 +9,11 @@ import React, { } from "react"; import { TinyEmitter } from "tiny-emitter"; import { - EventObject, - Typestate, - Interpreter, - InterpreterFrom, - AnyInterpreter, + Actor, + AnyActor, + ActorRefFrom, AnyEventObject, + AnyActorRef, } from "xstate"; import { @@ -30,7 +29,7 @@ import { GetSlotNames, Slot } from "./slots"; import { GlobalEvents, AnyXstateTreeMachine, XstateTreeHistory } from "./types"; import { useConstant } from "./useConstant"; import { useService } from "./useService"; -import { assertIsDefined, isLikelyPageLoad, mergeMeta } from "./utils"; +import { assertIsDefined, isLikelyPageLoad, mergeMeta, toJSON } from "./utils"; export const emitter = new TinyEmitter(); @@ -65,28 +64,27 @@ export function onBroadcast( function cacheKeyForInterpreter( // eslint-disable-next-line @typescript-eslint/no-explicit-any - interpreter: Interpreter + interpreter: AnyActor ) { return interpreter.sessionId; } const getViewForInterpreter = memoize( - (interpreter: AnyInterpreter) => { + (interpreter: ActorRefFrom) => { return React.memo(function InterpreterView() { const activeRouteEvents = useActiveRouteEvents(); useEffect(() => { if (activeRouteEvents) { activeRouteEvents.forEach((event) => { - // @ts-ignore fixed in v5 branch - if (interpreter.state.nextEvents.includes(event.type)) { + if (interpreter.getSnapshot().can(event)) { interpreter.send(event); } }); } }, []); - return ; + return ; }); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -97,18 +95,20 @@ const getViewForInterpreter = memoize( * @private */ export const getMultiSlotViewForChildren = memoize( - (parent: InterpreterFrom, slot: string) => { + (parent: ActorRefFrom, slot: string) => { return React.memo(function MultiSlotView() { const [_, children] = useService(parent); - const interpreters = [...children.values()]; + const interpreters = Object.values(children); // Once the interpreter is stopped, initialized gets set to false // We don't want to render stopped interpreters - const interpretersWeCareAbout = interpreters.filter( - (i) => i.id.includes(slot) && i.initialized + const interpretersWeCareAbout = interpreters.filter((i) => + i.id.includes(slot) ); return ( - + ); }); }, @@ -118,7 +118,7 @@ export const getMultiSlotViewForChildren = memoize( ); function useSlots( - interpreter: InterpreterFrom, + interpreter: ActorRefFrom, slots: GetSlotNames[] ): Record, React.ComponentType> { return useConstant(() => { @@ -136,9 +136,7 @@ function useSlots( ); return ; } else { - const interpreterForSlot = children.get( - `${slot.toLowerCase()}-slot` - ); + const interpreterForSlot = children[`${slot.toLowerCase()}-slot`]; if (interpreterForSlot) { const View = getViewForInterpreter(interpreterForSlot); @@ -156,45 +154,51 @@ function useSlots( } type XStateTreeMultiSlotViewProps = { - childInterpreters: AnyInterpreter[]; + childInterpreters: AnyActor[]; }; function XstateTreeMultiSlotView({ childInterpreters, }: XStateTreeMultiSlotViewProps) { + console.log("XstateTreeMultiSlotView", childInterpreters); return ( <> {childInterpreters.map((i) => ( - + ))} ); } type XStateTreeViewProps = { - interpreter: InterpreterFrom; + actor: ActorRefFrom; }; /** * @internal */ -export function XstateTreeView({ interpreter }: XStateTreeViewProps) { - const [current] = useService(interpreter); +export function XstateTreeView({ actor }: XStateTreeViewProps) { + const [current] = useService(actor); const currentRef = useRef(current); currentRef.current = current; const selectorsRef = useRef | undefined>( undefined ); - const { slots: interpreterSlots } = interpreter.machine.meta!; + const { + slots: interpreterSlots, + View, + actions: actionsFactory, + selectors: selectorsFactory, + } = (actor as Actor).logic._xstateTree; const slots = useSlots>( - interpreter, + actor, interpreterSlots.map((x) => x.name) ); const canHandleEvent = useCallback( (e: AnyEventObject) => { - return interpreter.getSnapshot().can(e); + return actor.getSnapshot().can(e); }, - [interpreter] + [actor] ); const inState = useCallback( (state: unknown) => { @@ -216,99 +220,52 @@ export function XstateTreeView({ interpreter }: XStateTreeViewProps) { ); }); const actions = useConstant(() => { - switch (interpreter.machine.meta?.builderVersion) { - case 1: - return interpreter.machine.meta!.actions( - interpreter.send, - selectorsProxy - ); - case 2: - return interpreter.machine.meta!.actions({ - send: interpreter.send, - selectors: selectorsProxy, - }); - default: - throw new Error("builderVersion not set"); - } + return actionsFactory({ + send: actor.send, + selectors: selectorsProxy, + }); }); if (!current) { return null; } - switch (interpreter.machine.meta?.builderVersion) { - case 1: - selectorsRef.current = interpreter.machine.meta!.selectors( - current.context, - canHandleEvent, - inState, - current.value as never - ); - break; - case 2: - selectorsRef.current = interpreter.machine.meta!.selectors({ - ctx: current.context, - canHandleEvent, - inState, - meta: mergeMeta(current.meta), - }); - break; - } + selectorsRef.current = selectorsFactory({ + ctx: current.context, + canHandleEvent, + // Workaround for type instantiation possibly infinite error + inState: inState as any, + meta: mergeMeta(current.getMeta()), + }); - switch (interpreter.machine.meta?.builderVersion) { - case 1: - const ViewV1 = interpreter.machine.meta!.view; - return ( - - ); - case 2: - const ViewV2 = interpreter.machine.meta!.View; - return ( - - ); - default: - throw new Error("builderVersion not set"); - } + return ( + + ); } /** * @internal */ -export function recursivelySend< - TContext, - TEvent extends EventObject, - TTypeState extends Typestate ->( - service: Interpreter, - event: GlobalEvents -) { - const children = ([...service.children.values()] || []).filter((s) => - s.id.includes("-slot") - ) as unknown as Interpreter[]; +export function recursivelySend(service: AnyActorRef, event: GlobalEvents) { + const children = Object.values( + service.getSnapshot().children + ).filter((s) => s.id.includes("-slot")); // If the service can't handle the event, don't send it - if (service.getSnapshot()?.nextEvents.includes((event as any).type)) { + if (service.getSnapshot().can(event)) { try { - service.send(event as any); + service.send(event); } catch (e) { console.error( "Error sending event ", event, " to machine ", - service.machine.id, + service.id, e ); } } + children.forEach((child) => recursivelySend(child, event)); } @@ -330,30 +287,60 @@ export function buildRootComponent( getQueryString?: () => string; } ) { - if (!machine.meta) { - throw new Error("Root machine has no meta"); + if (!machine._xstateTree) { + throw new Error( + "Root machine is not an xstate-tree machine, missing metadata" + ); } - switch (machine.meta.builderVersion) { - case 1: - if (!machine.meta.view) { - throw new Error("Root machine has no associated view"); - } - break; - case 2: - if (!machine.meta.View) { - throw new Error("Root machine has no associated view"); - } - break; + if (!machine._xstateTree.View) { + throw new Error("Root machine has no associated view"); } const RootComponent = function XstateTreeRootComponent() { - const [_, __, interpreter] = useMachine(machine, { devTools: true }); + const lastSnapshotsRef = useRef>({}); + const [_, __, interpreter] = useActor(machine, { + inspect(event) { + switch (event.type) { + case "@xstate.actor": + console.log(`[xstate-tree] actor spawned: ${event.actorRef.id}`); + break; + case "@xstate.event": + console.log( + `[xstate-tree] event: ${ + event.sourceRef ? event.sourceRef.id : "UNKNOWN" + } -> ${event.event.type} -> ${event.actorRef.id}`, + event.event + ); + break; + case "@xstate.snapshot": + const lastSnapshot = + lastSnapshotsRef.current[event.actorRef.sessionId]; + + if (!lastSnapshot) { + console.log( + `[xstate-tree] initial snapshot: ${event.actorRef.id}`, + toJSON(event.snapshot) + ); + } else { + console.log( + `[xstate-tree] snapshot: ${event.actorRef.id} transitioning to`, + toJSON(event.snapshot), + "from", + toJSON(lastSnapshot) + ); + } + + lastSnapshotsRef.current[event.actorRef.sessionId] = event.snapshot; + break; + } + }, + id: machine.config.id, + }); const [activeRoute, setActiveRoute] = useState( undefined ); - const activeRouteEventsRef = useRef[]>([]); - const [forceRenderValue, forceRender] = useState(false); - const setActiveRouteEvents = (events: RoutingEvent[]) => { + const activeRouteEventsRef = useRef[]>([]); + const setActiveRouteEvents = (events: RoutingEvent[]) => { activeRouteEventsRef.current = events; }; const insideRoutingContext = useInRoutingContext(); @@ -525,19 +512,14 @@ export function buildRootComponent( }; }, [activeRoute]); - if (!interpreter.initialized) { - setTimeout(() => forceRender(!forceRenderValue), 0); - return null; - } - if (routingProviderValue) { return ( - + ); } else { - return ; + return ; } }; diff --git a/tsconfig.json b/tsconfig.json index 90e47b3..95cd2d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,10 +3,11 @@ "compilerOptions": { "incremental": true, "declaration": true, - "target": "ES2018", + "target": "ES2020", "module": "CommonJS", "jsx": "react", "strict": true, + "skipLibCheck": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, diff --git a/xstate-tree.api.md b/xstate-tree.api.md index fffb35e..b70c0fa 100644 --- a/xstate-tree.api.md +++ b/xstate-tree.api.md @@ -4,41 +4,28 @@ ```ts -import { AnyEventObject } from 'xstate'; -import { AnyFunction } from 'xstate'; +import type { ActorRefFrom } from 'xstate'; import { AnyStateMachine } from 'xstate'; -import { BaseActionObject } from 'xstate'; -import { ComponentPropsWithRef } from 'react'; import { ContextFrom } from 'xstate'; -import { EventFrom } from 'xstate'; +import type { EventFrom } from 'xstate'; import { EventObject } from 'xstate'; import { History as History_2 } from 'history'; -import { InterpreterFrom } from 'xstate'; -import { JSXElementConstructor } from 'react'; +import { InputFrom } from 'xstate'; +import type { IsNever } from 'xstate'; import { ParsedQuery } from 'query-string'; import { default as React_2 } from 'react'; -import { ResolveTypegenMeta } from 'xstate'; -import { ServiceMap } from 'xstate'; -import type { StateFrom } from 'xstate'; -import { StateMachine } from 'xstate'; -import { TypegenDisabled } from 'xstate'; +import type { SnapshotFrom } from 'xstate'; +import type { StateValue } from 'xstate'; import * as Z from 'zod'; // @public (undocumented) export type Actions = (args: { - send: InterpreterFrom["send"]; + send: ActorRefFrom["send"]; selectors: TSelectorsOutput; }) => TOut; // @public -export type ActionsFrom = TMachine extends StateMachine ? TMeta extends { - meta: { - actions: infer TOut; - }; -} ? TOut extends (...args: any) => any ? ReturnType : never : never : never; - -// @public (undocumented) -export type AnyActions = (send: any, selectors: any) => any; +export type ActionsFrom = TMachine["_xstateTree"] extends XstateTreeMachineStateSchemaV2 ? TOut : never; // @public (undocumented) export type AnyRoute = { @@ -59,10 +46,7 @@ export type AnyRoute = { }; // @public (undocumented) -export type AnySelector = V1Selectors; - -// @public (undocumented) -export type AnyXstateTreeMachine = StateMachine | XstateTreeMachineStateSchemaV2, any>; +export type AnyXstateTreeMachine = XstateTreeMachine; // @public (undocumented) export type ArgumentsForRoute = T extends Route ? RouteArguments : never; @@ -70,9 +54,6 @@ export type ArgumentsForRoute = T extends Route["send"]>(__machine: TMachine, __selectors: TSelectors, actions: (send: TSend, selectors: OutputFromSelector) => TActions): (send: TSend, selectors: OutputFromSelector) => TActions; - // @public export function buildCreateRoute(history: () => XstateTreeHistory, basePath: string): { simpleRoute(baseRoute?: TBaseRoute | undefined): string; getQueryString?: () => string; }): { - (): JSX.Element | null; + (): JSX.Element; rootMachine: AnyXstateTreeMachine; }; // @public export function buildRoutingMachine(_routes: TRoutes, mappings: Record): AnyXstateTreeMachine; -// Warning: (ae-incompatible-release-tags) The symbol "buildSelectors" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal -// Warning: (ae-incompatible-release-tags) The symbol "buildSelectors" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal -// -// @public @deprecated -export function buildSelectors>(__machine: TMachine, selectors: (ctx: TContext, canHandleEvent: CanHandleEvent, inState: MatchesFrom, __currentState: never) => TSelectors): V1Selectors, TSelectors, MatchesFrom>; - -// @public -export function buildTestRootComponent>(machine: StateMachine | XstateTreeMachineStateSchemaV2, EventFrom>, logger: typeof console.log): { - rootComponent: () => JSX.Element | null; - addTransitionListener: (listener: () => void) => void; - awaitTransition(): Promise; -}; - -// Warning: (ae-incompatible-release-tags) The symbol "buildView" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal -// -// @public @deprecated -export function buildView, TViewProps = ViewProps, TActions, TSlots, TMatches>, TSend = (send: TEvent) => void>(__machine: TMachine, __selectors: TSelectors, __actions: (send: TSend, selectors: OutputFromSelector) => TActions, __slots: TSlots, view: React_2.ComponentType): React_2.ComponentType; - -// Warning: (ae-forgotten-export) The symbol "InferViewProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "PropsOf" needs to be exported by the entry point index.d.ts -// -// @public -export function buildViewProps>(_view: C, props: Pick>, "actions" | "selectors">): InferViewProps>; - -// @public @deprecated -export function buildXStateTreeMachine(machine: TMachine, meta: XStateTreeMachineMetaV1): StateMachine, XstateTreeMachineStateSchemaV1, EventFrom, any, any, any, any>; - // Warning: (ae-internal-missing-underscore) The name "CanHandleEvent" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) export type CanHandleEvent = (e: EventFrom) => boolean; // @public -export function createXStateTreeMachine, TActionsOutput = Record, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta): StateMachine, XstateTreeMachineStateSchemaV2, EventFrom, any, any, any, any>; +export function createXStateTreeMachine, TActionsOutput = Record, TSlots extends readonly Slot[] = []>(machine: TMachine, options: V2BuilderMeta): XstateTreeMachine; // @public export const genericSlotsTestingDummy: any; @@ -180,12 +134,9 @@ export type GlobalEvents = { }[keyof XstateTreeEvents]; // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "Context" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "Events" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "States" needs to be exported by the entry point index.d.ts // // @public -export function lazy(factory: () => Promise, { Loader, withContext, }?: Options): StateMachine; +export function lazy(factory: () => Promise, { Loader, input }?: Options): AnyXstateTreeMachine; // Warning: (ae-forgotten-export) The symbol "LinkInner" needs to be exported by the entry point index.d.ts // @@ -219,10 +170,11 @@ export function loggingMetaOptions(ignore }; }; +// Warning: (ae-forgotten-export) The symbol "ToStatePaths" needs to be exported by the entry point index.d.ts // Warning: (ae-internal-missing-underscore) The name "MatchesFrom" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) -export type MatchesFrom = StateFrom["matches"]; +export type MatchesFrom = (value: ToStatePaths["value"]>) => boolean; // Warning: (ae-forgotten-export) The symbol "Return" needs to be exported by the entry point index.d.ts // @@ -247,9 +199,6 @@ export function multiSlot(name: T): MultiSlot; // @public export function onBroadcast(handler: (event: GlobalEvents) => void): () => void; -// @public (undocumented) -export type OutputFromSelector = T extends V1Selectors ? O : never; - // @public export type Params = T extends { params: infer TParams; @@ -351,11 +300,7 @@ export type Selectors = (args: { }) => TOut; // @public -export type SelectorsFrom = TMachine extends StateMachine ? TMeta extends { - meta: { - selectors: infer TOut; - }; -} ? TOut extends (...args: any) => any ? ReturnType : never : never : never; +export type SelectorsFrom = TMachine["_xstateTree"] extends XstateTreeMachineStateSchemaV2 ? TOut : never; // @public (undocumented) export type SharedMeta = { @@ -378,12 +323,6 @@ export function singleSlot(name: T): SingleSlot; // @public (undocumented) export type Slot = SingleSlot | MultiSlot; -// @public -export function slotTestingDummyFactory(name: string): StateMachine>, () => {}, () => {}>, AnyEventObject, any, any, any, any>; - // @public (undocumented) export enum SlotType { // (undocumented) @@ -397,13 +336,13 @@ export type StyledLink = (props: Link // @public export function TestRoutingContext({ activeRouteEvents, children, }: { - activeRouteEvents: RoutingEvent[]; + activeRouteEvents: RoutingEvent[]; children: React_2.ReactNode; }): JSX.Element; // @public export function useActiveRouteEvents(): { - type: unknown; + type: string; originalUrl: string; params: unknown; query: unknown; @@ -416,9 +355,6 @@ export function useIsRouteActive(...routes: AnyRoute[]): boolean; // @public export function useRouteArgsIfActive(route: TRoute): ArgumentsForRoute | undefined; -// @public (undocumented) -export type V1Selectors = (ctx: TContext, canHandleEvent: (e: TEvent) => boolean, inState: TMatches, __currentState: never) => TSelectors; - // @public (undocumented) export type V2BuilderMeta, TActionsOutput = Record, TSlots extends readonly Slot[] = Slot[]> = { selectors?: Selectors; @@ -434,14 +370,6 @@ export type View; -// @public (undocumented) -export type ViewProps = { - slots: Record, React_2.ComponentType>; - actions: TActions; - selectors: TSelectors; - inState: TMatches; -}; - // @public export function viewToMachine(view: () => JSX.Element): AnyXstateTreeMachine; @@ -451,37 +379,28 @@ export type XstateTreeHistory = History_2<{ previousUrl?: string; }>; +// Warning: (ae-incompatible-release-tags) The symbol "XstateTreeMachine" is marked as @public, but its signature references "XstateTreeMachineInjection" which is marked as @internal +// // @public (undocumented) -export type XStateTreeMachineMetaV1 = { - slots: TSlots; - view: React_2.ComponentType, ReturnType, TSlots, MatchesFrom>>; - selectors: TSelectors; - actions: TActions; - xstateTreeMachine?: true; -}; +export type XstateTreeMachine = TMachine & XstateTreeMachineInjection; -// @public (undocumented) -export type XstateTreeMachineStateSchemaV1 = { - meta: XStateTreeMachineMetaV1 & { - builderVersion: 1; - }; +// Warning: (ae-internal-missing-underscore) The name "XstateTreeMachineInjection" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export type XstateTreeMachineInjection = { + _xstateTree: XstateTreeMachineStateSchemaV2; }; // @public (undocumented) -export type XstateTreeMachineStateSchemaV2, TActionsOutput = Record, TSlots extends readonly Slot[] = Slot[]> = { - meta: Required & { - builderVersion: 2; - }>; -}; +export type XstateTreeMachineStateSchemaV2, TActionsOutput = Record, TSlots extends readonly Slot[] = Slot[]> = Required>; // Warnings were encountered during analysis: // // src/routing/createRoute/createRoute.ts:279:19 - (ae-forgotten-export) The symbol "MergeRouteTypes" needs to be exported by the entry point index.d.ts // src/routing/createRoute/createRoute.ts:279:19 - (ae-forgotten-export) The symbol "ResolveZodType" needs to be exported by the entry point index.d.ts // src/routing/createRoute/createRoute.ts:316:9 - (ae-forgotten-export) The symbol "RouteRedirect" needs to be exported by the entry point index.d.ts -// src/types.ts:25:3 - (ae-incompatible-release-tags) The symbol "view" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal -// src/types.ts:172:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal -// src/types.ts:173:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal +// src/types.ts:117:3 - (ae-incompatible-release-tags) The symbol "canHandleEvent" is marked as @public, but its signature references "CanHandleEvent" which is marked as @internal +// src/types.ts:118:3 - (ae-incompatible-release-tags) The symbol "inState" is marked as @public, but its signature references "MatchesFrom" which is marked as @internal // (No @packageDocumentation comment for this package)