├── vendor
└── .gitkeep
├── packages
├── site
│ ├── app
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ └── application.ts
│ │ ├── components
│ │ │ ├── .gitkeep
│ │ │ ├── ui-loading.hbs
│ │ │ ├── docs-header.js
│ │ │ ├── ui-button.hbs
│ │ │ ├── api-toc.js
│ │ │ ├── mobile-docs-toc.ts
│ │ │ ├── docs-viewer.js
│ │ │ ├── typed-button.hbs
│ │ │ ├── quickstart-button.hbs
│ │ │ ├── quickstart-button-refined.hbs
│ │ │ ├── page-headings.ts
│ │ │ ├── docs
│ │ │ │ ├── quickstart-guide.js
│ │ │ │ ├── typescript-usage.js
│ │ │ │ ├── quickstart-final.js
│ │ │ │ ├── quickstart-guide.hbs
│ │ │ │ ├── typescript-usage.hbs
│ │ │ │ └── quickstart-final.hbs
│ │ │ ├── counter.hbs
│ │ │ ├── counter-restart.hbs
│ │ │ ├── mobile-docs-toc.hbs
│ │ │ ├── counter-restart.js
│ │ │ ├── docs-toc.hbs
│ │ │ ├── counter.js
│ │ │ ├── demo.hbs
│ │ │ ├── ui-intro.md
│ │ │ ├── api-toc.hbs
│ │ │ ├── quickstart-button.js
│ │ │ ├── page-headings.hbs
│ │ │ ├── docs-header.hbs
│ │ │ ├── quickstart-button-refined.js
│ │ │ └── typed-button.ts
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ ├── docs
│ │ │ │ └── statecharts.js
│ │ │ └── docs.ts
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── router.js
│ │ ├── snippets
│ │ │ └── quickstart
│ │ │ │ ├── onExit.js
│ │ │ │ ├── onEntry.js
│ │ │ │ └── transition.js
│ │ ├── app.js
│ │ ├── config
│ │ │ └── environment.d.ts
│ │ ├── index.html
│ │ ├── machines
│ │ │ ├── quickstart-button.js
│ │ │ ├── counter-machine.js
│ │ │ ├── quickstart-button-refined.js
│ │ │ └── typed-button.ts
│ │ ├── utils
│ │ │ └── scroll-to.ts
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── templates
│ │ │ └── docs.hbs
│ │ └── modifiers
│ │ │ └── intersect-headings.js
│ ├── tests
│ │ ├── unit
│ │ │ └── .gitkeep
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── integration
│ │ │ ├── .gitkeep
│ │ │ └── components
│ │ │ │ └── x-button-test.js
│ │ ├── test-helper.js
│ │ └── index.html
│ ├── public
│ │ ├── CNAME
│ │ ├── robots.txt
│ │ ├── tutorial.png
│ │ ├── xstate-viz.png
│ │ ├── harel-paper-1.png
│ │ ├── xstate-guides.png
│ │ ├── statecharts-blog.png
│ │ ├── harel-statechart-paper.png
│ │ ├── quote.svg
│ │ ├── effective-ember.svg
│ │ ├── youtube.svg
│ │ ├── discord.svg
│ │ ├── xstate.svg
│ │ ├── ember-statecharts-black.svg
│ │ ├── ember-statecharts-white.svg
│ │ └── statecharts.svg
│ ├── .template-lintrc.js
│ ├── .eslintrc.js
│ ├── lib
│ │ ├── deploy-versioned-doc
│ │ │ ├── package.json
│ │ │ └── index.js
│ │ └── versioned-doc-info.js
│ ├── config
│ │ ├── optional-features.json
│ │ ├── deploy.js
│ │ ├── targets.js
│ │ ├── ember-cli-update.json
│ │ └── environment.js
│ ├── tsconfig.json
│ ├── tailwind.config.js
│ ├── .prettierignore
│ ├── .eslintignore
│ ├── .gitignore
│ ├── testem.js
│ ├── .docfy-config.js
│ ├── ember-cli-build.js
│ └── package.json
├── test-app
│ ├── vendor
│ │ └── .gitkeep
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ ├── .gitkeep
│ │ │ ├── use-machine.hbs
│ │ │ ├── computed.hbs
│ │ │ ├── computed.js
│ │ │ └── use-machine.js
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ └── application.hbs
│ │ ├── router.js
│ │ ├── machines
│ │ │ └── toggle-machine.js
│ │ ├── app.js
│ │ └── index.html
│ ├── tests
│ │ ├── unit
│ │ │ └── .gitkeep
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── integration
│ │ │ └── .gitkeep
│ │ ├── test-helper.js
│ │ ├── index.html
│ │ └── acceptance
│ │ │ └── smoke-test.js
│ ├── .watchmanconfig
│ ├── public
│ │ └── robots.txt
│ ├── .prettierrc.js
│ ├── .template-lintrc.js
│ ├── config
│ │ ├── optional-features.json
│ │ ├── ember-cli-update.json
│ │ ├── targets.js
│ │ ├── environment.js
│ │ └── ember-try.js
│ ├── .ember-cli
│ ├── .prettierignore
│ ├── .eslintignore
│ ├── .editorconfig
│ ├── .travis.yml
│ ├── .gitignore
│ ├── testem.js
│ ├── ember-cli-build.js
│ ├── .eslintrc.js
│ ├── README.md
│ └── package.json
└── ember-statecharts
│ ├── addon
│ ├── .gitkeep
│ ├── utils
│ │ └── statechart.js
│ ├── computed.js
│ ├── -private
│ │ ├── use-resource.ts
│ │ ├── resource.ts
│ │ └── usables.ts
│ └── index.ts
│ ├── app
│ ├── .gitkeep
│ └── utils
│ │ └── statechart.js
│ ├── tests
│ ├── unit
│ │ ├── .gitkeep
│ │ ├── statechart-test.js
│ │ └── computed-test.js
│ ├── helpers
│ │ └── .gitkeep
│ ├── integration
│ │ └── .gitkeep
│ ├── dummy
│ │ ├── app
│ │ │ ├── models
│ │ │ │ └── .gitkeep
│ │ │ ├── routes
│ │ │ │ └── .gitkeep
│ │ │ ├── styles
│ │ │ │ └── app.css
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ ├── controllers
│ │ │ │ └── .gitkeep
│ │ │ ├── helpers
│ │ │ │ └── .gitkeep
│ │ │ ├── templates
│ │ │ │ └── application.hbs
│ │ │ ├── router.js
│ │ │ ├── app.js
│ │ │ ├── config
│ │ │ │ └── environment.d.ts
│ │ │ └── index.html
│ │ ├── public
│ │ │ └── .gitkeep
│ │ └── config
│ │ │ ├── optional-features.json
│ │ │ ├── ember-cli-update.json
│ │ │ ├── targets.js
│ │ │ └── environment.js
│ ├── test-helper.js
│ └── index.html
│ ├── index.js
│ ├── .template-lintrc.js
│ ├── .eslintrc.js
│ ├── types
│ ├── @glimmer
│ │ └── tracking
│ │ │ └── primitives
│ │ │ └── cache
│ │ │ └── index.d.ts
│ └── @ember
│ │ └── helper
│ │ └── index.d.ts
│ ├── config
│ └── environment.js
│ ├── tsconfig.json
│ ├── .prettierignore
│ ├── .eslintignore
│ ├── -private
│ ├── resource.d.ts
│ ├── use-resource.d.ts
│ └── usables.d.ts
│ ├── .gitignore
│ ├── .npmignore
│ ├── testem.js
│ ├── ember-cli-build.js
│ ├── README.md
│ ├── index.d.ts
│ └── package.json
├── .watchmanconfig
├── .prettierrc.js
├── jsconfig.json
├── types
├── dummy
│ └── index.d.ts
├── global.d.ts
└── ember-metrics
│ └── index.d.ts
├── lerna.json
├── .ember-cli
├── .eslintignore
├── .prettierignore
├── .editorconfig
├── .vscode
└── launch.json
├── package.json
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── tsconfig.settings.json
├── .github
└── workflows
│ ├── docs.yml
│ └── ci.yml
├── .eslintrc.base.js
└── README.md
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/test-app/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/site/public/CNAME:
--------------------------------------------------------------------------------
1 | ember-statecharts.com
2 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{outlet}}
2 |
--------------------------------------------------------------------------------
/packages/test-app/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------
/packages/site/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/packages/test-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/types/dummy/index.d.ts:
--------------------------------------------------------------------------------
1 | import 'ember-concurrency-async';
2 | import 'ember-concurrency-ts/async';
3 |
--------------------------------------------------------------------------------
/packages/site/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'octane',
5 | };
6 |
--------------------------------------------------------------------------------
/packages/test-app/.prettierrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/app/utils/statechart.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-statecharts/utils/statechart';
2 |
--------------------------------------------------------------------------------
/packages/site/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: '../../.eslintrc.base.js',
5 | };
6 |
--------------------------------------------------------------------------------
/packages/test-app/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'recommended',
5 | };
6 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: require('./package').name,
5 | };
6 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'recommended',
5 | };
6 |
--------------------------------------------------------------------------------
/packages/site/public/tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/tutorial.png
--------------------------------------------------------------------------------
/packages/ember-statecharts/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: '../../.eslintrc.base.js',
5 | };
6 |
--------------------------------------------------------------------------------
/packages/site/app/components/ui-loading.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/site/public/xstate-viz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/xstate-viz.png
--------------------------------------------------------------------------------
/packages/ember-statecharts/types/@glimmer/tracking/primitives/cache/index.d.ts:
--------------------------------------------------------------------------------
1 | export { createCache, getValue } from '@glimmer/validator';
2 |
--------------------------------------------------------------------------------
/packages/site/public/harel-paper-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/harel-paper-1.png
--------------------------------------------------------------------------------
/packages/site/public/xstate-guides.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/xstate-guides.png
--------------------------------------------------------------------------------
/packages/site/public/statecharts-blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/statecharts-blog.png
--------------------------------------------------------------------------------
/packages/ember-statecharts/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (/* environment, appConfig */) {
4 | return {};
5 | };
6 |
--------------------------------------------------------------------------------
/packages/site/public/harel-statechart-paper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pangratz/ember-statecharts/master/packages/site/public/harel-statechart-paper.png
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "yarn",
3 | "useWorkspaces": true,
4 | "version": "independent",
5 | "packages": [
6 | "packages/*"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs-header.js:
--------------------------------------------------------------------------------
1 | import DocsHeader from 'ember-cli-addon-docs/components/docs-header/component';
2 |
3 | export default DocsHeader;
4 |
--------------------------------------------------------------------------------
/packages/site/lib/deploy-versioned-doc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "deploy-versioned-doc",
3 | "keywords": [
4 | "ember-addon",
5 | "ember-cli-deploy-plugin"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/types/@ember/helper/index.d.ts:
--------------------------------------------------------------------------------
1 | export {
2 | setHelperManager,
3 | helperCapabilities as capabilities,
4 | } from '@glimmer/manager';
5 | export { invokeHelper } from '@glimmer/runtime';
6 |
--------------------------------------------------------------------------------
/packages/site/app/components/ui-button.hbs:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/packages/site/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/packages/test-app/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/packages/site/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.settings.json",
3 | "compilerOptions": {
4 | "composite": true
5 | },
6 | "include": [
7 | "app/**/*",
8 | "types/**/*"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/types/global.d.ts:
--------------------------------------------------------------------------------
1 | // Types for compiled templates
2 | declare module 'ember-statecharts/templates/*' {
3 | import { TemplateFactory } from 'htmlbars-inline-precompile';
4 | const tmpl: TemplateFactory;
5 | export default tmpl;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/packages/test-app/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{page-title 'TestApp'}}
2 |
3 |
4 | Ember statecharts smoke tests
5 |
6 |
7 | {{outlet}}
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/site/app/components/api-toc.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import config from 'site/config/environment';
3 |
4 | export default class ApiTocComponent extends Component {
5 | get rootUrl() {
6 | return config.rootURL;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/test-app/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'test-app/config/environment';
3 |
4 | export default class Router extends EmberRouter {
5 | location = config.locationType;
6 | rootURL = config.rootURL;
7 | }
8 |
9 | Router.map(function () {});
10 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'dummy/config/environment';
3 |
4 | export default class Router extends EmberRouter {
5 | location = config.locationType;
6 | rootURL = config.rootURL;
7 | }
8 |
9 | Router.map(function () {});
10 |
--------------------------------------------------------------------------------
/packages/site/config/deploy.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | module.exports = function (/* deployTarget */) {
5 | let ENV = {
6 | build: {},
7 | git: {
8 | repo: 'git@github.com:pangratz/ember-statecharts.git',
9 | commitMessage: 'chore: deploy %@',
10 | },
11 | };
12 | return ENV;
13 | };
14 |
--------------------------------------------------------------------------------
/packages/test-app/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.settings.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "baseUrl": "."
6 | },
7 | "include": [
8 | "app/**/*",
9 | "addon/**/*",
10 | "tests/**/*",
11 | "types/**/*",
12 | "test-support/**/*",
13 | "addon-test-support/**/*"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/site/app/components/mobile-docs-toc.ts:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { tracked } from '@glimmer/tracking';
3 | import { action } from '@ember/object';
4 |
5 | export default class MobileDocsToc extends Component {
6 | @tracked showToc = false;
7 |
8 | @action toggleToc(): void {
9 | this.showToc = !this.showToc;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/test-app/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
--------------------------------------------------------------------------------
/packages/site/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from 'site/config/environment';
3 | import { addDocfyRoutes } from '@docfy/ember';
4 |
5 | export default class Router extends EmberRouter {
6 | location = config.locationType;
7 | rootURL = config.rootURL;
8 | }
9 |
10 | Router.map(function () {
11 | addDocfyRoutes(this);
12 | });
13 |
--------------------------------------------------------------------------------
/packages/test-app/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .*/
17 | .eslintcache
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /package.json.ember-try
23 |
--------------------------------------------------------------------------------
/types/ember-metrics/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'ember-metrics/services/metrics' {
2 | import Service from '@ember/service';
3 |
4 | export default class Metrics extends Service {
5 | trackEvent(params: Record): void;
6 |
7 | trackPage(params: Record): void;
8 | trackPage(adapterName: string, params: Record): void;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /**/dist/
7 | /**/tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .*/
17 | .eslintcache
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /package.json.ember-try
23 |
--------------------------------------------------------------------------------
/packages/site/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from 'site/app';
2 | import config from 'site/config/environment';
3 | import * as QUnit from 'qunit';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setup } from 'qunit-dom';
6 | import { start } from 'ember-qunit';
7 |
8 | setApplication(Application.create(config.APP));
9 |
10 | setup(QUnit.assert);
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/packages/test-app/app/components/use-machine.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | use-machine
4 |
5 |
8 |
9 | {{#if this.isOn}}
10 | On
11 | {{/if}}
12 | {{#if this.isOff}}
13 | Off
14 | {{/if}}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/test-app/app/components/computed.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | @statechart-computed
4 |
5 |
8 |
9 | {{#if this.isOn}}
10 | On
11 | {{/if}}
12 | {{#if this.isOff}}
13 | Off
14 | {{/if}}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/test-app/app/machines/toggle-machine.js:
--------------------------------------------------------------------------------
1 | import { createMachine } from 'xstate';
2 |
3 | export const toggleConfig = {
4 | initial: 'off',
5 | states: {
6 | off: {
7 | on: {
8 | TOGGLE: 'on',
9 | },
10 | },
11 | on: {
12 | on: {
13 | TOGGLE: 'off',
14 | },
15 | },
16 | },
17 | };
18 |
19 | export default createMachine(toggleConfig);
20 |
--------------------------------------------------------------------------------
/packages/site/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | colors: {
7 | ember: '#e04e39',
8 | },
9 | },
10 | },
11 | variants: {
12 | extend: {},
13 | },
14 | plugins: [
15 | require('@tailwindcss/typography'),
16 | require('@tailwindcss/aspect-ratio'),
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/packages/test-app/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from 'test-app/app';
2 | import config from 'test-app/config/environment';
3 | import * as QUnit from 'qunit';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setup } from 'qunit-dom';
6 | import { start } from 'ember-qunit';
7 |
8 | setApplication(Application.create(config.APP));
9 |
10 | setup(QUnit.assert);
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from 'dummy/app';
2 | import config from 'dummy/config/environment';
3 | import * as QUnit from 'qunit';
4 | import { setApplication } from '@ember/test-helpers';
5 | import { setup } from 'qunit-dom';
6 | import { start } from 'ember-qunit';
7 |
8 | setApplication(Application.create(config.APP));
9 |
10 | setup(QUnit.assert);
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/packages/site/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
23 | # api-docs
24 | /public/api-docs/
25 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /**/dist/
7 | /**/tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
23 | # api-docs
24 | /packages/site/public/api-docs/
25 |
--------------------------------------------------------------------------------
/packages/site/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /**/dist/
7 | /**/tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
23 | # api-docs
24 | /public/api-docs/
25 |
--------------------------------------------------------------------------------
/packages/site/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | const isCI = Boolean(process.env.CI);
10 | const isProduction = process.env.EMBER_ENV === 'production';
11 |
12 | if (isCI || isProduction) {
13 | browsers.push('ie 11');
14 | }
15 |
16 | module.exports = {
17 | browsers,
18 | };
19 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /**/dist/
7 | /**/tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /bower.json.ember-try
21 | /package.json.ember-try
22 |
23 | # api-docs
24 | /packages/site/public/api-docs/
25 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs-viewer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import Component from '@ember/component';
3 | import layout from 'ember-cli-addon-docs/components/docs-viewer/template';
4 | import { inject as service } from '@ember/service';
5 |
6 | export default Component.extend({
7 | layout,
8 | docsRoutes: service(),
9 | router: service(),
10 |
11 | classNames: 'docs-viewer docs-flex docs-flex1',
12 | });
13 |
--------------------------------------------------------------------------------
/packages/site/app/snippets/quickstart/onExit.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default // BEGIN-SNIPPET quickstart-on-exit
3 | ({
4 | initial: 'idle',
5 | states: {
6 | idle: {
7 | on: {
8 | SUBMIT: 'busy',
9 | },
10 | exit: ['handleSubmit'],
11 | },
12 | busy: {},
13 | },
14 | },
15 | {
16 | actions: {
17 | handleSubmit(/*context, event*/) {},
18 | },
19 | });
20 | // END-SNIPPET
21 |
--------------------------------------------------------------------------------
/packages/site/app/snippets/quickstart/onEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default // BEGIN-SNIPPET quickstart-on-entry
3 | ({
4 | initial: 'idle',
5 | states: {
6 | idle: {
7 | on: {
8 | SUBMIT: 'busy',
9 | },
10 | },
11 | busy: {
12 | entry: ['handleSubmit'],
13 | },
14 | },
15 | },
16 | {
17 | actions: {
18 | handleSubmit(/*context, event*/) {},
19 | },
20 | });
21 | // END-SNIPPET
22 |
--------------------------------------------------------------------------------
/packages/site/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'site/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.hbs]
16 | insert_final_newline = false
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/packages/test-app/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'test-app/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/packages/site/app/config/environment.d.ts:
--------------------------------------------------------------------------------
1 | export default config;
2 |
3 | /**
4 | * Type declarations for
5 | * import config from './config/environment'
6 | *
7 | * For now these need to be managed by the developer
8 | * since different ember addons can materialize new entries.
9 | */
10 | declare const config: {
11 | environment: any;
12 | modulePrefix: string;
13 | podModulePrefix: string;
14 | locationType: string;
15 | rootURL: string;
16 | };
17 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from 'ember-resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from 'dummy/config/environment';
5 |
6 | export default class App extends Application {
7 | modulePrefix = config.modulePrefix;
8 | podModulePrefix = config.podModulePrefix;
9 | Resolver = Resolver;
10 | }
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
--------------------------------------------------------------------------------
/packages/test-app/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.hbs]
16 | insert_final_newline = false
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/packages/site/app/snippets/quickstart/transition.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | export default // BEGIN-SNIPPET quickstart-transition
3 | ({
4 | initial: 'idle',
5 | states: {
6 | idle: {
7 | on: {
8 | SUBMIT: {
9 | target: 'busy',
10 | actions: ['handleSubmit'],
11 | },
12 | },
13 | },
14 | busy: {},
15 | },
16 | },
17 | {
18 | actions: {
19 | handleSubmit(/*context, event*/) {},
20 | },
21 | });
22 | // END-SNIPPET
23 |
--------------------------------------------------------------------------------
/packages/test-app/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "12"
5 |
6 | dist: xenial
7 |
8 | addons:
9 | chrome: stable
10 |
11 | cache:
12 | yarn: true
13 |
14 | env:
15 | global:
16 | # See https://git.io/vdao3 for details.
17 | - JOBS=1
18 |
19 | branches:
20 | only:
21 | - master
22 |
23 | before_install:
24 | - curl -o- -L https://yarnpkg.com/install.sh | bash
25 | - export PATH=$HOME/.yarn/bin:$PATH
26 |
27 | script:
28 | - yarn test
29 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/config/environment.d.ts:
--------------------------------------------------------------------------------
1 | export default config;
2 |
3 | /**
4 | * Type declarations for
5 | * import config from './config/environment'
6 | *
7 | * For now these need to be managed by the developer
8 | * since different ember addons can materialize new entries.
9 | */
10 | declare const config: {
11 | environment: any;
12 | modulePrefix: string;
13 | podModulePrefix: string;
14 | locationType: string;
15 | rootURL: string;
16 | };
17 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/-private/resource.d.ts:
--------------------------------------------------------------------------------
1 | declare type Owner = Record;
2 | export interface TemplateArgs {
3 | positional: readonly unknown[];
4 | named: Record;
5 | }
6 | export declare abstract class Resource<
7 | Args extends TemplateArgs = TemplateArgs
8 | > {
9 | protected readonly args: Args;
10 | constructor(owner: Owner, args: Args);
11 | setup(): void;
12 | update?(args: Args): void;
13 | teardown?(): void;
14 | }
15 | export {};
16 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Debug ember serve",
11 | "program": "${workspaceFolder}/node_modules/ember-cli/bin/ember",
12 | "args": ["serve"]
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/packages/test-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist/
5 | /tmp/
6 |
7 | # dependencies
8 | /bower_components/
9 | /node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /connect.lock
17 | /coverage/
18 | /libpeerconnection.log
19 | /npm-debug.log*
20 | /testem.log
21 | /yarn-error.log
22 |
23 | # ember-try
24 | /.node_modules.ember-try/
25 | /bower.json.ember-try
26 | /package.json.ember-try
27 |
--------------------------------------------------------------------------------
/packages/test-app/app/components/computed.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { statechart, matchesState } from 'ember-statecharts/computed';
3 | import { toggleConfig } from '../machines/toggle-machine';
4 | import { action } from '@ember/object';
5 |
6 | export default class ComputedTest extends Component {
7 | @statechart(toggleConfig) statechart;
8 |
9 | @matchesState('on') isOn;
10 | @matchesState('off') isOff;
11 |
12 | @action toggle() {
13 | this.statechart.send('TOGGLE');
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/site/app/controllers/docs/statecharts.js:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { action } from '@ember/object';
3 | import { tracked } from '@glimmer/tracking';
4 |
5 | export default class StatechartsController extends Controller {
6 | @tracked
7 | counterCount = 0;
8 | @tracked
9 | count = 0;
10 |
11 | @action
12 | updateCount({ target: { value } }) {
13 | this.count = value;
14 | }
15 |
16 | @action
17 | syncCounterCount() {
18 | this.counterCount = +this.count;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-statecharts-root",
3 | "version": "0.13.2",
4 | "private": true,
5 | "description": "Statecharts for Ember.js applications",
6 | "homepage": "https://ember-statecharts.com/",
7 | "repository": "https://github.com/LevelbossMike/ember-statecharts",
8 | "license": "MIT",
9 | "author": "",
10 | "workspaces": [
11 | "packages/*"
12 | ],
13 | "devDependencies": {
14 | "lerna": "^4.0.0"
15 | },
16 | "volta": {
17 | "node": "14.18.1",
18 | "yarn": "1.22.17"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/site/app/components/typed-button.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET typed-button }}
2 |
19 | {{! END-SNIPPET }}
20 |
--------------------------------------------------------------------------------
/packages/site/app/components/quickstart-button.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET quickstart-button }}
2 |
19 | {{! END-SNIPPET }}
20 |
--------------------------------------------------------------------------------
/packages/site/public/quote.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/site/app/components/quickstart-button-refined.hbs:
--------------------------------------------------------------------------------
1 | {{! BEGIN-SNIPPET quickstart-button-final }}
2 |
19 | {{! END-SNIPPET }}
20 |
--------------------------------------------------------------------------------
/packages/test-app/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "3.28.4",
7 | "blueprints": [
8 | {
9 | "name": "app",
10 | "outputRepo": "https://github.com/ember-cli/ember-new-output",
11 | "codemodsSource": "ember-app-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--no-welcome",
15 | "--yarn"
16 | ]
17 | }
18 | ]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/site/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "3.24.0",
7 | "blueprints": [
8 | {
9 | "name": "addon",
10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output",
11 | "codemodsSource": "ember-addon-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--yarn",
15 | "--no-welcome"
16 | ]
17 | }
18 | ]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /**/dist/
5 | /**/tmp/
6 |
7 | # dependencies
8 | /**/bower_components/
9 | /**/node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /*.tsbuildinfo
17 | /connect.lock
18 | /coverage/
19 | /libpeerconnection.log
20 | /npm-debug.log*
21 | /testem.log
22 | /yarn-error.log
23 |
24 | # ember-try
25 | /.node_modules.ember-try/
26 | /bower.json.ember-try
27 | /package.json.ember-try
28 |
29 | .DS_Store
30 |
31 | /packages/deploy-site/
32 | .npmrc
33 |
--------------------------------------------------------------------------------
/packages/test-app/app/components/use-machine.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { useMachine, matchesState } from 'ember-statecharts';
3 | import toggleMachine from '../machines/toggle-machine';
4 | import { action } from '@ember/object';
5 |
6 | export default class UseMachineTest extends Component {
7 | statechart = useMachine(this, () => {
8 | return {
9 | machine: toggleMachine,
10 | };
11 | });
12 |
13 | @matchesState('on') isOn;
14 | @matchesState('off') isOff;
15 |
16 | @action toggle() {
17 | this.statechart.send('TOGGLE');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "3.28.4",
7 | "blueprints": [
8 | {
9 | "name": "addon",
10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output",
11 | "codemodsSource": "ember-addon-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--yarn",
15 | "--no-welcome"
16 | ]
17 | }
18 | ]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/site/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist/
5 | /tmp/
6 |
7 | # dependencies
8 | /bower_components/
9 | /node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /*.tsbuildinfo
17 | /connect.lock
18 | /coverage/
19 | /libpeerconnection.log
20 | /npm-debug.log*
21 | /testem.log
22 | /yarn-error.log
23 |
24 | # ember-try
25 | /.node_modules.ember-try/
26 | /bower.json.ember-try
27 | /package.json.ember-try
28 |
29 | .DS_Store
30 | # generated api-docs
31 | /public/api-docs/
32 |
--------------------------------------------------------------------------------
/packages/site/app/controllers/docs.ts:
--------------------------------------------------------------------------------
1 | import Controller from '@ember/controller';
2 | import { tracked } from '@glimmer/tracking';
3 | import { action } from '@ember/object';
4 |
5 | export default class Docs extends Controller {
6 | @tracked currentHeadingId: string | undefined;
7 |
8 | @action
9 | setCurrentHeadingId(id: string | undefined): void {
10 | this.currentHeadingId = id;
11 | }
12 | }
13 |
14 | // DO NOT DELETE: this is how TypeScript knows how to look up your controllers.
15 | declare module '@ember/controller' {
16 | interface Registry {
17 | docs: Docs;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /**/dist/
5 | /**/tmp/
6 |
7 | # dependencies
8 | /**/bower_components/
9 | /**/node_modules/
10 |
11 | # misc
12 | /.env*
13 | /.pnp*
14 | /.sass-cache
15 | /.eslintcache
16 | /*.tsbuildinfo
17 | /connect.lock
18 | /coverage/
19 | /libpeerconnection.log
20 | /npm-debug.log*
21 | /testem.log
22 | /yarn-error.log
23 |
24 | # ember-try
25 | /.node_modules.ember-try/
26 | /bower.json.ember-try
27 | /package.json.ember-try
28 |
29 | .DS_Store
30 |
31 | /packages/deploy-site/
32 |
--------------------------------------------------------------------------------
/packages/site/app/components/page-headings.ts:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import scrollTo, { scrollToElement } from '../utils/scroll-to';
3 | import { action } from '@ember/object';
4 |
5 | interface PageHeadingsArgs {} //eslint-disable-line
6 |
7 | export default class PageHeadings extends Component {
8 | @action onClick(evt: MouseEvent): void {
9 | const href = (evt.target as HTMLElement).getAttribute('href');
10 | if (href) {
11 | const toElement = document.querySelector(href) as HTMLElement;
12 |
13 | scrollToElement(toElement);
14 | }
15 | }
16 |
17 | @action scrollToTop(): void {
18 | scrollTo(0);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/.npmignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /tmp/
4 |
5 | # dependencies
6 | /bower_components/
7 |
8 | # misc
9 | /.bowerrc
10 | /.editorconfig
11 | /.ember-cli
12 | /.env*
13 | /.eslintcache
14 | /.eslintignore
15 | /.eslintrc.js
16 | /.git/
17 | /.gitignore
18 | /.prettierignore
19 | /.prettierrc.js
20 | /.template-lintrc.js
21 | /.travis.yml
22 | /.watchmanconfig
23 | /bower.json
24 | /config/ember-try.js
25 | /CONTRIBUTING.md
26 | /ember-cli-build.js
27 | /testem.js
28 | /tests/
29 | /yarn-error.log
30 | /yarn.lock
31 | .gitkeep
32 |
33 | # ember-try
34 | /.node_modules.ember-try/
35 | /bower.json.ember-try
36 | /package.json.ember-try
37 | /config/addon-docs.js
38 |
--------------------------------------------------------------------------------
/packages/site/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/packages/test-app/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/testem.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | test_page: 'tests/index.html?hidepassed',
5 | disable_watching: true,
6 | launch_in_ci: ['Chrome'],
7 | launch_in_dev: ['Chrome'],
8 | browser_start_timeout: 120,
9 | browser_args: {
10 | Chrome: {
11 | ci: [
12 | // --no-sandbox is needed when running Chrome inside a container
13 | process.env.CI ? '--no-sandbox' : null,
14 | '--headless',
15 | '--disable-dev-shm-usage',
16 | '--disable-software-rasterizer',
17 | '--mute-audio',
18 | '--remote-debugging-port=0',
19 | '--window-size=1440,900',
20 | ].filter(Boolean),
21 | },
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How To Contribute
2 |
3 | ## Installation
4 |
5 | * `git clone `
6 | * `cd my-addon`
7 | * `yarn`
8 |
9 | ## Linting
10 |
11 | * `yarn run lint:hbs`
12 | * `yarn run lint:js`
13 | * `yarn run lint:js -- --fix`
14 |
15 | ## Running tests
16 |
17 | * `ember test` – Runs the test suite on the current Ember version
18 | * `ember test --server` – Runs the test suite in "watch mode"
19 | * `ember try:each` – Runs the test suite against multiple Ember versions
20 |
21 | ## Running the dummy application
22 |
23 | * `ember serve`
24 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200).
25 |
26 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
27 |
--------------------------------------------------------------------------------
/packages/test-app/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | // Ember's browser support policy is changing, and IE11 support will end in
10 | // v4.0 onwards.
11 | //
12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy
13 | //
14 | // If you need IE11 support on a version of Ember that still offers support
15 | // for it, uncomment the code block below.
16 | //
17 | // const isCI = Boolean(process.env.CI);
18 | // const isProduction = process.env.EMBER_ENV === 'production';
19 | //
20 | // if (isCI || isProduction) {
21 | // browsers.push('ie 11');
22 | // }
23 |
24 | module.exports = {
25 | browsers,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
3 |
4 | module.exports = function (defaults) {
5 | let app = new EmberAddon(defaults, {
6 | // Add options here
7 | });
8 |
9 | /*
10 | This build file specifies the options for the dummy test app of this
11 | addon, located in `/tests/dummy`
12 | This build file does *not* influence how the addon or the app using it
13 | behave. You most likely want to be modifying `./index.js` or app's build file
14 | */
15 |
16 | const { maybeEmbroider } = require('@embroider/test-setup');
17 | return maybeEmbroider(app, {
18 | skipBabel: [
19 | {
20 | package: 'qunit',
21 | },
22 | ],
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/packages/site/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{content-for "head"}}
10 |
11 |
12 |
13 |
14 | {{content-for "head-footer"}}
15 |
16 |
17 | {{content-for "body"}}
18 |
19 |
20 |
21 |
22 | {{content-for "body-footer"}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/-private/use-resource.d.ts:
--------------------------------------------------------------------------------
1 | import type { Resource } from './resource';
2 | interface TemplateArgs {
3 | positional: readonly unknown[];
4 | named: Record;
5 | }
6 | declare type Args =
7 | | TemplateArgs
8 | | TemplateArgs['positional']
9 | | TemplateArgs['named'];
10 | export declare function useUnproxiedResource<
11 | TArgs = Args,
12 | T extends Resource = Resource
13 | >(
14 | destroyable: object,
15 | definition: object,
16 | args?: () => TArgs
17 | ): {
18 | value: T;
19 | };
20 | export declare function useResource<
21 | TArgs = Args,
22 | T extends Resource = Resource
23 | >(destroyable: object, definition: object, args?: () => TArgs): T;
24 | export {};
25 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | // Ember's browser support policy is changing, and IE11 support will end in
10 | // v4.0 onwards.
11 | //
12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy
13 | //
14 | // If you need IE11 support on a version of Ember that still offers support
15 | // for it, uncomment the code block below.
16 | //
17 | // const isCI = Boolean(process.env.CI);
18 | // const isProduction = process.env.EMBER_ENV === 'production';
19 | //
20 | // if (isCI || isProduction) {
21 | // browsers.push('ie 11');
22 | // }
23 |
24 | module.exports = {
25 | browsers,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{content-for "head"}}
10 |
11 |
12 |
13 |
14 | {{content-for "head-footer"}}
15 |
16 |
17 | {{content-for "body"}}
18 |
19 |
20 |
21 |
22 | {{content-for "body-footer"}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs/quickstart-guide.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { tracked } from '@glimmer/tracking';
3 |
4 | import { action } from '@ember/object';
5 |
6 | import { task, timeout } from 'ember-concurrency';
7 |
8 | export default class extends Component {
9 | // BEGIN-SNIPPET quickstart-button-used
10 |
11 | // ...
12 | @tracked
13 | failRequest = false;
14 |
15 | @(task(function* () {
16 | yield timeout(1000);
17 |
18 | if (this.failRequest) {
19 | throw 'wat';
20 | }
21 | }).drop())
22 | submitTask;
23 |
24 | @action
25 | onSuccess() {
26 | window.alert('Submit successful');
27 | }
28 |
29 | @action
30 | onError() {
31 | window.alert('Submit failed');
32 | }
33 | // ...
34 | // END-SNIPPET
35 | }
36 |
--------------------------------------------------------------------------------
/packages/test-app/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TestApp
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/packages/site/app/components/counter.hbs:
--------------------------------------------------------------------------------
1 |
2 | Counter Count: {{this.statechart.state.context.count}}
3 |
4 |
5 |
6 | {{#if this.isActive}}
7 |
8 | Deactivate
9 |
10 | {{else}}
11 |
12 | Activate
13 |
14 | {{/if}}
15 |
16 |
17 |
21 | -
22 |
23 |
27 | +
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs/typescript-usage.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { action } from '@ember/object';
3 | import { tracked } from '@glimmer/tracking';
4 | import { task, timeout } from 'ember-concurrency';
5 |
6 | export default class extends Component {
7 | @tracked
8 | failRequest = false;
9 |
10 | @tracked
11 | disabled = false;
12 |
13 | // BEGIN-SNIPPET typescript-usage
14 |
15 | // ...
16 |
17 | @(task(function* () {
18 | yield timeout(1000);
19 |
20 | if (this.failRequest) {
21 | throw 'wat';
22 | }
23 | }).drop())
24 | submitTask;
25 |
26 | @action
27 | onSuccess() {
28 | window.alert('Submit successful');
29 | }
30 |
31 | @action
32 | onError() {
33 | window.alert('Submit failed');
34 | }
35 | // ...
36 | // END-SNIPPET
37 | }
38 |
--------------------------------------------------------------------------------
/packages/site/app/components/counter-restart.hbs:
--------------------------------------------------------------------------------
1 |
2 | Counter Count: {{this.statechart.state.context.count}}
3 |
4 |
5 |
6 | {{#if this.isActive}}
7 |
8 | Deactivate
9 |
10 | {{else}}
11 |
12 | Activate
13 |
14 | {{/if}}
15 |
16 |
17 |
21 | -
22 |
23 |
27 | +
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/packages/site/public/effective-ember.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs/quickstart-final.js:
--------------------------------------------------------------------------------
1 | import Component from '@glimmer/component';
2 | import { action } from '@ember/object';
3 | import { tracked } from '@glimmer/tracking';
4 | import { task, timeout } from 'ember-concurrency';
5 |
6 | export default class extends Component {
7 | @tracked
8 | failRequest = false;
9 |
10 | @tracked
11 | disabled = false;
12 |
13 | // BEGIN-SNIPPET quickstart-button-final-used
14 |
15 | // ...
16 |
17 | @(task(function* () {
18 | yield timeout(1000);
19 |
20 | if (this.failRequest) {
21 | throw 'wat';
22 | }
23 | }).drop())
24 | submitTask;
25 |
26 | @action
27 | onSuccess() {
28 | window.alert('Submit successful');
29 | }
30 |
31 | @action
32 | onError() {
33 | window.alert('Submit failed');
34 | }
35 | // ...
36 | // END-SNIPPET
37 | }
38 |
--------------------------------------------------------------------------------
/packages/site/app/components/mobile-docs-toc.hbs:
--------------------------------------------------------------------------------
1 |
2 |
23 | {{#if this.showToc}}
24 |
28 | {{/if}}
29 |
30 |
--------------------------------------------------------------------------------
/packages/site/.docfy-config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const autolinkHeadings = require('remark-autolink-headings');
3 | const docfyWithProse = require('@docfy/plugin-with-prose');
4 | const prism = require('@mapbox/rehype-prism');
5 | const refractor = require('refractor');
6 |
7 | refractor.alias('handlebars', 'hbs');
8 | refractor.alias('shell', 'sh');
9 |
10 | module.exports = {
11 | tocMaxDepth: 3,
12 | sources: [
13 | {
14 | root: path.resolve(__dirname, '../../docs'),
15 | pattern: '**/*.md',
16 | urlPrefix: 'docs',
17 | },
18 | ],
19 | remarkPlugins: [
20 | [
21 | autolinkHeadings,
22 | {
23 | behavior: 'wrap',
24 | },
25 | ],
26 | ],
27 | plugins: [docfyWithProse],
28 | rehypePlugins: [prism],
29 | labels: {
30 | components: 'Components',
31 | workflow: 'Workflow',
32 | docs: 'Documentation',
33 | },
34 | };
35 |
--------------------------------------------------------------------------------
/packages/test-app/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app');
4 |
5 | module.exports = function (defaults) {
6 | let app = new EmberApp(defaults, {
7 | // Add options here
8 | });
9 |
10 | // Use `app.import` to add additional libraries to the generated
11 | // output files.
12 | //
13 | // If you need to use different assets in different
14 | // environments, specify an object as the first parameter. That
15 | // object's keys should be the environment name and the values
16 | // should be the asset to use in that environment.
17 | //
18 | // If the library that you are including contains AMD or ES6
19 | // modules that you would like to import into your application
20 | // please specify an object with the list of modules as keys
21 | // along with the exports of each module as its value.
22 |
23 | return app.toTree();
24 | };
25 |
--------------------------------------------------------------------------------
/packages/site/app/machines/quickstart-button.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET quickstart-button-machine
2 | import { Machine } from 'xstate';
3 |
4 | export default Machine(
5 | {
6 | initial: 'idle',
7 | states: {
8 | idle: {
9 | on: {
10 | SUBMIT: 'busy',
11 | },
12 | },
13 | busy: {
14 | entry: ['handleSubmit'],
15 | on: {
16 | SUCCESS: 'success',
17 | ERROR: 'error',
18 | },
19 | },
20 | success: {
21 | entry: ['handleSuccess'],
22 | on: {
23 | SUBMIT: 'busy',
24 | },
25 | },
26 | error: {
27 | entry: ['handleError'],
28 | on: {
29 | SUBMIT: 'busy',
30 | },
31 | },
32 | },
33 | },
34 | {
35 | actions: {
36 | handleSubmit() {},
37 | handleSuccess() {},
38 | handleError() {},
39 | },
40 | }
41 | );
42 | // END-SNIPPET
43 |
--------------------------------------------------------------------------------
/packages/site/public/youtube.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/site/lib/versioned-doc-info.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | const getRepoInfo = require('git-repo-info');
5 |
6 | module.exports = function (deployTarget) {
7 | let dir, name, rootURL;
8 | let { sha, tag, branch } = getRepoInfo();
9 |
10 | if (deployTarget === 'tag') {
11 | dir = tag;
12 | name = tag;
13 | branch = null;
14 | } else if (deployTarget === 'branch') {
15 | dir = branch;
16 | name = branch;
17 | tag = null;
18 | } else if (deployTarget === 'latest') {
19 | dir = 'latest';
20 | name = 'Latest';
21 | } else {
22 | throw new Error(`unsupported deploy target ${deployTarget}`);
23 | }
24 |
25 | let destDir = `versions/${dir}`;
26 |
27 | if (deployTarget === 'latest') {
28 | rootURL = '/';
29 | } else {
30 | rootURL = `/${destDir}/`;
31 | }
32 |
33 | return {
34 | name,
35 | sha,
36 | tag,
37 | branch,
38 | destDir,
39 | rootURL,
40 | deployTarget,
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/README.md:
--------------------------------------------------------------------------------
1 | ember-statecharts
2 | ==============================================================================
3 |
4 | [Short description of the addon.]
5 |
6 |
7 | Compatibility
8 | ------------------------------------------------------------------------------
9 |
10 | * Ember.js v3.20 or above
11 | * Ember CLI v3.20 or above
12 | * Node.js v12 or above
13 |
14 |
15 | Installation
16 | ------------------------------------------------------------------------------
17 |
18 | ```
19 | ember install ember-statecharts
20 | ```
21 |
22 |
23 | Usage
24 | ------------------------------------------------------------------------------
25 |
26 | [Longer description of how to use the addon in apps.]
27 |
28 |
29 | Contributing
30 | ------------------------------------------------------------------------------
31 |
32 | See the [Contributing](CONTRIBUTING.md) guide for details.
33 |
34 |
35 | License
36 | ------------------------------------------------------------------------------
37 |
38 | This project is licensed under the [MIT License](LICENSE.md).
39 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/packages/site/app/machines/counter-machine.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET counter-machine
2 | import { Machine, assign } from 'xstate';
3 |
4 | export default Machine({
5 | id: 'counterMachine',
6 | initial: 'inactive',
7 | context: {
8 | count: 0,
9 | },
10 | on: {
11 | RESET_COUNT: {
12 | actions: [
13 | assign({
14 | count: (_context, { count }) => count,
15 | }),
16 | ],
17 | },
18 | },
19 | states: {
20 | inactive: {
21 | on: {
22 | ACTIVATE: 'active',
23 | },
24 | },
25 | active: {
26 | on: {
27 | DEACTIVATE: 'inactive',
28 | INCREMENT: {
29 | target: 'active',
30 | actions: [
31 | assign({
32 | count: (context) => context.count + 1,
33 | }),
34 | ],
35 | },
36 | DECREMENT: {
37 | target: 'active',
38 | actions: [
39 | assign({
40 | count: (context) => context.count - 1,
41 | }),
42 | ],
43 | },
44 | },
45 | },
46 | },
47 | });
48 | // END-SNIPPET
49 |
--------------------------------------------------------------------------------
/packages/site/app/components/counter-restart.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET counter-update-restart
2 | import Component from '@glimmer/component';
3 | import { action } from '@ember/object';
4 | import { useMachine, matchesState } from 'ember-statecharts';
5 | import CounterMachine from '../machines/counter-machine';
6 |
7 | export default class CounterComponent extends Component {
8 | statechart = useMachine(this, () => {
9 | return {
10 | machine: CounterMachine.withContext({
11 | count: this.args.count,
12 | }),
13 | update: ({ restart }) => {
14 | restart();
15 | },
16 | };
17 | });
18 |
19 | @matchesState('active')
20 | isActive;
21 |
22 | @matchesState('inactive')
23 | isDisabled;
24 |
25 | @action
26 | decrement() {
27 | this.statechart.send('DECREMENT');
28 | }
29 |
30 | @action
31 | increment() {
32 | this.statechart.send('INCREMENT');
33 | }
34 |
35 | @action
36 | activate() {
37 | this.statechart.send('ACTIVATE');
38 | }
39 |
40 | @action
41 | deactivate() {
42 | this.statechart.send('DEACTIVATE');
43 | }
44 | }
45 | // END-SNIPPET
46 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs-toc.hbs:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
--------------------------------------------------------------------------------
/packages/site/public/discord.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/site/app/routes/application.ts:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 | import RouterService from '@ember/routing/router-service';
3 | import { inject as service } from '@ember/service';
4 | import config from 'site/config/environment';
5 | import { action } from '@ember/object';
6 |
7 | import MetricsService from 'ember-metrics/services/metrics';
8 |
9 | export default class ApplicationRoute extends Route {
10 | @service
11 | router!: RouterService;
12 |
13 | @service
14 | metrics!: MetricsService;
15 |
16 | constructor() {
17 | // eslint-disable-next-line prefer-rest-params
18 | super(...arguments);
19 |
20 | this.setupTracking();
21 | }
22 |
23 | @action
24 | didTransition(): void {
25 | if (
26 | config.environment !== 'test' &&
27 | window &&
28 | typeof window.scrollTo === 'function'
29 | ) {
30 | window.scrollTo(0, 0);
31 | }
32 | }
33 |
34 | private setupTracking() {
35 | this.router.on('routeDidChange', () => {
36 | const { currentURL, currentRouteName } = this.router;
37 |
38 | this.metrics.trackPage({
39 | page: currentURL,
40 | title: currentRouteName,
41 | });
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/site/app/components/counter.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET counter-update-event
2 | import Component from '@glimmer/component';
3 | import { action } from '@ember/object';
4 | import { matchesState } from 'ember-statecharts';
5 | import { useMachine } from 'ember-statecharts/-private/usables';
6 | import CounterMachine from '../machines/counter-machine';
7 |
8 | export default class CounterComponent extends Component {
9 | statechart = useMachine(this, () => {
10 | return {
11 | machine: CounterMachine.withContext({
12 | count: this.args.count,
13 | }),
14 | update: ({ send, machine: { context } }) => {
15 | send('RESET_COUNT', { count: context.count });
16 | },
17 | };
18 | });
19 |
20 | @matchesState('active')
21 | isActive;
22 |
23 | @matchesState('inactive')
24 | isDisabled;
25 |
26 | @action
27 | decrement() {
28 | this.statechart.send('DECREMENT');
29 | }
30 |
31 | @action
32 | increment() {
33 | this.statechart.send('INCREMENT');
34 | }
35 |
36 | @action
37 | activate() {
38 | this.statechart.send('ACTIVATE');
39 | }
40 |
41 | @action
42 | deactivate() {
43 | this.statechart.send('DEACTIVATE');
44 | }
45 | }
46 | // END-SNIPPET
47 |
--------------------------------------------------------------------------------
/packages/site/public/xstate.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/tsconfig.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "allowJs": true,
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "noImplicitAny": true,
8 | "noImplicitThis": true,
9 | "alwaysStrict": true,
10 | "strictNullChecks": true,
11 | "strictPropertyInitialization": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "noEmitOnError": false,
17 | "noEmit": true,
18 | "inlineSourceMap": true,
19 | "inlineSources": true,
20 | "baseUrl": ".",
21 | "module": "es6",
22 | "experimentalDecorators": true,
23 | "paths": {
24 | "dummy/tests/*": [
25 | "tests/*"
26 | ],
27 | "dummy/*": [
28 | "tests/dummy/app/*",
29 | "app/*"
30 | ],
31 | "ember-statecharts": [
32 | "addon"
33 | ],
34 | "ember-statecharts/*": [
35 | "addon/*"
36 | ],
37 | "ember-statecharts/test-support": [
38 | "addon-test-support"
39 | ],
40 | "ember-statecharts/test-support/*": [
41 | "addon-test-support/*"
42 | ],
43 | "*": [
44 | "types/*"
45 | ]
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs/quickstart-guide.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{! BEGIN-SNIPPET quickstart-button-used }}
4 |
9 | Click me!
10 |
11 | {{! END-SNIPPET }}
12 |
13 |
16 |
23 |
24 |
25 |
29 |
33 |
37 |
41 |
45 |
46 |
--------------------------------------------------------------------------------
/packages/site/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{content-for "head"}}
10 | {{content-for "test-head"}}
11 |
12 |
13 |
14 |
15 |
16 | {{content-for "head-footer"}}
17 | {{content-for "test-head-footer"}}
18 |
19 |
20 | {{content-for "body"}}
21 | {{content-for "test-body"}}
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{content-for "body-footer"}}
37 | {{content-for "test-body-footer"}}
38 |
39 |
40 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{content-for "head"}}
10 | {{content-for "test-head"}}
11 |
12 |
13 |
14 |
15 |
16 | {{content-for "head-footer"}}
17 | {{content-for "test-head-footer"}}
18 |
19 |
20 | {{content-for "body"}}
21 | {{content-for "test-body"}}
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{content-for "body-footer"}}
37 | {{content-for "test-body-footer"}}
38 |
39 |
40 |
--------------------------------------------------------------------------------
/packages/site/app/components/demo.hbs:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | {{yield (hash ui=(hash useSnippet=showcase.ui.useSnippet))}}
8 |
9 |
10 |
11 | {{#each showcase.state.snippets as |snippet|}}
12 |
28 | {{/each}}
29 |
30 | {{#if showcase.state.activeSnippet}}
31 |
36 | {{/if}}
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/packages/test-app/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TestApp Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{content-for "body-footer"}}
38 | {{content-for "test-body-footer"}}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs/typescript-usage.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{! BEGIN-SNIPPET typed-button-used }}
4 |
10 | .ts FTW
11 |
12 | {{! END-SNIPPET }}
13 |
14 |
17 |
24 |
25 |
26 |
29 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/packages/site/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const crawl = require('prember-crawler');
3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app');
4 |
5 | module.exports = function (defaults) {
6 | let app = new EmberApp(defaults, {
7 | // Add options here
8 | postcssOptions: {
9 | compile: {
10 | plugins: [
11 | require('postcss-import'),
12 | require('tailwindcss')('./tailwind.config.js'),
13 | require('autoprefixer'),
14 | ],
15 | },
16 | },
17 | snippetPaths: ['app'],
18 | snippetSearchPaths: ['app'], //, '../../docs'],
19 | 'ember-prism': {
20 | theme: 'tomorrow',
21 | components: ['markup-templating', 'handlebars', 'typescript'],
22 | },
23 | prember: {
24 | urls: async ({ visit }) => {
25 | let docsURLs = await crawl({
26 | visit,
27 | startingFrom: ['/docs'],
28 | selector: 'a[data-prember]',
29 | });
30 |
31 | let otherURLS = ['/', '/docs'];
32 |
33 | return docsURLs.concat(otherURLS);
34 | },
35 | },
36 | });
37 |
38 | /*
39 | This build file specifies the options for the dummy test app of this
40 | addon, located in `/tests/dummy`
41 | This build file does *not* influence how the addon or the app using it
42 | behave. You most likely want to be modifying `./index.js` or app's build file
43 | */
44 |
45 | return app.toTree();
46 | };
47 |
--------------------------------------------------------------------------------
/packages/site/app/utils/scroll-to.ts:
--------------------------------------------------------------------------------
1 | // http://goo.gl/5HLl8
2 | const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
3 | t /= d / 2;
4 | if (t < 1) {
5 | return (c / 2) * t * t + b;
6 | }
7 | t--;
8 | return (-c / 2) * (t * (t - 2) - 1) + b;
9 | };
10 |
11 | function scrollTo(
12 | toPosition: number,
13 | callback?: () => void,
14 | duration = 500
15 | ): void {
16 | const scrollingElement = document.scrollingElement
17 | ? document.scrollingElement
18 | : document.body;
19 | const startPosition = scrollingElement.scrollTop;
20 | const change = toPosition - startPosition;
21 | let currentTime = 0;
22 | const increment = 20;
23 |
24 | const animateScroll = (): void => {
25 | currentTime += increment;
26 | scrollingElement.scrollTop = easeInOutQuad(
27 | currentTime,
28 | startPosition,
29 | change,
30 | duration
31 | );
32 |
33 | if (currentTime < duration) {
34 | requestAnimationFrame(animateScroll);
35 | } else {
36 | if (callback && typeof callback === 'function') {
37 | callback();
38 | }
39 | }
40 | };
41 | animateScroll();
42 | }
43 |
44 | function scrollToElement(
45 | element: HTMLElement,
46 | callback?: () => void,
47 | duration = 500
48 | ): void {
49 | const toPosition = element.offsetTop;
50 | scrollTo(toPosition, callback, duration);
51 | }
52 |
53 | export { scrollToElement };
54 | export default scrollTo;
55 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - '*'
9 | workflow_dispatch:
10 | latest:
11 | description: 'Deploy latest?'
12 | required: true
13 | type: boolean
14 |
15 | jobs:
16 | checkout:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout Code
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: '0'
23 | - name: Install Node
24 | uses: actions/setup-node@v2
25 | with:
26 | node-version: 14
27 | - name: Install Dependencies
28 | run: yarn install --frozen-lockfile
29 | - name: Lerna Bootstrap
30 | run: yarn run lerna bootstrap
31 | - name: Create API docs
32 | run: yarn workspace ember-statecharts api-docs
33 | - name: update git credentials
34 | run: |
35 | git config --global user.name "ember-statecharts deployer"
36 | git config --global user.email "urbi@orbi.vat"
37 |
38 | - name: Deploy tag
39 | if: startsWith(github.ref, 'refs/tags/v')
40 | run: yarn workspace site run ember deploy tag
41 |
42 | - name: Deploy branch
43 | if: github.ref == 'refs/heads/master'
44 | run: yarn workspace site run ember deploy branch
45 |
46 | - name: Deploy latest
47 | if: github.event.inputs.latest == 'true'
48 | run: yarn workspace site run ember deploy latest
49 |
--------------------------------------------------------------------------------
/packages/test-app/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (environment) {
4 | let ENV = {
5 | modulePrefix: 'test-app',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
13 | },
14 | EXTEND_PROTOTYPES: {
15 | // Prevent Ember Data from overriding Date.parse.
16 | Date: false,
17 | },
18 | },
19 |
20 | APP: {
21 | // Here you can pass flags/options to your application instance
22 | // when it is created
23 | },
24 | };
25 |
26 | if (environment === 'development') {
27 | // ENV.APP.LOG_RESOLVER = true;
28 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
29 | // ENV.APP.LOG_TRANSITIONS = true;
30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
31 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
32 | }
33 |
34 | if (environment === 'test') {
35 | // Testem prefers this...
36 | ENV.locationType = 'none';
37 |
38 | // keep test console output quieter
39 | ENV.APP.LOG_ACTIVE_GENERATION = false;
40 | ENV.APP.LOG_VIEW_LOOKUPS = false;
41 |
42 | ENV.APP.rootElement = '#ember-testing';
43 | ENV.APP.autoboot = false;
44 | }
45 |
46 | if (environment === 'production') {
47 | // here you can enable a production-specific feature
48 | }
49 |
50 | return ENV;
51 | };
52 |
--------------------------------------------------------------------------------
/packages/test-app/tests/acceptance/smoke-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { visit, click, find } from '@ember/test-helpers';
3 | import { setupApplicationTest } from 'ember-qunit';
4 |
5 | class TogglePage {
6 | constructor(scope) {
7 | this.scope = scope;
8 | }
9 |
10 | get content() {
11 | return find(`${this.scope} [data-test-content]`);
12 | }
13 |
14 | toggle() {
15 | return click(`${this.scope} [data-test-toggle-button]`);
16 | }
17 | }
18 |
19 | async function runToggleTest(page, assert) {
20 | assert.dom(page.content).hasText('Off', 'machine is off by default');
21 |
22 | await page.toggle();
23 |
24 | assert.dom(page.content).hasText('On', 'toggling turns machine on');
25 |
26 | await page.toggle();
27 |
28 | assert
29 | .dom(page.content)
30 | .hasText('Off', 'toggling again turns machine off again');
31 | }
32 |
33 | module('Acceptance | smoke', function (hooks) {
34 | setupApplicationTest(hooks);
35 |
36 | test('@statechart computed api works', async function (assert) {
37 | assert.expect(3);
38 |
39 | await visit('/');
40 |
41 | const page = new TogglePage('[data-test-computed]');
42 |
43 | await runToggleTest(page, assert);
44 | });
45 |
46 | test('use-machine api works', async function (assert) {
47 | assert.expect(3);
48 |
49 | await visit('/');
50 |
51 | const page = new TogglePage('[data-test-use-machine]');
52 |
53 | await runToggleTest(page, assert);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/packages/test-app/.eslintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | ecmaVersion: 2018,
8 | sourceType: 'module',
9 | ecmaFeatures: {
10 | legacyDecorators: true,
11 | },
12 | },
13 | plugins: ['ember'],
14 | extends: [
15 | 'eslint:recommended',
16 | 'plugin:ember/recommended',
17 | 'plugin:prettier/recommended',
18 | ],
19 | env: {
20 | browser: true,
21 | },
22 | rules: {},
23 | overrides: [
24 | // node files
25 | {
26 | files: [
27 | './.eslintrc.js',
28 | './.prettierrc.js',
29 | './.template-lintrc.js',
30 | './ember-cli-build.js',
31 | './testem.js',
32 | './blueprints/*/index.js',
33 | './config/**/*.js',
34 | './lib/*/index.js',
35 | './server/**/*.js',
36 | ],
37 | parserOptions: {
38 | sourceType: 'script',
39 | },
40 | env: {
41 | browser: false,
42 | node: true,
43 | },
44 | plugins: ['node'],
45 | extends: ['plugin:node/recommended'],
46 | rules: {
47 | // this can be removed once the following is fixed
48 | // https://github.com/mysticatea/eslint-plugin-node/issues/77
49 | 'node/no-unpublished-require': 'off',
50 | },
51 | },
52 | {
53 | // Test files:
54 | files: ['tests/**/*-test.{js,ts}'],
55 | extends: ['plugin:qunit/recommended'],
56 | },
57 | ],
58 | };
59 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/utils/statechart.js:
--------------------------------------------------------------------------------
1 | import { Machine, interpret } from 'xstate';
2 | import { set } from '@ember/object';
3 | import { later, cancel } from '@ember/runloop';
4 | import { warn } from '@ember/debug';
5 |
6 | export default class Statechart {
7 | constructor(config, options, initialContext) {
8 | const machine = Machine(config, options, initialContext);
9 | this.service = interpret(machine, {
10 | clock: {
11 | setTimeout: (fn, ms) => {
12 | return later.call(null, fn, ms);
13 | },
14 | clearTimeout: (timer) => {
15 | return cancel.call(null, timer);
16 | },
17 | },
18 | })
19 | .onTransition((state) => {
20 | set(this, 'currentState', state);
21 | })
22 | .start();
23 | }
24 |
25 | send(event, data = {}) {
26 | if (arguments.length === 1) {
27 | this._sendEventObject(event);
28 | } else {
29 | const { type, ...eventData } = data;
30 | if (type) {
31 | warn(
32 | `You passed property \`type\` as part of the data you sent with the event \`${event}\` . This is not supported - \`${event}\` will be used as event name.`,
33 | false,
34 | { id: 'statecharts.event-object.no-override-type' }
35 | );
36 | }
37 | const eventObject = { ...eventData, type: event };
38 |
39 | this._sendEventObject(eventObject);
40 | }
41 | }
42 |
43 | _sendEventObject(eventObject) {
44 | this.service.send(eventObject);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/site/app/components/ui-intro.md:
--------------------------------------------------------------------------------
1 | # Motivation
2 |
3 | `ember-statecharts` provides an easy way to use statecharts in ember applications.
4 | This is especially useful in `Ember.Component`-architecture but can be used across all layers of your application - e.g. when implementing global application state backed by an `Ember.Service` that needs to switch your application into a specifc mode based on data that your application receives via push events.
5 |
6 | By using statecharts you can improve your development workflow in multiple ways:
7 |
8 | * Model behavior explicitly. Instead of relying on implicit states that you manage by setting properties on your objects you will model your behavior as a set of explicit `state`s that handle events.
9 | * Create robust applications that won't break. Because behavior is only executed when a given state understands an event that is being triggered it is impossible to trigger invalid or unexpected application behavior.
10 | * Refactor and refine with confidence. Application flows modeled with statecharts are easy to change without the risk of breaking existing behavior.
11 | * Document behavior. Because statecharts can be visualized you finally have a way of communicating about application behavior with stakeholders that don't have a programming background. It will also help fellow developers to reason about behavior in your application.
12 | * Identify missing requirements. Because you make use of a visual language that describes behavior it gets very easy to identify holes in requirement documents.
--------------------------------------------------------------------------------
/packages/site/app/components/docs/quickstart-final.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{! BEGIN-SNIPPET quickstart-button-final-used }}
4 |
10 | Click me!
11 |
12 | {{! END-SNIPPET }}
13 |
14 |
17 |
24 |
25 |
26 |
29 |
36 |
37 |
38 |
42 |
46 |
50 |
54 |
55 |
--------------------------------------------------------------------------------
/packages/test-app/README.md:
--------------------------------------------------------------------------------
1 | # test-app
2 |
3 | This README outlines the details of collaborating on this Ember application.
4 | A short introduction of this app could easily go here.
5 |
6 | ## Prerequisites
7 |
8 | You will need the following things properly installed on your computer.
9 |
10 | * [Git](https://git-scm.com/)
11 | * [Node.js](https://nodejs.org/)
12 | * [Yarn](https://yarnpkg.com/)
13 | * [Ember CLI](https://ember-cli.com/)
14 | * [Google Chrome](https://google.com/chrome/)
15 |
16 | ## Installation
17 |
18 | * `git clone ` this repository
19 | * `cd test-app`
20 | * `yarn install`
21 |
22 | ## Running / Development
23 |
24 | * `ember serve`
25 | * Visit your app at [http://localhost:4200](http://localhost:4200).
26 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
27 |
28 | ### Code Generators
29 |
30 | Make use of the many generators for code, try `ember help generate` for more details
31 |
32 | ### Running Tests
33 |
34 | * `ember test`
35 | * `ember test --server`
36 |
37 | ### Linting
38 |
39 | * `yarn lint`
40 | * `yarn lint:fix`
41 |
42 | ### Building
43 |
44 | * `ember build` (development)
45 | * `ember build --environment production` (production)
46 |
47 | ### Deploying
48 |
49 | Specify what it takes to deploy your app.
50 |
51 | ## Further Reading / Useful Links
52 |
53 | * [ember.js](https://emberjs.com/)
54 | * [ember-cli](https://ember-cli.com/)
55 | * Development Browser Extensions
56 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
57 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
58 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/computed.js:
--------------------------------------------------------------------------------
1 | import { computed, get } from '@ember/object';
2 | import { matchesState as xstateMatchesState } from 'xstate';
3 | import { A, makeArray } from '@ember/array';
4 | import Statechart from './utils/statechart';
5 |
6 | function decorateStopInterpreterOnDestroy(destroyFn, service) {
7 | return function () {
8 | service.stop();
9 |
10 | destroyFn.apply(this, ...arguments);
11 | };
12 | }
13 |
14 | function matchesState(states, statechartPropertyName = 'statechart') {
15 | return computed(`${statechartPropertyName}.currentState`, function () {
16 | const _states = A(makeArray(states));
17 |
18 | return _states.any((state) => {
19 | return xstateMatchesState(
20 | state,
21 | get(this, `${statechartPropertyName}.currentState.value`)
22 | );
23 | });
24 | });
25 | }
26 |
27 | function debugState(statechartPropertyName = 'statechart') {
28 | return computed(`${statechartPropertyName}.currentState`, function () {
29 | return JSON.stringify(
30 | get(this, `${statechartPropertyName}.currentState.value`)
31 | );
32 | });
33 | }
34 |
35 | function statechart(config, options) {
36 | /* eslint-disable ember/require-computed-property-dependencies */
37 | return computed(function () {
38 | const initialContext = this;
39 |
40 | const statechart = new Statechart(config, options, initialContext);
41 |
42 | /* eslint-disable ember/no-side-effects */
43 | this.willDestroy = decorateStopInterpreterOnDestroy(
44 | this.willDestroy,
45 | statechart.service
46 | );
47 |
48 | return statechart;
49 | });
50 | }
51 |
52 | export { statechart, matchesState, debugState };
53 |
--------------------------------------------------------------------------------
/packages/site/app/styles/app.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss/base";
2 | @import "tailwindcss/components";
3 | @import "tailwindcss/utilities";
4 |
5 | .w-128 {
6 | width: 32rem;
7 | }
8 |
9 | .h-128 {
10 | height: 32rem;
11 | }
12 |
13 | .lds-ring {
14 | display: inline-block;
15 | position: relative;
16 | width: 16px;
17 | height: 16px;
18 | }
19 |
20 | .lds-ring div {
21 | box-sizing: border-box;
22 | display: block;
23 | position: absolute;
24 | width: 16px;
25 | height: 16px;
26 | border: 2px solid #fff;
27 | border-radius: 50%;
28 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
29 | border-color: #fff transparent transparent transparent;
30 | }
31 | .lds-ring div:nth-child(1) {
32 | animation-delay: -0.45s;
33 | }
34 | .lds-ring div:nth-child(2) {
35 | animation-delay: -0.3s;
36 | }
37 | .lds-ring div:nth-child(3) {
38 | animation-delay: -0.15s;
39 | }
40 | @keyframes lds-ring {
41 | 0% {
42 | transform: rotate(0deg);
43 | }
44 | 100% {
45 | transform: rotate(360deg);
46 | }
47 | }
48 |
49 | .quote {
50 | @apply flex;
51 | }
52 | .quote:before {
53 | content: "";
54 | }
55 |
56 | .quote:after {
57 | content: "";
58 | }
59 |
60 | .fancy-underline {
61 | @apply inline-block relative
62 |
63 | }
64 | .fancy-underline::before {
65 | content: '';
66 | left: 50%;
67 | @apply bg-current absolute bottom-0 w-0 h-0.5 transform transition-all ease-in-out
68 | }
69 |
70 | .fancy-underline:hover::before {
71 | @apply w-full left-0
72 | }
73 |
74 | .text-underline-under {
75 | text-underline-position: under;
76 | }
77 |
78 | /* for easier styling of demo blocks - prism and prose will be overidden */
79 | pre[class*="language-"] {
80 | margin: 0;
81 | }
82 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/index.d.ts:
--------------------------------------------------------------------------------
1 | import { useMachine } from './-private/usables';
2 | import type { StateValue } from 'xstate';
3 | /**
4 | * A decorator that can be used to create a getter that matches against an
5 | * {@link Statechart}'s state and will return either `true` or `false`
6 | *
7 | *
8 | * ```js
9 | * import Component from '@glimmer/component';
10 | * import buttonMachine from '../machines/button';
11 | *
12 | * export default class Button extends Component {
13 | * @use statechart = useMachine(this, () => {
14 | * return {
15 | * machine: buttonMachine
16 | * }
17 | * })
18 | *
19 | * @matchesState('disabled')
20 | * isDisabled; // true when statechart is in `disabled` state
21 | *
22 | * @matchesState({ interactivity: 'disabled' })
23 | * isDisabled; // it is possible to match against nested/parallel states
24 | * }
25 | * ```
26 | *
27 | * You can match against any XState [StateValue](https://xstate.js.org/api/globals.html#statevalue)
28 | *
29 | * By default `matchesState` expects your {@link Statechart} to be called `statechart`.
30 | * If you named it differently you can use the second param to this decorator:
31 | *
32 | *
33 | * ```js
34 | * import Component from '@glimmer/component';
35 | * import buttonMachine from '../machines/button';
36 | *
37 | * export default class Button extends Component {
38 | * @use sc = useMachine(this, () => {
39 | * return {
40 | * machine: buttonMachine
41 | * }
42 | * })
43 | *
44 | * @matchesState('disabled', 'sc')
45 | * isDisabled;
46 | * }
47 | * ```
48 | *
49 | */
50 | declare function matchesState(
51 | state: StateValue,
52 | statechartPropertyName?: string
53 | ): any;
54 | export { useMachine, matchesState };
55 |
--------------------------------------------------------------------------------
/packages/site/app/components/api-toc.hbs:
--------------------------------------------------------------------------------
1 |
57 |
--------------------------------------------------------------------------------
/packages/site/app/components/quickstart-button.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET quickstart-button
2 | import Component from '@glimmer/component';
3 | import { action } from '@ember/object';
4 | import { task } from 'ember-concurrency';
5 | import { matchesState } from 'ember-statecharts';
6 | import { useMachine } from 'ember-statecharts/-private/usables';
7 |
8 | import quickstartButtonMachine from '../machines/quickstart-button';
9 |
10 | function noop() {}
11 |
12 | export default class QuickstartButton extends Component {
13 | get onClick() {
14 | return this.args.onClick || noop;
15 | }
16 |
17 | statechart = useMachine(this, () => {
18 | const { performSubmitTask, onSuccess, onError } = this;
19 |
20 | return {
21 | machine: quickstartButtonMachine.withConfig({
22 | actions: {
23 | handleSubmit: performSubmitTask,
24 | handleSuccess: onSuccess,
25 | handleError: onError,
26 | },
27 | }),
28 | };
29 | });
30 |
31 | @matchesState('busy')
32 | isBusy;
33 |
34 | get isDisabled() {
35 | return this.isBusy || this.args.disabled;
36 | }
37 |
38 | @task(function* () {
39 | try {
40 | const result = yield this.onClick();
41 | this.statechart.send('SUCCESS', { result });
42 | } catch (e) {
43 | this.statechart.send('ERROR', { error: e });
44 | }
45 | })
46 | handleSubmitTask;
47 |
48 | @action
49 | handleClick() {
50 | this.statechart.send('SUBMIT');
51 | }
52 |
53 | @action
54 | onSuccess(_context, { result }) {
55 | return (this.args.onSuccess && this.args.onSuccess(result)) || noop();
56 | }
57 |
58 | @action
59 | onError(_context, { error }) {
60 | return (this.args.onError && this.args.onError(error)) || noop();
61 | }
62 |
63 | @action
64 | performSubmitTask() {
65 | this.handleSubmitTask.perform();
66 | }
67 | }
68 | // END-SNIPPET
69 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/-private/use-resource.ts:
--------------------------------------------------------------------------------
1 | import type { Resource } from './resource';
2 | import type { Cache } from '@glimmer/validator';
3 | import { invokeHelper } from '@ember/helper';
4 |
5 | interface TemplateArgs {
6 | positional: readonly unknown[];
7 | named: Record;
8 | }
9 | type Args = TemplateArgs | TemplateArgs['positional'] | TemplateArgs['named'];
10 |
11 | import { getValue } from '@glimmer/tracking/primitives/cache';
12 |
13 | export function useUnproxiedResource<
14 | TArgs = Args,
15 | T extends Resource = Resource
16 | >(destroyable: object, definition: object, args?: () => TArgs): { value: T } {
17 | let resource: Cache;
18 |
19 | return {
20 | get value(): T {
21 | if (!resource) {
22 | resource = invokeHelper(
23 | destroyable,
24 | definition, // eslint-disable-line
25 | args
26 | ) as Cache;
27 | }
28 |
29 | return getValue(resource)!; // eslint-disable-line
30 | },
31 | };
32 | }
33 |
34 | export function useResource<
35 | TArgs = Args,
36 | T extends Resource = Resource
37 | >(destroyable: object, definition: object, args?: () => TArgs): T {
38 | const target = useUnproxiedResource(destroyable, definition, args);
39 |
40 | return new Proxy(target, {
41 | get(target, key): unknown {
42 | const instance = target.value;
43 | const value = Reflect.get(instance, key, instance);
44 |
45 | return typeof value === 'function' ? value.bind(instance) : value;
46 | },
47 | ownKeys(target): (string | number | symbol)[] {
48 | return Reflect.ownKeys(target.value);
49 | },
50 | getOwnPropertyDescriptor(target, key): PropertyDescriptor | undefined {
51 | return Reflect.getOwnPropertyDescriptor(target.value, key);
52 | },
53 | }) as never as T;
54 | }
55 |
--------------------------------------------------------------------------------
/.eslintrc.base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | ecmaVersion: 2018,
8 | sourceType: 'module',
9 | ecmaFeatures: {
10 | legacyDecorators: true,
11 | },
12 | },
13 | plugins: ['ember'],
14 | extends: [
15 | 'eslint:recommended',
16 | 'plugin:ember/recommended',
17 | 'plugin:prettier/recommended',
18 | ],
19 | env: {
20 | browser: true,
21 | },
22 | overrides: [
23 | // use different parser for ts files
24 | {
25 | files: ['**/*.ts'],
26 | parser: '@typescript-eslint/parser',
27 | extends: [
28 | 'eslint:recommended',
29 | 'plugin:@typescript-eslint/recommended',
30 | 'plugin:ember/recommended',
31 | 'plugin:prettier/recommended',
32 | ],
33 | rules: {
34 | '@typescript-eslint/ban-types': ['off', { object: null }],
35 | },
36 | },
37 | // node files
38 | {
39 | files: [
40 | './.eslintrc.js',
41 | './.eslintrc.base.js',
42 | './.prettierrc.js',
43 | './.template-lintrc.js',
44 | './.docfy-config.js',
45 | './tailwind.config.js',
46 | './ember-cli-build.js',
47 | './index.js',
48 | './testem.js',
49 | './blueprints/*/index.js',
50 | './config/**/*.js',
51 | './tests/dummy/config/**/*.js',
52 | ],
53 | excludedFiles: [
54 | 'addon/**',
55 | 'addon-test-support/**',
56 | 'app/**',
57 | 'tests/dummy/app/**',
58 | ],
59 | parserOptions: {
60 | sourceType: 'script',
61 | },
62 | env: {
63 | browser: false,
64 | node: true,
65 | },
66 | plugins: ['node'],
67 | extends: ['plugin:node/recommended'],
68 | rules: {
69 | 'node/no-unpublished-require': 'off',
70 | 'node/no-extraneous-require': 'off',
71 | },
72 | },
73 | {
74 | // Test files:
75 | files: ['tests/**/*-test.{js,ts}'],
76 | extends: ['plugin:qunit/recommended'],
77 | },
78 | ],
79 | };
80 |
--------------------------------------------------------------------------------
/packages/site/app/machines/quickstart-button-refined.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET quickstart-button-machine-refined.js
2 | import { Machine } from 'xstate';
3 |
4 | export default Machine(
5 | {
6 | type: 'parallel',
7 | states: {
8 | interactivity: {
9 | initial: 'unknown',
10 | states: {
11 | unknown: {
12 | on: {
13 | '': [
14 | { target: 'enabled', cond: 'isEnabled' },
15 | { target: 'disabled' },
16 | ],
17 | },
18 | },
19 | enabled: {
20 | on: {
21 | DISABLE: 'disabled',
22 | },
23 | },
24 | disabled: {
25 | on: {
26 | ENABLE: 'enabled',
27 | },
28 | },
29 | },
30 | },
31 | activity: {
32 | initial: 'idle',
33 | states: {
34 | idle: {
35 | on: {
36 | SUBMIT: {
37 | target: 'busy',
38 | cond: 'isEnabled',
39 | },
40 | },
41 | },
42 | busy: {
43 | entry: ['handleSubmit'],
44 | on: {
45 | SUCCESS: 'success',
46 | ERROR: 'error',
47 | },
48 | },
49 | success: {
50 | entry: ['handleSuccess'],
51 | on: {
52 | SUBMIT: {
53 | target: 'busy',
54 | cond: 'isEnabled',
55 | },
56 | },
57 | },
58 | error: {
59 | entry: ['handleError'],
60 | on: {
61 | SUBMIT: {
62 | target: 'busy',
63 | cond: 'isEnabled',
64 | },
65 | },
66 | },
67 | },
68 | },
69 | },
70 | },
71 | {
72 | actions: {
73 | handleSubmit() {},
74 | handleSuccess() {},
75 | handleError() {},
76 | },
77 | guards: {
78 | isEnabled(context) {
79 | return !context.disabled;
80 | },
81 | },
82 | }
83 | );
84 | // END-SNIPPET
85 |
--------------------------------------------------------------------------------
/packages/site/app/components/page-headings.hbs:
--------------------------------------------------------------------------------
1 |
4 |
5 | {{#if page.headings.length}}
6 |
7 | -
8 |
15 |
16 | {{#each page.headings as |heading|}}
17 | -
18 |
29 | {{heading.title}}
30 |
31 |
32 | {{#if heading.headings.length}}
33 |
52 | {{/if}}
53 |
54 | {{/each}}
55 |
56 | {{/if}}
57 |
58 |
59 |
--------------------------------------------------------------------------------
/packages/site/app/components/docs-header.hbs:
--------------------------------------------------------------------------------
1 | {{!-- template-lint-disable --}}
2 |
52 |
53 | {{#if isShowingVersionSelector}}
54 | {{docs-header/version-selector on-close=(action (mut isShowingVersionSelector false))}}
55 | {{/if}}
56 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function matomoSiteIdForDeployTarget(deployTarget) {
4 | if (deployTarget === 'production') {
5 | return '3';
6 | }
7 |
8 | return '2';
9 | }
10 |
11 | module.exports = function (environment) {
12 | const deployTarget = process.env.DEPLOY_TARGET || 'development';
13 |
14 | let ENV = {
15 | modulePrefix: 'dummy',
16 | deployTarget,
17 | environment,
18 | rootURL: '/',
19 | locationType: 'auto',
20 | EmberENV: {
21 | FEATURES: {
22 | // Here you can enable experimental features on an ember canary build
23 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
24 | },
25 | EXTEND_PROTOTYPES: {
26 | // Prevent Ember Data from overriding Date.parse.
27 | Date: false,
28 | },
29 | },
30 |
31 | APP: {
32 | // Here you can pass flags/options to your application instance
33 | // when it is created
34 | },
35 | metricsAdapters: [
36 | {
37 | name: 'Matomo',
38 | environments: ['development', 'production'],
39 | config: {
40 | scriptUrl: '//cdn.matomo.cloud/effective-ember.matomo.cloud',
41 | trackerUrl: 'https://effective-ember.matomo.cloud',
42 | siteId: matomoSiteIdForDeployTarget(deployTarget),
43 | disableCookies: true,
44 | },
45 | },
46 | ],
47 | };
48 |
49 | if (environment === 'development') {
50 | // ENV.APP.LOG_RESOLVER = true;
51 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
52 | // ENV.APP.LOG_TRANSITIONS = true;
53 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
54 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
55 | }
56 |
57 | if (environment === 'test') {
58 | // Testem prefers this...
59 | ENV.locationType = 'none';
60 |
61 | // keep test console output quieter
62 | ENV.APP.LOG_ACTIVE_GENERATION = false;
63 | ENV.APP.LOG_VIEW_LOOKUPS = false;
64 |
65 | ENV.APP.rootElement = '#ember-testing';
66 | ENV.APP.autoboot = false;
67 | }
68 |
69 | if (environment === 'production') {
70 | // here you can enable a production-specific feature
71 | }
72 |
73 | return ENV;
74 | };
75 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/index.ts:
--------------------------------------------------------------------------------
1 | import { useMachine } from './-private/usables';
2 |
3 | import { matchesState as xstateMatchesState } from 'xstate';
4 |
5 | import type { StateValue } from 'xstate';
6 |
7 | /**
8 | * A decorator that can be used to create a getter that matches against an
9 | * {@link Statechart}'s state and will return either `true` or `false`
10 | *
11 | *
12 | * ```js
13 | * import Component from '@glimmer/component';
14 | * import buttonMachine from '../machines/button';
15 | *
16 | * export default class Button extends Component {
17 | * @use statechart = useMachine(this, () => {
18 | * return {
19 | * machine: buttonMachine
20 | * }
21 | * })
22 | *
23 | * @matchesState('disabled')
24 | * isDisabled; // true when statechart is in `disabled` state
25 | *
26 | * @matchesState({ interactivity: 'disabled' })
27 | * isDisabled; // it is possible to match against nested/parallel states
28 | * }
29 | * ```
30 | *
31 | * You can match against any XState [StateValue](https://xstate.js.org/api/globals.html#statevalue)
32 | *
33 | * By default `matchesState` expects your {@link Statechart} to be called `statechart`.
34 | * If you named it differently you can use the second param to this decorator:
35 | *
36 | *
37 | * ```js
38 | * import Component from '@glimmer/component';
39 | * import buttonMachine from '../machines/button';
40 | *
41 | * export default class Button extends Component {
42 | * @use sc = useMachine(this, () => {
43 | * return {
44 | * machine: buttonMachine
45 | * }
46 | * })
47 | *
48 | * @matchesState('disabled', 'sc')
49 | * isDisabled;
50 | * }
51 | * ```
52 | *
53 | */
54 | /* eslint-disable @typescript-eslint/no-explicit-any */
55 | function matchesState(
56 | state: StateValue,
57 | statechartPropertyName = 'statechart'
58 | ): any {
59 | return function () {
60 | return {
61 | get(this: any): boolean {
62 | const statechart = this[statechartPropertyName] as
63 | | { state: { value: StateValue } }
64 | | undefined;
65 |
66 | if (statechart) {
67 | return xstateMatchesState(state, statechart.state.value);
68 | } else {
69 | return false;
70 | }
71 | },
72 | };
73 | };
74 | }
75 |
76 | export { useMachine, matchesState };
77 |
--------------------------------------------------------------------------------
/packages/test-app/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
5 |
6 | module.exports = async function () {
7 | return {
8 | useYarn: true,
9 | scenarios: [
10 | {
11 | name: 'ember-lts-3.24',
12 | npm: {
13 | devDependencies: {
14 | 'ember-source': '~3.24.3',
15 | },
16 | },
17 | },
18 | {
19 | name: 'ember-release',
20 | npm: {
21 | devDependencies: {
22 | 'ember-source': await getChannelURL('release'),
23 | 'ember-auto-import': '^2.0.0',
24 | webpack: '^5.0.0',
25 | },
26 | },
27 | },
28 | {
29 | name: 'ember-beta',
30 | npm: {
31 | devDependencies: {
32 | 'ember-source': await getChannelURL('beta'),
33 | 'ember-auto-import': '^2.0.0',
34 | webpack: '^5.0.0',
35 | },
36 | },
37 | },
38 | {
39 | name: 'ember-canary',
40 | npm: {
41 | devDependencies: {
42 | 'ember-source': await getChannelURL('canary'),
43 | 'ember-auto-import': '^2.0.0',
44 | webpack: '^5.0.0',
45 | },
46 | },
47 | },
48 | {
49 | name: 'ember-default-with-jquery',
50 | env: {
51 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
52 | 'jquery-integration': true,
53 | }),
54 | },
55 | npm: {
56 | devDependencies: {
57 | '@ember/jquery': '^1.1.0',
58 | },
59 | },
60 | },
61 | {
62 | name: 'ember-classic',
63 | env: {
64 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
65 | 'application-template-wrapper': true,
66 | 'default-async-observers': false,
67 | 'template-only-glimmer-components': false,
68 | }),
69 | },
70 | npm: {
71 | devDependencies: {
72 | 'ember-source': '~3.28.0',
73 | },
74 | ember: {
75 | edition: 'classic',
76 | },
77 | },
78 | },
79 | embroiderSafe(),
80 | embroiderOptimized(),
81 | ],
82 | };
83 | };
84 |
--------------------------------------------------------------------------------
/packages/site/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function matomoSiteIdForDeployTarget(deployTarget) {
4 | if (deployTarget === 'production') {
5 | return '3';
6 | }
7 |
8 | return '2';
9 | }
10 |
11 | module.exports = function (environment) {
12 | const deployTarget = process.env.DEPLOY_TARGET || 'development';
13 |
14 | let ENV = {
15 | modulePrefix: 'site',
16 | deployTarget,
17 | environment,
18 | rootURL: '/',
19 | locationType: 'auto',
20 | EmberENV: {
21 | FEATURES: {
22 | // Here you can enable experimental features on an ember canary build
23 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
24 | },
25 | EXTEND_PROTOTYPES: {
26 | // Prevent Ember Data from overriding Date.parse.
27 | Date: false,
28 | },
29 | },
30 |
31 | APP: {
32 | // Here you can pass flags/options to your application instance
33 | // when it is created
34 | },
35 | metricsAdapters: [
36 | {
37 | name: 'Matomo',
38 | environments: ['development', 'production'],
39 | config: {
40 | scriptUrl: '//cdn.matomo.cloud/effective-ember.matomo.cloud',
41 | trackerUrl: 'https://effective-ember.matomo.cloud',
42 | siteId: matomoSiteIdForDeployTarget(deployTarget),
43 | disableCookies: true,
44 | },
45 | },
46 | ],
47 | };
48 |
49 | if (process.env.DEPLOY_TARGET) {
50 | const versionedDocInfo = require('../lib/versioned-doc-info');
51 | ENV.rootURL = versionedDocInfo(process.env.DEPLOY_TARGET).rootURL;
52 | }
53 |
54 | if (environment === 'development') {
55 | // ENV.APP.LOG_RESOLVER = true;
56 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
57 | // ENV.APP.LOG_TRANSITIONS = true;
58 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
59 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
60 | }
61 |
62 | if (environment === 'test') {
63 | // Testem prefers this...
64 | ENV.locationType = 'none';
65 |
66 | // keep test console output quieter
67 | ENV.APP.LOG_ACTIVE_GENERATION = false;
68 | ENV.APP.LOG_VIEW_LOOKUPS = false;
69 |
70 | ENV.APP.rootElement = '#ember-testing';
71 | ENV.APP.autoboot = false;
72 | }
73 |
74 | if (environment === 'production') {
75 | // here you can enable a production-specific feature
76 | }
77 |
78 | return ENV;
79 | };
80 |
--------------------------------------------------------------------------------
/packages/site/app/components/quickstart-button-refined.js:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET quickstart-button-final
2 | import Component from '@glimmer/component';
3 | import { action } from '@ember/object';
4 | import { task } from 'ember-concurrency';
5 | import { matchesState } from 'ember-statecharts';
6 | import { useMachine } from 'ember-statecharts/-private/usables';
7 | import quickstartButtonRefinedMachine from '../machines/quickstart-button-refined';
8 |
9 | function noop() {}
10 |
11 | export default class QuickstartButtonFinal extends Component {
12 | get onClick() {
13 | return this.args.onClick || noop;
14 | }
15 |
16 | @matchesState({ activity: 'busy' })
17 | isBusy;
18 |
19 | @matchesState({ interactivity: 'disabled' })
20 | isDisabled;
21 |
22 | @matchesState({ interactivity: 'unknown' })
23 | isInteractivityUnknown;
24 |
25 | get showAsDisabled() {
26 | const { isDisabled, isBusy, isInteractivityUnknown } = this;
27 |
28 | return isDisabled || isBusy || isInteractivityUnknown;
29 | }
30 |
31 | statechart = useMachine(this, () => {
32 | return {
33 | machine: quickstartButtonRefinedMachine
34 | .withContext({
35 | disabled: this.args.disabled,
36 | })
37 | .withConfig({
38 | actions: {
39 | handleSubmit: this.performSubmitTask,
40 | handleSuccess: this.onSuccess,
41 | handleError: this.onError,
42 | },
43 | guards: {
44 | isEnabled({ disabled }) {
45 | return !disabled;
46 | },
47 | },
48 | }),
49 | update: ({ send, machine: { context } }) => {
50 | const { disabled } = context;
51 |
52 | if (disabled) {
53 | send('DISABLE');
54 | } else {
55 | send('ENABLE');
56 | }
57 | },
58 | };
59 | });
60 |
61 | @task(function* () {
62 | try {
63 | const result = yield this.onClick();
64 | this.statechart.send('SUCCESS', { result });
65 | } catch (e) {
66 | this.statechart.send('ERROR', { error: e });
67 | }
68 | })
69 | handleSubmitTask;
70 |
71 | @action
72 | handleClick() {
73 | this.statechart.send('SUBMIT');
74 | }
75 |
76 | @action
77 | onSuccess(_context, { result }) {
78 | return this.args.onSuccess(result) || noop();
79 | }
80 |
81 | @action
82 | onError(_context, { error }) {
83 | return this.args.onError(error) || noop();
84 | }
85 |
86 | @action
87 | performSubmitTask() {
88 | this.handleSubmitTask.perform();
89 | }
90 | }
91 | // END-SNIPPET
92 |
--------------------------------------------------------------------------------
/packages/site/lib/deploy-versioned-doc/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | 'use strict';
3 |
4 | const versionedDocInfo = require('../versioned-doc-info');
5 | const path = require('path');
6 | const fs = require('fs-extra');
7 | const os = require('os');
8 |
9 | module.exports = {
10 | name: require('./package').name,
11 |
12 | createDeployPlugin: function (options) {
13 | return {
14 | name: options.name,
15 |
16 | async willUpload(context) {
17 | const {
18 | deployTarget,
19 | distDir,
20 | gitDeploy: { worktreePath },
21 | } = context;
22 |
23 | const { name, destDir, rootURL, sha, tag } = versionedDocInfo(
24 | deployTarget
25 | );
26 |
27 | // cache current build
28 | let currentBuild = path.join(os.tmpdir(), 'current-build-');
29 | fs.moveSync(distDir, currentBuild);
30 |
31 | // ensure we have a ${worktree}/versions/
32 | let versions = path.resolve(worktreePath, 'versions');
33 | fs.ensureDirSync(versions);
34 |
35 | if (deployTarget === 'latest') {
36 | // keep the current versions
37 | fs.copySync(versions, path.resolve(currentBuild, 'versions'));
38 |
39 | // make current build the new dist
40 | fs.moveSync(currentBuild, distDir);
41 | } else {
42 | // keep current state of gh-pages
43 | fs.copySync(worktreePath, distDir);
44 |
45 | // move current build into versions/${branch || tag}
46 | fs.moveSync(currentBuild, path.resolve(distDir, destDir), {
47 | overwrite: true,
48 | });
49 | }
50 |
51 | // get ${worktree}/versions.json
52 | let versionsJSON = {};
53 | let versionsFile = path.resolve(worktreePath, 'versions.json');
54 | if (fs.existsSync(versionsFile)) {
55 | versionsJSON = fs.readJsonSync(versionsFile);
56 | }
57 |
58 | let key = name;
59 |
60 | // update key for latest, this is for ember-cli-addon-docs compatibility
61 | if (deployTarget === 'latest') {
62 | key = '-latest';
63 | }
64 |
65 | versionsJSON[key] = {
66 | path: rootURL,
67 | name,
68 | sha,
69 | tag,
70 | };
71 |
72 | // write current versions.json
73 | fs.writeFileSync(
74 | path.resolve(distDir, 'versions.json'),
75 | JSON.stringify(versionsJSON, null, 2)
76 | );
77 |
78 | fs.removeSync(currentBuild);
79 | },
80 | };
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/packages/site/app/templates/docs.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
15 | {{outlet}}
16 |
17 |
18 |
21 |
22 |
23 | {{#if previous}}
24 |
35 |
36 |
37 | {{previous.title}}
38 |
39 | {{/if}}
40 |
41 |
42 | {{#if next}}
43 |
44 | {{next.title}}
45 |
46 |
47 |
58 | {{/if}}
59 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/packages/test-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-app",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "Small description for test-app goes here",
6 | "repository": "",
7 | "license": "MIT",
8 | "author": "",
9 | "directories": {
10 | "doc": "doc",
11 | "test": "tests"
12 | },
13 | "scripts": {
14 | "build": "ember build --environment=production",
15 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
16 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
17 | "lint:hbs": "ember-template-lint .",
18 | "lint:hbs:fix": "ember-template-lint . --fix",
19 | "lint:js": "eslint . --cache",
20 | "lint:js:fix": "eslint . --fix",
21 | "start": "ember serve",
22 | "test": "npm-run-all lint test:*",
23 | "test:ember": "ember test --test-port=0",
24 | "test:ember-compatibility": "ember try:each"
25 | },
26 | "devDependencies": {
27 | "@ember/optional-features": "^2.0.0",
28 | "@ember/test-helpers": "^2.4.2",
29 | "@embroider/test-setup": "^0.43.5",
30 | "@glimmer/component": "^1.0.4",
31 | "@glimmer/tracking": "^1.0.4",
32 | "babel-eslint": "^10.1.0",
33 | "broccoli-asset-rev": "^3.0.0",
34 | "ember-auto-import": "^1.11.3",
35 | "ember-cli": "~3.28.4",
36 | "ember-cli-app-version": "^5.0.0",
37 | "ember-cli-babel": "^7.26.6",
38 | "ember-cli-dependency-checker": "^3.2.0",
39 | "ember-cli-htmlbars": "^5.7.1",
40 | "ember-cli-inject-live-reload": "^2.1.0",
41 | "ember-cli-sri": "^2.1.1",
42 | "ember-cli-terser": "^4.0.2",
43 | "ember-data": "~3.28.0",
44 | "ember-export-application-global": "^2.0.1",
45 | "ember-fetch": "^8.1.1",
46 | "ember-load-initializers": "^2.1.2",
47 | "ember-maybe-import-regenerator": "^0.1.6",
48 | "ember-page-title": "^6.2.2",
49 | "ember-qunit": "^5.1.4",
50 | "ember-resolver": "^8.0.2",
51 | "ember-source": "~3.28.0",
52 | "ember-source-channel-url": "^3.0.0",
53 | "ember-statecharts": "^0.13.2",
54 | "ember-template-lint": "^3.6.0",
55 | "ember-try": "^1.4.0",
56 | "eslint": "^7.32.0",
57 | "eslint-config-prettier": "^8.3.0",
58 | "eslint-plugin-ember": "^10.5.4",
59 | "eslint-plugin-node": "^11.1.0",
60 | "eslint-plugin-prettier": "^3.4.1",
61 | "eslint-plugin-qunit": "^6.2.0",
62 | "loader.js": "^4.7.0",
63 | "npm-run-all": "^4.1.5",
64 | "prettier": "^2.3.2",
65 | "qunit": "^2.16.0",
66 | "qunit-dom": "^1.6.0",
67 | "xstate": "^4.12.0"
68 | },
69 | "engines": {
70 | "node": "12.* || 14.* || >= 16"
71 | },
72 | "ember": {
73 | "edition": "octane"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/packages/site/app/machines/typed-button.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-empty-function */
2 | // BEGIN-SNIPPET typed-button-machine
3 | import { createMachine } from 'xstate';
4 |
5 | export interface ButtonContext {
6 | disabled?: boolean;
7 | }
8 |
9 | export type ButtonEvent =
10 | | { type: 'SUBMIT' }
11 | | { type: 'SUCCESS'; result: any }
12 | | { type: 'ERROR'; error: any }
13 | | { type: 'ENABLE' }
14 | | { type: 'DISABLE' };
15 |
16 | export type ButtonState =
17 | | { value: 'idle'; context: { disabled?: boolean } }
18 | | { value: 'busy'; context: { disabled?: boolean } }
19 | | { value: 'success'; context: { disabled?: boolean } }
20 | | { value: 'error'; context: { disabled?: boolean } };
21 |
22 | export default createMachine(
23 | {
24 | type: 'parallel',
25 | states: {
26 | interactivity: {
27 | initial: 'unknown',
28 | states: {
29 | unknown: {
30 | on: {
31 | '': [
32 | { target: 'enabled', cond: 'isEnabled' },
33 | { target: 'disabled' },
34 | ],
35 | },
36 | },
37 | enabled: {
38 | on: {
39 | DISABLE: 'disabled',
40 | },
41 | },
42 | disabled: {
43 | on: {
44 | ENABLE: 'enabled',
45 | },
46 | },
47 | },
48 | },
49 | activity: {
50 | initial: 'idle',
51 | states: {
52 | idle: {
53 | on: {
54 | SUBMIT: {
55 | target: 'busy',
56 | cond: 'isEnabled',
57 | },
58 | },
59 | },
60 | busy: {
61 | entry: ['handleSubmit'],
62 | on: {
63 | SUCCESS: 'success',
64 | ERROR: 'error',
65 | },
66 | },
67 | success: {
68 | entry: ['handleSuccess'],
69 | on: {
70 | SUBMIT: {
71 | target: 'busy',
72 | cond: 'isEnabled',
73 | },
74 | },
75 | },
76 | error: {
77 | entry: ['handleError'],
78 | on: {
79 | SUBMIT: {
80 | target: 'busy',
81 | cond: 'isEnabled',
82 | },
83 | },
84 | },
85 | },
86 | },
87 | },
88 | },
89 | {
90 | actions: {
91 | handleSubmit() {},
92 | handleSuccess() {},
93 | handleError() {},
94 | },
95 | guards: {
96 | isEnabled(context) {
97 | return !context.disabled;
98 | },
99 | },
100 | }
101 | );
102 | // END-SNIPPET
103 |
--------------------------------------------------------------------------------
/packages/site/public/ember-statecharts-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
--------------------------------------------------------------------------------
/packages/site/app/components/typed-button.ts:
--------------------------------------------------------------------------------
1 | // BEGIN-SNIPPET typed-button
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | import Component from '@glimmer/component';
4 | import { useMachine, matchesState } from 'ember-statecharts';
5 | import buttonMachine, {
6 | ButtonContext,
7 | ButtonEvent,
8 | ButtonState,
9 | } from '../machines/typed-button';
10 | import { TaskGenerator } from 'ember-concurrency';
11 |
12 | import { task } from 'ember-concurrency-decorators';
13 | import { taskFor } from 'ember-concurrency-ts';
14 |
15 | import { action } from '@ember/object';
16 |
17 | interface ButtonArgs {
18 | disabled?: boolean;
19 | onClick?: () => any;
20 | onSuccess?: (result: any) => any;
21 | onError?: (error: any) => any;
22 | }
23 |
24 | /* eslint-disable-next-line @typescript-eslint/no-empty-function */
25 | function noop() {}
26 |
27 | export default class TypedButton extends Component {
28 | get onClick(): any {
29 | return this.args.onClick || noop;
30 | }
31 |
32 | @matchesState({ activity: 'busy' })
33 | isBusy!: boolean;
34 |
35 | @matchesState({ interactivity: 'disabled' })
36 | isDisabled!: boolean;
37 |
38 | @matchesState({ interactivity: 'unknown' })
39 | isInteractivityUnknown!: boolean;
40 |
41 | get showAsDisabled(): boolean {
42 | const { isDisabled, isBusy, isInteractivityUnknown } = this;
43 |
44 | return isDisabled || isBusy || isInteractivityUnknown;
45 | }
46 |
47 | statechart = useMachine(
48 | this,
49 | () => {
50 | return {
51 | machine: buttonMachine
52 | .withContext({
53 | disabled: this.args.disabled,
54 | })
55 | .withConfig({
56 | actions: {
57 | handleSubmit: this.performSubmitTask,
58 | handleSuccess: this.onSuccess,
59 | handleError: this.onError,
60 | },
61 | }),
62 | update: ({ machine, send }) => {
63 | const disabled = machine.context?.disabled;
64 |
65 | if (disabled) {
66 | send('DISABLE');
67 | } else {
68 | send('ENABLE');
69 | }
70 | },
71 | };
72 | }
73 | );
74 |
75 | @task *submitTask(): TaskGenerator {
76 | try {
77 | const result = yield this.onClick();
78 |
79 | this.statechart.send('SUCCESS', { result });
80 | } catch (e) {
81 | this.statechart.send('ERROR', { error: e });
82 | }
83 | }
84 |
85 | @action
86 | handleClick(): void {
87 | this.statechart.send('SUBMIT');
88 | }
89 |
90 | @action
91 | onSuccess(
92 | _context: ButtonContext,
93 | { result }: Extract
94 | ): any {
95 | const functionToCall = this.args.onSuccess || noop;
96 |
97 | return functionToCall(result);
98 | }
99 |
100 | @action
101 | onError(
102 | _context: ButtonContext,
103 | { error }: Extract
104 | ): any {
105 | const functionToCall = this.args.onError || noop;
106 |
107 | return functionToCall(error);
108 | }
109 |
110 | @action
111 | performSubmitTask(): void {
112 | taskFor(this.submitTask).perform();
113 | }
114 | }
115 | // END-SNIPPET
116 |
--------------------------------------------------------------------------------
/packages/site/public/ember-statecharts-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow is largely based on
2 | # https://github.com/salsify/ember-css-modules/blob/master/.github/workflows/ci.yml
3 |
4 | name: CI
5 |
6 | # These trigger patterns courtesy of https://github.com/broccolijs/broccoli/pull/436
7 | on:
8 | pull_request:
9 | push:
10 | # filtering branches here prevents duplicate builds from pull_request and push
11 | branches:
12 | - master
13 | - 'v*'
14 | # always run CI for tags
15 | tags:
16 | - '*'
17 |
18 | # early issue detection: run CI weekly on Sundays
19 | schedule:
20 | - cron: '0 6 * * 0'
21 |
22 | env:
23 | CI: true
24 |
25 | jobs:
26 | test:
27 | name: Test
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout Code
31 | uses: actions/checkout@v2
32 | - name: Install Node
33 | uses: actions/setup-node@v2
34 | with:
35 | node-version: 14
36 | - name: Install Dependencies
37 | run: yarn install --frozen-lockfile
38 | - name: Lerna Bootstrap
39 | run: yarn run lerna bootstrap
40 | - name: Lint
41 | run: yarn run lerna run lint
42 | - name: Tests
43 | run: yarn run lerna run test:ember
44 |
45 | test-locked-deps:
46 | name: Locked Deps
47 | runs-on: ubuntu-latest
48 | steps:
49 | - name: Checkout Code
50 | uses: actions/checkout@v2
51 | - name: Install Node
52 | uses: actions/setup-node@v2
53 | with:
54 | node-version: 14
55 | - name: Install Dependencies
56 | run: yarn install --frozen-lockfile
57 | - name: Lerna Bootstrap
58 | run: yarn run lerna bootstrap
59 | - name: Lint
60 | run: yarn run lerna run lint
61 | - name: Browser Tests
62 | run: yarn run lerna run test:ember
63 |
64 | test-old-dependencies:
65 | name: Oldest Supported Env
66 | runs-on: ubuntu-latest
67 | steps:
68 | - name: Checkout Code
69 | uses: actions/checkout@v2
70 | - name: Install Node
71 | uses: actions/setup-node@v2
72 | with:
73 | node-version: 12
74 | - name: Install Dependencies
75 | run: yarn install --frozen-lockfile
76 | - name: Lerna Bootstrap
77 | run: yarn run lerna bootstrap
78 | - name: Browser Tests
79 | run: yarn run lerna run test:ember
80 |
81 | test-try:
82 | name: Ember Try (${{ matrix.scenario }})
83 | runs-on: ubuntu-latest
84 | strategy:
85 | fail-fast: false
86 | matrix:
87 | include:
88 | - scenario: ember-lts-3.24
89 | allow-failure: false
90 | - scenario: ember-release
91 | allow-failure: false
92 | - scenario: ember-beta
93 | allow-failure: false
94 | - scenario: ember-canary
95 | allow-failure: false
96 | steps:
97 | - name: Checkout Code
98 | uses: actions/checkout@v2
99 | - name: Install Node
100 | uses: actions/setup-node@v2
101 | with:
102 | node-version: 14
103 | - name: Install Dependencies
104 | run: yarn install --frozen-lockfile
105 | - name: Lerna Bootstrap
106 | run: yarn run lerna bootstrap
107 | - name: Try Scenario
108 | run: yarn run lerna exec --stream --scope test-app ember try:one ${{ matrix.scenario }}
109 | continue-on-error: ${{ matrix.allow-failure }}
110 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/-private/resource.ts:
--------------------------------------------------------------------------------
1 | import { setHelperManager, capabilities } from '@ember/helper';
2 |
3 | import { setOwner } from '@ember/application';
4 | import {
5 | destroy,
6 | associateDestroyableChild,
7 | registerDestructor,
8 | } from '@ember/destroyable';
9 |
10 | import { createCache, getValue } from '@glimmer/tracking/primitives/cache';
11 |
12 | import type { Cache } from '@glimmer/validator';
13 |
14 | type Owner = Record;
15 |
16 | export interface TemplateArgs {
17 | positional: readonly unknown[];
18 | named: Record;
19 | }
20 |
21 | declare const HELPER_DEFINITION: unique symbol;
22 | type HelperDefinition = T & {
23 | readonly [HELPER_DEFINITION]: true;
24 | };
25 |
26 | export abstract class Resource {
27 | protected readonly args!: Args;
28 |
29 | constructor(owner: Owner, args: Args) {
30 | setOwner(this, owner);
31 |
32 | this.args = args;
33 | }
34 |
35 | // eslint-disable-next-line @typescript-eslint/no-empty-function
36 | setup(): void {}
37 |
38 | update?(args: Args): void;
39 | teardown?(): void;
40 | }
41 |
42 | class ResourceManager {
43 | readonly capabilities = capabilities('3.23', {
44 | hasValue: true,
45 | hasDestroyable: true,
46 | });
47 |
48 | private readonly owner: Owner;
49 |
50 | constructor(owner: Owner) {
51 | this.owner = owner;
52 | }
53 |
54 | createHelper(
55 | Class: HelperDefinition Resource>,
56 | args: Args
57 | ): Cache {
58 | const { update, teardown } = Class.prototype as Resource;
59 |
60 | const hasUpdate = typeof update === 'function';
61 | const hasTeardown = typeof teardown === 'function';
62 |
63 | const owner = this.owner;
64 |
65 | let instance: Resource | undefined;
66 | let cache: Cache;
67 |
68 | if (hasUpdate) {
69 | cache = createCache(() => {
70 | if (instance === undefined) {
71 | instance = setupInstance(cache, Class, owner, args, hasTeardown);
72 | } else {
73 | instance.update!(args); // eslint-disable-line
74 | }
75 |
76 | return instance;
77 | });
78 | } else {
79 | cache = createCache(() => {
80 | if (instance !== undefined) {
81 | destroy(instance);
82 | }
83 |
84 | instance = setupInstance(cache, Class, owner, args, hasTeardown);
85 |
86 | return instance;
87 | });
88 | }
89 |
90 | return cache;
91 | }
92 |
93 | getValue(cache: Cache): Resource | undefined {
94 | const instance = getValue(cache);
95 |
96 | return instance;
97 | }
98 |
99 | getDestroyable(cache: Cache): Cache {
100 | return cache;
101 | }
102 |
103 | //eslint-disable-next-line
104 | getDebugName(fn: (...args: any[]) => void): string {
105 | return fn.name || '(anonymous function)';
106 | }
107 | }
108 |
109 | function setupInstance(
110 | cache: Cache,
111 | Class: new (owner: Owner, args: TemplateArgs) => T,
112 | owner: Owner,
113 | args: TemplateArgs,
114 | hasTeardown: boolean
115 | ): T {
116 | const instance = new Class(owner, args);
117 | associateDestroyableChild(cache, instance);
118 | instance.setup();
119 |
120 | if (hasTeardown) {
121 | registerDestructor(instance, () => instance.teardown!()); // eslint-disable-line
122 | }
123 |
124 | return instance;
125 | }
126 |
127 | setHelperManager((owner: Owner) => new ResourceManager(owner), Resource);
128 |
--------------------------------------------------------------------------------
/packages/site/app/modifiers/intersect-headings.js:
--------------------------------------------------------------------------------
1 | import Modifier from 'ember-modifier';
2 | import { action } from '@ember/object';
3 |
4 | function getHeadingIds(headings, output = []) {
5 | if (typeof headings === 'undefined') {
6 | return [];
7 | }
8 | headings.forEach((heading) => {
9 | output.push(heading.id);
10 | getHeadingIds(heading.headings, output);
11 | });
12 | return output;
13 | }
14 |
15 | export default class IntersectHeadingsModifier extends Modifier {
16 | handler = null;
17 | headings = [];
18 | observer = null;
19 | activeIndex = null;
20 |
21 | @action
22 | handleObserver(elements) {
23 | // Based on https://taylor.callsen.me/modern-navigation-menus-with-css-position-sticky-and-intersectionobservers/
24 |
25 | // current index must be memoized or tracked outside of function for comparison
26 | let localActiveIndex = this.activeIndex;
27 |
28 | // track which elements register above or below the document's current position
29 | const aboveIndeces = [];
30 | const belowIndeces = [];
31 |
32 | // loop through each intersection element
33 | // due to the asychronous nature of observers, callbacks must be designed to handle 1 or many intersecting elements
34 | elements.forEach((element) => {
35 | // detect if intersecting element is above the browser viewport; include cross browser logic
36 | const boundingClientRectY =
37 | typeof element.boundingClientRect.y !== 'undefined'
38 | ? element.boundingClientRect.y
39 | : element.boundingClientRect.top;
40 | const rootBoundsY =
41 | typeof element.rootBounds.y !== 'undefined'
42 | ? element.rootBounds.y
43 | : element.rootBounds.top;
44 | const isAbove = boundingClientRectY < rootBoundsY;
45 |
46 | const id = element.target.getAttribute('id');
47 | const intersectingElemIdx = this.headings.findIndex((item) => item == id);
48 |
49 | // record index as either above or below current index
50 | if (isAbove) aboveIndeces.push(intersectingElemIdx);
51 | else belowIndeces.push(intersectingElemIdx);
52 | });
53 |
54 | // determine min and max fired indeces values (support for multiple elements firing at once)
55 | const minIndex = Math.min(...belowIndeces);
56 | const maxIndex = Math.max(...aboveIndeces);
57 |
58 | // determine how to adjust localActiveIndex based on scroll direction
59 | if (aboveIndeces.length > 0) {
60 | // scrolling down - set to max of fired indeces
61 | localActiveIndex = maxIndex;
62 | } else if (belowIndeces.length > 0 && minIndex <= this.activeIndex) {
63 | // scrolling up - set to minimum of fired indeces
64 | localActiveIndex = minIndex - 1 >= 0 ? minIndex - 1 : 0;
65 | }
66 |
67 | // render new index to DOM (if required)
68 | if (localActiveIndex != this.activeIndex) {
69 | this.activeIndex = localActiveIndex;
70 |
71 | this.handler(this.headings[this.activeIndex]);
72 | }
73 | }
74 |
75 | observe() {
76 | if ('IntersectionObserver' in window) {
77 | this.observer = new IntersectionObserver(this.handleObserver, {
78 | rootMargin: '-96px', // Distance from top to heading id
79 | threshold: 1.0,
80 | });
81 |
82 | this.headings.forEach((id) => {
83 | const el = document.getElementById(id);
84 | if (el) {
85 | this.observer.observe(el);
86 | }
87 | });
88 | }
89 | }
90 |
91 | unobserve() {
92 | if (this.observer) {
93 | this.observer.disconnect();
94 | }
95 | }
96 |
97 | didUpdateArguments() {
98 | this.unobserve();
99 | }
100 |
101 | didReceiveArguments() {
102 | const [handler] = this.args.positional;
103 | this.handler = handler;
104 | this.headings = getHeadingIds(this.args.named.headings);
105 |
106 | this.observe();
107 | }
108 |
109 | willRemove() {
110 | this.unobserve();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/packages/site/tests/integration/components/x-button-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render, click, waitFor } from '@ember/test-helpers';
4 | import hbs from 'htmlbars-inline-precompile';
5 | import { Promise, resolve, reject } from 'rsvp';
6 |
7 | module('Integration | Component | quickstart-button', function (hooks) {
8 | setupRenderingTest(hooks);
9 |
10 | test('it renders a button', async function (assert) {
11 | await render(hbs``);
12 |
13 | assert.dom('button').exists('button is rendered');
14 | });
15 |
16 | test('it is possible to use the button in block-format', async function (assert) {
17 | let TEXT = 'Hello';
18 |
19 | this.set('text', TEXT);
20 |
21 | await render(hbs`
22 |
23 | {{this.text}} World!
24 |
25 | `);
26 |
27 | assert.dom('[data-test-button]').hasText('Hello World!');
28 | });
29 |
30 | test("it's possible to pass an onClick-action handler to the button", async function (assert) {
31 | assert.expect(1);
32 |
33 | this.set('wat', function () {
34 | assert.ok(true, 'action handler was called');
35 | });
36 |
37 | await render(hbs`
38 |
42 | `);
43 |
44 | await click('[data-test-button]');
45 | });
46 |
47 | test("when the action triggered by the button clicked gets fired and takes time it's not possible to trigger it again", async function (assert) {
48 | assert.expect(3);
49 |
50 | this.set('onClick', function () {
51 | assert.ok(true, 'onClick was triggered');
52 | return new Promise(function () {});
53 | });
54 |
55 | await render(hbs`
56 |
57 | `);
58 |
59 | click('[data-test-button]');
60 |
61 | await waitFor('[data-test-loading]');
62 |
63 | assert
64 | .dom('[data-test-loading]')
65 | .exists('button is displayed with loading ui while busy');
66 |
67 | this.set('onClick', function () {
68 | assert.ok(false, 'onClick should not be triggered again');
69 | });
70 |
71 | assert.dom('[data-test-button]').isDisabled('button is disabled when busy');
72 | });
73 |
74 | test("when passing the disabled property the button is disabled and won't trigger its action", async function (assert) {
75 | assert.expect(1);
76 |
77 | this.set('onClick', function () {
78 | assert.ok(false, 'onClick should not be triggered');
79 | });
80 |
81 | await render(hbs`
82 |
88 | `);
89 |
90 | assert.dom('[data-test-button]').isDisabled('button is disabled');
91 | });
92 |
93 | test('when the triggered action resolves the `onSuccess` handler is triggered', async function (assert) {
94 | assert.expect(1);
95 |
96 | this.set('onClick', function () {
97 | return resolve();
98 | });
99 |
100 | this.set('onSuccess', function () {
101 | assert.ok(true, '`onSuccess` was triggered');
102 | });
103 |
104 | await render(hbs`
105 |
111 | `);
112 |
113 | await click('[data-test-button]');
114 | });
115 |
116 | test('when the triggered action rejects the `onError` handler is triggered', async function (assert) {
117 | assert.expect(1);
118 |
119 | this.set('onClick', function () {
120 | return reject();
121 | });
122 |
123 | this.set('onError', function () {
124 | assert.ok(true, '`onError` was triggered');
125 | });
126 |
127 | await render(hbs`
128 |
134 | `);
135 |
136 | await click('[data-test-button]');
137 | });
138 | });
139 |
--------------------------------------------------------------------------------
/packages/site/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "site",
4 | "version": "0.13.2",
5 | "description": "Site for ember-statecharts",
6 | "homepage": "https://ember-statecharts.com/",
7 | "repository": "https://github.com/LevelbossMike/ember-statecharts",
8 | "license": "MIT",
9 | "author": "",
10 | "types": "./index.d.ts",
11 | "scripts": {
12 | "build": "ember build --environment=production",
13 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel 'lint:!(fix)'",
14 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
15 | "lint:hbs": "ember-template-lint .",
16 | "lint:hbs:fix": "ember-template-lint . --fix",
17 | "lint:js": "eslint . --cache",
18 | "lint:js:fix": "eslint . --fix",
19 | "prepublishOnly": "ember ts:precompile",
20 | "postpublish": "ember ts:clean",
21 | "start": "ember serve",
22 | "test": "npm-run-all lint test:*",
23 | "test:ember": "ember test --test-port=0"
24 | },
25 | "dependencies": {
26 | "@glimmer/tracking": "^1.0.3",
27 | "ember-auto-import": "^1.10.1",
28 | "ember-cli-babel": "^7.23.0",
29 | "ember-cli-htmlbars": "^5.3.1",
30 | "ember-cli-typescript": "^3.1.4",
31 | "ember-statecharts": "0.13.2"
32 | },
33 | "devDependencies": {
34 | "@docfy/ember": "^0.4.3",
35 | "@docfy/plugin-with-prose": "^0.4.2",
36 | "@ember/optional-features": "^2.0.0",
37 | "@ember/render-modifiers": "^1.0.2",
38 | "@ember/test-helpers": "^2.1.4",
39 | "@glimmer/component": "^1.0.3",
40 | "@mapbox/rehype-prism": "^0.6.0",
41 | "@release-it/conventional-changelog": "^1.1.4",
42 | "@tailwindcss/aspect-ratio": "^0.2.0",
43 | "@tailwindcss/typography": "^0.4.0",
44 | "@types/ember": "^3.16.5",
45 | "@types/ember-qunit": "^3.4.9",
46 | "@types/ember__test-helpers": "^1.7.2",
47 | "@types/qunit": "^2.9.1",
48 | "@types/rsvp": "^4.0.3",
49 | "@typescript-eslint/eslint-plugin": "^4.0.1",
50 | "@typescript-eslint/parser": "^4.0.1",
51 | "autoprefixer": "^10.2.5",
52 | "babel-eslint": "^10.1.0",
53 | "broccoli-asset-rev": "^3.0.0",
54 | "ember-angle-bracket-invocation-polyfill": "^2.0.2",
55 | "ember-cli": "~3.24.0",
56 | "ember-cli-dependency-checker": "^3.2.0",
57 | "ember-cli-deploy": "^1.0.2",
58 | "ember-cli-deploy-build": "^2.0.0",
59 | "ember-cli-deploy-git": "^1.3.4",
60 | "ember-cli-fastboot": "^2.2.3",
61 | "ember-cli-inject-live-reload": "^2.0.2",
62 | "ember-cli-postcss": "^7.0.0",
63 | "ember-cli-sri": "^2.1.1",
64 | "ember-cli-terser": "^4.0.1",
65 | "ember-cli-typescript-blueprints": "^3.0.0",
66 | "ember-concurrency": "^1.2.1",
67 | "ember-concurrency-async": "^0.3.1",
68 | "ember-concurrency-decorators": "^2.0.1",
69 | "ember-concurrency-ts": "^0.2.0",
70 | "ember-disable-prototype-extensions": "^1.1.3",
71 | "ember-export-application-global": "^2.0.1",
72 | "ember-load-initializers": "^2.1.2",
73 | "ember-maybe-import-regenerator": "^0.1.6",
74 | "ember-metrics": "^0.16.0",
75 | "ember-metrics-matomo-adapter": "^0.1.0",
76 | "ember-modifier": "^2.1.1",
77 | "ember-page-title": "^6.2.1",
78 | "ember-qunit": "^5.1.1",
79 | "ember-qunit-assert-helpers": "^0.2.2",
80 | "ember-resolver": "^8.0.2",
81 | "ember-showcase": "^0.2.0",
82 | "ember-source": "~3.24.0",
83 | "ember-source-channel-url": "^3.0.0",
84 | "ember-svg-jar": "^2.3.3",
85 | "ember-template-lint": "^2.15.0",
86 | "ember-try": "^1.4.0",
87 | "eslint": "^7.17.0",
88 | "eslint-config-prettier": "^7.1.0",
89 | "eslint-plugin-ember": "^10.1.1",
90 | "eslint-plugin-node": "^11.1.0",
91 | "eslint-plugin-prettier": "^3.3.1",
92 | "fs-extra": "^9.1.0",
93 | "git-repo-info": "^2.1.1",
94 | "loader.js": "^4.7.0",
95 | "npm-run-all": "^4.1.5",
96 | "postcss-import": "^14.0.2",
97 | "prember": "^1.0.5",
98 | "prember-crawler": "^1.0.0",
99 | "prettier": "^2.2.1",
100 | "qunit": "^2.13.0",
101 | "qunit-dom": "^1.6.0",
102 | "release-it": "^13.6.2",
103 | "remark-autolink-headings": "^6.0.1",
104 | "tailwindcss": "^2.1.2",
105 | "typedoc": "^0.20.36",
106 | "typescript": "^4.0.2",
107 | "xstate": "^4.12.0"
108 | },
109 | "engines": {
110 | "node": "10.* || >= 12"
111 | },
112 | "ember": {
113 | "edition": "octane"
114 | },
115 | "ember-addon": {
116 | "paths": [
117 | "lib/deploy-versioned-doc"
118 | ]
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-statecharts",
3 | "version": "0.13.2",
4 | "description": "Statecharts for Ember.js applications",
5 | "keywords": [
6 | "ember-addon"
7 | ],
8 | "homepage": "https://ember-statecharts.com/",
9 | "repository": "https://github.com/LevelbossMike/ember-statecharts",
10 | "license": "MIT",
11 | "author": "",
12 | "types": "./index.d.ts",
13 | "directories": {
14 | "doc": "doc",
15 | "test": "tests"
16 | },
17 | "scripts": {
18 | "api-docs": "typedoc addon --out ../site/public/api-docs",
19 | "build": "ember build --environment=production",
20 | "changelog": "lerna-changelog",
21 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
22 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
23 | "lint:hbs": "ember-template-lint .",
24 | "lint:hbs:fix": "ember-template-lint . --fix",
25 | "lint:js": "eslint . --cache",
26 | "lint:js:fix": "eslint . --fix",
27 | "prepublishOnly": "ember ts:precompile",
28 | "postpublish": "ember ts:clean",
29 | "start": "ember serve",
30 | "test": "npm-run-all lint test:*",
31 | "test:ember": "ember test --test-port=0",
32 | "npm-publish": "release-it"
33 | },
34 | "dependencies": {
35 | "ember-cli-babel": "^7.26.6",
36 | "ember-cli-htmlbars": "^5.7.1",
37 | "ember-cli-typescript": "^3.1.4"
38 | },
39 | "peerDependencies": {
40 | "xstate": "^4.12.0"
41 | },
42 | "devDependencies": {
43 | "@ember/optional-features": "^2.0.0",
44 | "@ember/render-modifiers": "^1.0.2",
45 | "@ember/test-helpers": "^2.4.2",
46 | "@glimmer/component": "^1.0.4",
47 | "@glimmer/manager": "^0.83.1",
48 | "@glimmer/runtime": "^0.83.1",
49 | "@glimmer/tracking": "^1.0.4",
50 | "@glimmer/validator": "^0.83.1",
51 | "@mapbox/rehype-prism": "^0.6.0",
52 | "@release-it/conventional-changelog": "^1.1.4",
53 | "@types/ember": "^3.16.5",
54 | "@types/ember-qunit": "^3.4.9",
55 | "@types/ember__test-helpers": "^1.7.2",
56 | "@types/qunit": "^2.9.1",
57 | "@types/rsvp": "^4.0.3",
58 | "@typescript-eslint/eslint-plugin": "^4.0.1",
59 | "@typescript-eslint/parser": "^4.0.1",
60 | "autoprefixer": "^10.2.5",
61 | "babel-eslint": "^10.1.0",
62 | "broccoli-asset-rev": "^3.0.0",
63 | "ember-angle-bracket-invocation-polyfill": "^2.0.2",
64 | "ember-cli": "~3.28.4",
65 | "ember-auto-import": "^1.11.3",
66 | "ember-cli-dependency-checker": "^3.2.0",
67 | "ember-cli-inject-live-reload": "^2.1.0",
68 | "ember-cli-sri": "^2.1.1",
69 | "ember-cli-terser": "^4.0.2",
70 | "ember-cli-typescript-blueprints": "^3.0.0",
71 | "ember-concurrency": "^1.2.1",
72 | "ember-concurrency-async": "^0.3.1",
73 | "ember-concurrency-decorators": "^2.0.1",
74 | "ember-concurrency-ts": "^0.2.0",
75 | "ember-disable-prototype-extensions": "^1.1.3",
76 | "ember-export-application-global": "^2.0.1",
77 | "ember-load-initializers": "^2.1.2",
78 | "ember-maybe-import-regenerator": "^0.1.6",
79 | "ember-modifier": "^2.1.1",
80 | "ember-page-title": "^6.2.2",
81 | "ember-qunit": "^5.1.4",
82 | "ember-qunit-assert-helpers": "^0.2.2",
83 | "ember-resolver": "^8.0.2",
84 | "ember-source": "~3.28.0",
85 | "ember-template-lint": "^3.6.0",
86 | "eslint": "^7.32.0",
87 | "eslint-config-prettier": "^8.3.0",
88 | "eslint-plugin-ember": "^10.5.4",
89 | "eslint-plugin-node": "^11.1.0",
90 | "eslint-plugin-prettier": "^3.4.1",
91 | "eslint-plugin-qunit": "^6.2.0",
92 | "loader.js": "^4.7.0",
93 | "npm-run-all": "^4.1.5",
94 | "prettier": "^2.3.2",
95 | "qunit": "^2.16.0",
96 | "qunit-dom": "^1.6.0",
97 | "release-it": "^13.6.2",
98 | "typedoc": "^0.20.36",
99 | "typescript": "^4.0.2"
100 | },
101 | "engines": {
102 | "node": "12.* || 14.* || >= 16"
103 | },
104 | "changelog": {
105 | "repo": "levelbossmike/ember-statecharts",
106 | "labels": {
107 | ":boom: Breaking": ":boom: Breaking Change",
108 | "Feature / Enhancement": ":rocket: Feature / Enhancement",
109 | "Bug": ":bug: Bug Fix",
110 | "Documentation": ":memo: Documentation",
111 | "internal": ":house: Internal"
112 | }
113 | },
114 | "ember": {
115 | "edition": "octane"
116 | },
117 | "ember-addon": {
118 | "configPath": "tests/dummy/config"
119 | },
120 | "release-it": {
121 | "git": {
122 | "commitMessage": "chore: release v${version}"
123 | },
124 | "github": {
125 | "release": true
126 | },
127 | "plugins": {
128 | "@release-it/conventional-changelog": {
129 | "preset": "angular",
130 | "infile": "CHANGELOG.md"
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/-private/usables.d.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Interpreter,
3 | StateSchema,
4 | EventObject,
5 | Typestate,
6 | StateMachine,
7 | State,
8 | SingleOrArray,
9 | Event,
10 | EventData,
11 | InterpreterOptions,
12 | } from 'xstate';
13 | import { TemplateArgs, Resource } from './resource';
14 | export declare const ARGS_STATE_CHANGE_WARNING =
15 | 'A change to passed `args` or a local state change triggered an update to a `useMachine`-usable. You can send a dedicated event to the machine or restart it so this is handled. This is done via the `.update`-hook of the `useMachine`-usable.';
16 | interface StatechartArgs<
17 | TContext,
18 | TStateSchema extends StateSchema,
19 | TEvent extends EventObject,
20 | TTypestate extends Typestate = {
21 | value: any;
22 | context: TContext;
23 | }
24 | > extends TemplateArgs {
25 | named: {
26 | machine: StateMachine;
27 | update?(opts: {
28 | machine: StateMachine;
29 | restart(
30 | args: StatechartArgs
31 | ): void;
32 | send(
33 | event: SingleOrArray> | Event,
34 | payload?: EventData
35 | ): void;
36 | }): void;
37 | onTransition?(): void;
38 | interpreterOptions?: InterpreterOptions;
39 | initialState?: State;
40 | };
41 | }
42 | /**
43 | *
44 | * A [resource](https://www.pzuraq.com/introducing-use/) that starts
45 | * a [XState-interpreter](https://xstate.js.org/docs/guides/interpretation.html)
46 | * when accessed and will allow handling args-updates when args change after
47 | * the interpreter has started.
48 | *
49 | */
50 | export declare class Statechart<
51 | TContext,
52 | TStateSchema extends StateSchema,
53 | TEvent extends EventObject,
54 | TTypestate extends Typestate = {
55 | value: any;
56 | context: TContext;
57 | }
58 | > extends Resource> {
59 | service: Interpreter;
60 | state: State;
61 | services: Interpreter[];
62 | constructor(
63 | owner: Record,
64 | args: StatechartArgs
65 | );
66 | setup(): void;
67 | update(
68 | args: StatechartArgs
69 | ): void;
70 | teardown(): void;
71 | send(
72 | event: SingleOrArray> | Event,
73 | payload?: EventData
74 | ): void;
75 | _start(
76 | initialState?: State
77 | ): void;
78 | _interpretMachine(
79 | machine: StateMachine,
80 | opts?: InterpreterOptions
81 | ): Interpreter;
82 | _restart(
83 | machine: StateMachine,
84 | initialState?: State
85 | ): void;
86 | _stopOldService(): void;
87 | _rememberInterpretedService(
88 | service: Interpreter
89 | ): void;
90 | }
91 | /**
92 | * A function that can be used to create a {@link Statechart}-resource
93 | * that starts a [XState-interpreter](https://xstate.js.org/docs/guides/interpretation.html)
94 | * when accessed and makes the state of the interpreter accessible to the calling
95 | * context.
96 | *
97 | * ```js
98 | * export default class Button extends Component {
99 | * statechart = useMachine(this, () => {
100 | * return {
101 | * machine: toggleMachine
102 | * }
103 | * })
104 | *
105 | * @action toggleButton() {
106 | * this.statechart.send('TOGGLE');
107 | * }
108 | * }
109 | * ```
110 | */
111 | export declare function useMachine<
112 | TContext,
113 | TStateSchema extends StateSchema,
114 | TEvent extends EventObject,
115 | TTypestate extends Typestate = {
116 | value: any;
117 | context: TContext;
118 | }
119 | >(
120 | context: object,
121 | computeArgs: () => {
122 | machine: StateMachine;
123 | update?(opts: {
124 | machine: StateMachine;
125 | restart(
126 | args: StatechartArgs
127 | ): void;
128 | send(
129 | event: SingleOrArray> | Event,
130 | payload?: EventData
131 | ): void;
132 | }): void;
133 | onTransition?(): void;
134 | interpreterOptions?: InterpreterOptions;
135 | initialState?: State;
136 | }
137 | ): Statechart;
138 | export {};
139 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/unit/statechart-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import { module, test } from 'qunit';
3 | import { statechart } from 'ember-statecharts/computed';
4 | import { timeout } from 'ember-concurrency';
5 | import { run } from '@ember/runloop';
6 |
7 | module('Unit | computed | statechart', function () {
8 | test('it adds statechart functionality to an ember-object', async function (assert) {
9 | assert.expect(5);
10 |
11 | class TestClass extends EmberObject {
12 | @statechart(
13 | {
14 | initial: 'new',
15 | states: {
16 | new: {
17 | exit() {
18 | assert.ok(true, 'exitState was called');
19 | },
20 | on: {
21 | woot: {
22 | target: 'foo',
23 | actions: ['handleFoo'],
24 | },
25 | },
26 | },
27 | foo: {
28 | // eslint-disable-next-line no-unused-vars
29 | entry(_context, { type, ...data }) {
30 | assert.deepEqual(
31 | data,
32 | testData,
33 | 'passed data is available in eventObject'
34 | );
35 | },
36 | },
37 | },
38 | },
39 | {
40 | actions: {
41 | handleFoo() {
42 | assert.ok(true, 'event was called');
43 | },
44 | },
45 | }
46 | )
47 | statechart;
48 | }
49 | let subject = TestClass.create();
50 |
51 | const testData = { wat: 'lol' };
52 |
53 | assert.equal(subject.statechart.currentState.value, 'new');
54 |
55 | subject.statechart.send('woot', testData);
56 |
57 | assert.equal(subject.statechart.currentState.value, 'foo');
58 | });
59 |
60 | test('it is possible to pass statechart-options to the statechart when passing an array of params', async function (assert) {
61 | assert.expect(5);
62 |
63 | class TestClass extends EmberObject {
64 | name = 'Tomster';
65 |
66 | power = null;
67 |
68 | @statechart(
69 | {
70 | initial: 'powerOff',
71 | states: {
72 | powerOff: {
73 | on: {
74 | power: {
75 | target: 'powerOn',
76 | cond: 'enoughPowerIsAvailable',
77 | },
78 | },
79 | },
80 | powerOn: {},
81 | },
82 | },
83 | {
84 | guards: {
85 | enoughPowerIsAvailable: (context, { power }) => {
86 | assert.equal(context.name, 'Tomster', 'accessing context works');
87 |
88 | return power > 9000;
89 | },
90 | },
91 | }
92 | )
93 | statechart;
94 | }
95 |
96 | let subject = TestClass.create();
97 |
98 | assert.equal(
99 | subject.statechart.currentState.value,
100 | 'powerOff',
101 | 'passing an array as a statechart property works'
102 | );
103 |
104 | await subject.get('statechart').send('power', { power: 1 });
105 |
106 | assert.equal(
107 | subject.statechart.currentState.value,
108 | 'powerOff',
109 | 'guards will not execute transition when a falsy value is returned'
110 | );
111 |
112 | await subject.get('statechart').send('power', { power: 9001 });
113 |
114 | assert.equal(
115 | subject.statechart.currentState.value,
116 | 'powerOn',
117 | 'returning a truthy from a guard executes the transition'
118 | );
119 | });
120 |
121 | test('statechart services will be cleaned properly when the object containing the statechart is destroyed', async function (assert) {
122 | class TestClass extends EmberObject {
123 | offCounter = 0;
124 |
125 | @statechart(
126 | {
127 | initial: 'powerOff',
128 | states: {
129 | powerOff: {
130 | entry: ['incrementOffCounter'],
131 | on: {
132 | POWER: 'powerOn',
133 | },
134 | },
135 | powerOn: {
136 | after: {
137 | 1000: 'powerOff',
138 | },
139 | },
140 | },
141 | },
142 | {
143 | actions: {
144 | incrementOffCounter(context) {
145 | context.incrementProperty('offCounter');
146 | },
147 | },
148 | }
149 | )
150 | statechart;
151 |
152 | willDestroy() {
153 | super.willDestroy(...arguments);
154 |
155 | assert.step('willDestroy hook is called correctly');
156 | }
157 | }
158 |
159 | let subject = TestClass.create();
160 |
161 | assert.equal(subject.statechart.currentState.value, 'powerOff');
162 | assert.equal(
163 | subject.offCounter,
164 | 1,
165 | 'offCounter was incremented as expected'
166 | );
167 |
168 | subject.statechart.send('POWER');
169 |
170 | await timeout(300);
171 |
172 | assert.equal(subject.statechart.currentState.value, 'powerOn');
173 |
174 | // will fail with `calling set on destroyed object` if this doesn't work
175 | run(() => subject.destroy());
176 |
177 | assert.verifySteps(['willDestroy hook is called correctly']);
178 | });
179 | });
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-statecharts [](https://github.com/LevelbossMike/ember-statecharts/actions/workflows/ci.yml) [](https://emberobserver.com/addons/ember-statecharts)
2 |
3 | This addon provides a statechart abstraction based on [XState](https://xstate.js.org/)
4 | for adding statecharts to your Ember.js application. Statecharts can be used to describe
5 | complex behaviour of your objects and separate ui-concern from behavioral concerns
6 | in your applications. This is especially useful in `Ember.Component`-architecture
7 | but can be used across all layers of your application (e.g. when implementing
8 | global application state).
9 |
10 | [View the docs here.](https://ember-statecharts.com)
11 |
12 | ## Compatibility
13 |
14 | - Ember.js v3.24 or above
15 | - Ember CLI v3.20 or above
16 | - Node.js v12 or above
17 |
18 | For classic Ember.js-versions pre Ember Octane please use the `0.8.x`-version
19 | of this addon.
20 |
21 | For Ember.js versions `< 3.24` please use the `0.13.x`-version of this addon.
22 |
23 | ## Installation
24 |
25 | ```
26 | ember install ember-statecharts
27 | ```
28 |
29 | Because ember-statecharts works with [XState](https://xstate.js.org) internally
30 | you have to install it as a dependency as well.
31 |
32 | ```
33 | yarn add --dev xstate
34 | ```
35 |
36 | or
37 |
38 | ```
39 | npm install --save-dev xstate
40 | ```
41 |
42 | ## Usage
43 |
44 | Statecharts have been around for a long time and have been used to model
45 | stateful, reactive system successfully. You can read about statecharts in the
46 | original paper [Statecharts - A Visual Formalism for Complex
47 | Systems](http://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf)
48 | by David Harel.
49 |
50 | With statecharts we finally have a good abstraction to model and discuss behaviour with
51 | other stakeholders of our applications in addition to a design language that
52 | visualizes this behaviour. Here's an example of a button component:
53 |
54 |
55 |
56 |
57 |
58 | In addition to their modeling capabilities Statecharts are executable and can be used to drive user experience behavior in your Ember.js applications:
59 |
60 | ```js
61 | import Component from '@glimmer/component';
62 | import { action } from '@ember/object';
63 | import { task } from 'ember-concurrency';
64 |
65 | import { matchesState, useMachine } from 'ember-statecharts';
66 | import { Machine } from 'xstate';
67 |
68 | function noop() {}
69 |
70 | const buttonMachine = Machine(
71 | {
72 | initial: 'idle',
73 | states: {
74 | idle: {
75 | on: {
76 | SUBMIT: 'busy',
77 | },
78 | },
79 | busy: {
80 | entry: ['handleSubmit'],
81 | on: {
82 | SUCCESS: 'success',
83 | ERROR: 'error',
84 | },
85 | },
86 | success: {
87 | entry: ['handleSuccess'],
88 | on: {
89 | SUBMIT: 'busy',
90 | },
91 | },
92 | error: {
93 | entry: ['handleError'],
94 | on: {
95 | SUBMIT: 'busy',
96 | },
97 | },
98 | },
99 | },
100 | {
101 | actions: {
102 | handleSubmit() {},
103 | handleSuccess() {},
104 | handleError() {},
105 | },
106 | }
107 | );
108 |
109 | export default class QuickstartButton extends Component {
110 | get onClick() {
111 | return this.args.onClick || noop;
112 | }
113 |
114 | statechart = useMachine(this, () => {
115 | const { performSubmitTask, onSuccess, onError } = this;
116 |
117 | return {
118 | machine: quickstartButtonMachine.withConfig({
119 | actions: {
120 | handleSubmit: performSubmitTask,
121 | handleSuccess: onSuccess,
122 | handleError: onError,
123 | },
124 | }),
125 | };
126 | });
127 |
128 | @matchesState('busy')
129 | isBusy;
130 |
131 | get isDisabled() {
132 | return this.isBusy || this.args.disabled;
133 | }
134 |
135 | @task(function* () {
136 | try {
137 | const result = yield this.onClick();
138 | this.statechart.send('SUCCESS', { result });
139 | } catch (e) {
140 | this.statechart.send('ERROR', { error: e });
141 | }
142 | })
143 | handleSubmitTask;
144 |
145 | @action
146 | handleClick() {
147 | this.statechart.send('SUBMIT');
148 | }
149 |
150 | @action
151 | onSuccess(_context, { result }) {
152 | return (this.args.onSuccess && this.args.onSuccess(result)) || noop();
153 | }
154 |
155 | @action
156 | onError(_context, { error }) {
157 | return (this.args.onError && this.args.onError(error)) || noop();
158 | }
159 |
160 | @action
161 | performSubmitTask() {
162 | this.handleSubmitTask.perform();
163 | }
164 | }
165 | ```
166 |
167 | Please refer to the [documentation page](http://ember-statecharts.com) for a detailed guide of how you can use statecharts to improve your Ember.js application architecture.
168 |
169 | ## Contributing
170 |
171 | See the [Contributing](CONTRIBUTING.md) guide for details.
172 |
173 | ## License
174 |
175 | This project has been developed by https://www.effective-ember.com/ and [contributors](https://github.com/LevelbossMike/ember-statecharts/graphs/contributors). It is licensed under the [MIT License](LICENSE.md).
176 |
--------------------------------------------------------------------------------
/packages/site/public/statecharts.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/addon/-private/usables.ts:
--------------------------------------------------------------------------------
1 | import { useResource } from './use-resource';
2 | import { tracked } from '@glimmer/tracking';
3 | import { interpret } from 'xstate';
4 | import type {
5 | Interpreter,
6 | StateSchema,
7 | EventObject,
8 | Typestate,
9 | StateMachine,
10 | State,
11 | SingleOrArray,
12 | Event,
13 | EventData,
14 | InterpreterOptions,
15 | } from 'xstate';
16 | import { TemplateArgs, Resource } from './resource';
17 | import { warn } from '@ember/debug';
18 | import { next } from '@ember/runloop';
19 |
20 | export const ARGS_STATE_CHANGE_WARNING =
21 | 'A change to passed `args` or a local state change triggered an update to a `useMachine`-usable. You can send a dedicated event to the machine or restart it so this is handled. This is done via the `.update`-hook of the `useMachine`-usable.';
22 |
23 | interface StatechartArgs<
24 | TContext,
25 | TStateSchema extends StateSchema,
26 | TEvent extends EventObject,
27 | TTypestate extends Typestate = { value: any; context: TContext }
28 | > extends TemplateArgs {
29 | named: {
30 | machine: StateMachine;
31 | update?(opts: {
32 | machine: StateMachine;
33 | restart(
34 | args: StatechartArgs
35 | ): void;
36 | send(
37 | event: SingleOrArray> | Event,
38 | payload?: EventData
39 | ): void;
40 | }): void;
41 | onTransition?(): void;
42 | interpreterOptions?: InterpreterOptions;
43 | initialState?: State;
44 | };
45 | }
46 |
47 | /**
48 | *
49 | * A [resource](https://www.pzuraq.com/introducing-use/) that starts
50 | * a [XState-interpreter](https://xstate.js.org/docs/guides/interpretation.html)
51 | * when accessed and will allow handling args-updates when args change after
52 | * the interpreter has started.
53 | *
54 | */
55 | export class Statechart<
56 | TContext,
57 | TStateSchema extends StateSchema,
58 | TEvent extends EventObject,
59 | TTypestate extends Typestate = { value: any; context: TContext }
60 | > extends Resource> {
61 | @tracked service: Interpreter;
62 | @tracked state: State;
63 | @tracked services: Interpreter[] =
64 | [];
65 |
66 | constructor(
67 | owner: Record,
68 | args: StatechartArgs
69 | ) {
70 | super(owner, args);
71 |
72 | const machine = args.named.machine;
73 |
74 | const service = this._interpretMachine(
75 | machine,
76 | args.named.interpreterOptions
77 | );
78 |
79 | this.service = service;
80 | this.state = machine.initialState;
81 | }
82 |
83 | setup() {
84 | const { initialState } = this.args.named;
85 |
86 | this._start(initialState);
87 | }
88 |
89 | update(args: StatechartArgs) {
90 | const { named } = args;
91 |
92 | const { update, machine } = named;
93 | const { _restart, send } = this;
94 |
95 | if (update) {
96 | update({
97 | machine,
98 | send: send.bind(this),
99 | restart: _restart.bind(this, machine),
100 | });
101 | } else {
102 | warn(ARGS_STATE_CHANGE_WARNING, false, {
103 | id: 'statecharts.use-machine.args-state-change',
104 | });
105 | }
106 | }
107 |
108 | teardown() {
109 | this.service.stop();
110 | }
111 |
112 | send(
113 | event: SingleOrArray> | Event,
114 | payload?: EventData
115 | ) {
116 | this.service.send(event, payload);
117 | }
118 |
119 | _start(initialState?: State) {
120 | this.service.start(initialState).onTransition((state) => {
121 | this.state = state;
122 | });
123 |
124 | if (this.args.named.onTransition) {
125 | this.service.onTransition(this.args.named.onTransition);
126 | }
127 | }
128 |
129 | _interpretMachine(
130 | machine: StateMachine,
131 | opts?: InterpreterOptions
132 | ) {
133 | const service = interpret(machine, opts);
134 |
135 | return service;
136 | }
137 |
138 | _restart(
139 | machine: StateMachine,
140 | initialState?: State
141 | ) {
142 | const newService = this._interpretMachine(machine);
143 |
144 | this.service = newService;
145 |
146 | this._start(initialState || this.args.named.initialState);
147 |
148 | this._stopOldService();
149 | }
150 |
151 | _stopOldService() {
152 | const oldService = this.services[this.services.length - 2];
153 |
154 | if (oldService) {
155 | oldService.stop();
156 | }
157 | }
158 |
159 | _rememberInterpretedService(
160 | service: Interpreter
161 | ) {
162 | next(this, () => {
163 | this.services = [...this.services, service];
164 | });
165 | }
166 | }
167 |
168 | /**
169 | * A function that can be used to create a {@link Statechart}-resource
170 | * that starts a [XState-interpreter](https://xstate.js.org/docs/guides/interpretation.html)
171 | * when accessed and makes the state of the interpreter accessible to the calling
172 | * context.
173 | *
174 | * ```js
175 | * export default class Button extends Component {
176 | * statechart = useMachine(this, () => {
177 | * return {
178 | * machine: toggleMachine
179 | * }
180 | * })
181 | *
182 | * @action toggleButton() {
183 | * this.statechart.send('TOGGLE');
184 | * }
185 | * }
186 | * ```
187 | */
188 | export function useMachine<
189 | TContext,
190 | TStateSchema extends StateSchema,
191 | TEvent extends EventObject,
192 | TTypestate extends Typestate = { value: any; context: TContext }
193 | >(
194 | context: object,
195 | computeArgs: () => {
196 | machine: StateMachine;
197 | update?(opts: {
198 | machine: StateMachine;
199 | restart(
200 | args: StatechartArgs
201 | ): void;
202 | send(
203 | event: SingleOrArray> | Event,
204 | payload?: EventData
205 | ): void;
206 | }): void;
207 | onTransition?(): void;
208 | interpreterOptions?: InterpreterOptions;
209 | initialState?: State;
210 | }
211 | ): Statechart {
212 | return useResource(context, Statechart, () => {
213 | return {
214 | named: computeArgs(),
215 | };
216 | });
217 | }
218 |
--------------------------------------------------------------------------------
/packages/ember-statecharts/tests/unit/computed-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import { module, test } from 'qunit';
3 | import {
4 | statechart,
5 | matchesState,
6 | debugState,
7 | } from 'ember-statecharts/computed';
8 |
9 | module('Unit | statechart computeds', function (hooks) {
10 | hooks.beforeEach(function () {
11 | class TestClass extends EmberObject {
12 | @statechart({
13 | initial: 'playerOff',
14 | states: {
15 | playerOff: {
16 | on: {
17 | power: 'playerOn',
18 | },
19 | },
20 | playerOn: {
21 | initial: 'stopped',
22 | on: {
23 | power: 'playerOff',
24 | },
25 | states: {
26 | stopped: {
27 | on: {
28 | play: 'playing',
29 | },
30 | },
31 | playing: {
32 | on: {
33 | stop: 'stopped',
34 | pause: 'paused',
35 | },
36 | },
37 | paused: {
38 | on: {
39 | play: 'playing',
40 | stop: 'stopped',
41 | },
42 | },
43 | },
44 | },
45 | },
46 | })
47 | statechart;
48 |
49 | @statechart({
50 | initial: 'off',
51 |
52 | states: {
53 | off: {
54 | on: {
55 | POWER: 'on',
56 | },
57 | },
58 | on: {
59 | on: {
60 | POWER: 'off',
61 | },
62 | initial: 'paused',
63 | states: {
64 | paused: {
65 | on: {
66 | START: 'started',
67 | },
68 | },
69 | started: {
70 | on: {
71 | PAUSE: 'paused',
72 | },
73 | },
74 | },
75 | },
76 | },
77 | })
78 | secondStatechart;
79 |
80 | @matchesState('playerOff')
81 | playerIsOff;
82 | @matchesState('playerOn')
83 | playerIsOn;
84 |
85 | @matchesState({
86 | playerOn: 'stopped',
87 | })
88 | playerIsStopped;
89 |
90 | @matchesState({
91 | playerOn: 'playing',
92 | })
93 | playerIsPlaying;
94 |
95 | @matchesState({
96 | playerOn: 'paused',
97 | })
98 | playerIsPaused;
99 |
100 | @matchesState([
101 | {
102 | playerOn: 'stopped',
103 | },
104 | {
105 | playerOn: 'paused',
106 | },
107 | ])
108 | playerActiveMusicNotPlaying;
109 |
110 | @matchesState('off', 'secondStatechart')
111 | secondIsOff;
112 |
113 | @matchesState('on', 'secondStatechart')
114 | secondIsOn;
115 |
116 | @matchesState({ on: 'started' }, 'secondStatechart')
117 | secondIsStarted;
118 |
119 | @debugState()
120 | _debug;
121 |
122 | @debugState('secondStatechart')
123 | _debugSecond;
124 | }
125 |
126 | this.subject = TestClass.create();
127 | });
128 |
129 | module('#matchesState', function () {
130 | test('can be used to match against the current state of the statechart', async function (assert) {
131 | let { subject } = this;
132 |
133 | assert.true(subject.playerIsOff, 'works for initial states');
134 |
135 | await subject.get('statechart').send('power');
136 |
137 | assert.true(subject.playerIsOn, 'works after updating state');
138 |
139 | assert.true(subject.playerIsStopped, 'works for nested states');
140 |
141 | await subject.get('statechart').send('play');
142 |
143 | assert.false(
144 | subject.playerIsStopped,
145 | 'works inside of nested states - stopped false - playing again'
146 | );
147 | assert.true(
148 | subject.playerIsPlaying,
149 | 'works inside of nested states - playing true - playing'
150 | );
151 |
152 | await subject.get('statechart').send('pause');
153 |
154 | assert.false(
155 | subject.playerIsStopped,
156 | 'works inside of nested states - stopped false - paused'
157 | );
158 | assert.false(
159 | subject.playerIsPlaying,
160 | 'works inside of nested states - playing false - paused'
161 | );
162 | assert.true(
163 | subject.playerIsPaused,
164 | 'works inside of nested states - paused true - paused'
165 | );
166 |
167 | assert.true(
168 | subject.playerActiveMusicNotPlaying,
169 | 'works when passing array'
170 | );
171 | });
172 |
173 | test('it can be used with other computeds not named `statechart`', async function (assert) {
174 | let { subject } = this;
175 |
176 | assert.true(
177 | subject.get('secondIsOff'),
178 | 'work for initial state of second statechart'
179 | );
180 |
181 | await subject.get('secondStatechart').send('POWER');
182 |
183 | assert.false(
184 | subject.get('secondIsOff'),
185 | 'matchesState updates with custom named statecharts'
186 | );
187 |
188 | assert.true(
189 | subject.get('secondIsOn'),
190 | 'matchesState computeds work as expected for second statechart'
191 | );
192 |
193 | await subject.get('secondStatechart').send('START');
194 |
195 | assert.true(
196 | subject.get('secondIsStarted'),
197 | 'matchesState computeds work as expected for nested states for second statechart'
198 | );
199 | });
200 | });
201 |
202 | module('#debugState', function () {
203 | test('can be used to log the current state of the statechart as a string', async function (assert) {
204 | let { subject } = this;
205 |
206 | assert.deepEqual(subject.get('_debug'), '"playerOff"');
207 |
208 | await subject.get('statechart').send('power');
209 |
210 | assert.deepEqual(
211 | subject.get('_debug'),
212 | JSON.stringify({ playerOn: 'stopped' }, 'works for nested states')
213 | );
214 |
215 | await subject.get('statechart').send('play');
216 |
217 | assert.deepEqual(
218 | subject.get('_debug'),
219 | JSON.stringify({ playerOn: 'playing' }, 'updates when state is updated')
220 | );
221 | });
222 |
223 | test('can be used to log the current state of the statechart as a string - custom name', async function (assert) {
224 | let { subject } = this;
225 |
226 | assert.deepEqual(subject.get('_debugSecond'), '"off"');
227 |
228 | await subject.get('secondStatechart').send('POWER');
229 |
230 | assert.deepEqual(
231 | subject.get('_debugSecond'),
232 | JSON.stringify({ on: 'paused' }, 'works for nested states')
233 | );
234 |
235 | await subject.get('secondStatechart').send('START');
236 |
237 | assert.deepEqual(
238 | subject.get('_debugSecond'),
239 | JSON.stringify({ on: 'started' }, 'updates when state is updated')
240 | );
241 | });
242 | });
243 | });
244 |
--------------------------------------------------------------------------------