@thyn/core 0.0.344 → 0.0.347
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/.github/workflows/static.yml +48 -0
- package/.github/workflows/test.yml +39 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/{element.js → core/element.js} +14 -36
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/{router.d.ts → core/router.d.ts} +1 -1
- package/dist/{router.js → core/router.js} +22 -5
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/plugin/html-parser.d.ts +31 -0
- package/dist/plugin/html-parser.js +275 -0
- package/dist/plugin/index.d.ts +24 -0
- package/dist/plugin/index.js +1009 -0
- package/dist/plugin/utils.d.ts +12 -0
- package/dist/plugin/utils.js +194 -0
- package/docs/CNAME +1 -0
- package/docs/index.html +18 -0
- package/docs/package-lock.json +980 -0
- package/docs/package.json +15 -0
- package/docs/public/thyn.png +0 -0
- package/docs/public/thyn.svg +1 -0
- package/docs/src/App.thyn +10 -0
- package/docs/src/components/Button.thyn +3 -0
- package/docs/src/docs/GettingStarted.thyn +8 -0
- package/docs/src/main.css +17 -0
- package/docs/src/main.js +5 -0
- package/docs/src/pages/Home.thyn +147 -0
- package/docs/vite.config.js +7 -0
- package/package.json +18 -10
- package/src/{element.ts → core/element.ts} +14 -34
- package/src/core/index.ts +1 -0
- package/src/{router.ts → core/router.ts} +22 -6
- package/src/{signals.ts → core/signals.ts} +1 -1
- package/src/index.ts +5 -15
- package/src/plugin/html-parser.ts +332 -0
- package/src/plugin/index.ts +1127 -0
- package/src/plugin/utils.ts +213 -0
- package/tests/Bind.test.ts +14 -0
- package/tests/Bind.thyn +7 -0
- package/tests/ConsecInterps.test.ts +9 -0
- package/tests/ConsecInterps.thyn +9 -0
- package/tests/Counter.test.ts +12 -0
- package/tests/Counter.thyn +7 -0
- package/tests/DoubleQuotes.test.ts +9 -0
- package/tests/DoubleQuotes.thyn +3 -0
- package/tests/Escape.test.ts +9 -0
- package/tests/Escape.thyn +3 -0
- package/tests/EscapeDollar.test.ts +9 -0
- package/tests/EscapeDollar.thyn +5 -0
- package/tests/EventPipes.test.ts +13 -0
- package/tests/EventPipes.thyn +11 -0
- package/tests/List.test.ts +21 -0
- package/tests/List.thyn +15 -0
- package/tests/ListV2.test.ts +20 -0
- package/tests/ListV2.thyn +16 -0
- package/tests/MixElemAndText.test.ts +9 -0
- package/tests/MixElemAndText.thyn +12 -0
- package/tests/Show.test.ts +13 -0
- package/tests/Show.thyn +8 -0
- package/tests/Template.test.ts +9 -0
- package/tests/Template.thyn +8 -0
- package/tests/list/comprehensive.test.ts +659 -0
- package/tests/list/operations/ChildrenAppend.thyn +11 -0
- package/tests/list/operations/ChildrenFilter.thyn +11 -0
- package/tests/list/operations/ChildrenInsert.thyn +11 -0
- package/tests/list/operations/ChildrenNoneToSome.thyn +11 -0
- package/tests/list/operations/ChildrenPrepend.thyn +11 -0
- package/tests/list/operations/ChildrenRemove.thyn +11 -0
- package/tests/list/operations/ChildrenReplaceAll.thyn +11 -0
- package/tests/list/operations/ChildrenSomeToNone.thyn +11 -0
- package/tests/list/operations/ChildrenSort.thyn +11 -0
- package/tests/list/operations/IsolatedAppend.thyn +10 -0
- package/tests/list/operations/IsolatedFilter.thyn +16 -0
- package/tests/list/operations/IsolatedInsert.thyn +10 -0
- package/tests/list/operations/IsolatedMove.thyn +16 -0
- package/tests/list/operations/IsolatedNoneToSome.thyn +16 -0
- package/tests/list/operations/IsolatedPrepend.thyn +10 -0
- package/tests/list/operations/IsolatedRemove.thyn +17 -0
- package/tests/list/operations/IsolatedReplaceAll.thyn +10 -0
- package/tests/list/operations/IsolatedSomeToNone.thyn +10 -0
- package/tests/list/operations/IsolatedSort.thyn +16 -0
- package/tests/list/operations/TerminalAppend.thyn +12 -0
- package/tests/list/operations/TerminalFilter.thyn +12 -0
- package/tests/list/operations/TerminalInsert.thyn +12 -0
- package/tests/list/operations/TerminalNoneToSome.thyn +12 -0
- package/tests/list/operations/TerminalPrepend.thyn +12 -0
- package/tests/list/operations/TerminalRemove.thyn +12 -0
- package/tests/list/operations/TerminalReplaceAll.thyn +12 -0
- package/tests/list/operations/TerminalSomeToNone.thyn +12 -0
- package/tests/list/operations/TerminalSort.thyn +12 -0
- package/tests/tsconfig.json +14 -0
- package/tsconfig.json +11 -6
- package/types/thyn.d.ts +4 -0
- package/vitest.config.ts +7 -2
- package/tests/fx.test.ts +0 -31
- package/tests/lists.test.ts +0 -184
- package/tests/router.test.ts +0 -69
- package/tests/show.test.ts +0 -66
- package/tests/utils.ts +0 -3
- package/tsconfig.tsbuildinfo +0 -1
- /package/dist/{element.d.ts → core/element.d.ts} +0 -0
- /package/dist/{signals.d.ts → core/signals.d.ts} +0 -0
- /package/dist/{signals.js → core/signals.js} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export function extractParts(code: string) {
|
|
2
|
+
const scriptMatch = code.match(/<script([^>]*?)>([\s\S]*?)<\/script>/);
|
|
3
|
+
const styleMatch = code.match(/<style[^>]*>([\s\S]*?)<\/style>/);
|
|
4
|
+
const html = code
|
|
5
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/, "")
|
|
6
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/, "")
|
|
7
|
+
.trim();
|
|
8
|
+
|
|
9
|
+
let scriptLang = "js";
|
|
10
|
+
if (scriptMatch && scriptMatch[1]) {
|
|
11
|
+
const langMatch = scriptMatch[1].match(/lang\s*=\s*["']([^"']+)["']/);
|
|
12
|
+
if (langMatch) {
|
|
13
|
+
scriptLang = langMatch[1];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
script: scriptMatch?.[2]?.trim() ?? "",
|
|
19
|
+
scriptLang,
|
|
20
|
+
style: styleMatch?.[1]?.trim() ?? "",
|
|
21
|
+
html,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function escapeTemplateLiteral(text: string): string {
|
|
26
|
+
return text
|
|
27
|
+
.replace(/\\/g, '\\\\') // Escape backslashes
|
|
28
|
+
.replace(/`/g, '\\`') // Escape backticks
|
|
29
|
+
.replace(/\$/g, '\\$'); // Escape interpolation
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function escapeHtml(text: string): string {
|
|
33
|
+
return text
|
|
34
|
+
.replace(/&/g, "&")
|
|
35
|
+
.replace(/</g, "<")
|
|
36
|
+
.replace(/>/g, ">")
|
|
37
|
+
.replace(/"/g, """)
|
|
38
|
+
.replace(/'/g, "'");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function splitScript(script: string) {
|
|
42
|
+
if (!script || typeof script !== "string") {
|
|
43
|
+
return { imports: [], body: [] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lines = script.split("\n");
|
|
47
|
+
const imports = [];
|
|
48
|
+
const body = [];
|
|
49
|
+
let currentImport = [];
|
|
50
|
+
let inImport = false;
|
|
51
|
+
let braceCount = 0;
|
|
52
|
+
let inString = false;
|
|
53
|
+
let stringChar = "";
|
|
54
|
+
let inMultiLineComment = false;
|
|
55
|
+
|
|
56
|
+
// Helper function to check if import is complete without semicolon
|
|
57
|
+
function isImportComplete(line, braceCount, inString) {
|
|
58
|
+
// If we have balanced braces and not in a string, check if next non-empty line starts a new statement
|
|
59
|
+
if (braceCount === 0 && !inString) {
|
|
60
|
+
// Look ahead to see if next line starts a new statement/declaration
|
|
61
|
+
const nextLineIndex = lines.indexOf(line) + 1;
|
|
62
|
+
for (let i = nextLineIndex; i < lines.length; i++) {
|
|
63
|
+
const nextLine = lines[i].trim();
|
|
64
|
+
if (
|
|
65
|
+
!nextLine || nextLine.startsWith("//") || nextLine.startsWith("/*")
|
|
66
|
+
) {
|
|
67
|
+
continue; // Skip empty lines and comments
|
|
68
|
+
}
|
|
69
|
+
// If next line starts with typical JS keywords/patterns, current import is complete
|
|
70
|
+
return /^(const|let|var|function|class|export|if|for|while|switch|try|return|\w+\s*[=:]|\w+\()/
|
|
71
|
+
.test(nextLine);
|
|
72
|
+
}
|
|
73
|
+
// If we reached end of file, import is complete
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < lines.length; i++) {
|
|
80
|
+
const line = lines[i];
|
|
81
|
+
const trimmed = line.trim();
|
|
82
|
+
|
|
83
|
+
// Handle multi-line comments
|
|
84
|
+
if (inMultiLineComment) {
|
|
85
|
+
if (inImport) {
|
|
86
|
+
currentImport.push(line);
|
|
87
|
+
} else {
|
|
88
|
+
body.push(line);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (line.includes("*/")) {
|
|
92
|
+
inMultiLineComment = false;
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check for start of multi-line comment
|
|
98
|
+
if (line.includes("/*") && !inString) {
|
|
99
|
+
inMultiLineComment = true;
|
|
100
|
+
if (inImport) {
|
|
101
|
+
currentImport.push(line);
|
|
102
|
+
} else {
|
|
103
|
+
body.push(line);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!line.includes("*/")) {
|
|
107
|
+
continue;
|
|
108
|
+
} else {
|
|
109
|
+
inMultiLineComment = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Skip single-line comments when not in import
|
|
114
|
+
if (trimmed.startsWith("//") && !inImport) {
|
|
115
|
+
body.push(line);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Skip empty lines when not in import
|
|
120
|
+
if (!trimmed && !inImport) {
|
|
121
|
+
body.push(line);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Start of import statement
|
|
126
|
+
if (!inImport && trimmed.startsWith("import")) {
|
|
127
|
+
inImport = true;
|
|
128
|
+
currentImport = [line];
|
|
129
|
+
braceCount = 0;
|
|
130
|
+
inString = false;
|
|
131
|
+
|
|
132
|
+
// Count braces and track strings in the import line
|
|
133
|
+
for (let j = 0; j < line.length; j++) {
|
|
134
|
+
const char = line[j];
|
|
135
|
+
|
|
136
|
+
if (inString) {
|
|
137
|
+
if (char === stringChar && line[j - 1] !== "\\") {
|
|
138
|
+
inString = false;
|
|
139
|
+
stringChar = "";
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
143
|
+
inString = true;
|
|
144
|
+
stringChar = char;
|
|
145
|
+
} else if (char === "{") {
|
|
146
|
+
braceCount++;
|
|
147
|
+
} else if (char === "}") {
|
|
148
|
+
braceCount--;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check if import is complete
|
|
154
|
+
if (
|
|
155
|
+
(trimmed.endsWith(";") ||
|
|
156
|
+
isImportComplete(trimmed, braceCount, inString)) &&
|
|
157
|
+
braceCount === 0 && !inString
|
|
158
|
+
) {
|
|
159
|
+
imports.push(currentImport.join("\n"));
|
|
160
|
+
currentImport = [];
|
|
161
|
+
inImport = false;
|
|
162
|
+
}
|
|
163
|
+
} // Continue import statement
|
|
164
|
+
else if (inImport) {
|
|
165
|
+
currentImport.push(line);
|
|
166
|
+
|
|
167
|
+
// Count braces and track strings in the current line
|
|
168
|
+
for (let j = 0; j < line.length; j++) {
|
|
169
|
+
const char = line[j];
|
|
170
|
+
|
|
171
|
+
if (inString) {
|
|
172
|
+
if (char === stringChar && line[j - 1] !== "\\") {
|
|
173
|
+
inString = false;
|
|
174
|
+
stringChar = "";
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
178
|
+
inString = true;
|
|
179
|
+
stringChar = char;
|
|
180
|
+
} else if (char === "{") {
|
|
181
|
+
braceCount++;
|
|
182
|
+
} else if (char === "}") {
|
|
183
|
+
braceCount--;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check if import is complete
|
|
189
|
+
if (
|
|
190
|
+
(trimmed.endsWith(";") ||
|
|
191
|
+
isImportComplete(trimmed, braceCount, inString)) &&
|
|
192
|
+
braceCount === 0 && !inString
|
|
193
|
+
) {
|
|
194
|
+
imports.push(currentImport.join("\n"));
|
|
195
|
+
currentImport = [];
|
|
196
|
+
inImport = false;
|
|
197
|
+
}
|
|
198
|
+
} // Regular body content
|
|
199
|
+
else {
|
|
200
|
+
body.push(line);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Handle unterminated import (likely malformed)
|
|
205
|
+
if (currentImport.length > 0) {
|
|
206
|
+
imports.push(currentImport.join("\n"));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
imports: imports.filter((imp) => imp.trim()),
|
|
211
|
+
body: body.length > 0 ? body : [""],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import Bind from "./Bind.thyn";
|
|
3
|
+
|
|
4
|
+
describe("Bind component", () => {
|
|
5
|
+
it("has bound class name", async () => {
|
|
6
|
+
const root = Bind();
|
|
7
|
+
expect(root.className).toBe("bar");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("does not touch text content", async () => {
|
|
11
|
+
const root = Bind();
|
|
12
|
+
expect(root.textContent).toBe("const n={run:t,deps:new Set,td:null}");
|
|
13
|
+
});
|
|
14
|
+
});
|
package/tests/Bind.thyn
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import Counter from "./Counter.thyn";
|
|
3
|
+
|
|
4
|
+
describe("Counter component", () => {
|
|
5
|
+
it("increments on click", async () => {
|
|
6
|
+
const root = Counter();
|
|
7
|
+
expect(root.textContent).toBe("Count: 0");
|
|
8
|
+
root.click();
|
|
9
|
+
await Promise.resolve();
|
|
10
|
+
expect(root.textContent).toBe("Count: 1");
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import DoubleQuotes from "./DoubleQuotes.thyn";
|
|
3
|
+
|
|
4
|
+
describe("DoubleQuotes component", () => {
|
|
5
|
+
it("handles double quotes in bound js expression", async () => {
|
|
6
|
+
const root = DoubleQuotes();
|
|
7
|
+
expect(root.textContent).toBe("abc");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import EscapeDollar from "./EscapeDollar.thyn";
|
|
3
|
+
|
|
4
|
+
describe("EscapeDollar component", () => {
|
|
5
|
+
it("escapes dollar sign", async () => {
|
|
6
|
+
const root = EscapeDollar();
|
|
7
|
+
expect(root.textContent).toBe("$foo");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import EventPipes from "./EventPipes.thyn";
|
|
3
|
+
|
|
4
|
+
describe("EventPipes component", () => {
|
|
5
|
+
it("pipes event handlers", async () => {
|
|
6
|
+
const root = EventPipes();
|
|
7
|
+
expect(root.textContent).toBe("00");
|
|
8
|
+
root.querySelector(".inner").click();
|
|
9
|
+
await Promise.resolve();
|
|
10
|
+
await Promise.resolve();
|
|
11
|
+
expect(root.textContent).toBe("10");
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import List from "./List.thyn";
|
|
3
|
+
|
|
4
|
+
describe("List component", () => {
|
|
5
|
+
it("appends on click", async () => {
|
|
6
|
+
const root = List();
|
|
7
|
+
expect(root.textContent).toBe("start012end");
|
|
8
|
+
root.click();
|
|
9
|
+
await Promise.resolve();
|
|
10
|
+
await Promise.resolve();
|
|
11
|
+
expect(root.textContent).toBe("start0123end");
|
|
12
|
+
root.click();
|
|
13
|
+
await Promise.resolve();
|
|
14
|
+
await Promise.resolve();
|
|
15
|
+
expect(root.textContent).toBe("startend");
|
|
16
|
+
root.click();
|
|
17
|
+
await Promise.resolve();
|
|
18
|
+
await Promise.resolve();
|
|
19
|
+
expect(root.textContent).toBe("start0end");
|
|
20
|
+
});
|
|
21
|
+
});
|
package/tests/List.thyn
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import List from "./ListV2.thyn";
|
|
3
|
+
|
|
4
|
+
describe("List component", () => {
|
|
5
|
+
it("appends on click", async () => {
|
|
6
|
+
const root = List();
|
|
7
|
+
expect(root.textContent).toBe("start012end");
|
|
8
|
+
expect(root.querySelector(".selected").id).toBe("1");
|
|
9
|
+
root.click();
|
|
10
|
+
await Promise.resolve();
|
|
11
|
+
await Promise.resolve();
|
|
12
|
+
expect(root.textContent).toBe("start0123end");
|
|
13
|
+
expect(root.querySelector(".selected").id).toBe("1");
|
|
14
|
+
root.click();
|
|
15
|
+
await Promise.resolve();
|
|
16
|
+
await Promise.resolve();
|
|
17
|
+
expect(root.textContent).toBe("startend");
|
|
18
|
+
expect(root.querySelector(".selected")).toBeFalsy();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
const items = $signal([$signal(0), $signal(1), $signal(2)]);
|
|
3
|
+
const selected = $signal(1);
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<div onclick={() => items(v => v.length === 4 ? [] : [...v, $signal(v.length)])}>
|
|
7
|
+
<p>
|
|
8
|
+
start
|
|
9
|
+
</p>
|
|
10
|
+
<p #for={item in items()} class={selected() === item() ? 'selected' : undefined} id={item()}>
|
|
11
|
+
{{ item() }}
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
end
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import Show from "./Show.thyn";
|
|
3
|
+
|
|
4
|
+
describe("Show component", () => {
|
|
5
|
+
it("swaps on click", async () => {
|
|
6
|
+
const root = Show();
|
|
7
|
+
expect(root.textContent).toBe("foo");
|
|
8
|
+
root.click();
|
|
9
|
+
await Promise.resolve();
|
|
10
|
+
await Promise.resolve();
|
|
11
|
+
expect(root.textContent).toBe("bar");
|
|
12
|
+
});
|
|
13
|
+
});
|
package/tests/Show.thyn
ADDED