@uistate/core 4.1.2 → 5.0.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 (150) hide show
  1. package/LICENSE-eventTest.md +26 -0
  2. package/README.md +409 -42
  3. package/cssState.js +32 -1
  4. package/eventState.js +58 -49
  5. package/eventTest.js +196 -0
  6. package/examples/001-counter/README.md +44 -0
  7. package/examples/001-counter/eventState.js +86 -0
  8. package/examples/001-counter/index.html +30 -0
  9. package/examples/002-counter-improved/README.md +44 -0
  10. package/examples/002-counter-improved/eventState.js +86 -0
  11. package/examples/002-counter-improved/index.html +44 -0
  12. package/examples/003-input-reactive/README.md +44 -0
  13. package/examples/003-input-reactive/eventState.js +86 -0
  14. package/examples/003-input-reactive/index.html +30 -0
  15. package/examples/004-computed-state/README.md +45 -0
  16. package/examples/004-computed-state/eventState.js +86 -0
  17. package/examples/004-computed-state/index.html +62 -0
  18. package/examples/005-conditional-rendering/README.md +42 -0
  19. package/examples/005-conditional-rendering/eventState.js +86 -0
  20. package/examples/005-conditional-rendering/index.html +36 -0
  21. package/examples/006-list-rendering/README.md +49 -0
  22. package/examples/006-list-rendering/eventState.js +86 -0
  23. package/examples/006-list-rendering/index.html +60 -0
  24. package/examples/007-form-validation/README.md +52 -0
  25. package/examples/007-form-validation/eventState.js +86 -0
  26. package/examples/007-form-validation/index.html +99 -0
  27. package/examples/008-undo-redo/README.md +70 -0
  28. package/examples/008-undo-redo/eventState.js +86 -0
  29. package/examples/008-undo-redo/index.html +105 -0
  30. package/examples/009-localStorage-side-effects/README.md +72 -0
  31. package/examples/009-localStorage-side-effects/eventState.js +86 -0
  32. package/examples/009-localStorage-side-effects/index.html +54 -0
  33. package/examples/010-decoupled-components/README.md +74 -0
  34. package/examples/010-decoupled-components/eventState.js +86 -0
  35. package/examples/010-decoupled-components/index.html +90 -0
  36. package/examples/011-async-patterns/README.md +98 -0
  37. package/examples/011-async-patterns/eventState.js +86 -0
  38. package/examples/011-async-patterns/index.html +129 -0
  39. package/examples/028-counter-improved-eventTest/LICENSE +55 -0
  40. package/examples/028-counter-improved-eventTest/README.md +131 -0
  41. package/examples/028-counter-improved-eventTest/app/store.js +9 -0
  42. package/examples/028-counter-improved-eventTest/index.html +49 -0
  43. package/examples/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +282 -0
  44. package/examples/028-counter-improved-eventTest/runtime/core/eventState.js +100 -0
  45. package/examples/028-counter-improved-eventTest/runtime/core/helpers.js +212 -0
  46. package/examples/028-counter-improved-eventTest/runtime/core/router.js +271 -0
  47. package/examples/028-counter-improved-eventTest/store.d.ts +8 -0
  48. package/examples/028-counter-improved-eventTest/style.css +170 -0
  49. package/examples/028-counter-improved-eventTest/tests/README.md +208 -0
  50. package/examples/028-counter-improved-eventTest/tests/counter.test.js +116 -0
  51. package/examples/028-counter-improved-eventTest/tests/eventTest.js +176 -0
  52. package/examples/028-counter-improved-eventTest/tests/generateTypes.js +168 -0
  53. package/examples/028-counter-improved-eventTest/tests/run.js +20 -0
  54. package/examples/030-todo-app-with-eventTest/LICENSE +55 -0
  55. package/examples/030-todo-app-with-eventTest/README.md +121 -0
  56. package/examples/030-todo-app-with-eventTest/app/router.js +25 -0
  57. package/examples/030-todo-app-with-eventTest/app/store.js +16 -0
  58. package/examples/030-todo-app-with-eventTest/app/views/home.js +11 -0
  59. package/examples/030-todo-app-with-eventTest/app/views/todoDemo.js +88 -0
  60. package/examples/030-todo-app-with-eventTest/index.html +65 -0
  61. package/examples/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  62. package/examples/030-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  63. package/examples/030-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  64. package/examples/030-todo-app-with-eventTest/runtime/core/router.js +271 -0
  65. package/examples/030-todo-app-with-eventTest/store.d.ts +18 -0
  66. package/examples/030-todo-app-with-eventTest/style.css +170 -0
  67. package/examples/030-todo-app-with-eventTest/tests/README.md +208 -0
  68. package/examples/030-todo-app-with-eventTest/tests/eventTest.js +176 -0
  69. package/examples/030-todo-app-with-eventTest/tests/generateTypes.js +189 -0
  70. package/examples/030-todo-app-with-eventTest/tests/run.js +20 -0
  71. package/examples/030-todo-app-with-eventTest/tests/todos.test.js +167 -0
  72. package/examples/031-todo-app-with-eventTest/LICENSE +55 -0
  73. package/examples/031-todo-app-with-eventTest/README.md +54 -0
  74. package/examples/031-todo-app-with-eventTest/TUTORIAL.md +390 -0
  75. package/examples/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
  76. package/examples/031-todo-app-with-eventTest/app/bridges.js +113 -0
  77. package/examples/031-todo-app-with-eventTest/app/router.js +26 -0
  78. package/examples/031-todo-app-with-eventTest/app/store.js +15 -0
  79. package/examples/031-todo-app-with-eventTest/app/views/home.js +46 -0
  80. package/examples/031-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
  81. package/examples/031-todo-app-with-eventTest/devtools/dock.js +41 -0
  82. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
  83. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
  84. package/examples/031-todo-app-with-eventTest/devtools/telemetry.js +104 -0
  85. package/examples/031-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
  86. package/examples/031-todo-app-with-eventTest/index.html +103 -0
  87. package/examples/031-todo-app-with-eventTest/package-lock.json +2184 -0
  88. package/examples/031-todo-app-with-eventTest/package.json +24 -0
  89. package/examples/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  90. package/examples/031-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  91. package/examples/031-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  92. package/examples/031-todo-app-with-eventTest/runtime/core/router.js +271 -0
  93. package/examples/031-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
  94. package/examples/031-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
  95. package/examples/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
  96. package/examples/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
  97. package/examples/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
  98. package/examples/031-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
  99. package/examples/031-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
  100. package/examples/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
  101. package/examples/031-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
  102. package/examples/031-todo-app-with-eventTest/store.d.ts +23 -0
  103. package/examples/031-todo-app-with-eventTest/style.css +170 -0
  104. package/examples/031-todo-app-with-eventTest/tests/README.md +208 -0
  105. package/examples/031-todo-app-with-eventTest/tests/eventTest.js +176 -0
  106. package/examples/031-todo-app-with-eventTest/tests/generateTypes.js +191 -0
  107. package/examples/031-todo-app-with-eventTest/tests/run.js +20 -0
  108. package/examples/031-todo-app-with-eventTest/tests/todos.test.js +192 -0
  109. package/examples/032-todo-app-with-eventTest/LICENSE +55 -0
  110. package/examples/032-todo-app-with-eventTest/README.md +54 -0
  111. package/examples/032-todo-app-with-eventTest/TUTORIAL.md +390 -0
  112. package/examples/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
  113. package/examples/032-todo-app-with-eventTest/app/actions/index.js +153 -0
  114. package/examples/032-todo-app-with-eventTest/app/bridges.js +113 -0
  115. package/examples/032-todo-app-with-eventTest/app/router.js +26 -0
  116. package/examples/032-todo-app-with-eventTest/app/store.js +15 -0
  117. package/examples/032-todo-app-with-eventTest/app/views/home.js +46 -0
  118. package/examples/032-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
  119. package/examples/032-todo-app-with-eventTest/devtools/dock.js +41 -0
  120. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
  121. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
  122. package/examples/032-todo-app-with-eventTest/devtools/telemetry.js +104 -0
  123. package/examples/032-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
  124. package/examples/032-todo-app-with-eventTest/index.html +87 -0
  125. package/examples/032-todo-app-with-eventTest/package-lock.json +2184 -0
  126. package/examples/032-todo-app-with-eventTest/package.json +24 -0
  127. package/examples/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  128. package/examples/032-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  129. package/examples/032-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  130. package/examples/032-todo-app-with-eventTest/runtime/core/router.js +271 -0
  131. package/examples/032-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
  132. package/examples/032-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
  133. package/examples/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
  134. package/examples/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
  135. package/examples/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
  136. package/examples/032-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
  137. package/examples/032-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
  138. package/examples/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
  139. package/examples/032-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
  140. package/examples/032-todo-app-with-eventTest/store.d.ts +23 -0
  141. package/examples/032-todo-app-with-eventTest/style.css +170 -0
  142. package/examples/032-todo-app-with-eventTest/tests/README.md +208 -0
  143. package/examples/032-todo-app-with-eventTest/tests/eventTest.js +176 -0
  144. package/examples/032-todo-app-with-eventTest/tests/generateTypes.js +191 -0
  145. package/examples/032-todo-app-with-eventTest/tests/run.js +20 -0
  146. package/examples/032-todo-app-with-eventTest/tests/todos.test.js +192 -0
  147. package/index.js +14 -3
  148. package/package.json +16 -7
  149. package/stateSerializer.js +99 -4
  150. package/templateManager.js +50 -2
@@ -0,0 +1,26 @@
1
+ # Proprietary License for eventTest.js
2
+
3
+ Copyright (c) 2025 Ajdin Imsirovic
4
+
5
+ ## Permitted Uses
6
+
7
+ You may use `eventTest.js` for:
8
+ - Personal projects
9
+ - Open-source projects
10
+ - Educational purposes
11
+
12
+ ## Restrictions
13
+
14
+ You may NOT:
15
+ - Use this file in commercial products without a license
16
+ - Modify or create derivative works
17
+ - Redistribute this file separately from @uistate/core
18
+ - Remove or alter this license notice
19
+
20
+ ## Commercial Licensing
21
+
22
+ For commercial use, please contact: your@email.com
23
+
24
+ ---
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
package/README.md CHANGED
@@ -1,11 +1,19 @@
1
- # UIstate
1
+ # UIstate v5
2
2
 
3
- A revolutionary approach to UI state management using CSS custom properties and DOM attributes, featuring Attribute-Driven State Inheritance (ADSI).
3
+ **Lightweight event-driven state management for modern web applications**
4
4
 
5
- **Current Version**: 4.1.2
5
+ [![npm version](https://img.shields.io/npm/v/@uistate/core.svg)](https://www.npmjs.com/package/@uistate/core)
6
+ [![License: MIT + Commercial](https://img.shields.io/badge/License-MIT%20%2B%20Commercial-blue.svg)](#license)
6
7
 
7
- **Author**: Ajdin Imsirovic <ajdika@live.com> (GitHub)
8
- **Maintainer**: uistate <ajdika.i@gmail.com> (npm)
8
+ After many experiments exploring different state management paradigms, UIstate v5 emerges as a focused, production-ready solution for complex UIs. Born from real-world challenges like building data tables, dashboards, and interactive applications.
9
+
10
+ ## What's New in v5
11
+
12
+ - **EventState Core**: Path-based subscriptions with wildcard support (~80 LOC)
13
+ - **Slot Orchestration**: Atomic component swapping without layout shift
14
+ - **Intent-Driven**: Declarative command handling for decoupled architecture
15
+ - **Zero Dependencies**: Pure JavaScript, works in any modern browser
16
+ - **Framework-Free**: No build step required (but works great with Vite)
9
17
 
10
18
  ## Installation
11
19
 
@@ -15,48 +23,351 @@ npm install @uistate/core
15
23
 
16
24
  ## Quick Start
17
25
 
18
- UIstate v4.1.2 provides four core modules that can be imported individually:
26
+ ```javascript
27
+ import { createEventState } from '@uistate/core';
28
+
29
+ const store = createEventState({
30
+ user: { name: 'Alice' },
31
+ count: 0
32
+ });
33
+
34
+ // Subscribe to specific paths
35
+ store.subscribe('user.name', (value) => {
36
+ console.log('Name changed:', value);
37
+ });
38
+
39
+ // Subscribe with wildcards
40
+ store.subscribe('user.*', ({ path, value }) => {
41
+ console.log(`${path} changed to:`, value);
42
+ });
43
+
44
+ // Update state
45
+ store.set('user.name', 'Bob'); // → Name changed: Bob
46
+ store.set('count', 1);
47
+ ```
48
+
49
+ ## Core API
50
+
51
+ ### `createEventState(initial?)`
52
+
53
+ Creates a new reactive store.
54
+
55
+ ```javascript
56
+ const store = createEventState({ count: 0 });
57
+ ```
58
+
59
+ ### `store.get(path)`
60
+
61
+ Retrieves a value by path.
62
+
63
+ ```javascript
64
+ const count = store.get('count');
65
+ const user = store.get('user'); // Returns entire user object
66
+ const all = store.get(); // Returns entire store
67
+ ```
68
+
69
+ ### `store.set(path, value)`
70
+
71
+ Sets a value and triggers subscriptions.
19
72
 
20
73
  ```javascript
21
- import { createCssState, createEventState, stateSerializer, createTemplateManager } from '@uistate/core';
74
+ store.set('count', 42);
75
+ store.set('user.email', 'alice@example.com');
76
+ ```
22
77
 
23
- // Create CSS-based state management
24
- const cssState = createCssState();
78
+ ### `store.subscribe(path, handler)`
25
79
 
26
- // Create event-based state management
27
- const eventState = createEventState();
80
+ Subscribes to changes. Returns an unsubscribe function.
28
81
 
29
- // Use state serialization utilities
30
- const serialized = stateSerializer.serialize(myState);
82
+ ```javascript
83
+ // Exact path
84
+ const unsub = store.subscribe('count', (value) => {
85
+ console.log('Count:', value);
86
+ });
31
87
 
32
- // Create template manager for declarative UI
33
- const templateManager = createTemplateManager();
88
+ // Wildcard (child changes)
89
+ store.subscribe('user.*', ({ path, value }) => {
90
+ console.log(`User property ${path} changed`);
91
+ });
92
+
93
+ // Global wildcard (all changes)
94
+ store.subscribe('*', ({ path, value }) => {
95
+ console.log('Something changed');
96
+ });
97
+
98
+ // Cleanup
99
+ unsub();
100
+ ```
101
+
102
+ ### `store.destroy()`
103
+
104
+ Cleans up the event bus and prevents further updates.
105
+
106
+ ```javascript
107
+ store.destroy();
34
108
  ```
35
109
 
36
- ## Core Modules
110
+ # Changelog
37
111
 
38
- ### `createCssState`
39
- Manages state using CSS custom properties for optimal performance and automatic reactivity.
112
+ ## [5.0.0] - 2025-12-09
40
113
 
41
- ### `createEventState`
42
- Provides event-driven state management with pub/sub patterns.
114
+ ### Breaking Changes
115
+ - eventState is now primary export (was cssState)
116
+ - DOM element event bus replaced with EventTarget
117
+ - ...
43
118
 
44
- ### `stateSerializer`
45
- Utilities for serializing and deserializing state data.
119
+ ### Added
120
+ - Slot orchestration pattern
121
+ - Intent-driven architecture
122
+ - Event-sequence testing (experimental)
123
+ - Example 009: Financial dashboard
124
+ - Example 010: Event testing
46
125
 
47
- ### `createTemplateManager`
48
- Declarative template management system for building UIs with CSS-based templates.
126
+ ## Architecture Patterns
49
127
 
50
- ## Key Features
128
+ UIstate v5 introduces battle-tested patterns from many experiments:
51
129
 
52
- - Potentially O(1) state updates
53
- - Significant memory savings compared to virtual DOM approaches
54
- - DOM as the single source of truth
55
- - CSS-driven state derivation
56
- - Framework agnostic
57
- - Tiny bundle size (~30KB uncompressed, ~8-10KB gzipped)
58
- - Zero dependencies
59
- - Modular architecture - import only what you need
130
+ ### 1. **Intent-Driven Updates** (Declarative Commands)
131
+
132
+ Intents are just regular state paths—no special API needed. Components subscribe to intent paths and execute logic when triggered:
133
+
134
+ ```javascript
135
+ // Subscribe to an intent path
136
+ store.subscribe('intent.todo.add', ({ text }) => {
137
+ const items = store.get('todos.items') || [];
138
+ store.set('todos.items', [...items, { id: Date.now(), text, done: false }]);
139
+ });
140
+
141
+ // Trigger intent from UI by setting the path
142
+ button.addEventListener('click', () => {
143
+ store.set('intent.todo.add', { text: input.value });
144
+ });
145
+ ```
146
+
147
+ **Key insight**: Intents are paths, not events. This means:
148
+ - Subscribe once at component mount
149
+ - Multiple components can listen to the same intent
150
+ - Easy to test (just `set()` the intent path)
151
+ - No special event emitter needed
152
+
153
+ ### 2. **Slot Orchestration** (Flicker-free component loading)
154
+
155
+ Load layouts and components separately for atomic, layout-shift-free updates:
156
+
157
+ ```javascript
158
+ // 1. Mount persistent layout once
159
+ async function mountMaster(master) {
160
+ const layout = await fetchHTML(master.layout);
161
+ document.getElementById('app').innerHTML = layout;
162
+ }
163
+
164
+ // 2. Load components into slots atomically
165
+ async function loadSlot({ slot, url }) {
166
+ const config = await fetchJSON(url);
167
+ const html = await fetchHTML(config.component);
168
+
169
+ // Find slot container
170
+ const slotHost = document.querySelector(`[data-slot="${slot}"]`);
171
+
172
+ // Atomic replacement - no layout shift!
173
+ const fragment = createFragment(html);
174
+ slotHost.replaceChildren(fragment);
175
+
176
+ // Wire up component subscriptions
177
+ wireComponent(slotHost, config.bindings);
178
+ }
179
+
180
+ // Trigger slot loading via intent
181
+ store.subscribe('intent.slot.load', loadSlot);
182
+ store.set('intent.slot.load', { slot: 'main', url: '/slots/todo.json' });
183
+ ```
184
+
185
+ **Key insight**: Separate layout (skeleton) from content (slots):
186
+ - Layout provides stable structure
187
+ - Slots swap atomically without reflow
188
+ - Each component manages its own subscriptions
189
+ - Clean separation of concerns
190
+
191
+ ### 3. **Component Bindings** (Path-based reactivity)
192
+
193
+ Components subscribe to specific state paths, not global wildcards:
194
+
195
+ ```javascript
196
+ function wireComponent(root, bindings) {
197
+ const input = root.querySelector('#input');
198
+ const list = root.querySelector('#list');
199
+ const itemsPath = bindings.items || 'domain.items';
200
+
201
+ // Subscribe to specific path for this component
202
+ const unsubscribe = store.subscribe(itemsPath, (items) => {
203
+ list.replaceChildren();
204
+ items.forEach(item => {
205
+ const li = document.createElement('li');
206
+ li.textContent = item.text;
207
+ list.appendChild(li);
208
+ });
209
+ });
210
+
211
+ // Initial render
212
+ const items = store.get(itemsPath);
213
+ if (items) store.set(itemsPath, items);
214
+
215
+ // Return cleanup function
216
+ return unsubscribe;
217
+ }
218
+ ```
219
+
220
+ **Key insight**: Each component subscribes to its own paths:
221
+ - No global `*` wildcards in production code
222
+ - Components are isolated and testable
223
+ - Easy cleanup when unmounting
224
+ - Clear data dependencies
225
+
226
+ ### 4. **Component Lifecycle Pattern**
227
+
228
+ Proper mount/unmount with subscription cleanup:
229
+
230
+ ```javascript
231
+ const componentRegistry = new Map();
232
+
233
+ async function loadSlot({ slot, url }) {
234
+ // Cleanup previous component in this slot
235
+ const existing = componentRegistry.get(slot);
236
+ if (existing?.cleanup) {
237
+ existing.cleanup();
238
+ }
239
+
240
+ // Load and mount new component
241
+ const config = await fetchJSON(url);
242
+ const html = await fetchHTML(config.component);
243
+ const slotHost = document.querySelector(`[data-slot="${slot}"]`);
244
+ slotHost.replaceChildren(createFragment(html));
245
+
246
+ // Wire up with cleanup function
247
+ const cleanup = wireComponent(slotHost, config.bindings);
248
+
249
+ // Store for later cleanup
250
+ componentRegistry.set(slot, { cleanup });
251
+ }
252
+ ```
253
+
254
+ **Key insight**: Always clean up subscriptions:
255
+ - Prevents memory leaks
256
+ - Allows safe component swapping
257
+ - Components are truly hot-swappable
258
+ - No zombie subscriptions
259
+
260
+ ### 5. **Content-Driven Layout** (Layout morphs based on active content)
261
+
262
+ One layout HTML can adapt to different use cases by subscribing to state and toggling CSS classes:
263
+
264
+ ```javascript
265
+ function wireLayout(root, bindings) {
266
+ // ...existing button wiring...
267
+
268
+ // Layout adapts based on which slot is active
269
+ store.subscribe('ui.view.active', (active) => {
270
+ const kpiCol = root.querySelector('[data-slot="kpi"]').closest('.col-12');
271
+ const newsCol = root.querySelector('[data-slot="news"]').closest('.col-12');
272
+ const positionsCol = root.querySelector('[data-slot="positions"]').closest('.col-12');
273
+
274
+ if (active === 'todos') {
275
+ // Full-width editing mode
276
+ kpiCol.classList.add('d-none');
277
+ newsCol.classList.add('d-none');
278
+ positionsCol.classList.remove('col-md-6', 'col-lg-4');
279
+ positionsCol.classList.add('col-md-12', 'col-lg-8');
280
+ } else {
281
+ // Multi-column dashboard mode
282
+ kpiCol.classList.remove('d-none');
283
+ newsCol.classList.remove('d-none');
284
+ positionsCol.classList.remove('col-md-12', 'col-lg-8');
285
+ positionsCol.classList.add('col-md-6', 'col-lg-4');
286
+ }
287
+ });
288
+ }
289
+ ```
290
+
291
+ **Key insight**: Content configures layout, not the other way around:
292
+ - One layout HTML handles all scenarios
293
+ - Panels show/hide based on active content
294
+ - Sections expand/collapse responsively
295
+ - Pure CSS class manipulation - no DOM restructuring
296
+ - Declarative layout rules via state subscription
297
+
298
+ **Real-world applications**:
299
+ ```javascript
300
+ // E-commerce: hide sidebar, expand product grid
301
+ if (active === 'products') hideSlots(['filters', 'cart']); expandSlot('grid');
302
+
303
+ // Admin: hide navigation, expand table
304
+ if (active === 'report') hideSlots(['nav', 'sidebar']); expandSlot('table');
305
+
306
+ // Editor: focus mode - hide everything except content
307
+ if (active === 'editor') hideSlots(['toolbar', 'preview']); expandSlot('content');
308
+ ```
309
+
310
+ This pattern eliminates the "rigid layout" criticism - UIstate layouts are as flexible as any framework, with zero VDOM overhead.
311
+
312
+ ## Complete Example
313
+
314
+ See `examples/009-financial-dashboard` for a real-world application featuring:
315
+
316
+ - Multi-slot dashboard layout
317
+ - TodoList with filtering
318
+ - Financial data tables (KPI, Positions, Debts, Sales, News)
319
+ - Intent-driven architecture
320
+ - Atomic slot swapping
321
+ - State inspector
322
+
323
+ Run the example:
324
+
325
+ ```bash
326
+ cd node_modules/@uistate/core/examples/009-financial-dashboard
327
+ npx serve .
328
+ ```
329
+
330
+ Open http://localhost:3000 and explore the multi-slot dashboard!
331
+
332
+ ## Why UIstate v5?
333
+
334
+ ### vs. React/Vue
335
+
336
+ - ✅ **No VDOM overhead** - Direct DOM manipulation with surgical updates
337
+ - ✅ **No build step required** - Works in browsers directly
338
+ - ✅ **Simpler mental model** - Paths + subscriptions, not components + lifecycle hooks
339
+ - ✅ **Smaller bundle** - ~3KB core vs ~40KB+ frameworks
340
+ - ✅ **Explicit reactivity** - Know exactly what triggers what
341
+ - ❌ No component ecosystem
342
+ - ❌ Manual DOM updates (but that's the point!)
343
+
344
+ ### vs. Alpine.js
345
+
346
+ - ✅ **Better for complex state** - Nested paths, cross-component communication
347
+ - ✅ **First-class SPA patterns** - Slot orchestration, intent-driven updates
348
+ - ✅ **More powerful subscriptions** - Path-based, wildcard support, granular control
349
+ - ✅ **Explicit data flow** - No magic, clear cause and effect
350
+ - ❌ More JavaScript required (Alpine is more declarative HTML)
351
+
352
+ ### vs. Zustand/Jotai
353
+
354
+ - ✅ **Framework-agnostic** - Not tied to React
355
+ - ✅ **Path-based state** - More intuitive for deeply nested data
356
+ - ✅ **Wildcard subscriptions** - Subscribe to entire subtrees when needed
357
+ - ✅ **Simpler API** - Just get/set/subscribe, no atoms or stores
358
+ - ❌ Smaller ecosystem
359
+ - ❌ No React DevTools integration
360
+
361
+ ## Use Cases
362
+
363
+ UIstate v5 excels at:
364
+
365
+ - ✅ **Internal dashboards** - Complex state, multiple data sources
366
+ - ✅ **Admin panels** - CRUD operations, forms, data tables
367
+ - ✅ **Financial applications** - Real-time updates, calculations
368
+ - ✅ **Data-heavy UIs** - Large datasets, filtered views
369
+ - ✅ **Multi-pane interfaces** - Independent but coordinated components
370
+ - ✅ **Progressive enhancement** - Add interactivity to server-rendered HTML
60
371
 
61
372
  ## Browser Support
62
373
 
@@ -65,21 +376,77 @@ Declarative template management system for building UIs with CSS-based templates
65
376
  - Safari 10.1+
66
377
  - Edge 79+
67
378
 
379
+ ## Migration from v4
380
+
381
+ UIstate v5 shifts focus from CSS-driven state to event-driven state. Key changes:
382
+
383
+ 1. **eventState is now primary** - Import from root or `/eventState`
384
+ 2. **cssState still available** - Import from `/cssState`
385
+ 3. **New examples** - See `009-financial-dashboard`
386
+ 4. **Breaking changes** - Major version bump
387
+
68
388
  ## Philosophy
69
389
 
70
- UIstate challenges traditional assumptions in web development by using the DOM as the source of truth for state, leveraging CSS variables and data attributes for state storage, and using the CSS cascade for state inheritance and derivation.
390
+ UIstate challenges traditional assumptions:
391
+
392
+ - **State should be simple** - Paths + subscriptions, not selectors + reducers
393
+ - **Reactivity should be explicit** - Know what updates what
394
+ - **DOM updates can be fast** - Atomic replacements beat VDOM for many use cases
395
+ - **Components should own their subscriptions** - No global wildcards in production
396
+ - **Frameworks aren't always needed** - Pure JS + patterns can go far
397
+ - **Build steps should be optional** - ESM works in browsers
398
+ - **Intents are just paths** - No special event system needed
399
+
400
+ UIstate v5 represents lessons learned from building:
401
+ - Data tables with 1M+ rows
402
+ - Multi-tab synchronized state
403
+ - Workflowy-style nested lists
404
+ - Financial dashboards
405
+ - Admin panels
71
406
 
72
- The v4.1.0 release focuses on simplicity and modularity - providing clean, individual modules that can be composed together as needed, without the complexity of a unified framework.
407
+ The core insight: **Most web UIs need reactive state and component orchestration, not a full framework.**
73
408
 
74
- ## Examples
409
+ ## Testing (Experimental)
75
410
 
76
- Explore our documentation and examples to see UIstate in action:
411
+ UIstate includes `eventTest.js` for event-sequence testing:
77
412
 
78
- - Range sliders with different state derivation approaches
79
- - Button toggles with CSS state projection
80
- - Font adjusters with domain-based state management
81
- - And more!
413
+ **Note:** `eventTest.js` is dual-licensed. Free for personal/OSS use. Commercial use requires a separate license. See LICENSE-eventTest.md for details.
414
+
415
+ ```javascript
416
+ import { createEventTest } from '@uistate/core/eventTest';
417
+
418
+ const test = createEventTest({ count: 0 });
419
+
420
+ test
421
+ .trigger('intent.increment')
422
+ .assertPath('count', 1)
423
+ .assertEventFired('count', 1);
424
+ ```
425
+
426
+ See `examples/010-event-testing` for more.
82
427
 
83
428
  ## License
84
429
 
85
- MIT Ajdin Imsirovic
430
+ **@uistate/core** is licensed under the **MIT License**, with an exception, as decribed next.
431
+
432
+ **Exception:** `eventTest.js` is licensed under a **proprietary license** that permits:
433
+ - ✅ Personal use
434
+ - ✅ Open-source projects
435
+ - ✅ Educational use
436
+
437
+ For **commercial use** of `eventTest.js`, please contact: [ajdika@live.com](mailto:ajdika@live.com)
438
+
439
+ See the file header in `eventTest.js` for full terms.
440
+
441
+ ---
442
+
443
+ Copyright © 2025 Ajdin Imsirovic (ajdika@live.com)
444
+
445
+ - Core library: MIT License
446
+ - eventTest.js: Commercial License (free for personal/OSS)
447
+
448
+ ## Links
449
+
450
+ - [GitHub](https://github.com/ImsirovicAjdin/uistate)
451
+ - [npm](https://www.npmjs.com/package/@uistate/core)
452
+ - [Issues](https://github.com/ImsirovicAjdin/uistate/issues)
package/cssState.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * UIstate - CSS-based state management module with integrated serialization
3
+ * Part of the UIstate declarative state management system
4
+ * Uses CSS custom properties and data attributes for state representation
5
+ * Features modular extension capabilities for DOM binding and events
6
+ */
1
7
  import StateSerializer from './stateSerializer.js';
2
8
 
3
9
  const createCssState = (initialState = {}, serializer = StateSerializer) => {
@@ -6,7 +12,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
6
12
  _observers: new Map(),
7
13
  _serializer: serializer,
8
14
  _specialHandlers: {},
9
- _eventHandlers: new Map(),
15
+ _eventHandlers: new Map(), // Store custom event binding handlers
10
16
 
11
17
  init(serializerConfig) {
12
18
  if (!this._sheet) {
@@ -16,10 +22,12 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
16
22
  this._addRule(':root {}');
17
23
  }
18
24
 
25
+ // Configure serializer if options provided
19
26
  if (serializerConfig && typeof serializerConfig === 'object') {
20
27
  this._serializer.configure(serializerConfig);
21
28
  }
22
29
 
30
+ // Initialize with any provided state
23
31
  if (initialState && typeof initialState === 'object') {
24
32
  Object.entries(initialState).forEach(([key, value]) => {
25
33
  this.setState(key, value);
@@ -30,11 +38,14 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
30
38
  },
31
39
 
32
40
  setState(key, value) {
41
+ // Use serializer for CSS variables
33
42
  const cssValue = this._serializer.serialize(key, value);
34
43
  document.documentElement.style.setProperty(`--${key}`, cssValue);
35
44
 
45
+ // Use serializer to handle all attribute application consistently
36
46
  this._serializer.applyToAttributes(key, value);
37
47
 
48
+ // Notify any registered observers of the state change
38
49
  this._notifyObservers(key, value);
39
50
  return value;
40
51
  },
@@ -50,6 +61,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
50
61
  const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
51
62
  if (!value) return '';
52
63
 
64
+ // Use serializer for deserialization
53
65
  return this._serializer.deserialize(key, value);
54
66
  },
55
67
 
@@ -78,6 +90,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
78
90
  return this;
79
91
  },
80
92
 
93
+ // New method for registering event bindings
81
94
  registerEventBinding(eventType, handler) {
82
95
  this._eventHandlers.set(eventType, handler);
83
96
  return this;
@@ -88,21 +101,27 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
88
101
  const stateKey = el.dataset.observe;
89
102
 
90
103
  this.observe(stateKey, (value) => {
104
+ // Special handlers should run first to set data-state
91
105
  if (this._specialHandlers[stateKey]?.observe) {
92
106
  this._specialHandlers[stateKey].observe(value, el);
93
107
  } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
108
+ // Only update data-state for elements that already have this attribute
94
109
  el.dataset.state = value;
95
110
  } else {
111
+ // For normal state observers like theme, counter, etc.
96
112
  el.textContent = value;
97
113
  }
98
114
  });
99
115
 
116
+ // Trigger initial state
100
117
  const initialValue = this.getState(stateKey);
101
118
  if (this._specialHandlers[stateKey]?.observe) {
102
119
  this._specialHandlers[stateKey].observe(initialValue, el);
103
120
  } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
121
+ // Only set data-state for elements that should have this attribute
104
122
  el.dataset.state = initialValue;
105
123
  } else {
124
+ // For normal elements
106
125
  el.textContent = initialValue;
107
126
  }
108
127
 
@@ -112,6 +131,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
112
131
  return this;
113
132
  },
114
133
 
134
+ // Default event handlers available for implementations to use
115
135
  defaultClickHandler(e) {
116
136
  const target = e.target.closest('[data-state-action]');
117
137
  if (!target) return;
@@ -119,11 +139,13 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
119
139
  const stateAction = target.dataset.stateAction;
120
140
  if (!stateAction) return;
121
141
 
142
+ // Special handlers get first priority
122
143
  if (this._specialHandlers[stateAction]?.action) {
123
144
  this._specialHandlers[stateAction].action(target);
124
145
  return;
125
146
  }
126
147
 
148
+ // Handle direct value setting via data-state-value
127
149
  if (target.dataset.stateValue !== undefined) {
128
150
  const valueToSet = target.dataset.stateValue;
129
151
  this.setState(stateAction, valueToSet);
@@ -136,16 +158,20 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
136
158
 
137
159
  if (!stateAction) return;
138
160
 
161
+ // Special handlers should access any needed data directly from the target
139
162
  if (this._specialHandlers[stateAction]?.action) {
140
163
  this._specialHandlers[stateAction].action(target);
141
164
  }
142
165
  },
143
166
 
167
+ // Updated setupStateActions to use registered event handlers
144
168
  setupStateActions(container = document) {
169
+ // Only bind the registered event types
145
170
  this._eventHandlers.forEach((handler, eventType) => {
146
171
  container.addEventListener(eventType, handler);
147
172
  });
148
173
 
174
+ // If no event handlers registered, register the default ones
149
175
  if (this._eventHandlers.size === 0) {
150
176
  container.addEventListener('click', (e) => this.defaultClickHandler(e));
151
177
  container.addEventListener('input', (e) => this.defaultInputHandler(e));
@@ -160,6 +186,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
160
186
  }
161
187
  },
162
188
 
189
+ // Add serializer configuration method
163
190
  configureSerializer(config) {
164
191
  if (this._serializer.configure) {
165
192
  this._serializer.configure(config);
@@ -167,14 +194,18 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
167
194
  return this;
168
195
  },
169
196
 
197
+ // Clean up resources
170
198
  destroy() {
171
199
  this._observers.clear();
200
+ // The style element will remain in the DOM
201
+ // as removing it would affect the UI state
172
202
  }
173
203
  };
174
204
 
175
205
  return state.init();
176
206
  };
177
207
 
208
+ // Create a singleton instance for easy usage
178
209
  const UIstate = createCssState();
179
210
 
180
211
  export { createCssState };