@torpor/view 0.0.9 → 0.0.11

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/compile.cjs +28 -6
  3. package/dist/compile.cjs.map +1 -1
  4. package/dist/compile.d.cts +1 -0
  5. package/dist/compile.d.ts +1 -0
  6. package/dist/compile.js +28 -6
  7. package/dist/compile.js.map +1 -1
  8. package/dist/index.cjs +12 -4
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +12 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/metafile-cjs.json +1 -1
  15. package/dist/metafile-esm.json +1 -1
  16. package/package.json +3 -3
  17. package/src/compile/build/client/BuildStatus.ts +1 -0
  18. package/src/compile/build/client/buildCode.ts +1 -0
  19. package/src/compile/build/client/buildFragment.ts +1 -1
  20. package/src/compile/build/client/buildFragmentText.ts +26 -4
  21. package/src/compile/build/client/buildHtmlNode.ts +1 -5
  22. package/src/compile/types/nodes/Fragment.ts +1 -0
  23. package/src/render/getFragment.ts +13 -3
  24. package/test/console/components/temp/{Console-client-2f206752ce59e59fba9bb91263b4102fd515951e.ts → Console-client-254d7d4799471401334d698b1631ab1cb5b70558.ts} +1 -1
  25. package/test/svg/components/Shape.d.ts +18 -0
  26. package/test/svg/components/Shape.torp +12 -0
  27. package/test/svg/components/output/Shape-client.ts +61 -0
  28. package/test/svg/components/output/Shape-server.ts +23 -0
  29. package/test/svg/components/temp/Shape-client-ea698ed1b7d0c3615adff35fa4956f22840f5fda.ts +61 -0
  30. package/test/svg/components/temp/Shape-server-eb32049cfb7c058b2c4079c5f86ef00651ff6d69.ts +23 -0
  31. package/test/svg/svg.test.ts +50 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@torpor/view",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Torpor's view library, for writing and mounting components",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -35,9 +35,9 @@
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.14.0",
38
+ "@types/node": "^22.14.1",
39
39
  "fast-glob": "^3.3.3",
40
- "jsdom": "^26.0.0",
40
+ "jsdom": "^26.1.0",
41
41
  "sucrase": "^3.35.0",
42
42
  "tinybench": "^4.0.1",
43
43
  "tsup": "^8.4.0",
@@ -13,6 +13,7 @@ export type BuildStatus = {
13
13
  path: string;
14
14
  }[];
15
15
  forVarNames: string[];
16
+ ns: boolean;
16
17
  preserveWhitespace: boolean;
17
18
  inHead?: boolean;
18
19
  options?: BuildOptions;
@@ -120,6 +120,7 @@ function buildTemplate(template: Template, imports: Set<string>, b: Builder) {
120
120
  varNames: {},
121
121
  fragmentStack: [],
122
122
  forVarNames: [],
123
+ ns: false,
123
124
  preserveWhitespace: false,
124
125
  };
125
126
 
@@ -74,7 +74,7 @@ export default function buildFragment(
74
74
  status.imports.add("t_fragment");
75
75
  const fragmentText = fragment.text.replaceAll("`", "\\`").replaceAll(/\s+/g, " ");
76
76
  b.append(
77
- `const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`);`,
77
+ `const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`,
78
78
  );
79
79
  let fragmentPath = { parent: null, type: "fragment", children: [] };
80
80
  let varPaths = new Map<string, string>();
@@ -45,7 +45,11 @@ function buildNodeFragmentText(
45
45
  break;
46
46
  }
47
47
  case "element": {
48
+ // NOTE: We don't support nested SVG for now
49
+ let ns = (node as ElementNode).tagName === "svg";
50
+ if (ns) status.ns = true;
48
51
  buildElementFragmentText(node as ElementNode, status, fragments, currentFragment!);
52
+ if (ns) status.ns = false;
49
53
  break;
50
54
  }
51
55
  case "text": {
@@ -63,7 +67,7 @@ function buildNodeFragmentText(
63
67
  }
64
68
 
65
69
  function buildRootFragmentText(node: RootNode, status: BuildStatus, fragments: Fragment[]) {
66
- node.fragment = { number: fragments.length, text: "", events: [], animations: [] };
70
+ node.fragment = { number: fragments.length, text: "", ns: status.ns, events: [], animations: [] };
67
71
  fragments.push(node.fragment);
68
72
  for (let child of node.children) {
69
73
  buildNodeFragmentText(child, status, fragments, node.fragment);
@@ -94,7 +98,13 @@ function buildControlFragmentText(
94
98
  }
95
99
  default: {
96
100
  // Add a new fragment if it's a control branch and it has children
97
- node.fragment = { number: fragments.length, text: "", events: [], animations: [] };
101
+ node.fragment = {
102
+ number: fragments.length,
103
+ text: "",
104
+ ns: status.ns,
105
+ events: [],
106
+ animations: [],
107
+ };
98
108
  fragments.push(node.fragment);
99
109
  for (let child of node.children) {
100
110
  buildNodeFragmentText(child, status, fragments, node.fragment);
@@ -114,7 +124,13 @@ function buildComponentFragmentText(
114
124
 
115
125
  // Add fragments for slots if there are children
116
126
  if (node.children.length) {
117
- node.fragment = { number: fragments.length, text: "", events: [], animations: [] };
127
+ node.fragment = {
128
+ number: fragments.length,
129
+ text: "",
130
+ ns: status.ns,
131
+ events: [],
132
+ animations: [],
133
+ };
118
134
  fragments.push(node.fragment);
119
135
  for (let child of node.children) {
120
136
  // TODO: Make sure it's not a :fill node
@@ -172,7 +188,13 @@ function buildSpecialFragmentText(
172
188
  }
173
189
  case ":fill": {
174
190
  // Add a new fragment for filled slot content
175
- node.fragment = { number: fragments.length, text: "", events: [], animations: [] };
191
+ node.fragment = {
192
+ number: fragments.length,
193
+ text: "",
194
+ ns: status.ns,
195
+ events: [],
196
+ animations: [],
197
+ };
176
198
  fragments.push(node.fragment);
177
199
  for (let child of node.children) {
178
200
  buildNodeFragmentText(child, status, fragments, node.fragment);
@@ -36,14 +36,10 @@ function buildHtmlBranch(
36
36
  ) {
37
37
  status.imports.add("t_run_branch");
38
38
 
39
- //b.append(`${node.statement};`);
40
39
  b.append(`t_run_branch(${rangeName}, -1, () => {`);
41
40
 
42
- //const fragment = node.fragment!;
43
- //const templateName = `t_template_${fragment.number}`;
44
- //const fragmentName = `t_fragment_${fragment.number}`;
45
41
  const templateName = nextVarName("template", status);
46
- const fragmentName = nextVarName("fragment", status);
42
+ const fragmentName = `t_fragment_${node.fragment!.number}`;
47
43
  b.append(`const ${templateName} = document.createElement("template");`);
48
44
  b.append(`${templateName}.innerHTML = ${node.statement};`);
49
45
  b.append(`let ${fragmentName} = ${templateName}.content.cloneNode(true) as DocumentFragment;`);
@@ -1,6 +1,7 @@
1
1
  export type Fragment = {
2
2
  number: number;
3
3
  text: string;
4
+ ns: boolean;
4
5
  events: {
5
6
  varName: string;
6
7
  eventName: string;
@@ -3,12 +3,22 @@ export default function getFragment(
3
3
  array: DocumentFragment[],
4
4
  index: number,
5
5
  html: string,
6
+ ns?: boolean,
6
7
  ): DocumentFragment {
7
8
  // Create the fragment in the array if it hasn't yet been used
8
9
  if (array[index] === undefined) {
9
- const template = document.createElement("template");
10
- template.innerHTML = html;
11
- array[index] = template.content;
10
+ // TODO: Should pass a string for the ns, when we support other element types
11
+ if (ns) {
12
+ const template = document.createElementNS("http://www.w3.org/2000/svg", "template");
13
+ template.innerHTML = html;
14
+ const fragment = new DocumentFragment();
15
+ fragment.append(...template.childNodes);
16
+ array[index] = fragment;
17
+ } else {
18
+ const template = document.createElement("template");
19
+ template.innerHTML = html;
20
+ array[index] = template.content;
21
+ }
12
22
  }
13
23
 
14
24
  // Get the fragment from the array
@@ -18,7 +18,7 @@ export default function Console(
18
18
  /* User interface */
19
19
  const t_fragments: DocumentFragment[] = [];
20
20
 
21
- const t_fragment_0 = t_fragment($parent.ownerDocument!, t_fragments, 0, `<div> </div>`);
21
+ const t_fragment_0 = t_fragment($parent.ownerDocument!, t_fragments, 0, `<div> </div>`, false);
22
22
  // @ts-ignore
23
23
  const t_div_1 = t_root(t_fragment_0) as HTMLDivElement;
24
24
  /* @console */
@@ -0,0 +1,18 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+ /**
4
+ * Mounts or hydrates the component into the supplied parent node.
5
+ * @param $parent -- The parent node.
6
+ * @param $anchor -- The node to mount the component before.
7
+ * @param $props -- The values that have been passed into the component as properties.
8
+ * @param $context -- Values that have been passed into the component from its ancestors.
9
+ * @param $slots -- Functions for rendering children into slot nodes within the component.
10
+ */
11
+ declare function Shape(
12
+ $parent: ParentNode,
13
+ $anchor: Node | null,
14
+ $props: { name: string },
15
+ $context?: Record<PropertyKey, any>,
16
+ $slots?: Record<string, SlotRender>,
17
+ ): void;
18
+ export default Shape;
@@ -0,0 +1,12 @@
1
+
2
+ export default function Shape($props: { name: string }) {
3
+ @render {
4
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img">
5
+ @if ($props.name === "rect") {
6
+ <rect width="100" height="100" fill="red"></rect>
7
+ } else {
8
+ <circle r="45" cx="50" cy="50" fill="red"></circle>
9
+ }
10
+ </svg>
11
+ }
12
+ }
@@ -0,0 +1,61 @@
1
+ import { type SlotRender } from "../../../../src/types/SlotRender";
2
+ import t_add_fragment from "../../../../src/render/addFragment";
3
+ import t_anchor from "../../../../src/render/nodeAnchor";
4
+ import t_child from "../../../../src/render/nodeChild";
5
+ import t_fragment from "../../../../src/render/getFragment";
6
+ import t_next from "../../../../src/render/nodeNext";
7
+ import t_range from "../../../../src/render/newRange";
8
+ import t_root from "../../../../src/render/nodeRoot";
9
+ import t_run_branch from "../../../../src/render/runControlBranch";
10
+ import t_run_control from "../../../../src/render/runControl";
11
+
12
+ export default function Shape(
13
+ $parent: ParentNode,
14
+ $anchor: Node | null,
15
+ $props: { name: string },
16
+ // @ts-ignore
17
+ $context?: Record<PropertyKey, any>,
18
+ // @ts-ignore
19
+ $slots?: Record<string, SlotRender>
20
+ ) {
21
+ $props ??= {};
22
+
23
+ /* User interface */
24
+ const t_fragments: DocumentFragment[] = [];
25
+
26
+ const t_fragment_0 = t_fragment($parent.ownerDocument!, t_fragments, 0, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img"> <!> </svg>`);
27
+ // @ts-ignore
28
+ const t_svg_1 = t_root(t_fragment_0) as HTMLElement;
29
+ const t_if_anchor_1 = t_anchor(t_next(t_child(t_svg_1))) as HTMLElement;
30
+
31
+ /* @if */
32
+ const t_if_range_1 = t_range();
33
+ t_run_control(t_if_range_1, t_if_anchor_1, (t_before) => {
34
+ if ($props.name === "rect") {
35
+ t_run_branch(t_if_range_1, 0, () => {
36
+ const t_fragment_1 = t_fragment($parent.ownerDocument!, t_fragments, 1, ` <rect width="100" height="100" fill="red"></rect> `, true);
37
+ // @ts-ignore
38
+ const t_root_1 = t_root(t_fragment_1);
39
+ // @ts-ignore
40
+ const t_text_1 = t_next(t_next(t_root_1));
41
+ t_add_fragment(t_fragment_1, t_svg_1, t_before);
42
+ t_next(t_text_1);
43
+ });
44
+ }
45
+ else {
46
+ t_run_branch(t_if_range_1, 1, () => {
47
+ const t_fragment_2 = t_fragment($parent.ownerDocument!, t_fragments, 2, ` <circle r="45" cx="50" cy="50" fill="red"></circle> `, true);
48
+ // @ts-ignore
49
+ const t_root_2 = t_root(t_fragment_2);
50
+ // @ts-ignore
51
+ const t_text_2 = t_next(t_next(t_root_2));
52
+ t_add_fragment(t_fragment_2, t_svg_1, t_before);
53
+ t_next(t_text_2);
54
+ });
55
+ }
56
+ });
57
+
58
+ t_add_fragment(t_fragment_0, $parent, $anchor);
59
+ t_next(t_svg_1);
60
+
61
+ }
@@ -0,0 +1,23 @@
1
+ import { type ServerSlotRender } from "../../../../src/types/ServerSlotRender";
2
+
3
+ export default function Shape(
4
+ $props: { name: string },
5
+ // @ts-ignore
6
+ $context?: Record<PropertyKey, any>,
7
+ // @ts-ignore
8
+ $slots?: Record<string, ServerSlotRender>
9
+ ) {
10
+
11
+ /* User interface */
12
+ let $output = "";
13
+ $output += `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img"> <![>`;
14
+ if ($props.name === "rect") {
15
+ $output += ` <rect width="100" height="100" fill="red"></rect> `;
16
+ }
17
+ else {
18
+ $output += ` <circle r="45" cx="50" cy="50" fill="red"></circle> `;
19
+ }
20
+ $output += `<!]><!> </svg>`;
21
+
22
+ return $output;
23
+ }
@@ -0,0 +1,61 @@
1
+ import { type SlotRender } from "../../../../src/types/SlotRender";
2
+ import t_add_fragment from "../../../../src/render/addFragment";
3
+ import t_anchor from "../../../../src/render/nodeAnchor";
4
+ import t_child from "../../../../src/render/nodeChild";
5
+ import t_fragment from "../../../../src/render/getFragment";
6
+ import t_next from "../../../../src/render/nodeNext";
7
+ import t_range from "../../../../src/render/newRange";
8
+ import t_root from "../../../../src/render/nodeRoot";
9
+ import t_run_branch from "../../../../src/render/runControlBranch";
10
+ import t_run_control from "../../../../src/render/runControl";
11
+
12
+ export default function Shape(
13
+ $parent: ParentNode,
14
+ $anchor: Node | null,
15
+ $props: { name: string },
16
+ // @ts-ignore
17
+ $context?: Record<PropertyKey, any>,
18
+ // @ts-ignore
19
+ $slots?: Record<string, SlotRender>
20
+ ) {
21
+ $props ??= {};
22
+
23
+ /* User interface */
24
+ const t_fragments: DocumentFragment[] = [];
25
+
26
+ const t_fragment_0 = t_fragment($parent.ownerDocument!, t_fragments, 0, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img"> <!> </svg>`);
27
+ // @ts-ignore
28
+ const t_svg_1 = t_root(t_fragment_0) as HTMLElement;
29
+ const t_if_anchor_1 = t_anchor(t_next(t_child(t_svg_1))) as HTMLElement;
30
+
31
+ /* @if */
32
+ const t_if_range_1 = t_range();
33
+ t_run_control(t_if_range_1, t_if_anchor_1, (t_before) => {
34
+ if ($props.name === "rect") {
35
+ t_run_branch(t_if_range_1, 0, () => {
36
+ const t_fragment_1 = t_fragment($parent.ownerDocument!, t_fragments, 1, ` <rect width="100" height="100" fill="red"></rect> `, true);
37
+ // @ts-ignore
38
+ const t_root_1 = t_root(t_fragment_1);
39
+ // @ts-ignore
40
+ const t_text_1 = t_next(t_next(t_root_1));
41
+ t_add_fragment(t_fragment_1, t_svg_1, t_before);
42
+ t_next(t_text_1);
43
+ });
44
+ }
45
+ else {
46
+ t_run_branch(t_if_range_1, 1, () => {
47
+ const t_fragment_2 = t_fragment($parent.ownerDocument!, t_fragments, 2, ` <circle r="45" cx="50" cy="50" fill="red"></circle> `, true);
48
+ // @ts-ignore
49
+ const t_root_2 = t_root(t_fragment_2);
50
+ // @ts-ignore
51
+ const t_text_2 = t_next(t_next(t_root_2));
52
+ t_add_fragment(t_fragment_2, t_svg_1, t_before);
53
+ t_next(t_text_2);
54
+ });
55
+ }
56
+ });
57
+
58
+ t_add_fragment(t_fragment_0, $parent, $anchor);
59
+ t_next(t_svg_1);
60
+
61
+ }
@@ -0,0 +1,23 @@
1
+ import { type ServerSlotRender } from "../../../../src/types/ServerSlotRender";
2
+
3
+ export default function Shape(
4
+ $props: { name: string },
5
+ // @ts-ignore
6
+ $context?: Record<PropertyKey, any>,
7
+ // @ts-ignore
8
+ $slots?: Record<string, ServerSlotRender>
9
+ ) {
10
+
11
+ /* User interface */
12
+ let $output = "";
13
+ $output += `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img"> <![>`;
14
+ if ($props.name === "rect") {
15
+ $output += ` <rect width="100" height="100" fill="red"></rect> `;
16
+ }
17
+ else {
18
+ $output += ` <circle r="45" cx="50" cy="50" fill="red"></circle> `;
19
+ }
20
+ $output += `<!]><!> </svg>`;
21
+
22
+ return $output;
23
+ }
@@ -0,0 +1,50 @@
1
+ import { queryByRole } from "@testing-library/dom";
2
+ import "@testing-library/jest-dom/vitest";
3
+ import { beforeAll, expect, test } from "vitest";
4
+ import $watch from "../../src/render/$watch";
5
+ import buildOutputFiles from "../buildOutputFiles";
6
+ import hydrateComponent from "../hydrateComponent";
7
+ import importComponent from "../importComponent";
8
+ import mountComponent from "../mountComponent";
9
+
10
+ const componentPath = "./test/svg/components/Shape";
11
+
12
+ beforeAll(() => {
13
+ buildOutputFiles(componentPath);
14
+ });
15
+
16
+ interface State {
17
+ name: string;
18
+ }
19
+
20
+ test("svg -- mounted", async () => {
21
+ const $state = $watch({ name: "rect" });
22
+
23
+ const container = document.createElement("div");
24
+ const component = await importComponent(componentPath, "client");
25
+ mountComponent(container, component, $state);
26
+
27
+ check(container, $state);
28
+ });
29
+
30
+ test("svg -- hydrated", async () => {
31
+ const $state = $watch({ name: "rect" });
32
+
33
+ const container = document.createElement("div");
34
+ const clientComponent = await importComponent(componentPath, "client");
35
+ const serverComponent = await importComponent(componentPath, "server");
36
+ hydrateComponent(container, clientComponent, serverComponent, $state);
37
+
38
+ check(container, $state);
39
+ });
40
+
41
+ function check(container: HTMLElement, state: State) {
42
+ const img = queryByRole(container, "img");
43
+ expect(img).not.toBeNull();
44
+
45
+ expect(img).toContainHTML('<rect width="100" height="100" fill="red"></rect>');
46
+
47
+ state.name = "circle";
48
+
49
+ expect(img).toContainHTML('<circle r="45" cx="50" cy="50" fill="red"></circle>');
50
+ }