ripple 0.2.170 → 0.2.171

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.170",
6
+ "version": "0.2.171",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -81,6 +81,6 @@
81
81
  "typescript": "^5.9.2"
82
82
  },
83
83
  "peerDependencies": {
84
- "ripple": "0.2.170"
84
+ "ripple": "0.2.171"
85
85
  }
86
86
  }
@@ -950,6 +950,16 @@ function RipplePlugin(config) {
950
950
  return this.finishNode(node, isForIn ? 'ForInStatement' : 'ForOfStatement');
951
951
  }
952
952
 
953
+ checkUnreserved(ref) {
954
+ if (ref.name === 'component') {
955
+ this.raise(
956
+ ref.start,
957
+ '"component" is a Ripple keyword and cannot be used as an identifier',
958
+ );
959
+ }
960
+ return super.checkUnreserved(ref);
961
+ }
962
+
953
963
  shouldParseExportStatement() {
954
964
  if (super.shouldParseExportStatement()) {
955
965
  return true;
@@ -134,7 +134,7 @@ function visit_head_element(node, context) {
134
134
  }
135
135
 
136
136
  function apply_updates(init, update, state) {
137
- if (update.length === 1) {
137
+ if (update.length === 1 && !update[0].needsPrevTracking) {
138
138
  init.push(
139
139
  b.stmt(
140
140
  b.call(
@@ -185,18 +185,27 @@ function apply_updates(init, update, state) {
185
185
  b.var('__' + key, u.expression),
186
186
  b.if(
187
187
  b.binary('!==', b.member(b.id('__prev'), b.id(key)), b.id('__' + key)),
188
- b.block([
189
- u.operation(b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key))),
190
- ]),
188
+ b.block(
189
+ u.needsPrevTracking
190
+ ? [
191
+ u.operation(b.id('__' + key), b.member(b.id('__prev'), b.id(key))),
192
+ b.stmt(
193
+ b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key)),
194
+ ),
195
+ ]
196
+ : [
197
+ u.operation(
198
+ b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key)),
199
+ ),
200
+ ],
201
+ ),
191
202
  ),
192
203
  );
193
204
  index++;
194
205
  } else {
195
206
  const key = index_to_key(index);
196
207
  /** @type {Array<Statement>} */
197
- const if_body = [
198
- b.stmt(b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key))),
199
- ];
208
+ const if_body = [];
200
209
  initial.push(b.prop('init', b.id(key), updates[0].initial));
201
210
  render_statements.push(
202
211
  b.var('__' + key, updates[0].expression),
@@ -207,14 +216,22 @@ function apply_updates(init, update, state) {
207
216
  );
208
217
  for (const u of updates) {
209
218
  index_map.set(u.operation, key);
210
- if_body.push(u.operation(b.id('__' + key)));
219
+ if_body.push(
220
+ u.needsPrevTracking
221
+ ? u.operation(b.id('__' + key), b.member(b.id('__prev'), b.id(key)))
222
+ : u.operation(b.id('__' + key)),
223
+ );
211
224
  index++;
212
225
  }
226
+ // Update prev after all operations
227
+ if_body.push(
228
+ b.stmt(b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key))),
229
+ );
213
230
  }
214
231
  }
215
232
 
216
233
  for (const u of update) {
217
- if (!u.initial) {
234
+ if (!u.initial && !u.needsPrevTracking) {
218
235
  render_statements.push(u.operation);
219
236
  }
220
237
  }
@@ -879,7 +896,6 @@ const visitors = {
879
896
  if (is_dom_element) {
880
897
  let class_attribute = null;
881
898
  let style_attribute = null;
882
- /** @type {Array<Statement>} */
883
899
  const local_updates = [];
884
900
  const is_void = is_void_element(node.id.name);
885
901
 
@@ -954,7 +970,7 @@ const visitors = {
954
970
  const metadata = { tracking: false, await: false };
955
971
  const expression = visit(attr.value, { ...state, metadata });
956
972
 
957
- if (name === '$checked' || metadata.tracking) {
973
+ if (metadata.tracking) {
958
974
  local_updates.push({
959
975
  operation: b.stmt(b.call('_$_.set_checked', id, expression)),
960
976
  });
@@ -1093,14 +1109,27 @@ const visitors = {
1093
1109
  const expression = visit(style_attribute.value, { ...state, metadata });
1094
1110
 
1095
1111
  if (metadata.tracking) {
1096
- local_updates.push({
1097
- operation: (key) => b.stmt(b.call('_$_.set_style', id, key)),
1098
- identity: style_attribute.value,
1099
- expression,
1100
- initial: b.void0,
1101
- });
1112
+ if (
1113
+ style_attribute.value.type === 'Literal' ||
1114
+ style_attribute.value.type === 'TemplateLiteral'
1115
+ ) {
1116
+ // Doesn't need prev tracking
1117
+ local_updates.push({
1118
+ operation: b.stmt(b.call('_$_.set_style', id, expression, b.void0)),
1119
+ });
1120
+ } else {
1121
+ // Object or unknown - needs prev tracking
1122
+ local_updates.push({
1123
+ operation: (new_value, prev_value) =>
1124
+ b.stmt(b.call('_$_.set_style', id, new_value, prev_value)),
1125
+ identity: style_attribute.value,
1126
+ expression,
1127
+ initial: b.void0,
1128
+ needsPrevTracking: true,
1129
+ });
1130
+ }
1102
1131
  } else {
1103
- state.init.push(b.stmt(b.call('_$_.set_style', id, expression)));
1132
+ state.init.push(b.stmt(b.call('_$_.set_style', id, expression, b.void0)));
1104
1133
  }
1105
1134
  }
1106
1135
  }
@@ -81,14 +81,18 @@ export { on } from './internal/client/events.js';
81
81
  export {
82
82
  bindValue,
83
83
  bindChecked,
84
+ bindGroup,
84
85
  bindClientWidth,
85
86
  bindClientHeight,
86
87
  bindContentRect,
87
88
  bindContentBoxSize,
88
89
  bindBorderBoxSize,
89
90
  bindDevicePixelContentBoxSize,
91
+ bindIndeterminate,
90
92
  bindInnerHTML,
91
93
  bindInnerText,
92
94
  bindTextContent,
93
95
  bindNode,
96
+ bindOffsetWidth,
97
+ bindOffsetHeight,
94
98
  } from './internal/client/bindings.js';
@@ -263,6 +263,95 @@ export function bindChecked(maybe_tracked) {
263
263
  set(tracked, input.checked);
264
264
  });
265
265
 
266
+ effect(() => {
267
+ var value = get(tracked);
268
+ input.checked = Boolean(value);
269
+ });
270
+
271
+ return clear_event;
272
+ };
273
+ }
274
+
275
+ /**
276
+ * @param {unknown} maybe_tracked
277
+ * @returns {(node: HTMLInputElement) => void}
278
+ */
279
+ export function bindIndeterminate(maybe_tracked) {
280
+ if (!is_tracked_object(maybe_tracked)) {
281
+ throw not_tracked_type_error('bindIndeterminate()');
282
+ }
283
+
284
+ const tracked = /** @type {Tracked} */ (maybe_tracked);
285
+
286
+ return (input) => {
287
+ const clear_event = on(input, 'change', () => {
288
+ set(tracked, input.indeterminate);
289
+ });
290
+
291
+ effect(() => {
292
+ var value = get(tracked);
293
+ input.indeterminate = Boolean(value);
294
+ });
295
+
296
+ return clear_event;
297
+ };
298
+ }
299
+
300
+ /**
301
+ * @param {unknown} maybe_tracked
302
+ * @returns {(node: HTMLInputElement) => void}
303
+ */
304
+ export function bindGroup(maybe_tracked) {
305
+ if (!is_tracked_object(maybe_tracked)) {
306
+ throw not_tracked_type_error('bindGroup()');
307
+ }
308
+
309
+ var tracked = /** @type {Tracked} */ (maybe_tracked);
310
+
311
+ return (input) => {
312
+ var is_checkbox = input.getAttribute('type') === 'checkbox';
313
+
314
+ // Store the input's value
315
+ // @ts-ignore
316
+ input.__value = input.value;
317
+
318
+ var clear_event = on(input, 'change', () => {
319
+ // @ts-ignore
320
+ var value = input.__value;
321
+
322
+ if (is_checkbox) {
323
+ /** @type {Array<any>} */
324
+ var current_value = get(tracked) || [];
325
+
326
+ if (input.checked) {
327
+ // Add if not already present
328
+ if (!current_value.includes(value)) {
329
+ value = [...current_value, value];
330
+ } else {
331
+ value = current_value;
332
+ }
333
+ } else {
334
+ // Remove the value
335
+ value = current_value.filter((v) => v !== value);
336
+ }
337
+ }
338
+
339
+ set(tracked, value);
340
+ });
341
+
342
+ effect(() => {
343
+ var value = get(tracked);
344
+
345
+ if (is_checkbox) {
346
+ value = value || [];
347
+ // @ts-ignore
348
+ input.checked = value.includes(input.__value);
349
+ } else {
350
+ // @ts-ignore
351
+ input.checked = value === input.__value;
352
+ }
353
+ });
354
+
266
355
  return clear_event;
267
356
  };
268
357
  }
@@ -1,6 +1,6 @@
1
1
  /** @import { Block } from '#client' */
2
2
 
3
- import { branch, destroy_block, ref } from './blocks.js';
3
+ import { destroy_block, ref } from './blocks.js';
4
4
  import { REF_PROP } from './constants.js';
5
5
  import {
6
6
  get_descriptors,
@@ -66,13 +66,14 @@ function get_setters(element) {
66
66
  /**
67
67
  * @param {Element} element
68
68
  * @param {any} value
69
+ * @param {Record<string, string> | undefined} prev
69
70
  * @returns {void}
70
71
  */
71
- export function set_style(element, value) {
72
+ export function set_style(element, value, prev = {}) {
72
73
  if (value == null) {
73
74
  element.removeAttribute('style');
74
75
  } else if (typeof value !== 'string') {
75
- apply_styles(/** @type {HTMLElement} */ (element), value);
76
+ apply_styles(/** @type {HTMLElement} */ (element), value, prev);
76
77
  } else {
77
78
  // @ts-ignore
78
79
  element.style.cssText = value;
@@ -97,27 +98,27 @@ export function set_attribute(element, attribute, value) {
97
98
 
98
99
  /**
99
100
  * @param {HTMLElement} element
100
- * @param {HTMLElement['style']} new_styles
101
+ * @param {Record<string, string | number>} new_styles
102
+ * @param {Record<string, string>} prev
101
103
  */
102
- export function apply_styles(element, new_styles) {
104
+ function apply_styles(element, new_styles, prev) {
103
105
  const style = element.style;
104
- const new_properties = new Set();
105
106
 
106
- for (const [property, value] of Object.entries(new_styles)) {
107
- const normalized_property = normalize_css_property_name(property);
108
- const normalized_value = String(value);
107
+ // Apply new styles
108
+ for (const key in new_styles) {
109
+ const css_prop = normalize_css_property_name(key);
110
+ const value = String(new_styles[key]);
109
111
 
110
- if (style.getPropertyValue(normalized_property) !== normalized_value) {
111
- style.setProperty(normalized_property, normalized_value);
112
+ if (!(key in prev) || prev[key] !== value) {
113
+ style.setProperty(css_prop, value);
112
114
  }
113
-
114
- new_properties.add(normalized_property);
115
115
  }
116
116
 
117
- for (let i = style.length - 1; i >= 0; i--) {
118
- const property = style[i];
119
- if (!new_properties.has(property)) {
120
- style.removeProperty(property);
117
+ // Remove properties that were in prev but not in new_styles
118
+ for (const key in prev) {
119
+ if (!(key in new_styles)) {
120
+ const css_prop = normalize_css_property_name(key);
121
+ style.removeProperty(css_prop);
121
122
  }
122
123
  }
123
124
  }
@@ -128,13 +129,14 @@ export function apply_styles(element, new_styles) {
128
129
  * @param {string} key
129
130
  * @param {any} value
130
131
  * @param {Record<string, (() => void) | undefined>} remove_listeners
132
+ * @param {Record<string, any>} prev
131
133
  */
132
- function set_attribute_helper(element, key, value, remove_listeners) {
134
+ function set_attribute_helper(element, key, value, remove_listeners, prev) {
133
135
  if (key === 'class') {
134
136
  const is_html = element.namespaceURI === 'http://www.w3.org/1999/xhtml';
135
137
  set_class(/** @type {HTMLElement} */ (element), value, undefined, is_html);
136
138
  } else if (key === 'style') {
137
- set_style(/** @type {HTMLElement} */ (element), value);
139
+ set_style(element, value, prev.style);
138
140
  } else if (key === '#class') {
139
141
  // Special case for static class when spreading props
140
142
  element.classList.add(value);
@@ -167,7 +169,7 @@ export function set_class(dom, value, hash, is_html = true) {
167
169
  : clsx([value, hash]);
168
170
 
169
171
  // Removing the attribute when the value is only an empty string causes
170
- // peformance issues vs simply making the className an empty string. So
172
+ // performance issues vs simply making the className an empty string. So
171
173
  // we should only remove the class if the the value is nullish.
172
174
  if (value == null && hash === undefined) {
173
175
  dom.removeAttribute('class');
@@ -290,7 +292,7 @@ export function apply_element_spread(element, fn) {
290
292
  if (key === '#class') {
291
293
  continue;
292
294
  }
293
- set_attribute_helper(element, key, null, remove_listeners);
295
+ set_attribute_helper(element, key, null, remove_listeners, prev);
294
296
  }
295
297
  }
296
298
 
@@ -309,7 +311,7 @@ export function apply_element_spread(element, fn) {
309
311
  continue;
310
312
  }
311
313
 
312
- set_attribute_helper(element, key, value, remove_listeners);
314
+ set_attribute_helper(element, key, value, remove_listeners, prev);
313
315
  }
314
316
  prev = current;
315
317
  };