@uistate/core 5.2.0 → 5.4.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.
Files changed (142) hide show
  1. package/index.js +2 -9
  2. package/package.json +4 -10
  3. package/queryClient.js +55 -0
  4. package/cssState.js +0 -212
  5. package/examples/001-counter/README.md +0 -44
  6. package/examples/001-counter/index.html +0 -33
  7. package/examples/002-counter-improved/README.md +0 -44
  8. package/examples/002-counter-improved/index.html +0 -47
  9. package/examples/003-input-reactive/README.md +0 -44
  10. package/examples/003-input-reactive/index.html +0 -33
  11. package/examples/004-computed-state/README.md +0 -45
  12. package/examples/004-computed-state/index.html +0 -65
  13. package/examples/005-conditional-rendering/README.md +0 -42
  14. package/examples/005-conditional-rendering/index.html +0 -39
  15. package/examples/006-list-rendering/README.md +0 -49
  16. package/examples/006-list-rendering/index.html +0 -63
  17. package/examples/007-form-validation/README.md +0 -52
  18. package/examples/007-form-validation/index.html +0 -102
  19. package/examples/008-undo-redo/README.md +0 -70
  20. package/examples/008-undo-redo/index.html +0 -108
  21. package/examples/009-localStorage-side-effects/README.md +0 -72
  22. package/examples/009-localStorage-side-effects/index.html +0 -57
  23. package/examples/010-decoupled-components/README.md +0 -74
  24. package/examples/010-decoupled-components/index.html +0 -93
  25. package/examples/011-async-patterns/README.md +0 -98
  26. package/examples/011-async-patterns/index.html +0 -132
  27. package/examples/028-counter-improved-eventTest/LICENSE +0 -55
  28. package/examples/028-counter-improved-eventTest/README.md +0 -131
  29. package/examples/028-counter-improved-eventTest/app/store.js +0 -9
  30. package/examples/028-counter-improved-eventTest/index.html +0 -49
  31. package/examples/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +0 -282
  32. package/examples/028-counter-improved-eventTest/runtime/core/eventState.js +0 -100
  33. package/examples/028-counter-improved-eventTest/runtime/core/eventStateNew.js +0 -149
  34. package/examples/028-counter-improved-eventTest/runtime/core/helpers.js +0 -212
  35. package/examples/028-counter-improved-eventTest/runtime/core/router.js +0 -271
  36. package/examples/028-counter-improved-eventTest/store.d.ts +0 -8
  37. package/examples/028-counter-improved-eventTest/style.css +0 -170
  38. package/examples/028-counter-improved-eventTest/tests/README.md +0 -208
  39. package/examples/028-counter-improved-eventTest/tests/counter.test.js +0 -116
  40. package/examples/028-counter-improved-eventTest/tests/eventTest.js +0 -176
  41. package/examples/028-counter-improved-eventTest/tests/generateTypes.js +0 -168
  42. package/examples/028-counter-improved-eventTest/tests/run.js +0 -20
  43. package/examples/030-todo-app-with-eventTest/LICENSE +0 -55
  44. package/examples/030-todo-app-with-eventTest/README.md +0 -121
  45. package/examples/030-todo-app-with-eventTest/app/router.js +0 -25
  46. package/examples/030-todo-app-with-eventTest/app/store.js +0 -16
  47. package/examples/030-todo-app-with-eventTest/app/views/home.js +0 -11
  48. package/examples/030-todo-app-with-eventTest/app/views/todoDemo.js +0 -88
  49. package/examples/030-todo-app-with-eventTest/index.html +0 -65
  50. package/examples/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  51. package/examples/030-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  52. package/examples/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  53. package/examples/030-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  54. package/examples/030-todo-app-with-eventTest/runtime/core/router.js +0 -271
  55. package/examples/030-todo-app-with-eventTest/store.d.ts +0 -18
  56. package/examples/030-todo-app-with-eventTest/style.css +0 -170
  57. package/examples/030-todo-app-with-eventTest/tests/README.md +0 -208
  58. package/examples/030-todo-app-with-eventTest/tests/eventTest.js +0 -176
  59. package/examples/030-todo-app-with-eventTest/tests/generateTypes.js +0 -189
  60. package/examples/030-todo-app-with-eventTest/tests/run.js +0 -20
  61. package/examples/030-todo-app-with-eventTest/tests/todos.test.js +0 -167
  62. package/examples/031-todo-app-with-eventTest/LICENSE +0 -55
  63. package/examples/031-todo-app-with-eventTest/README.md +0 -54
  64. package/examples/031-todo-app-with-eventTest/TUTORIAL.md +0 -390
  65. package/examples/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  66. package/examples/031-todo-app-with-eventTest/app/bridges.js +0 -113
  67. package/examples/031-todo-app-with-eventTest/app/router.js +0 -26
  68. package/examples/031-todo-app-with-eventTest/app/store.js +0 -15
  69. package/examples/031-todo-app-with-eventTest/app/views/home.js +0 -46
  70. package/examples/031-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  71. package/examples/031-todo-app-with-eventTest/devtools/dock.js +0 -41
  72. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  73. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  74. package/examples/031-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  75. package/examples/031-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  76. package/examples/031-todo-app-with-eventTest/index.html +0 -103
  77. package/examples/031-todo-app-with-eventTest/package-lock.json +0 -2184
  78. package/examples/031-todo-app-with-eventTest/package.json +0 -24
  79. package/examples/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  80. package/examples/031-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  81. package/examples/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  82. package/examples/031-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  83. package/examples/031-todo-app-with-eventTest/runtime/core/router.js +0 -271
  84. package/examples/031-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  85. package/examples/031-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  86. package/examples/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  87. package/examples/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  88. package/examples/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  89. package/examples/031-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  90. package/examples/031-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  91. package/examples/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  92. package/examples/031-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  93. package/examples/031-todo-app-with-eventTest/store.d.ts +0 -23
  94. package/examples/031-todo-app-with-eventTest/style.css +0 -170
  95. package/examples/031-todo-app-with-eventTest/tests/README.md +0 -208
  96. package/examples/031-todo-app-with-eventTest/tests/eventTest.js +0 -176
  97. package/examples/031-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  98. package/examples/031-todo-app-with-eventTest/tests/run.js +0 -20
  99. package/examples/031-todo-app-with-eventTest/tests/todos.test.js +0 -192
  100. package/examples/032-todo-app-with-eventTest/LICENSE +0 -55
  101. package/examples/032-todo-app-with-eventTest/README.md +0 -54
  102. package/examples/032-todo-app-with-eventTest/TUTORIAL.md +0 -390
  103. package/examples/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  104. package/examples/032-todo-app-with-eventTest/app/actions/index.js +0 -153
  105. package/examples/032-todo-app-with-eventTest/app/bridges.js +0 -113
  106. package/examples/032-todo-app-with-eventTest/app/router.js +0 -26
  107. package/examples/032-todo-app-with-eventTest/app/store.js +0 -15
  108. package/examples/032-todo-app-with-eventTest/app/views/home.js +0 -46
  109. package/examples/032-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  110. package/examples/032-todo-app-with-eventTest/devtools/dock.js +0 -41
  111. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  112. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  113. package/examples/032-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  114. package/examples/032-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  115. package/examples/032-todo-app-with-eventTest/index.html +0 -87
  116. package/examples/032-todo-app-with-eventTest/package-lock.json +0 -2184
  117. package/examples/032-todo-app-with-eventTest/package.json +0 -24
  118. package/examples/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  119. package/examples/032-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  120. package/examples/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  121. package/examples/032-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  122. package/examples/032-todo-app-with-eventTest/runtime/core/router.js +0 -271
  123. package/examples/032-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  124. package/examples/032-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  125. package/examples/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  126. package/examples/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  127. package/examples/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  128. package/examples/032-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  129. package/examples/032-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  130. package/examples/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  131. package/examples/032-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  132. package/examples/032-todo-app-with-eventTest/store.d.ts +0 -23
  133. package/examples/032-todo-app-with-eventTest/style.css +0 -170
  134. package/examples/032-todo-app-with-eventTest/tests/README.md +0 -208
  135. package/examples/032-todo-app-with-eventTest/tests/eventTest.js +0 -176
  136. package/examples/032-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  137. package/examples/032-todo-app-with-eventTest/tests/run.js +0 -20
  138. package/examples/032-todo-app-with-eventTest/tests/todos.test.js +0 -192
  139. package/playground/exercise001.html +0 -38
  140. package/playground/exercise002.html +0 -49
  141. package/stateSerializer.js +0 -267
  142. package/templateManager.js +0 -216
@@ -1,42 +0,0 @@
1
- # 005 Conditional Rendering - Show/Hide Elements
2
-
3
- Demonstrates how to conditionally render elements based on state.
4
-
5
- ## What's Here
6
-
7
- - Checkbox to toggle visibility
8
- - Message that shows/hides based on state
9
- - **No `v-if` or `{#if}` needed** - just DOM manipulation
10
-
11
- ## How It Works
12
-
13
- ```javascript
14
- // 1. Store boolean state
15
- const store = createEventState({ isVisible: false });
16
-
17
- // 2. Subscribe and toggle display
18
- store.subscribe('isVisible', (value) => {
19
- const message = document.getElementById('message');
20
- message.style.display = value ? 'block' : 'none';
21
- });
22
-
23
- // 3. Update state on checkbox change
24
- document.getElementById('toggle').onchange = (e) => {
25
- store.set('isVisible', e.target.checked);
26
- };
27
- ```
28
-
29
- ## Key Insight
30
-
31
- **Conditional rendering is just DOM manipulation.**
32
-
33
- Other frameworks need:
34
- - React: `{isVisible && <p>Message</p>}`
35
- - Vue: `v-if="isVisible"`
36
- - Svelte: `{#if isVisible}`
37
-
38
- **EventState:** Just set `style.display` in a subscriber. That's it.
39
-
40
- ## Run It
41
-
42
- Open `index.html` in a browser. Check the box to reveal the message.
@@ -1,39 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>005 Conditional Rendering - EventState</title>
7
- <script type="importmap">
8
- { "imports": { "@uistate/core": "../../index.js" } }
9
- </script>
10
- </head>
11
- <body>
12
- <h1>Conditional Rendering</h1>
13
-
14
- <label>
15
- <input type="checkbox" id="toggle">
16
- Show secret message
17
- </label>
18
-
19
- <p id="message" style="display: none;">You found the secret message!</p>
20
-
21
- <script type="module">
22
- import { createEventState } from '@uistate/core';
23
-
24
- // Create store
25
- const store = createEventState({ isVisible: false });
26
-
27
- // Subscribe to visibility changes
28
- store.subscribe('isVisible', (value) => {
29
- const message = document.getElementById('message');
30
- message.style.display = value ? 'block' : 'none';
31
- });
32
-
33
- // Toggle on checkbox change
34
- document.getElementById('toggle').onchange = (e) => {
35
- store.set('isVisible', e.target.checked);
36
- };
37
- </script>
38
- </body>
39
- </html>
@@ -1,49 +0,0 @@
1
- # 006 List Rendering - Dynamic Arrays
2
-
3
- Demonstrates how to render lists from array state.
4
-
5
- ## What's Here
6
-
7
- - Input to add items
8
- - Dynamic list that updates on state change
9
- - **No `v-for` or `.map()` in JSX** - just `innerHTML`
10
-
11
- ## How It Works
12
-
13
- ```javascript
14
- // 1. Store array in state
15
- const store = createEventState({ items: [] });
16
-
17
- // 2. Render function
18
- const renderList = () => {
19
- const items = store.get('items');
20
- const list = document.getElementById('itemList');
21
- list.innerHTML = items.map(item => `<li>${item}</li>`).join('');
22
- };
23
-
24
- // 3. Subscribe to array changes
25
- store.subscribe('items', renderList);
26
-
27
- // 4. Add items by creating new array
28
- const items = store.get('items');
29
- store.set('items', [...items, newItem]);
30
- ```
31
-
32
- ## Key Insight
33
-
34
- **List rendering is just string concatenation.**
35
-
36
- Other frameworks need:
37
- - React: `{items.map(item => <li>{item}</li>)}`
38
- - Vue: `v-for="item in items"`
39
- - Svelte: `{#each items as item}`
40
-
41
- **EventState:** Just use `.map()` and `innerHTML`. That's it.
42
-
43
- ## Important
44
-
45
- We create a **new array** with `[...items, newItem]` instead of mutating. This ensures the subscription fires (new reference = change detected).
46
-
47
- ## Run It
48
-
49
- Open `index.html` in a browser. Type items and click Add (or press Enter).
@@ -1,63 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>006 List Rendering - EventState</title>
7
- <script type="importmap">
8
- { "imports": { "@uistate/core": "../../index.js" } }
9
- </script>
10
- </head>
11
- <body>
12
- <h1>Shopping List</h1>
13
-
14
- <input type="text" id="itemInput" placeholder="Add item...">
15
- <button id="addBtn">Add</button>
16
-
17
- <ul id="itemList"></ul>
18
-
19
- <script type="module">
20
- import { createEventState } from '@uistate/core';
21
-
22
- // Create store
23
- const store = createEventState({ items: [] });
24
-
25
- // Render list
26
- const renderList = () => {
27
- const items = store.get('items');
28
- const list = document.getElementById('itemList');
29
-
30
- if (items.length === 0) {
31
- list.innerHTML = '<li><em>No items yet</em></li>';
32
- } else {
33
- list.innerHTML = items.map(item => `<li>${item}</li>`).join('');
34
- }
35
- };
36
-
37
- // Subscribe to items changes
38
- store.subscribe('items', renderList);
39
-
40
- // Add item
41
- document.getElementById('addBtn').onclick = () => {
42
- const input = document.getElementById('itemInput');
43
- const value = input.value.trim();
44
-
45
- if (value) {
46
- const items = store.get('items');
47
- store.set('items', [...items, value]);
48
- input.value = '';
49
- }
50
- };
51
-
52
- // Add on Enter key
53
- document.getElementById('itemInput').onkeypress = (e) => {
54
- if (e.key === 'Enter') {
55
- document.getElementById('addBtn').click();
56
- }
57
- };
58
-
59
- // Initial render
60
- renderList();
61
- </script>
62
- </body>
63
- </html>
@@ -1,52 +0,0 @@
1
- # 007 Form Validation - Real-time Feedback
2
-
3
- Demonstrates form validation with multiple state paths and computed validity.
4
-
5
- ## What's Here
6
-
7
- - Email and password inputs
8
- - Real-time validation messages
9
- - Submit button enabled only when form is valid
10
- - **No form library needed** - just state and functions
11
-
12
- ## How It Works
13
-
14
- ```javascript
15
- // 1. Store input values AND validation state
16
- const store = createEventState({
17
- email: '',
18
- password: '',
19
- emailValid: false,
20
- passwordValid: false
21
- });
22
-
23
- // 2. Validate on input change
24
- store.subscribe('email', (value) => {
25
- const isValid = validateEmail(value);
26
- store.set('emailValid', isValid);
27
- // Show error message
28
- });
29
-
30
- // 3. Enable submit when all fields valid
31
- store.subscribe('emailValid', updateSubmitButton);
32
- store.subscribe('passwordValid', updateSubmitButton);
33
- ```
34
-
35
- ## Key Insight
36
-
37
- **Validation is just derived state.**
38
-
39
- - Input values are **source state**
40
- - Validation flags are **derived state**
41
- - Submit button state is **computed from derived state**
42
-
43
- Other frameworks need:
44
- - React: `useForm` hooks, validation libraries
45
- - Vue: `v-model` + validation plugins
46
- - Svelte: Stores + validation actions
47
-
48
- **EventState:** Just subscribe to inputs, validate, and update state. That's it.
49
-
50
- ## Run It
51
-
52
- Open `index.html` in a browser. Type invalid data to see error messages. Submit button enables when both fields are valid.
@@ -1,102 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>007 Form Validation - EventState</title>
7
- <script type="importmap">
8
- { "imports": { "@uistate/core": "../../index.js" } }
9
- </script>
10
- </head>
11
- <body>
12
- <h1>Sign Up Form</h1>
13
-
14
- <label>
15
- Email:
16
- <input type="email" id="email" placeholder="you@example.com">
17
- </label>
18
- <p id="emailError" style="color: red;"></p>
19
-
20
- <label>
21
- Password:
22
- <input type="password" id="password" placeholder="Min 8 characters">
23
- </label>
24
- <p id="passwordError" style="color: red;"></p>
25
-
26
- <button id="submitBtn" disabled>Submit</button>
27
- <p id="submitMessage" style="color: green;"></p>
28
-
29
- <script type="module">
30
- import { createEventState } from '@uistate/core';
31
-
32
- // Create store
33
- const store = createEventState({
34
- email: '',
35
- password: '',
36
- emailValid: false,
37
- passwordValid: false
38
- });
39
-
40
- // Validate email
41
- const validateEmail = (email) => {
42
- return email.includes('@') && email.includes('.');
43
- };
44
-
45
- // Validate password
46
- const validatePassword = (password) => {
47
- return password.length >= 8;
48
- };
49
-
50
- // Update submit button state
51
- const updateSubmitButton = () => {
52
- const emailValid = store.get('emailValid');
53
- const passwordValid = store.get('passwordValid');
54
- document.getElementById('submitBtn').disabled = !(emailValid && passwordValid);
55
- };
56
-
57
- // Subscribe to email changes
58
- store.subscribe('email', (value) => {
59
- const isValid = validateEmail(value);
60
- store.set('emailValid', isValid);
61
-
62
- const error = document.getElementById('emailError');
63
- error.textContent = value && !isValid ? 'Invalid email address' : '';
64
- });
65
-
66
- // Subscribe to password changes
67
- store.subscribe('password', (value) => {
68
- const isValid = validatePassword(value);
69
- store.set('passwordValid', isValid);
70
-
71
- const error = document.getElementById('passwordError');
72
- error.textContent = value && !isValid ? 'Password must be at least 8 characters' : '';
73
- });
74
-
75
- // Subscribe to validation state changes
76
- store.subscribe('emailValid', updateSubmitButton);
77
- store.subscribe('passwordValid', updateSubmitButton);
78
-
79
- // Wire up inputs
80
- document.getElementById('email').oninput = (e) => {
81
- store.set('email', e.target.value);
82
- };
83
-
84
- document.getElementById('password').oninput = (e) => {
85
- store.set('password', e.target.value);
86
- };
87
-
88
- // Handle submit
89
- document.getElementById('submitBtn').onclick = () => {
90
- const email = store.get('email');
91
- const message = document.getElementById('submitMessage');
92
- message.textContent = `✓ Form submitted for ${email}`;
93
-
94
- // Clear form
95
- document.getElementById('email').value = '';
96
- document.getElementById('password').value = '';
97
- store.set('email', '');
98
- store.set('password', '');
99
- };
100
- </script>
101
- </body>
102
- </html>
@@ -1,70 +0,0 @@
1
- # 008 Undo/Redo - Time Travel Debugging
2
-
3
- Demonstrates how EventState's event-driven architecture makes undo/redo trivial.
4
-
5
- ## What's Here
6
-
7
- - Counter with multiple operations
8
- - Undo/Redo buttons
9
- - History tracking showing current position
10
- - **No special library needed** - just an array
11
-
12
- ## How It Works
13
-
14
- ```javascript
15
- // 1. Track history in an array
16
- let history = [0];
17
- let historyIndex = 0;
18
-
19
- // 2. Record state changes via subscription
20
- store.subscribe('count', (value) => {
21
- if (!isTimeTravel) {
22
- history.push(value);
23
- historyIndex = history.length - 1;
24
- }
25
- });
26
-
27
- // 3. Undo = go back in history
28
- document.getElementById('undo').onclick = () => {
29
- historyIndex--;
30
- isTimeTravel = true;
31
- store.set('count', history[historyIndex]);
32
- isTimeTravel = false;
33
- };
34
- ```
35
-
36
- ## Key Insight
37
-
38
- **Time travel is just replaying state.**
39
-
40
- Because EventState uses **events** for all state changes, you can:
41
- - ✅ Record every change automatically (wildcard subscription)
42
- - ✅ Replay any previous state
43
- - ✅ Build undo/redo in ~30 lines
44
-
45
- Other frameworks need:
46
- - React: Redux DevTools, Immer, or custom middleware
47
- - Vue: Vuex plugins or manual history tracking
48
- - Svelte: Custom stores with history logic
49
-
50
- **EventState:** Just subscribe to `'*'` and push to an array. That's it.
51
-
52
- ## This is Telemetry
53
-
54
- This example shows the **foundation of telemetry**:
55
- - Every state change is an event
56
- - Events can be logged, replayed, or analyzed
57
- - Time-travel debugging comes for free
58
-
59
- In the full UIstate framework, this becomes:
60
- - Automatic telemetry logging
61
- - Event sequence testing (`eventTest.js`)
62
- - Production debugging and replay
63
-
64
- ## Run It
65
-
66
- Open `index.html` in a browser. Click operations, then use Undo/Redo to travel through time.
67
-
68
- ## Try This
69
-
70
- Make multiple changes, undo halfway, then make a new change. Notice how the "future" states are discarded (like Git branches).
@@ -1,108 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>008 Undo/Redo - EventState</title>
7
- <script type="importmap">
8
- { "imports": { "@uistate/core": "../../index.js" } }
9
- </script>
10
- </head>
11
- <body>
12
- <h1>Counter with Time Travel</h1>
13
-
14
- <h2>Count: <span id="count">0</span></h2>
15
-
16
- <button id="increment">+1</button>
17
- <button id="decrement">-1</button>
18
- <button id="double">×2</button>
19
-
20
- <hr>
21
-
22
- <button id="undo" disabled>⟲ Undo</button>
23
- <button id="redo" disabled>⟳ Redo</button>
24
-
25
- <p><small>History: <span id="historyInfo">0 states</span></small></p>
26
-
27
- <script type="module">
28
- import { createEventState } from '@uistate/core';
29
-
30
- // Create store
31
- const store = createEventState({ count: 0 });
32
-
33
- // History tracking
34
- let history = [0]; // Start with initial state
35
- let historyIndex = 0;
36
- let isTimeTravel = false; // Flag to prevent recording during undo/redo
37
-
38
- // Update UI
39
- const updateUI = () => {
40
- const count = store.get('count');
41
- document.getElementById('count').textContent = count;
42
-
43
- // Update button states
44
- document.getElementById('undo').disabled = historyIndex === 0;
45
- document.getElementById('redo').disabled = historyIndex === history.length - 1;
46
-
47
- // Update history info
48
- document.getElementById('historyInfo').textContent =
49
- `${historyIndex + 1} of ${history.length} states`;
50
- };
51
-
52
- // Subscribe to count changes and record history
53
- store.subscribe('count', (value) => {
54
- updateUI();
55
-
56
- // Only record if not time traveling
57
- if (!isTimeTravel) {
58
- // Remove any "future" states if we're in the middle of history
59
- history = history.slice(0, historyIndex + 1);
60
-
61
- // Add new state
62
- history.push(value);
63
- historyIndex = history.length - 1;
64
-
65
- updateUI();
66
- }
67
- });
68
-
69
- // Counter operations
70
- document.getElementById('increment').onclick = () => {
71
- store.set('count', store.get('count') + 1);
72
- };
73
-
74
- document.getElementById('decrement').onclick = () => {
75
- store.set('count', store.get('count') - 1);
76
- };
77
-
78
- document.getElementById('double').onclick = () => {
79
- store.set('count', store.get('count') * 2);
80
- };
81
-
82
- // Undo
83
- document.getElementById('undo').onclick = () => {
84
- if (historyIndex > 0) {
85
- historyIndex--;
86
- isTimeTravel = true;
87
- store.set('count', history[historyIndex]);
88
- isTimeTravel = false;
89
- updateUI();
90
- }
91
- };
92
-
93
- // Redo
94
- document.getElementById('redo').onclick = () => {
95
- if (historyIndex < history.length - 1) {
96
- historyIndex++;
97
- isTimeTravel = true;
98
- store.set('count', history[historyIndex]);
99
- isTimeTravel = false;
100
- updateUI();
101
- }
102
- };
103
-
104
- // Initial UI update
105
- updateUI();
106
- </script>
107
- </body>
108
- </html>
@@ -1,72 +0,0 @@
1
- # 009 localStorage Side Effects - Persistent State
2
-
3
- Demonstrates how to sync state with localStorage for persistence across page reloads.
4
-
5
- ## What's Here
6
-
7
- - Counter that persists across browser sessions
8
- - Automatic save on every state change
9
- - Load saved state on page load
10
- - **No persistence library needed** - just localStorage API
11
-
12
- ## How It Works
13
-
14
- ```javascript
15
- // 1. Load initial state from localStorage
16
- const savedCount = localStorage.getItem('counter');
17
- const initialCount = savedCount !== null ? parseInt(savedCount, 10) : 0;
18
-
19
- // 2. Create store with saved state
20
- const store = createEventState({ count: initialCount });
21
-
22
- // 3. Side effect: save on every change
23
- store.subscribe('count', (value) => {
24
- localStorage.setItem('counter', value);
25
- // Also update UI
26
- document.getElementById('count').textContent = value;
27
- });
28
- ```
29
-
30
- ## Key Insight
31
-
32
- **Side effects are just subscribers.**
33
-
34
- Subscriptions aren't only for UI updates. They're for:
35
- - ✅ Saving to localStorage
36
- - ✅ Logging to analytics
37
- - ✅ Syncing to server
38
- - ✅ Triggering other actions
39
-
40
- Other frameworks need:
41
- - React: `useEffect` with dependency arrays
42
- - Vue: `watch` or `watchEffect`
43
- - Svelte: Reactive statements with side effects
44
-
45
- **EventState:** Just subscribe and do whatever you want. That's it.
46
-
47
- ## Pattern: Separation of Concerns
48
-
49
- Notice the subscriber does TWO things:
50
- 1. Saves to localStorage (side effect)
51
- 2. Updates the DOM (UI effect)
52
-
53
- You could split these into separate subscribers:
54
- ```javascript
55
- store.subscribe('count', (value) => {
56
- localStorage.setItem('counter', value);
57
- });
58
-
59
- store.subscribe('count', (value) => {
60
- document.getElementById('count').textContent = value;
61
- });
62
- ```
63
-
64
- This is the **single responsibility principle** in action.
65
-
66
- ## Run It
67
-
68
- Open `index.html`, increment the counter, then reload the page. Your count persists!
69
-
70
- ## Try This
71
-
72
- Open DevTools → Application → Local Storage to see the saved value update in real-time.
@@ -1,57 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>009 localStorage Side Effects - EventState</title>
7
- <script type="importmap">
8
- { "imports": { "@uistate/core": "../../index.js" } }
9
- </script>
10
- </head>
11
- <body>
12
- <h1>Persistent Counter</h1>
13
-
14
- <h2>Count: <span id="count">0</span></h2>
15
-
16
- <button id="increment">+1</button>
17
- <button id="decrement">-1</button>
18
- <button id="reset">Reset</button>
19
-
20
- <p><small>Reload the page - your count persists!</small></p>
21
-
22
- <script type="module">
23
- import { createEventState } from '@uistate/core';
24
-
25
- const STORAGE_KEY = 'eventstate-counter';
26
-
27
- // Load initial state from localStorage
28
- const savedCount = localStorage.getItem(STORAGE_KEY);
29
- const initialCount = savedCount !== null ? parseInt(savedCount, 10) : 0;
30
-
31
- // Create store with saved state
32
- const store = createEventState({ count: initialCount });
33
-
34
- // Side effect: save to localStorage on every change
35
- store.subscribe('count', (value) => {
36
- localStorage.setItem(STORAGE_KEY, value);
37
- document.getElementById('count').textContent = value;
38
- });
39
-
40
- // Operations
41
- document.getElementById('increment').onclick = () => {
42
- store.set('count', store.get('count') + 1);
43
- };
44
-
45
- document.getElementById('decrement').onclick = () => {
46
- store.set('count', store.get('count') - 1);
47
- };
48
-
49
- document.getElementById('reset').onclick = () => {
50
- store.set('count', 0);
51
- };
52
-
53
- // Initial render
54
- document.getElementById('count').textContent = initialCount;
55
- </script>
56
- </body>
57
- </html>