@uistate/core 4.0.0 → 4.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
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/README.md CHANGED
@@ -1,37 +1,11 @@
1
- # @uistate/core v3.1.2
1
+ # UIstate
2
2
 
3
- **author**: Ajdin Imsirovic <ajdika@live.com> (GitHub)
4
- **maintainer**: uistate <ajdika.i@gmail.com> (npm)
3
+ A revolutionary approach to UI state management using CSS custom properties and DOM attributes, featuring Attribute-Driven State Inheritance (ADSI).
5
4
 
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.
5
+ **Current Version**: 4.1.1
7
6
 
8
- ## What's New in v3.1.1
9
-
10
- - 🛠️ Added `setStates()` method for batch state updates
11
- - 🧩 Added `registerSpecialHandler()` and `registerEventBinding()` for extensibility
12
- - 🏷️ New `setupObservers()` for automatic state-to-DOM binding
13
- - ⚡ Improved `setupStateActions()` with customizable event handling
14
- - 💾 Default event handlers for clicks and inputs with declarative binding
15
- - 🗡️ Enhanced attribute serialization with specialized handlers
16
-
17
- ## What's New in v3.0.0
18
-
19
- - 🔄 Fully declarative state management approach
20
- - 🧩 Enhanced template system with CSS-based templates
21
- - 🚀 Improved performance through optimized state propagation
22
- - 📦 New state serialization and inspection capabilities
23
- - 🔍 Telemetry plugin for better debugging
24
-
25
- ## Features
26
-
27
- - 🚀 O(1) state updates using CSS custom properties
28
- - 📉 Significant memory savings compared to virtual DOM approaches
29
- - 🎯 Zero configuration
30
- - 🔄 Automatic reactivity through CSS cascade
31
- - 🎨 Framework agnostic
32
- - 📦 Tiny bundle size (~2KB)
33
- - 🧩 Modular architecture with dedicated modules for CSS state and templates
34
- - 📝 Declarative HTML-in-CSS templates
7
+ **Author**: Ajdin Imsirovic <ajdika@live.com> (GitHub)
8
+ **Maintainer**: uistate <ajdika.i@gmail.com> (npm)
35
9
 
36
10
  ## Installation
37
11
 
@@ -39,85 +13,73 @@ High-performance UI state management using CSS custom properties and ADSI (Attri
39
13
  npm install @uistate/core
40
14
  ```
41
15
 
42
- ## Why @uistate/core?
43
-
44
- ### Performance
16
+ ## Quick Start
45
17
 
46
- - **CSS-Driven Updates**: Leverages browser's CSS engine for optimal performance with O(1) complexity
47
- - **DOM as Source of Truth**: Efficient state storage using CSS custom properties and data attributes
48
- - **Minimal Overhead**: No virtual DOM diffing or shadow DOM needed
18
+ UIstate v4.1.1 provides four core modules that can be imported individually:
49
19
 
50
- ### Developer Experience
20
+ ```javascript
21
+ import { createCssState, createEventState, stateSerializer, createTemplateManager } from '@uistate/core';
51
22
 
52
- - **Declarative API**: Define UI structure in CSS templates
53
- - **Framework Agnostic**: Works with any framework or vanilla JavaScript
54
- - **Zero Config**: No store setup, no reducers, no actions
55
- - **CSS-Native**: Leverages the power of CSS selectors and the cascade
23
+ // Create CSS-based state management
24
+ const cssState = createCssState();
56
25
 
57
- ### Core Concepts
26
+ // Create event-based state management
27
+ const eventState = createEventState();
58
28
 
59
- - **Attribute-Driven State Inheritance (ADSI)**: State represented both as CSS variables and data attributes
60
- - **Declarative Templates**: Define UI components directly in CSS
61
- - **Automatic State Propagation**: State changes automatically update the UI
29
+ // Use state serialization utilities
30
+ const serialized = stateSerializer.serialize(myState);
62
31
 
63
- ## Project Structure
64
-
65
- ```
66
- @uistate/core/
67
- ├── src/ # Core library
68
- │ ├── index.js # Main entry
69
- │ ├── cssState.js # CSS variables management
70
- │ ├── templateManager.js # Declarative template management
71
- │ ├── stateInspector.js # State inspection tools
72
- │ └── stateSerializer.js # State serialization
73
- └── examples/ # Example applications
74
- └── 001-.../ # Progressive examples from simpler to more complex
32
+ // Create template manager for declarative UI
33
+ const templateManager = createTemplateManager();
75
34
  ```
76
35
 
77
- ## Browser Support
36
+ ## Core Modules
78
37
 
79
- - Chrome 60+
80
- - Firefox 54+
81
- - Safari 10.1+
82
- - Edge 79+
83
-
84
- # Core Ideas Behind UIstate
38
+ ### `createCssState`
39
+ Manages state using CSS custom properties for optimal performance and automatic reactivity.
85
40
 
86
- 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.
41
+ ### `createEventState`
42
+ Provides event-driven state management with pub/sub patterns.
87
43
 
88
- ## Key Components
44
+ ### `stateSerializer`
45
+ Utilities for serializing and deserializing state data.
89
46
 
90
- ### UIstate Core
47
+ ### `createTemplateManager`
48
+ Declarative template management system for building UIs with CSS-based templates.
91
49
 
92
- The main UIstate object provides methods to manage state and templates:
50
+ ## Key Features
93
51
 
94
- - **init()**: Initialize the UIstate system
95
- - **setState()**: Set state values
96
- - **getState()**: Get state values
97
- - **subscribe()**: Subscribe to state changes
98
- - **observe()**: Observe state paths for changes
52
+ - Potentially O(1) state updates
53
+ - Significant memory savings compared to virtual DOM approaches
54
+ - DOM as the single source of truth
55
+ - CSS-driven state derivation
56
+ - Framework agnostic
57
+ - Tiny bundle size (~30KB uncompressed, ~8-10KB gzipped)
58
+ - Zero dependencies
59
+ - Modular architecture - import only what you need
99
60
 
100
- ### Template Manager
61
+ ## Browser Support
101
62
 
102
- The template manager provides tools for declarative UI rendering:
63
+ - Chrome 60+
64
+ - Firefox 54+
65
+ - Safari 10.1+
66
+ - Edge 79+
103
67
 
104
- - **renderTemplateFromCss()**: Render UI components from CSS-defined templates
105
- - **registerActions()**: Register event handlers for UI components
106
- - **attachDelegation()**: Set up event delegation for efficient event handling
68
+ ## Philosophy
107
69
 
108
- ## Key Features
70
+ UIstate challenges traditional assumptions in web development by using the DOM as the source of truth for state, leveraging CSS variables and data attributes for state storage, and using the CSS cascade for state inheritance and derivation.
109
71
 
110
- 1. Uses CSS custom properties as a storage mechanism, making state changes automatically trigger UI updates
111
- 2. Provides a clear separation between state storage (CSS) and behavior (JavaScript)
112
- 3. Implements a pub/sub pattern for reactive updates
113
- 4. Leverages CSS templates for declarative UI definition
72
+ The v4.1.0 release focuses on simplicity and modularity - providing clean, individual modules that can be composed together as needed, without the complexity of a unified framework.
114
73
 
115
- This implementation is particularly useful for building UI components with clean separation of concerns, optimal performance, and a fully declarative approach.
74
+ ## Examples
116
75
 
117
- ## Contributing
76
+ Explore our documentation and examples to see UIstate in action:
118
77
 
119
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
78
+ - Range sliders with different state derivation approaches
79
+ - Button toggles with CSS state projection
80
+ - Font adjusters with domain-based state management
81
+ - And more!
120
82
 
121
83
  ## License
122
84
 
123
- MIT © Ajdin Imsirovic
85
+ MIT Ajdin Imsirovic
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "@uistate/core",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
+ "description": "Revolutionary DOM-based state management using CSS custom properties - zero dependencies, potential O(1) updates",
4
5
  "type": "module",
5
- "description": "High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance)",
6
- "main": "src/index.js",
7
- "module": "src/index.js",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./cssState": "./cssState.js",
10
+ "./eventState": "./eventState.js",
11
+ "./stateSerializer": "./stateSerializer.js",
12
+ "./templateManager": "./templateManager.js"
13
+ },
8
14
  "files": [
9
- "src"
15
+ "index.js",
16
+ "cssState.js",
17
+ "eventState.js",
18
+ "stateSerializer.js",
19
+ "templateManager.js"
10
20
  ],
11
- "scripts": {},
12
21
  "keywords": [
13
22
  "state-management",
14
- "ui",
15
- "css",
16
- "performance",
17
- "framework-agnostic",
18
- "vanilla-js"
19
- ],
20
- "author": "Ajdin Imsirovic <ajdika@live.com> (GitHub)",
21
- "contributors": [
22
- "uistate <ajdika.i@gmail.com> (npm)"
23
+ "css-variables",
24
+ "dom-events",
25
+ "zero-dependency",
26
+ "modular"
23
27
  ],
28
+ "author": "Ajdin Imsirovic",
24
29
  "license": "MIT",
25
30
  "repository": {
26
31
  "type": "git",
27
- "url": "git+https://github.com/ImsirovicAjdin/uistate.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/ImsirovicAjdin/uistate/issues"
31
- },
32
- "homepage": "https://github.com/ImsirovicAjdin/uistate#readme",
33
- "sideEffects": false
32
+ "url": "https://github.com/ImsirovicAjdin/uistate"
33
+ }
34
34
  }
@@ -1,140 +0,0 @@
1
- /**
2
- * StateInspector - Real-time state inspection panel for UIstate
3
- *
4
- * This module provides a configurable UI panel that displays and allows manipulation
5
- * of CSS variables and state values in UIstate applications.
6
- *
7
- * Features:
8
- * - Toggle visibility with a dedicated button
9
- * - Real-time updates of CSS variable values
10
- * - Organized display of state by categories
11
- * - Ability to filter state variables
12
- * - Direct manipulation of state values
13
- */
14
-
15
- /**
16
- * Create a configured state inspector instance
17
- * @param {Object} config - Configuration options
18
- * @returns {Object} - StateInspector instance
19
- */
20
- function createStateInspector(config = {}) {
21
- // Default configuration
22
- const defaultConfig = {
23
- target: document.body,
24
- initiallyVisible: false,
25
- categories: ['app', 'ui', 'data'],
26
- position: 'bottom-right',
27
- maxHeight: '300px',
28
- stateManager: null, // Optional reference to UIstate or CssState instance
29
- theme: 'light'
30
- };
31
-
32
- // Merge provided config with defaults
33
- const options = { ...defaultConfig, ...config };
34
-
35
- // Private state
36
- let isVisible = options.initiallyVisible;
37
- let panel = null;
38
- let toggleButton = null;
39
- let filterInput = null;
40
- let stateContainer = null;
41
- let currentFilter = '';
42
-
43
- // StateInspector instance
44
- const inspector = {
45
- // Current configuration
46
- config: options,
47
-
48
- /**
49
- * Update configuration
50
- * @param {Object} newConfig - New configuration options
51
- */
52
- configure(newConfig) {
53
- Object.assign(this.config, newConfig);
54
- this.refresh();
55
- },
56
-
57
- /**
58
- * Attach the inspector panel to the target element
59
- * @param {Element} element - Target element to attach to (defaults to config.target)
60
- * @returns {Object} - The inspector instance for chaining
61
- */
62
- attach(element = this.config.target) {
63
- // Implementation will create and attach the panel
64
- // For now, this is a placeholder
65
- console.log('StateInspector: Panel would be attached to', element);
66
- return this;
67
- },
68
-
69
- /**
70
- * Show the inspector panel
71
- * @returns {Object} - The inspector instance for chaining
72
- */
73
- show() {
74
- isVisible = true;
75
- if (panel) panel.style.display = 'block';
76
- return this;
77
- },
78
-
79
- /**
80
- * Hide the inspector panel
81
- * @returns {Object} - The inspector instance for chaining
82
- */
83
- hide() {
84
- isVisible = false;
85
- if (panel) panel.style.display = 'none';
86
- return this;
87
- },
88
-
89
- /**
90
- * Toggle the visibility of the inspector panel
91
- * @returns {Object} - The inspector instance for chaining
92
- */
93
- toggle() {
94
- return isVisible ? this.hide() : this.show();
95
- },
96
-
97
- /**
98
- * Refresh the state display
99
- * @returns {Object} - The inspector instance for chaining
100
- */
101
- refresh() {
102
- // Implementation will update the displayed state values
103
- // For now, this is a placeholder
104
- console.log('StateInspector: State display would be refreshed');
105
- return this;
106
- },
107
-
108
- /**
109
- * Filter the displayed state variables
110
- * @param {string} filterText - Text to filter by
111
- * @returns {Object} - The inspector instance for chaining
112
- */
113
- filter(filterText) {
114
- currentFilter = filterText;
115
- // Implementation will filter the displayed variables
116
- return this.refresh();
117
- },
118
-
119
- /**
120
- * Clean up resources used by the inspector
121
- */
122
- destroy() {
123
- if (panel && panel.parentNode) {
124
- panel.parentNode.removeChild(panel);
125
- }
126
- panel = null;
127
- toggleButton = null;
128
- filterInput = null;
129
- stateContainer = null;
130
- }
131
- };
132
-
133
- return inspector;
134
- }
135
-
136
- // Create a default instance
137
- const StateInspector = createStateInspector();
138
-
139
- export default StateInspector;
140
- export { createStateInspector };
@@ -1,638 +0,0 @@
1
- /**
2
- * UIstate Telemetry Plugin
3
- *
4
- * A plugin for tracking and analyzing usage patterns in UIstate applications.
5
- * Provides insights into method calls, state changes, and performance metrics.
6
- *
7
- * Features:
8
- * - Automatic tracking of API method calls
9
- * - State change monitoring
10
- * - Performance timing
11
- * - Usage statistics and reporting
12
- * - Visual dashboard integration
13
- */
14
-
15
- /**
16
- * Create a telemetry plugin instance
17
- * @param {Object} config - Configuration options
18
- * @returns {Object} - Configured telemetry plugin
19
- */
20
- const createTelemetryPlugin = (config = {}) => {
21
- // Default configuration
22
- const defaultConfig = {
23
- enabled: true,
24
- trackStateChanges: true,
25
- trackMethodCalls: true,
26
- trackPerformance: true,
27
- maxEntries: 1000,
28
- logToConsole: false,
29
- samplingRate: 1.0, // 1.0 = track everything, 0.5 = track 50% of events
30
- };
31
-
32
- // Merge provided config with defaults
33
- const options = { ...defaultConfig, ...config };
34
-
35
- // Telemetry data storage
36
- const data = {
37
- startTime: Date.now(),
38
- methodCalls: new Map(),
39
- stateChanges: new Map(),
40
- performanceMetrics: new Map(),
41
- unusedMethods: new Set(),
42
- activeTimers: new Map()
43
- };
44
-
45
- // Reference to the UIstate instance
46
- let uistateRef = null;
47
-
48
- // Original method references
49
- const originalMethods = new Map();
50
-
51
- // Methods to track (will be populated during initialization)
52
- const methodsToTrack = [
53
- 'getState', 'setState', 'subscribe', 'observe',
54
- 'configureSerializer', 'getSerializerConfig', 'setSerializationMode',
55
- 'onAction', 'registerActions', 'attachDelegation', 'mount',
56
- 'renderTemplateFromCss', 'createComponent', 'applyClassesFromState'
57
- ];
58
-
59
- // Helper function to check if we should sample this event
60
- const shouldSample = () => {
61
- return options.samplingRate >= 1.0 || Math.random() <= options.samplingRate;
62
- };
63
-
64
- // Helper function to track a method call
65
- const trackMethodCall = (methodName, args = [], result = undefined, error = null, duration = 0) => {
66
- if (!options.enabled || !options.trackMethodCalls || !shouldSample()) return;
67
-
68
- if (!data.methodCalls.has(methodName)) {
69
- data.methodCalls.set(methodName, []);
70
- }
71
-
72
- const calls = data.methodCalls.get(methodName);
73
-
74
- // Limit the number of entries to prevent memory issues
75
- if (calls.length >= options.maxEntries) {
76
- calls.shift(); // Remove oldest entry
77
- }
78
-
79
- // Create a safe copy of arguments (avoiding circular references)
80
- const safeArgs = Array.from(args).map(arg => {
81
- try {
82
- // For simple types, return as is
83
- if (arg === null || arg === undefined ||
84
- typeof arg === 'string' ||
85
- typeof arg === 'number' ||
86
- typeof arg === 'boolean') {
87
- return arg;
88
- }
89
-
90
- // For functions, just return the function name or "[Function]"
91
- if (typeof arg === 'function') {
92
- return arg.name || "[Function]";
93
- }
94
-
95
- // For objects, create a simplified representation
96
- if (typeof arg === 'object') {
97
- if (Array.isArray(arg)) {
98
- return `[Array(${arg.length})]`;
99
- }
100
- return Object.keys(arg).length > 0
101
- ? `[Object: ${Object.keys(arg).join(', ')}]`
102
- : '[Object]';
103
- }
104
-
105
- return String(arg);
106
- } catch (e) {
107
- return "[Complex Value]";
108
- }
109
- });
110
-
111
- // Create a safe copy of the result
112
- let safeResult;
113
- try {
114
- if (result === null || result === undefined ||
115
- typeof result === 'string' ||
116
- typeof result === 'number' ||
117
- typeof result === 'boolean') {
118
- safeResult = result;
119
- } else if (typeof result === 'function') {
120
- safeResult = result.name || "[Function]";
121
- } else if (typeof result === 'object') {
122
- if (Array.isArray(result)) {
123
- safeResult = `[Array(${result.length})]`;
124
- } else {
125
- safeResult = Object.keys(result).length > 0
126
- ? `[Object: ${Object.keys(result).join(', ')}]`
127
- : '[Object]';
128
- }
129
- } else {
130
- safeResult = String(result);
131
- }
132
- } catch (e) {
133
- safeResult = "[Complex Value]";
134
- }
135
-
136
- // Record the call
137
- calls.push({
138
- timestamp: Date.now(),
139
- relativeTime: Date.now() - data.startTime,
140
- args: safeArgs,
141
- result: safeResult,
142
- error: error ? error.message : null,
143
- duration
144
- });
145
-
146
- // Log to console if enabled
147
- if (options.logToConsole) {
148
- console.log(`[Telemetry] ${methodName}`, {
149
- args: safeArgs,
150
- result: safeResult,
151
- error: error ? error.message : null,
152
- duration
153
- });
154
- }
155
- };
156
-
157
- // Helper function to track state changes
158
- const trackStateChange = (path, value, previousValue) => {
159
- if (!options.enabled || !options.trackStateChanges || !shouldSample()) return;
160
-
161
- if (!data.stateChanges.has(path)) {
162
- data.stateChanges.set(path, []);
163
- }
164
-
165
- const changes = data.stateChanges.get(path);
166
-
167
- // Limit the number of entries to prevent memory issues
168
- if (changes.length >= options.maxEntries) {
169
- changes.shift(); // Remove oldest entry
170
- }
171
-
172
- // Create safe copies of values
173
- let safeValue, safePreviousValue;
174
- try {
175
- safeValue = JSON.stringify(value);
176
- } catch (e) {
177
- safeValue = String(value);
178
- }
179
-
180
- try {
181
- safePreviousValue = JSON.stringify(previousValue);
182
- } catch (e) {
183
- safePreviousValue = String(previousValue);
184
- }
185
-
186
- // Record the change
187
- changes.push({
188
- timestamp: Date.now(),
189
- relativeTime: Date.now() - data.startTime,
190
- value: safeValue,
191
- previousValue: safePreviousValue
192
- });
193
- };
194
-
195
- // Helper function to start a performance timer
196
- const startTimer = (label) => {
197
- if (!options.enabled || !options.trackPerformance) return;
198
-
199
- data.activeTimers.set(label, performance.now());
200
- };
201
-
202
- // Helper function to end a performance timer
203
- const endTimer = (label) => {
204
- if (!options.enabled || !options.trackPerformance) return 0;
205
-
206
- if (!data.activeTimers.has(label)) return 0;
207
-
208
- const startTime = data.activeTimers.get(label);
209
- const duration = performance.now() - startTime;
210
-
211
- data.activeTimers.delete(label);
212
-
213
- if (!data.performanceMetrics.has(label)) {
214
- data.performanceMetrics.set(label, {
215
- count: 0,
216
- totalDuration: 0,
217
- minDuration: Infinity,
218
- maxDuration: 0,
219
- samples: []
220
- });
221
- }
222
-
223
- const metrics = data.performanceMetrics.get(label);
224
- metrics.count++;
225
- metrics.totalDuration += duration;
226
- metrics.minDuration = Math.min(metrics.minDuration, duration);
227
- metrics.maxDuration = Math.max(metrics.maxDuration, duration);
228
-
229
- // Store sample data (limited to prevent memory issues)
230
- if (metrics.samples.length >= options.maxEntries) {
231
- metrics.samples.shift();
232
- }
233
-
234
- metrics.samples.push({
235
- timestamp: Date.now(),
236
- relativeTime: Date.now() - data.startTime,
237
- duration
238
- });
239
-
240
- return duration;
241
- };
242
-
243
- // Method proxying function
244
- const createMethodProxy = (obj, methodName) => {
245
- // Store original method
246
- const originalMethod = obj[methodName];
247
- originalMethods.set(methodName, originalMethod);
248
-
249
- // Create proxy
250
- return function(...args) {
251
- if (!options.enabled) {
252
- return originalMethod.apply(this, args);
253
- }
254
-
255
- let result, error;
256
-
257
- startTimer(`method.${methodName}`);
258
-
259
- try {
260
- // Call original method
261
- result = originalMethod.apply(this, args);
262
-
263
- // Handle promises
264
- if (result instanceof Promise) {
265
- return result
266
- .then(asyncResult => {
267
- const duration = endTimer(`method.${methodName}`);
268
- trackMethodCall(methodName, args, asyncResult, null, duration);
269
- return asyncResult;
270
- })
271
- .catch(asyncError => {
272
- const duration = endTimer(`method.${methodName}`);
273
- trackMethodCall(methodName, args, undefined, asyncError, duration);
274
- throw asyncError;
275
- });
276
- }
277
-
278
- const duration = endTimer(`method.${methodName}`);
279
- trackMethodCall(methodName, args, result, null, duration);
280
- return result;
281
-
282
- } catch (e) {
283
- error = e;
284
- const duration = endTimer(`method.${methodName}`);
285
- trackMethodCall(methodName, args, undefined, error, duration);
286
- throw error;
287
- }
288
- };
289
- };
290
-
291
- // Create the telemetry plugin
292
- const telemetryPlugin = {
293
- name: 'telemetry',
294
-
295
- // Initialize the plugin
296
- init(api) {
297
- if (!options.enabled) return this;
298
-
299
- uistateRef = api;
300
-
301
- // Proxy methods for tracking
302
- if (options.trackMethodCalls) {
303
- methodsToTrack.forEach(methodName => {
304
- if (typeof api[methodName] === 'function') {
305
- // Add to list of methods to check for usage
306
- data.unusedMethods.add(methodName);
307
-
308
- // Create proxy
309
- api[methodName] = createMethodProxy(api, methodName);
310
- }
311
- });
312
- }
313
-
314
- return this;
315
- },
316
-
317
- // Middleware for state changes
318
- middlewares: [
319
- (path, value, getState) => {
320
- if (options.enabled && options.trackStateChanges) {
321
- const previousValue = getState(path);
322
- startTimer(`stateChange.${path}`);
323
-
324
- // Return a proxy that will track after the state change
325
- return {
326
- __telemetryValue: true,
327
- value,
328
- path,
329
- previousValue,
330
- finalize() {
331
- const duration = endTimer(`stateChange.${path}`);
332
- trackStateChange(path, value, previousValue);
333
- return value;
334
- }
335
- };
336
- }
337
- return value;
338
- }
339
- ],
340
-
341
- // Lifecycle hooks
342
- hooks: {
343
- beforeStateChange: (path, value) => {
344
- // If this is our proxy value, do nothing
345
- if (value && value.__telemetryValue) {
346
- return;
347
- }
348
- },
349
-
350
- afterStateChange: (path, value) => {
351
- // If this is our proxy value, finalize it
352
- if (value && value.__telemetryValue) {
353
- return value.finalize();
354
- }
355
- }
356
- },
357
-
358
- // Plugin methods
359
- methods: {
360
- // Enable or disable telemetry
361
- setEnabled(enabled) {
362
- options.enabled = enabled;
363
- return this;
364
- },
365
-
366
- // Get current configuration
367
- getConfig() {
368
- return { ...options };
369
- },
370
-
371
- // Update configuration
372
- configure(newConfig) {
373
- Object.assign(options, newConfig);
374
- return this;
375
- },
376
-
377
- // Reset telemetry data
378
- reset() {
379
- data.startTime = Date.now();
380
- data.methodCalls.clear();
381
- data.stateChanges.clear();
382
- data.performanceMetrics.clear();
383
- data.activeTimers.clear();
384
- return this;
385
- },
386
-
387
- // Get method call statistics
388
- getMethodStats() {
389
- const stats = {};
390
-
391
- data.methodCalls.forEach((calls, methodName) => {
392
- // Mark method as used
393
- data.unusedMethods.delete(methodName);
394
-
395
- stats[methodName] = {
396
- count: calls.length,
397
- averageDuration: calls.reduce((sum, call) => sum + (call.duration || 0), 0) / calls.length,
398
- lastCall: calls[calls.length - 1],
399
- firstCall: calls[0]
400
- };
401
- });
402
-
403
- return stats;
404
- },
405
-
406
- // Get detailed method call data
407
- getMethodCalls(methodName) {
408
- if (!methodName) {
409
- const result = {};
410
- data.methodCalls.forEach((calls, method) => {
411
- result[method] = [...calls];
412
- });
413
- return result;
414
- }
415
-
416
- return data.methodCalls.has(methodName)
417
- ? [...data.methodCalls.get(methodName)]
418
- : [];
419
- },
420
-
421
- // Get state change statistics
422
- getStateStats() {
423
- const stats = {};
424
-
425
- data.stateChanges.forEach((changes, path) => {
426
- stats[path] = {
427
- count: changes.length,
428
- lastChange: changes[changes.length - 1],
429
- firstChange: changes[0]
430
- };
431
- });
432
-
433
- return stats;
434
- },
435
-
436
- // Get detailed state change data
437
- getStateChanges(path) {
438
- if (!path) {
439
- const result = {};
440
- data.stateChanges.forEach((changes, statePath) => {
441
- result[statePath] = [...changes];
442
- });
443
- return result;
444
- }
445
-
446
- return data.stateChanges.has(path)
447
- ? [...data.stateChanges.get(path)]
448
- : [];
449
- },
450
-
451
- // Get performance metrics
452
- getPerformanceMetrics() {
453
- const metrics = {};
454
-
455
- data.performanceMetrics.forEach((data, label) => {
456
- metrics[label] = {
457
- count: data.count,
458
- totalDuration: data.totalDuration,
459
- averageDuration: data.totalDuration / data.count,
460
- minDuration: data.minDuration,
461
- maxDuration: data.maxDuration
462
- };
463
- });
464
-
465
- return metrics;
466
- },
467
-
468
- // Get unused methods
469
- getUnusedMethods() {
470
- return [...data.unusedMethods];
471
- },
472
-
473
- // Get comprehensive telemetry report
474
- getReport() {
475
- return {
476
- startTime: data.startTime,
477
- duration: Date.now() - data.startTime,
478
- methodStats: this.getMethodStats(),
479
- stateStats: this.getStateStats(),
480
- performanceMetrics: this.getPerformanceMetrics(),
481
- unusedMethods: this.getUnusedMethods(),
482
- config: this.getConfig(),
483
- // Add detailed logs for download
484
- detailedLogs: {
485
- methodCalls: this.getMethodCalls(),
486
- stateChanges: this.getStateChanges()
487
- }
488
- };
489
- },
490
-
491
- // Download telemetry logs as a JSON file
492
- downloadLogs() {
493
- const report = this.getReport();
494
- const data = JSON.stringify(report, null, 2);
495
- const blob = new Blob([data], { type: 'application/json' });
496
- const url = URL.createObjectURL(blob);
497
-
498
- const a = document.createElement('a');
499
- a.href = url;
500
- a.download = `uistate-telemetry-${new Date().toISOString().replace(/:/g, '-')}.json`;
501
- document.body.appendChild(a);
502
- a.click();
503
- document.body.removeChild(a);
504
- URL.revokeObjectURL(url);
505
-
506
- return true;
507
- },
508
-
509
- // Create a visual dashboard
510
- createDashboard(container = document.body) {
511
- // Create dashboard element
512
- const dashboard = document.createElement('div');
513
- dashboard.className = 'uistate-telemetry-dashboard';
514
- dashboard.style.cssText = `
515
- position: fixed;
516
- bottom: 10px;
517
- right: 10px;
518
- width: 300px;
519
- max-height: 400px;
520
- overflow: auto;
521
- background: #fff;
522
- border: 1px solid #ccc;
523
- border-radius: 4px;
524
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
525
- padding: 10px;
526
- font-family: monospace;
527
- font-size: 12px;
528
- z-index: 9999;
529
- `;
530
-
531
- // Update function
532
- const updateDashboard = () => {
533
- const report = this.getReport();
534
-
535
- dashboard.innerHTML = `
536
- <h3 style="margin: 0 0 10px; font-size: 14px;">UIstate Telemetry</h3>
537
- <div style="margin-bottom: 5px;">
538
- <button id="telemetry-refresh">Refresh</button>
539
- <button id="telemetry-reset">Reset</button>
540
- <button id="telemetry-close">Close</button>
541
- </div>
542
- <div style="margin: 10px 0;">
543
- <strong>Duration:</strong> ${Math.round((Date.now() - report.startTime) / 1000)}s
544
- </div>
545
-
546
- <h4 style="margin: 10px 0 5px; font-size: 13px;">Method Calls</h4>
547
- <table style="width: 100%; border-collapse: collapse;">
548
- <tr>
549
- <th style="text-align: left; border-bottom: 1px solid #eee;">Method</th>
550
- <th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
551
- <th style="text-align: right; border-bottom: 1px solid #eee;">Avg Time</th>
552
- </tr>
553
- ${Object.entries(report.methodStats)
554
- .sort((a, b) => b[1].count - a[1].count)
555
- .map(([method, stats]) => `
556
- <tr>
557
- <td style="padding: 2px 0; border-bottom: 1px solid #eee;">${method}</td>
558
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
559
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.averageDuration ? stats.averageDuration.toFixed(2) + 'ms' : 'n/a'}</td>
560
- </tr>
561
- `).join('')}
562
- </table>
563
-
564
- <h4 style="margin: 10px 0 5px; font-size: 13px;">State Changes</h4>
565
- <table style="width: 100%; border-collapse: collapse;">
566
- <tr>
567
- <th style="text-align: left; border-bottom: 1px solid #eee;">Path</th>
568
- <th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
569
- </tr>
570
- ${Object.entries(report.stateStats)
571
- .sort((a, b) => b[1].count - a[1].count)
572
- .map(([path, stats]) => `
573
- <tr>
574
- <td style="padding: 2px 0; border-bottom: 1px solid #eee;">${path}</td>
575
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
576
- </tr>
577
- `).join('')}
578
- </table>
579
-
580
- ${report.unusedMethods.length > 0 ? `
581
- <h4 style="margin: 10px 0 5px; font-size: 13px;">Unused Methods</h4>
582
- <div style="color: #666;">
583
- ${report.unusedMethods.join(', ')}
584
- </div>
585
- ` : ''}
586
- `;
587
-
588
- // Add event listeners
589
- dashboard.querySelector('#telemetry-refresh').addEventListener('click', updateDashboard);
590
- dashboard.querySelector('#telemetry-reset').addEventListener('click', () => {
591
- this.reset();
592
- updateDashboard();
593
- });
594
- dashboard.querySelector('#telemetry-close').addEventListener('click', () => {
595
- dashboard.remove();
596
- });
597
- };
598
-
599
- // Initial update
600
- updateDashboard();
601
-
602
- // Add to container
603
- container.appendChild(dashboard);
604
-
605
- return dashboard;
606
- }
607
- },
608
-
609
- // Clean up
610
- destroy() {
611
- // Restore original methods
612
- if (uistateRef) {
613
- originalMethods.forEach((originalMethod, methodName) => {
614
- if (uistateRef[methodName]) {
615
- uistateRef[methodName] = originalMethod;
616
- }
617
- });
618
- }
619
-
620
- // Clear data
621
- data.methodCalls.clear();
622
- data.stateChanges.clear();
623
- data.performanceMetrics.clear();
624
- data.activeTimers.clear();
625
- data.unusedMethods.clear();
626
-
627
- uistateRef = null;
628
- }
629
- };
630
-
631
- return telemetryPlugin;
632
- };
633
-
634
- // Create a default instance with standard configuration
635
- const telemetryPlugin = createTelemetryPlugin();
636
-
637
- export default telemetryPlugin;
638
- export { createTelemetryPlugin };
File without changes
File without changes
File without changes
File without changes
File without changes