@uistate/core 1.0.0 → 2.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.
- package/README.md +75 -5
- package/package.json +10 -21
- package/src/cssState.js +87 -0
- package/src/eventState.js +98 -0
- package/src/index.js +111 -0
- package/src/templateManager.js +97 -0
- package/LICENSE +0 -21
- package/dist/index.d.mts +0 -16
- package/dist/index.d.ts +0 -16
- package/dist/index.js +0 -107
- package/dist/index.mjs +0 -79
- package/src/UIState.ts +0 -68
- package/src/index.ts +0 -2
- package/src/react/index.ts +0 -33
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# @uistate/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**author**: Ajdin Imsirovic <ajdika@live.com> (GitHub),
|
|
4
|
+
**maintainer**: uistate <ajdika.i@gmail.com> (npm)
|
|
5
|
+
|
|
6
|
+
High-performance UI state management using CSS custom properties and a number of other novel ideas. Focused heavily on DX and performance, in that order.
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
10
|
+
- ~~🚀 44% faster state updates than Redux~~ Potentially O(1) state updates!
|
|
11
|
+
- ~~📉 12.5% lower memory usage~~ Large memory usage savings (perf pending...)
|
|
9
12
|
- 🎯 Zero configuration
|
|
10
13
|
- 🔄 Automatic reactivity
|
|
11
14
|
- 🎨 Framework agnostic
|
|
12
15
|
- 📦 Tiny bundle size (~1KB)
|
|
13
|
-
-
|
|
14
|
-
-
|
|
16
|
+
- ~~💪 Full TypeScript support~~ Sorry, JS-only for a while...
|
|
17
|
+
- ~~📊 Optional performance monitoring~~ In progress (perf pending...)
|
|
15
18
|
|
|
16
19
|
## Installation
|
|
17
20
|
|
|
@@ -123,6 +126,73 @@ The performance tracker monitors:
|
|
|
123
126
|
- Safari 10.1+
|
|
124
127
|
- Edge 79+
|
|
125
128
|
|
|
129
|
+
# Explanation of the Core Ideas Behind the UI State Management System
|
|
130
|
+
|
|
131
|
+
A TypeScript-based UI state management system that leverages CSS custom properties (CSS variables) as the storage mechanism.
|
|
132
|
+
|
|
133
|
+
## Key Components
|
|
134
|
+
|
|
135
|
+
### Type Definitions
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
type StateObserver<T> = (value: T) => void;
|
|
139
|
+
interface UIStateType { ... }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- Defines a type for state change observers (callbacks)
|
|
143
|
+
- Defines the interface for the state management system
|
|
144
|
+
|
|
145
|
+
## Core Functionality
|
|
146
|
+
|
|
147
|
+
The `UIState` object provides several key methods:
|
|
148
|
+
|
|
149
|
+
### `init()`
|
|
150
|
+
- Creates a style element in the document head
|
|
151
|
+
- Initializes a CSS stylesheet for managing custom properties
|
|
152
|
+
- Returns the UIState instance for chaining
|
|
153
|
+
|
|
154
|
+
### `setState<T>(key: string, value: T)`
|
|
155
|
+
- Stores values as CSS custom properties (--key)
|
|
156
|
+
- Converts non-string values to JSON strings
|
|
157
|
+
- Notifies observers when values change
|
|
158
|
+
|
|
159
|
+
### `getState<T>(key: string)`
|
|
160
|
+
- Retrieves values from CSS custom properties
|
|
161
|
+
- Attempts to parse JSON values back to their original type
|
|
162
|
+
- Falls back to raw string if parsing fails
|
|
163
|
+
|
|
164
|
+
### `observe<T>(key: string, callback)`
|
|
165
|
+
- Implements an observer pattern for state changes
|
|
166
|
+
- Returns a cleanup function to remove the observer
|
|
167
|
+
- Allows multiple observers per state key
|
|
168
|
+
|
|
169
|
+
## Internal Mechanisms
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
_sheet: CSSStyleSheet | null // Stores the stylesheet reference
|
|
173
|
+
_observers: Map<string, Set<StateObserver>> // Stores observers per key
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Key Features
|
|
177
|
+
|
|
178
|
+
1. Uses CSS custom properties as a storage mechanism, making state changes automatically trigger UI updates
|
|
179
|
+
2. Provides type safety through TypeScript generics
|
|
180
|
+
3. Implements the observer pattern for reactive updates
|
|
181
|
+
4. Handles serialization/deserialization of complex data types through JSON
|
|
182
|
+
|
|
183
|
+
## Example Usage
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
UIState.init();
|
|
187
|
+
UIState.setState('theme', 'dark');
|
|
188
|
+
UIState.observe('theme', (newValue) => {
|
|
189
|
+
console.log('Theme changed to:', newValue);
|
|
190
|
+
});
|
|
191
|
+
const currentTheme = UIState.getState<string>('theme');
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
This implementation is particularly useful for managing global UI state like themes, layout preferences, or any other state that might affect multiple components simultaneously.
|
|
195
|
+
|
|
126
196
|
## Contributing
|
|
127
197
|
|
|
128
198
|
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
package/package.json
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "High-performance UI state management using CSS custom properties",
|
|
5
|
-
"main": "
|
|
6
|
-
"module": "
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance)",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"module": "src/index.js",
|
|
8
7
|
"files": [
|
|
9
|
-
"dist",
|
|
10
8
|
"src"
|
|
11
9
|
],
|
|
12
10
|
"scripts": {
|
|
13
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
14
11
|
"test": "jest",
|
|
15
|
-
"lint": "eslint src"
|
|
16
|
-
"typecheck": "tsc --noEmit",
|
|
17
|
-
"prepare": "npm run build"
|
|
12
|
+
"lint": "eslint src"
|
|
18
13
|
},
|
|
19
14
|
"keywords": [
|
|
20
15
|
"state-management",
|
|
@@ -23,7 +18,10 @@
|
|
|
23
18
|
"css",
|
|
24
19
|
"performance"
|
|
25
20
|
],
|
|
26
|
-
"author": "Imsirovic
|
|
21
|
+
"author": "Ajdin Imsirovic <ajdika@live.com> (GitHub)",
|
|
22
|
+
"contributors": [
|
|
23
|
+
"uistate <ajdika.i@gmail.com> (npm)"
|
|
24
|
+
],
|
|
27
25
|
"license": "MIT",
|
|
28
26
|
"repository": {
|
|
29
27
|
"type": "git",
|
|
@@ -37,18 +35,9 @@
|
|
|
37
35
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^20.0.0",
|
|
41
|
-
"@types/react": "^18.0.0",
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
43
|
-
"@typescript-eslint/parser": "^6.0.0",
|
|
44
38
|
"eslint": "^8.0.0",
|
|
45
39
|
"jest": "^29.0.0",
|
|
46
|
-
"jest-environment-jsdom": "^29.0.0"
|
|
47
|
-
"@types/jest": "^29.0.0",
|
|
48
|
-
"ts-jest": "^29.0.0",
|
|
49
|
-
"tsup": "^8.0.0",
|
|
50
|
-
"typescript": "^5.0.0",
|
|
51
|
-
"react": "^18.0.0"
|
|
40
|
+
"jest-environment-jsdom": "^29.0.0"
|
|
52
41
|
},
|
|
53
42
|
"sideEffects": false
|
|
54
43
|
}
|
package/src/cssState.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIstate - CSS-based state management module
|
|
3
|
+
* Part of the UIstate declarative state management system
|
|
4
|
+
* Uses CSS custom properties and data attributes for state representation
|
|
5
|
+
*/
|
|
6
|
+
const createCssState = (initialState = {}) => {
|
|
7
|
+
const state = {
|
|
8
|
+
_sheet: null,
|
|
9
|
+
_observers: new Map(),
|
|
10
|
+
|
|
11
|
+
init() {
|
|
12
|
+
if (!this._sheet) {
|
|
13
|
+
const style = document.createElement('style');
|
|
14
|
+
document.head.appendChild(style);
|
|
15
|
+
this._sheet = style.sheet;
|
|
16
|
+
this._addRule(':root {}');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Initialize with any provided state
|
|
20
|
+
if (initialState && typeof initialState === 'object') {
|
|
21
|
+
Object.entries(initialState).forEach(([key, value]) => {
|
|
22
|
+
this.setState(key, value);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return this;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
setState(key, value) {
|
|
30
|
+
const cssValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
31
|
+
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
32
|
+
document.documentElement.setAttribute(`data-${key}`, value);
|
|
33
|
+
this._notifyObservers(key, value);
|
|
34
|
+
return value;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
getState(key) {
|
|
38
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(value);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
observe(key, callback) {
|
|
47
|
+
if (!this._observers.has(key)) {
|
|
48
|
+
this._observers.set(key, new Set());
|
|
49
|
+
}
|
|
50
|
+
this._observers.get(key).add(callback);
|
|
51
|
+
return () => {
|
|
52
|
+
const observers = this._observers.get(key);
|
|
53
|
+
if (observers) {
|
|
54
|
+
observers.delete(callback);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
_notifyObservers(key, value) {
|
|
60
|
+
const observers = this._observers.get(key);
|
|
61
|
+
if (observers) {
|
|
62
|
+
observers.forEach(cb => cb(value));
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
_addRule(rule) {
|
|
67
|
+
if (this._sheet) {
|
|
68
|
+
this._sheet.insertRule(rule, this._sheet.cssRules.length);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Clean up resources
|
|
73
|
+
destroy() {
|
|
74
|
+
this._observers.clear();
|
|
75
|
+
// The style element will remain in the DOM
|
|
76
|
+
// as removing it would affect the UI state
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return state.init();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Legacy singleton for backward compatibility
|
|
84
|
+
const CssState = createCssState();
|
|
85
|
+
|
|
86
|
+
export default createCssState;
|
|
87
|
+
export { createCssState, CssState };
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
const createEventState = (initial = {}) => {
|
|
8
|
+
// Clone the initial state to avoid direct mutations to the passed object
|
|
9
|
+
const store = JSON.parse(JSON.stringify(initial));
|
|
10
|
+
|
|
11
|
+
// Create a dedicated DOM element to use as an event bus
|
|
12
|
+
const bus = document.createElement("x-store");
|
|
13
|
+
|
|
14
|
+
// Optional: Keep the bus element off the actual DOM for better encapsulation
|
|
15
|
+
// but this isn't strictly necessary for functionality
|
|
16
|
+
bus.style.display = "none";
|
|
17
|
+
document.documentElement.appendChild(bus);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
// get a value from the store by path
|
|
21
|
+
get: (path) => {
|
|
22
|
+
if (!path) return store;
|
|
23
|
+
return path
|
|
24
|
+
.split(".")
|
|
25
|
+
.reduce(
|
|
26
|
+
(obj, prop) =>
|
|
27
|
+
obj && obj[prop] !== undefined ? obj[prop] : undefined,
|
|
28
|
+
store
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// set a value in the store by path
|
|
33
|
+
set: (path, value) => {
|
|
34
|
+
if(!path) return;
|
|
35
|
+
|
|
36
|
+
// Update the store
|
|
37
|
+
let target = store;
|
|
38
|
+
const parts = path.split(".");
|
|
39
|
+
const last = parts.pop();
|
|
40
|
+
|
|
41
|
+
// Create the path if it doesn't exist
|
|
42
|
+
parts.forEach((part) => {
|
|
43
|
+
if (!target[part] || typeof target[part] !== "object") {
|
|
44
|
+
target[part] = {};
|
|
45
|
+
}
|
|
46
|
+
target = target[part];
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Set the value
|
|
50
|
+
target[last] = value;
|
|
51
|
+
|
|
52
|
+
// Notify subscribers with a DOM event
|
|
53
|
+
bus.dispatchEvent(new CustomEvent(path, { detail: value }));
|
|
54
|
+
|
|
55
|
+
// Also dispatch events for parent paths to support wildcards
|
|
56
|
+
if (parts.length > 0) {
|
|
57
|
+
let parentPath = "";
|
|
58
|
+
for (const part of parts) {
|
|
59
|
+
parentPath = parentPath ? `${parentPath}.${part}` : part;
|
|
60
|
+
bus.dispatchEvent(
|
|
61
|
+
new CustomEvent(`${parentPath}.*`, {
|
|
62
|
+
detail: { path, value },
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Dispatch root wildcard for any state change
|
|
68
|
+
bus.dispatchEvent(
|
|
69
|
+
new CustomEvent("*", {
|
|
70
|
+
detail: { path, value},
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return value;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Subscribe to changes on a path
|
|
79
|
+
subscribe: (path, callback) => {
|
|
80
|
+
if (!path || typeof callback !== "function") return () => {};
|
|
81
|
+
|
|
82
|
+
const handler = (e) => callback(e.detail, path);
|
|
83
|
+
bus.addEventListener(path, handler);
|
|
84
|
+
|
|
85
|
+
return () => bus.removeEventListener(path, handler);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Optional method to clean up resources
|
|
89
|
+
destroy: () => {
|
|
90
|
+
if (bus.parentNode) {
|
|
91
|
+
bus.parentNode.removeChild(bus);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default createEventState;
|
|
98
|
+
export { createEventState };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UIstate - Declarative state management for the web
|
|
3
|
+
*
|
|
4
|
+
* A unified system that combines CSS and Event-based state management with templating
|
|
5
|
+
* Integrates CSS variables, event-based state, and template management for optimal performance
|
|
6
|
+
*/
|
|
7
|
+
import { createCssState } from './cssState.js';
|
|
8
|
+
import { createEventState } from './eventState.js';
|
|
9
|
+
import { createTemplateManager } from './templateManager.js';
|
|
10
|
+
|
|
11
|
+
const createUnifiedState = (initialState = {}) => {
|
|
12
|
+
// Initialize state store
|
|
13
|
+
const store = JSON.parse(JSON.stringify(initialState));
|
|
14
|
+
|
|
15
|
+
// Create the CSS state manager
|
|
16
|
+
const cssState = createCssState(initialState);
|
|
17
|
+
|
|
18
|
+
// Create the event state manager with the same initial state
|
|
19
|
+
const eventState = createEventState(initialState);
|
|
20
|
+
|
|
21
|
+
// Create a unified API
|
|
22
|
+
const unifiedState = {
|
|
23
|
+
_isNotifying: false, // Flag to prevent recursive notifications
|
|
24
|
+
|
|
25
|
+
// Get state with hierarchical path support
|
|
26
|
+
getState(path) {
|
|
27
|
+
if (!path) return store;
|
|
28
|
+
|
|
29
|
+
// Try to get from event state first (faster)
|
|
30
|
+
const value = eventState.get(path);
|
|
31
|
+
|
|
32
|
+
if (value !== undefined) return value;
|
|
33
|
+
|
|
34
|
+
// Fall back to CSS variable (for values set outside this API)
|
|
35
|
+
const cssPath = path.replace(/\./g, "-");
|
|
36
|
+
return cssState.getState(cssPath);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Set state with hierarchical path support
|
|
40
|
+
setState(path, value) {
|
|
41
|
+
// Prevent recursive notifications
|
|
42
|
+
if (this._isNotifying) return value;
|
|
43
|
+
|
|
44
|
+
this._isNotifying = true;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Update event state
|
|
48
|
+
eventState.set(path, value);
|
|
49
|
+
|
|
50
|
+
// Update CSS state (convert dots to dashes for CSS variables)
|
|
51
|
+
const cssPath = path.replace(/\./g, "-");
|
|
52
|
+
cssState.setState(cssPath, value);
|
|
53
|
+
|
|
54
|
+
return value;
|
|
55
|
+
} finally {
|
|
56
|
+
this._isNotifying = false;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// Subscribe to state changes with support for wildcards
|
|
61
|
+
subscribe(path, callback) {
|
|
62
|
+
return eventState.subscribe(path, callback);
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Observe CSS state changes (simpler API, no wildcards)
|
|
66
|
+
observe(key, callback) {
|
|
67
|
+
return cssState.observe(key, callback);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Clean up resources
|
|
71
|
+
destroy() {
|
|
72
|
+
cssState.destroy();
|
|
73
|
+
eventState.destroy();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return unifiedState;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Create a singleton instance of the unified state
|
|
81
|
+
const UnifiedState = createUnifiedState();
|
|
82
|
+
|
|
83
|
+
// Create a template manager connected to the unified state
|
|
84
|
+
const TemplateManager = createTemplateManager(UnifiedState);
|
|
85
|
+
|
|
86
|
+
// Create a combined API for backward compatibility
|
|
87
|
+
const UIstate = {
|
|
88
|
+
...UnifiedState,
|
|
89
|
+
|
|
90
|
+
// Add template manager methods
|
|
91
|
+
handlers: TemplateManager.handlers,
|
|
92
|
+
onAction: TemplateManager.onAction.bind(TemplateManager),
|
|
93
|
+
attachDelegation: TemplateManager.attachDelegation.bind(TemplateManager),
|
|
94
|
+
mount: TemplateManager.mount.bind(TemplateManager),
|
|
95
|
+
|
|
96
|
+
// Initialize both systems
|
|
97
|
+
init() {
|
|
98
|
+
// Attach event delegation to document body
|
|
99
|
+
this.attachDelegation(document.body);
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default UIstate;
|
|
105
|
+
export {
|
|
106
|
+
createUnifiedState,
|
|
107
|
+
UnifiedState,
|
|
108
|
+
createTemplateManager,
|
|
109
|
+
TemplateManager,
|
|
110
|
+
UIstate
|
|
111
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TemplateManager - Component mounting and event delegation
|
|
3
|
+
* Handles HTML templating, component mounting, and event delegation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const createTemplateManager = (stateManager) => {
|
|
7
|
+
const manager = {
|
|
8
|
+
handlers: {},
|
|
9
|
+
|
|
10
|
+
onAction(action, handler) {
|
|
11
|
+
this.handlers[action] = handler;
|
|
12
|
+
return this;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
attachDelegation(root = document.body) {
|
|
16
|
+
root.addEventListener('click', e => {
|
|
17
|
+
const target = e.target.closest('[data-action]');
|
|
18
|
+
if (!target) return;
|
|
19
|
+
|
|
20
|
+
const action = target.dataset.action;
|
|
21
|
+
if (!action) return;
|
|
22
|
+
|
|
23
|
+
const handler = this.handlers[action];
|
|
24
|
+
if (typeof handler === 'function') {
|
|
25
|
+
handler(e);
|
|
26
|
+
} else if (target.dataset.value !== undefined && stateManager) {
|
|
27
|
+
// If we have a state manager, use it to update state
|
|
28
|
+
stateManager.setState(action, target.dataset.value);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return this;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
mount(componentName, container) {
|
|
35
|
+
const tpl = document.getElementById(`${componentName}-template`);
|
|
36
|
+
if (!tpl) throw new Error(`Template not found: ${componentName}-template`);
|
|
37
|
+
const clone = tpl.content.cloneNode(true);
|
|
38
|
+
|
|
39
|
+
function resolvePlaceholders(fragment) {
|
|
40
|
+
Array.from(fragment.querySelectorAll('*')).forEach(el => {
|
|
41
|
+
const tag = el.tagName.toLowerCase();
|
|
42
|
+
if (tag.endsWith('-placeholder')) {
|
|
43
|
+
const name = tag.replace('-placeholder','');
|
|
44
|
+
const childTpl = document.getElementById(`${name}-template`);
|
|
45
|
+
if (!childTpl) throw new Error(`Template not found: ${name}-template`);
|
|
46
|
+
const childClone = childTpl.content.cloneNode(true);
|
|
47
|
+
resolvePlaceholders(childClone);
|
|
48
|
+
el.replaceWith(childClone);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
resolvePlaceholders(clone);
|
|
54
|
+
container.appendChild(clone);
|
|
55
|
+
return clone.firstElementChild;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Helper to create a reactive component with automatic updates
|
|
59
|
+
createComponent(name, renderFn, stateKeys = []) {
|
|
60
|
+
if (!stateManager) {
|
|
61
|
+
throw new Error('State manager is required for reactive components');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create template element if it doesn't exist
|
|
65
|
+
let tpl = document.getElementById(`${name}-template`);
|
|
66
|
+
if (!tpl) {
|
|
67
|
+
tpl = document.createElement('template');
|
|
68
|
+
tpl.id = `${name}-template`;
|
|
69
|
+
document.body.appendChild(tpl);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Initial render
|
|
73
|
+
tpl.innerHTML = renderFn(stateManager);
|
|
74
|
+
|
|
75
|
+
// Set up observers for reactive updates
|
|
76
|
+
if (stateKeys.length > 0) {
|
|
77
|
+
stateKeys.forEach(key => {
|
|
78
|
+
stateManager.observe(key, () => {
|
|
79
|
+
tpl.innerHTML = renderFn(stateManager);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
mount: (container) => this.mount(name, container)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return manager;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Create a standalone instance that doesn't depend on any state manager
|
|
94
|
+
const TemplateManager = createTemplateManager();
|
|
95
|
+
|
|
96
|
+
export default createTemplateManager;
|
|
97
|
+
export { createTemplateManager, TemplateManager };
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Imsirovic Ajdin
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/dist/index.d.mts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
type StateObserver<T = any> = (value: T) => void;
|
|
2
|
-
interface UIStateType {
|
|
3
|
-
_sheet: CSSStyleSheet | null;
|
|
4
|
-
_observers: Map<string, Set<StateObserver>>;
|
|
5
|
-
init(): UIStateType;
|
|
6
|
-
setState<T>(key: string, value: T): void;
|
|
7
|
-
getState<T>(key: string): T;
|
|
8
|
-
observe<T>(key: string, callback: StateObserver<T>): () => void;
|
|
9
|
-
_notifyObservers<T>(key: string, value: T): void;
|
|
10
|
-
_addRule(rule: string): void;
|
|
11
|
-
}
|
|
12
|
-
declare const UIState: UIStateType;
|
|
13
|
-
|
|
14
|
-
declare function useUIState<T>(key: string, initialValue: T): [T, (value: T) => void];
|
|
15
|
-
|
|
16
|
-
export { UIState, useUIState };
|
package/dist/index.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
type StateObserver<T = any> = (value: T) => void;
|
|
2
|
-
interface UIStateType {
|
|
3
|
-
_sheet: CSSStyleSheet | null;
|
|
4
|
-
_observers: Map<string, Set<StateObserver>>;
|
|
5
|
-
init(): UIStateType;
|
|
6
|
-
setState<T>(key: string, value: T): void;
|
|
7
|
-
getState<T>(key: string): T;
|
|
8
|
-
observe<T>(key: string, callback: StateObserver<T>): () => void;
|
|
9
|
-
_notifyObservers<T>(key: string, value: T): void;
|
|
10
|
-
_addRule(rule: string): void;
|
|
11
|
-
}
|
|
12
|
-
declare const UIState: UIStateType;
|
|
13
|
-
|
|
14
|
-
declare function useUIState<T>(key: string, initialValue: T): [T, (value: T) => void];
|
|
15
|
-
|
|
16
|
-
export { UIState, useUIState };
|
package/dist/index.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
UIState: () => UIState_default,
|
|
24
|
-
useUIState: () => useUIState
|
|
25
|
-
});
|
|
26
|
-
module.exports = __toCommonJS(index_exports);
|
|
27
|
-
|
|
28
|
-
// src/UIState.ts
|
|
29
|
-
var UIState = {
|
|
30
|
-
_sheet: null,
|
|
31
|
-
_observers: /* @__PURE__ */ new Map(),
|
|
32
|
-
init() {
|
|
33
|
-
if (!this._sheet) {
|
|
34
|
-
const style = document.createElement("style");
|
|
35
|
-
document.head.appendChild(style);
|
|
36
|
-
this._sheet = style.sheet;
|
|
37
|
-
this._addRule(":root {}");
|
|
38
|
-
}
|
|
39
|
-
return this;
|
|
40
|
-
},
|
|
41
|
-
setState(key, value) {
|
|
42
|
-
const cssValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
43
|
-
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
44
|
-
this._notifyObservers(key, value);
|
|
45
|
-
},
|
|
46
|
-
getState(key) {
|
|
47
|
-
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
|
|
48
|
-
try {
|
|
49
|
-
return JSON.parse(value);
|
|
50
|
-
} catch (e) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
observe(key, callback) {
|
|
55
|
-
var _a;
|
|
56
|
-
if (!this._observers.has(key)) {
|
|
57
|
-
this._observers.set(key, /* @__PURE__ */ new Set());
|
|
58
|
-
}
|
|
59
|
-
(_a = this._observers.get(key)) == null ? void 0 : _a.add(callback);
|
|
60
|
-
return () => {
|
|
61
|
-
var _a2;
|
|
62
|
-
(_a2 = this._observers.get(key)) == null ? void 0 : _a2.delete(callback);
|
|
63
|
-
};
|
|
64
|
-
},
|
|
65
|
-
_notifyObservers(key, value) {
|
|
66
|
-
var _a;
|
|
67
|
-
(_a = this._observers.get(key)) == null ? void 0 : _a.forEach(
|
|
68
|
-
(callback) => callback(value)
|
|
69
|
-
);
|
|
70
|
-
},
|
|
71
|
-
_addRule(rule) {
|
|
72
|
-
if (this._sheet) {
|
|
73
|
-
this._sheet.insertRule(rule, this._sheet.cssRules.length);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
var UIState_default = UIState;
|
|
78
|
-
|
|
79
|
-
// src/react/index.ts
|
|
80
|
-
var import_react = require("react");
|
|
81
|
-
function useUIState(key, initialValue) {
|
|
82
|
-
(0, import_react.useEffect)(() => {
|
|
83
|
-
UIState_default.init();
|
|
84
|
-
}, []);
|
|
85
|
-
const [state, setState] = (0, import_react.useState)(() => {
|
|
86
|
-
try {
|
|
87
|
-
const value = UIState_default.getState(key);
|
|
88
|
-
return value !== void 0 ? value : initialValue;
|
|
89
|
-
} catch (e) {
|
|
90
|
-
return initialValue;
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
(0, import_react.useEffect)(() => {
|
|
94
|
-
return UIState_default.observe(key, (value) => {
|
|
95
|
-
setState(value);
|
|
96
|
-
});
|
|
97
|
-
}, [key]);
|
|
98
|
-
const setUIState = (value) => {
|
|
99
|
-
UIState_default.setState(key, value);
|
|
100
|
-
};
|
|
101
|
-
return [state, setUIState];
|
|
102
|
-
}
|
|
103
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
104
|
-
0 && (module.exports = {
|
|
105
|
-
UIState,
|
|
106
|
-
useUIState
|
|
107
|
-
});
|
package/dist/index.mjs
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// src/UIState.ts
|
|
2
|
-
var UIState = {
|
|
3
|
-
_sheet: null,
|
|
4
|
-
_observers: /* @__PURE__ */ new Map(),
|
|
5
|
-
init() {
|
|
6
|
-
if (!this._sheet) {
|
|
7
|
-
const style = document.createElement("style");
|
|
8
|
-
document.head.appendChild(style);
|
|
9
|
-
this._sheet = style.sheet;
|
|
10
|
-
this._addRule(":root {}");
|
|
11
|
-
}
|
|
12
|
-
return this;
|
|
13
|
-
},
|
|
14
|
-
setState(key, value) {
|
|
15
|
-
const cssValue = typeof value === "string" ? value : JSON.stringify(value);
|
|
16
|
-
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
17
|
-
this._notifyObservers(key, value);
|
|
18
|
-
},
|
|
19
|
-
getState(key) {
|
|
20
|
-
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${key}`).trim();
|
|
21
|
-
try {
|
|
22
|
-
return JSON.parse(value);
|
|
23
|
-
} catch (e) {
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
observe(key, callback) {
|
|
28
|
-
var _a;
|
|
29
|
-
if (!this._observers.has(key)) {
|
|
30
|
-
this._observers.set(key, /* @__PURE__ */ new Set());
|
|
31
|
-
}
|
|
32
|
-
(_a = this._observers.get(key)) == null ? void 0 : _a.add(callback);
|
|
33
|
-
return () => {
|
|
34
|
-
var _a2;
|
|
35
|
-
(_a2 = this._observers.get(key)) == null ? void 0 : _a2.delete(callback);
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
_notifyObservers(key, value) {
|
|
39
|
-
var _a;
|
|
40
|
-
(_a = this._observers.get(key)) == null ? void 0 : _a.forEach(
|
|
41
|
-
(callback) => callback(value)
|
|
42
|
-
);
|
|
43
|
-
},
|
|
44
|
-
_addRule(rule) {
|
|
45
|
-
if (this._sheet) {
|
|
46
|
-
this._sheet.insertRule(rule, this._sheet.cssRules.length);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
var UIState_default = UIState;
|
|
51
|
-
|
|
52
|
-
// src/react/index.ts
|
|
53
|
-
import { useState, useEffect } from "react";
|
|
54
|
-
function useUIState(key, initialValue) {
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
UIState_default.init();
|
|
57
|
-
}, []);
|
|
58
|
-
const [state, setState] = useState(() => {
|
|
59
|
-
try {
|
|
60
|
-
const value = UIState_default.getState(key);
|
|
61
|
-
return value !== void 0 ? value : initialValue;
|
|
62
|
-
} catch (e) {
|
|
63
|
-
return initialValue;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
return UIState_default.observe(key, (value) => {
|
|
68
|
-
setState(value);
|
|
69
|
-
});
|
|
70
|
-
}, [key]);
|
|
71
|
-
const setUIState = (value) => {
|
|
72
|
-
UIState_default.setState(key, value);
|
|
73
|
-
};
|
|
74
|
-
return [state, setUIState];
|
|
75
|
-
}
|
|
76
|
-
export {
|
|
77
|
-
UIState_default as UIState,
|
|
78
|
-
useUIState
|
|
79
|
-
};
|
package/src/UIState.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
type StateObserver<T = any> = (value: T) => void;
|
|
2
|
-
|
|
3
|
-
interface UIStateType {
|
|
4
|
-
_sheet: CSSStyleSheet | null;
|
|
5
|
-
_observers: Map<string, Set<StateObserver>>;
|
|
6
|
-
init(): UIStateType;
|
|
7
|
-
setState<T>(key: string, value: T): void;
|
|
8
|
-
getState<T>(key: string): T;
|
|
9
|
-
observe<T>(key: string, callback: StateObserver<T>): () => void;
|
|
10
|
-
_notifyObservers<T>(key: string, value: T): void;
|
|
11
|
-
_addRule(rule: string): void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const UIState: UIStateType = {
|
|
15
|
-
_sheet: null,
|
|
16
|
-
_observers: new Map(),
|
|
17
|
-
|
|
18
|
-
init() {
|
|
19
|
-
if (!this._sheet) {
|
|
20
|
-
const style = document.createElement('style');
|
|
21
|
-
document.head.appendChild(style);
|
|
22
|
-
this._sheet = style.sheet;
|
|
23
|
-
this._addRule(':root {}');
|
|
24
|
-
}
|
|
25
|
-
return this;
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
setState<T>(key: string, value: T): void {
|
|
29
|
-
const cssValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
30
|
-
document.documentElement.style.setProperty(`--${key}`, cssValue);
|
|
31
|
-
this._notifyObservers(key, value);
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
getState<T>(key: string): T {
|
|
35
|
-
const value = getComputedStyle(document.documentElement)
|
|
36
|
-
.getPropertyValue(`--${key}`).trim();
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(value) as T;
|
|
39
|
-
} catch {
|
|
40
|
-
return value as unknown as T;
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
observe<T>(key: string, callback: StateObserver<T>): () => void {
|
|
45
|
-
if (!this._observers.has(key)) {
|
|
46
|
-
this._observers.set(key, new Set());
|
|
47
|
-
}
|
|
48
|
-
this._observers.get(key)?.add(callback as StateObserver);
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
this._observers.get(key)?.delete(callback as StateObserver);
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
_notifyObservers<T>(key: string, value: T): void {
|
|
56
|
-
this._observers.get(key)?.forEach(callback =>
|
|
57
|
-
(callback as StateObserver<T>)(value)
|
|
58
|
-
);
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
_addRule(rule: string): void {
|
|
62
|
-
if (this._sheet) {
|
|
63
|
-
this._sheet.insertRule(rule, this._sheet.cssRules.length);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export default UIState;
|
package/src/index.ts
DELETED
package/src/react/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import UIState from '../UIState';
|
|
3
|
-
|
|
4
|
-
export function useUIState<T>(key: string, initialValue: T): [T, (value: T) => void] {
|
|
5
|
-
// Initialize UIState if needed
|
|
6
|
-
useEffect(() => {
|
|
7
|
-
UIState.init();
|
|
8
|
-
}, []);
|
|
9
|
-
|
|
10
|
-
// Get initial state
|
|
11
|
-
const [state, setState] = useState<T>(() => {
|
|
12
|
-
try {
|
|
13
|
-
const value = UIState.getState<T>(key);
|
|
14
|
-
return value !== undefined ? value : initialValue;
|
|
15
|
-
} catch {
|
|
16
|
-
return initialValue;
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// Set up observer
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
return UIState.observe<T>(key, (value) => {
|
|
23
|
-
setState(value);
|
|
24
|
-
});
|
|
25
|
-
}, [key]);
|
|
26
|
-
|
|
27
|
-
// Return state and setter
|
|
28
|
-
const setUIState = (value: T) => {
|
|
29
|
-
UIState.setState(key, value);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return [state, setUIState];
|
|
33
|
-
}
|