@blockquote-web-components/blockquote-controller-xstate 1.1.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,11 @@
2
2
 
3
3
  ![Lit](https://img.shields.io/badge/lit-3.0.0-blue.svg)
4
4
 
5
- ### Connect XState machines with Lit's reactive property
6
- The `BlockquoteControllerXstate` is a Lit Reactive Controller specifically designed for straightforward integration with XState.
7
- This controller allows you to subscribe to an XState actor, updating a specified reactive property whenever the state machine transitions.
5
+ ### Connect XState machines with Lit
6
+ The BlockquoteControllerXstate is a Lit Reactive Controller that is specifically designed to facilitate a integration with XState. This controller provides the capability to subscribe to an XState actor. It also provides a callback function to handle the state changes.
8
7
 
9
8
  - [xstate v5](https://stately.ai/docs/installation)
10
9
  - [xstate v5 - examples](https://stately.ai/docs/examples)
11
- - [Original idea](https://codesandbox.io/s/z3o0s?file=/src/toggleMachine.ts)
12
10
 
13
11
  <hr>
14
12
 
@@ -93,12 +91,15 @@ export const counterMachine = createMachine(
93
91
  );
94
92
  ```
95
93
 
96
- ***xstate-counter.js***
94
+ **`new BlockquoteControllerXstate(this, {machine, options?, callback?})`**
95
+
96
+ ***Usage***
97
97
 
98
98
  ```javascript
99
99
  import { html, LitElement } from 'lit';
100
- import { BlockquoteControllerXstate } from '@blockquote-web-components/blockquote-controller-xstate';
100
+ import { BlockquoteControllerXstate } from '../index.js';
101
101
  import { counterMachine } from './counterMachine.js';
102
+ import { styles } from './styles/xstate-counter-styles.css.js';
102
103
 
103
104
  export class XstateCounter extends LitElement {
104
105
  static properties = {
@@ -108,10 +109,31 @@ export class XstateCounter extends LitElement {
108
109
  },
109
110
  };
110
111
 
112
+ static styles = [styles];
113
+
111
114
  constructor() {
112
115
  super();
113
116
  this._xstate = {};
114
- this.counterController = new BlockquoteControllerXstate(this, counterMachine, '_xstate');
117
+ this._inspectEvents = this._inspectEvents.bind(this);
118
+ this._callbackCounterController = this._callbackCounterController.bind(this);
119
+
120
+ this.counterController = new BlockquoteControllerXstate(this, {
121
+ machine: counterMachine,
122
+ options: {
123
+ inspect: this._inspectEvents,
124
+ },
125
+ callback: this._callbackCounterController,
126
+ });
127
+ }
128
+
129
+ _callbackCounterController(snapshot) {
130
+ this._xstate = snapshot;
131
+ }
132
+
133
+ _inspectEvents(inspEvent) {
134
+ if (inspEvent.type === '@xstate.snapshot' && inspEvent.event.type === 'xstate.stop') {
135
+ this._xstate = {};
136
+ }
115
137
  }
116
138
 
117
139
  updated(props) {
@@ -126,32 +148,39 @@ export class XstateCounter extends LitElement {
126
148
  }
127
149
  }
128
150
 
129
- // ...
130
-
131
151
  get #disabled() {
132
- return this.counterController.state.matches('disabled');
152
+ return this.counterController.snapshot.matches('disabled');
133
153
  }
134
154
 
135
155
  render() {
136
156
  return html`
137
- <button
138
- ?disabled="${this.#disabled}"
139
- data-counter="increment"
140
- \@click=${() => this.counterController.send({ type: 'INC' })}
141
- >
142
- Increment
143
- </button>
144
- <button
145
- ?disabled="${this.#disabled}"
146
- data-counter="decrement"
147
- \@click=${() => this.counterController.send({ type: 'DEC' })}
148
- >
149
- Decrement
150
- </button>
157
+ <slot></slot>
158
+ <div aria-disabled="${this.#disabled}">
159
+ <span>
160
+ <button
161
+ ?disabled="${this.#disabled}"
162
+ data-counter="increment"
163
+ \@click=${() => this.counterController.send({ type: 'INC' })}
164
+ >
165
+ Increment
166
+ </button>
167
+ <button
168
+ ?disabled="${this.#disabled}"
169
+ data-counter="decrement"
170
+ \@click=${() => this.counterController.send({ type: 'DEC' })}
171
+ >
172
+ Decrement
173
+ </button>
174
+ </span>
175
+ <p>${this.counterController.snapshot.context.counter}</p>
176
+ </div>
177
+ <div>
178
+ <button \@click=${() => this.counterController.send({ type: 'TOGGLE' })}>
179
+ ${this.#disabled ? 'Enabled counter' : 'Disabled counter'}
180
+ </button>
181
+ </div>
151
182
  `;
152
183
  }
153
-
154
- // ...
155
184
  }
156
185
  ```
157
186
  <hr>
@@ -159,31 +188,38 @@ export class XstateCounter extends LitElement {
159
188
 
160
189
  ### `src/BlockquoteControllerXstate.js`:
161
190
 
162
- #### class: `BlockquoteControllerXstate`
191
+ #### class: `UseMachine`
163
192
 
164
193
  ##### Fields
165
194
 
166
- | Name | Privacy | Type | Default | Description | Inherited From |
167
- | --------- | ------- | ---- | --------- | ----------- | -------------- |
168
- | `state` | | | | | |
169
- | `service` | | | | | |
170
- | `propKey` | | | `propKey` | | |
195
+ | Name | Privacy | Type | Default | Description | Inherited From |
196
+ | ----------------- | ------- | ---- | ---------- | ----------- | -------------- |
197
+ | `actor` | | | | | |
198
+ | `snapshot` | | | | | |
199
+ | `machine` | | | `machine` | | |
200
+ | `options` | | | `options` | | |
201
+ | `callback` | | | `callback` | | |
202
+ | `currentSnapshot` | | | | | |
171
203
 
172
204
  ##### Methods
173
205
 
174
- | Name | Privacy | Description | Parameters | Return | Inherited From |
175
- | ------------------ | ------- | ----------- | ----------------- | ------ | -------------- |
176
- | `send` | | | `ev: EventObject` | | |
177
- | `hostConnected` | | | | | |
178
- | `hostDisconnected` | | | | | |
206
+ | Name | Privacy | Description | Parameters | Return | Inherited From |
207
+ | ------------------ | ------- | ----------- | --------------------------------------------- | ------ | -------------- |
208
+ | `send` | | | `ev: EventFrom<typeof this.machine>` | | |
209
+ | `unsubscribe` | | | | | |
210
+ | `onNext` | | | `snapshot: SnapshotFrom<typeof this.machine>` | | |
211
+ | `startService` | | | | | |
212
+ | `stopService` | | | | | |
213
+ | `hostConnected` | | | | | |
214
+ | `hostDisconnected` | | | | | |
179
215
 
180
216
  <hr/>
181
217
 
182
218
  #### Exports
183
219
 
184
- | Kind | Name | Declaration | Module | Package |
185
- | ---- | ---------------------------- | -------------------------- | --------------------------------- | ------- |
186
- | `js` | `BlockquoteControllerXstate` | BlockquoteControllerXstate | src/BlockquoteControllerXstate.js | |
220
+ | Kind | Name | Declaration | Module | Package |
221
+ | ---- | ---------------------------- | ----------- | --------------------------------- | ------- |
222
+ | `js` | `BlockquoteControllerXstate` | UseMachine | src/BlockquoteControllerXstate.js | |
187
223
 
188
224
  ### `index.js`:
189
225
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockquote-web-components/blockquote-controller-xstate",
3
- "version": "1.1.5",
3
+ "version": "2.0.0",
4
4
  "description": "This controller allows you to subscribe to an XState actor, updating a specified reactive property whenever the state machine transitions.",
5
5
  "keywords": [
6
6
  "lit",
@@ -132,16 +132,16 @@
132
132
  }
133
133
  },
134
134
  "dependencies": {
135
- "lit": "^3.1.1",
136
- "xstate": "^5.5.2"
135
+ "lit": "^3.1.2",
136
+ "xstate": "^5.9.1"
137
137
  },
138
138
  "devDependencies": {
139
- "@blockquote-web-components/blockquote-base-common-dev-dependencies": "^1.7.3",
140
- "@blockquote-web-components/blockquote-base-embedded-webview": "^1.7.0"
139
+ "@blockquote-web-components/blockquote-base-common-dev-dependencies": "^1.8.0",
140
+ "@blockquote-web-components/blockquote-base-embedded-webview": "^1.9.0"
141
141
  },
142
142
  "publishConfig": {
143
143
  "access": "public"
144
144
  },
145
145
  "customElements": "custom-elements.json",
146
- "gitHead": "d9bc07c424437dd78ecace41ec5465ebda063ab8"
146
+ "gitHead": "c19026ccc69693273fe4b13b4786672ee6c7172a"
147
147
  }
@@ -1,17 +1,14 @@
1
1
  import { createActor } from 'xstate';
2
-
3
2
  /**
4
3
  * # BlockquoteControllerXstate
5
4
  *
6
5
  * ![Lit](https://img.shields.io/badge/lit-3.0.0-blue.svg)
7
6
  *
8
- * ### Connect XState machines with Lit's reactive property
9
- * The `BlockquoteControllerXstate` is a Lit Reactive Controller specifically designed for straightforward integration with XState.
10
- * This controller allows you to subscribe to an XState actor, updating a specified reactive property whenever the state machine transitions.
7
+ * ### Connect XState machines with Lit
8
+ * The BlockquoteControllerXstate is a Lit Reactive Controller that is specifically designed to facilitate a integration with XState. This controller provides the capability to subscribe to an XState actor. It also provides a callback function to handle the state changes.
11
9
  *
12
10
  * - [xstate v5](https://stately.ai/docs/installation)
13
11
  * - [xstate v5 - examples](https://stately.ai/docs/examples)
14
- * - [Original idea](https://codesandbox.io/s/z3o0s?file=/src/toggleMachine.ts)
15
12
  *
16
13
  * <hr>
17
14
  *
@@ -96,12 +93,15 @@ import { createActor } from 'xstate';
96
93
  * );
97
94
  * ```
98
95
  *
99
- * ***xstate-counter.js***
96
+ * **`new BlockquoteControllerXstate(this, {machine, options?, callback?})`**
97
+ *
98
+ * ***Usage***
100
99
  *
101
100
  * ```javascript
102
101
  * import { html, LitElement } from 'lit';
103
- * import { BlockquoteControllerXstate } from '@blockquote-web-components/blockquote-controller-xstate';
102
+ * import { BlockquoteControllerXstate } from '../index.js';
104
103
  * import { counterMachine } from './counterMachine.js';
104
+ * import { styles } from './styles/xstate-counter-styles.css.js';
105
105
  *
106
106
  * export class XstateCounter extends LitElement {
107
107
  * static properties = {
@@ -111,10 +111,31 @@ import { createActor } from 'xstate';
111
111
  * },
112
112
  * };
113
113
  *
114
+ * static styles = [styles];
115
+ *
114
116
  * constructor() {
115
117
  * super();
116
118
  * this._xstate = {};
117
- * this.counterController = new BlockquoteControllerXstate(this, counterMachine, '_xstate');
119
+ * this._inspectEvents = this._inspectEvents.bind(this);
120
+ * this._callbackCounterController = this._callbackCounterController.bind(this);
121
+ *
122
+ * this.counterController = new BlockquoteControllerXstate(this, {
123
+ * machine: counterMachine,
124
+ * options: {
125
+ * inspect: this._inspectEvents,
126
+ * },
127
+ * callback: this._callbackCounterController,
128
+ * });
129
+ * }
130
+ *
131
+ * _callbackCounterController(snapshot) {
132
+ * this._xstate = snapshot;
133
+ * }
134
+ *
135
+ * _inspectEvents(inspEvent) {
136
+ * if (inspEvent.type === '@xstate.snapshot' && inspEvent.event.type === 'xstate.stop') {
137
+ * this._xstate = {};
138
+ * }
118
139
  * }
119
140
  *
120
141
  * updated(props) {
@@ -129,70 +150,111 @@ import { createActor } from 'xstate';
129
150
  * }
130
151
  * }
131
152
  *
132
- * // ...
133
- *
134
153
  * get #disabled() {
135
- * return this.counterController.state.matches('disabled');
154
+ * return this.counterController.snapshot.matches('disabled');
136
155
  * }
137
156
  *
138
157
  * render() {
139
158
  * return html`
140
- * <button
141
- * ?disabled="${this.#disabled}"
142
- * data-counter="increment"
143
- * \@click=${() => this.counterController.send({ type: 'INC' })}
144
- * >
145
- * Increment
146
- * </button>
147
- * <button
148
- * ?disabled="${this.#disabled}"
149
- * data-counter="decrement"
150
- * \@click=${() => this.counterController.send({ type: 'DEC' })}
151
- * >
152
- * Decrement
153
- * </button>
159
+ * <slot></slot>
160
+ * <div aria-disabled="${this.#disabled}">
161
+ * <span>
162
+ * <button
163
+ * ?disabled="${this.#disabled}"
164
+ * data-counter="increment"
165
+ * \@click=${() => this.counterController.send({ type: 'INC' })}
166
+ * >
167
+ * Increment
168
+ * </button>
169
+ * <button
170
+ * ?disabled="${this.#disabled}"
171
+ * data-counter="decrement"
172
+ * \@click=${() => this.counterController.send({ type: 'DEC' })}
173
+ * >
174
+ * Decrement
175
+ * </button>
176
+ * </span>
177
+ * <p>${this.counterController.snapshot.context.counter}</p>
178
+ * </div>
179
+ * <div>
180
+ * <button \@click=${() => this.counterController.send({ type: 'TOGGLE' })}>
181
+ * ${this.#disabled ? 'Enabled counter' : 'Disabled counter'}
182
+ * </button>
183
+ * </div>
154
184
  * `;
155
185
  * }
156
- *
157
- * // ...
158
186
  * }
159
187
  * ```
160
188
  * <hr>
161
189
  */
162
- export class BlockquoteControllerXstate {
190
+ class UseMachine {
163
191
  /**
164
- * @param {import('lit').ReactiveElement} host
165
- * @param {import('xstate').StateMachine} machine
166
- * @param {string} propKey
192
+ * @param {import('lit').ReactiveElement} host - The host object.
193
+ * @param {{
194
+ * machine: import('xstate').StateMachine,
195
+ * options?: import('xstate').ActorOptions,
196
+ * callback?: Function
197
+ * }} arg - The arguments for the constructor.
167
198
  */
168
- constructor(host, machine, propKey) {
199
+ constructor(host, { machine, options, callback }) {
200
+ this.machine = machine;
201
+ this.options = options;
202
+ this.callback = callback;
203
+ this.currentSnapshot = this.snapshot;
204
+ this.onNext = this.onNext.bind(this);
205
+
169
206
  (this.host = host).addController(this);
170
- this.service = createActor(machine).start();
171
- this.propKey = propKey;
172
207
  }
173
208
 
174
- get state() {
175
- return this.service.getSnapshot();
209
+ get actor() {
210
+ return this.actorRef;
211
+ }
212
+
213
+ get snapshot() {
214
+ return this.actorRef?.getSnapshot?.();
176
215
  }
177
216
 
178
217
  /**
179
- * @param {import('xstate').EventObject} ev
218
+ * @param {import('xstate').EventFrom<typeof this.machine>} ev
180
219
  */
181
220
  send(ev) {
182
- this.service.send(ev);
221
+ this.actorRef?.send(ev);
222
+ }
223
+
224
+ unsubscribe() {
225
+ this.subs?.unsubscribe();
226
+ }
227
+
228
+ /**
229
+ * @param {import('xstate').SnapshotFrom<typeof this.machine>} snapshot
230
+ */
231
+ onNext(snapshot) {
232
+ if (this.currentSnapshot !== snapshot) {
233
+ this.currentSnapshot = snapshot;
234
+ this.callback?.(snapshot);
235
+ this.host.requestUpdate();
236
+ }
237
+ }
238
+
239
+ startService() {
240
+ this.actorRef = createActor(this.machine, this.options);
241
+ if (this.actorRef) {
242
+ this.subs = this.actorRef.subscribe(this.onNext);
243
+ }
244
+ this.actorRef?.start();
245
+ }
246
+
247
+ stopService() {
248
+ this.actorRef?.stop();
183
249
  }
184
250
 
185
251
  hostConnected() {
186
- /* Do not mutate the context object. Instead, you should use the assign(...) action to update context immutably.
187
- * https://stately.ai/docs/context#updating-context-with-assign
188
- * https://lit.dev/docs/components/properties/#mutating-properties
189
- */
190
- this.service.subscribe(state => {
191
- this.propKey in this.host && (this.host[this.propKey] = state);
192
- });
252
+ this.startService();
193
253
  }
194
254
 
195
255
  hostDisconnected() {
196
- this.service.stop();
256
+ this.stopService();
197
257
  }
198
258
  }
259
+
260
+ export { UseMachine as BlockquoteControllerXstate };