nightire / ember-statechart-component

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

Repository from Github https://github.comnightire/ember-statechart-componentRepository from Github https://github.comnightire/ember-statechart-component

ember-statechart-component

CI npm version

Use XState Machines as components.

Installation

ember install ember-statechart-component
# or
npm install ember-statechart-component
# or
yarn add ember-statechart-component

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

ember install ember-functions-as-helper-polyfill
# or
npm install ember-functions-as-helper-polyfill
# or
yarn add ember-functions-as-helper-polyfill

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 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>

API

@config

This argument allows you to pass a MachineConfig 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.js v3.25 or above
  • Node.js v12 or above
  • A browser that supports Proxy

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

About

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

License:MIT License


Languages

Language:TypeScript 69.6%Language:JavaScript 25.1%Language:HTML 4.9%Language:Handlebars 0.4%