ripple 0.2.206 → 0.2.208
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 +3 -3
- package/src/compiler/phases/3-transform/client/index.js +28 -22
- package/src/runtime/internal/client/bindings.js +26 -20
- package/src/runtime/internal/client/css.js +5 -1
- package/src/utils/builders.js +6 -2
- package/tests/client/basic/basic.rendering.test.ripple +43 -0
- package/tests/client/input-value.test.ripple +539 -469
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.
|
|
6
|
+
"version": "0.2.208",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
79
|
-
"@sveltejs/acorn-typescript": "^1.0.
|
|
79
|
+
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
80
80
|
"acorn": "^8.15.0",
|
|
81
81
|
"clsx": "^2.1.1",
|
|
82
82
|
"devalue": "^5.3.2",
|
|
@@ -97,6 +97,6 @@
|
|
|
97
97
|
"vscode-languageserver-types": "^3.17.5"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
|
-
"ripple": "0.2.
|
|
100
|
+
"ripple": "0.2.208"
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -104,30 +104,28 @@ function visit_function(node, context) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
let body =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const new_body = [];
|
|
115
|
-
|
|
116
|
-
if (!is_inside_component(context, true) && is_component_level_function(context)) {
|
|
117
|
-
new_body.push(b.var('__block', b.call('_$_.scope')));
|
|
118
|
-
}
|
|
119
|
-
if (body.type === 'BlockStatement') {
|
|
120
|
-
new_body.push(...body.body);
|
|
121
|
-
}
|
|
107
|
+
let body = /** @type {AST.BlockStatement | AST.Expression} */ (
|
|
108
|
+
context.visit(node.body, {
|
|
109
|
+
...state,
|
|
110
|
+
// we are new context so tracking no longer applies
|
|
111
|
+
metadata: { ...state.metadata, tracking: false },
|
|
112
|
+
})
|
|
113
|
+
);
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
if (
|
|
116
|
+
metadata?.tracked === true &&
|
|
117
|
+
!is_inside_component(context, true) &&
|
|
118
|
+
is_component_level_function(context) &&
|
|
119
|
+
body.type === 'BlockStatement'
|
|
120
|
+
) {
|
|
121
|
+
body = { ...body, body: [b.var('__block', b.call('_$_.scope')), ...body.body] };
|
|
128
122
|
}
|
|
129
123
|
|
|
130
|
-
return
|
|
124
|
+
return {
|
|
125
|
+
...node,
|
|
126
|
+
params: node.params.map((param) => context.visit(param, state)),
|
|
127
|
+
body,
|
|
128
|
+
};
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
/**
|
|
@@ -2896,12 +2894,20 @@ function transform_children(children, context) {
|
|
|
2896
2894
|
: node.type == 'Text'
|
|
2897
2895
|
? state.scope.generate('text')
|
|
2898
2896
|
: state.scope.generate('node'),
|
|
2897
|
+
/** @type {AST.NodeWithLocation} */ (node.type === 'Element' ? node.openingElement : node),
|
|
2899
2898
|
);
|
|
2900
2899
|
};
|
|
2901
2900
|
|
|
2902
2901
|
/** @param {AST.Node} node */
|
|
2903
2902
|
const create_initial = (node) => {
|
|
2904
|
-
const id = is_fragment
|
|
2903
|
+
const id = is_fragment
|
|
2904
|
+
? b.id(
|
|
2905
|
+
state.scope.generate('fragment'),
|
|
2906
|
+
/** @type {AST.NodeWithLocation} */ (
|
|
2907
|
+
node.type === 'Element' ? node.openingElement : node
|
|
2908
|
+
),
|
|
2909
|
+
)
|
|
2910
|
+
: get_id(node);
|
|
2905
2911
|
initial = id;
|
|
2906
2912
|
template_id = state.scope.generate('root');
|
|
2907
2913
|
state.init?.push(b.var(id, b.call(template_id)));
|
|
@@ -248,15 +248,35 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
248
248
|
});
|
|
249
249
|
} else {
|
|
250
250
|
var input = /** @type {HTMLInputElement} */ (node);
|
|
251
|
-
var selection_restore_needed = false;
|
|
252
251
|
|
|
253
252
|
clear_event = on(input, 'input', () => {
|
|
254
|
-
selection_restore_needed = true;
|
|
255
253
|
/** @type {any} */
|
|
256
254
|
var value = input.value;
|
|
257
255
|
value = is_numberlike_input(input) ? to_number(value) : value;
|
|
258
|
-
// the setter will schedule a microtask and the render block below will run
|
|
259
256
|
setter(value);
|
|
257
|
+
const getter_value = getter();
|
|
258
|
+
|
|
259
|
+
// Check the getter to see if it's different from the input.value
|
|
260
|
+
// The setter may have decided not to update its track value or update it to something else
|
|
261
|
+
// We treat the getter as the source of truth since we cannot verify the change otherwise
|
|
262
|
+
// If getter() !== input.value, we set the input value right away
|
|
263
|
+
// the `render` block may be scheduled only if the tracked value has changed
|
|
264
|
+
// but it will not do anything if getter() === input.value
|
|
265
|
+
// The result is: the `render` block will ALWAYS exit early if the microtask
|
|
266
|
+
// came from this event handler
|
|
267
|
+
if (value !== getter_value) {
|
|
268
|
+
var start = input.selectionStart;
|
|
269
|
+
var end = input.selectionEnd;
|
|
270
|
+
|
|
271
|
+
input.value = getter_value ?? '';
|
|
272
|
+
|
|
273
|
+
if (end !== null && start !== null) {
|
|
274
|
+
end = Math.min(end, input.value.length);
|
|
275
|
+
start = Math.min(start, end);
|
|
276
|
+
input.selectionStart = start;
|
|
277
|
+
input.selectionEnd = end;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
260
280
|
});
|
|
261
281
|
|
|
262
282
|
render(() => {
|
|
@@ -271,23 +291,9 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
271
291
|
}
|
|
272
292
|
|
|
273
293
|
if (value !== input.value) {
|
|
274
|
-
if
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
input.value = value ?? '';
|
|
279
|
-
|
|
280
|
-
// Restore selection
|
|
281
|
-
if (end !== null && start !== null) {
|
|
282
|
-
end = Math.min(end, input.value.length);
|
|
283
|
-
start = Math.min(start, end);
|
|
284
|
-
input.selectionStart = start;
|
|
285
|
-
input.selectionEnd = end;
|
|
286
|
-
}
|
|
287
|
-
selection_restore_needed = false;
|
|
288
|
-
} else {
|
|
289
|
-
input.value = value ?? '';
|
|
290
|
-
}
|
|
294
|
+
// this can only get here if the tracked value was changed directly,
|
|
295
|
+
// and not via the input event
|
|
296
|
+
input.value = value ?? '';
|
|
291
297
|
}
|
|
292
298
|
});
|
|
293
299
|
|
|
@@ -31,7 +31,11 @@ function remove() {
|
|
|
31
31
|
*/
|
|
32
32
|
function remove_when_css_loaded(callback) {
|
|
33
33
|
/** @type {HTMLLinkElement[]} */
|
|
34
|
-
const links = Array.from(
|
|
34
|
+
const links = Array.from(
|
|
35
|
+
/** @type {NodeListOf<HTMLLinkElement>} */ (
|
|
36
|
+
document.querySelectorAll('link[rel="stylesheet"]')
|
|
37
|
+
),
|
|
38
|
+
);
|
|
35
39
|
let remaining = links.length;
|
|
36
40
|
|
|
37
41
|
if (remaining === 0) {
|
package/src/utils/builders.js
CHANGED
|
@@ -320,10 +320,14 @@ export function get(name, body) {
|
|
|
320
320
|
|
|
321
321
|
/**
|
|
322
322
|
* @param {string} name
|
|
323
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
323
324
|
* @returns {AST.Identifier}
|
|
324
325
|
*/
|
|
325
|
-
export function id(name) {
|
|
326
|
-
|
|
326
|
+
export function id(name, loc_info) {
|
|
327
|
+
/** @type {AST.Identifier} */
|
|
328
|
+
const node = { type: 'Identifier', name, metadata: { path: [] } };
|
|
329
|
+
|
|
330
|
+
return set_location(node, loc_info);
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
/**
|
|
@@ -231,4 +231,47 @@ describe('basic client > rendering & text', () => {
|
|
|
231
231
|
render(App);
|
|
232
232
|
expect(container).toMatchSnapshot();
|
|
233
233
|
});
|
|
234
|
+
|
|
235
|
+
it('should handle consecutive text nodes without duplication', () => {
|
|
236
|
+
component App() {
|
|
237
|
+
const Something = conditional('a');
|
|
238
|
+
|
|
239
|
+
<Something />
|
|
240
|
+
|
|
241
|
+
function conditional(item: 'a') {
|
|
242
|
+
let hello = 'Hello';
|
|
243
|
+
const obj = {
|
|
244
|
+
a: component() {
|
|
245
|
+
<div>
|
|
246
|
+
{'a'}
|
|
247
|
+
{' '}
|
|
248
|
+
{hello}
|
|
249
|
+
</div>
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return obj[item];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
render(App);
|
|
258
|
+
const div = container.querySelector('div');
|
|
259
|
+
expect(div.textContent).toEqual('a Hello');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should handle multiple consecutive text expressions', () => {
|
|
263
|
+
component App() {
|
|
264
|
+
let name = 'World';
|
|
265
|
+
<div>
|
|
266
|
+
{'Hello'}
|
|
267
|
+
{' '}
|
|
268
|
+
{name}
|
|
269
|
+
{'!'}
|
|
270
|
+
</div>
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
render(App);
|
|
274
|
+
const div = container.querySelector('div');
|
|
275
|
+
expect(div.textContent).toEqual('Hello World!');
|
|
276
|
+
});
|
|
234
277
|
});
|