@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 '
|
|
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.
|
|
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 };
|