@uistate/core 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cssState.js +1 -32
- package/eventState.js +0 -20
- package/index.js +0 -12
- package/package.json +1 -1
- package/stateSerializer.js +4 -99
- package/templateManager.js +2 -50
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A revolutionary approach to UI state management using CSS custom properties and DOM attributes, featuring Attribute-Driven State Inheritance (ADSI).
|
|
4
4
|
|
|
5
|
-
**Current Version**: 4.1.
|
|
5
|
+
**Current Version**: 4.1.2
|
|
6
6
|
|
|
7
7
|
**Author**: Ajdin Imsirovic <ajdika@live.com> (GitHub)
|
|
8
8
|
**Maintainer**: uistate <ajdika.i@gmail.com> (npm)
|
|
@@ -15,7 +15,7 @@ npm install @uistate/core
|
|
|
15
15
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
|
-
UIstate v4.1.
|
|
18
|
+
UIstate v4.1.2 provides four core modules that can be imported individually:
|
|
19
19
|
|
|
20
20
|
```javascript
|
|
21
21
|
import { createCssState, createEventState, stateSerializer, createTemplateManager } from '@uistate/core';
|
package/cssState.js
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
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
|
-
*/
|
|
7
1
|
import StateSerializer from './stateSerializer.js';
|
|
8
2
|
|
|
9
3
|
const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
@@ -12,7 +6,7 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
12
6
|
_observers: new Map(),
|
|
13
7
|
_serializer: serializer,
|
|
14
8
|
_specialHandlers: {},
|
|
15
|
-
_eventHandlers: new Map(),
|
|
9
|
+
_eventHandlers: new Map(),
|
|
16
10
|
|
|
17
11
|
init(serializerConfig) {
|
|
18
12
|
if (!this._sheet) {
|
|
@@ -22,12 +16,10 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
22
16
|
this._addRule(':root {}');
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
// Configure serializer if options provided
|
|
26
19
|
if (serializerConfig && typeof serializerConfig === 'object') {
|
|
27
20
|
this._serializer.configure(serializerConfig);
|
|
28
21
|
}
|
|
29
22
|
|
|
30
|
-
// Initialize with any provided state
|
|
31
23
|
if (initialState && typeof initialState === 'object') {
|
|
32
24
|
Object.entries(initialState).forEach(([key, value]) => {
|
|
33
25
|
this.setState(key, value);
|
|
@@ -38,14 +30,11 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
38
30
|
},
|
|
39
31
|
|
|
40
32
|
setState(key, value) {
|
|
41
|
-
// Use serializer for CSS variables
|
|
42
33
|
const cssValue = this._serializer.serialize(key, value);
|
|
43
34
|
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
44
35
|
|
|
45
|
-
// Use serializer to handle all attribute application consistently
|
|
46
36
|
this._serializer.applyToAttributes(key, value);
|
|
47
37
|
|
|
48
|
-
// Notify any registered observers of the state change
|
|
49
38
|
this._notifyObservers(key, value);
|
|
50
39
|
return value;
|
|
51
40
|
},
|
|
@@ -61,7 +50,6 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
61
50
|
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
|
|
62
51
|
if (!value) return '';
|
|
63
52
|
|
|
64
|
-
// Use serializer for deserialization
|
|
65
53
|
return this._serializer.deserialize(key, value);
|
|
66
54
|
},
|
|
67
55
|
|
|
@@ -90,7 +78,6 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
90
78
|
return this;
|
|
91
79
|
},
|
|
92
80
|
|
|
93
|
-
// New method for registering event bindings
|
|
94
81
|
registerEventBinding(eventType, handler) {
|
|
95
82
|
this._eventHandlers.set(eventType, handler);
|
|
96
83
|
return this;
|
|
@@ -101,27 +88,21 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
101
88
|
const stateKey = el.dataset.observe;
|
|
102
89
|
|
|
103
90
|
this.observe(stateKey, (value) => {
|
|
104
|
-
// Special handlers should run first to set data-state
|
|
105
91
|
if (this._specialHandlers[stateKey]?.observe) {
|
|
106
92
|
this._specialHandlers[stateKey].observe(value, el);
|
|
107
93
|
} else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
|
|
108
|
-
// Only update data-state for elements that already have this attribute
|
|
109
94
|
el.dataset.state = value;
|
|
110
95
|
} else {
|
|
111
|
-
// For normal state observers like theme, counter, etc.
|
|
112
96
|
el.textContent = value;
|
|
113
97
|
}
|
|
114
98
|
});
|
|
115
99
|
|
|
116
|
-
// Trigger initial state
|
|
117
100
|
const initialValue = this.getState(stateKey);
|
|
118
101
|
if (this._specialHandlers[stateKey]?.observe) {
|
|
119
102
|
this._specialHandlers[stateKey].observe(initialValue, el);
|
|
120
103
|
} else if (stateKey.endsWith('-state') && el.hasAttribute('data-state')) {
|
|
121
|
-
// Only set data-state for elements that should have this attribute
|
|
122
104
|
el.dataset.state = initialValue;
|
|
123
105
|
} else {
|
|
124
|
-
// For normal elements
|
|
125
106
|
el.textContent = initialValue;
|
|
126
107
|
}
|
|
127
108
|
|
|
@@ -131,7 +112,6 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
131
112
|
return this;
|
|
132
113
|
},
|
|
133
114
|
|
|
134
|
-
// Default event handlers available for implementations to use
|
|
135
115
|
defaultClickHandler(e) {
|
|
136
116
|
const target = e.target.closest('[data-state-action]');
|
|
137
117
|
if (!target) return;
|
|
@@ -139,13 +119,11 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
139
119
|
const stateAction = target.dataset.stateAction;
|
|
140
120
|
if (!stateAction) return;
|
|
141
121
|
|
|
142
|
-
// Special handlers get first priority
|
|
143
122
|
if (this._specialHandlers[stateAction]?.action) {
|
|
144
123
|
this._specialHandlers[stateAction].action(target);
|
|
145
124
|
return;
|
|
146
125
|
}
|
|
147
126
|
|
|
148
|
-
// Handle direct value setting via data-state-value
|
|
149
127
|
if (target.dataset.stateValue !== undefined) {
|
|
150
128
|
const valueToSet = target.dataset.stateValue;
|
|
151
129
|
this.setState(stateAction, valueToSet);
|
|
@@ -158,20 +136,16 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
158
136
|
|
|
159
137
|
if (!stateAction) return;
|
|
160
138
|
|
|
161
|
-
// Special handlers should access any needed data directly from the target
|
|
162
139
|
if (this._specialHandlers[stateAction]?.action) {
|
|
163
140
|
this._specialHandlers[stateAction].action(target);
|
|
164
141
|
}
|
|
165
142
|
},
|
|
166
143
|
|
|
167
|
-
// Updated setupStateActions to use registered event handlers
|
|
168
144
|
setupStateActions(container = document) {
|
|
169
|
-
// Only bind the registered event types
|
|
170
145
|
this._eventHandlers.forEach((handler, eventType) => {
|
|
171
146
|
container.addEventListener(eventType, handler);
|
|
172
147
|
});
|
|
173
148
|
|
|
174
|
-
// If no event handlers registered, register the default ones
|
|
175
149
|
if (this._eventHandlers.size === 0) {
|
|
176
150
|
container.addEventListener('click', (e) => this.defaultClickHandler(e));
|
|
177
151
|
container.addEventListener('input', (e) => this.defaultInputHandler(e));
|
|
@@ -186,7 +160,6 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
186
160
|
}
|
|
187
161
|
},
|
|
188
162
|
|
|
189
|
-
// Add serializer configuration method
|
|
190
163
|
configureSerializer(config) {
|
|
191
164
|
if (this._serializer.configure) {
|
|
192
165
|
this._serializer.configure(config);
|
|
@@ -194,18 +167,14 @@ const createCssState = (initialState = {}, serializer = StateSerializer) => {
|
|
|
194
167
|
return this;
|
|
195
168
|
},
|
|
196
169
|
|
|
197
|
-
// Clean up resources
|
|
198
170
|
destroy() {
|
|
199
171
|
this._observers.clear();
|
|
200
|
-
// The style element will remain in the DOM
|
|
201
|
-
// as removing it would affect the UI state
|
|
202
172
|
}
|
|
203
173
|
};
|
|
204
174
|
|
|
205
175
|
return state.init();
|
|
206
176
|
};
|
|
207
177
|
|
|
208
|
-
// Create a singleton instance for easy usage
|
|
209
178
|
const UIstate = createCssState();
|
|
210
179
|
|
|
211
180
|
export { createCssState };
|
package/eventState.js
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIstate - Event-based hierarchical state management module
|
|
3
|
-
* Part of the UIstate declarative state management system
|
|
4
|
-
* Uses DOM events for pub/sub with hierarchical path support
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
const createEventState = (initial = {}) => {
|
|
8
|
-
// Clone the initial state to avoid direct mutations to the passed object
|
|
9
2
|
const store = JSON.parse(JSON.stringify(initial));
|
|
10
3
|
|
|
11
|
-
// Create a dedicated DOM element to use as an event bus
|
|
12
4
|
const bus = document.createElement("x-store");
|
|
13
5
|
|
|
14
|
-
// Optional: Keep the bus element off the actual DOM for better encapsulation
|
|
15
|
-
// but this isn't strictly necessary for functionality
|
|
16
6
|
bus.style.display = "none";
|
|
17
7
|
document.documentElement.appendChild(bus);
|
|
18
8
|
|
|
19
9
|
return {
|
|
20
|
-
// get a value from the store by path
|
|
21
10
|
get: (path) => {
|
|
22
11
|
if (!path) return store;
|
|
23
12
|
return path
|
|
@@ -29,16 +18,13 @@ const createEventState = (initial = {}) => {
|
|
|
29
18
|
);
|
|
30
19
|
},
|
|
31
20
|
|
|
32
|
-
// set a value in the store by path
|
|
33
21
|
set: (path, value) => {
|
|
34
22
|
if(!path) return;
|
|
35
23
|
|
|
36
|
-
// Update the store
|
|
37
24
|
let target = store;
|
|
38
25
|
const parts = path.split(".");
|
|
39
26
|
const last = parts.pop();
|
|
40
27
|
|
|
41
|
-
// Create the path if it doesn't exist
|
|
42
28
|
parts.forEach((part) => {
|
|
43
29
|
if (!target[part] || typeof target[part] !== "object") {
|
|
44
30
|
target[part] = {};
|
|
@@ -46,13 +32,10 @@ const createEventState = (initial = {}) => {
|
|
|
46
32
|
target = target[part];
|
|
47
33
|
});
|
|
48
34
|
|
|
49
|
-
// Set the value
|
|
50
35
|
target[last] = value;
|
|
51
36
|
|
|
52
|
-
// Notify subscribers with a DOM event
|
|
53
37
|
bus.dispatchEvent(new CustomEvent(path, { detail: value }));
|
|
54
38
|
|
|
55
|
-
// Also dispatch events for parent paths to support wildcards
|
|
56
39
|
if (parts.length > 0) {
|
|
57
40
|
let parentPath = "";
|
|
58
41
|
for (const part of parts) {
|
|
@@ -64,7 +47,6 @@ const createEventState = (initial = {}) => {
|
|
|
64
47
|
);
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
// Dispatch root wildcard for any state change
|
|
68
50
|
bus.dispatchEvent(
|
|
69
51
|
new CustomEvent("*", {
|
|
70
52
|
detail: { path, value},
|
|
@@ -75,7 +57,6 @@ const createEventState = (initial = {}) => {
|
|
|
75
57
|
return value;
|
|
76
58
|
},
|
|
77
59
|
|
|
78
|
-
// Subscribe to changes on a path
|
|
79
60
|
subscribe: (path, callback) => {
|
|
80
61
|
if (!path || typeof callback !== "function") return () => {};
|
|
81
62
|
|
|
@@ -85,7 +66,6 @@ const createEventState = (initial = {}) => {
|
|
|
85
66
|
return () => bus.removeEventListener(path, handler);
|
|
86
67
|
},
|
|
87
68
|
|
|
88
|
-
// Optional method to clean up resources
|
|
89
69
|
destroy: () => {
|
|
90
70
|
if (bus.parentNode) {
|
|
91
71
|
bus.parentNode.removeChild(bus);
|
package/index.js
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIstate - Simple barrel file for core modules
|
|
3
|
-
*
|
|
4
|
-
* Exports the four core UIstate modules:
|
|
5
|
-
* - cssState: CSS custom properties state management
|
|
6
|
-
* - eventState: Event-based state management
|
|
7
|
-
* - stateSerializer: State serialization utilities
|
|
8
|
-
* - templateManager: Declarative template management
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Export the four core modules
|
|
12
1
|
export { createCssState } from './cssState.js';
|
|
13
2
|
export { createEventState } from './eventState.js';
|
|
14
3
|
export { default as stateSerializer } from './stateSerializer.js';
|
|
15
4
|
export { createTemplateManager } from './templateManager.js';
|
|
16
5
|
|
|
17
|
-
// For convenience, also export cssState as default
|
|
18
6
|
export { createCssState as default } from './cssState.js';
|
package/package.json
CHANGED
package/stateSerializer.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StateSerializer - Configurable serialization module for UIstate
|
|
3
|
-
* Handles transformation between JavaScript values and CSS-compatible string values
|
|
4
|
-
*
|
|
5
|
-
* Supports multiple serialization strategies:
|
|
6
|
-
* - 'escape': Uses custom escaping for all values (original UIstate approach)
|
|
7
|
-
* - 'json': Uses JSON.stringify for complex objects, direct values for primitives
|
|
8
|
-
* - 'hybrid': Automatically selects the best strategy based on value type
|
|
9
|
-
*
|
|
10
|
-
* Also handles serialization of values for data attributes and CSS variables
|
|
11
|
-
* with consistent rules and unified serialization behavior
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// Utility functions for CSS value escaping/unescaping
|
|
15
1
|
function escapeCssValue(value) {
|
|
16
2
|
if (typeof value !== 'string') return value;
|
|
17
3
|
return value.replace(/[^\x20-\x7E]|[!;{}:()[\]/@,'"]/g, function(char) {
|
|
@@ -22,7 +8,6 @@ function escapeCssValue(value) {
|
|
|
22
8
|
|
|
23
9
|
function unescapeCssValue(value) {
|
|
24
10
|
if (typeof value !== 'string') return value;
|
|
25
|
-
// Only perform unescaping if there are escape sequences
|
|
26
11
|
if (!value.includes('\\')) return value;
|
|
27
12
|
|
|
28
13
|
return value.replace(/\\([0-9a-f]{1,6})\s?/gi, function(match, hex) {
|
|
@@ -30,32 +15,19 @@ function unescapeCssValue(value) {
|
|
|
30
15
|
});
|
|
31
16
|
}
|
|
32
17
|
|
|
33
|
-
/**
|
|
34
|
-
* Create a configured serializer instance
|
|
35
|
-
* @param {Object} config - Configuration options
|
|
36
|
-
* @returns {Object} - Serializer instance
|
|
37
|
-
*/
|
|
38
18
|
function createSerializer(config = {}) {
|
|
39
|
-
// Default configuration
|
|
40
19
|
const defaultConfig = {
|
|
41
|
-
mode: 'hybrid',
|
|
42
|
-
debug: false,
|
|
43
|
-
complexThreshold: 3,
|
|
44
|
-
preserveTypes: true
|
|
20
|
+
mode: 'hybrid',
|
|
21
|
+
debug: false,
|
|
22
|
+
complexThreshold: 3,
|
|
23
|
+
preserveTypes: true,
|
|
45
24
|
};
|
|
46
25
|
|
|
47
|
-
// Merge provided config with defaults
|
|
48
26
|
const options = { ...defaultConfig, ...config };
|
|
49
27
|
|
|
50
|
-
// Serializer instance
|
|
51
28
|
const serializer = {
|
|
52
|
-
// Current configuration
|
|
53
29
|
config: options,
|
|
54
30
|
|
|
55
|
-
/**
|
|
56
|
-
* Update configuration
|
|
57
|
-
* @param {Object} newConfig - New configuration options
|
|
58
|
-
*/
|
|
59
31
|
configure(newConfig) {
|
|
60
32
|
Object.assign(this.config, newConfig);
|
|
61
33
|
if (this.config.debug) {
|
|
@@ -63,14 +35,7 @@ function createSerializer(config = {}) {
|
|
|
63
35
|
}
|
|
64
36
|
},
|
|
65
37
|
|
|
66
|
-
/**
|
|
67
|
-
* Serialize a value for storage in CSS variables
|
|
68
|
-
* @param {string} key - The state key (for context-aware serialization)
|
|
69
|
-
* @param {any} value - The value to serialize
|
|
70
|
-
* @returns {string} - Serialized value
|
|
71
|
-
*/
|
|
72
38
|
serialize(key, value) {
|
|
73
|
-
// Handle null/undefined
|
|
74
39
|
if (value === null || value === undefined) {
|
|
75
40
|
return '';
|
|
76
41
|
}
|
|
@@ -80,37 +45,24 @@ function createSerializer(config = {}) {
|
|
|
80
45
|
(Array.isArray(value) ||
|
|
81
46
|
(Object.keys(value).length >= this.config.complexThreshold));
|
|
82
47
|
|
|
83
|
-
// Select serialization strategy based on configuration and value type
|
|
84
48
|
if (this.config.mode === 'escape' ||
|
|
85
49
|
(this.config.mode === 'hybrid' && !isComplex)) {
|
|
86
|
-
// Use escape strategy for primitives or when escape mode is forced
|
|
87
50
|
if (valueType === 'string') {
|
|
88
51
|
return escapeCssValue(value);
|
|
89
52
|
} else if (valueType === 'object') {
|
|
90
|
-
// For simple objects in escape mode, still use JSON but with escaping
|
|
91
53
|
const jsonStr = JSON.stringify(value);
|
|
92
54
|
return escapeCssValue(jsonStr);
|
|
93
55
|
} else {
|
|
94
|
-
// For other primitives, convert to string
|
|
95
56
|
return String(value);
|
|
96
57
|
}
|
|
97
58
|
} else {
|
|
98
|
-
// Use JSON strategy for complex objects or when JSON mode is forced
|
|
99
59
|
return JSON.stringify(value);
|
|
100
60
|
}
|
|
101
61
|
},
|
|
102
62
|
|
|
103
|
-
/**
|
|
104
|
-
* Deserialize a value from CSS variable storage
|
|
105
|
-
* @param {string} key - The state key (for context-aware deserialization)
|
|
106
|
-
* @param {string} value - The serialized value
|
|
107
|
-
* @returns {any} - Deserialized value
|
|
108
|
-
*/
|
|
109
63
|
deserialize(key, value) {
|
|
110
|
-
// Handle empty values
|
|
111
64
|
if (!value) return '';
|
|
112
65
|
|
|
113
|
-
// Try JSON parse first for values that look like JSON
|
|
114
66
|
if (this.config.mode !== 'escape' &&
|
|
115
67
|
((value.startsWith('{') && value.endsWith('}')) ||
|
|
116
68
|
(value.startsWith('[') && value.endsWith(']')))) {
|
|
@@ -120,64 +72,42 @@ function createSerializer(config = {}) {
|
|
|
120
72
|
if (this.config.debug) {
|
|
121
73
|
console.warn(`Failed to parse JSON for key "${key}":`, value);
|
|
122
74
|
}
|
|
123
|
-
// Fall through to unescaping if JSON parse fails
|
|
124
75
|
}
|
|
125
76
|
}
|
|
126
77
|
|
|
127
|
-
// For non-JSON or escape mode, try unescaping
|
|
128
78
|
const unescaped = unescapeCssValue(value);
|
|
129
79
|
|
|
130
|
-
// If unescaped looks like JSON (might have been double-escaped), try parsing it
|
|
131
80
|
if (this.config.mode !== 'escape' &&
|
|
132
81
|
((unescaped.startsWith('{') && unescaped.endsWith('}')) ||
|
|
133
82
|
(unescaped.startsWith('[') && unescaped.endsWith(']')))) {
|
|
134
83
|
try {
|
|
135
84
|
return JSON.parse(unescaped);
|
|
136
85
|
} catch (e) {
|
|
137
|
-
// Not valid JSON, return unescaped string
|
|
138
86
|
}
|
|
139
87
|
}
|
|
140
88
|
|
|
141
89
|
return unescaped;
|
|
142
90
|
},
|
|
143
91
|
|
|
144
|
-
/**
|
|
145
|
-
* Serialize a value for data-* attribute
|
|
146
|
-
* @param {string} key - The state key
|
|
147
|
-
* @param {any} value - The value to serialize for attribute
|
|
148
|
-
* @returns {string} - Serialized attribute value
|
|
149
|
-
*/
|
|
150
92
|
serializeForAttribute(key, value) {
|
|
151
93
|
if (value === null || value === undefined) return null;
|
|
152
94
|
|
|
153
|
-
// For objects, use the standard serializer
|
|
154
95
|
if (typeof value === 'object') {
|
|
155
96
|
return this.serialize(key, value);
|
|
156
97
|
}
|
|
157
98
|
|
|
158
|
-
// For primitive values, use direct string conversion
|
|
159
99
|
return String(value);
|
|
160
100
|
},
|
|
161
101
|
|
|
162
|
-
/**
|
|
163
|
-
* Apply serialized state to HTML element attributes and properties
|
|
164
|
-
* @param {string} key - State key
|
|
165
|
-
* @param {any} value - Value to apply
|
|
166
|
-
* @param {HTMLElement} element - Target element (defaults to documentElement)
|
|
167
|
-
*/
|
|
168
102
|
applyToAttributes(key, value, element = document.documentElement) {
|
|
169
|
-
// Skip null/undefined values
|
|
170
103
|
if (value === null || value === undefined) {
|
|
171
104
|
element.removeAttribute(`data-${key}`);
|
|
172
105
|
return;
|
|
173
106
|
}
|
|
174
107
|
|
|
175
|
-
// Handle objects specially
|
|
176
108
|
if (typeof value === 'object') {
|
|
177
|
-
// Set the main attribute with serialized value
|
|
178
109
|
element.setAttribute(`data-${key}`, this.serialize(key, value));
|
|
179
110
|
|
|
180
|
-
// For non-array objects, set individual property attributes
|
|
181
111
|
if (!Array.isArray(value)) {
|
|
182
112
|
Object.entries(value).forEach(([propKey, propValue]) => {
|
|
183
113
|
const attributeKey = `data-${key}-${propKey.toLowerCase()}`;
|
|
@@ -196,37 +126,21 @@ function createSerializer(config = {}) {
|
|
|
196
126
|
});
|
|
197
127
|
}
|
|
198
128
|
} else {
|
|
199
|
-
// For primitives, set directly
|
|
200
129
|
element.setAttribute(`data-${key}`, value);
|
|
201
130
|
}
|
|
202
131
|
},
|
|
203
132
|
|
|
204
|
-
/**
|
|
205
|
-
* Utility method to determine if a value needs complex serialization
|
|
206
|
-
* @param {any} value - Value to check
|
|
207
|
-
* @returns {boolean} - True if complex serialization is needed
|
|
208
|
-
*/
|
|
209
133
|
needsComplexSerialization(value) {
|
|
210
134
|
return typeof value === 'object' && value !== null;
|
|
211
135
|
},
|
|
212
136
|
|
|
213
|
-
/**
|
|
214
|
-
* Set state with proper serialization for CSS variables
|
|
215
|
-
* @param {Object} uistate - UIstate instance
|
|
216
|
-
* @param {string} path - State path
|
|
217
|
-
* @param {any} value - Value to set
|
|
218
|
-
* @returns {any} - The set value
|
|
219
|
-
*/
|
|
220
137
|
setStateWithCss(uistate, path, value) {
|
|
221
|
-
// Update UIstate
|
|
222
138
|
uistate.setState(path, value);
|
|
223
139
|
|
|
224
|
-
// Update CSS variable with properly serialized value
|
|
225
140
|
const cssPath = path.replace(/\./g, '-');
|
|
226
141
|
const serialized = this.serialize(path, value);
|
|
227
142
|
document.documentElement.style.setProperty(`--${cssPath}`, serialized);
|
|
228
143
|
|
|
229
|
-
// Update data attribute for root level state
|
|
230
144
|
const segments = path.split('.');
|
|
231
145
|
if (segments.length === 1) {
|
|
232
146
|
document.documentElement.dataset[path] = typeof value === 'object'
|
|
@@ -237,18 +151,10 @@ function createSerializer(config = {}) {
|
|
|
237
151
|
return value;
|
|
238
152
|
},
|
|
239
153
|
|
|
240
|
-
/**
|
|
241
|
-
* Get state with fallback to CSS variables
|
|
242
|
-
* @param {Object} uistate - UIstate instance
|
|
243
|
-
* @param {string} path - State path
|
|
244
|
-
* @returns {any} - Retrieved value
|
|
245
|
-
*/
|
|
246
154
|
getStateFromCss(uistate, path) {
|
|
247
|
-
// First try UIstate
|
|
248
155
|
const value = uistate.getState(path);
|
|
249
156
|
if (value !== undefined) return value;
|
|
250
157
|
|
|
251
|
-
// If not found, try CSS variable
|
|
252
158
|
const cssPath = path.replace(/\./g, '-');
|
|
253
159
|
const cssValue = getComputedStyle(document.documentElement)
|
|
254
160
|
.getPropertyValue(`--${cssPath}`).trim();
|
|
@@ -260,7 +166,6 @@ function createSerializer(config = {}) {
|
|
|
260
166
|
return serializer;
|
|
261
167
|
}
|
|
262
168
|
|
|
263
|
-
// Create a default instance with hybrid mode
|
|
264
169
|
const StateSerializer = createSerializer();
|
|
265
170
|
|
|
266
171
|
export default StateSerializer;
|
package/templateManager.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TemplateManager - Component mounting and event delegation
|
|
3
|
-
* Handles HTML templating, component mounting, and event delegation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
const createTemplateManager = (stateManager) => {
|
|
7
2
|
const manager = {
|
|
8
3
|
handlers: {},
|
|
@@ -12,25 +7,11 @@ const createTemplateManager = (stateManager) => {
|
|
|
12
7
|
return this;
|
|
13
8
|
},
|
|
14
9
|
|
|
15
|
-
/**
|
|
16
|
-
* Register multiple actions with their handlers in a declarative way
|
|
17
|
-
* @param {Object} actionsMap - Map of action names to handlers or handler configs
|
|
18
|
-
* @returns {Object} - The manager instance for chaining
|
|
19
|
-
*
|
|
20
|
-
* Example usage:
|
|
21
|
-
* templateManager.registerActions({
|
|
22
|
-
* 'add-item': addItem,
|
|
23
|
-
* 'delete-item': { fn: deleteItem, extractId: true },
|
|
24
|
-
* 'toggle-state': toggleState
|
|
25
|
-
* });
|
|
26
|
-
*/
|
|
27
10
|
registerActions(actionsMap) {
|
|
28
11
|
Object.entries(actionsMap).forEach(([action, handler]) => {
|
|
29
12
|
if (typeof handler === 'function') {
|
|
30
|
-
// Simple function handler
|
|
31
13
|
this.onAction(action, handler);
|
|
32
14
|
} else if (typeof handler === 'object' && handler !== null) {
|
|
33
|
-
// Handler with configuration
|
|
34
15
|
const { fn, extractId = true, idAttribute = 'id' } = handler;
|
|
35
16
|
|
|
36
17
|
if (typeof fn !== 'function') {
|
|
@@ -40,7 +21,6 @@ const createTemplateManager = (stateManager) => {
|
|
|
40
21
|
this.onAction(action, (e) => {
|
|
41
22
|
if (extractId) {
|
|
42
23
|
const target = e.target.closest('[data-action]');
|
|
43
|
-
// Look for common ID attributes in order of preference
|
|
44
24
|
const id = target.dataset[idAttribute] ||
|
|
45
25
|
target.dataset.actionId ||
|
|
46
26
|
target.dataset.cardId ||
|
|
@@ -70,40 +50,30 @@ const createTemplateManager = (stateManager) => {
|
|
|
70
50
|
if (typeof handler === 'function') {
|
|
71
51
|
handler(e);
|
|
72
52
|
} else if (target.dataset.value !== undefined && stateManager) {
|
|
73
|
-
// If we have a state manager, use it to update state
|
|
74
53
|
stateManager.setState(action, target.dataset.value);
|
|
75
54
|
}
|
|
76
55
|
});
|
|
77
56
|
return this;
|
|
78
57
|
},
|
|
79
58
|
|
|
80
|
-
/**
|
|
81
|
-
* Render a template from a CSS variable
|
|
82
|
-
* @param {string} templateName - Name of the template (will be prefixed with --template-)
|
|
83
|
-
* @param {Object} data - Data to inject into the template
|
|
84
|
-
* @returns {HTMLElement} - The rendered element
|
|
85
|
-
*/
|
|
86
59
|
renderTemplateFromCss(templateName, data = {}) {
|
|
87
60
|
const cssTemplate = getComputedStyle(document.documentElement)
|
|
88
61
|
.getPropertyValue(`--template-${templateName}`)
|
|
89
62
|
.trim()
|
|
90
|
-
.replace(/^['"]|['"]$/g, '');
|
|
63
|
+
.replace(/^['"]|['"]$/g, '');
|
|
91
64
|
|
|
92
65
|
if (!cssTemplate) throw new Error(`Template not found in CSS: --template-${templateName}`);
|
|
93
66
|
|
|
94
67
|
let html = cssTemplate;
|
|
95
68
|
|
|
96
|
-
// Replace all placeholders with actual data
|
|
97
69
|
Object.entries(data).forEach(([key, value]) => {
|
|
98
70
|
const regex = new RegExp(`{{${key}}}`, 'g');
|
|
99
71
|
html = html.replace(regex, value);
|
|
100
72
|
});
|
|
101
73
|
|
|
102
|
-
// Create a temporary container
|
|
103
74
|
const temp = document.createElement('div');
|
|
104
75
|
temp.innerHTML = html;
|
|
105
76
|
|
|
106
|
-
// Return the first child (the rendered template)
|
|
107
77
|
return temp.firstElementChild;
|
|
108
78
|
},
|
|
109
79
|
|
|
@@ -131,13 +101,11 @@ const createTemplateManager = (stateManager) => {
|
|
|
131
101
|
return clone.firstElementChild;
|
|
132
102
|
},
|
|
133
103
|
|
|
134
|
-
// Helper to create a reactive component with automatic updates
|
|
135
104
|
createComponent(name, renderFn, stateKeys = []) {
|
|
136
105
|
if (!stateManager) {
|
|
137
106
|
throw new Error('State manager is required for reactive components');
|
|
138
107
|
}
|
|
139
108
|
|
|
140
|
-
// Create template element if it doesn't exist
|
|
141
109
|
let tpl = document.getElementById(`${name}-template`);
|
|
142
110
|
if (!tpl) {
|
|
143
111
|
tpl = document.createElement('template');
|
|
@@ -145,10 +113,8 @@ const createTemplateManager = (stateManager) => {
|
|
|
145
113
|
document.body.appendChild(tpl);
|
|
146
114
|
}
|
|
147
115
|
|
|
148
|
-
// Initial render
|
|
149
116
|
tpl.innerHTML = renderFn(stateManager);
|
|
150
117
|
|
|
151
|
-
// Set up observers for reactive updates
|
|
152
118
|
if (stateKeys.length > 0) {
|
|
153
119
|
stateKeys.forEach(key => {
|
|
154
120
|
stateManager.observe(key, () => {
|
|
@@ -162,17 +128,6 @@ const createTemplateManager = (stateManager) => {
|
|
|
162
128
|
};
|
|
163
129
|
},
|
|
164
130
|
|
|
165
|
-
/**
|
|
166
|
-
* Apply CSS classes to an element based on a state key stored in CSS variables
|
|
167
|
-
* @param {HTMLElement} element - Element to apply classes to
|
|
168
|
-
* @param {string} stateKey - State key to look up in CSS variables
|
|
169
|
-
* @param {Object} options - Options for class application
|
|
170
|
-
* @returns {HTMLElement} - The element for chaining
|
|
171
|
-
*
|
|
172
|
-
* Example usage:
|
|
173
|
-
* // CSS: :root { --card-primary-classes: "bg-primary text-white"; }
|
|
174
|
-
* templateManager.applyClassesFromState(cardElement, 'card-primary');
|
|
175
|
-
*/
|
|
176
131
|
applyClassesFromState(element, stateKey, options = {}) {
|
|
177
132
|
if (!element) return element;
|
|
178
133
|
|
|
@@ -191,25 +146,22 @@ const createTemplateManager = (stateManager) => {
|
|
|
191
146
|
.replace(/^['"]|['"]$/g, '');
|
|
192
147
|
|
|
193
148
|
if (classString) {
|
|
194
|
-
// Clear existing classes if specified
|
|
195
149
|
if (clearExisting) {
|
|
196
150
|
element.className = '';
|
|
197
151
|
}
|
|
198
152
|
|
|
199
|
-
// Add new classes
|
|
200
153
|
classString.split(' ').forEach(cls => {
|
|
201
154
|
if (cls) element.classList.add(cls);
|
|
202
155
|
});
|
|
203
156
|
}
|
|
204
157
|
|
|
205
|
-
return element;
|
|
158
|
+
return element;
|
|
206
159
|
}
|
|
207
160
|
};
|
|
208
161
|
|
|
209
162
|
return manager;
|
|
210
163
|
};
|
|
211
164
|
|
|
212
|
-
// Create a standalone instance that doesn't depend on any state manager
|
|
213
165
|
const TemplateManager = createTemplateManager();
|
|
214
166
|
|
|
215
167
|
export default createTemplateManager;
|