Giter Site home page Giter Site logo

nullvoxpopuli / ember-statechart-component Goto Github PK

View Code? Open in Web Editor NEW
29.0 3.0 6.0 5.81 MB

Statecharts as components. No classes. Pure declarative state transitions.

License: MIT License

JavaScript 20.20% HTML 4.40% Handlebars 0.02% TypeScript 75.38%
ember emberjs xstate statechart statemachine hacktoberfest

ember-statechart-component's Introduction

ember-statechart-component

CI npm version

Use XState Machines as components.

Support

  • XState >= 5
  • TypeScript >= 5
  • ember-source >= 5.1
  • Glint >= 1.2.1

Installation

npm install ember-statechart-component

To be able to use XState state.matches method in our templates, we will first need [email protected]+ or a HelperManager for handling vanilla functions. ember-functions-as-helper-polyfill provides one:

npm install ember-functions-as-helper-polyfill

In app/app.js / app/app.ts, a one time setup function will need to be called so that the ComponentManager is registered.

import Application from '@ember/application';

import config from 'ember-app/config/environment';
import loadInitializers from 'ember-load-initializers';
import Resolver from 'ember-resolver';

import { setupComponentMachines } from 'ember-statechart-component';

export default class App extends Application {
  modulePrefix = config.modulePrefix;
  podModulePrefix = config.podModulePrefix;
  Resolver = Resolver;
}

loadInitializers(App, config.modulePrefix);

setupComponentMachines();

Usage

Example with Ember Octane

// app/components/toggle.js
import { createMachine } from 'xstate';

export default createMachine({
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } },
  },
});

Usage:

<Toggle as |state send|>
  {{state.value}}

  <button {{on 'click' (fn send 'TOGGLE')}}>
    Toggle
  </button>
</Toggle>

The default template for every createMachine(..) is

{{yield this.state this.send}}

but that can be overriden to suit your needs by defining your own template. The this is an instance of the XState Interpreter

Accessing EmberJS Services

// app/components/authenticated-toggle.js
import { getService } from 'ember-statechart-component';
import { createMachine } from 'xstate';

export default createMachine({
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: [
          {
            target: 'active',
            cond: 'isAuthenticated',
          },
          { actions: ['notify'] },
        ],
      },
    },
    active: { on: { TOGGLE: 'inactive' } },
  },
}, {
  actions: {
    notify: (ctx) => {
      getService(ctx, 'toasts').notify('You must be logged in');
    },
  },
  guards: {
    isAuthenticated: (ctx) => getService(ctx, 'session').isAuthenticated,
  },
});

Usage:

<AuthenticatedToggle as |state send|>
  {{state.value}}

  <button {{on 'click' (fn send 'TOGGLE')}}>
    Toggle
  </button>
</AuthenticatedToggle>

Matching States

<Toggle as |state send|>
  {{#if (state.matches 'inactive')}}
    The inactive state
  {{else if (state.matches 'active')}}
    The active state
  {{else}}
    Unknown state
  {{/if}}

  <button {{on 'click' (fn send 'TOGGLE')}}>
    Toggle
  </button>
</Toggle>

Glint

Having type checking with these state machines can be done automatically after importing the /glint file in your types/<app-name>/glint-registry.d.ts.

import "@glint/environment-ember-loose";
import "@glint/environment-ember-loose/native-integration";
import "ember-page-title/glint";

// This import extends the type of `StateMachine` to be glint-compatible
import 'ember-statechart-component/glint';

declare module "@glint/environment-ember-loose/registry" {
  export default interface Registry {
    // How to define globals from external addons
  }
}

API

@config

This argument allows you to pass a MachineOptions for actions, services, guards, etc.

Usage:

Toggle machine that needs a config
// app/components/toggle.js
import { createMachine, assign } from 'xstate';

export default createMachine({
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: {
      on: {
        TOGGLE: {
          target: 'inactive',
          actions: ['toggleIsOn']
        }
      }
    },
  },
});
<Toggle
  @config={{hash
    actions=(hash
      toggleIsOn=@onRoomIlluminated
    )
  }}
as |state send|>
  <button {{on 'click' (fn send 'TOGGLE')}}>
    Toggle
  </button>
</Toggle>

@context

Sets the initial context. The current value of the context can then be accessed via state.context.

Usage:

Toggle machine that interacts with context
// app/components/toggle.js
import { createMachine, assign } from 'xstate';

export default createMachine({
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: {
          target: 'active',
          actions: ['increaseCounter']
        }
      }
    },
    active: {
      on: {
        TOGGLE: {
          target: 'inactive',
          actions: ['increaseCounter']
        }
      }
    },
  },
}, {
  actions: {
    increaseCounter: assign({
      counter: (context) => context.counter + 1
    })
  }
});
<Toggle @context=(hash counter=0) as |state send|>
  <button {{on 'click' (fn send 'TOGGLE')}}>
    Toggle
  </button>

  <p>
    Toggled: {{state.context.counter}} times.
  </p>
</Toggle>

@state

The machine will use @state as the initial state. Any changes to this argument are not automatically propagated to the machine. An ARGS_UPDATE event (see details below) is sent instead.

What happens if any of the passed args change?

An event will be sent to the machine for you, ARGS_UPDATE, along with all named arguments used to invoke the component.

Compatibility

  • ember-source v3.28+
  • typescript v4.5+
  • ember-auto-import v2+
  • A browser that supports Proxy
  • Glint 0.8.3+
    • Note that updates to glint support will not be covered by this library's adherance to SemVer. All glint-related updates will be bugfixes until Glint is declared stable.

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

ember-statechart-component's People

Contributors

ember-tomster avatar lgtm-migrator avatar michalbryxi avatar nightire avatar nullvoxpopuli avatar renovate-bot avatar renovate[bot] avatar rwjblue avatar semantic-release-bot avatar sergeastapov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ember-statechart-component's Issues

More-native Glint integration?

I don't have a deep enough understanding of how XState's types work to provide super precise input here, but based on a shallow reading it seems like it should be possible to avoid needing the asComponent function here, instead doing something like this in src/glint.ts:

declare module 'xstate' {
  export interface StateMachine<...> /* or maybe `StateNode`? */ 
    extends ComponentLike<{
      // derived from the zillion type params on `StateMachine`
    }> {}
}

// Maintain as just the identity function for backwards compatibility
export function asComponent<T extends AnyStateMachine>(machine: T): T {
  return machine;
}

Then downstream consumers would only need to import 'ember-statechart-component/glint' to "activate" the Glint integration and their other code could stay as-is.

Absorb noisy state updates from XState so that consumers of ember-statechart-component can have fine-grained updates

From this twitter thread: https://twitter.com/nullvoxpopuli/status/1673077290593554434

The solid.js implementation augments the state from the interpreter instance https://github.com/statelyai/xstate/blob/740409e549ea91974b7fa8ef63438341958dafd9/packages/xstate-solid/src/useMachine.ts#L38C29-L38C44

via this createImmutable.

Note that the concept of createImmutable should exist as a util in ember-resources https://github.com/NullVoxPopuli/ember-resources/

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Location: .github/renovate.json5
Error type: Invalid JSON5 (parsing failed)
Message: JSON5.parse error: JSON5: invalid character '<' at 12:1

README should be specific

The paragraph:

XState provides its own matches
method which is available on the state object.
We can utilize this provided there exists a HelperManager for
handling vanilla functions, such as what
ember-could-get-used-to-this
provides.

Left me wondering what am I supposed to do? Install that addon? Install & configure something somewhere? What is HelperManger anyway? Why should I care?

Proposal:

  • Tell user what to do with a good default option
  • Explain in the notes why was this necessary

Is @config really MachineConfig?

The README says:

@config

This argument allows you to pass a MachineConfig for actions, services, guards, etc...

When I look up MachineConfig in the xstate docs I will get to: https://xstate.js.org/docs/packages/xstate-fsm/#machine-config which is defined as:

- id (string) - an identifier for the type of machine this is. Useful for debugging.
- initial (string) - the key of the initial state.
- [states](https://xstate.js.org/docs/packages/xstate-fsm/#state-config) (object) - an object mapping state names (keys) to states
[#](https://xstate.js.org/docs/packages/xstate-fsm/#state-config)

Which does not have actions key as shown in the @config examples:

<Toggle 
  @config={{hash 
    actions=(hash 
      toggleIsOn=@onRoomIlluminated
    )
  }} 

So I think what @config is actually referring to is MachineOptions. Guessing based on createMachine syntax and knowledge how this fits together. So the docs wording should be changed:

#### `@config`
This argument allows you to pass a [MachineOptions](https://xstate.js.org/docs/packages/xstate-fsm/#machine-options) for ...

Provide example for `@config`

  • I think it would be nice to point out what part of xstate createMachine call this mirrors.
  • It expects hash within a hash (at least for actions), which is not that intuitive syntax, so an example would be nice.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Update dependency @types/eslint to v8
  • Update peter-evans/create-pull-request action to v6
  • Update pnpm to v9
  • ๐Ÿ” Create all rate-limited PRs at once ๐Ÿ”

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/upload-artifact v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/download-artifact v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/download-artifact v3
  • actions/checkout v4
  • wyvox/action-setup-pnpm v3
  • actions/download-artifact v3
  • actions/checkout v3
  • wyvox/action-setup-pnpm v3
  • actions/download-artifact v3
.github/workflows/codeql.yml
  • actions/checkout v3
  • github/codeql-action v2
  • github/codeql-action v2
  • github/codeql-action v2
.github/workflows/plan-release.yml
  • actions/checkout v4
  • actions/checkout v4
  • actions/setup-node v4
  • pnpm/action-setup v2
  • peter-evans/create-pull-request v5
.github/workflows/publish.yml
  • actions/checkout v4
  • actions/checkout v4
  • actions/setup-node v4
  • pnpm/action-setup v2
npm
ember-statechart-component/package.json
  • @babel/core 7.23.6
  • @babel/eslint-parser ^7.23.3
  • @babel/plugin-proposal-class-properties 7.18.6
  • @babel/plugin-syntax-decorators 7.23.3
  • @babel/plugin-transform-typescript ^7.18.6
  • @babel/preset-typescript 7.23.3
  • @embroider/addon-dev 1.8.3
  • @glimmer/compiler ^0.87.1
  • @glimmer/component ^1.0.4
  • @glimmer/env ^0.1.7
  • @glint/template ^1.2.1
  • @nullvoxpopuli/eslint-configs ^3.2.2
  • @types/ember ^4.0.0
  • @types/ember__application ^4.0.0
  • @types/ember__component ^4.0.0
  • @types/ember__debug ^4.0.0
  • @types/ember__destroyable ^4.0.0
  • @types/ember__routing ^4.0.0
  • @types/ember__runloop ^4.0.0
  • @types/ember__service ^4.0.0
  • @typescript-eslint/eslint-plugin ^6.16.0
  • @typescript-eslint/parser ^6.16.0
  • babel-eslint 10.1.0
  • concurrently ^8.2.2
  • ember-cli-htmlbars ^6.0.0
  • ember-template-lint 5.13.0
  • eslint ^8.56.0
  • eslint-config-prettier ^9.1.0
  • eslint-plugin-decorator-position ^5.0.2
  • eslint-plugin-ember ^11.12.0
  • eslint-plugin-import ^2.24.1
  • eslint-plugin-json ^3.1.0
  • eslint-plugin-node ^11.1.0
  • eslint-plugin-prettier ^5.1.2
  • eslint-plugin-simple-import-sort ^10.0.0
  • npm-run-all 4.1.5
  • prettier ^3.1.1
  • rollup 2.78.1
  • rollup-plugin-ts ^3.0.2
  • typescript ^4.4.2
  • xstate ^5.4.1
  • npm 8.18.0
package.json
  • ember-template-lint ^5.13.0
  • eslint ^8.56.0
  • eslint-config-prettier ^9.1.0
  • eslint-plugin-ember ^11.12.0
  • eslint-plugin-node ^11.1.0
  • eslint-plugin-prettier ^5.1.2
  • npm-run-all ^4.1.5
  • prettier ^3.1.1
  • release-plan ^0.6.0
  • node 20.10.0
  • pnpm 8.13.1
  • pnpm 8.12.1
  • @types/eslint 7.29.0
testing/ember-app/package.json

  • Check this box to trigger a request for Renovate to run again on this repository

How to use arguments?

Hello there!

Would it be possible to use arguments with a statechart component?

If yes, then how?

Contradiction?

These two sentences:

... Any changes to this argument are ignored.

And:

What happens if any of the passed args change? An event will be sent to the machine for you...

Seem to contradict each other.

How can I use `@context`?

  • The API section says that @context exists. It's obvious to some Ember users that you mean <Toggle @context=..., but not to all an example how to pass in context would be good
  • Can I then grab the mutated @context data back in my template? How?

Context should be merged, not replaced

Assuming:

const machine = createMachine(
  {
    id: 'foo',
    states: {
      ...
    },
    context: {
      foo: 'a',
    },
  }, {
    guards: {
      myGuard(context) {
        console.log({ context });
      },
  }
)

And:

<Machine
  @context={{hash
    bar='b'
  }}
  as |state send|
>
</Machine>

I would expect to see:

{ context: { foo: 'a', bar: 'b' } }

But instead I see:

{ context: { bar: 'b' } }

So it seems to me like the @context replaces the context defined on the machine?

I think merging is preferred, because this:

  1. Allows the developer to define the context of the machine correctly so that it can work correctly in the visualizer
  2. Allows the developer to define good default values
  3. Allows at the call site to override only those context variables that it cares about

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.