@uistate/core 5.0.1 → 5.0.2

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.
@@ -4,13 +4,16 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>001 Counter - EventState</title>
7
+ <script type="importmap">
8
+ { "imports": { "@uistate/core": "../../index.js" } }
9
+ </script>
7
10
  </head>
8
11
  <body>
9
12
  <h1>Counter: <span id="count">0</span></h1>
10
13
  <button id="increment">+</button>
11
14
 
12
15
  <script type="module">
13
- import { createEventState } from './eventState.js';
16
+ import { createEventState } from '@uistate/core';
14
17
 
15
18
  // Create store
16
19
  const store = createEventState({ count: 0 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uistate/core",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "Lightweight event-driven state management with slot orchestration and experimental event-sequence testing (eventTest.js available under dual license)",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -20,7 +20,8 @@
20
20
  "eventTest.js",
21
21
  "LICENSE",
22
22
  "LICENSE-eventTest.md",
23
- "examples/"
23
+ "examples/",
24
+ "playground/"
24
25
  ],
25
26
  "keywords": [
26
27
  "state-management",
@@ -0,0 +1,38 @@
1
+ <!-- playground/exercise001.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <script type="importmap">
6
+ { "imports": { "@uistate/core": "../index.js" } }
7
+ </script>
8
+ </head>
9
+ <body>
10
+ <h1>Counter: <span id="count">0</span></h1>
11
+ <button id="increment">+</button>
12
+ <button id="decrement">-</button>
13
+
14
+ <script type="module">
15
+ // (1) Import createEventState and create store
16
+ import { createEventState } from '@uistate/core';
17
+ const store = createEventState({
18
+ count: 0
19
+ });
20
+
21
+ // (2) Increment store.count on button click
22
+ document.getElementById('increment').onclick = () => {
23
+ const current = store.get('count');
24
+ store.set('count', current + 1);
25
+ };
26
+
27
+ // (3) Subscribe to count changes
28
+ store.subscribe('count', (value) => {
29
+ document.getElementById('count').textContent = value;
30
+ });
31
+
32
+ // (4) Exercise: Add decrement functionality
33
+ // Hint: Copy the increment handler above and modify it
34
+ // Your code here:
35
+
36
+ </script>
37
+ </body>
38
+ </html>
@@ -0,0 +1,49 @@
1
+ <!-- playground/exercise002.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <script type="importmap">
6
+ { "imports": { "@uistate/core": "../index.js" } }
7
+ </script>
8
+ <style>
9
+ body { font-family: system-ui; padding: 2rem; }
10
+ h1 { margin-bottom: 1rem; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <h1 id="heading">Counter: <span id="count">0</span></h1>
15
+ <button id="increment">+</button>
16
+ <button id="decrement">-</button>
17
+
18
+ <script type="module">
19
+ // Working counter with increment and decrement
20
+ import { createEventState } from '@uistate/core';
21
+
22
+ const store = createEventState({
23
+ count: 0
24
+ });
25
+
26
+ document.getElementById('increment').onclick = () => {
27
+ store.set('count', store.get('count') + 1);
28
+ };
29
+
30
+ document.getElementById('decrement').onclick = () => {
31
+ store.set('count', store.get('count') - 1);
32
+ };
33
+
34
+ store.subscribe('count', (value) => {
35
+ document.getElementById('count').textContent = value;
36
+ });
37
+
38
+ // Exercise: Change the h1 color based on count value
39
+ // - If count is positive: make it green
40
+ // - If count is negative: make it red
41
+ // - If count is zero: make it black
42
+ //
43
+ // Hint 1: Subscribe to 'count' changes
44
+ // Hint 2: Use document.getElementById('heading').style.color = '...'
45
+ // Your code here:
46
+
47
+ </script>
48
+ </body>
49
+ </html>
@@ -1,86 +0,0 @@
1
- const createEventState = (initial = {}) => {
2
- const store = JSON.parse(JSON.stringify(initial));
3
-
4
- const bus = new EventTarget();
5
- let destroyed = false;
6
-
7
- return {
8
- get: (path) => {
9
- if (destroyed) throw new Error('Cannot get from destroyed store');
10
- if (!path) return store;
11
-
12
- // Parse dot-paths: 'items.0' → ['items', '0']
13
- const parts = path.split('.').flatMap(part => {
14
- const match = part.match(/([^\[]+)\[(\d+)\]/);
15
- return match ? [match[1], match[2]] : part;
16
- });
17
-
18
- return parts.reduce(
19
- (obj, prop) => (obj && obj[prop] !== undefined ? obj[prop] : undefined),
20
- store
21
- );
22
- },
23
- set: (path, value) => {
24
- if (destroyed) throw new Error('Cannot set on destroyed store');
25
- if (!path) return;
26
- const parts = path.split('.');
27
- const last = parts.pop();
28
- let target = store;
29
-
30
- for (const key of parts) {
31
- // create intermediate objects as needed
32
- if (typeof target[key] !== 'object' || target[key] === null) {
33
- target[key] = {};
34
- }
35
- target = target[key];
36
- }
37
-
38
- target[last] = value;
39
-
40
- if (!destroyed) {
41
- // exact path
42
- bus.dispatchEvent(new CustomEvent(path, { detail: value }));
43
-
44
- // parent wildcards: a, a.b -> 'a.*', 'a.b.*'
45
- for (let i = 1; i <= parts.length; i++) {
46
- const parent = parts.slice(0, i).join('.');
47
- bus.dispatchEvent(new CustomEvent(`${parent}.*`, { detail: { path, value } }));
48
- }
49
-
50
- // root wildcard
51
- bus.dispatchEvent(new CustomEvent('*', { detail: { path, value } }));
52
- }
53
-
54
- return value;
55
- },
56
- subscribe(path, handler) {
57
- if (destroyed) throw new Error('store destroyed');
58
- if (!path || typeof handler !== 'function') {
59
- throw new TypeError('subscribe(path, handler) requires a string path and function handler');
60
- }
61
- const onEvent = (evt) => handler(evt.detail, evt);
62
- bus.addEventListener(path, onEvent);
63
-
64
- return function unsubscribe() {
65
- bus.removeEventListener(path, onEvent);
66
- };
67
- },
68
- off(unsubscribe) {
69
- if (typeof unsubscribe !== 'function') {
70
- throw new TypeError('off(unsubscribe) requires a function returned by subscribe');
71
- }
72
- return unsubscribe();
73
- },
74
-
75
- destroy() {
76
- if (!destroyed) {
77
- destroyed = true;
78
- // EventTarget has no parentNode - just mark as destroyed
79
- // Future sets/subscribes will be blocked by destroyed flag
80
- }
81
- }
82
- };
83
- }
84
-
85
- export default createEventState;
86
- export { createEventState };