web-manager 4.0.22 → 4.0.23

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 CHANGED
@@ -379,13 +379,37 @@ When an element is bound, Web Manager automatically adds the `wm-bound` class to
379
379
  }
380
380
  ```
381
381
 
382
+ #### Style Bindings
383
+ Set CSS custom properties (CSS variables) or inline styles dynamically:
384
+
385
+ ```html
386
+ <!-- Set CSS custom property -->
387
+ <div data-wm-bind="@style --rating-percent site.ratings.starWidth"></div>
388
+
389
+ <!-- Set regular style property -->
390
+ <div data-wm-bind="@style width user.profile.width"></div>
391
+
392
+ <!-- Multiple styles -->
393
+ <div data-wm-bind="@style --primary-color theme.primaryColor, @style --secondary-color theme.secondaryColor"></div>
394
+ ```
395
+
396
+ Then use the custom property in your CSS:
397
+
398
+ ```css
399
+ .rating-stars::before {
400
+ width: var(--rating-percent, 0%);
401
+ background: var(--primary-color, #007bff);
402
+ }
403
+ ```
404
+
382
405
  #### Supported Actions
383
406
  - **`@text`** (default): Sets the text content of the element
384
407
  - **`@show`**: Shows the element when condition is true
385
408
  - **`@hide`**: Hides the element when condition is true
386
409
  - **`@attr`**: Sets an attribute value (format: `@attr attributeName expression`)
410
+ - **`@style`**: Sets a CSS custom property or inline style (format: `@style propertyName expression`)
387
411
 
388
- Future actions like `@class` and `@style` can be easily added.
412
+ Future actions like `@class` can be easily added.
389
413
 
390
414
  ### Firebase Authentication
391
415
 
@@ -13,7 +13,10 @@ class Bindings {
13
13
  ...data
14
14
  };
15
15
 
16
- this._updateBindings(this._context);
16
+ // Get the top-level keys that were updated
17
+ const updatedKeys = Object.keys(data);
18
+
19
+ this._updateBindings(this._context, updatedKeys);
17
20
  }
18
21
 
19
22
  // Get current context
@@ -24,11 +27,11 @@ class Bindings {
24
27
  // Clear all context
25
28
  clear() {
26
29
  this._context = {};
27
- this._updateBindings(this._context);
30
+ this._updateBindings(this._context, null); // null = update all bindings
28
31
  }
29
32
 
30
33
  // Main binding update system
31
- _updateBindings(context) {
34
+ _updateBindings(context, updatedKeys = null) {
32
35
  // Find all elements with data-wm-bind attribute
33
36
  const bindElements = document.querySelectorAll('[data-wm-bind]');
34
37
 
@@ -40,16 +43,16 @@ class Bindings {
40
43
 
41
44
  // Execute each action
42
45
  bindings.forEach(({ action, expression }) => {
43
- this._executeAction(element, action, expression, context);
46
+ this._executeAction(element, action, expression, context, updatedKeys);
44
47
  });
45
48
 
46
- // Remove skeleton loader class first to trigger transition
47
- element.classList.remove('wm-binding-skeleton');
49
+ // Add bound class to trigger fade out
50
+ element.classList.add('wm-bound');
48
51
 
49
- // Add bound class after a brief delay to allow for smooth animation (0.25s)
52
+ // Remove skeleton class after fade completes
50
53
  setTimeout(() => {
51
- element.classList.add('wm-bound');
52
- }, 250);
54
+ element.classList.remove('wm-binding-skeleton');
55
+ }, 300);
53
56
  });
54
57
  }
55
58
 
@@ -85,10 +88,16 @@ class Bindings {
85
88
  }
86
89
 
87
90
  // Execute a single action on an element
88
- _executeAction(element, action, expression, context) {
91
+ _executeAction(element, action, expression, context, updatedKeys = null) {
89
92
  switch (action) {
90
93
  case '@show':
91
94
  // Show element if condition is true (or always if no condition)
95
+
96
+ // Check if this path should be updated
97
+ if (!this._shouldUpdatePath(expression, updatedKeys)) {
98
+ return;
99
+ }
100
+
92
101
  const shouldShow = expression ? this._evaluateCondition(expression, context) : true;
93
102
  if (shouldShow) {
94
103
  element.removeAttribute('hidden');
@@ -99,6 +108,12 @@ class Bindings {
99
108
 
100
109
  case '@hide':
101
110
  // Hide element if condition is true (or always if no condition)
111
+
112
+ // Check if this path should be updated
113
+ if (!this._shouldUpdatePath(expression, updatedKeys)) {
114
+ return;
115
+ }
116
+
102
117
  const shouldHide = expression ? this._evaluateCondition(expression, context) : true;
103
118
  if (shouldHide) {
104
119
  element.setAttribute('hidden', '');
@@ -113,6 +128,12 @@ class Bindings {
113
128
  const attrParts = expression.split(' ');
114
129
  const attrName = attrParts[0];
115
130
  const attrExpression = attrParts.slice(1).join(' ');
131
+
132
+ // Check if this path should be updated
133
+ if (!this._shouldUpdatePath(attrExpression, updatedKeys)) {
134
+ return;
135
+ }
136
+
116
137
  const attrValue = this._resolvePath(context, attrExpression) || '';
117
138
 
118
139
  if (attrValue) {
@@ -122,9 +143,47 @@ class Bindings {
122
143
  }
123
144
  break;
124
145
 
146
+ case '@style':
147
+ // Set CSS custom property or style
148
+ // Format: @style propertyName expression
149
+ const styleParts = expression.split(' ');
150
+ const styleName = styleParts[0];
151
+ const styleExpression = styleParts.slice(1).join(' ');
152
+
153
+ // Check if this path should be updated
154
+ if (!this._shouldUpdatePath(styleExpression, updatedKeys)) {
155
+ return;
156
+ }
157
+
158
+ const styleValue = this._resolvePath(context, styleExpression);
159
+
160
+ if (styleValue !== null && styleValue !== undefined && styleValue !== '') {
161
+ // If it starts with --, it's a CSS custom property
162
+ if (styleName.startsWith('--')) {
163
+ element.style.setProperty(styleName, styleValue);
164
+ } else {
165
+ // Regular style property
166
+ element.style[styleName] = styleValue;
167
+ }
168
+ } else {
169
+ // Remove the style if value is empty
170
+ if (styleName.startsWith('--')) {
171
+ element.style.removeProperty(styleName);
172
+ } else {
173
+ element.style[styleName] = '';
174
+ }
175
+ }
176
+ break;
177
+
125
178
  case '@text':
126
179
  default:
127
180
  // Set text content (default behavior)
181
+
182
+ // Check if this path should be updated
183
+ if (!this._shouldUpdatePath(expression, updatedKeys)) {
184
+ return;
185
+ }
186
+
128
187
  const value = this._resolvePath(context, expression) || '';
129
188
 
130
189
  if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
@@ -133,11 +192,21 @@ class Bindings {
133
192
  element.textContent = value;
134
193
  }
135
194
  break;
195
+ }
196
+ }
136
197
 
137
- // Future actions can be added here:
138
- // case '@class':
139
- // case '@style':
198
+ // Check if a path should be updated based on updatedKeys
199
+ _shouldUpdatePath(path, updatedKeys) {
200
+ // If no updatedKeys filter, always update
201
+ if (updatedKeys === null || !path) {
202
+ return true;
140
203
  }
204
+
205
+ // Extract the root key from the path
206
+ const rootKey = path.trim().split('.')[0];
207
+
208
+ // Only update if the root key is in updatedKeys
209
+ return updatedKeys.includes(rootKey);
141
210
  }
142
211
 
143
212
  // Resolve nested object path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-manager",
3
- "version": "4.0.22",
3
+ "version": "4.0.23",
4
4
  "description": "Easily access important variables such as the query string, current domain, and current page in a single object.",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",