ripple 0.4.2 → 0.4.4
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/CHANGELOG.md +164 -0
- package/package.json +10 -4
- package/src/constants.js +6 -0
- package/src/jsx-runtime.d.ts +2 -1
- package/src/runtime/array.js +15 -9
- package/src/runtime/index-client.js +25 -7
- package/src/runtime/index-server.js +1 -0
- package/src/runtime/internal/client/attributes.js +439 -0
- package/src/runtime/internal/client/blocks.js +20 -20
- package/src/runtime/internal/client/component.js +2 -5
- package/src/runtime/internal/client/composite.js +295 -95
- package/src/runtime/internal/client/constants.js +14 -6
- package/src/runtime/internal/client/context.js +3 -2
- package/src/runtime/internal/client/css.js +11 -26
- package/src/runtime/internal/client/errors.js +188 -0
- package/src/runtime/internal/client/events.js +79 -34
- package/src/runtime/internal/client/expression.js +25 -120
- package/src/runtime/internal/client/for.js +113 -262
- package/src/runtime/internal/client/head.js +4 -3
- package/src/runtime/internal/client/html.js +5 -3
- package/src/runtime/internal/client/hydrate.js +484 -0
- package/src/runtime/internal/client/hydration-enabled.js +7 -0
- package/src/runtime/internal/client/hydration.js +16 -81
- package/src/runtime/internal/client/if.js +69 -10
- package/src/runtime/internal/client/index.js +18 -10
- package/src/runtime/internal/client/operations.js +11 -7
- package/src/runtime/internal/client/portal.js +3 -2
- package/src/runtime/internal/client/render.js +42 -325
- package/src/runtime/internal/client/root-boundary-enabled.js +7 -0
- package/src/runtime/internal/client/runtime.js +157 -371
- package/src/runtime/internal/client/template-ns.js +88 -0
- package/src/runtime/internal/client/template.js +118 -139
- package/src/runtime/internal/client/track-async.js +356 -0
- package/src/runtime/internal/client/try.js +133 -319
- package/src/runtime/internal/client/types.d.ts +48 -24
- package/src/runtime/internal/server/index.js +31 -29
- package/src/runtime/internal/server/prerender.js +27 -0
- package/src/server/index.js +1 -0
- package/src/utils/class-name.js +28 -0
- package/tests/client/basic/basic.errors.test.tsrx +3 -3
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.tsrx.snap +0 -2
- package/tests/client/compiler/compiler.basic.test.tsrx +2 -2
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +3 -3
- package/tests/client/composite/composite.dynamic-components.test.tsrx +40 -0
- package/tests/client/composite/composite.props.test.tsrx +10 -10
- package/tests/client/css/scoped-styles.test.tsrx +3 -1
- package/tests/client/derived-release.test.tsrx +3 -3
- package/tests/client/for-item.test.tsrx +39 -0
- package/tests/client/for-selector.test.tsrx +95 -0
- package/tests/client/svg.test.tsrx +273 -1
- package/tests/hydration/compiled/client/basic.js +88 -87
- package/tests/hydration/compiled/client/composite.js +15 -6
- package/tests/hydration/compiled/client/events.js +40 -48
- package/tests/hydration/compiled/client/for.js +191 -199
- package/tests/hydration/compiled/client/fragment-cursor.js +633 -334
- package/tests/hydration/compiled/client/head.js +22 -22
- package/tests/hydration/compiled/client/hmr.js +14 -5
- package/tests/hydration/compiled/client/html-in-template.js +5 -5
- package/tests/hydration/compiled/client/html.js +195 -177
- package/tests/hydration/compiled/client/if-children.js +136 -63
- package/tests/hydration/compiled/client/if-fragment-controlflow.js +63 -43
- package/tests/hydration/compiled/client/if.js +78 -30
- package/tests/hydration/compiled/client/mixed-control-flow.js +90 -72
- package/tests/hydration/compiled/client/nested-control-flow.js +266 -307
- package/tests/hydration/compiled/client/portal.js +20 -11
- package/tests/hydration/compiled/client/reactivity.js +29 -29
- package/tests/hydration/compiled/client/return.js +2 -2
- package/tests/hydration/compiled/client/streaming.js +33 -35
- package/tests/hydration/compiled/client/switch.js +25 -25
- package/tests/hydration/compiled/client/track-async-serialization.js +41 -41
- package/tests/hydration/compiled/client/try.js +15 -15
- package/tests/hydration/compiled/client/typed-text.js +8 -7
- package/tests/hydration/compiled/server/basic.js +6 -6
- package/tests/hydration/compiled/server/events.js +8 -8
- package/tests/hydration/compiled/server/for.js +22 -22
- package/tests/hydration/compiled/server/fragment-cursor.js +188 -52
- package/tests/hydration/compiled/server/head.js +6 -6
- package/tests/hydration/compiled/server/hmr.js +1 -1
- package/tests/hydration/compiled/server/html.js +1 -1
- package/tests/hydration/compiled/server/if-children.js +9 -9
- package/tests/hydration/compiled/server/if.js +5 -5
- package/tests/hydration/compiled/server/mixed-control-flow.js +4 -4
- package/tests/hydration/compiled/server/portal.js +1 -1
- package/tests/hydration/compiled/server/reactivity.js +9 -9
- package/tests/hydration/compiled/server/streaming.js +9 -9
- package/tests/hydration/compiled/server/switch.js +4 -4
- package/tests/hydration/compiled/server/track-async-serialization.js +16 -16
- package/tests/hydration/compiled/server/try.js +4 -4
- package/tests/hydration/compiled/server/typed-text.js +1 -1
- package/tests/hydration/components/fragment-cursor.tsrx +60 -0
- package/tests/hydration/components/track-async-serialization.tsrx +2 -2
- package/tests/hydration/fragment-cursor.test.js +34 -1
- package/tests/hydration/track-async-serialization.test.js +1 -1
- package/tests/server/basic.components.test.tsrx +3 -3
- package/tests/server/track-async-serialization.test.tsrx +6 -6
- package/tests/utils/tracked-types.test.js +3 -3
- package/types/index.d.ts +8 -8
- package/types/server.d.ts +17 -0
|
@@ -1,7 +1,279 @@
|
|
|
1
|
-
import { track } from 'ripple';
|
|
1
|
+
import { track, flushSync } from 'ripple';
|
|
2
2
|
import type { Component, PropsWithChildren, PropsWithExtras } from 'ripple';
|
|
3
3
|
|
|
4
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
5
|
+
const HTML_NS = 'http://www.w3.org/1999/xhtml';
|
|
6
|
+
|
|
7
|
+
// The namespace is only known at runtime here: the component's children are
|
|
8
|
+
// compiled without it and rendered into an `<svg>` the component owns, so
|
|
9
|
+
// every block must keep the namespace it was created in when it reruns.
|
|
10
|
+
describe('SVG namespace in runtime-only contexts', () => {
|
|
11
|
+
function SVG({ children }: PropsWithChildren<{}>) @{
|
|
12
|
+
<svg>{children}</svg>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
it('parses a nested template in the SVG namespace', () => {
|
|
16
|
+
function App() @{
|
|
17
|
+
<SVG>
|
|
18
|
+
<g class="group">
|
|
19
|
+
<circle r="1" />
|
|
20
|
+
</g>
|
|
21
|
+
</SVG>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
render(App);
|
|
25
|
+
expect(container.querySelector('.group').namespaceURI).toBe(SVG_NS);
|
|
26
|
+
expect(container.querySelector('circle').namespaceURI).toBe(SVG_NS);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('keeps the namespace when an @if in component children toggles', () => {
|
|
30
|
+
let setFlag: ((value: boolean) => void) | undefined;
|
|
31
|
+
|
|
32
|
+
function App() @{
|
|
33
|
+
const flag = track(true);
|
|
34
|
+
setFlag = (value) => {
|
|
35
|
+
flag.value = value;
|
|
36
|
+
};
|
|
37
|
+
<SVG>
|
|
38
|
+
@if (flag.value) {
|
|
39
|
+
<polygon class="a" points="0,0 1,1" />
|
|
40
|
+
} @else {
|
|
41
|
+
<g class="b">
|
|
42
|
+
<rect width="1" height="1" />
|
|
43
|
+
</g>
|
|
44
|
+
}
|
|
45
|
+
</SVG>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render(App);
|
|
49
|
+
expect(container.querySelector('.a').namespaceURI).toBe(SVG_NS);
|
|
50
|
+
flushSync(() => setFlag?.(false));
|
|
51
|
+
expect(container.querySelector('.b').namespaceURI).toBe(SVG_NS);
|
|
52
|
+
expect(container.querySelector('rect').namespaceURI).toBe(SVG_NS);
|
|
53
|
+
flushSync(() => setFlag?.(true));
|
|
54
|
+
expect(container.querySelector('.a').namespaceURI).toBe(SVG_NS);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('keeps the namespace when an @if in a leaf component toggles', () => {
|
|
58
|
+
let setFlag: ((value: boolean) => void) | undefined;
|
|
59
|
+
|
|
60
|
+
function Shape() @{
|
|
61
|
+
const flag = track(true);
|
|
62
|
+
setFlag = (value) => {
|
|
63
|
+
flag.value = value;
|
|
64
|
+
};
|
|
65
|
+
@if (flag.value) {
|
|
66
|
+
<polygon class="a" points="0,0 1,1" />
|
|
67
|
+
} @else {
|
|
68
|
+
<rect class="b" width="1" height="1" />
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function App() @{
|
|
73
|
+
<svg>
|
|
74
|
+
<Shape />
|
|
75
|
+
</svg>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render(App);
|
|
79
|
+
expect(container.querySelector('.a').namespaceURI).toBe(SVG_NS);
|
|
80
|
+
flushSync(() => setFlag?.(false));
|
|
81
|
+
expect(container.querySelector('.b').namespaceURI).toBe(SVG_NS);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('keeps the namespace when a dynamic tag in component children changes', () => {
|
|
85
|
+
let setTag: ((value: string) => void) | undefined;
|
|
86
|
+
|
|
87
|
+
function App() @{
|
|
88
|
+
const tag = track('polygon');
|
|
89
|
+
setTag = (value) => {
|
|
90
|
+
tag.value = value;
|
|
91
|
+
};
|
|
92
|
+
<SVG>
|
|
93
|
+
<{tag.value} class="shape" />
|
|
94
|
+
</SVG>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render(App);
|
|
98
|
+
expect(container.querySelector('.shape').namespaceURI).toBe(SVG_NS);
|
|
99
|
+
flushSync(() => setTag?.('rect'));
|
|
100
|
+
const shape = container.querySelector('.shape');
|
|
101
|
+
expect(shape.localName).toBe('rect');
|
|
102
|
+
expect(shape.namespaceURI).toBe(SVG_NS);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('keeps the namespace for items added to an @for in component children', () => {
|
|
106
|
+
let setItems: ((value: string[]) => void) | undefined;
|
|
107
|
+
|
|
108
|
+
function App() @{
|
|
109
|
+
const items = track(['a']);
|
|
110
|
+
setItems = (value) => {
|
|
111
|
+
items.value = value;
|
|
112
|
+
};
|
|
113
|
+
<SVG>
|
|
114
|
+
@for (const id of items.value) {
|
|
115
|
+
<circle data-id={id} r="1" />
|
|
116
|
+
}
|
|
117
|
+
</SVG>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
render(App);
|
|
121
|
+
expect(container.querySelector('[data-id=a]').namespaceURI).toBe(SVG_NS);
|
|
122
|
+
flushSync(() => setItems?.(['a', 'b']));
|
|
123
|
+
expect(container.querySelector('[data-id=b]').namespaceURI).toBe(SVG_NS);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('renders HTML inside a foreignObject in component children', () => {
|
|
127
|
+
let setTag: ((value: string) => void) | undefined;
|
|
128
|
+
|
|
129
|
+
function App() @{
|
|
130
|
+
const tag = track('div');
|
|
131
|
+
setTag = (value) => {
|
|
132
|
+
tag.value = value;
|
|
133
|
+
};
|
|
134
|
+
<SVG>
|
|
135
|
+
<foreignObject>
|
|
136
|
+
@if (tag.value !== 'none') {
|
|
137
|
+
<div class="static-label">
|
|
138
|
+
<span>hi</span>
|
|
139
|
+
</div>
|
|
140
|
+
}
|
|
141
|
+
<{tag.value} class="html-label">Label</{tag.value}>
|
|
142
|
+
</foreignObject>
|
|
143
|
+
<circle class="after" r="1" />
|
|
144
|
+
</SVG>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
render(App);
|
|
148
|
+
expect(container.querySelector('.static-label').namespaceURI).toBe(HTML_NS);
|
|
149
|
+
expect(container.querySelector('span').namespaceURI).toBe(HTML_NS);
|
|
150
|
+
expect(container.querySelector('.html-label').namespaceURI).toBe(HTML_NS);
|
|
151
|
+
expect(container.querySelector('.after').namespaceURI).toBe(SVG_NS);
|
|
152
|
+
flushSync(() => setTag?.('span'));
|
|
153
|
+
const label = container.querySelector('.html-label');
|
|
154
|
+
expect(label.localName).toBe('span');
|
|
155
|
+
expect(label.namespaceURI).toBe(HTML_NS);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('sets a dynamic class on SVG elements in component children', () => {
|
|
159
|
+
let setName: ((value: string) => void) | undefined;
|
|
160
|
+
|
|
161
|
+
function App() @{
|
|
162
|
+
const name = track('one');
|
|
163
|
+
setName = (value) => {
|
|
164
|
+
name.value = value;
|
|
165
|
+
};
|
|
166
|
+
<SVG>
|
|
167
|
+
<circle class={name.value} r="1" />
|
|
168
|
+
<a class={name.value} href="#link">
|
|
169
|
+
<text>link</text>
|
|
170
|
+
</a>
|
|
171
|
+
</SVG>
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
render(App);
|
|
175
|
+
expect(container.querySelector('circle').getAttribute('class')).toBe('one');
|
|
176
|
+
expect(container.querySelector('a').getAttribute('class')).toBe('one');
|
|
177
|
+
flushSync(() => setName?.('two'));
|
|
178
|
+
expect(container.querySelector('circle').getAttribute('class')).toBe('two');
|
|
179
|
+
expect(container.querySelector('a').getAttribute('class')).toBe('two');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('dynamic element namespace changes', () => {
|
|
184
|
+
it('switches a root between HTML and SVG with its children', () => {
|
|
185
|
+
let setTag: ((value: string) => void) | undefined;
|
|
186
|
+
|
|
187
|
+
function App() @{
|
|
188
|
+
const tag = track('div');
|
|
189
|
+
setTag = (value) => {
|
|
190
|
+
tag.value = value;
|
|
191
|
+
};
|
|
192
|
+
<{tag.value} class="root">
|
|
193
|
+
<g class="inner">
|
|
194
|
+
<title>t</title>
|
|
195
|
+
</g>
|
|
196
|
+
</{tag.value}>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
render(App);
|
|
200
|
+
expect(container.querySelector('.root').namespaceURI).toBe(HTML_NS);
|
|
201
|
+
expect(container.querySelector('.inner').namespaceURI).toBe(HTML_NS);
|
|
202
|
+
flushSync(() => setTag?.('svg'));
|
|
203
|
+
expect(container.querySelector('.root').namespaceURI).toBe(SVG_NS);
|
|
204
|
+
expect(container.querySelector('.inner').namespaceURI).toBe(SVG_NS);
|
|
205
|
+
expect(container.querySelector('title').namespaceURI).toBe(SVG_NS);
|
|
206
|
+
flushSync(() => setTag?.('div'));
|
|
207
|
+
expect(container.querySelector('.root').namespaceURI).toBe(HTML_NS);
|
|
208
|
+
expect(container.querySelector('.inner').namespaceURI).toBe(HTML_NS);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('renders HTML children once the tag becomes foreignObject', () => {
|
|
212
|
+
let setTag: ((value: string) => void) | undefined;
|
|
213
|
+
|
|
214
|
+
function App() @{
|
|
215
|
+
const tag = track('g');
|
|
216
|
+
setTag = (value) => {
|
|
217
|
+
tag.value = value;
|
|
218
|
+
};
|
|
219
|
+
<svg>
|
|
220
|
+
<{tag.value} class="host">
|
|
221
|
+
<a class="content">hi</a>
|
|
222
|
+
</{tag.value}>
|
|
223
|
+
</svg>
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
render(App);
|
|
227
|
+
expect(container.querySelector('.host').namespaceURI).toBe(SVG_NS);
|
|
228
|
+
expect(container.querySelector('.content').namespaceURI).toBe(SVG_NS);
|
|
229
|
+
flushSync(() => setTag?.('foreignObject'));
|
|
230
|
+
const host = container.querySelector('.host');
|
|
231
|
+
expect(host.localName).toBe('foreignObject');
|
|
232
|
+
expect(host.namespaceURI).toBe(SVG_NS);
|
|
233
|
+
expect(container.querySelector('.content').namespaceURI).toBe(HTML_NS);
|
|
234
|
+
flushSync(() => setTag?.('g'));
|
|
235
|
+
expect(container.querySelector('.content').namespaceURI).toBe(SVG_NS);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
4
239
|
describe('SVG namespace handling', () => {
|
|
240
|
+
it('preserves SVG and foreignObject namespaces when dynamic tags change', () => {
|
|
241
|
+
let setTags: ((svgTag: string, htmlTag: string) => void) | undefined;
|
|
242
|
+
|
|
243
|
+
function App() @{
|
|
244
|
+
const shape = track('circle');
|
|
245
|
+
const html = track('div');
|
|
246
|
+
setTags = (svgTag, htmlTag) => {
|
|
247
|
+
shape.value = svgTag;
|
|
248
|
+
html.value = htmlTag;
|
|
249
|
+
};
|
|
250
|
+
<svg>
|
|
251
|
+
<{shape.value} class="shape">
|
|
252
|
+
<title>Dynamic shape</title>
|
|
253
|
+
</{shape.value}>
|
|
254
|
+
<foreignObject>
|
|
255
|
+
<{html.value} class="html-label">Label</{html.value}>
|
|
256
|
+
</foreignObject>
|
|
257
|
+
</svg>
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
render(App);
|
|
261
|
+
for (const [svgTag, htmlTag] of [['circle', 'div'], ['path', 'span'], ['line', 'div']]) {
|
|
262
|
+
flushSync(() => {
|
|
263
|
+
setTags?.(svgTag, htmlTag);
|
|
264
|
+
});
|
|
265
|
+
const element = container.querySelector('.shape');
|
|
266
|
+
expect(element.localName).toBe(svgTag);
|
|
267
|
+
expect(element.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
268
|
+
expect(element.querySelector('title').namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
269
|
+
expect(element.textContent).toBe('Dynamic shape');
|
|
270
|
+
const label = container.querySelector('.html-label');
|
|
271
|
+
expect(label.localName).toBe(htmlTag);
|
|
272
|
+
expect(label.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
|
|
273
|
+
expect(label.textContent).toBe('Label');
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
5
277
|
it('should render static SVG elements with correct namespace', () => {
|
|
6
278
|
function App() @{
|
|
7
279
|
<svg
|