@vertz/ui 0.2.0 → 0.2.1

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 (35) hide show
  1. package/README.md +339 -857
  2. package/dist/css/public.d.ts +24 -27
  3. package/dist/css/public.js +5 -1
  4. package/dist/form/public.d.ts +94 -38
  5. package/dist/form/public.js +5 -3
  6. package/dist/index.d.ts +696 -167
  7. package/dist/index.js +461 -84
  8. package/dist/internals.d.ts +192 -23
  9. package/dist/internals.js +151 -102
  10. package/dist/jsx-runtime/index.d.ts +44 -17
  11. package/dist/jsx-runtime/index.js +26 -7
  12. package/dist/query/public.d.ts +62 -7
  13. package/dist/query/public.js +12 -4
  14. package/dist/router/public.d.ts +186 -26
  15. package/dist/router/public.js +22 -7
  16. package/dist/shared/{chunk-f1ynwam4.js → chunk-0p5f7gmg.js} +155 -32
  17. package/dist/shared/{chunk-j8vzvne3.js → chunk-9e92w0wt.js} +4 -1
  18. package/dist/shared/{chunk-xd9d7q5p.js → chunk-cq7xg4xe.js} +59 -10
  19. package/dist/shared/chunk-g4rch80a.js +33 -0
  20. package/dist/shared/{chunk-pgymxpn1.js → chunk-hrd0mft1.js} +136 -34
  21. package/dist/shared/chunk-nmjyj8p9.js +290 -0
  22. package/dist/shared/chunk-pp3a6xbn.js +483 -0
  23. package/dist/shared/chunk-prj7nm08.js +67 -0
  24. package/dist/shared/chunk-q6cpe5k7.js +230 -0
  25. package/dist/shared/chunk-ryb49346.js +374 -0
  26. package/dist/shared/chunk-v3yyf79g.js +48 -0
  27. package/dist/shared/chunk-vx0kzack.js +103 -0
  28. package/dist/shared/chunk-wv6kkj1w.js +464 -0
  29. package/dist/test/index.d.ts +67 -6
  30. package/dist/test/index.js +4 -3
  31. package/package.json +13 -8
  32. package/dist/shared/chunk-bp3v6s9j.js +0 -62
  33. package/dist/shared/chunk-d8h2eh8d.js +0 -141
  34. package/dist/shared/chunk-tsdpgmks.js +0 -98
  35. package/dist/shared/chunk-zbbvx05f.js +0 -202
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI framework — signals, components, JSX runtime",
@@ -58,19 +58,24 @@
58
58
  ],
59
59
  "scripts": {
60
60
  "build": "bunup",
61
- "test": "vitest run",
62
- "test:watch": "vitest",
61
+ "test": "bun test",
62
+ "test:watch": "bun test --watch",
63
63
  "typecheck": "tsc --noEmit"
64
64
  },
65
+ "dependencies": {
66
+ "@vertz/fetch": "0.2.1"
67
+ },
65
68
  "devDependencies": {
66
- "@vitest/coverage-v8": "^4.0.18",
69
+ "@happy-dom/global-registrator": "^20.7.0",
70
+ "@vertz/schema": "0.2.1",
67
71
  "bunup": "latest",
68
- "happy-dom": "^18.0.1",
69
- "typescript": "^5.7.0",
70
- "vitest": "^4.0.18"
72
+ "happy-dom": "^20.7.0",
73
+ "typescript": "^5.7.0"
71
74
  },
72
75
  "engines": {
73
76
  "node": ">=22"
74
77
  },
75
- "sideEffects": false
78
+ "sideEffects": [
79
+ "dist/shared/*.js"
80
+ ]
76
81
  }
@@ -1,62 +0,0 @@
1
- import {
2
- effect,
3
- useContext
4
- } from "./chunk-pgymxpn1.js";
5
-
6
- // src/router/link.ts
7
- function createLink(currentPath, navigate) {
8
- return function Link(props) {
9
- const el = document.createElement("a");
10
- el.setAttribute("href", props.href);
11
- el.textContent = props.children;
12
- if (props.className) {
13
- el.classList.add(props.className);
14
- }
15
- if (props.activeClass) {
16
- const activeClass = props.activeClass;
17
- effect(() => {
18
- if (currentPath.value === props.href) {
19
- el.classList.add(activeClass);
20
- } else {
21
- el.classList.remove(activeClass);
22
- }
23
- });
24
- }
25
- el.addEventListener("click", (event) => {
26
- if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
27
- return;
28
- }
29
- event.preventDefault();
30
- navigate(props.href);
31
- });
32
- return el;
33
- };
34
- }
35
-
36
- // src/router/outlet.ts
37
- function createOutlet(outletCtx) {
38
- return function Outlet() {
39
- const ctx = useContext(outletCtx);
40
- if (!ctx || !ctx.childComponent) {
41
- return document.createComment("outlet:empty");
42
- }
43
- return ctx.childComponent();
44
- };
45
- }
46
-
47
- // src/router/search-params.ts
48
- function parseSearchParams(urlParams, schema) {
49
- const raw = {};
50
- for (const [key, value] of urlParams.entries()) {
51
- raw[key] = value;
52
- }
53
- if (schema) {
54
- return schema.parse(raw);
55
- }
56
- return raw;
57
- }
58
- function useSearchParams(searchSignal) {
59
- return searchSignal.value;
60
- }
61
-
62
- export { createLink, createOutlet, parseSearchParams, useSearchParams };
@@ -1,141 +0,0 @@
1
- // src/hydrate/component-registry.ts
2
- async function resolveComponent(registry, componentId) {
3
- const loader = registry[componentId];
4
- if (!loader) {
5
- throw new TypeError(`[hydrate] Component "${componentId}" not found in registry`);
6
- }
7
- const mod = await loader();
8
- return mod.default;
9
- }
10
-
11
- // src/hydrate/props-deserializer.ts
12
- function deserializeProps(container) {
13
- const script = container.querySelector('script[type="application/json"]');
14
- if (!script || !script.textContent) {
15
- return {};
16
- }
17
- try {
18
- return JSON.parse(script.textContent);
19
- } catch {
20
- return {};
21
- }
22
- }
23
-
24
- // src/hydrate/strategies.ts
25
- function eagerStrategy(el, hydrateFn) {
26
- hydrateFn();
27
- }
28
- function lazyStrategy(el, hydrateFn) {
29
- if (typeof IntersectionObserver === "undefined") {
30
- hydrateFn();
31
- return;
32
- }
33
- const observer = new IntersectionObserver((entries) => {
34
- for (const entry of entries) {
35
- if (entry.isIntersecting) {
36
- observer.disconnect();
37
- hydrateFn();
38
- return;
39
- }
40
- }
41
- }, { rootMargin: "200px" });
42
- observer.observe(el);
43
- }
44
- function interactionStrategy(el, hydrateFn) {
45
- const events = ["click", "focus", "pointerenter"];
46
- function handler() {
47
- for (const event of events) {
48
- el.removeEventListener(event, handler);
49
- }
50
- hydrateFn();
51
- }
52
- for (const event of events) {
53
- el.addEventListener(event, handler);
54
- }
55
- }
56
- function idleStrategy(el, hydrateFn) {
57
- if (typeof requestIdleCallback !== "undefined") {
58
- requestIdleCallback(() => hydrateFn());
59
- } else {
60
- setTimeout(hydrateFn, 0);
61
- }
62
- }
63
- function mediaStrategy(query) {
64
- return (el, hydrateFn) => {
65
- const mql = window.matchMedia(query);
66
- if (mql.matches) {
67
- hydrateFn();
68
- return;
69
- }
70
- function onChange(event) {
71
- if (event.matches) {
72
- mql.removeEventListener("change", onChange);
73
- hydrateFn();
74
- }
75
- }
76
- mql.addEventListener("change", onChange);
77
- };
78
- }
79
- function visibleStrategy(el, hydrateFn) {
80
- if (typeof IntersectionObserver === "undefined") {
81
- hydrateFn();
82
- return;
83
- }
84
- const observer = new IntersectionObserver((entries) => {
85
- for (const entry of entries) {
86
- if (entry.isIntersecting) {
87
- observer.disconnect();
88
- hydrateFn();
89
- return;
90
- }
91
- }
92
- });
93
- observer.observe(el);
94
- }
95
-
96
- // src/hydrate/hydrate.ts
97
- function hydrate(registry) {
98
- const elements = document.querySelectorAll("[data-v-id]");
99
- for (const el of elements) {
100
- if (el.hasAttribute("data-v-hydrated"))
101
- continue;
102
- const componentId = el.getAttribute("data-v-id");
103
- if (!componentId)
104
- continue;
105
- const strategy = el.getAttribute("hydrate") ?? "lazy";
106
- const props = deserializeProps(el);
107
- const doHydrate = () => {
108
- resolveComponent(registry, componentId).then((component) => {
109
- component(props, el);
110
- el.setAttribute("data-v-hydrated", "");
111
- }).catch((error) => {
112
- console.error(`[hydrate] Failed to hydrate component "${componentId}":`, error);
113
- });
114
- };
115
- switch (strategy) {
116
- case "eager":
117
- eagerStrategy(el, doHydrate);
118
- break;
119
- case "lazy":
120
- lazyStrategy(el, doHydrate);
121
- break;
122
- case "interaction":
123
- interactionStrategy(el, doHydrate);
124
- break;
125
- case "idle":
126
- idleStrategy(el, doHydrate);
127
- break;
128
- case "media": {
129
- const query = el.getAttribute("hydrate-media") ?? "";
130
- mediaStrategy(query)(el, doHydrate);
131
- break;
132
- }
133
- case "visible":
134
- visibleStrategy(el, doHydrate);
135
- break;
136
- default:
137
- lazyStrategy(el, doHydrate);
138
- }
139
- }
140
- }
141
- export { resolveComponent, deserializeProps, eagerStrategy, lazyStrategy, interactionStrategy, idleStrategy, mediaStrategy, visibleStrategy, hydrate };
@@ -1,98 +0,0 @@
1
- import {
2
- signal
3
- } from "./chunk-pgymxpn1.js";
4
-
5
- // src/form/form-data.ts
6
- function formDataToObject(formData, options) {
7
- const result = {};
8
- const coerce = options?.coerce ?? false;
9
- for (const [key, value] of formData.entries()) {
10
- if (typeof value !== "string") {
11
- continue;
12
- }
13
- result[key] = coerce ? coerceValue(value) : value;
14
- }
15
- return result;
16
- }
17
- function coerceValue(value) {
18
- if (value === "true")
19
- return true;
20
- if (value === "false")
21
- return false;
22
- if (value !== "" && !Number.isNaN(Number(value))) {
23
- return Number(value);
24
- }
25
- return value;
26
- }
27
-
28
- // src/form/validation.ts
29
- function validate(schema, data) {
30
- try {
31
- const parsed = schema.parse(data);
32
- return { success: true, data: parsed, errors: {} };
33
- } catch (err) {
34
- if (err instanceof Error) {
35
- const fieldErrors = err.fieldErrors;
36
- if (fieldErrors && Object.keys(fieldErrors).length > 0) {
37
- return { success: false, data: undefined, errors: fieldErrors };
38
- }
39
- return { success: false, data: undefined, errors: { _form: err.message } };
40
- }
41
- return { success: false, data: undefined, errors: { _form: "Validation failed" } };
42
- }
43
- }
44
-
45
- // src/form/form.ts
46
- function form(sdkMethod, options) {
47
- const submitting = signal(false);
48
- const errors = signal({});
49
- return {
50
- attrs() {
51
- return {
52
- action: sdkMethod.url,
53
- method: sdkMethod.method
54
- };
55
- },
56
- submitting,
57
- handleSubmit(callbacks) {
58
- return async (formDataOrEvent) => {
59
- let formData;
60
- if (formDataOrEvent instanceof Event) {
61
- formDataOrEvent.preventDefault();
62
- const target = formDataOrEvent.target;
63
- formData = new FormData(target);
64
- } else {
65
- formData = formDataOrEvent;
66
- }
67
- const data = formDataToObject(formData);
68
- const result = validate(options.schema, data);
69
- if (!result.success) {
70
- errors.value = result.errors;
71
- callbacks?.onError?.(result.errors);
72
- return;
73
- }
74
- errors.value = {};
75
- submitting.value = true;
76
- let response;
77
- try {
78
- response = await sdkMethod(result.data);
79
- } catch (err) {
80
- submitting.value = false;
81
- const message = err instanceof Error ? err.message : "Submission failed";
82
- const serverErrors = { _form: message };
83
- errors.value = serverErrors;
84
- callbacks?.onError?.(serverErrors);
85
- return;
86
- }
87
- submitting.value = false;
88
- callbacks?.onSuccess?.(response);
89
- };
90
- },
91
- error(field) {
92
- const currentErrors = errors.value;
93
- return currentErrors[field];
94
- }
95
- };
96
- }
97
-
98
- export { formDataToObject, validate, form };
@@ -1,202 +0,0 @@
1
- import {
2
- computed,
3
- effect,
4
- setReadValueCallback,
5
- signal,
6
- untrack
7
- } from "./chunk-pgymxpn1.js";
8
-
9
- // src/query/cache.ts
10
- class MemoryCache {
11
- _store = new Map;
12
- get(key) {
13
- return this._store.get(key);
14
- }
15
- set(key, value) {
16
- this._store.set(key, value);
17
- }
18
- delete(key) {
19
- this._store.delete(key);
20
- }
21
- }
22
-
23
- // src/query/key-derivation.ts
24
- function deriveKey(thunk) {
25
- return `__q:${hashString(thunk.toString())}`;
26
- }
27
- function hashString(str) {
28
- let hash = 5381;
29
- for (let i = 0;i < str.length; i++) {
30
- hash = hash * 33 ^ str.charCodeAt(i);
31
- }
32
- return (hash >>> 0).toString(36);
33
- }
34
-
35
- // src/query/query.ts
36
- var defaultCache = new MemoryCache;
37
- var inflight = new Map;
38
- function query(thunk, options = {}) {
39
- const {
40
- initialData,
41
- debounce: debounceMs,
42
- enabled = true,
43
- key: customKey,
44
- cache = defaultCache
45
- } = options;
46
- const baseKey = deriveKey(thunk);
47
- const depHashSignal = signal("");
48
- const cacheKeyComputed = computed(() => {
49
- const dh = depHashSignal.value;
50
- return customKey ?? (dh ? `${baseKey}:${dh}` : `${baseKey}:init`);
51
- });
52
- function getCacheKey() {
53
- return cacheKeyComputed.value;
54
- }
55
- function callThunkWithCapture() {
56
- const captured = [];
57
- const prevCb = setReadValueCallback((v) => captured.push(v));
58
- let promise;
59
- try {
60
- promise = thunk();
61
- } finally {
62
- setReadValueCallback(prevCb);
63
- }
64
- const serialized = captured.map((v) => JSON.stringify(v)).join("|");
65
- untrack(() => {
66
- depHashSignal.value = hashString(serialized);
67
- });
68
- return promise;
69
- }
70
- const data = signal(initialData);
71
- const loading = signal(initialData === undefined && enabled);
72
- const error = signal(undefined);
73
- if (initialData !== undefined) {
74
- cache.set(getCacheKey(), initialData);
75
- }
76
- let fetchId = 0;
77
- let debounceTimer;
78
- const inflightKeys = new Set;
79
- const refetchTrigger = signal(0);
80
- function handleFetchPromise(promise, id, key) {
81
- promise.then((result) => {
82
- inflight.delete(key);
83
- inflightKeys.delete(key);
84
- if (id !== fetchId)
85
- return;
86
- cache.set(key, result);
87
- data.value = result;
88
- loading.value = false;
89
- }, (err) => {
90
- inflight.delete(key);
91
- inflightKeys.delete(key);
92
- if (id !== fetchId)
93
- return;
94
- error.value = err;
95
- loading.value = false;
96
- });
97
- }
98
- function startFetch(fetchPromise, key) {
99
- const id = ++fetchId;
100
- untrack(() => {
101
- loading.value = true;
102
- error.value = undefined;
103
- });
104
- const existing = inflight.get(key);
105
- if (existing) {
106
- handleFetchPromise(existing, id, key);
107
- return;
108
- }
109
- inflight.set(key, fetchPromise);
110
- inflightKeys.add(key);
111
- handleFetchPromise(fetchPromise, id, key);
112
- }
113
- function refetch() {
114
- const key = getCacheKey();
115
- cache.delete(key);
116
- inflight.delete(key);
117
- refetchTrigger.value = refetchTrigger.peek() + 1;
118
- }
119
- let disposeFn;
120
- if (enabled) {
121
- let isFirst = true;
122
- disposeFn = effect(() => {
123
- refetchTrigger.value;
124
- if (customKey) {
125
- const existing = untrack(() => inflight.get(customKey));
126
- if (existing) {
127
- const id = ++fetchId;
128
- untrack(() => {
129
- loading.value = true;
130
- error.value = undefined;
131
- });
132
- handleFetchPromise(existing, id, customKey);
133
- isFirst = false;
134
- return;
135
- }
136
- }
137
- const promise = callThunkWithCapture();
138
- const key = untrack(() => getCacheKey());
139
- if (!customKey) {
140
- const existing = untrack(() => inflight.get(key));
141
- if (existing) {
142
- promise.catch(() => {});
143
- const id = ++fetchId;
144
- untrack(() => {
145
- loading.value = true;
146
- error.value = undefined;
147
- });
148
- handleFetchPromise(existing, id, key);
149
- isFirst = false;
150
- return;
151
- }
152
- }
153
- if (!isFirst && !customKey) {
154
- const cached = untrack(() => cache.get(key));
155
- if (cached !== undefined) {
156
- promise.catch(() => {});
157
- untrack(() => {
158
- data.value = cached;
159
- loading.value = false;
160
- error.value = undefined;
161
- });
162
- isFirst = false;
163
- return;
164
- }
165
- }
166
- if (isFirst && initialData !== undefined) {
167
- promise.catch(() => {});
168
- isFirst = false;
169
- return;
170
- }
171
- isFirst = false;
172
- if (debounceMs !== undefined && debounceMs > 0) {
173
- clearTimeout(debounceTimer);
174
- promise.catch(() => {});
175
- debounceTimer = setTimeout(() => {
176
- startFetch(promise, key);
177
- }, debounceMs);
178
- } else {
179
- startFetch(promise, key);
180
- }
181
- });
182
- }
183
- function dispose() {
184
- disposeFn?.();
185
- clearTimeout(debounceTimer);
186
- fetchId++;
187
- for (const key of inflightKeys) {
188
- inflight.delete(key);
189
- }
190
- inflightKeys.clear();
191
- }
192
- return {
193
- data,
194
- loading,
195
- error,
196
- refetch,
197
- revalidate: refetch,
198
- dispose
199
- };
200
- }
201
-
202
- export { MemoryCache, deriveKey, query };