atmx-web 0.42.0
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/justfile +8 -0
- package/package.json +22 -0
- package/public/.axiom +170 -0
- package/public/axiom_runtime.wasm +0 -0
- package/scripts/upload.sh +61 -0
- package/src/core/callback.ts +92 -0
- package/src/core/context.ts +56 -0
- package/src/core/query.ts +162 -0
- package/src/core/router.ts +91 -0
- package/src/core/types.ts +49 -0
- package/src/core/vendor/axiom_runtime.js +885 -0
- package/src/core/wasm.ts +223 -0
- package/src/dom/components/engine-status.ts +62 -0
- package/src/dom/indicators.ts +36 -0
- package/src/dom/lifecycle.ts +379 -0
- package/src/dom/scanner.ts +125 -0
- package/src/index.ts +78 -0
- package/src/resolver/evaluator.ts +46 -0
- package/src/resolver/render.ts +196 -0
- package/tsconfig.json +25 -0
- package/vite-env.d.ts +11 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// FILE: src/dom/scanner.ts
|
|
2
|
+
import { connectQuery, executeMutation, parseRpcCall } from "./lifecycle";
|
|
3
|
+
import { updateContext, getContext } from "../core/context";
|
|
4
|
+
import { queryRegistry } from "../core/query";
|
|
5
|
+
import { resolveRoute } from "../core/router";
|
|
6
|
+
import { evaluateExpression } from "../resolver/evaluator";
|
|
7
|
+
|
|
8
|
+
const intersectionObserver = new IntersectionObserver((entries) => {
|
|
9
|
+
entries.forEach((entry) => {
|
|
10
|
+
if (entry.isIntersecting) {
|
|
11
|
+
const el = entry.target as HTMLElement;
|
|
12
|
+
if (el.hasAttribute("ax-query")) connectQuery(el);
|
|
13
|
+
intersectionObserver.unobserve(el);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ✨ NEW: Evaluates dynamic prefixed attributes like :ax-query="sdk.pyExample.login()"
|
|
20
|
+
*/
|
|
21
|
+
function evaluateDynamicAttributes(root: HTMLElement | Document) {
|
|
22
|
+
const dynamicElements = root.querySelectorAll<HTMLElement>(
|
|
23
|
+
"[\\:ax-query], [\\:ax-mutate]",
|
|
24
|
+
);
|
|
25
|
+
dynamicElements.forEach((el) => {
|
|
26
|
+
if (el.hasAttribute(":ax-query")) {
|
|
27
|
+
const expr = el.getAttribute(":ax-query")!;
|
|
28
|
+
const val = evaluateExpression(expr, getContext(el));
|
|
29
|
+
if (val) el.setAttribute("ax-query", String(val));
|
|
30
|
+
el.removeAttribute(":ax-query");
|
|
31
|
+
}
|
|
32
|
+
if (el.hasAttribute(":ax-mutate")) {
|
|
33
|
+
const expr = el.getAttribute(":ax-mutate")!;
|
|
34
|
+
const val = evaluateExpression(expr, getContext(el));
|
|
35
|
+
if (val) el.setAttribute("ax-mutate", String(val));
|
|
36
|
+
el.removeAttribute(":ax-mutate");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function scanAndInitialize(root: HTMLElement | Document = document) {
|
|
42
|
+
// 0. Unwrap Dynamic Typescript Clients first
|
|
43
|
+
evaluateDynamicAttributes(root);
|
|
44
|
+
|
|
45
|
+
// 1. Initialize ax-query blocks
|
|
46
|
+
const queries = root.querySelectorAll("[ax-query]");
|
|
47
|
+
queries.forEach((el) => {
|
|
48
|
+
const htmlEl = el as HTMLElement;
|
|
49
|
+
updateContext(htmlEl, {});
|
|
50
|
+
|
|
51
|
+
const triggerMode = htmlEl.getAttribute("ax-trigger") || "load";
|
|
52
|
+
if (triggerMode === "load") {
|
|
53
|
+
connectQuery(htmlEl);
|
|
54
|
+
} else if (triggerMode === "intersect") {
|
|
55
|
+
intersectionObserver.observe(htmlEl);
|
|
56
|
+
} else {
|
|
57
|
+
htmlEl.addEventListener(triggerMode, (e) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
connectQuery(htmlEl);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 2. Initialize ax-mutate blocks
|
|
65
|
+
const mutates = root.querySelectorAll("[ax-mutate]");
|
|
66
|
+
mutates.forEach((el) => {
|
|
67
|
+
const htmlEl = el as HTMLElement;
|
|
68
|
+
updateContext(htmlEl, {});
|
|
69
|
+
|
|
70
|
+
const trigger = htmlEl.tagName === "FORM" ? "submit" : "click";
|
|
71
|
+
|
|
72
|
+
htmlEl.addEventListener(trigger, (e) => {
|
|
73
|
+
if (
|
|
74
|
+
htmlEl.tagName === "A" ||
|
|
75
|
+
htmlEl.tagName === "BUTTON" ||
|
|
76
|
+
htmlEl.tagName === "FORM"
|
|
77
|
+
) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
}
|
|
80
|
+
executeMutation(htmlEl);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 3. Action Modifiers (e.g., ax-trigger="refresh")
|
|
85
|
+
const refreshers = root.querySelectorAll('[ax-trigger="refresh"]');
|
|
86
|
+
refreshers.forEach((el) => {
|
|
87
|
+
const htmlEl = el as HTMLElement;
|
|
88
|
+
htmlEl.addEventListener("click", (e) => {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
|
|
91
|
+
const parentQuery = htmlEl.closest("[ax-query]") as HTMLElement;
|
|
92
|
+
if (parentQuery) {
|
|
93
|
+
const attrVal = parentQuery.getAttribute("ax-query")!;
|
|
94
|
+
const parsed = parseRpcCall(parentQuery, attrVal);
|
|
95
|
+
|
|
96
|
+
if (parsed) {
|
|
97
|
+
const route = resolveRoute(parsed.target);
|
|
98
|
+
if (route) {
|
|
99
|
+
const queryKey = `${route.namespace}:${route.id}:${JSON.stringify(parsed.args)}`;
|
|
100
|
+
const activeQuery = queryRegistry.get(queryKey);
|
|
101
|
+
if (activeQuery) {
|
|
102
|
+
activeQuery.fetch(true);
|
|
103
|
+
} else {
|
|
104
|
+
connectQuery(parentQuery);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function initMutationObserver() {
|
|
114
|
+
const observer = new MutationObserver((mutations) => {
|
|
115
|
+
mutations.forEach((mutation) => {
|
|
116
|
+
mutation.addedNodes.forEach((node) => {
|
|
117
|
+
if (node instanceof HTMLElement) {
|
|
118
|
+
scanAndInitialize(node);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
125
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// FILE: src/index.ts
|
|
2
|
+
import { initWasm, setAuthToken, clearAuthToken } from "./core/wasm";
|
|
3
|
+
import { scanAndInitialize, initMutationObserver } from "./dom/scanner";
|
|
4
|
+
import { connectQuery } from "./dom/lifecycle";
|
|
5
|
+
import { AtmxConfig, InitResult } from "./core/types";
|
|
6
|
+
import "./dom/components/engine-status";
|
|
7
|
+
|
|
8
|
+
export const ATMX_VERSION = "0.42.0";
|
|
9
|
+
|
|
10
|
+
class ATMX {
|
|
11
|
+
public version = ATMX_VERSION;
|
|
12
|
+
public initialized = false;
|
|
13
|
+
public debug = false;
|
|
14
|
+
|
|
15
|
+
public async init(config: AtmxConfig): Promise<InitResult> {
|
|
16
|
+
if (this.initialized) {
|
|
17
|
+
return { ok: true, contracts: {}, initDurationMs: 0 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = await initWasm(config);
|
|
21
|
+
|
|
22
|
+
if (result.ok) {
|
|
23
|
+
this.debug = config.debug || false;
|
|
24
|
+
|
|
25
|
+
initMutationObserver();
|
|
26
|
+
scanAndInitialize(document.body);
|
|
27
|
+
|
|
28
|
+
this.initialized = true;
|
|
29
|
+
if (this.debug)
|
|
30
|
+
console.log(
|
|
31
|
+
"%c🚀 ATMX Debug Mode: Enabled",
|
|
32
|
+
"color: #2563eb; font-weight: bold;",
|
|
33
|
+
);
|
|
34
|
+
console.log(
|
|
35
|
+
`✅ ATMX v${this.version} Online (${result.initDurationMs}ms). Reactive Engine Started.`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
document.dispatchEvent(new CustomEvent("atmx:ready", { detail: result }));
|
|
39
|
+
} else {
|
|
40
|
+
console.error("❌ ATMX Initialization Failed:", result.error);
|
|
41
|
+
// Broadcast fatal error so UI components can catch it
|
|
42
|
+
document.dispatchEvent(
|
|
43
|
+
new CustomEvent("atmx:error", { detail: result.error }),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public triggerQuery(elementOrSelector: string | HTMLElement) {
|
|
51
|
+
const el =
|
|
52
|
+
typeof elementOrSelector === "string"
|
|
53
|
+
? (document.querySelector(elementOrSelector) as HTMLElement)
|
|
54
|
+
: elementOrSelector;
|
|
55
|
+
|
|
56
|
+
if (el && el instanceof HTMLElement && el.hasAttribute("ax-query")) {
|
|
57
|
+
connectQuery(el);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public on(
|
|
62
|
+
eventName: "ready" | "data" | "error" | "success" | "contract-loaded",
|
|
63
|
+
callback: (e: any) => void,
|
|
64
|
+
) {
|
|
65
|
+
document.addEventListener(`atmx:${eventName}`, callback);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public setAuthToken(namespace: string, methodName: string, token: string) {
|
|
69
|
+
setAuthToken(namespace, methodName, token);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public clearAuthToken(namespace: string, methodName: string) {
|
|
73
|
+
clearAuthToken(namespace, methodName);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const atmx = new ATMX();
|
|
78
|
+
if (typeof window !== "undefined") (window as any).atmx = atmx;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// FILE: src/resolver/evaluator.ts
|
|
2
|
+
import { AtmxContextData } from "../core/context";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Safely evaluates a JS expression from an HTML attribute against the local ATMX Context.
|
|
6
|
+
*/
|
|
7
|
+
export function evaluateExpression(
|
|
8
|
+
expr: string,
|
|
9
|
+
context: AtmxContextData,
|
|
10
|
+
locals: Record<string, any> = {},
|
|
11
|
+
): any {
|
|
12
|
+
if (!expr) return undefined;
|
|
13
|
+
try {
|
|
14
|
+
const keys = Object.keys(locals);
|
|
15
|
+
const values = Object.values(locals);
|
|
16
|
+
|
|
17
|
+
// Dynamically build a function that accepts $data, $error, $state, AND our locals ($item, etc.)
|
|
18
|
+
const fn = new Function(
|
|
19
|
+
"$data",
|
|
20
|
+
"$error",
|
|
21
|
+
"$state",
|
|
22
|
+
...keys,
|
|
23
|
+
`return (${expr});`,
|
|
24
|
+
);
|
|
25
|
+
return fn(context.data || {}, context.error || {}, context, ...values);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error(`ATMX Eval Error: ${expr}`, e);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function executeAction(
|
|
33
|
+
expr: string,
|
|
34
|
+
context: AtmxContextData,
|
|
35
|
+
locals: Record<string, any> = {},
|
|
36
|
+
): void {
|
|
37
|
+
if (!expr) return;
|
|
38
|
+
try {
|
|
39
|
+
const keys = Object.keys(locals);
|
|
40
|
+
const values = Object.values(locals);
|
|
41
|
+
const fn = new Function("$data", "$error", "$state", ...keys, expr);
|
|
42
|
+
fn(context.data || {}, context.error || {}, context, ...values);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error(`ATMX Action Error: ${expr}`, e);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// FILE: src/resolver/render.ts
|
|
2
|
+
import { AtmxContextData } from "../core/context";
|
|
3
|
+
import { evaluateExpression } from "./evaluator";
|
|
4
|
+
|
|
5
|
+
export function renderContext(rootEl: HTMLElement, context: AtmxContextData) {
|
|
6
|
+
walkAndRender(rootEl, context, true, {});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function walkAndRender(
|
|
10
|
+
node: HTMLElement,
|
|
11
|
+
context: AtmxContextData,
|
|
12
|
+
isRoot: boolean,
|
|
13
|
+
locals: Record<string, any>,
|
|
14
|
+
) {
|
|
15
|
+
// Stop traversal if we hit an independent reactive boundary
|
|
16
|
+
if (
|
|
17
|
+
!isRoot &&
|
|
18
|
+
(node.hasAttribute("ax-query") || node.hasAttribute("ax-mutate"))
|
|
19
|
+
) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ✨ PHASE 1: DECLARATIVE LIST RENDERING (ax-for)
|
|
24
|
+
if (node.hasAttribute("ax-for")) {
|
|
25
|
+
handleAxFor(node, context, locals);
|
|
26
|
+
return; // Stop normal traversal for the template node!
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 1. STATE RENDERING (ax-when)
|
|
30
|
+
if (node.hasAttribute("ax-when")) {
|
|
31
|
+
const expectedStates = node
|
|
32
|
+
.getAttribute("ax-when")!
|
|
33
|
+
.split(",")
|
|
34
|
+
.map((s) => s.trim());
|
|
35
|
+
if (!expectedStates.includes(context.state)) {
|
|
36
|
+
node.setAttribute("hidden", "true");
|
|
37
|
+
node.style.setProperty("display", "none", "important");
|
|
38
|
+
} else {
|
|
39
|
+
node.removeAttribute("hidden");
|
|
40
|
+
node.style.removeProperty("display");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. CONDITIONAL RENDERING (ax-if)
|
|
45
|
+
if (node.hasAttribute("ax-if")) {
|
|
46
|
+
const result = evaluateExpression(
|
|
47
|
+
node.getAttribute("ax-if")!,
|
|
48
|
+
context,
|
|
49
|
+
locals,
|
|
50
|
+
);
|
|
51
|
+
if (!result) {
|
|
52
|
+
node.setAttribute("hidden", "");
|
|
53
|
+
node.style.setProperty("display", "none", "important");
|
|
54
|
+
} else {
|
|
55
|
+
node.removeAttribute("hidden");
|
|
56
|
+
node.style.removeProperty("display");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3. TEXT INTERPOLATION (ax-text)
|
|
61
|
+
if (node.hasAttribute("ax-text")) {
|
|
62
|
+
const result = evaluateExpression(
|
|
63
|
+
node.getAttribute("ax-text")!,
|
|
64
|
+
context,
|
|
65
|
+
locals,
|
|
66
|
+
);
|
|
67
|
+
// ✨ SECURE: textContent completely eliminates XSS vulnerabilities!
|
|
68
|
+
node.textContent =
|
|
69
|
+
result !== undefined && result !== null ? String(result) : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 4. FIELD-LEVEL ERRORS (ax-error-for)
|
|
73
|
+
if (node.hasAttribute("ax-error-for")) {
|
|
74
|
+
const fieldName = node.getAttribute("ax-error-for")!;
|
|
75
|
+
let errorMessage = "";
|
|
76
|
+
if (
|
|
77
|
+
context.state === "error" &&
|
|
78
|
+
context.error?.code === "ValidationError"
|
|
79
|
+
) {
|
|
80
|
+
const details = context.error.details || "";
|
|
81
|
+
const lines = details.split("\n");
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.startsWith(fieldName + ":")) {
|
|
84
|
+
errorMessage = line.substring(fieldName.length + 1).trim();
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (errorMessage) {
|
|
90
|
+
node.textContent = errorMessage;
|
|
91
|
+
node.removeAttribute("hidden");
|
|
92
|
+
node.style.removeProperty("display");
|
|
93
|
+
} else {
|
|
94
|
+
node.setAttribute("hidden", "");
|
|
95
|
+
node.style.setProperty("display", "none", "important");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 5. ATTRIBUTE BINDING (ax-bind:*)
|
|
100
|
+
Array.from(node.attributes).forEach((attr) => {
|
|
101
|
+
if (attr.name.startsWith("ax-bind:")) {
|
|
102
|
+
const targetAttr = attr.name.substring(8);
|
|
103
|
+
const result = evaluateExpression(attr.value, context, locals);
|
|
104
|
+
if (result === false || result === null || result === undefined) {
|
|
105
|
+
node.removeAttribute(targetAttr);
|
|
106
|
+
} else {
|
|
107
|
+
node.setAttribute(
|
|
108
|
+
targetAttr,
|
|
109
|
+
result === true ? targetAttr : String(result),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
Array.from(node.children).forEach((child) => {
|
|
116
|
+
walkAndRender(child as HTMLElement, context, false, locals);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function handleAxFor(
|
|
121
|
+
templateNode: HTMLElement,
|
|
122
|
+
context: AtmxContextData,
|
|
123
|
+
locals: Record<string, any>,
|
|
124
|
+
) {
|
|
125
|
+
templateNode.style.display = "none"; // Hide the template node
|
|
126
|
+
|
|
127
|
+
if (!templateNode.hasAttribute("data-ax-for-id")) {
|
|
128
|
+
templateNode.setAttribute(
|
|
129
|
+
"data-ax-for-id",
|
|
130
|
+
Math.random().toString(36).substr(2, 9),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const forId = templateNode.getAttribute("data-ax-for-id")!;
|
|
134
|
+
const forExpr = templateNode.getAttribute("ax-for")!;
|
|
135
|
+
|
|
136
|
+
// Parse: "$item in $data" or "($item, $index) in $data"
|
|
137
|
+
const match = forExpr.match(
|
|
138
|
+
/^\s*(?:(?:\(\s*([\w\$]+)\s*,\s*([\w\$]+)\s*\))|([\w\$]+))\s+in\s+(.+)$/,
|
|
139
|
+
);
|
|
140
|
+
if (!match) return;
|
|
141
|
+
|
|
142
|
+
const itemVar = match[1] || match[3];
|
|
143
|
+
const indexVar = match[2];
|
|
144
|
+
const iterableExpr = match[4];
|
|
145
|
+
|
|
146
|
+
const iterable = evaluateExpression(iterableExpr, context, locals);
|
|
147
|
+
const parent = templateNode.parentElement;
|
|
148
|
+
if (!parent) return;
|
|
149
|
+
|
|
150
|
+
const existingClones = Array.from(
|
|
151
|
+
parent.querySelectorAll(`[data-ax-clone-of="${forId}"]`),
|
|
152
|
+
) as HTMLElement[];
|
|
153
|
+
|
|
154
|
+
if (!iterable || !Array.isArray(iterable)) {
|
|
155
|
+
existingClones.forEach((el) => el.remove());
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const keyAttr = templateNode.getAttribute("ax-key");
|
|
160
|
+
const newClones: HTMLElement[] = [];
|
|
161
|
+
|
|
162
|
+
iterable.forEach((item, index) => {
|
|
163
|
+
const childLocals = { ...locals, [itemVar]: item };
|
|
164
|
+
if (indexVar) childLocals[indexVar] = index;
|
|
165
|
+
|
|
166
|
+
let keyValue = String(index);
|
|
167
|
+
if (keyAttr) {
|
|
168
|
+
keyValue = String(evaluateExpression(keyAttr, context, childLocals));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Try to reuse an existing DOM node for performance
|
|
172
|
+
let clone = existingClones.find(
|
|
173
|
+
(el) => el.getAttribute("data-ax-key") === keyValue,
|
|
174
|
+
);
|
|
175
|
+
if (!clone) {
|
|
176
|
+
clone = templateNode.cloneNode(true) as HTMLElement;
|
|
177
|
+
clone.removeAttribute("ax-for");
|
|
178
|
+
clone.style.removeProperty("display");
|
|
179
|
+
if (clone.style.length === 0) clone.removeAttribute("style");
|
|
180
|
+
clone.setAttribute("data-ax-clone-of", forId);
|
|
181
|
+
clone.setAttribute("data-ax-key", keyValue);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Maintain exact order in the DOM
|
|
185
|
+
parent.insertBefore(clone, templateNode);
|
|
186
|
+
|
|
187
|
+
// Render the contents of the clone with the injected locals!
|
|
188
|
+
walkAndRender(clone, context, false, childLocals);
|
|
189
|
+
newClones.push(clone);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Cleanup old clones that are no longer in the list
|
|
193
|
+
existingClones.forEach((el) => {
|
|
194
|
+
if (!newClones.includes(el)) el.remove();
|
|
195
|
+
});
|
|
196
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2020",
|
|
8
|
+
"DOM",
|
|
9
|
+
"DOM.Iterable"
|
|
10
|
+
],
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"src"
|
|
24
|
+
]
|
|
25
|
+
}
|
package/vite-env.d.ts
ADDED
package/vite.config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
server: {
|
|
6
|
+
open: '/example/index.html',
|
|
7
|
+
proxy: {
|
|
8
|
+
// Any request to /api will be proxied to your backend
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:8000',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
rewrite: (path) => path.replace(/^\/api/, '') // Strips /api before sending to backend
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
build: {
|
|
17
|
+
lib: {
|
|
18
|
+
entry: resolve(__dirname, 'src/index.ts'),
|
|
19
|
+
name: 'atmx',
|
|
20
|
+
fileName: (format) => `atmx.${format}.js`,
|
|
21
|
+
formats: ['es', 'umd']
|
|
22
|
+
},
|
|
23
|
+
assetsInlineLimit: 0,
|
|
24
|
+
outDir: 'dist',
|
|
25
|
+
emptyOutDir: true
|
|
26
|
+
}
|
|
27
|
+
});
|