Last active
May 9, 2024 23:51
-
-
Save sukima/43d64f6c96d05a40393d21df09107d21 to your computer and use it in GitHub Desktop.
XState Decorators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const CURRENT_STATE = Symbol('current state'); | |
export const STATE_PROPS = Symbol('state properties'); | |
export const MACHINE = Symbol('machine'); | |
export const LISTENERS = Symbol('listeners'); | |
export const CONFIG = Symbol('config'); | |
export const CONTEXT = Symbol('context'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Controller from '@ember/controller'; | |
import { useMachine, machineState, transition } from '../ember-xstate'; | |
const { Machine } = XState; | |
// Only one machine per class. If you want more then manage multiple EmberObjects with machines and/or dive into XState itself. | |
@useMachine(Machine({ | |
id: 'example', | |
initial: 'green', | |
states: { | |
'green': { | |
on: { SWITCH: 'yellow' } | |
}, | |
'yellow': { | |
on: { SWITCH: 'red' } | |
}, | |
'red': { | |
on: { SWITCH: 'green' } | |
} | |
} | |
})) | |
export default class ApplicationController extends Controller { | |
@machineState myState; | |
@transition | |
switch(send) { | |
send('SWITCH'); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
CURRENT_STATE, | |
STATE_PROPS, | |
MACHINE, | |
INTERPRETER, | |
LISTENERS, | |
CONFIG, | |
CONTEXT | |
} from './-private'; | |
import { action } from '@ember/object'; | |
import { tracked } from '@glimmer/tracking'; | |
import { stateComparators } from './utils/state-comparators'; | |
const { interpret } = XState; | |
export function useMachine(machine) { | |
return function decorator(Klass) { | |
class EmberXstateWrapper extends Klass { | |
constructor() { | |
super(...arguments); | |
this[MACHINE] = machine | |
.withContext(this[Klass[CONTEXT]]?.() ?? machine.context) | |
.withConfig(this[Klass[CONFIG]]?.() ?? {}); | |
this[INTERPRETER] = interpret(this[MACHINE]) | |
.onTransition(state => { | |
this[CURRENT_STATE] = { | |
...state, | |
valueStrings: state.toStrings().join(' '), | |
in: stateComparators(state.value) | |
}; | |
Klass[STATE_PROPS]?.forEach(prop => { | |
this[prop] = this[CURRENT_STATE]; | |
}); | |
}); | |
Klass[LISTENERS]?.forEach(method => { | |
this[INTERPRETER].onTransition(state => this[method](state)); | |
}); | |
this[INTERPRETER].start(); | |
} | |
willDestroy() { | |
super.willDestroy(...arguments); | |
this[INTERPRETER].stop(); | |
} | |
} | |
return EmberXstateWrapper; | |
}; | |
} | |
export function machineState(target, name, descriptor) { | |
target.constructor[STATE_PROPS] = target.constructor[STATE_PROPS] ?? []; | |
target.constructor[STATE_PROPS].push(name); | |
return tracked(target, name, descriptor); | |
} | |
export function withConfig(target, name, descriptor) { | |
target.constructor[CONFIG] = name; | |
return descriptor; | |
} | |
export function withContext(target, name, descriptor) { | |
target.constructor[CONTEXT] = name; | |
return descriptor; | |
} | |
export function onTransition(target, name, descriptor) { | |
target.constructor[LISTENERS] = target.constructor[LISTENERS] ?? []; | |
target.constructor[LISTENERS].push(name); | |
return descriptor; | |
} | |
export function transition(target, name, descriptor) { | |
let originalFn = descriptor.value; | |
descriptor.value = function(...args) { | |
return originalFn.call(this, this[INTERPRETER].send, ...args); | |
} | |
return action(target, name, descriptor); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { helper } from '@ember/component/helper'; | |
import { INTERPRETER } from '../-private'; | |
export function transition([target, ...curryArgs]) { | |
return function(...args) { | |
return target[INTERPRETER].send(...curryArgs, ...args); | |
}; | |
} | |
export default helper(transition); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
background-color: #eee; | |
} | |
code: { | |
text-shadow: 4px 4px 4px 4px #000; | |
} | |
[data-state~="green"] { | |
color: green; | |
} | |
[data-state~="yellow"] { | |
color: yellow; | |
} | |
[data-state~="red"] { | |
color: red; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"version": "0.17.0", | |
"EmberENV": { | |
"FEATURES": {}, | |
"_TEMPLATE_ONLY_GLIMMER_COMPONENTS": false, | |
"_APPLICATION_TEMPLATE_WRAPPER": true, | |
"_JQUERY_INTEGRATION": true | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js", | |
"xstate": "https://unpkg.com/xstate@4/dist/xstate.js", | |
"ember": "3.17.0", | |
"ember-template-compiler": "3.17.0", | |
"ember-testing": "3.17.0" | |
}, | |
"addons": { | |
"@glimmer/component": "1.0.0" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { typeOf } from '@ember/utils'; | |
export function stateComparators(stateValue) { | |
let foundKeys = []; | |
function recurse(states) { | |
if (typeOf(states) === 'string') { | |
foundKeys.push(states); | |
return { [states]: true }; | |
} | |
let comparator = {}; | |
for (let [key, value] of Object.entries(states)) { | |
foundKeys.push(key); | |
comparator[key] = recurse(value); | |
} | |
return comparator; | |
} | |
let top = recurse(stateValue); | |
for (let key of foundKeys) { | |
top[key] = top[key] ?? true; | |
} | |
return top; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment