@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 +39 -103
- package/package.json +1 -1
- package/src/cssState.js +149 -24
- package/src/eventState.js +4 -4
- package/src/index.js +301 -59
- package/src/stateInspector.js +140 -0
- package/src/stateSerializer.js +267 -0
- package/src/telemetryPlugin.js +638 -0
- package/src/templateManager.js +119 -0
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
|
-
- 🚀
|
|
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
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
89
|
-
- **
|
|
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/
|
|
96
|
-
│ ├── index.js
|
|
97
|
-
│ ├── cssState.js
|
|
98
|
-
│ ├──
|
|
99
|
-
│
|
|
100
|
-
└──
|
|
101
|
-
|
|
102
|
-
└──
|
|
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
|
|
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
|
-
###
|
|
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
|
|
83
|
+
The main UIstate object provides methods to manage state and templates:
|
|
136
84
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
eventState.emit('theme:change', 'light');
|
|
145
|
-
|
|
146
|
-
// Clean up listeners
|
|
147
|
-
eventState.off('theme:change');
|
|
148
|
-
```
|
|
91
|
+
### Template Manager
|
|
149
92
|
|
|
150
|
-
|
|
93
|
+
The template manager provides tools for declarative UI rendering:
|
|
151
94
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
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
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
+
// Use serializer for CSS variables
|
|
42
|
+
const cssValue = this._serializer.serialize(key, value);
|
|
31
43
|
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
32
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
84
|
-
const
|
|
208
|
+
// Create a singleton instance for easy usage
|
|
209
|
+
const UIstate = createCssState();
|
|
85
210
|
|
|
86
|
-
export
|
|
87
|
-
export
|
|
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
|
},
|