@uistate/core 2.0.1 → 3.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.
@@ -12,6 +12,52 @@ const createTemplateManager = (stateManager) => {
12
12
  return this;
13
13
  },
14
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
+
15
61
  attachDelegation(root = document.body) {
16
62
  root.addEventListener('click', e => {
17
63
  const target = e.target.closest('[data-action]');
@@ -31,6 +77,36 @@ const createTemplateManager = (stateManager) => {
31
77
  return this;
32
78
  },
33
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
+
34
110
  mount(componentName, container) {
35
111
  const tpl = document.getElementById(`${componentName}-template`);
36
112
  if (!tpl) throw new Error(`Template not found: ${componentName}-template`);
@@ -84,6 +160,49 @@ const createTemplateManager = (stateManager) => {
84
160
  return {
85
161
  mount: (container) => this.mount(name, container)
86
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
87
206
  }
88
207
  };
89
208