ripple 0.3.74 → 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 +50 -0
- package/package.json +3 -3
- package/src/jsx-runtime.d.ts +2 -2
- package/src/runtime/dynamic-client.js +33 -0
- package/src/runtime/dynamic-server.js +80 -0
- package/src/runtime/index-client.js +2 -0
- package/src/runtime/index-server.js +2 -0
- package/src/runtime/internal/client/blocks.js +3 -2
- package/src/runtime/internal/client/composite.js +11 -6
- package/src/runtime/internal/client/render.js +5 -2
- package/src/runtime/internal/server/index.js +8 -1
- package/tests/client/basic/basic.components.test.tsrx +2 -2
- package/tests/client/basic/basic.styling.test.tsrx +16 -14
- package/tests/client/composite/composite.dynamic-components.test.tsrx +24 -6
- package/tests/client/css/global-additional-cases.test.tsrx +4 -4
- package/tests/client/css/style-identifier.test.tsrx +4 -4
- package/tests/client/dynamic-elements.test.tsrx +113 -38
- package/tests/client/head.test.tsrx +34 -0
- package/tests/client/svg.test.tsrx +8 -7
- package/tests/server/basic.components.test.tsrx +2 -2
- package/tests/server/basic.test.tsrx +3 -3
- package/tests/server/dynamic-elements.test.tsrx +60 -29
- package/tests/server/head.test.tsrx +23 -0
- package/tests/server/style-identifier.test.tsrx +4 -4
- package/types/index.d.ts +28 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { PropsWithExtras } from 'ripple';
|
|
2
|
-
import { createRefKey, track } from 'ripple';
|
|
1
|
+
import type { DynamicProps, PropsWithExtras, TSRXElement } from 'ripple';
|
|
2
|
+
import { createRefKey, Dynamic, track } from 'ripple';
|
|
3
3
|
|
|
4
4
|
describe('server dynamic DOM elements', () => {
|
|
5
5
|
it('renders static dynamic element', async () => {
|
|
6
6
|
function App() @{
|
|
7
7
|
let tag = track('div');
|
|
8
|
-
|
|
8
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const { body } = await render(App);
|
|
@@ -13,13 +13,11 @@ describe('server dynamic DOM elements', () => {
|
|
|
13
13
|
expect(body).toBeHtml('<div>Hello World</div>');
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
// The ts errors below are due to limitations in our current tsx generation for dynamic elements.
|
|
17
|
-
// They can be ignored for now. But we'll fix them via jsx() vs <jsx>
|
|
18
16
|
it('renders static dynamic element from a plain object with a tracked property', async () => {
|
|
19
17
|
function App() @{
|
|
20
18
|
let obj = { tag: track('div') };
|
|
21
19
|
let tag = obj.tag;
|
|
22
|
-
|
|
20
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
const { body } = await render(App);
|
|
@@ -27,11 +25,30 @@ describe('server dynamic DOM elements', () => {
|
|
|
27
25
|
expect(body).toBeHtml('<div>Hello World</div>');
|
|
28
26
|
});
|
|
29
27
|
|
|
28
|
+
it('does not pass is to dynamic component props', async () => {
|
|
29
|
+
function Child(props: Omit<DynamicProps<'div'>, 'is'> & {
|
|
30
|
+
label: string;
|
|
31
|
+
is?: Function;
|
|
32
|
+
}): TSRXElement<'div'> @{
|
|
33
|
+
<div>
|
|
34
|
+
{props.is === undefined && !('is' in props) ? 'hidden' : 'leaked'}
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function App() @{
|
|
39
|
+
<Dynamic is={Child} label="child" class="yo" />
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { body } = await render(App);
|
|
43
|
+
|
|
44
|
+
expect(body).toBeHtml('<div>hidden</div>');
|
|
45
|
+
});
|
|
46
|
+
|
|
30
47
|
it('renders static dynamic element from a tracked object with a tracked property', async () => {
|
|
31
48
|
function App() @{
|
|
32
49
|
let obj = track({ tag: track('div') });
|
|
33
50
|
let tag = obj.value.tag;
|
|
34
|
-
|
|
51
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
const { body } = await render(App);
|
|
@@ -45,7 +62,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
45
62
|
function App() @{
|
|
46
63
|
let obj = track({ tag: track('div') });
|
|
47
64
|
let tag = obj.value['tag'];
|
|
48
|
-
|
|
65
|
+
<Dynamic is={tag}>{'Hello World'}</Dynamic>
|
|
49
66
|
}
|
|
50
67
|
|
|
51
68
|
const { body } = await render(App);
|
|
@@ -57,7 +74,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
57
74
|
it('renders self-closing dynamic element', async () => {
|
|
58
75
|
function App() @{
|
|
59
76
|
let tag = track('input');
|
|
60
|
-
|
|
77
|
+
<Dynamic is={tag} type="text" value="test" />
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
const { body } = await render(App);
|
|
@@ -69,7 +86,12 @@ describe('server dynamic DOM elements', () => {
|
|
|
69
86
|
function App() @{
|
|
70
87
|
let tag = track('div');
|
|
71
88
|
let &[className] = track('test-class');
|
|
72
|
-
|
|
89
|
+
<Dynamic
|
|
90
|
+
is={tag}
|
|
91
|
+
class={className}
|
|
92
|
+
id="test"
|
|
93
|
+
data-testid="dynamic-element"
|
|
94
|
+
>{'Content'}</Dynamic>
|
|
73
95
|
}
|
|
74
96
|
const { body } = await render(App);
|
|
75
97
|
const { document } = parseHtml(body);
|
|
@@ -86,9 +108,9 @@ describe('server dynamic DOM elements', () => {
|
|
|
86
108
|
function App() @{
|
|
87
109
|
let outerTag = track('div');
|
|
88
110
|
let innerTag = track('span');
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
<Dynamic is={outerTag} class="outer">
|
|
112
|
+
<Dynamic is={innerTag} class="inner">{'Nested content'}</Dynamic>
|
|
113
|
+
</Dynamic>
|
|
92
114
|
}
|
|
93
115
|
const { body } = await render(App);
|
|
94
116
|
const { document } = parseHtml(body);
|
|
@@ -106,7 +128,10 @@ describe('server dynamic DOM elements', () => {
|
|
|
106
128
|
function App() @{
|
|
107
129
|
let tag = track('div');
|
|
108
130
|
let &[active] = track(true);
|
|
109
|
-
|
|
131
|
+
<Dynamic
|
|
132
|
+
is={tag}
|
|
133
|
+
class={{ active: active, 'dynamic-element': true }}
|
|
134
|
+
>{'Element with class object'}</Dynamic>
|
|
110
135
|
}
|
|
111
136
|
|
|
112
137
|
const { body } = await render(App);
|
|
@@ -121,13 +146,14 @@ describe('server dynamic DOM elements', () => {
|
|
|
121
146
|
it('handles dynamic element with style object', async () => {
|
|
122
147
|
function App() @{
|
|
123
148
|
let tag = track('span');
|
|
124
|
-
|
|
149
|
+
<Dynamic
|
|
150
|
+
is={tag}
|
|
125
151
|
style={{
|
|
126
152
|
color: 'red',
|
|
127
153
|
fontSize: '16px',
|
|
128
154
|
fontWeight: 'bold',
|
|
129
155
|
}}
|
|
130
|
-
>{'Styled dynamic element'}
|
|
156
|
+
>{'Styled dynamic element'}</Dynamic>
|
|
131
157
|
}
|
|
132
158
|
|
|
133
159
|
const { body } = await render(App);
|
|
@@ -148,7 +174,11 @@ describe('server dynamic DOM elements', () => {
|
|
|
148
174
|
'data-testid': 'spread-test',
|
|
149
175
|
class: 'spread-class',
|
|
150
176
|
};
|
|
151
|
-
|
|
177
|
+
<Dynamic
|
|
178
|
+
is={tag}
|
|
179
|
+
{...attrs}
|
|
180
|
+
data-extra="additional"
|
|
181
|
+
>{'Element with spread attributes'}</Dynamic>
|
|
152
182
|
}
|
|
153
183
|
const { body } = await render(App);
|
|
154
184
|
const { document } = parseHtml(body);
|
|
@@ -166,12 +196,13 @@ describe('server dynamic DOM elements', () => {
|
|
|
166
196
|
|
|
167
197
|
function App() @{
|
|
168
198
|
let tag = track('article');
|
|
169
|
-
|
|
199
|
+
<Dynamic
|
|
200
|
+
is={tag}
|
|
170
201
|
ref={(node: HTMLElement) => {
|
|
171
202
|
capturedElement = node;
|
|
172
203
|
}}
|
|
173
204
|
id="ref-test"
|
|
174
|
-
>{'Element with ref'}
|
|
205
|
+
>{'Element with ref'}</Dynamic>
|
|
175
206
|
}
|
|
176
207
|
|
|
177
208
|
const { body } = await render(App);
|
|
@@ -199,7 +230,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
199
230
|
class: 'ref-element',
|
|
200
231
|
[createRefKey()]: elementRef,
|
|
201
232
|
};
|
|
202
|
-
|
|
233
|
+
<Dynamic is={tag} {...dynamicProps}>{'Element with spread ref'}</Dynamic>
|
|
203
234
|
}
|
|
204
235
|
|
|
205
236
|
const { body } = await render(App);
|
|
@@ -218,7 +249,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
218
249
|
function App() @{
|
|
219
250
|
let tag = track('div');
|
|
220
251
|
<>
|
|
221
|
-
|
|
252
|
+
<Dynamic is={tag} class="test-class">{'Dynamic element'}</Dynamic>
|
|
222
253
|
<style>
|
|
223
254
|
.test-class {
|
|
224
255
|
color: red;
|
|
@@ -246,7 +277,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
246
277
|
}>) @{
|
|
247
278
|
const tag = track('button');
|
|
248
279
|
<>
|
|
249
|
-
|
|
280
|
+
<Dynamic is={tag} {...rest}>{rest.class}</Dynamic>
|
|
250
281
|
<style>
|
|
251
282
|
.even {
|
|
252
283
|
background-color: green;
|
|
@@ -283,9 +314,9 @@ describe('server dynamic DOM elements', () => {
|
|
|
283
314
|
function App() @{
|
|
284
315
|
let tag = track('div');
|
|
285
316
|
<>
|
|
286
|
-
|
|
317
|
+
<Dynamic is={tag} class="scoped">
|
|
287
318
|
<p>{'Scoped dynamic element'}</p>
|
|
288
|
-
|
|
319
|
+
</Dynamic>
|
|
289
320
|
<style>
|
|
290
321
|
.scoped {
|
|
291
322
|
color: blue;
|
|
@@ -312,9 +343,9 @@ describe('server dynamic DOM elements', () => {
|
|
|
312
343
|
function App() @{
|
|
313
344
|
let tag = track('div');
|
|
314
345
|
<>
|
|
315
|
-
|
|
346
|
+
<Dynamic is={tag} class="scoped">
|
|
316
347
|
<p>{'Scoped dynamic element'}</p>
|
|
317
|
-
|
|
348
|
+
</Dynamic>
|
|
318
349
|
<style>
|
|
319
350
|
div {
|
|
320
351
|
color: blue;
|
|
@@ -351,10 +382,10 @@ describe('server dynamic DOM elements', () => {
|
|
|
351
382
|
function App() @{
|
|
352
383
|
let tag = track('div');
|
|
353
384
|
<>
|
|
354
|
-
|
|
385
|
+
<Dynamic is={tag} class="scoped">
|
|
355
386
|
<p>{'Scoped dynamic element'}</p>
|
|
356
387
|
<Child />
|
|
357
|
-
|
|
388
|
+
</Dynamic>
|
|
358
389
|
<style>
|
|
359
390
|
div {
|
|
360
391
|
color: blue;
|
|
@@ -399,7 +430,7 @@ describe('server dynamic DOM elements', () => {
|
|
|
399
430
|
function App() @{
|
|
400
431
|
let tag = track(() => Child);
|
|
401
432
|
<>
|
|
402
|
-
|
|
433
|
+
<Dynamic is={tag} />
|
|
403
434
|
<style>
|
|
404
435
|
.child {
|
|
405
436
|
color: red;
|
|
@@ -90,6 +90,29 @@ describe('head elements', () => {
|
|
|
90
90
|
expect(document.title).toBe('');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
it('renders external scripts with src attributes from a loop', async () => {
|
|
94
|
+
const Head = ({ scripts }: { scripts: { src: string }[] }) => @{
|
|
95
|
+
<head>
|
|
96
|
+
@for (const script of scripts) {
|
|
97
|
+
<script src={script.src} />
|
|
98
|
+
}
|
|
99
|
+
</head>
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function App() @{
|
|
103
|
+
<Head scripts={[{ src: '/a.js' }, { src: '/b.js' }]} />
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { head, body } = await render(App);
|
|
107
|
+
const { document } = parseAsFullHtml(head, body);
|
|
108
|
+
|
|
109
|
+
const srcs = Array.from(document.querySelectorAll('head script[src]')).map(
|
|
110
|
+
(node) => node.getAttribute('src'),
|
|
111
|
+
).sort();
|
|
112
|
+
|
|
113
|
+
expect(srcs).toEqual(['/a.js', '/b.js']);
|
|
114
|
+
});
|
|
115
|
+
|
|
93
116
|
it('renders title with conditional content', async () => {
|
|
94
117
|
function App() @{
|
|
95
118
|
let &[showPrefix] = track(true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { track } from 'ripple';
|
|
1
|
+
import { Dynamic, track } from 'ripple';
|
|
2
2
|
import { compile } from '@tsrx/ripple';
|
|
3
3
|
|
|
4
4
|
const external_styles = <style>
|
|
@@ -178,7 +178,7 @@ describe('style class maps (server)', () => {
|
|
|
178
178
|
|
|
179
179
|
let dynamic = track(() => Child);
|
|
180
180
|
<div class="wrapper">
|
|
181
|
-
|
|
181
|
+
<Dynamic is={dynamic} cls={styles.text} />
|
|
182
182
|
</div>
|
|
183
183
|
}
|
|
184
184
|
|
|
@@ -269,9 +269,9 @@ describe('style class maps (server)', () => {
|
|
|
269
269
|
function App() @{
|
|
270
270
|
const DynamicWrapper = track(() => Wrapper);
|
|
271
271
|
<>
|
|
272
|
-
|
|
272
|
+
<Dynamic is={DynamicWrapper}>
|
|
273
273
|
<div class="green">{'Slotted child'}</div>
|
|
274
|
-
|
|
274
|
+
</Dynamic>
|
|
275
275
|
<style>
|
|
276
276
|
.green {
|
|
277
277
|
color: green;
|
package/types/index.d.ts
CHANGED
|
@@ -2,15 +2,16 @@ import type { ExtendedEventOptions } from '@tsrx/core/types';
|
|
|
2
2
|
export type { RefValue } from '@tsrx/core/runtime/ref';
|
|
3
3
|
export type { AddEventOptions, AddEventObject, ExtendedEventOptions } from '@tsrx/core/types';
|
|
4
4
|
|
|
5
|
-
export type Component<T = Record<string, any>> = (props: T) => void;
|
|
5
|
+
export type Component<T = Record<string, any>> = (props: T) => void | TSRXElement;
|
|
6
6
|
|
|
7
7
|
declare const TSRX_ELEMENT: unique symbol;
|
|
8
8
|
declare const REF_KEY: unique symbol;
|
|
9
9
|
export type RefKey = typeof REF_KEY;
|
|
10
10
|
|
|
11
|
-
export type TSRXElement = {
|
|
11
|
+
export type TSRXElement<Tag = any> = {
|
|
12
12
|
readonly render: Function;
|
|
13
13
|
readonly [TSRX_ELEMENT]: true;
|
|
14
|
+
readonly __tag?: Tag;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/** Type for implicit children fragments rendered with `{children}`. */
|
|
@@ -26,6 +27,29 @@ export function tsrx_element(render: Function): TSRXElement;
|
|
|
26
27
|
|
|
27
28
|
export function Fragment(props: FragmentProps): TSRXElement;
|
|
28
29
|
|
|
30
|
+
type DynamicIntrinsicElements = JSX.IntrinsicElements;
|
|
31
|
+
export type DynamicElementType = keyof DynamicIntrinsicElements | Component<any> | (string & {});
|
|
32
|
+
type UnwrapTracked<T> = T extends Tracked<infer V> ? V : T;
|
|
33
|
+
type DynamicTarget<T> = Exclude<UnwrapTracked<T>, null | undefined | false>;
|
|
34
|
+
type DynamicComponentProps<T> = [T] extends [never]
|
|
35
|
+
? Props
|
|
36
|
+
: T extends Component<infer P>
|
|
37
|
+
? Omit<P, 'is'>
|
|
38
|
+
: T extends keyof DynamicIntrinsicElements
|
|
39
|
+
? DynamicIntrinsicElements[T]
|
|
40
|
+
: Props;
|
|
41
|
+
export type DynamicProps<T> = Expand<
|
|
42
|
+
{
|
|
43
|
+
is: T;
|
|
44
|
+
} & DynamicComponentProps<DynamicTarget<NoInfer<T>>>
|
|
45
|
+
>;
|
|
46
|
+
|
|
47
|
+
export function Dynamic<T>(
|
|
48
|
+
props: {
|
|
49
|
+
is: T;
|
|
50
|
+
} & DynamicComponentProps<DynamicTarget<NoInfer<T>>>,
|
|
51
|
+
): TSRXElement;
|
|
52
|
+
|
|
29
53
|
export function mount(
|
|
30
54
|
component: Component,
|
|
31
55
|
options: { target: HTMLElement; props?: Record<string, any>; rootBoundary?: RootBoundaryOptions },
|
|
@@ -174,8 +198,8 @@ interface TrackedBase<V> {
|
|
|
174
198
|
'#v': V;
|
|
175
199
|
value: V;
|
|
176
200
|
}
|
|
177
|
-
// Augment Tracked to be callable when V is a Component
|
|
178
|
-
// This allows
|
|
201
|
+
// Augment Tracked to be callable when V is a Component.
|
|
202
|
+
// This allows tracked component values to continue flowing through JSX checks.
|
|
179
203
|
interface TrackedCallable<V> {
|
|
180
204
|
(props: V extends Component<infer P> ? P : never): V extends Component ? void : never;
|
|
181
205
|
}
|