ripple 0.3.72 → 0.3.76
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 +116 -0
- package/package.json +3 -3
- package/src/jsx-runtime.d.ts +4 -10
- package/src/runtime/dynamic-client.js +33 -0
- package/src/runtime/dynamic-server.js +80 -0
- package/src/runtime/index-client.js +5 -13
- package/src/runtime/index-server.js +2 -0
- package/src/runtime/internal/client/blocks.js +6 -27
- package/src/runtime/internal/client/composite.js +11 -6
- package/src/runtime/internal/client/for.js +80 -5
- package/src/runtime/internal/client/index.js +0 -2
- package/src/runtime/internal/client/render.js +5 -2
- package/src/runtime/internal/client/types.d.ts +0 -10
- package/src/runtime/internal/server/index.js +8 -1
- package/tests/client/__snapshots__/computed-properties.test.tsrx.snap +8 -0
- package/tests/client/__snapshots__/for.test.tsrx.snap +22 -0
- package/tests/client/__snapshots__/html.test.tsrx.snap +4 -0
- package/tests/client/array/array.copy-within.test.tsrx +19 -19
- package/tests/client/array/array.derived.test.tsrx +97 -109
- package/tests/client/array/array.iteration.test.tsrx +28 -28
- package/tests/client/array/array.mutations.test.tsrx +68 -68
- package/tests/client/array/array.static.test.tsrx +82 -92
- package/tests/client/array/array.to-methods.test.tsrx +15 -15
- package/tests/client/async-suspend.test.tsrx +180 -179
- package/tests/client/basic/__snapshots__/basic.attributes.test.tsrx.snap +2 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.tsrx.snap +4 -0
- package/tests/client/basic/basic.attributes.test.tsrx +273 -317
- package/tests/client/basic/basic.collections.test.tsrx +55 -61
- package/tests/client/basic/basic.components.test.tsrx +198 -220
- package/tests/client/basic/basic.errors.test.tsrx +70 -76
- package/tests/client/basic/basic.events.test.tsrx +80 -85
- package/tests/client/basic/basic.get-set.test.tsrx +54 -64
- package/tests/client/basic/basic.hmr.test.tsrx +15 -19
- package/tests/client/basic/basic.reactivity.test.tsrx +121 -135
- package/tests/client/basic/basic.rendering.test.tsrx +273 -178
- package/tests/client/basic/basic.styling.test.tsrx +16 -14
- package/tests/client/basic/basic.utilities.test.tsrx +8 -10
- package/tests/client/boundaries.test.tsrx +18 -18
- package/tests/client/compiler/compiler.assignments.test.tsrx +77 -76
- package/tests/client/compiler/compiler.attributes.test.tsrx +18 -14
- package/tests/client/compiler/compiler.basic.test.tsrx +357 -288
- package/tests/client/compiler/compiler.regex.test.tsrx +40 -44
- package/tests/client/compiler/compiler.tracked-access.test.tsrx +57 -38
- package/tests/client/compiler/compiler.try-in-function.test.tsrx +16 -16
- package/tests/client/compiler/compiler.typescript.test.tsrx +4 -3
- package/tests/client/composite/composite.dynamic-components.test.tsrx +62 -47
- package/tests/client/composite/composite.generics.test.tsrx +165 -167
- package/tests/client/composite/composite.props.test.tsrx +66 -74
- package/tests/client/composite/composite.reactivity.test.tsrx +132 -166
- package/tests/client/composite/composite.render.test.tsrx +92 -101
- package/tests/client/computed-properties.test.tsrx +14 -18
- package/tests/client/context.test.tsrx +14 -18
- package/tests/client/css/global-additional-cases.test.tsrx +493 -439
- package/tests/client/css/global-advanced-selectors.test.tsrx +169 -153
- package/tests/client/css/global-at-rules.test.tsrx +71 -66
- package/tests/client/css/global-basic.test.tsrx +105 -98
- package/tests/client/css/global-classes-ids.test.tsrx +128 -114
- package/tests/client/css/global-combinators.test.tsrx +83 -78
- package/tests/client/css/global-complex-nesting.test.tsrx +134 -120
- package/tests/client/css/global-edge-cases.test.tsrx +138 -120
- package/tests/client/css/global-keyframes.test.tsrx +108 -96
- package/tests/client/css/global-nested.test.tsrx +88 -78
- package/tests/client/css/global-pseudo.test.tsrx +104 -98
- package/tests/client/css/global-scoping.test.tsrx +145 -125
- package/tests/client/css/style-identifier.test.tsrx +65 -72
- package/tests/client/date.test.tsrx +83 -83
- package/tests/client/dynamic-elements.test.tsrx +318 -299
- package/tests/client/events.test.tsrx +252 -266
- package/tests/client/for.test.tsrx +120 -127
- package/tests/client/head.test.tsrx +74 -48
- package/tests/client/html.test.tsrx +37 -49
- package/tests/client/input-value.test.tsrx +1125 -1354
- package/tests/client/lazy-array.test.tsrx +10 -16
- package/tests/client/lazy-destructuring.test.tsrx +169 -221
- package/tests/client/map.test.tsrx +39 -41
- package/tests/client/media-query.test.tsrx +15 -19
- package/tests/client/object.test.tsrx +46 -56
- package/tests/client/portal.test.tsrx +31 -37
- package/tests/client/ref.test.tsrx +173 -193
- package/tests/client/return.test.tsrx +62 -37
- package/tests/client/set.test.tsrx +33 -33
- package/tests/client/svg.test.tsrx +197 -216
- package/tests/client/switch.test.tsrx +201 -191
- package/tests/client/track-async-hydration.test.tsrx +14 -18
- package/tests/client/tracked-index-access.test.tsrx +18 -28
- package/tests/client/try.test.tsrx +494 -619
- package/tests/client/tsx.test.tsrx +286 -292
- package/tests/client/typescript-generics.test.tsrx +121 -129
- package/tests/client/url/url.derived.test.tsrx +21 -25
- package/tests/client/url/url.parsing.test.tsrx +35 -35
- package/tests/client/url/url.partial-removal.test.tsrx +32 -32
- package/tests/client/url/url.reactivity.test.tsrx +68 -72
- package/tests/client/url/url.serialization.test.tsrx +8 -8
- package/tests/client/url-search-params/url-search-params.derived.test.tsrx +21 -27
- package/tests/client/url-search-params/url-search-params.initialization.test.tsrx +16 -16
- package/tests/client/url-search-params/url-search-params.iteration.test.tsrx +37 -37
- package/tests/client/url-search-params/url-search-params.mutation.test.tsrx +56 -60
- package/tests/client/url-search-params/url-search-params.retrieval.test.tsrx +32 -34
- package/tests/client/url-search-params/url-search-params.serialization.test.tsrx +9 -9
- package/tests/client/url-search-params/url-search-params.tracked-url.test.tsrx +10 -10
- package/tests/hydration/compiled/client/basic.js +390 -319
- package/tests/hydration/compiled/client/composite.js +52 -44
- package/tests/hydration/compiled/client/for.js +734 -604
- package/tests/hydration/compiled/client/head.js +183 -103
- package/tests/hydration/compiled/client/html.js +93 -86
- package/tests/hydration/compiled/client/if-children.js +95 -71
- package/tests/hydration/compiled/client/if.js +113 -89
- package/tests/hydration/compiled/client/mixed-control-flow.js +225 -209
- package/tests/hydration/compiled/client/nested-control-flow.js +94 -98
- package/tests/hydration/compiled/client/reactivity.js +26 -24
- package/tests/hydration/compiled/client/return.js +8 -42
- package/tests/hydration/compiled/client/switch.js +208 -173
- package/tests/hydration/compiled/client/track-async-serialization.js +176 -128
- package/tests/hydration/compiled/client/try.js +29 -21
- package/tests/hydration/compiled/server/basic.js +210 -221
- package/tests/hydration/compiled/server/composite.js +13 -14
- package/tests/hydration/compiled/server/for.js +427 -444
- package/tests/hydration/compiled/server/head.js +199 -189
- package/tests/hydration/compiled/server/html.js +33 -41
- package/tests/hydration/compiled/server/if-children.js +114 -117
- package/tests/hydration/compiled/server/if.js +77 -83
- package/tests/hydration/compiled/server/mixed-control-flow.js +145 -150
- package/tests/hydration/compiled/server/nested-control-flow.js +10 -0
- package/tests/hydration/compiled/server/reactivity.js +24 -22
- package/tests/hydration/compiled/server/return.js +6 -18
- package/tests/hydration/compiled/server/switch.js +179 -176
- package/tests/hydration/compiled/server/track-async-serialization.js +88 -70
- package/tests/hydration/compiled/server/try.js +31 -35
- package/tests/hydration/components/basic.tsrx +216 -258
- package/tests/hydration/components/composite.tsrx +32 -42
- package/tests/hydration/components/events.tsrx +81 -101
- package/tests/hydration/components/for.tsrx +270 -336
- package/tests/hydration/components/head.tsrx +43 -39
- package/tests/hydration/components/hmr.tsrx +16 -22
- package/tests/hydration/components/html-in-template.tsrx +15 -21
- package/tests/hydration/components/html.tsrx +442 -526
- package/tests/hydration/components/if-children.tsrx +107 -125
- package/tests/hydration/components/if.tsrx +68 -90
- package/tests/hydration/components/mixed-control-flow.tsrx +65 -72
- package/tests/hydration/components/nested-control-flow.tsrx +202 -216
- package/tests/hydration/components/portal.tsrx +33 -41
- package/tests/hydration/components/reactivity.tsrx +26 -34
- package/tests/hydration/components/return.tsrx +4 -6
- package/tests/hydration/components/switch.tsrx +73 -78
- package/tests/hydration/components/track-async-serialization.tsrx +83 -93
- package/tests/hydration/components/try.tsrx +37 -51
- package/tests/hydration/switch.test.js +8 -8
- package/tests/server/await.test.tsrx +3 -3
- package/tests/server/basic.attributes.test.tsrx +117 -162
- package/tests/server/basic.components.test.tsrx +164 -194
- package/tests/server/basic.test.tsrx +299 -199
- package/tests/server/compiler.test.tsrx +142 -72
- package/tests/server/composite.props.test.tsrx +54 -58
- package/tests/server/composite.test.tsrx +165 -167
- package/tests/server/context.test.tsrx +13 -17
- package/tests/server/dynamic-elements.test.tsrx +147 -148
- package/tests/server/for.test.tsrx +115 -84
- package/tests/server/head.test.tsrx +54 -31
- package/tests/server/html-nesting-validation.test.tsrx +16 -8
- package/tests/server/if.test.tsrx +49 -59
- package/tests/server/lazy-destructuring.test.tsrx +288 -366
- package/tests/server/return.test.tsrx +58 -36
- package/tests/server/streaming-ssr.test.tsrx +4 -4
- package/tests/server/style-identifier.test.tsrx +61 -69
- package/tests/server/switch.test.tsrx +89 -97
- package/tests/server/track-async-serialization.test.tsrx +85 -103
- package/tests/server/try.test.tsrx +275 -360
- package/tests/utils/ref-types.test.js +72 -0
- package/tests/utils/vite-plugin-config.test.js +41 -74
- package/types/index.d.ts +29 -4
- package/src/runtime/internal/client/compat.js +0 -40
- package/tests/utils/compiler-compat-config.test.js +0 -38
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { PropsWithExtras } from 'ripple';
|
|
2
|
-
import { createRefKey, effect, flushSync, track } from 'ripple';
|
|
2
|
+
import { createRefKey, Dynamic, effect, flushSync, track } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('dynamic DOM elements', () => {
|
|
5
5
|
it('renders static dynamic element', () => {
|
|
6
|
-
function App() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<@tag>{'Hello World'}</@tag>
|
|
10
|
-
</>;
|
|
6
|
+
function App() @{
|
|
7
|
+
let tag = track('div');
|
|
8
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
11
9
|
}
|
|
12
10
|
render(App);
|
|
13
11
|
|
|
@@ -16,14 +14,10 @@ describe('dynamic DOM elements', () => {
|
|
|
16
14
|
expect(element.textContent).toBe('Hello World');
|
|
17
15
|
});
|
|
18
16
|
|
|
19
|
-
// The ts errors below are due to limitations in our current tsx generation for dynamic elements.
|
|
20
|
-
// They can be ignored for now. But we'll fix them via jsx() vs <jsx>
|
|
21
17
|
it('renders static dynamic element from a plain object with a tracked property', () => {
|
|
22
|
-
function App() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<obj.tag.value>{'Hello World'}</obj.tag.value>
|
|
26
|
-
</>;
|
|
18
|
+
function App() @{
|
|
19
|
+
let obj = { tag: track('div') };
|
|
20
|
+
<Dynamic is={obj.tag}>{'Hello World'}</Dynamic>
|
|
27
21
|
}
|
|
28
22
|
render(App);
|
|
29
23
|
|
|
@@ -33,12 +27,10 @@ describe('dynamic DOM elements', () => {
|
|
|
33
27
|
});
|
|
34
28
|
|
|
35
29
|
it('renders static dynamic element from a tracked object with a tracked property', () => {
|
|
36
|
-
function App() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<@tag>{'Hello World'}</@tag>
|
|
41
|
-
</>;
|
|
30
|
+
function App() @{
|
|
31
|
+
let obj = track({ tag: track('div') });
|
|
32
|
+
let tag = obj.value.tag;
|
|
33
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
42
34
|
}
|
|
43
35
|
render(App);
|
|
44
36
|
|
|
@@ -50,12 +42,10 @@ describe('dynamic DOM elements', () => {
|
|
|
50
42
|
it(
|
|
51
43
|
'renders static dynamic element from a tracked object with a computed tracked property',
|
|
52
44
|
() => {
|
|
53
|
-
function App() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<@tag>{'Hello World'}</@tag>
|
|
58
|
-
</>;
|
|
45
|
+
function App() @{
|
|
46
|
+
let obj = track({ tag: track('div') });
|
|
47
|
+
let tag = obj.value['tag'];
|
|
48
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
59
49
|
}
|
|
60
50
|
render(App);
|
|
61
51
|
|
|
@@ -66,18 +56,16 @@ describe('dynamic DOM elements', () => {
|
|
|
66
56
|
);
|
|
67
57
|
|
|
68
58
|
it('renders reactive dynamic element', () => {
|
|
69
|
-
function App() {
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
function App() @{
|
|
60
|
+
let &[tag] = track('div');
|
|
61
|
+
<>
|
|
72
62
|
<button
|
|
73
63
|
onClick={() => {
|
|
74
64
|
tag = 'span';
|
|
75
65
|
}}
|
|
76
|
-
>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<@tag id="dynamic">{'Hello World'}</@tag>
|
|
80
|
-
</>;
|
|
66
|
+
>{'Change Tag'}</button>
|
|
67
|
+
<Dynamic is={tag} id="dynamic">{'Hello World'}</Dynamic>
|
|
68
|
+
</>
|
|
81
69
|
}
|
|
82
70
|
render(App);
|
|
83
71
|
|
|
@@ -98,11 +86,9 @@ describe('dynamic DOM elements', () => {
|
|
|
98
86
|
});
|
|
99
87
|
|
|
100
88
|
it('renders self-closing dynamic element', () => {
|
|
101
|
-
function App() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<@tag type="text" value="test" />
|
|
105
|
-
</>;
|
|
89
|
+
function App() @{
|
|
90
|
+
let tag = track('input');
|
|
91
|
+
<Dynamic is={tag} type="text" value="test" />
|
|
106
92
|
}
|
|
107
93
|
render(App);
|
|
108
94
|
|
|
@@ -113,12 +99,15 @@ describe('dynamic DOM elements', () => {
|
|
|
113
99
|
});
|
|
114
100
|
|
|
115
101
|
it('handles dynamic element with attributes', () => {
|
|
116
|
-
function App() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
function App() @{
|
|
103
|
+
let tag = track('div');
|
|
104
|
+
let &[className] = track('test-class');
|
|
105
|
+
<Dynamic
|
|
106
|
+
is={tag}
|
|
107
|
+
class={className}
|
|
108
|
+
id="test"
|
|
109
|
+
data-testid="dynamic-element"
|
|
110
|
+
>{'Content'}</Dynamic>
|
|
122
111
|
}
|
|
123
112
|
render(App);
|
|
124
113
|
|
|
@@ -130,14 +119,12 @@ describe('dynamic DOM elements', () => {
|
|
|
130
119
|
});
|
|
131
120
|
|
|
132
121
|
it('handles nested dynamic elements', () => {
|
|
133
|
-
function App() {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
</@outerTag>
|
|
140
|
-
</>;
|
|
122
|
+
function App() @{
|
|
123
|
+
let outerTag = track('div');
|
|
124
|
+
let innerTag = track('span');
|
|
125
|
+
<Dynamic is={outerTag} class="outer">
|
|
126
|
+
<Dynamic is={innerTag} class="inner">{'Nested content'}</Dynamic>
|
|
127
|
+
</Dynamic>
|
|
141
128
|
}
|
|
142
129
|
render(App);
|
|
143
130
|
|
|
@@ -151,14 +138,13 @@ describe('dynamic DOM elements', () => {
|
|
|
151
138
|
});
|
|
152
139
|
|
|
153
140
|
it('handles dynamic element with class object', () => {
|
|
154
|
-
function App() {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
</>;
|
|
141
|
+
function App() @{
|
|
142
|
+
let tag = track('div');
|
|
143
|
+
let &[active] = track(true);
|
|
144
|
+
<Dynamic
|
|
145
|
+
is={tag}
|
|
146
|
+
class={{ active: active, 'dynamic-element': true }}
|
|
147
|
+
>{'Element with class object'}</Dynamic>
|
|
162
148
|
}
|
|
163
149
|
render(App);
|
|
164
150
|
|
|
@@ -169,19 +155,16 @@ describe('dynamic DOM elements', () => {
|
|
|
169
155
|
});
|
|
170
156
|
|
|
171
157
|
it('handles dynamic element with style object', () => {
|
|
172
|
-
function App() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{'Styled dynamic element'}
|
|
183
|
-
</@tag>
|
|
184
|
-
</>;
|
|
158
|
+
function App() @{
|
|
159
|
+
let tag = track('span');
|
|
160
|
+
<Dynamic
|
|
161
|
+
is={tag}
|
|
162
|
+
style={{
|
|
163
|
+
color: 'red',
|
|
164
|
+
fontSize: '16px',
|
|
165
|
+
fontWeight: 'bold',
|
|
166
|
+
}}
|
|
167
|
+
>{'Styled dynamic element'}</Dynamic>
|
|
185
168
|
}
|
|
186
169
|
render(App);
|
|
187
170
|
|
|
@@ -193,16 +176,18 @@ describe('dynamic DOM elements', () => {
|
|
|
193
176
|
});
|
|
194
177
|
|
|
195
178
|
it('handles dynamic element with spread attributes', () => {
|
|
196
|
-
function App() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
179
|
+
function App() @{
|
|
180
|
+
let tag = track('section');
|
|
181
|
+
const attrs = {
|
|
182
|
+
id: 'spread-section',
|
|
183
|
+
'data-testid': 'spread-test',
|
|
184
|
+
class: 'spread-class',
|
|
185
|
+
};
|
|
186
|
+
<Dynamic
|
|
187
|
+
is={tag}
|
|
188
|
+
{...attrs}
|
|
189
|
+
data-extra="additional"
|
|
190
|
+
>{'Element with spread attributes'}</Dynamic>
|
|
206
191
|
}
|
|
207
192
|
render(App);
|
|
208
193
|
|
|
@@ -217,18 +202,15 @@ describe('dynamic DOM elements', () => {
|
|
|
217
202
|
it('handles dynamic element with ref', () => {
|
|
218
203
|
let capturedElement: HTMLElement | null = null;
|
|
219
204
|
|
|
220
|
-
function App() {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{'Element with ref'}
|
|
230
|
-
</@tag>
|
|
231
|
-
</>;
|
|
205
|
+
function App() @{
|
|
206
|
+
let tag = track('article');
|
|
207
|
+
<Dynamic
|
|
208
|
+
is={tag}
|
|
209
|
+
ref={(node: HTMLElement) => {
|
|
210
|
+
capturedElement = node;
|
|
211
|
+
}}
|
|
212
|
+
id="ref-test"
|
|
213
|
+
>{'Element with ref'}</Dynamic>
|
|
232
214
|
}
|
|
233
215
|
render(App);
|
|
234
216
|
flushSync();
|
|
@@ -244,27 +226,26 @@ describe('dynamic DOM elements', () => {
|
|
|
244
226
|
let anonymousRefElement: HTMLInputElement | null = null;
|
|
245
227
|
let namedRefElement: HTMLInputElement | null = null;
|
|
246
228
|
|
|
247
|
-
function App() {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</>;
|
|
229
|
+
function App() @{
|
|
230
|
+
let tag = track('input');
|
|
231
|
+
let input: HTMLInputElement | undefined;
|
|
232
|
+
const state: { anonymous?: HTMLInputElement } = {};
|
|
233
|
+
effect(() => {
|
|
234
|
+
refAttrElement = input ?? null;
|
|
235
|
+
anonymousRefElement = state.anonymous ?? null;
|
|
236
|
+
});
|
|
237
|
+
<Dynamic
|
|
238
|
+
is={tag}
|
|
239
|
+
id="dynamic-ref-combo"
|
|
240
|
+
type="text"
|
|
241
|
+
ref={[
|
|
242
|
+
input,
|
|
243
|
+
state.anonymous,
|
|
244
|
+
(node: HTMLInputElement | null) => {
|
|
245
|
+
namedRefElement = node;
|
|
246
|
+
},
|
|
247
|
+
]}
|
|
248
|
+
/>
|
|
268
249
|
}
|
|
269
250
|
|
|
270
251
|
render(App);
|
|
@@ -284,59 +265,115 @@ describe('dynamic DOM elements', () => {
|
|
|
284
265
|
let anonymousRefElement: HTMLInputElement | null = null;
|
|
285
266
|
let namedRefElement: HTMLInputElement | null = null;
|
|
286
267
|
|
|
287
|
-
function Child(props: PropsWithExtras<{}>) {
|
|
288
|
-
|
|
268
|
+
function Child(props: PropsWithExtras<{}>) @{
|
|
269
|
+
<input id="dynamic-component-ref-combo" type="text" {...props} />
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function App() @{
|
|
273
|
+
let dynamic = track(() => Child);
|
|
274
|
+
let input: HTMLInputElement | undefined;
|
|
275
|
+
const state: { anonymous?: HTMLInputElement } = {};
|
|
276
|
+
effect(() => {
|
|
277
|
+
refAttrElement = input ?? null;
|
|
278
|
+
anonymousRefElement = state.anonymous ?? null;
|
|
279
|
+
});
|
|
280
|
+
<Dynamic
|
|
281
|
+
is={dynamic}
|
|
282
|
+
ref={[
|
|
283
|
+
input,
|
|
284
|
+
state.anonymous,
|
|
285
|
+
(node: HTMLInputElement | null) => {
|
|
286
|
+
namedRefElement = node;
|
|
287
|
+
},
|
|
288
|
+
]}
|
|
289
|
+
/>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
render(App);
|
|
293
|
+
flushSync();
|
|
294
|
+
|
|
295
|
+
const element = container.querySelector('#dynamic-component-ref-combo');
|
|
296
|
+
expect(element).toBeInstanceOf(HTMLInputElement);
|
|
297
|
+
expect(refAttrElement).toBe(element);
|
|
298
|
+
expect(anonymousRefElement).toBe(element);
|
|
299
|
+
expect(namedRefElement).toBe(element);
|
|
300
|
+
expect(element!.hasAttribute('ref')).toBe(false);
|
|
301
|
+
expect(element!.hasAttribute('input_ref')).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('updates forwarded refs when a dynamic component changes', () => {
|
|
305
|
+
let refAttrElement: HTMLInputElement | null = null;
|
|
306
|
+
let anonymousRefElement: HTMLInputElement | null = null;
|
|
307
|
+
let namedRefElement: HTMLInputElement | null = null;
|
|
308
|
+
|
|
309
|
+
function TextInput(props: PropsWithExtras<{}>) @{
|
|
310
|
+
<input id="dynamic-component-text" type="text" {...props} />
|
|
289
311
|
}
|
|
290
312
|
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
313
|
+
function SearchInput(props: PropsWithExtras<{}>) @{
|
|
314
|
+
<input id="dynamic-component-search" type="search" {...props} />
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function App() @{
|
|
318
|
+
let &[dynamic] = track(() => TextInput);
|
|
319
|
+
<>
|
|
320
|
+
<button
|
|
321
|
+
onClick={() => {
|
|
322
|
+
dynamic = dynamic === TextInput ? SearchInput : TextInput;
|
|
323
|
+
}}
|
|
324
|
+
>{'Change Component'}</button>
|
|
325
|
+
<Dynamic
|
|
326
|
+
is={dynamic}
|
|
297
327
|
ref={[
|
|
298
|
-
|
|
299
|
-
|
|
328
|
+
(node: HTMLInputElement | null) => {
|
|
329
|
+
refAttrElement = node;
|
|
330
|
+
},
|
|
331
|
+
(node: HTMLInputElement | null) => {
|
|
332
|
+
anonymousRefElement = node;
|
|
333
|
+
},
|
|
300
334
|
(node: HTMLInputElement | null) => {
|
|
301
335
|
namedRefElement = node;
|
|
302
336
|
},
|
|
303
337
|
]}
|
|
304
338
|
/>
|
|
305
|
-
|
|
306
|
-
refAttrElement = input ?? null;
|
|
307
|
-
anonymousRefElement = state.anonymous ?? null;
|
|
308
|
-
});
|
|
309
|
-
</>;
|
|
339
|
+
</>
|
|
310
340
|
}
|
|
311
341
|
|
|
312
342
|
render(App);
|
|
313
343
|
flushSync();
|
|
314
344
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
expect(
|
|
318
|
-
expect(
|
|
319
|
-
expect(
|
|
320
|
-
expect(
|
|
321
|
-
|
|
345
|
+
const button = container.querySelector('button')!;
|
|
346
|
+
const textInput = container.querySelector('#dynamic-component-text');
|
|
347
|
+
expect(textInput).toBeInstanceOf(HTMLInputElement);
|
|
348
|
+
expect(refAttrElement).toBe(textInput);
|
|
349
|
+
expect(anonymousRefElement).toBe(textInput);
|
|
350
|
+
expect(namedRefElement).toBe(textInput);
|
|
351
|
+
|
|
352
|
+
button.click();
|
|
353
|
+
flushSync();
|
|
354
|
+
|
|
355
|
+
const searchInput = container.querySelector('#dynamic-component-search');
|
|
356
|
+
expect(searchInput).toBeInstanceOf(HTMLInputElement);
|
|
357
|
+
expect(container.querySelector('#dynamic-component-text')).toBeNull();
|
|
358
|
+
expect(refAttrElement).toBe(searchInput);
|
|
359
|
+
expect(anonymousRefElement).toBe(searchInput);
|
|
360
|
+
expect(namedRefElement).toBe(searchInput);
|
|
322
361
|
});
|
|
323
362
|
|
|
324
363
|
it('handles dynamic element with createRefKey in spread', () => {
|
|
325
|
-
function App() {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<@tag {...dynamicProps}>{'Element with spread ref'}</@tag>
|
|
339
|
-
</>;
|
|
364
|
+
function App() @{
|
|
365
|
+
let tag = track('header');
|
|
366
|
+
function elementRef(node: HTMLElement) {
|
|
367
|
+
// Set an attribute on the element to prove ref was called
|
|
368
|
+
node.setAttribute('data-spread-ref-called', 'true');
|
|
369
|
+
node.setAttribute('data-spread-ref-tag', node.tagName.toLowerCase());
|
|
370
|
+
}
|
|
371
|
+
const dynamicProps = {
|
|
372
|
+
id: 'spread-ref-test',
|
|
373
|
+
class: 'ref-element',
|
|
374
|
+
[createRefKey()]: elementRef,
|
|
375
|
+
};
|
|
376
|
+
<Dynamic is={tag} {...dynamicProps}>{'Element with spread ref'}</Dynamic>
|
|
340
377
|
}
|
|
341
378
|
render(App);
|
|
342
379
|
flushSync();
|
|
@@ -351,26 +388,25 @@ describe('dynamic DOM elements', () => {
|
|
|
351
388
|
});
|
|
352
389
|
|
|
353
390
|
it('has reactive attributes on dynamic elements', () => {
|
|
354
|
-
function App() {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
391
|
+
function App() @{
|
|
392
|
+
let tag = track('div');
|
|
393
|
+
let &[count] = track(0);
|
|
394
|
+
<>
|
|
358
395
|
<button
|
|
359
396
|
onClick={() => {
|
|
360
397
|
count++;
|
|
361
398
|
}}
|
|
362
|
-
>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
<@tag
|
|
399
|
+
>{'Increment'}</button>
|
|
400
|
+
<Dynamic
|
|
401
|
+
is={tag}
|
|
366
402
|
id={count % 2 ? 'even' : 'odd'}
|
|
367
403
|
class={count % 2 ? 'even-class' : 'odd-class'}
|
|
368
404
|
data-count={count}
|
|
369
405
|
>
|
|
370
406
|
{'Count: '}
|
|
371
407
|
{count}
|
|
372
|
-
|
|
373
|
-
|
|
408
|
+
</Dynamic>
|
|
409
|
+
</>
|
|
374
410
|
}
|
|
375
411
|
|
|
376
412
|
render(App);
|
|
@@ -406,16 +442,16 @@ describe('dynamic DOM elements', () => {
|
|
|
406
442
|
});
|
|
407
443
|
|
|
408
444
|
it('applies scoped CSS to dynamic elements', () => {
|
|
409
|
-
function App() {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
445
|
+
function App() @{
|
|
446
|
+
let tag = track('div');
|
|
447
|
+
<>
|
|
448
|
+
<Dynamic is={tag} class="test-class">{'Dynamic element'}</Dynamic>
|
|
413
449
|
<style>
|
|
414
450
|
.test-class {
|
|
415
451
|
color: red;
|
|
416
452
|
}
|
|
417
453
|
</style>
|
|
418
|
-
|
|
454
|
+
</>
|
|
419
455
|
}
|
|
420
456
|
|
|
421
457
|
render(App);
|
|
@@ -431,11 +467,12 @@ describe('dynamic DOM elements', () => {
|
|
|
431
467
|
});
|
|
432
468
|
|
|
433
469
|
it('applies scoped CSS to dynamic elements with reactive classes', () => {
|
|
434
|
-
function App() {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
470
|
+
function App() @{
|
|
471
|
+
let tag = track('button');
|
|
472
|
+
let &[count] = track(0);
|
|
473
|
+
<>
|
|
474
|
+
<Dynamic
|
|
475
|
+
is={tag}
|
|
439
476
|
class={count % 2 ? 'even' : 'odd'}
|
|
440
477
|
id={count % 2 ? 'even' : 'odd'}
|
|
441
478
|
onClick={() => {
|
|
@@ -444,7 +481,7 @@ describe('dynamic DOM elements', () => {
|
|
|
444
481
|
>
|
|
445
482
|
{'Count: '}
|
|
446
483
|
{count}
|
|
447
|
-
|
|
484
|
+
</Dynamic>
|
|
448
485
|
<style>
|
|
449
486
|
.even {
|
|
450
487
|
background-color: green;
|
|
@@ -455,7 +492,7 @@ describe('dynamic DOM elements', () => {
|
|
|
455
492
|
color: white;
|
|
456
493
|
}
|
|
457
494
|
</style>
|
|
458
|
-
|
|
495
|
+
</>
|
|
459
496
|
}
|
|
460
497
|
|
|
461
498
|
render(App);
|
|
@@ -505,10 +542,10 @@ describe('dynamic DOM elements', () => {
|
|
|
505
542
|
class: string;
|
|
506
543
|
id: string;
|
|
507
544
|
onClick: EventListener;
|
|
508
|
-
}>) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
545
|
+
}>) @{
|
|
546
|
+
const tag = track('button');
|
|
547
|
+
<>
|
|
548
|
+
<Dynamic is={tag} {...rest}>{rest.class}</Dynamic>
|
|
512
549
|
<style>
|
|
513
550
|
.even {
|
|
514
551
|
background-color: green;
|
|
@@ -517,20 +554,18 @@ describe('dynamic DOM elements', () => {
|
|
|
517
554
|
background-color: red;
|
|
518
555
|
}
|
|
519
556
|
</style>
|
|
520
|
-
|
|
557
|
+
</>
|
|
521
558
|
}
|
|
522
559
|
|
|
523
|
-
function App() {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
/>
|
|
533
|
-
</>;
|
|
560
|
+
function App() @{
|
|
561
|
+
let &[count] = track(0);
|
|
562
|
+
<DynamicButton
|
|
563
|
+
class={count % 2 ? 'even' : 'odd'}
|
|
564
|
+
id={count % 2 ? 'even' : 'odd'}
|
|
565
|
+
onClick={() => {
|
|
566
|
+
count++;
|
|
567
|
+
}}
|
|
568
|
+
/>
|
|
534
569
|
}
|
|
535
570
|
|
|
536
571
|
render(App);
|
|
@@ -565,18 +600,18 @@ describe('dynamic DOM elements', () => {
|
|
|
565
600
|
});
|
|
566
601
|
|
|
567
602
|
it('adds scoping class to dynamic elements', () => {
|
|
568
|
-
function App() {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
603
|
+
function App() @{
|
|
604
|
+
let tag = track('div');
|
|
605
|
+
<>
|
|
606
|
+
<Dynamic is={tag} class="scoped">
|
|
572
607
|
<p>{'Scoped dynamic element'}</p>
|
|
573
|
-
|
|
608
|
+
</Dynamic>
|
|
574
609
|
<style>
|
|
575
610
|
.scoped {
|
|
576
611
|
color: blue;
|
|
577
612
|
}
|
|
578
613
|
</style>
|
|
579
|
-
|
|
614
|
+
</>
|
|
580
615
|
}
|
|
581
616
|
render(App);
|
|
582
617
|
|
|
@@ -588,18 +623,18 @@ describe('dynamic DOM elements', () => {
|
|
|
588
623
|
});
|
|
589
624
|
|
|
590
625
|
it('adds scoping class to dynamic elements when selector targets by tag name', () => {
|
|
591
|
-
function App() {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
626
|
+
function App() @{
|
|
627
|
+
let tag = track('div');
|
|
628
|
+
<>
|
|
629
|
+
<Dynamic is={tag} class="scoped">
|
|
595
630
|
<p>{'Scoped dynamic element'}</p>
|
|
596
|
-
|
|
631
|
+
</Dynamic>
|
|
597
632
|
<style>
|
|
598
633
|
div {
|
|
599
634
|
color: blue;
|
|
600
635
|
}
|
|
601
636
|
</style>
|
|
602
|
-
|
|
637
|
+
</>
|
|
603
638
|
}
|
|
604
639
|
render(App);
|
|
605
640
|
|
|
@@ -611,8 +646,8 @@ describe('dynamic DOM elements', () => {
|
|
|
611
646
|
});
|
|
612
647
|
|
|
613
648
|
it('doesn\'t add scoping class to components inside dynamic element', () => {
|
|
614
|
-
function Child() {
|
|
615
|
-
|
|
649
|
+
function Child() @{
|
|
650
|
+
<>
|
|
616
651
|
<div class="child">
|
|
617
652
|
<p>{'I am a child component'}</p>
|
|
618
653
|
</div>
|
|
@@ -621,22 +656,22 @@ describe('dynamic DOM elements', () => {
|
|
|
621
656
|
color: blue;
|
|
622
657
|
}
|
|
623
658
|
</style>
|
|
624
|
-
|
|
659
|
+
</>
|
|
625
660
|
}
|
|
626
661
|
|
|
627
|
-
function App() {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
662
|
+
function App() @{
|
|
663
|
+
let tag = track('div');
|
|
664
|
+
<>
|
|
665
|
+
<Dynamic is={tag} class="scoped">
|
|
631
666
|
<p>{'Scoped dynamic element'}</p>
|
|
632
667
|
<Child />
|
|
633
|
-
|
|
668
|
+
</Dynamic>
|
|
634
669
|
<style>
|
|
635
670
|
div {
|
|
636
671
|
color: blue;
|
|
637
672
|
}
|
|
638
673
|
</style>
|
|
639
|
-
|
|
674
|
+
</>
|
|
640
675
|
}
|
|
641
676
|
render(App);
|
|
642
677
|
|
|
@@ -657,8 +692,8 @@ describe('dynamic DOM elements', () => {
|
|
|
657
692
|
});
|
|
658
693
|
|
|
659
694
|
it('doesn\'t add scoping class to dynamically rendered component', () => {
|
|
660
|
-
function Child() {
|
|
661
|
-
|
|
695
|
+
function Child() @{
|
|
696
|
+
<>
|
|
662
697
|
<div class="child">
|
|
663
698
|
<p>{'I am a child component'}</p>
|
|
664
699
|
</div>
|
|
@@ -667,19 +702,19 @@ describe('dynamic DOM elements', () => {
|
|
|
667
702
|
color: green;
|
|
668
703
|
}
|
|
669
704
|
</style>
|
|
670
|
-
|
|
705
|
+
</>
|
|
671
706
|
}
|
|
672
707
|
|
|
673
|
-
function App() {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
708
|
+
function App() @{
|
|
709
|
+
let tag = track(() => Child);
|
|
710
|
+
<>
|
|
711
|
+
<Dynamic is={tag} />
|
|
677
712
|
<style>
|
|
678
713
|
.child {
|
|
679
714
|
color: red;
|
|
680
715
|
}
|
|
681
716
|
</style>
|
|
682
|
-
|
|
717
|
+
</>
|
|
683
718
|
}
|
|
684
719
|
render(App);
|
|
685
720
|
|
|
@@ -697,29 +732,23 @@ describe('dynamic DOM elements', () => {
|
|
|
697
732
|
let capturedElement: HTMLElement | null = null;
|
|
698
733
|
let refCallCount = 0;
|
|
699
734
|
|
|
700
|
-
function Button(props: any) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
<@el {...props} />
|
|
704
|
-
</>;
|
|
735
|
+
function Button(props: any) @{
|
|
736
|
+
const el = track('button');
|
|
737
|
+
<Dynamic is={el} {...props} />
|
|
705
738
|
}
|
|
706
739
|
|
|
707
|
-
function App() {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
>
|
|
720
|
-
{'content'}
|
|
721
|
-
</Button>
|
|
722
|
-
</>;
|
|
740
|
+
function App() @{
|
|
741
|
+
let &[active] = track(false);
|
|
742
|
+
<Button
|
|
743
|
+
data-active={String(active)}
|
|
744
|
+
onClick={() => {
|
|
745
|
+
active = !active;
|
|
746
|
+
}}
|
|
747
|
+
ref={(el: HTMLElement) => {
|
|
748
|
+
capturedElement = el;
|
|
749
|
+
refCallCount++;
|
|
750
|
+
}}
|
|
751
|
+
>{'content'}</Button>
|
|
723
752
|
}
|
|
724
753
|
|
|
725
754
|
render(App);
|
|
@@ -743,34 +772,30 @@ describe('dynamic DOM elements', () => {
|
|
|
743
772
|
it('handles ref on dynamic element with spread props containing reactive values', () => {
|
|
744
773
|
let capturedElement: HTMLElement | null = null;
|
|
745
774
|
|
|
746
|
-
function Button(props: any) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
{'content: '}
|
|
771
|
-
{active}
|
|
772
|
-
</Button>
|
|
773
|
-
</>;
|
|
775
|
+
function Button(props: any) @{
|
|
776
|
+
const el = track('button');
|
|
777
|
+
<Dynamic is={el} {...props} />
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function App() @{
|
|
781
|
+
let &[active] = track(false);
|
|
782
|
+
let &[buttonProps] = track(
|
|
783
|
+
() => ({
|
|
784
|
+
'data-active': active,
|
|
785
|
+
}),
|
|
786
|
+
);
|
|
787
|
+
<Button
|
|
788
|
+
{...buttonProps}
|
|
789
|
+
onClick={() => {
|
|
790
|
+
active = !active;
|
|
791
|
+
}}
|
|
792
|
+
ref={(el: HTMLElement) => {
|
|
793
|
+
capturedElement = el;
|
|
794
|
+
}}
|
|
795
|
+
>
|
|
796
|
+
{'content: '}
|
|
797
|
+
{active}
|
|
798
|
+
</Button>
|
|
774
799
|
}
|
|
775
800
|
|
|
776
801
|
render(App);
|
|
@@ -792,32 +817,26 @@ describe('dynamic DOM elements', () => {
|
|
|
792
817
|
let refCallCount = 0;
|
|
793
818
|
let capturedElement: HTMLElement | null = null;
|
|
794
819
|
|
|
795
|
-
function Button(props: any) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
<@el {...props} />
|
|
799
|
-
</>;
|
|
820
|
+
function Button(props: any) @{
|
|
821
|
+
const el = track('button');
|
|
822
|
+
<Dynamic is={el} {...props} />
|
|
800
823
|
}
|
|
801
824
|
|
|
802
|
-
function App() {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
>
|
|
818
|
-
{'content'}
|
|
819
|
-
</Button>
|
|
820
|
-
</>;
|
|
825
|
+
function App() @{
|
|
826
|
+
let &[active] = track(false);
|
|
827
|
+
<Button
|
|
828
|
+
data-active={String(active)}
|
|
829
|
+
onClick={() => {
|
|
830
|
+
active = !active;
|
|
831
|
+
}}
|
|
832
|
+
ref={(el: HTMLElement) => {
|
|
833
|
+
capturedElement = el;
|
|
834
|
+
refCallCount++;
|
|
835
|
+
return () => {
|
|
836
|
+
cleanupCount++;
|
|
837
|
+
};
|
|
838
|
+
}}
|
|
839
|
+
>{'content'}</Button>
|
|
821
840
|
}
|
|
822
841
|
|
|
823
842
|
render(App);
|
|
@@ -837,16 +856,16 @@ describe('dynamic DOM elements', () => {
|
|
|
837
856
|
});
|
|
838
857
|
|
|
839
858
|
it('should remove and add back a text node in a conditional statement with a tracked', () => {
|
|
840
|
-
function App() {
|
|
841
|
-
|
|
842
|
-
|
|
859
|
+
function App() @{
|
|
860
|
+
let &[b] = track(true);
|
|
861
|
+
<>
|
|
843
862
|
<div>
|
|
844
|
-
if (b) {
|
|
845
|
-
|
|
863
|
+
@if (b) {
|
|
864
|
+
<>Inside if</>
|
|
846
865
|
}
|
|
847
866
|
</div>
|
|
848
867
|
<button onClick={() => (b = !b)}>{'Toggle b'}</button>
|
|
849
|
-
|
|
868
|
+
</>
|
|
850
869
|
}
|
|
851
870
|
|
|
852
871
|
render(App);
|