@thyn/core 0.0.343 → 0.0.346

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 (105) hide show
  1. package/.github/workflows/static.yml +48 -0
  2. package/.github/workflows/test.yml +39 -0
  3. package/LICENSE +21 -0
  4. package/README.md +50 -0
  5. package/dist/{element.js → core/element.js} +14 -36
  6. package/dist/core/index.d.ts +1 -0
  7. package/dist/core/index.js +1 -0
  8. package/dist/index.d.ts +5 -2
  9. package/dist/index.js +5 -2
  10. package/dist/plugin/html-parser.d.ts +31 -0
  11. package/dist/plugin/html-parser.js +275 -0
  12. package/dist/plugin/index.d.ts +24 -0
  13. package/dist/plugin/index.js +1009 -0
  14. package/dist/plugin/utils.d.ts +12 -0
  15. package/dist/plugin/utils.js +194 -0
  16. package/docs/CNAME +1 -0
  17. package/docs/index.html +18 -0
  18. package/docs/package-lock.json +980 -0
  19. package/docs/package.json +15 -0
  20. package/docs/public/thyn.png +0 -0
  21. package/docs/public/thyn.svg +1 -0
  22. package/docs/src/App.thyn +10 -0
  23. package/docs/src/components/Button.thyn +3 -0
  24. package/docs/src/docs/GettingStarted.thyn +8 -0
  25. package/docs/src/main.css +17 -0
  26. package/docs/src/main.js +5 -0
  27. package/docs/src/pages/Home.thyn +147 -0
  28. package/docs/vite.config.js +7 -0
  29. package/package.json +18 -10
  30. package/src/{element.ts → core/element.ts} +14 -34
  31. package/src/core/index.ts +1 -0
  32. package/src/{signals.ts → core/signals.ts} +1 -1
  33. package/src/index.ts +5 -15
  34. package/src/plugin/html-parser.ts +332 -0
  35. package/src/plugin/index.ts +1127 -0
  36. package/src/plugin/utils.ts +213 -0
  37. package/tests/Bind.test.ts +14 -0
  38. package/tests/Bind.thyn +7 -0
  39. package/tests/ConsecInterps.test.ts +9 -0
  40. package/tests/ConsecInterps.thyn +9 -0
  41. package/tests/Counter.test.ts +12 -0
  42. package/tests/Counter.thyn +7 -0
  43. package/tests/DoubleQuotes.test.ts +9 -0
  44. package/tests/DoubleQuotes.thyn +3 -0
  45. package/tests/Escape.test.ts +9 -0
  46. package/tests/Escape.thyn +3 -0
  47. package/tests/EscapeDollar.test.ts +9 -0
  48. package/tests/EscapeDollar.thyn +5 -0
  49. package/tests/EventPipes.test.ts +13 -0
  50. package/tests/EventPipes.thyn +11 -0
  51. package/tests/List.test.ts +21 -0
  52. package/tests/List.thyn +15 -0
  53. package/tests/ListV2.test.ts +20 -0
  54. package/tests/ListV2.thyn +16 -0
  55. package/tests/MixElemAndText.test.ts +9 -0
  56. package/tests/MixElemAndText.thyn +12 -0
  57. package/tests/Show.test.ts +13 -0
  58. package/tests/Show.thyn +8 -0
  59. package/tests/Template.test.ts +9 -0
  60. package/tests/Template.thyn +8 -0
  61. package/tests/list/comprehensive.test.ts +659 -0
  62. package/tests/list/operations/ChildrenAppend.thyn +11 -0
  63. package/tests/list/operations/ChildrenFilter.thyn +11 -0
  64. package/tests/list/operations/ChildrenInsert.thyn +11 -0
  65. package/tests/list/operations/ChildrenNoneToSome.thyn +11 -0
  66. package/tests/list/operations/ChildrenPrepend.thyn +11 -0
  67. package/tests/list/operations/ChildrenRemove.thyn +11 -0
  68. package/tests/list/operations/ChildrenReplaceAll.thyn +11 -0
  69. package/tests/list/operations/ChildrenSomeToNone.thyn +11 -0
  70. package/tests/list/operations/ChildrenSort.thyn +11 -0
  71. package/tests/list/operations/IsolatedAppend.thyn +10 -0
  72. package/tests/list/operations/IsolatedFilter.thyn +16 -0
  73. package/tests/list/operations/IsolatedInsert.thyn +10 -0
  74. package/tests/list/operations/IsolatedMove.thyn +16 -0
  75. package/tests/list/operations/IsolatedNoneToSome.thyn +16 -0
  76. package/tests/list/operations/IsolatedPrepend.thyn +10 -0
  77. package/tests/list/operations/IsolatedRemove.thyn +17 -0
  78. package/tests/list/operations/IsolatedReplaceAll.thyn +10 -0
  79. package/tests/list/operations/IsolatedSomeToNone.thyn +10 -0
  80. package/tests/list/operations/IsolatedSort.thyn +16 -0
  81. package/tests/list/operations/TerminalAppend.thyn +12 -0
  82. package/tests/list/operations/TerminalFilter.thyn +12 -0
  83. package/tests/list/operations/TerminalInsert.thyn +12 -0
  84. package/tests/list/operations/TerminalNoneToSome.thyn +12 -0
  85. package/tests/list/operations/TerminalPrepend.thyn +12 -0
  86. package/tests/list/operations/TerminalRemove.thyn +12 -0
  87. package/tests/list/operations/TerminalReplaceAll.thyn +12 -0
  88. package/tests/list/operations/TerminalSomeToNone.thyn +12 -0
  89. package/tests/list/operations/TerminalSort.thyn +12 -0
  90. package/tests/tsconfig.json +14 -0
  91. package/tsconfig.json +11 -6
  92. package/types/thyn.d.ts +4 -0
  93. package/vitest.config.ts +7 -2
  94. package/tests/fx.test.ts +0 -31
  95. package/tests/lists.test.ts +0 -184
  96. package/tests/router.test.ts +0 -69
  97. package/tests/show.test.ts +0 -66
  98. package/tests/utils.ts +0 -3
  99. package/tsconfig.tsbuildinfo +0 -1
  100. /package/dist/{element.d.ts → core/element.d.ts} +0 -0
  101. /package/dist/{router.d.ts → core/router.d.ts} +0 -0
  102. /package/dist/{router.js → core/router.js} +0 -0
  103. /package/dist/{signals.d.ts → core/signals.d.ts} +0 -0
  104. /package/dist/{signals.js → core/signals.js} +0 -0
  105. /package/src/{router.ts → core/router.ts} +0 -0
@@ -0,0 +1,332 @@
1
+ interface Node {
2
+ nodeType: number;
3
+ nodeName: string;
4
+ textContent: string;
5
+ childNodes: Node[];
6
+ }
7
+
8
+ interface Element extends Node {
9
+ tagName: string;
10
+ attributes: Array<{ name: string; value: string }>;
11
+ children: Element[];
12
+ firstElementChild: Element | null;
13
+ hasAttribute(name: string): boolean;
14
+ getAttribute(name: string): string | null;
15
+ setAttribute(name: string, value: string): void;
16
+ removeAttribute(name: string): void;
17
+ classList: { add(className: string): void };
18
+ }
19
+
20
+ interface DocumentFragment {
21
+ childNodes: Node[];
22
+ firstElementChild: Element | null;
23
+ }
24
+
25
+ interface TemplateElement extends Element {
26
+ content: DocumentFragment;
27
+ }
28
+
29
+ function parseAttributes(attrStr: string): Array<{ name: string; value: string }> {
30
+ const attrs: Array<{ name: string; value: string }> = []
31
+ let i = 0;
32
+
33
+ while (i < attrStr.length) {
34
+ // Skip whitespace
35
+ while (i < attrStr.length && /\s/.test(attrStr[i])) i++;
36
+ if (i >= attrStr.length) break;
37
+
38
+ // Parse attribute name
39
+ let name = "";
40
+ while (i < attrStr.length && !/[\s=]/.test(attrStr[i])) {
41
+ name += attrStr[i];
42
+ i++;
43
+ }
44
+
45
+ if (!name) break;
46
+
47
+ // Skip whitespace
48
+ while (i < attrStr.length && /\s/.test(attrStr[i])) i++;
49
+
50
+ let value = "";
51
+ if (i < attrStr.length && attrStr[i] === "=") {
52
+ i++; // skip '='
53
+ // Skip whitespace
54
+ while (i < attrStr.length && /\s/.test(attrStr[i])) i++;
55
+
56
+ if (i < attrStr.length) {
57
+ const quote = attrStr[i];
58
+ if (quote === '"' || quote === "'") {
59
+ i++; // skip opening quote
60
+ while (i < attrStr.length && attrStr[i] !== quote) {
61
+ value += attrStr[i];
62
+ i++;
63
+ }
64
+ if (i < attrStr.length) i++; // skip closing quote
65
+ } else {
66
+ // Unquoted value - take until whitespace
67
+ while (i < attrStr.length && !/\s/.test(attrStr[i])) {
68
+ value += attrStr[i];
69
+ i++;
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ attrs.push({ name, value });
76
+ }
77
+
78
+ return attrs;
79
+ }
80
+
81
+ function createTextNode(text: string): Node {
82
+ return {
83
+ nodeType: 3,
84
+ nodeName: "#text",
85
+ textContent: text,
86
+ childNodes: [],
87
+ };
88
+ }
89
+
90
+ function createElement(tagName: string, attributes: Array<{ name: string; value: string }> = []): Element {
91
+ const children: Element[] = [];
92
+ const childNodes: Node[] = [];
93
+
94
+ const element: Element = {
95
+ nodeType: 1,
96
+ nodeName: tagName.toUpperCase(),
97
+ tagName: tagName.toUpperCase(),
98
+ textContent: "",
99
+ attributes: [...attributes],
100
+ children,
101
+ childNodes,
102
+ firstElementChild: null,
103
+ hasAttribute(name: string): boolean {
104
+ return this.attributes.some((attr) => attr.name === name);
105
+ },
106
+ getAttribute(name: string): string | null {
107
+ const attr = this.attributes.find((attr) => attr.name === name);
108
+ return attr ? attr.value : null;
109
+ },
110
+ setAttribute(name: string, value: string): void {
111
+ const existing = this.attributes.find((attr) => attr.name === name);
112
+ if (existing) {
113
+ existing.value = value;
114
+ } else {
115
+ this.attributes.push({ name, value });
116
+ }
117
+ },
118
+ removeAttribute(name: string): void {
119
+ this.attributes = this.attributes.filter((attr) => attr.name !== name);
120
+ },
121
+ classList: {
122
+ add: (className: string) => {
123
+ const existing = element.getAttribute("class");
124
+ const classes = existing ? existing.split(" ").filter(Boolean) : [];
125
+ if (!classes.includes(className)) {
126
+ classes.push(className);
127
+ element.setAttribute("class", classes.join(" "));
128
+ }
129
+ },
130
+ },
131
+ };
132
+
133
+ return element;
134
+ }
135
+
136
+ // Find next tag position, properly handling quoted strings
137
+ function findNextTag(html: string, startIndex: number): { index: number; endIndex: number; isClose: boolean; tagName: string; attrs: string; isSelfClose: boolean } | null {
138
+ let i = startIndex;
139
+
140
+ while (i < html.length) {
141
+ // Find the next '<'
142
+ while (i < html.length && html[i] !== '<') {
143
+ i++;
144
+ }
145
+
146
+ if (i >= html.length) return null;
147
+
148
+ const tagStart = i;
149
+ i++; // skip '<'
150
+
151
+ // Check if it's a closing tag
152
+ const isClose = i < html.length && html[i] === '/';
153
+ if (isClose) i++;
154
+
155
+ // Parse tag name
156
+ let tagName = '';
157
+ while (i < html.length && /[a-zA-Z0-9-]/.test(html[i])) {
158
+ tagName += html[i];
159
+ i++;
160
+ }
161
+
162
+ if (!tagName) {
163
+ // Not a valid tag, continue searching
164
+ i = tagStart + 1;
165
+ continue;
166
+ }
167
+
168
+ // Parse attributes, respecting quotes
169
+ let attrs = '';
170
+ let inQuote: string | null = null;
171
+ let tagEnd = -1;
172
+
173
+ while (i < html.length) {
174
+ const char = html[i];
175
+
176
+ if (inQuote) {
177
+ attrs += char;
178
+ if (char === inQuote) {
179
+ inQuote = null;
180
+ }
181
+ i++;
182
+ } else if (char === '"' || char === "'") {
183
+ attrs += char;
184
+ inQuote = char;
185
+ i++;
186
+ } else if (char === '>') {
187
+ tagEnd = i + 1; // Include the '>'
188
+ i++;
189
+ break;
190
+ } else {
191
+ attrs += char;
192
+ i++;
193
+ }
194
+ }
195
+
196
+ if (tagEnd === -1) {
197
+ // Malformed tag (no closing >), continue searching
198
+ i = tagStart + 1;
199
+ continue;
200
+ }
201
+
202
+ // Check for self-closing
203
+ const trimmedAttrs = attrs.trim();
204
+ const isSelfClose = trimmedAttrs.endsWith('/');
205
+ const finalAttrs = isSelfClose ? trimmedAttrs.slice(0, -1).trim() : trimmedAttrs;
206
+
207
+ return {
208
+ index: tagStart,
209
+ endIndex: tagEnd,
210
+ isClose,
211
+ tagName,
212
+ attrs: finalAttrs,
213
+ isSelfClose
214
+ };
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ export function parseHTML(html: string): TemplateElement {
221
+ const match = html.match(/<template([^>]*)>([\s\S]*)<\/template>/i);
222
+
223
+ if (!match) {
224
+ throw new Error("No <template> tag found in HTML");
225
+ }
226
+
227
+ const content = match[2].trim();
228
+ const stack: Element[] = [];
229
+ const textChunks: string[] = [];
230
+ const fragmentChildren: Node[] = [];
231
+ const fragmentElements: Element[] = [];
232
+
233
+ let pos = 0;
234
+
235
+ const flushText = () => {
236
+ if (textChunks.length > 0) {
237
+ const text = textChunks.join("");
238
+ textChunks.length = 0;
239
+ const textNode = createTextNode(text);
240
+ if (stack.length > 0) {
241
+ const parent = stack[stack.length - 1];
242
+ parent.childNodes.push(textNode);
243
+ } else {
244
+ fragmentChildren.push(textNode);
245
+ }
246
+ }
247
+ };
248
+
249
+ while (pos < content.length) {
250
+ const tagInfo = findNextTag(content, pos);
251
+
252
+ if (!tagInfo) {
253
+ // No more tags, add remaining as text
254
+ if (pos < content.length) {
255
+ textChunks.push(content.slice(pos));
256
+ }
257
+ break;
258
+ }
259
+
260
+ // Add text before this tag
261
+ if (tagInfo.index > pos) {
262
+ textChunks.push(content.slice(pos, tagInfo.index));
263
+ }
264
+
265
+ const { isClose, tagName, attrs, isSelfClose, endIndex } = tagInfo;
266
+
267
+ if (isClose) {
268
+ flushText();
269
+ if (stack.length > 0) {
270
+ const closedElement = stack.pop()!;
271
+ if (closedElement.tagName.toLowerCase() !== tagName.toLowerCase()) {
272
+ throw new Error(`Mismatched tags: expected </${closedElement.tagName}>, got </${tagName}>`);
273
+ }
274
+
275
+ // Update parent's firstElementChild if needed
276
+ const parent = stack.length > 0 ? stack[stack.length - 1] : null;
277
+ if (parent && !parent.firstElementChild) {
278
+ parent.firstElementChild = closedElement;
279
+ }
280
+ }
281
+ } else {
282
+ flushText();
283
+ const attributes = parseAttributes(attrs);
284
+ const element = createElement(tagName, attributes);
285
+
286
+ if (stack.length === 0) {
287
+ // Top-level element
288
+ fragmentChildren.push(element);
289
+ fragmentElements.push(element);
290
+ } else {
291
+ const parent = stack[stack.length - 1];
292
+ parent.children.push(element);
293
+ parent.childNodes.push(element);
294
+ if (!parent.firstElementChild) {
295
+ parent.firstElementChild = element;
296
+ }
297
+ }
298
+
299
+ if (!isSelfClose) {
300
+ stack.push(element);
301
+ }
302
+ }
303
+
304
+ // Move position past this tag
305
+ pos = endIndex;
306
+ }
307
+
308
+ // Flush any remaining text
309
+ if (stack.length === 0) {
310
+ flushText();
311
+ }
312
+
313
+ if (stack.length > 0) {
314
+ throw new Error(`Unclosed tags remain: ${stack.map(e => e.tagName).join(', ')}`);
315
+ }
316
+
317
+ const fragment: DocumentFragment = {
318
+ childNodes: fragmentChildren,
319
+ firstElementChild: fragmentElements[0] || null,
320
+ };
321
+
322
+ const templateAttrs = parseAttributes(match[1].trim());
323
+ const templateElement: TemplateElement = {
324
+ ...createElement("template", templateAttrs),
325
+ content: fragment,
326
+ };
327
+ templateElement.childNodes = [...fragmentChildren];
328
+ templateElement.children = [...fragmentElements];
329
+ templateElement.firstElementChild = fragmentElements[0] || null;
330
+
331
+ return templateElement;
332
+ }