@citolab/tspci 2.5.23 → 2.5.30-beta.10

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,145 @@
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.5.30-beta.10](/compare/v2.5.30-beta.9...v2.5.30-beta.10) (2025-02-04)
7
+
8
+ **Note:** Version bump only for package @citolab/tspci
9
+
10
+
11
+
12
+
13
+
14
+ ## [2.5.30-beta.9](/compare/v2.5.30-beta.8...v2.5.30-beta.9) (2025-02-04)
15
+
16
+ **Note:** Version bump only for package @citolab/tspci
17
+
18
+
19
+
20
+
21
+
22
+ ## [2.5.30-beta.8](/compare/v2.5.30-beta.7...v2.5.30-beta.8) (2025-02-04)
23
+
24
+ **Note:** Version bump only for package @citolab/tspci
25
+
26
+
27
+
28
+
29
+
30
+ ## [2.5.30-beta.7](/compare/v2.5.30-beta.6...v2.5.30-beta.7) (2025-02-04)
31
+
32
+
33
+ ### Features
34
+
35
+ * dispatch qti-interaction-changed event 5c81bab
36
+
37
+
38
+
39
+
40
+
41
+ ## [2.5.30-beta.6](/compare/v2.5.30-beta.5...v2.5.30-beta.6) (2025-02-03)
42
+
43
+ **Note:** Version bump only for package @citolab/tspci
44
+
45
+
46
+
47
+
48
+
49
+ ## [2.5.30-beta.5](/compare/v2.5.30-beta.4...v2.5.30-beta.5) (2025-02-03)
50
+
51
+ **Note:** Version bump only for package @citolab/tspci
52
+
53
+
54
+
55
+
56
+
57
+ ## [2.5.30-beta.4](/compare/v2.5.30-beta.3...v2.5.30-beta.4) (2025-02-03)
58
+
59
+ **Note:** Version bump only for package @citolab/tspci
60
+
61
+
62
+
63
+
64
+
65
+ ## [2.5.30-beta.3](/compare/v2.5.30-beta.2...v2.5.30-beta.3) (2025-02-03)
66
+
67
+ **Note:** Version bump only for package @citolab/tspci
68
+
69
+
70
+
71
+
72
+
73
+ ## [2.5.30-beta.2](/compare/v2.5.30-beta.1...v2.5.30-beta.2) (2025-02-03)
74
+
75
+ **Note:** Version bump only for package @citolab/tspci
76
+
77
+
78
+
79
+
80
+
81
+ ## [2.5.30-beta.1](/compare/v2.5.30-beta.0...v2.5.30-beta.1) (2025-02-03)
82
+
83
+ **Note:** Version bump only for package @citolab/tspci
84
+
85
+
86
+
87
+
88
+
89
+ ## [2.5.30-beta.0](/compare/v2.5.29...v2.5.30-beta.0) (2025-02-03)
90
+
91
+ **Note:** Version bump only for package @citolab/tspci
92
+
93
+
94
+
95
+
96
+
97
+ ## 2.5.29 (2025-02-03)
98
+
99
+ **Note:** Version bump only for package @citolab/tspci
100
+
101
+
102
+
103
+
104
+
105
+ ## [2.5.28](/compare/v2.5.27...v2.5.28) (2025-02-03)
106
+
107
+ **Note:** Version bump only for package @citolab/tspci
108
+
109
+
110
+
111
+
112
+
113
+ ## [2.5.27](/compare/v2.5.26...v2.5.27) (2025-02-03)
114
+
115
+ **Note:** Version bump only for package @citolab/tspci
116
+
117
+
118
+
119
+
120
+
121
+ ## [2.5.26](/compare/v2.5.25...v2.5.26) (2025-02-03)
122
+
123
+ **Note:** Version bump only for package @citolab/tspci
124
+
125
+
126
+
127
+
128
+
129
+ ## [2.5.25](/compare/v2.5.24...v2.5.25) (2025-02-03)
130
+
131
+ **Note:** Version bump only for package @citolab/tspci
132
+
133
+
134
+
135
+
136
+
137
+ ## [2.5.24](/compare/v2.5.23...v2.5.24) (2025-02-03)
138
+
139
+ **Note:** Version bump only for package @citolab/tspci
140
+
141
+
142
+
143
+
144
+
6
145
  ## 2.5.23 (2024-10-16)
7
146
 
8
147
  **Note:** Version bump only for package @citolab/tspci
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citolab/tspci",
3
- "version": "2.5.23",
3
+ "version": "2.5.30-beta.10",
4
4
  "description": "an opiniated typescript ims-pci builder with tao integration",
5
5
  "author": "patrick <patrick.deklein@cito.nl>",
6
6
  "repository": "@citolab/tspci",
@@ -24,6 +24,7 @@
24
24
  "@rollup/plugin-terser": "^0.4.4",
25
25
  "@rollup/plugin-typescript": "^12.1.0",
26
26
  "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
27
+ "@tailwindcss/postcss": "^4.0.3",
27
28
  "@testing-library/dom": "^10.4.0",
28
29
  "@types/node": "^22.5.5",
29
30
  "@typescript-eslint/eslint-plugin": "^8.9.0",
@@ -37,7 +38,7 @@
37
38
  "live-server": "^1.2.2",
38
39
  "postcss": "^8.4.47",
39
40
  "postcss-import": "^16.1.0",
40
- "rollup": "^4.24.0",
41
+ "rollup": "^4.34.1",
41
42
  "rollup-plugin-copy": "^3.5.0",
42
43
  "rollup-plugin-live-server": "^2.0.0",
43
44
  "rollup-plugin-livereload": "^2.0.5",
@@ -45,10 +46,10 @@
45
46
  "rollup-plugin-node-globals": "^1.4.0",
46
47
  "rollup-plugin-postcss": "^4.0.2",
47
48
  "rollup-plugin-string": "^3.0.0",
48
- "tailwindcss": "^3.4.11"
49
+ "tailwindcss": "^4.0.3"
49
50
  },
50
51
  "resolutions": {
51
52
  "chokidar": "^4.0.0"
52
53
  },
53
- "gitHead": "8fc38f29c16c17d5ad302fbeaf52d50c643a7c03"
54
+ "gitHead": "5be4bc6341a95ed5d5cf8bc9100e6c9182cc6b79"
54
55
  }
@@ -41,14 +41,38 @@ class Pci {
41
41
 
42
42
  this.shadowdom = dom.attachShadow({ mode: "closed" });
43
43
  this.render();
44
+ const event = {
45
+ interaction: this,
46
+ responseIdentifier: this.config.responseIdentifier,
47
+ valid: true,
48
+ value: this.getResponse(),
49
+ };
50
+ // dispatch a custom event to notify the Delivery System that the interaction has changed
51
+ const interactionChangedEvent = new CustomEvent("qtiInteractionChanged", {
52
+ detail: event,
53
+ });
54
+ this.shadowdom.dispatchEvent(interactionChangedEvent);
55
+ };
44
56
 
45
- config.onready(this);
57
+ interactionChanged = () => {
58
+ const event = {
59
+ interaction: this,
60
+ responseIdentifier: this.config.responseIdentifier,
61
+ valid: true,
62
+ value: this.getResponse(),
63
+ };
64
+ // dispatch a custom event to notify the Delivery System that the interaction has changed
65
+ const interactionChangedEvent = new CustomEvent("qti-interaction-changed", {
66
+ detail: event,
67
+ });
68
+ this.dom.dispatchEvent(interactionChangedEvent);
46
69
  };
47
70
 
48
71
  render = () => {
49
72
  this.shadowdom.addEventListener("change", (e) => {
50
73
  const value = e.target.value;
51
74
  this.state = value;
75
+ this.interactionChanged();
52
76
  });
53
77
  this.shadowdom.innerHTML = `<div class="pci-container">
54
78
  <h1>${this.config.properties.title}</h1>
@@ -4,7 +4,7 @@ import { IStore } from "@citolab/preact-store";
4
4
  import * as ctx from "qtiCustomInteractionContext";
5
5
  import Interaction from "./interaction";
6
6
  import style from "./styles.css";
7
- import { Configuration, IMSpci, QtiVariableJSON } from "@citolab/tspci";
7
+ import { ConfigProperties, IMSpci, QtiInteractionChangedDetail, QtiVariableJSON } from "@citolab/tspci";
8
8
  import configProps from "./config.json";
9
9
  import { StateModel, initStore } from "./store";
10
10
 
@@ -12,7 +12,7 @@ type PropTypes = typeof configProps;
12
12
 
13
13
  class App implements IMSpci<PropTypes> {
14
14
  typeIdentifier = "###___PCI_NAME___###"; // typeIdentifier is mandatory for all PCI's
15
- config: Configuration<PropTypes>; // reference to the interface of the config object which you get when getInstance is called by the player
15
+ config: ConfigProperties<PropTypes>; // reference to the interface of the config object which you get when getInstance is called by the player
16
16
  state: string; // keep a reference to the state
17
17
  shadowdom: ShadowRoot; // Not mandatory, but its wise to create a shadowroot
18
18
  store: IStore<StateModel>;
@@ -20,13 +20,17 @@ class App implements IMSpci<PropTypes> {
20
20
  private logActions: { type: string; payload: unknown }[] = []; // optional logActions
21
21
  private initialState: StateModel = { input: undefined }; // optional initial state
22
22
  constructor() {
23
- ctx && ctx.register(this);
23
+ if (ctx) {
24
+ ctx.register(this);
25
+ }
24
26
  }
25
27
 
26
- getInstance = (dom: HTMLElement, config: Configuration<PropTypes>, stateString: string) => {
28
+ getInstance = (dom: HTMLElement, config: ConfigProperties<PropTypes>, stateString: string) => {
27
29
  config.properties = { ...configProps, ...config.properties }; // merge current props with incoming
28
30
  this.config = config;
29
-
31
+ if (!dom) {
32
+ throw new Error("No DOM Element provided");
33
+ }
30
34
  this.logActions = stateString ? JSON.parse(stateString).log : [];
31
35
  this.store = initStore(this.initialState);
32
36
  try {
@@ -39,9 +43,26 @@ class App implements IMSpci<PropTypes> {
39
43
  }
40
44
 
41
45
  this.shadowdom = dom.attachShadow({ mode: "closed" });
46
+
47
+ this.store.subscribe(() => {
48
+ const event: QtiInteractionChangedDetail = {
49
+ interaction: this,
50
+ responseIdentifier: this.config.responseIdentifier,
51
+ valid: true,
52
+ value: this.getResponse(),
53
+ };
54
+ // dispatch a custom event to notify the Delivery System that the interaction has changed
55
+ const interactionChangedEvent = new CustomEvent("qti-interaction-changed", {
56
+ detail: event,
57
+ });
58
+ dom.dispatchEvent(interactionChangedEvent);
59
+ });
60
+
42
61
  this.render();
43
62
 
44
- this.config.onready && this.config.onready(this);
63
+ if (this.config.onready) {
64
+ this.config.onready(this);
65
+ }
45
66
  };
46
67
 
47
68
  render = () => {
@@ -1,15 +1,19 @@
1
1
  import { Store } from "@citolab/preact-store";
2
2
 
3
3
  export type ActionType = "SET_INPUT" | "RESTORE_STATE";
4
- export type StateModel = { input: number };
4
+ export type StateModel = { input: number | undefined };
5
5
 
6
- export const initStore = (initialState: StateModel, restoreData?: {
7
- state?: StateModel;
8
- actions: { type: string; payload: any; timestamp?: number }[];
9
- }) => {
6
+ export const initStore = (
7
+ initialState: StateModel,
8
+ restoreData?: {
9
+ state: StateModel;
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ actions: { type: string; payload: any; timestamp?: number }[];
12
+ }
13
+ ) => {
10
14
  const store = new Store<StateModel>(initialState, restoreData);
11
- store.addReducer<{ input: number }>("SET_INPUT", (state, { input }) => {
12
- return {...state, input };
15
+ store.addReducer<{ input: number | undefined }>("SET_INPUT", (state, { input }) => {
16
+ return { ...state, input };
13
17
  });
14
18
  return store;
15
- }
19
+ };
@@ -1,8 +1,8 @@
1
1
  module.exports = {
2
2
  plugins: [
3
- require('postcss-import'),
4
- require('@tailwindcss/nesting'),
5
- require('tailwindcss'),
6
- require('autoprefixer'),
7
- ]
8
- }
3
+ require("postcss-import"),
4
+ require("@tailwindcss/nesting"),
5
+ require("@tailwindcss/postcss"),
6
+ require("autoprefixer"),
7
+ ],
8
+ };
@@ -1,7 +1,10 @@
1
- @tailwind components;
2
- @tailwind utilities;
3
- @tailwind base;
4
-
5
- .pci-container { @apply container flex flex-col justify-center items-center; }
6
- h1 { @apply text-2xl text-red-500 m-4; }
7
- input { @apply border border-gray-400 rounded p-2 m-1; }
1
+ @import "tailwindcss";
2
+ .pci-container {
3
+ @apply container flex flex-col justify-center items-center;
4
+ }
5
+ h1 {
6
+ @apply text-2xl text-red-500 m-4;
7
+ }
8
+ input {
9
+ @apply border border-gray-400 rounded p-2 m-1;
10
+ }
@@ -1,4 +1,4 @@
1
- import { Configuration, IMSpci, QtiVariableJSON } from "@citolab/tspci";
1
+ import { ConfigProperties, IMSpci, QtiInteractionChangedDetail, QtiVariableJSON } from "@citolab/tspci";
2
2
  import * as ctx from "qtiCustomInteractionContext";
3
3
  import configProps from "./config.json";
4
4
  import Interaction from "./interaction"; // import and bundle this interaction file
@@ -10,31 +10,53 @@ type PropTypes = typeof configProps;
10
10
 
11
11
  class Pci implements IMSpci<PropTypes> {
12
12
  typeIdentifier = "###___PCI_NAME___###"; // typeIdentifier is mandatory for all PCI's
13
- config: Configuration<PropTypes>; // reference to the interface of the config object which you get when getInstance is called by the player
13
+ config: ConfigProperties<PropTypes>; // reference to the interface of the config object which you get when getInstance is called by the player
14
14
  state: string; // keep a reference to the state
15
15
  shadowdom: ShadowRoot | Element; // Not mandatory, but its wise to create a shadowroot
16
+ dom: HTMLElement;
16
17
 
17
18
  constructor() {
18
- ctx && ctx.register(this); // we assume the qtiCustomInteractionContext is avaible due to the import above
19
+ if (ctx) {
20
+ ctx.register(this); // we assume the qtiCustomInteractionContext is avaible due to the import above
21
+ }
19
22
  }
20
23
 
21
24
  // First in the lifecycle of a PCI, this method is called with the domElement ( usually qti-interaction-markup ) where we can add our dom tree.
22
25
  // config is the configuration object which has an onready
23
- getInstance = (dom: HTMLElement, config: Configuration<any>, state: string) => {
26
+ getInstance = (dom: HTMLElement, config: ConfigProperties<PropTypes>, state: string) => {
24
27
  config.properties = { ...configProps, ...config.properties }; // merge current props with incoming
25
28
  this.config = config;
26
29
  this.state = state ? state : "";
27
-
30
+ if (!dom) {
31
+ throw new Error("No DOM Element provided");
32
+ }
28
33
  this.shadowdom = dom.attachShadow({ mode: "closed" });
34
+ this.dom = dom;
35
+
29
36
  this.render();
30
37
 
31
38
  config.onready(this);
32
39
  };
33
40
 
41
+ private interactionChanged = () => {
42
+ const event: QtiInteractionChangedDetail = {
43
+ interaction: this,
44
+ responseIdentifier: this.config.responseIdentifier,
45
+ valid: true,
46
+ value: this.getResponse(),
47
+ };
48
+ // dispatch a custom event to notify the Delivery System that the interaction has changed
49
+ const interactionChangedEvent = new CustomEvent("qti-interaction-changed", {
50
+ detail: event,
51
+ });
52
+ this.dom.dispatchEvent(interactionChangedEvent);
53
+ };
54
+
34
55
  private render = () => {
35
56
  this.shadowdom.addEventListener("change", (e: Event) => {
36
57
  const value = (e.target as HTMLInputElement)?.value || "";
37
58
  this.state = value;
59
+ this.interactionChanged();
38
60
  });
39
61
  this.shadowdom.innerHTML = `<div class="pci-container">
40
62
  <h1>${this.config.properties.title}</h1>
package/readme.md CHANGED
@@ -97,7 +97,7 @@ Add the following PCI in the src folder `index.ts`
97
97
 
98
98
  ```ts
99
99
  // src/index.ts
100
- import { Configuration, IMSpci } from "@citolab/tspci";
100
+ import { ConfigProperties, IMSpci } from "@citolab/tspci";
101
101
  import * as ctx from "qtiCustomInteractionContext";
102
102
 
103
103
  class Pci implements IMSpci<{}> {
@@ -108,7 +108,7 @@ class Pci implements IMSpci<{}> {
108
108
  ctx && ctx.register(this);
109
109
  }
110
110
 
111
- getInstance = (dom: HTMLElement, config: Configuration<any>, state: string) => {
111
+ getInstance = (dom: HTMLElement, config: ConfigProperties<any>, state: string) => {
112
112
  this.shadowdom = dom.attachShadow({ mode: "closed" });
113
113
  this.render();
114
114
  config.onready(this);
@@ -337,9 +337,9 @@ Also used in the TAO export for configuring PCIs in TAO
337
337
  + type PropTypes = typeof configProps;
338
338
 
339
339
  // add to types
340
- + private config: Configuration<PropTypes>;
340
+ + private config: ConfigProperties<PropTypes>;
341
341
 
342
- getInstance = (dom: HTMLElement, config: Configuration<PropTypes>, stateString: string) => {
342
+ getInstance = (dom: HTMLElement, config: ConfigProperties<PropTypes>, stateString: string) => {
343
343
  + config.properties = { ...configProps, ...config.properties }; // merge our props with players
344
344
  + this.config = config;
345
345
 
package/src/cli.js CHANGED
@@ -289,11 +289,17 @@ const run = async () => {
289
289
 
290
290
  // read postcss.config.js
291
291
  const postcssConfig = path.resolve(cliPath, "./../preset/tailwind/postcss.config.js");
292
+ // get all plugin names
292
293
  const postcssConfigContent = fs.readFileSync(postcssConfig, "utf8");
293
294
  // get all plugin names
294
- const pluginNames = postcssConfigContent
295
- .match(/require\('(.*)'\)/g)
296
- .map((plugin) => plugin.replace(/require\('(.*)'\)/, "$1"));
295
+ // Match require statements that use either single or double quotes
296
+ const pluginMatches = postcssConfigContent.match(/require\(["']([^"']+)["']\)/g);
297
+ let pluginNames = [];
298
+ if (pluginMatches) {
299
+ pluginNames = pluginMatches.map((plugin) => plugin.replace(/require\(["']([^"']+)["']\)/, "$1"));
300
+ } else {
301
+ console.log("No PostCSS plugins found.");
302
+ }
297
303
  const packageJsonCli = path.resolve(cliPath, "./../package.json");
298
304
  const packageJsonCliContent = fs.readFileSync(packageJsonCli, "utf8");
299
305
  const packageJsonCliContentJson = JSON.parse(packageJsonCliContent);
@@ -430,15 +436,14 @@ const run = async () => {
430
436
  }
431
437
 
432
438
  prodBuild().then(() => {
433
- if (t) {
434
- if (!targetName) {
435
- console.error("Missing target name. Use tspci --target target or tspci --targetExt target");
436
- }
437
- const scriptFile = path.join(path.join(path.join(NODE_MODULES, `${targetName}`), "src"), `createPackage.mjs`);
438
- import(`file:///${scriptFile}`).then((script) => {
439
- script.bundle();
440
- });
439
+ if (!targetName) {
440
+ console.error("Missing target name. Use tspci --target target or tspci --targetExt target");
441
+ return;
441
442
  }
443
+ const scriptFile = path.join(path.join(path.join(NODE_MODULES, `${targetName}`), "src"), `createPackage.mjs`);
444
+ import(`file:///${scriptFile}`).then((script) => {
445
+ script.bundle();
446
+ });
442
447
  });
443
448
  };
444
449
 
package/types/index.d.ts CHANGED
@@ -1,4 +1,31 @@
1
- export interface IMSpci<ConfigProperties> {
1
+ // Define the payload structure for the custom event
2
+ export interface QtiInteractionChangedDetail {
3
+ interaction: IMSpci<unknown>; // The PCI instance itself
4
+ responseIdentifier: string; // The response identifier provided in the getInstance call
5
+ valid?: boolean; // Optional boolean indicating if checkValidity() returns true
6
+ value?: QtiVariableJSON; // Optional value returned by getResponse()
7
+ }
8
+
9
+ // Define the ConfigProperties interface
10
+ export interface ConfigProperties<T> {
11
+ properties: T; // Follows dataset conversion rules (camelCased keys)
12
+ templateVariables: Record<string, QtiVariableJSON>; // Follows structure in Appendix C
13
+ contextVariables: Record<string, QtiVariableJSON>; // Follows structure in Appendix C
14
+ boundTo: Record<string, QtiVariableJSON>; // Follows structure in Appendix C
15
+ responseIdentifier: string; // Unique within interaction scope
16
+
17
+ onready: (interaction: IMSpci<T>, state?: string) => void; // Callback when PCI is fully constructed and ready
18
+ ondone?: (
19
+ interaction: IMSpci<T>,
20
+ response: Record<string, QtiVariableJSON>,
21
+ state: string,
22
+ status?: "interacting" | "closed" | "solution" | "review"
23
+ ) => void; // Optional callback when candidate finishes interaction
24
+
25
+ status?: "interacting" | "suspended" | "closed" | "solution" | "review"; // Optional, defaults to "interacting"
26
+ }
27
+
28
+ export interface IMSpci<T> {
2
29
  typeIdentifier: string;
3
30
 
4
31
  /** @access public
@@ -10,13 +37,13 @@ export interface IMSpci<ConfigProperties> {
10
37
  * This must have been obtained from a prior call to getState on an
11
38
  * instance of this type (same typeIdentifier)
12
39
  */
13
- getInstance: (dom: HTMLElement, configuration: Configuration<ConfigProperties>, state: string) => void;
40
+ getInstance: (dom: HTMLElement, configuration: ConfigProperties<T>, state: string) => void;
14
41
 
15
42
  /** @access public
16
43
  * @method getResponse
17
44
  * @return {Object} - the value to assign to the bound QTI response variable
18
45
  */
19
- getResponse: () => QtiVariableJSON;
46
+ getResponse: () => QtiVariableJSON | undefined;
20
47
 
21
48
  /** @access public
22
49
  * @method getState
@@ -28,23 +55,6 @@ export interface IMSpci<ConfigProperties> {
28
55
  destroy?: () => void; // Not used in IMS and not in TAO implementation, so not used here (optional)
29
56
  }
30
57
 
31
-
32
-
33
- export declare type Configuration<T> = {
34
- onready: (pci: IMSpci<T>, state?: string) => void;
35
- properties: T;
36
- // PK: following this: https://www.imsglobal.org/spec/qti/v3p0/impl#h.1mc9puik2ft6
37
- // All below are not used in the IMS Reference implementation, keeping for ref
38
- /*
39
- ondone: (pci: IMSPci<T>,response:any,state:string, status?: "interacting" | "closed" | "solution" | "review" ) => void;
40
- status: string;
41
- templateVariables: any;
42
- boundTo: any;
43
- contextVariables
44
- responseIdentifier
45
- */
46
- };
47
-
48
58
  export interface directedPair {
49
59
  destination: string;
50
60
  source: string;