ui: Gradual deprecation of old StateChart interface (#13604)

This commit is contained in:
John Cowen 2022-07-04 11:22:14 +01:00 committed by GitHub
parent 07e4e6bd7d
commit ea33bc249c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 43 deletions

View File

@ -3,6 +3,8 @@ class: ember
---
# StateChart
**This component is deprecated and is currently undergoing an interface change in <StateMachine />**
```hbs
<StateChart
@chart={{xstateStateChartObject}}

View File

@ -0,0 +1,61 @@
---
class: ember
---
# StateMachine
```hbs
<StateMachine
@chart={{xstateStateChartObject}}
as |fsm|>
</StateMachine>
```
`<StateMachine />` is a renderless component that eases rendering of different states
from within templates using XState State Machine and Statechart objects.
**Please note**: This component is currently a gradual replacement for
StateChart, which has a less ergonmic interface. Guard and Action component are
currently omitted due to a preference to use entries to define those instead of
components (which is WIP)
### Arguments
| Argument/Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `chart` | `object` | | An xstate statechart/state machine object |
| `initial` | `String` | The initial value of the state chart itself | The initial state of the machine/chart (defaults to whatever is defined on the object itself) |
The component currently yields 1 contextual components:
- `<fsm.State />`: Used for rendering matching certain states ([also see State Component](../state/README.mdx))
and 2 further objects:
- `fsm.dispatch`: An action to dispatch an xstate event
- `fsm.state`: The state object itself for usage in the `state-matches` helper
### Example
```hbs
<StateMachine
@chart={{xstateStateChartObject}}
as |fsm|>
<fsm.State @matches="idle">
Currently Idle
</fsm.State>
<fsm.State @matches="loading">
Currently Loading
</fsm.State>
<fsm.State @matches={{array 'loading' 'idle'}}>
Idle and loading
<button disabled={{state-matches fsm.state "loading"}} onclick={{action fsm.dispatch "START"}}>Load</button>
</fsm.State>
</StateMachine>
```
### See
- [Component Source Code](./index.js)
- [Template Source Code](./index.hbs)
---

View File

@ -0,0 +1,7 @@
{{yield
(hash
State=(component 'state' state=this.state)
dispatch=(action 'dispatch')
state=this.state
)
}}

View File

@ -0,0 +1,4 @@
import Component from '../state-chart/index';
export default Component.extend({});

View File

@ -1,6 +1,3 @@
---
class: ember
---
# State
`<State @state={{matchableStateObject}} @matches="idle">Currently Idle</State>`

View File

@ -1,3 +1,5 @@
{{#if rendering}}
{{did-insert this.attributeChanged @state @matches @notMatches}}
{{did-update this.attributeChanged @state @matches @notMatches}}
{{#if render}}
{{yield}}
{{/if}}

View File

@ -1,20 +1,20 @@
import Component from '@ember/component';
import { set } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default Component.extend({
service: service('state'),
tagName: '',
didReceiveAttrs: function() {
if (typeof this.state === 'undefined') {
export default class State extends Component {
@service('state') state;
@tracked render = false;
@action
attributeChanged([state, matches, notMatches]) {
if (typeof state === 'undefined') {
return;
}
let match = true;
if (typeof this.matches !== 'undefined') {
match = this.service.matches(this.state, this.matches);
} else if (typeof this.notMatches !== 'undefined') {
match = !this.service.matches(this.state, this.notMatches);
this.render = typeof matches !== 'undefined' ?
this.state.matches(state, matches) :
!this.state.matches(state, notMatches);
}
}
set(this, 'rendering', match);
},
});

View File

@ -1,27 +1,11 @@
import { helper } from '@ember/component/helper';
import require from 'require';
import { css } from '@lit/reactive-element';
import resolve from 'consul-ui/utils/path/resolve';
import panel from 'consul-ui/components/panel/index.css';
import distributionMeter from 'consul-ui/components/distribution-meter/index.css';
import distributionMeterMeter from 'consul-ui/components/distribution-meter/meter/index.css';
import distributionMeterMeterElement from 'consul-ui/components/distribution-meter/meter/element';
import visuallyHidden from 'consul-ui/styles/base/decoration/visually-hidden.css';
import baseKeyframes from 'consul-ui/styles/base/icons/base-keyframes.css';
import chevronDown from 'consul-ui/styles/base/icons/icons/chevron-down/index.css';
const fs = {
['/components/panel/index.css']: panel,
['/components/distribution-meter/index.css']: distributionMeter,
['/components/distribution-meter/meter/index.css']: distributionMeterMeter,
['/components/distribution-meter/meter/element']: distributionMeterMeterElement,
['/styles/base/decoration/visually-hidden.css']: visuallyHidden,
['/styles/base/icons/base-keyframes.css']: baseKeyframes,
['/styles/base/icons/icons/chevron-down/index.css']: chevronDown
};
const appName = 'consul-ui';
const container = new Map();
@ -29,17 +13,27 @@ const container = new Map();
// we get the advantage of laziness here, i.e. we only call css as and when we
// need to
export default helper(([path = ''], { from }) => {
const fullPath = resolve(from, path);
const fullPath = resolve(`${appName}${from}`, path);
let module;
if(require.has(fullPath)) {
module = require(fullPath).default;
} else {
throw new Error(`Unable to resolve '${fullPath}' does the file exist?`)
}
switch(true) {
case fullPath.endsWith('.css'):
return fs[fullPath](css)
return module(css);
case fullPath.endsWith('.xstate'):
return module;
default: {
if(container.has(fullPath)) {
return container.get(fullPath);
}
const module = fs[fullPath](HTMLElement);
container.set(fullPath, module);
return module;
const component = module(HTMLElement);
container.set(fullPath, component);
return component;
}
}
});