@uistate/core 2.0.1 → 3.1.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.
package/README.md CHANGED
@@ -1,72 +1,35 @@
1
- # @uistate/core
1
+ # @uistate/core v3.0.0
2
2
 
3
3
  **author**: Ajdin Imsirovic <ajdika@live.com> (GitHub)
4
4
  **maintainer**: uistate <ajdika.i@gmail.com> (npm)
5
5
 
6
- High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance). Focused heavily on DX and performance.
6
+ High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance). Focused heavily on DX and performance with a fully declarative approach.
7
+
8
+ ## What's New in v3.0.0
9
+
10
+ - 🔄 Fully declarative state management approach
11
+ - 🧩 Enhanced template system with CSS-based templates
12
+ - 🚀 Improved performance through optimized state propagation
13
+ - 📦 New state serialization and inspection capabilities
14
+ - 🔍 Telemetry plugin for better debugging
7
15
 
8
16
  ## Features
9
17
 
10
- - 🚀 Potentially O(1) state updates using CSS custom properties
18
+ - 🚀 O(1) state updates using CSS custom properties
11
19
  - 📉 Significant memory savings compared to virtual DOM approaches
12
20
  - 🎯 Zero configuration
13
21
  - 🔄 Automatic reactivity through CSS cascade
14
22
  - 🎨 Framework agnostic
15
23
  - 📦 Tiny bundle size (~2KB)
16
- - 🧩 Modular architecture with dedicated modules for CSS state and events
24
+ - 🧩 Modular architecture with dedicated modules for CSS state and templates
25
+ - 📝 Declarative HTML-in-CSS templates
17
26
 
18
27
  ## Installation
19
28
 
20
29
  ```bash
21
- # Install the core package
22
30
  npm install @uistate/core
23
31
  ```
24
32
 
25
- ## Quick Start
26
-
27
- ```javascript
28
- import { cssState, eventState } from '@uistate/core';
29
-
30
- // Initialize state
31
- cssState.init();
32
-
33
- // Set state via CSS variables
34
- cssState.set('--counter-value', '0');
35
-
36
- // Get state
37
- const count = parseInt(cssState.get('--counter-value'));
38
-
39
- // Subscribe to changes
40
- const unsubscribe = eventState.on('counter:change', (newValue) => {
41
- console.log('Counter changed:', newValue);
42
- });
43
-
44
- // Set state with an attribute
45
- document.documentElement.dataset.counterValue = count + 1;
46
- ```
47
-
48
- ### HTML Usage Example
49
-
50
- ```html
51
- <button data-counter-value="0" id="counter-btn">Count: 0</button>
52
- ```
53
-
54
- ```css
55
- [data-counter-value] {
56
- /* Style based on state */
57
- }
58
- ```
59
-
60
- ```javascript
61
- document.getElementById('counter-btn').addEventListener('click', () => {
62
- const btn = document.getElementById('counter-btn');
63
- const currentValue = parseInt(btn.dataset.counterValue);
64
- btn.dataset.counterValue = currentValue + 1;
65
- btn.textContent = `Count: ${currentValue + 1}`;
66
- eventState.emit('counter:change', currentValue + 1);
67
- });
68
- ```
69
-
70
33
  ## Why @uistate/core?
71
34
 
72
35
  ### Performance
@@ -77,7 +40,7 @@ document.getElementById('counter-btn').addEventListener('click', () => {
77
40
 
78
41
  ### Developer Experience
79
42
 
80
- - **Simple API**: Modular `cssState` and `eventState` for clear separation of concerns
43
+ - **Declarative API**: Define UI structure in CSS templates
81
44
  - **Framework Agnostic**: Works with any framework or vanilla JavaScript
82
45
  - **Zero Config**: No store setup, no reducers, no actions
83
46
  - **CSS-Native**: Leverages the power of CSS selectors and the cascade
@@ -85,21 +48,21 @@ document.getElementById('counter-btn').addEventListener('click', () => {
85
48
  ### Core Concepts
86
49
 
87
50
  - **Attribute-Driven State Inheritance (ADSI)**: State represented both as CSS variables and data attributes
88
- - **Hierarchical State Machines**: Model complex UI states with nested state machines
89
- - **CSS-Driven State Derivation**: Derive complex states using CSS without JavaScript
51
+ - **Declarative Templates**: Define UI components directly in CSS
52
+ - **Automatic State Propagation**: State changes automatically update the UI
90
53
 
91
54
  ## Project Structure
92
55
 
93
56
  ```
94
57
  @uistate/core/
95
- ├── src/ # Core library
96
- │ ├── index.js # Main entry
97
- │ ├── cssState.js # CSS variables management
98
- │ ├── eventState.js # Event-based state transitions
99
- └── templateManager.js # Component management
100
- └── examples/ # Example applications
101
- ├── basic/ # Simple examples (range sliders, toggles, etc)
102
- └── advanced/ # Advanced patterns and techniques
58
+ ├── src/ # Core library
59
+ │ ├── index.js # Main entry
60
+ │ ├── cssState.js # CSS variables management
61
+ │ ├── templateManager.js # Declarative template management
62
+ ├── stateInspector.js # State inspection tools
63
+ └── stateSerializer.js # State serialization
64
+ └── examples/ # Example applications
65
+ └── 001-slider-and-cards/ # Advanced slider example
103
66
  ```
104
67
 
105
68
  ## Browser Support
@@ -111,63 +74,36 @@ document.getElementById('counter-btn').addEventListener('click', () => {
111
74
 
112
75
  # Core Ideas Behind UIstate
113
76
 
114
- UIstate is a JavaScript-based UI state management system that leverages CSS custom properties and data attributes as the storage mechanism, paired with event-based state transitions.
77
+ UIstate is a JavaScript-based UI state management system that leverages CSS custom properties and HTML-in-CSS templates for a fully declarative approach to building UIs.
115
78
 
116
79
  ## Key Components
117
80
 
118
- ### cssState
119
-
120
- The `cssState` module provides methods to manage state through CSS custom properties:
121
-
122
- ```javascript
123
- // Initialize CSS state management
124
- cssState.init();
125
-
126
- // Set a CSS custom property
127
- cssState.set('--theme-mode', 'dark');
128
-
129
- // Get a CSS custom property value
130
- const theme = cssState.get('--theme-mode');
131
- ```
132
-
133
- ### eventState
81
+ ### UIstate Core
134
82
 
135
- The `eventState` module provides an event system for state transitions:
83
+ The main UIstate object provides methods to manage state and templates:
136
84
 
137
- ```javascript
138
- // Listen for state changes
139
- eventState.on('theme:change', (newTheme) => {
140
- console.log('Theme changed to:', newTheme);
141
- });
85
+ - **init()**: Initialize the UIstate system
86
+ - **setState()**: Set state values
87
+ - **getState()**: Get state values
88
+ - **subscribe()**: Subscribe to state changes
89
+ - **observe()**: Observe state paths for changes
142
90
 
143
- // Trigger state changes
144
- eventState.emit('theme:change', 'light');
145
-
146
- // Clean up listeners
147
- eventState.off('theme:change');
148
- ```
91
+ ### Template Manager
149
92
 
150
- ### templateManager
93
+ The template manager provides tools for declarative UI rendering:
151
94
 
152
- The `templateManager` module helps with component initialization and templating:
153
-
154
- ```javascript
155
- // Initialize components from templates
156
- templateManager.init();
157
-
158
- // Create a component from a template
159
- const button = templateManager.createFromTemplate('button-template');
160
- document.body.appendChild(button);
161
- ```
95
+ - **renderTemplateFromCss()**: Render UI components from CSS-defined templates
96
+ - **registerActions()**: Register event handlers for UI components
97
+ - **attachDelegation()**: Set up event delegation for efficient event handling
162
98
 
163
99
  ## Key Features
164
100
 
165
101
  1. Uses CSS custom properties as a storage mechanism, making state changes automatically trigger UI updates
166
102
  2. Provides a clear separation between state storage (CSS) and behavior (JavaScript)
167
103
  3. Implements a pub/sub pattern for reactive updates
168
- 4. Leverages the CSS cascade for hierarchical state inheritance
104
+ 4. Leverages CSS templates for declarative UI definition
169
105
 
170
- This implementation is particularly useful for building UI components with clean separation of concerns and optimal performance.
106
+ This implementation is particularly useful for building UI components with clean separation of concerns, optimal performance, and a fully declarative approach.
171
107
 
172
108
  ## Contributing
173
109
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uistate/core",
3
- "version": "2.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance)",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
package/src/cssState.js CHANGED
@@ -1,48 +1,70 @@
1
1
  /**
2
- * UIstate - CSS-based state management module
2
+ * UIstate - CSS-based state management module with integrated serialization
3
3
  * Part of the UIstate declarative state management system
4
4
  * Uses CSS custom properties and data attributes for state representation
5
+ * Features modular extension capabilities for DOM binding and events
5
6
  */
6
- const createCssState = (initialState = {}) => {
7
+ import StateSerializer from './stateSerializer.js';
8
+
9
+ const createCssState = (initialState = {}, serializer = StateSerializer) => {
7
10
  const state = {
8
11
  _sheet: null,
9
12
  _observers: new Map(),
10
-
11
- init() {
13
+ _serializer: serializer,
14
+ _specialHandlers: {},
15
+ _eventHandlers: new Map(), // Store custom event binding handlers
16
+
17
+ init(serializerConfig) {
12
18
  if (!this._sheet) {
13
19
  const style = document.createElement('style');
14
20
  document.head.appendChild(style);
15
21
  this._sheet = style.sheet;
16
22
  this._addRule(':root {}');
17
23
  }
18
-
24
+
25
+ // Configure serializer if options provided
26
+ if (serializerConfig && typeof serializerConfig === 'object') {
27
+ this._serializer.configure(serializerConfig);
28
+ }
29
+
19
30
  // Initialize with any provided state
20
31
  if (initialState && typeof initialState === 'object') {
21
32
  Object.entries(initialState).forEach(([key, value]) => {
22
33
  this.setState(key, value);
23
34
  });
24
35
  }
25
-
36
+
26
37
  return this;
27
38
  },
28
-
39
+
29
40
  setState(key, value) {
30
- const cssValue = typeof value === 'string' ? value : JSON.stringify(value);
41
+ // Use serializer for CSS variables
42
+ const cssValue = this._serializer.serialize(key, value);
31
43
  document.documentElement.style.setProperty(`--${key}`, cssValue);
32
- document.documentElement.setAttribute(`data-${key}`, value);
44
+
45
+ // Use serializer to handle all attribute application consistently
46
+ this._serializer.applyToAttributes(key, value);
47
+
48
+ // Notify any registered observers of the state change
33
49
  this._notifyObservers(key, value);
34
50
  return value;
35
51
  },
36
-
52
+
53
+ setStates(stateObject) {
54
+ Object.entries(stateObject).forEach(([key, value]) => {
55
+ this.setState(key, value);
56
+ });
57
+ return this;
58
+ },
59
+
37
60
  getState(key) {
38
61
  const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
39
- try {
40
- return JSON.parse(value);
41
- } catch (e) {
42
- return value;
43
- }
62
+ if (!value) return '';
63
+
64
+ // Use serializer for deserialization
65
+ return this._serializer.deserialize(key, value);
44
66
  },
45
-
67
+
46
68
  observe(key, callback) {
47
69
  if (!this._observers.has(key)) {
48
70
  this._observers.set(key, new Set());
@@ -55,20 +77,123 @@ const createCssState = (initialState = {}) => {
55
77
  }
56
78
  };
57
79
  },
58
-
80
+
59
81
  _notifyObservers(key, value) {
60
82
  const observers = this._observers.get(key);
61
83
  if (observers) {
62
84
  observers.forEach(cb => cb(value));
63
85
  }
64
86
  },
65
-
87
+
88
+ registerSpecialHandler(stateKey, handlerFn) {
89
+ this._specialHandlers[stateKey] = handlerFn;
90
+ return this;
91
+ },
92
+
93
+ // New method for registering event bindings
94
+ registerEventBinding(eventType, handler) {
95
+ this._eventHandlers.set(eventType, handler);
96
+ return this;
97
+ },
98
+
99
+ setupObservers(container = document) {
100
+ container.querySelectorAll('[data-observe]:not([data-observing])').forEach(el => {
101
+ const stateKey = el.dataset.observe;
102
+
103
+ this.observe(stateKey, (value) => {
104
+ // Special handlers should run first to set data-state
105
+ if (this._specialHandlers[stateKey]?.observe) {
106
+ this._specialHandlers[stateKey].observe(value, el);
107
+ } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
108
+ // Only update data-state for elements that already have this attribute
109
+ el.dataset.state = value;
110
+ } else {
111
+ // For normal state observers like theme, counter, etc.
112
+ el.textContent = value;
113
+ }
114
+ });
115
+
116
+ // Trigger initial state
117
+ const initialValue = this.getState(stateKey);
118
+ if (this._specialHandlers[stateKey]?.observe) {
119
+ this._specialHandlers[stateKey].observe(initialValue, el);
120
+ } else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
121
+ // Only set data-state for elements that should have this attribute
122
+ el.dataset.state = initialValue;
123
+ } else {
124
+ // For normal elements
125
+ el.textContent = initialValue;
126
+ }
127
+
128
+ el.dataset.observing = 'true';
129
+ });
130
+
131
+ return this;
132
+ },
133
+
134
+ // Default event handlers available for implementations to use
135
+ defaultClickHandler(e) {
136
+ const target = e.target.closest('[data-state-action]');
137
+ if (!target) return;
138
+
139
+ const stateAction = target.dataset.stateAction;
140
+ if (!stateAction) return;
141
+
142
+ // Special handlers get first priority
143
+ if (this._specialHandlers[stateAction]?.action) {
144
+ this._specialHandlers[stateAction].action(target);
145
+ return;
146
+ }
147
+
148
+ // Handle direct value setting via data-state-value
149
+ if (target.dataset.stateValue !== undefined) {
150
+ const valueToSet = target.dataset.stateValue;
151
+ this.setState(stateAction, valueToSet);
152
+ }
153
+ },
154
+
155
+ defaultInputHandler(e) {
156
+ const target = e.target;
157
+ const stateAction = target.dataset.stateAction;
158
+
159
+ if (!stateAction) return;
160
+
161
+ // Special handlers should access any needed data directly from the target
162
+ if (this._specialHandlers[stateAction]?.action) {
163
+ this._specialHandlers[stateAction].action(target);
164
+ }
165
+ },
166
+
167
+ // Updated setupStateActions to use registered event handlers
168
+ setupStateActions(container = document) {
169
+ // Only bind the registered event types
170
+ this._eventHandlers.forEach((handler, eventType) => {
171
+ container.addEventListener(eventType, handler);
172
+ });
173
+
174
+ // If no event handlers registered, register the default ones
175
+ if (this._eventHandlers.size === 0) {
176
+ container.addEventListener('click', (e) => this.defaultClickHandler(e));
177
+ container.addEventListener('input', (e) => this.defaultInputHandler(e));
178
+ }
179
+
180
+ return this;
181
+ },
182
+
66
183
  _addRule(rule) {
67
184
  if (this._sheet) {
68
185
  this._sheet.insertRule(rule, this._sheet.cssRules.length);
69
186
  }
70
187
  },
71
-
188
+
189
+ // Add serializer configuration method
190
+ configureSerializer(config) {
191
+ if (this._serializer.configure) {
192
+ this._serializer.configure(config);
193
+ }
194
+ return this;
195
+ },
196
+
72
197
  // Clean up resources
73
198
  destroy() {
74
199
  this._observers.clear();
@@ -76,12 +201,12 @@ const createCssState = (initialState = {}) => {
76
201
  // as removing it would affect the UI state
77
202
  }
78
203
  };
79
-
204
+
80
205
  return state.init();
81
206
  };
82
207
 
83
- // Legacy singleton for backward compatibility
84
- const CssState = createCssState();
208
+ // Create a singleton instance for easy usage
209
+ const UIstate = createCssState();
85
210
 
86
- export default createCssState;
87
- export { createCssState, CssState };
211
+ export { createCssState };
212
+ export default UIstate;
package/src/eventState.js CHANGED
@@ -23,12 +23,12 @@ const createEventState = (initial = {}) => {
23
23
  return path
24
24
  .split(".")
25
25
  .reduce(
26
- (obj, prop) =>
26
+ (obj, prop) =>
27
27
  obj && obj[prop] !== undefined ? obj[prop] : undefined,
28
28
  store
29
29
  );
30
30
  },
31
-
31
+
32
32
  // set a value in the store by path
33
33
  set: (path, value) => {
34
34
  if(!path) return;
@@ -84,10 +84,10 @@ const createEventState = (initial = {}) => {
84
84
 
85
85
  return () => bus.removeEventListener(path, handler);
86
86
  },
87
-
87
+
88
88
  // Optional method to clean up resources
89
89
  destroy: () => {
90
- if (bus.parentNode) {
90
+ if (bus.parentNode) {
91
91
  bus.parentNode.removeChild(bus);
92
92
  }
93
93
  },