ripple 0.2.89 → 0.2.90
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 +1 -1
- package/src/compiler/phases/1-parse/index.js +17 -1
- package/src/compiler/phases/3-transform/client/index.js +16 -8
- package/src/runtime/internal/client/blocks.js +15 -10
- package/src/runtime/internal/client/html.js +41 -0
- package/src/runtime/internal/client/index.js +3 -1
- package/src/runtime/internal/client/template.js +1 -1
- package/tests/client/__snapshots__/html.test.ripple.snap +40 -0
- package/tests/client/html.test.ripple +52 -0
package/package.json
CHANGED
|
@@ -455,10 +455,25 @@ function RipplePlugin(config) {
|
|
|
455
455
|
jsx_parseExpressionContainer() {
|
|
456
456
|
let node = this.startNode();
|
|
457
457
|
this.next();
|
|
458
|
+
let tracked = false;
|
|
459
|
+
|
|
460
|
+
if (this.value === 'html') {
|
|
461
|
+
node.html = true;
|
|
462
|
+
this.next();
|
|
463
|
+
if (this.type.label === '@') {
|
|
464
|
+
this.next(); // consume @
|
|
465
|
+
tracked = true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
458
468
|
|
|
459
469
|
node.expression =
|
|
460
470
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
461
471
|
this.expect(tt.braceR);
|
|
472
|
+
|
|
473
|
+
if (tracked && node.expression.type === 'Identifier') {
|
|
474
|
+
node.expression.tracked = true;
|
|
475
|
+
}
|
|
476
|
+
|
|
462
477
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
463
478
|
}
|
|
464
479
|
|
|
@@ -954,7 +969,8 @@ function RipplePlugin(config) {
|
|
|
954
969
|
|
|
955
970
|
if (this.type.label === '{') {
|
|
956
971
|
const node = this.jsx_parseExpressionContainer();
|
|
957
|
-
node.type = 'Text';
|
|
972
|
+
node.type = node.html ? 'Html' : 'Text';
|
|
973
|
+
delete node.html;
|
|
958
974
|
body.push(node);
|
|
959
975
|
} else if (this.type.label === '}') {
|
|
960
976
|
return;
|
|
@@ -133,9 +133,7 @@ function visit_title_element(node, context) {
|
|
|
133
133
|
),
|
|
134
134
|
);
|
|
135
135
|
} else {
|
|
136
|
-
context.state.init.push(
|
|
137
|
-
b.stmt(b.assignment('=', b.id('_$_.document.title'), result)),
|
|
138
|
-
);
|
|
136
|
+
context.state.init.push(b.stmt(b.assignment('=', b.id('_$_.document.title'), result)));
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
139
|
|
|
@@ -346,7 +344,7 @@ const visitors = {
|
|
|
346
344
|
|
|
347
345
|
return b.new(
|
|
348
346
|
b.id('TrackedObject'),
|
|
349
|
-
b.object(node.properties.map((prop) => context.visit(prop)))
|
|
347
|
+
b.object(node.properties.map((prop) => context.visit(prop))),
|
|
350
348
|
);
|
|
351
349
|
}
|
|
352
350
|
|
|
@@ -723,9 +721,10 @@ const visitors = {
|
|
|
723
721
|
const metadata = { tracking: false, await: false };
|
|
724
722
|
let expression = visit(class_attribute.value, { ...state, metadata });
|
|
725
723
|
|
|
726
|
-
const hash_arg =
|
|
727
|
-
|
|
728
|
-
|
|
724
|
+
const hash_arg =
|
|
725
|
+
node.metadata.scoped && state.component.css
|
|
726
|
+
? b.literal(state.component.css.hash)
|
|
727
|
+
: undefined;
|
|
729
728
|
const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
|
|
730
729
|
|
|
731
730
|
if (metadata.tracking) {
|
|
@@ -1491,6 +1490,7 @@ function transform_children(children, context) {
|
|
|
1491
1490
|
node.type === 'IfStatement' ||
|
|
1492
1491
|
node.type === 'TryStatement' ||
|
|
1493
1492
|
node.type === 'ForOfStatement' ||
|
|
1493
|
+
node.type === 'Html' ||
|
|
1494
1494
|
(node.type === 'Element' &&
|
|
1495
1495
|
(node.id.type !== 'Identifier' || !is_element_dom_element(node))),
|
|
1496
1496
|
) ||
|
|
@@ -1585,6 +1585,14 @@ function transform_children(children, context) {
|
|
|
1585
1585
|
visit(node, { ...state, flush_node, namespace: state.namespace });
|
|
1586
1586
|
} else if (node.type === 'HeadElement') {
|
|
1587
1587
|
visit(node, { ...state, flush_node, namespace: state.namespace });
|
|
1588
|
+
} else if (node.type === 'Html') {
|
|
1589
|
+
const metadata = { tracking: false, await: false };
|
|
1590
|
+
const expression = visit(node.expression, { ...state, metadata });
|
|
1591
|
+
|
|
1592
|
+
context.state.template.push('<!>');
|
|
1593
|
+
|
|
1594
|
+
const id = flush_node();
|
|
1595
|
+
state.update.push(b.stmt(b.call('_$_.html', id, b.thunk(expression))));
|
|
1588
1596
|
} else if (node.type === 'Text') {
|
|
1589
1597
|
const metadata = { tracking: false, await: false };
|
|
1590
1598
|
const expression = visit(node.expression, { ...state, metadata });
|
|
@@ -1633,7 +1641,7 @@ function transform_children(children, context) {
|
|
|
1633
1641
|
visit_head_element(head_element, context);
|
|
1634
1642
|
}
|
|
1635
1643
|
|
|
1636
|
-
if (context.state.inside_head) {
|
|
1644
|
+
if (context.state.inside_head) {
|
|
1637
1645
|
const title_element = children.find(
|
|
1638
1646
|
(node) =>
|
|
1639
1647
|
node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'title',
|
|
@@ -318,6 +318,20 @@ export function is_destroyed(target_block) {
|
|
|
318
318
|
return true;
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
/**
|
|
322
|
+
* @param {Node | null} node
|
|
323
|
+
* @param {Node} end
|
|
324
|
+
*/
|
|
325
|
+
export function remove_block_dom(node, end) {
|
|
326
|
+
while (node !== null) {
|
|
327
|
+
/** @type {Node | null} */
|
|
328
|
+
var next = node === end ? null : next_sibling(node);
|
|
329
|
+
|
|
330
|
+
/** @type {Element | Text | Comment} */ (node).remove();
|
|
331
|
+
node = next;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
321
335
|
/**
|
|
322
336
|
* @param {Block} block
|
|
323
337
|
* @param {boolean} [remove_dom]
|
|
@@ -330,16 +344,7 @@ export function destroy_block(block, remove_dom = true) {
|
|
|
330
344
|
|
|
331
345
|
if ((remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK)) !== 0) || (f & HEAD_BLOCK) !== 0) {
|
|
332
346
|
var s = block.s;
|
|
333
|
-
|
|
334
|
-
var end = s.end;
|
|
335
|
-
|
|
336
|
-
while (node !== null) {
|
|
337
|
-
var next = node === end ? null : next_sibling(node);
|
|
338
|
-
|
|
339
|
-
node.remove();
|
|
340
|
-
node = next;
|
|
341
|
-
}
|
|
342
|
-
|
|
347
|
+
remove_block_dom(s.start, s.end);
|
|
343
348
|
removed = true;
|
|
344
349
|
}
|
|
345
350
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** @import { Block } from '#client' */
|
|
2
|
+
|
|
3
|
+
import { remove_block_dom, render } from './blocks.js';
|
|
4
|
+
import { first_child } from './operations.js';
|
|
5
|
+
import { active_block } from './runtime.js';
|
|
6
|
+
import { assign_nodes, create_fragment_from_html } from './template.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders dynamic HTML content into the DOM by inserting it before the anchor node.
|
|
10
|
+
* Manages the lifecycle of HTML blocks, removing old content and inserting new content.
|
|
11
|
+
*
|
|
12
|
+
* TODO handle SVG/MathML
|
|
13
|
+
*
|
|
14
|
+
* @param {ChildNode} node
|
|
15
|
+
* @param {() => string} get_html
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
export function html(node, get_html) {
|
|
19
|
+
/** @type {ChildNode} */
|
|
20
|
+
var anchor = node;
|
|
21
|
+
/** @type {string} */
|
|
22
|
+
var html = '';
|
|
23
|
+
|
|
24
|
+
render(() => {
|
|
25
|
+
var block = /** @type {Block} */ (active_block);
|
|
26
|
+
html = get_html() + '';
|
|
27
|
+
|
|
28
|
+
if (block.s !== null && block.s.start !== null) {
|
|
29
|
+
remove_block_dom(block.s.start, /** @type {Node} */ (block.s.end));
|
|
30
|
+
block.s.start = block.s.end = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (html === '') return;
|
|
34
|
+
/** @type {DocumentFragment} */
|
|
35
|
+
var node = create_fragment_from_html(html);
|
|
36
|
+
|
|
37
|
+
assign_nodes(/** @type {Node } */ (first_child(node)), /** @type {Node} */ (node.lastChild));
|
|
38
|
+
|
|
39
|
+
anchor.before(node);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -35,7 +35,7 @@ export function assign_nodes(start, end) {
|
|
|
35
35
|
* @param {boolean} use_mathml_namespace - Whether to use MathML namespace.
|
|
36
36
|
* @returns {DocumentFragment}
|
|
37
37
|
*/
|
|
38
|
-
function create_fragment_from_html(html, use_svg_namespace = false, use_mathml_namespace = false) {
|
|
38
|
+
export function create_fragment_from_html(html, use_svg_namespace = false, use_mathml_namespace = false) {
|
|
39
39
|
if (use_svg_namespace) {
|
|
40
40
|
return from_namespace(html, 'svg');
|
|
41
41
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`html directive > renders dynamic html 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<!---->
|
|
6
|
+
<div>
|
|
7
|
+
Test
|
|
8
|
+
</div>
|
|
9
|
+
<!---->
|
|
10
|
+
<button>
|
|
11
|
+
Update
|
|
12
|
+
</button>
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
exports[`html directive > renders dynamic html 2`] = `
|
|
18
|
+
<div>
|
|
19
|
+
<!---->
|
|
20
|
+
<div>
|
|
21
|
+
Updated
|
|
22
|
+
</div>
|
|
23
|
+
<!---->
|
|
24
|
+
<button>
|
|
25
|
+
Update
|
|
26
|
+
</button>
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
exports[`html directive > renders static html 1`] = `
|
|
32
|
+
<div>
|
|
33
|
+
<!---->
|
|
34
|
+
<div>
|
|
35
|
+
Test
|
|
36
|
+
</div>
|
|
37
|
+
<!---->
|
|
38
|
+
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, track } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('html directive', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders static html', () => {
|
|
24
|
+
component App() {
|
|
25
|
+
let str = '<div>Test</div>';
|
|
26
|
+
|
|
27
|
+
{html str}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render(App);
|
|
31
|
+
expect(container).toMatchSnapshot();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders dynamic html', () => {
|
|
35
|
+
component App() {
|
|
36
|
+
let str = track('<div>Test</div>');
|
|
37
|
+
|
|
38
|
+
{html @str}
|
|
39
|
+
|
|
40
|
+
<button onClick={() => { @str = '<div>Updated</div>'; }}>{'Update'}</button>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(App);
|
|
44
|
+
expect(container).toMatchSnapshot();
|
|
45
|
+
|
|
46
|
+
const button = container.querySelector('button');
|
|
47
|
+
button.click();
|
|
48
|
+
flushSync();
|
|
49
|
+
|
|
50
|
+
expect(container).toMatchSnapshot();
|
|
51
|
+
});
|
|
52
|
+
});
|