@torpor/view 0.1.0 → 0.1.2
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 +15 -0
- package/dist/compile.cjs +13 -6
- package/dist/compile.cjs.map +1 -1
- package/dist/compile.js +13 -6
- package/dist/compile.js.map +1 -1
- package/dist/index.cjs +16 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -7
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +3 -3
- package/src/compile/build/client/buildElementNode.ts +15 -4
- package/src/compile/build/client/buildIfNode.ts +2 -1
- package/src/compile/build/client/buildReplaceNode.ts +2 -1
- package/src/compile/build/client/buildSwitchNode.ts +2 -1
- package/src/motion/animate.ts +10 -6
- package/src/render/addFragment.ts +12 -7
- package/src/render/hydrate.ts +7 -0
- package/src/watch/transferEffects.ts +1 -1
- package/test/props/reactive.test.ts +5 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@torpor/view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Torpor's view library, for writing and mounting components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@testing-library/user-event": "^14.6.1",
|
|
36
36
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
37
37
|
"@types/jsdom": "^21.1.7",
|
|
38
|
-
"@types/node": "^22.15.
|
|
38
|
+
"@types/node": "^22.15.29",
|
|
39
39
|
"fast-glob": "^3.3.3",
|
|
40
40
|
"jsdom": "^26.1.0",
|
|
41
41
|
"sucrase": "^3.35.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"tsup": "^8.5.0",
|
|
44
44
|
"tsx": "^4.19.4",
|
|
45
45
|
"typescript": "^5.8.3",
|
|
46
|
-
"vitest": "^3.1
|
|
46
|
+
"vitest": "^3.2.1"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc --noEmit && tsup && tsx ./scripts/copyToRepl.ts",
|
|
@@ -173,9 +173,23 @@ function buildElementAttributes(
|
|
|
173
173
|
// TODO: Flatten this out
|
|
174
174
|
// TODO: Add an error if any reactive attributes are used non-reactively
|
|
175
175
|
|
|
176
|
+
// Do &ref first, just in case any other attributes depend on it being set
|
|
177
|
+
// e.g. if you have a `style` attribute that depends on the element's size
|
|
178
|
+
const refAttribute = node.attributes.find((a) => a.name === "&ref");
|
|
179
|
+
if (refAttribute) {
|
|
180
|
+
let value = refAttribute.value;
|
|
181
|
+
if (value != null && isFullyReactive(value)) {
|
|
182
|
+
// Bind the DOM element to a user-defined variable
|
|
183
|
+
value = value.substring(1, value.length - 1);
|
|
184
|
+
b.append(`${value} = ${varName};`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
for (let { name, value } of node.attributes) {
|
|
177
189
|
if (name === "self" && node.tagName === ":element") {
|
|
178
190
|
// Ignore this special attribute
|
|
191
|
+
} else if (name === "&ref") {
|
|
192
|
+
// Ignore this one, it should have been done already, above
|
|
179
193
|
} else if (name.startsWith("{") && name.endsWith("}")) {
|
|
180
194
|
// It's a shortcut attribute
|
|
181
195
|
// It could be e.g. {width} or it could be {$state.width}, but
|
|
@@ -191,10 +205,7 @@ function buildElementAttributes(
|
|
|
191
205
|
value = `\`${trimQuotes(value).replaceAll("{", "${")}\``;
|
|
192
206
|
}
|
|
193
207
|
|
|
194
|
-
if (name === "&
|
|
195
|
-
// Bind the DOM element to a user-defined variable
|
|
196
|
-
b.append(`${value} = ${varName};`);
|
|
197
|
-
} else if (name === "&group") {
|
|
208
|
+
if (name === "&group") {
|
|
198
209
|
buildBindGroupAttribute(node, varName, value, status, b);
|
|
199
210
|
} else if (name === "&value" || name === "&checked") {
|
|
200
211
|
buildBindAttribute(node, varName, name, value, status, b);
|
|
@@ -6,6 +6,7 @@ import { type BuildStatus } from "./BuildStatus";
|
|
|
6
6
|
import buildAddFragment from "./buildAddFragment";
|
|
7
7
|
import buildFragment from "./buildFragment";
|
|
8
8
|
import buildNode from "./buildNode";
|
|
9
|
+
import replaceForVarNames from "./replaceForVarNames";
|
|
9
10
|
|
|
10
11
|
// TODO: Are there too many branches for ifs etc?
|
|
11
12
|
|
|
@@ -55,7 +56,7 @@ function buildIfBranch(
|
|
|
55
56
|
) {
|
|
56
57
|
status.imports.add("t_run_branch");
|
|
57
58
|
|
|
58
|
-
b.append(`${node.statement} {`);
|
|
59
|
+
b.append(`${replaceForVarNames(node.statement, status)} {`);
|
|
59
60
|
b.append(`t_run_branch(${rangeName}, ${index}, () => {`);
|
|
60
61
|
|
|
61
62
|
buildFragment(node, status, b, parentName, "t_before");
|
|
@@ -5,6 +5,7 @@ import { type BuildStatus } from "./BuildStatus";
|
|
|
5
5
|
import buildAddFragment from "./buildAddFragment";
|
|
6
6
|
import buildFragment from "./buildFragment";
|
|
7
7
|
import buildNode from "./buildNode";
|
|
8
|
+
import replaceForVarNames from "./replaceForVarNames";
|
|
8
9
|
|
|
9
10
|
export default function buildReplaceNode(node: ControlNode, status: BuildStatus, b: Builder): void {
|
|
10
11
|
const replaceAnchorName = node.varName!;
|
|
@@ -38,7 +39,7 @@ function buildReplaceBranch(
|
|
|
38
39
|
) {
|
|
39
40
|
status.imports.add("t_run_branch");
|
|
40
41
|
|
|
41
|
-
b.append(`${node.statement};`);
|
|
42
|
+
b.append(`${replaceForVarNames(node.statement, status)};`);
|
|
42
43
|
b.append(`t_run_branch(${rangeName}, -1, () => {`);
|
|
43
44
|
|
|
44
45
|
buildFragment(node, status, b, parentName, "t_before");
|
|
@@ -6,6 +6,7 @@ import { type BuildStatus } from "./BuildStatus";
|
|
|
6
6
|
import buildAddFragment from "./buildAddFragment";
|
|
7
7
|
import buildFragment from "./buildFragment";
|
|
8
8
|
import buildNode from "./buildNode";
|
|
9
|
+
import replaceForVarNames from "./replaceForVarNames";
|
|
9
10
|
|
|
10
11
|
export default function buildSwitchNode(node: ControlNode, status: BuildStatus, b: Builder): void {
|
|
11
12
|
const switchParentName = node.parentName!;
|
|
@@ -55,7 +56,7 @@ function buildSwitchBranch(
|
|
|
55
56
|
) {
|
|
56
57
|
status.imports.add("t_run_branch");
|
|
57
58
|
|
|
58
|
-
b.append(`${node.statement} {`);
|
|
59
|
+
b.append(`${replaceForVarNames(node.statement, status)} {`);
|
|
59
60
|
b.append(`t_run_branch(${rangeName}, ${index}, () => {`);
|
|
60
61
|
|
|
61
62
|
buildFragment(node, status, b, parentName, "t_before");
|
package/src/motion/animate.ts
CHANGED
|
@@ -18,13 +18,13 @@ export default async function animate(
|
|
|
18
18
|
// - store the animation
|
|
19
19
|
// - reverse it if it's running and the range appears/disappears
|
|
20
20
|
// - transfer the animation to the new element
|
|
21
|
-
|
|
22
21
|
let animationOptions = Object.assign(
|
|
23
22
|
{
|
|
24
23
|
direction: enter ? "normal" : "reverse",
|
|
25
|
-
// TODO: Should probably have a global setting for
|
|
24
|
+
// TODO: Should probably have a global setting for these somewhere
|
|
26
25
|
// And respect browser reduce motion settings
|
|
27
|
-
duration:
|
|
26
|
+
duration: 300,
|
|
27
|
+
easing: "ease-in-out",
|
|
28
28
|
fill: "forwards",
|
|
29
29
|
},
|
|
30
30
|
options,
|
|
@@ -32,9 +32,13 @@ export default async function animate(
|
|
|
32
32
|
|
|
33
33
|
const animation = el.animate(keyframes, animationOptions);
|
|
34
34
|
|
|
35
|
-
// HACK: I'm not entirely sure why
|
|
36
|
-
// animations never start
|
|
37
|
-
|
|
35
|
+
// HACK: I'm not entirely sure why we need to sent the animation's timeline,
|
|
36
|
+
// but without it, animations never start
|
|
37
|
+
// HACK: We also need to make sure the element has been added to the DOM
|
|
38
|
+
// with a timeout
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
animation.timeline = el.ownerDocument.timeline;
|
|
41
|
+
}, 1);
|
|
38
42
|
|
|
39
43
|
// Add the animation to the active range, so that the range won't be cleared
|
|
40
44
|
// until the animation is completed
|
|
@@ -41,16 +41,19 @@ export default function addFragment(
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// If we're adding this fragment to the DOM, we can now run any $mount
|
|
44
|
-
//
|
|
44
|
+
// effects, add our stashed events and play our stashed animations
|
|
45
45
|
if (!parentIsFragment) {
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
// Only run $mount effects if not hydrating (if hydrating, they will get
|
|
47
|
+
// run at the end when everything is hooked up)
|
|
48
|
+
if (!context.hydrationNode) {
|
|
49
|
+
for (let effect of context.mountEffects) {
|
|
50
|
+
$run(effect);
|
|
51
|
+
}
|
|
52
|
+
context.mountEffects.length = 0;
|
|
51
53
|
}
|
|
52
|
-
context.mountEffects.length = 0;
|
|
53
54
|
|
|
55
|
+
// Set the active range for each event so it will get attached to the
|
|
56
|
+
// right one and set it back afterwards
|
|
54
57
|
for (let event of context.stashedEvents) {
|
|
55
58
|
context.activeRange = event.range;
|
|
56
59
|
$run(function addFragmentEvent() {
|
|
@@ -62,6 +65,8 @@ export default function addFragment(
|
|
|
62
65
|
}
|
|
63
66
|
context.stashedEvents.length = 0;
|
|
64
67
|
|
|
68
|
+
// Set the active range for each animation so it will get attached to
|
|
69
|
+
// the right one and set it back afterwards
|
|
65
70
|
for (let animation of context.stashedAnimations) {
|
|
66
71
|
context.activeRange = animation.range;
|
|
67
72
|
$run(function addFragmentAnimation() {
|
package/src/render/hydrate.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Component } from "../types/Component";
|
|
2
2
|
import { type SlotRender } from "../types/SlotRender";
|
|
3
|
+
import $run from "./$run";
|
|
3
4
|
import context from "./context";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -22,4 +23,10 @@ export default function hydrate(
|
|
|
22
23
|
component(parent, null, props, undefined, slots);
|
|
23
24
|
|
|
24
25
|
context.hydrationNode = null;
|
|
26
|
+
|
|
27
|
+
// Now that we've hydrated, we can run $mount effects
|
|
28
|
+
for (let effect of context.mountEffects) {
|
|
29
|
+
$run(effect);
|
|
30
|
+
}
|
|
31
|
+
context.mountEffects.length = 0;
|
|
25
32
|
}
|
|
@@ -28,7 +28,7 @@ function moveChildEffects(
|
|
|
28
28
|
newData.propData.set(key, props);
|
|
29
29
|
|
|
30
30
|
let oldPropValue = oldValue[key];
|
|
31
|
-
let oldPropData = oldPropValue[proxyDataSymbol];
|
|
31
|
+
let oldPropData = oldPropValue != null ? oldPropValue[proxyDataSymbol] : undefined;
|
|
32
32
|
|
|
33
33
|
// If the old value was a proxy, make the new value one too
|
|
34
34
|
// TODO: Make sure we aren't accidentally creating proxies for
|
|
@@ -2,7 +2,6 @@ import { getByText, queryByText } from "@testing-library/dom";
|
|
|
2
2
|
import "@testing-library/jest-dom/vitest";
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import { beforeAll, expect, test } from "vitest";
|
|
5
|
-
import { $watch } from "../../src";
|
|
6
5
|
import buildOutputFiles from "../buildOutputFiles";
|
|
7
6
|
import hydrateComponent from "../hydrateComponent";
|
|
8
7
|
import importComponent from "../importComponent";
|
|
@@ -14,38 +13,28 @@ beforeAll(() => {
|
|
|
14
13
|
buildOutputFiles(componentPath);
|
|
15
14
|
});
|
|
16
15
|
|
|
17
|
-
interface State {
|
|
18
|
-
text: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
16
|
test("props reactive -- mounted", async () => {
|
|
22
|
-
let $state = $watch({ text: "before" });
|
|
23
|
-
|
|
24
17
|
const container = document.createElement("div");
|
|
25
18
|
const component = await importComponent(componentPath, "client");
|
|
26
|
-
mountComponent(container, component
|
|
19
|
+
mountComponent(container, component);
|
|
27
20
|
|
|
28
|
-
check(container
|
|
21
|
+
await check(container);
|
|
29
22
|
});
|
|
30
23
|
|
|
31
24
|
test("props reactive -- hydrated", async () => {
|
|
32
|
-
let $state = $watch({ text: "before" });
|
|
33
|
-
|
|
34
25
|
const container = document.createElement("div");
|
|
35
26
|
const clientComponent = await importComponent(componentPath, "client");
|
|
36
27
|
const serverComponent = await importComponent(componentPath, "server");
|
|
37
|
-
hydrateComponent(container, clientComponent, serverComponent
|
|
28
|
+
hydrateComponent(container, clientComponent, serverComponent);
|
|
38
29
|
|
|
39
|
-
check(container
|
|
30
|
+
await check(container);
|
|
40
31
|
});
|
|
41
32
|
|
|
42
|
-
async function check(container: HTMLElement
|
|
33
|
+
async function check(container: HTMLElement) {
|
|
43
34
|
expect(queryByText(container, "before")).toBeInTheDocument();
|
|
44
35
|
|
|
45
36
|
await userEvent.click(getByText(container, "Update text"));
|
|
46
37
|
|
|
47
|
-
$state.text = "after";
|
|
48
|
-
|
|
49
38
|
expect(queryByText(container, "before")).not.toBeInTheDocument();
|
|
50
39
|
expect(queryByText(container, "after")).toBeInTheDocument();
|
|
51
40
|
}
|