@citolab/tspci 2.13.0 → 2.14.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [2.14.0](https://github.com/Citolab/tspci/compare/v2.13.0...v2.14.0) (2026-10-14)
7
+
8
+
9
+ ### Features
10
+
11
+ * **tspci:** the preact template uses typed store actions and the unsubscribe function ([5297db7](https://github.com/Citolab/tspci/commit/5297db72a3d2c62cdba58c1b2fe49bbbdee1e024))
12
+
13
+
14
+
15
+
16
+
6
17
  # [2.13.0](/compare/v2.12.1...v2.13.0) (2026-10-14)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citolab/tspci",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "an opiniated typescript ims-pci builder with tao integration",
5
5
  "author": "patrick <patrick.deklein@cito.nl>",
6
6
  "repository": {
@@ -65,5 +65,5 @@
65
65
  "access": "public",
66
66
  "registry": "https://registry.npmjs.org/"
67
67
  },
68
- "gitHead": "b62ff736df6ebd0ec8c86e35338a6bb705e0820c"
68
+ "gitHead": "d72febca7cea937f77be4cef910a7f2d72fd524f"
69
69
  }
@@ -17,7 +17,7 @@ import * as ctx from "qtiCustomInteractionContext"; // provided by the delivery
17
17
  import configProps from "./config.json"; // the settings; the build passes only their defaults (AUTHORING.md)
18
18
  import style from "./styles.css"; // Tailwind, compiled to a string, added to the shadow root below
19
19
  import Interaction from "./interaction";
20
- import { StateModel, initStore } from "./store";
20
+ import { Actions, StateModel, initStore } from "./store";
21
21
 
22
22
  // Like every data- attribute, each setting arrives as a string.
23
23
  type PropTypes = PciProperties<typeof configProps>;
@@ -49,7 +49,7 @@ class PciInstance implements IMSpci<PropTypes> {
49
49
  private config: ConfigProperties<PropTypes>;
50
50
  private dom: HTMLElement;
51
51
  private shadowdom: ShadowRoot;
52
- private store: IStore<StateModel> = initStore();
52
+ private store: IStore<StateModel, Actions> = initStore();
53
53
  private customValidity = "";
54
54
 
55
55
  constructor(dom: HTMLElement, config: ConfigProperties<PropTypes>, stateString?: string) {
@@ -79,13 +79,14 @@ class PciInstance implements IMSpci<PropTypes> {
79
79
 
80
80
  // From here on, tell the engine when the response changes.
81
81
  this.previousResponse = JSON.stringify(this.getResponse() ?? null);
82
- this.store.subscribe(this.onStoreChange);
82
+ this.unsubscribe = this.store.subscribe(this.onStoreChange);
83
83
 
84
84
  this.render();
85
85
  config.onready?.(this, this.getState());
86
86
  }
87
87
 
88
88
  private previousResponse: string;
89
+ private unsubscribe = () => {};
89
90
  private onStoreChange: Listener<StateModel> = () => {
90
91
  const response = JSON.stringify(this.getResponse() ?? null);
91
92
  if (response === this.previousResponse) return; // subscribe() calls us right away, and not every action changes it
@@ -148,7 +149,7 @@ class PciInstance implements IMSpci<PropTypes> {
148
149
 
149
150
  /** The engine is done with this instance: unsubscribe and unmount (which also runs the components' cleanups). */
150
151
  oncompleted = () => {
151
- this.store.unsubscribe(this.onStoreChange);
152
+ this.unsubscribe();
152
153
  render(null, this.shadowdom);
153
154
  };
154
155
  }
@@ -3,14 +3,14 @@ import { IStore, useStore } from "@citolab/preact-store";
3
3
  import { useLayoutEffect, useRef, useState } from "preact/hooks";
4
4
  import configProps from "./config.json";
5
5
  import { Pie } from "./pie";
6
- import { StateModel } from "./store";
6
+ import { Actions, StateModel } from "./store";
7
7
  // Images are bundled too: `import logo from "./assets/logo.png"` gives a data URL for <img src={logo} />.
8
8
 
9
9
  type PropTypes = PciProperties<typeof configProps>;
10
10
 
11
11
  type Props = {
12
12
  config: PropTypes;
13
- store: IStore<StateModel>;
13
+ store: IStore<StateModel, Actions>;
14
14
  readOnly: boolean; // review, solution, closed: show the answer, but do not change it
15
15
  };
16
16
 
@@ -5,12 +5,13 @@ import { Store } from "@citolab/preact-store";
5
5
  * getState() saves both, so a session can be restored, and even replayed (store.replay), action by action.
6
6
  */
7
7
  export type StateModel = { value: string }; // the text in the input, also when it is not a number yet
8
- export type ActionType = "SET_VALUE";
8
+ /** The actions, as { type: payload }: dispatch and addReducer only accept these. */
9
+ export type Actions = { SET_VALUE: { value: string } };
9
10
 
10
11
  export const initialState: StateModel = { value: "" };
11
12
 
12
13
  export const initStore = (state: StateModel = initialState) => {
13
- const store = new Store<StateModel>(state);
14
- store.addReducer<{ value: string }>("SET_VALUE", (current, { value }) => ({ ...current, value }));
14
+ const store = new Store<StateModel, Actions>(state);
15
+ store.addReducer("SET_VALUE", (current, { value }) => ({ ...current, value }));
15
16
  return store;
16
17
  };
package/readme.md CHANGED
@@ -515,17 +515,27 @@ class VoxelPainterClass {
515
515
 
516
516
  ### Record user interaction
517
517
 
518
- A store will centralize your PCI state but also all mutations.
519
- This means, this way you could replay everything a user did
520
- And you can use Redux devtools to debug
518
+ With `@citolab/preact-store` (the `preact+tailwind` template) every change of the PCI state is an action. The
519
+ store keeps the actions with a timestamp, so `getState()` can save how a candidate came to an answer, and
520
+ `store.replay(log)` plays it back.
521
521
 
522
522
  ```ts
523
- // src/index.ts
524
- const [state, dispatch] = useStore<StateModel, ActionType>((type, payload) => {});
523
+ // src/store.ts
524
+ type Actions = { ADD_CUBE: { x: number }; CLEAR: null }; // { type: payload }
525
+ const store = new Store<StateModel, Actions>({ cubes: [] });
526
+ store.addReducer("ADD_CUBE", (state, cube) => ({ ...state, cubes: [...state.cubes, cube] }));
527
+
528
+ // src/interaction.tsx
529
+ const { cubes } = useStore(store); // re-renders on every change
530
+ store.dispatch({ type: "ADD_CUBE", payload: { x: 1 } });
525
531
 
526
- dispatch<{ x: number }>("ADD_ACTION", { x: +inputValue });
532
+ // src/index.ts
533
+ const unsubscribe = store.subscribe(() => notifyTheEngine(), { immediate: false });
534
+ getState = () => JSON.stringify({ state: store.getState(), log: store.getActions() });
527
535
  ```
528
536
 
537
+ See [preact-store](../preact-store/readme.md) for the whole API.
538
+
529
539
  ### Let authors configure your PCI in QTI
530
540
 
531
541
  Use properties in config.json to let authors configure your PCI. Each one becomes a `data-` attribute of the