@uistate/core 5.2.0 → 5.4.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.
Files changed (142) hide show
  1. package/index.js +2 -9
  2. package/package.json +4 -10
  3. package/queryClient.js +55 -0
  4. package/cssState.js +0 -212
  5. package/examples/001-counter/README.md +0 -44
  6. package/examples/001-counter/index.html +0 -33
  7. package/examples/002-counter-improved/README.md +0 -44
  8. package/examples/002-counter-improved/index.html +0 -47
  9. package/examples/003-input-reactive/README.md +0 -44
  10. package/examples/003-input-reactive/index.html +0 -33
  11. package/examples/004-computed-state/README.md +0 -45
  12. package/examples/004-computed-state/index.html +0 -65
  13. package/examples/005-conditional-rendering/README.md +0 -42
  14. package/examples/005-conditional-rendering/index.html +0 -39
  15. package/examples/006-list-rendering/README.md +0 -49
  16. package/examples/006-list-rendering/index.html +0 -63
  17. package/examples/007-form-validation/README.md +0 -52
  18. package/examples/007-form-validation/index.html +0 -102
  19. package/examples/008-undo-redo/README.md +0 -70
  20. package/examples/008-undo-redo/index.html +0 -108
  21. package/examples/009-localStorage-side-effects/README.md +0 -72
  22. package/examples/009-localStorage-side-effects/index.html +0 -57
  23. package/examples/010-decoupled-components/README.md +0 -74
  24. package/examples/010-decoupled-components/index.html +0 -93
  25. package/examples/011-async-patterns/README.md +0 -98
  26. package/examples/011-async-patterns/index.html +0 -132
  27. package/examples/028-counter-improved-eventTest/LICENSE +0 -55
  28. package/examples/028-counter-improved-eventTest/README.md +0 -131
  29. package/examples/028-counter-improved-eventTest/app/store.js +0 -9
  30. package/examples/028-counter-improved-eventTest/index.html +0 -49
  31. package/examples/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +0 -282
  32. package/examples/028-counter-improved-eventTest/runtime/core/eventState.js +0 -100
  33. package/examples/028-counter-improved-eventTest/runtime/core/eventStateNew.js +0 -149
  34. package/examples/028-counter-improved-eventTest/runtime/core/helpers.js +0 -212
  35. package/examples/028-counter-improved-eventTest/runtime/core/router.js +0 -271
  36. package/examples/028-counter-improved-eventTest/store.d.ts +0 -8
  37. package/examples/028-counter-improved-eventTest/style.css +0 -170
  38. package/examples/028-counter-improved-eventTest/tests/README.md +0 -208
  39. package/examples/028-counter-improved-eventTest/tests/counter.test.js +0 -116
  40. package/examples/028-counter-improved-eventTest/tests/eventTest.js +0 -176
  41. package/examples/028-counter-improved-eventTest/tests/generateTypes.js +0 -168
  42. package/examples/028-counter-improved-eventTest/tests/run.js +0 -20
  43. package/examples/030-todo-app-with-eventTest/LICENSE +0 -55
  44. package/examples/030-todo-app-with-eventTest/README.md +0 -121
  45. package/examples/030-todo-app-with-eventTest/app/router.js +0 -25
  46. package/examples/030-todo-app-with-eventTest/app/store.js +0 -16
  47. package/examples/030-todo-app-with-eventTest/app/views/home.js +0 -11
  48. package/examples/030-todo-app-with-eventTest/app/views/todoDemo.js +0 -88
  49. package/examples/030-todo-app-with-eventTest/index.html +0 -65
  50. package/examples/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  51. package/examples/030-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  52. package/examples/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  53. package/examples/030-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  54. package/examples/030-todo-app-with-eventTest/runtime/core/router.js +0 -271
  55. package/examples/030-todo-app-with-eventTest/store.d.ts +0 -18
  56. package/examples/030-todo-app-with-eventTest/style.css +0 -170
  57. package/examples/030-todo-app-with-eventTest/tests/README.md +0 -208
  58. package/examples/030-todo-app-with-eventTest/tests/eventTest.js +0 -176
  59. package/examples/030-todo-app-with-eventTest/tests/generateTypes.js +0 -189
  60. package/examples/030-todo-app-with-eventTest/tests/run.js +0 -20
  61. package/examples/030-todo-app-with-eventTest/tests/todos.test.js +0 -167
  62. package/examples/031-todo-app-with-eventTest/LICENSE +0 -55
  63. package/examples/031-todo-app-with-eventTest/README.md +0 -54
  64. package/examples/031-todo-app-with-eventTest/TUTORIAL.md +0 -390
  65. package/examples/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  66. package/examples/031-todo-app-with-eventTest/app/bridges.js +0 -113
  67. package/examples/031-todo-app-with-eventTest/app/router.js +0 -26
  68. package/examples/031-todo-app-with-eventTest/app/store.js +0 -15
  69. package/examples/031-todo-app-with-eventTest/app/views/home.js +0 -46
  70. package/examples/031-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  71. package/examples/031-todo-app-with-eventTest/devtools/dock.js +0 -41
  72. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  73. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  74. package/examples/031-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  75. package/examples/031-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  76. package/examples/031-todo-app-with-eventTest/index.html +0 -103
  77. package/examples/031-todo-app-with-eventTest/package-lock.json +0 -2184
  78. package/examples/031-todo-app-with-eventTest/package.json +0 -24
  79. package/examples/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  80. package/examples/031-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  81. package/examples/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  82. package/examples/031-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  83. package/examples/031-todo-app-with-eventTest/runtime/core/router.js +0 -271
  84. package/examples/031-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  85. package/examples/031-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  86. package/examples/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  87. package/examples/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  88. package/examples/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  89. package/examples/031-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  90. package/examples/031-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  91. package/examples/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  92. package/examples/031-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  93. package/examples/031-todo-app-with-eventTest/store.d.ts +0 -23
  94. package/examples/031-todo-app-with-eventTest/style.css +0 -170
  95. package/examples/031-todo-app-with-eventTest/tests/README.md +0 -208
  96. package/examples/031-todo-app-with-eventTest/tests/eventTest.js +0 -176
  97. package/examples/031-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  98. package/examples/031-todo-app-with-eventTest/tests/run.js +0 -20
  99. package/examples/031-todo-app-with-eventTest/tests/todos.test.js +0 -192
  100. package/examples/032-todo-app-with-eventTest/LICENSE +0 -55
  101. package/examples/032-todo-app-with-eventTest/README.md +0 -54
  102. package/examples/032-todo-app-with-eventTest/TUTORIAL.md +0 -390
  103. package/examples/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  104. package/examples/032-todo-app-with-eventTest/app/actions/index.js +0 -153
  105. package/examples/032-todo-app-with-eventTest/app/bridges.js +0 -113
  106. package/examples/032-todo-app-with-eventTest/app/router.js +0 -26
  107. package/examples/032-todo-app-with-eventTest/app/store.js +0 -15
  108. package/examples/032-todo-app-with-eventTest/app/views/home.js +0 -46
  109. package/examples/032-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  110. package/examples/032-todo-app-with-eventTest/devtools/dock.js +0 -41
  111. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  112. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  113. package/examples/032-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  114. package/examples/032-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  115. package/examples/032-todo-app-with-eventTest/index.html +0 -87
  116. package/examples/032-todo-app-with-eventTest/package-lock.json +0 -2184
  117. package/examples/032-todo-app-with-eventTest/package.json +0 -24
  118. package/examples/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  119. package/examples/032-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  120. package/examples/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  121. package/examples/032-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  122. package/examples/032-todo-app-with-eventTest/runtime/core/router.js +0 -271
  123. package/examples/032-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  124. package/examples/032-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  125. package/examples/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  126. package/examples/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  127. package/examples/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  128. package/examples/032-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  129. package/examples/032-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  130. package/examples/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  131. package/examples/032-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  132. package/examples/032-todo-app-with-eventTest/store.d.ts +0 -23
  133. package/examples/032-todo-app-with-eventTest/style.css +0 -170
  134. package/examples/032-todo-app-with-eventTest/tests/README.md +0 -208
  135. package/examples/032-todo-app-with-eventTest/tests/eventTest.js +0 -176
  136. package/examples/032-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  137. package/examples/032-todo-app-with-eventTest/tests/run.js +0 -20
  138. package/examples/032-todo-app-with-eventTest/tests/todos.test.js +0 -192
  139. package/playground/exercise001.html +0 -38
  140. package/playground/exercise002.html +0 -49
  141. package/stateSerializer.js +0 -267
  142. package/templateManager.js +0 -216
@@ -1,267 +0,0 @@
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
- function escapeCssValue(value) {
16
- if (typeof value !== 'string') return value;
17
- return value.replace(/[^\x20-\x7E]|[!;{}:()[\]/@,'"]/g, function(char) {
18
- const hex = char.charCodeAt(0).toString(16);
19
- return '\\' + hex + ' ';
20
- });
21
- }
22
-
23
- function unescapeCssValue(value) {
24
- if (typeof value !== 'string') return value;
25
- // Only perform unescaping if there are escape sequences
26
- if (!value.includes('\\')) return value;
27
-
28
- return value.replace(/\\([0-9a-f]{1,6})\s?/gi, function(match, hex) {
29
- return String.fromCharCode(parseInt(hex, 16));
30
- });
31
- }
32
-
33
- /**
34
- * Create a configured serializer instance
35
- * @param {Object} config - Configuration options
36
- * @returns {Object} - Serializer instance
37
- */
38
- function createSerializer(config = {}) {
39
- // Default configuration
40
- const defaultConfig = {
41
- mode: 'hybrid', // 'escape', 'json', or 'hybrid'
42
- debug: false, // Enable debug logging
43
- complexThreshold: 3, // Object properties threshold for hybrid mode
44
- preserveTypes: true // Preserve type information in serialization
45
- };
46
-
47
- // Merge provided config with defaults
48
- const options = { ...defaultConfig, ...config };
49
-
50
- // Serializer instance
51
- const serializer = {
52
- // Current configuration
53
- config: options,
54
-
55
- /**
56
- * Update configuration
57
- * @param {Object} newConfig - New configuration options
58
- */
59
- configure(newConfig) {
60
- Object.assign(this.config, newConfig);
61
- if (this.config.debug) {
62
- console.log('StateSerializer config updated:', this.config);
63
- }
64
- },
65
-
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
- serialize(key, value) {
73
- // Handle null/undefined
74
- if (value === null || value === undefined) {
75
- return '';
76
- }
77
-
78
- const valueType = typeof value;
79
- const isComplex = valueType === 'object' &&
80
- (Array.isArray(value) ||
81
- (Object.keys(value).length >= this.config.complexThreshold));
82
-
83
- // Select serialization strategy based on configuration and value type
84
- if (this.config.mode === 'escape' ||
85
- (this.config.mode === 'hybrid' && !isComplex)) {
86
- // Use escape strategy for primitives or when escape mode is forced
87
- if (valueType === 'string') {
88
- return escapeCssValue(value);
89
- } else if (valueType === 'object') {
90
- // For simple objects in escape mode, still use JSON but with escaping
91
- const jsonStr = JSON.stringify(value);
92
- return escapeCssValue(jsonStr);
93
- } else {
94
- // For other primitives, convert to string
95
- return String(value);
96
- }
97
- } else {
98
- // Use JSON strategy for complex objects or when JSON mode is forced
99
- return JSON.stringify(value);
100
- }
101
- },
102
-
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
- deserialize(key, value) {
110
- // Handle empty values
111
- if (!value) return '';
112
-
113
- // Try JSON parse first for values that look like JSON
114
- if (this.config.mode !== 'escape' &&
115
- ((value.startsWith('{') && value.endsWith('}')) ||
116
- (value.startsWith('[') && value.endsWith(']')))) {
117
- try {
118
- return JSON.parse(value);
119
- } catch (e) {
120
- if (this.config.debug) {
121
- console.warn(`Failed to parse JSON for key "${key}":`, value);
122
- }
123
- // Fall through to unescaping if JSON parse fails
124
- }
125
- }
126
-
127
- // For non-JSON or escape mode, try unescaping
128
- const unescaped = unescapeCssValue(value);
129
-
130
- // If unescaped looks like JSON (might have been double-escaped), try parsing it
131
- if (this.config.mode !== 'escape' &&
132
- ((unescaped.startsWith('{') && unescaped.endsWith('}')) ||
133
- (unescaped.startsWith('[') && unescaped.endsWith(']')))) {
134
- try {
135
- return JSON.parse(unescaped);
136
- } catch (e) {
137
- // Not valid JSON, return unescaped string
138
- }
139
- }
140
-
141
- return unescaped;
142
- },
143
-
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
- serializeForAttribute(key, value) {
151
- if (value === null || value === undefined) return null;
152
-
153
- // For objects, use the standard serializer
154
- if (typeof value === 'object') {
155
- return this.serialize(key, value);
156
- }
157
-
158
- // For primitive values, use direct string conversion
159
- return String(value);
160
- },
161
-
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
- applyToAttributes(key, value, element = document.documentElement) {
169
- // Skip null/undefined values
170
- if (value === null || value === undefined) {
171
- element.removeAttribute(`data-${key}`);
172
- return;
173
- }
174
-
175
- // Handle objects specially
176
- if (typeof value === 'object') {
177
- // Set the main attribute with serialized value
178
- element.setAttribute(`data-${key}`, this.serialize(key, value));
179
-
180
- // For non-array objects, set individual property attributes
181
- if (!Array.isArray(value)) {
182
- Object.entries(value).forEach(([propKey, propValue]) => {
183
- const attributeKey = `data-${key}-${propKey.toLowerCase()}`;
184
- if (propValue !== null && propValue !== undefined) {
185
- if (typeof propValue === 'object') {
186
- element.setAttribute(
187
- attributeKey,
188
- this.serialize(`${key}.${propKey}`, propValue)
189
- );
190
- } else {
191
- element.setAttribute(attributeKey, propValue);
192
- }
193
- } else {
194
- element.removeAttribute(attributeKey);
195
- }
196
- });
197
- }
198
- } else {
199
- // For primitives, set directly
200
- element.setAttribute(`data-${key}`, value);
201
- }
202
- },
203
-
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
- needsComplexSerialization(value) {
210
- return typeof value === 'object' && value !== null;
211
- },
212
-
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
- setStateWithCss(uistate, path, value) {
221
- // Update UIstate
222
- uistate.setState(path, value);
223
-
224
- // Update CSS variable with properly serialized value
225
- const cssPath = path.replace(/\./g, '-');
226
- const serialized = this.serialize(path, value);
227
- document.documentElement.style.setProperty(`--${cssPath}`, serialized);
228
-
229
- // Update data attribute for root level state
230
- const segments = path.split('.');
231
- if (segments.length === 1) {
232
- document.documentElement.dataset[path] = typeof value === 'object'
233
- ? JSON.stringify(value)
234
- : value;
235
- }
236
-
237
- return value;
238
- },
239
-
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
- getStateFromCss(uistate, path) {
247
- // First try UIstate
248
- const value = uistate.getState(path);
249
- if (value !== undefined) return value;
250
-
251
- // If not found, try CSS variable
252
- const cssPath = path.replace(/\./g, '-');
253
- const cssValue = getComputedStyle(document.documentElement)
254
- .getPropertyValue(`--${cssPath}`).trim();
255
-
256
- return cssValue ? this.deserialize(path, cssValue) : undefined;
257
- }
258
- };
259
-
260
- return serializer;
261
- }
262
-
263
- // Create a default instance with hybrid mode
264
- const StateSerializer = createSerializer();
265
-
266
- export default StateSerializer;
267
- export { createSerializer, escapeCssValue, unescapeCssValue };
@@ -1,216 +0,0 @@
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
- /**
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
- registerActions(actionsMap) {
28
- Object.entries(actionsMap).forEach(([action, handler]) => {
29
- if (typeof handler === 'function') {
30
- // Simple function handler
31
- this.onAction(action, handler);
32
- } else if (typeof handler === 'object' && handler !== null) {
33
- // Handler with configuration
34
- const { fn, extractId = true, idAttribute = 'id' } = handler;
35
-
36
- if (typeof fn !== 'function') {
37
- throw new Error(`Handler for action '${action}' must be a function`);
38
- }
39
-
40
- this.onAction(action, (e) => {
41
- if (extractId) {
42
- const target = e.target.closest('[data-action]');
43
- // Look for common ID attributes in order of preference
44
- const id = target.dataset[idAttribute] ||
45
- target.dataset.actionId ||
46
- target.dataset.cardId ||
47
- target.dataset.itemId;
48
-
49
- fn(id, e, target);
50
- } else {
51
- fn(e);
52
- }
53
- });
54
- } else {
55
- throw new Error(`Invalid handler for action '${action}'`);
56
- }
57
- });
58
- return this;
59
- },
60
-
61
- attachDelegation(root = document.body) {
62
- root.addEventListener('click', e => {
63
- const target = e.target.closest('[data-action]');
64
- if (!target) return;
65
-
66
- const action = target.dataset.action;
67
- if (!action) return;
68
-
69
- const handler = this.handlers[action];
70
- if (typeof handler === 'function') {
71
- handler(e);
72
- } else if (target.dataset.value !== undefined && stateManager) {
73
- // If we have a state manager, use it to update state
74
- stateManager.setState(action, target.dataset.value);
75
- }
76
- });
77
- return this;
78
- },
79
-
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
- renderTemplateFromCss(templateName, data = {}) {
87
- const cssTemplate = getComputedStyle(document.documentElement)
88
- .getPropertyValue(`--template-${templateName}`)
89
- .trim()
90
- .replace(/^['"]|['"]$/g, ''); // Remove surrounding quotes
91
-
92
- if (!cssTemplate) throw new Error(`Template not found in CSS: --template-${templateName}`);
93
-
94
- let html = cssTemplate;
95
-
96
- // Replace all placeholders with actual data
97
- Object.entries(data).forEach(([key, value]) => {
98
- const regex = new RegExp(`{{${key}}}`, 'g');
99
- html = html.replace(regex, value);
100
- });
101
-
102
- // Create a temporary container
103
- const temp = document.createElement('div');
104
- temp.innerHTML = html;
105
-
106
- // Return the first child (the rendered template)
107
- return temp.firstElementChild;
108
- },
109
-
110
- mount(componentName, container) {
111
- const tpl = document.getElementById(`${componentName}-template`);
112
- if (!tpl) throw new Error(`Template not found: ${componentName}-template`);
113
- const clone = tpl.content.cloneNode(true);
114
-
115
- function resolvePlaceholders(fragment) {
116
- Array.from(fragment.querySelectorAll('*')).forEach(el => {
117
- const tag = el.tagName.toLowerCase();
118
- if (tag.endsWith('-placeholder')) {
119
- const name = tag.replace('-placeholder','');
120
- const childTpl = document.getElementById(`${name}-template`);
121
- if (!childTpl) throw new Error(`Template not found: ${name}-template`);
122
- const childClone = childTpl.content.cloneNode(true);
123
- resolvePlaceholders(childClone);
124
- el.replaceWith(childClone);
125
- }
126
- });
127
- }
128
-
129
- resolvePlaceholders(clone);
130
- container.appendChild(clone);
131
- return clone.firstElementChild;
132
- },
133
-
134
- // Helper to create a reactive component with automatic updates
135
- createComponent(name, renderFn, stateKeys = []) {
136
- if (!stateManager) {
137
- throw new Error('State manager is required for reactive components');
138
- }
139
-
140
- // Create template element if it doesn't exist
141
- let tpl = document.getElementById(`${name}-template`);
142
- if (!tpl) {
143
- tpl = document.createElement('template');
144
- tpl.id = `${name}-template`;
145
- document.body.appendChild(tpl);
146
- }
147
-
148
- // Initial render
149
- tpl.innerHTML = renderFn(stateManager);
150
-
151
- // Set up observers for reactive updates
152
- if (stateKeys.length > 0) {
153
- stateKeys.forEach(key => {
154
- stateManager.observe(key, () => {
155
- tpl.innerHTML = renderFn(stateManager);
156
- });
157
- });
158
- }
159
-
160
- return {
161
- mount: (container) => this.mount(name, container)
162
- };
163
- },
164
-
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
- applyClassesFromState(element, stateKey, options = {}) {
177
- if (!element) return element;
178
-
179
- const {
180
- prefix = '',
181
- clearExisting = false,
182
- namespace = ''
183
- } = typeof options === 'string' ? { prefix: options } : options;
184
-
185
- const prefixPath = prefix ? `${prefix}-` : '';
186
- const namespacePath = namespace ? `${namespace}-` : '';
187
-
188
- const classString = getComputedStyle(document.documentElement)
189
- .getPropertyValue(`--${namespacePath}${stateKey}-classes`)
190
- .trim()
191
- .replace(/^['"]|['"]$/g, '');
192
-
193
- if (classString) {
194
- // Clear existing classes if specified
195
- if (clearExisting) {
196
- element.className = '';
197
- }
198
-
199
- // Add new classes
200
- classString.split(' ').forEach(cls => {
201
- if (cls) element.classList.add(cls);
202
- });
203
- }
204
-
205
- return element; // For chaining
206
- }
207
- };
208
-
209
- return manager;
210
- };
211
-
212
- // Create a standalone instance that doesn't depend on any state manager
213
- const TemplateManager = createTemplateManager();
214
-
215
- export default createTemplateManager;
216
- export { createTemplateManager, TemplateManager };