Skip to content

Commit

Permalink
feat: basic reactivity implemented
Browse files Browse the repository at this point in the history
BREAKING CHANGE: first release
  • Loading branch information
NullVoxPopuli committed Apr 23, 2021
1 parent 60b7f99 commit 1bc4e28
Show file tree
Hide file tree
Showing 17 changed files with 28,189 additions and 16,321 deletions.
55 changes: 3 additions & 52 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,5 @@
'use strict';

module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true,
},
},
plugins: ['ember'],
extends: [
'eslint:recommended',
'plugin:ember/recommended',
'plugin:prettier/recommended',
],
env: {
browser: true,
},
rules: {},
overrides: [
// node files
{
files: [
'.eslintrc.js',
'.prettierrc.js',
'.template-lintrc.js',
'ember-cli-build.js',
'index.js',
'testem.js',
'blueprints/*/index.js',
'config/**/*.js',
'tests/dummy/config/**/*.js',
],
excludedFiles: [
'addon/**',
'addon-test-support/**',
'app/**',
'tests/dummy/app/**',
],
parserOptions: {
sourceType: 'script',
},
env: {
browser: false,
node: true,
},
plugins: ['node'],
extends: ['plugin:node/recommended'],
},
],
};
const { configs } = require('@nullvoxpopuli/eslint-configs');

module.exports = configs.ember();
21 changes: 0 additions & 21 deletions .prettierignore

This file was deleted.

5 changes: 0 additions & 5 deletions .prettierrc.js

This file was deleted.

61 changes: 0 additions & 61 deletions .travis.yml

This file was deleted.

5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ ember-statechart-component
Compatibility
------------------------------------------------------------------------------

* Ember.js v3.16 or above
* Ember CLI v2.13 or above
* Node.js v10 or above
* Ember.js v3.25 or above
* Node.js v12 or above


Installation
Expand Down
38 changes: 38 additions & 0 deletions addon/-private/proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { assert } from '@ember/debug';
import { get as consumeTag, notifyPropertyChange as dirtyKey } from '@ember/object';

import { tracked, TrackedWeakMap } from 'tracked-built-ins';

import type { Interpreter } from 'xstate';

const CACHE = new TrackedWeakMap<Interpreter<unknown>, Record<string, unknown>>();

export function reactiveInterpreter(interpreter: Interpreter<unknown>) {
CACHE.set(interpreter, tracked({}));

interpreter.onTransition(() => {
dirtyState(interpreter);
});

return new Proxy(interpreter, {
get(target, key, receiver) {
if (key === '_state') {
let tracking = CACHE.get(target);

assert(`Tracking context lost!`, tracking);

consumeTag(tracking, key);
}

return Reflect.get(target, key, receiver);
},
});
}

function dirtyState(interpreter: Interpreter<unknown>) {
let tracking = CACHE.get(interpreter);

assert(`Tracking context lost!`, tracking);

dirtyKey(tracking, '_state');
}
83 changes: 83 additions & 0 deletions addon/-private/statechart-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
// TODO: remove all the any types -- xstate requires too many generics
import { DEBUG } from '@glimmer/env';
import { getOwner, setOwner } from '@ember/application';
import { capabilities } from '@ember/component';
import { destroy, isDestroying } from '@ember/destroyable';
import { cancel, later } from '@ember/runloop';

import { interpret } from 'xstate';

import { dirtyState, reactiveInterpreter } from './proxy';

import type { Interpreter, StateNode } from 'xstate';

export interface Args {
named: Record<string, unknown>;
positional: unknown[];
}

export default class ComponentManager {
capabilities = capabilities('3.13', {
destructor: true,
updateHook: true,
});

static create(attrs: unknown) {
let owner = getOwner(attrs);
let manager = new ComponentManager();

setOwner(manager, owner);

return manager;
}

createComponent(machine: StateNode, args: Args) {
let { named } = args;

if ('config' in named) {
machine = machine.withConfig(named.config as any);
}

if ('context' in named) {
machine = machine.withContext(named.context);
}

let interpreter = interpret(machine, {
devTools: DEBUG,
clock: {
setTimeout(fn, ms) {
return later.call(null, fn, ms);
},
clearTimeout(timer) {
return cancel.call(null, timer);
},
},
});

let withReactivity = reactiveInterpreter(interpreter);

withReactivity.start();

return withReactivity;
}

updateComponent(interpreter: Interpreter<any>, args: Args) {
interpreter.send('ARGS_UPDATE', args.named);
}

destroyComponent(interpreter: Interpreter<any>) {
if (isDestroying(interpreter)) {
return;
}

interpreter.stop();

destroy(interpreter);
}

getContext(interpreter: Interpreter<any>) {
return interpreter;
}
}
Empty file removed addon/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions addon/instance-initializers/ember-statechart-component-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setComponentManager } from '@ember/component';

import ComponentManager from 'ember-statechart-component/-private/statechart-manager';
import { StateNode } from 'xstate';

import type ApplicationInstance from '@ember/application/instance';

export function initialize(): void {
setComponentManager(
(owner: ApplicationInstance) => {
return ComponentManager.create(owner);
} /* hmm, it seems XState creates a totally new object here, so we have no prototype or superclass to checkt */,
StateNode.prototype
);
}

export default {
initialize,
};
Empty file removed app/.gitkeep
Empty file.
4 changes: 4 additions & 0 deletions app/instance-initializers/ember-statechart-component-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
default,
initialize,
} from 'ember-statechart-component/instance-initializers/ember-statechart-component-setup';
Loading

0 comments on commit 1bc4e28

Please sign in to comment.