@vertz/ui 0.2.15 → 0.2.16

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 (32) hide show
  1. package/README.md +49 -0
  2. package/dist/shared/chunk-14eqne2a.js +10 -0
  3. package/dist/shared/{chunk-dksg08fq.js → chunk-1rxa2fz4.js} +2 -8
  4. package/dist/shared/{chunk-8hsz5y4a.js → chunk-4fwcwxn6.js} +14 -4
  5. package/dist/shared/{chunk-hw67ckr3.js → chunk-4mtn7af6.js} +230 -19
  6. package/dist/shared/{chunk-4txc67nd.js → chunk-6jyt4ycw.js} +67 -2
  7. package/dist/shared/{chunk-83g4h38e.js → chunk-6wd36w21.js} +1 -0
  8. package/dist/shared/{chunk-h89w580h.js → chunk-afawz764.js} +1 -1
  9. package/dist/shared/{chunk-nn9v1zmk.js → chunk-b0qqqk03.js} +86 -21
  10. package/dist/shared/{chunk-1wby7nex.js → chunk-dhehvmj0.js} +161 -9
  11. package/dist/shared/{chunk-wymw818z.js → chunk-fkbgbf3n.js} +48 -9
  12. package/dist/shared/{chunk-5dbq8jp9.js → chunk-j09yyh34.js} +72 -6
  13. package/dist/shared/chunk-mtsvrj9e.js +23 -0
  14. package/dist/shared/chunk-pnv25zep.js +7 -0
  15. package/dist/shared/{chunk-j6qyxfdc.js → chunk-vndfjfdy.js} +3 -3
  16. package/dist/src/auth/public.d.ts +56 -9
  17. package/dist/src/auth/public.js +189 -13
  18. package/dist/src/css/public.d.ts +110 -2
  19. package/dist/src/css/public.js +8 -4
  20. package/dist/src/form/public.d.ts +29 -6
  21. package/dist/src/form/public.js +2 -2
  22. package/dist/src/index.d.ts +284 -13
  23. package/dist/src/index.js +161 -14
  24. package/dist/src/internals.d.ts +141 -5
  25. package/dist/src/internals.js +17 -9
  26. package/dist/src/query/public.js +4 -3
  27. package/dist/src/router/public.d.ts +17 -4
  28. package/dist/src/router/public.js +17 -11
  29. package/dist/src/test/index.d.ts +5 -0
  30. package/dist/src/test/index.js +4 -3
  31. package/package.json +3 -3
  32. package/reactivity.json +1 -11
@@ -1,7 +1,15 @@
1
1
  import {
2
- __classList,
2
+ __classList
3
+ } from "./chunk-1rxa2fz4.js";
4
+ import {
5
+ RouterContext
6
+ } from "./chunk-mtsvrj9e.js";
7
+ import {
3
8
  __on
4
- } from "./chunk-dksg08fq.js";
9
+ } from "./chunk-pnv25zep.js";
10
+ import {
11
+ isBrowser
12
+ } from "./chunk-14eqne2a.js";
5
13
  import {
6
14
  __append,
7
15
  __element,
@@ -9,18 +17,19 @@ import {
9
17
  __exitChildren,
10
18
  __staticText,
11
19
  getIsHydrating
12
- } from "./chunk-j6qyxfdc.js";
20
+ } from "./chunk-vndfjfdy.js";
13
21
  import {
14
22
  _tryOnCleanup,
15
23
  createContext,
16
24
  domEffect,
25
+ getSSRContext,
17
26
  popScope,
18
27
  pushScope,
19
28
  runCleanups,
20
29
  signal,
21
30
  untrack,
22
31
  useContext
23
- } from "./chunk-8hsz5y4a.js";
32
+ } from "./chunk-4fwcwxn6.js";
24
33
 
25
34
  // src/router/link.ts
26
35
  var DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:"];
@@ -65,8 +74,10 @@ function createLink(currentPath, navigate, factoryOptions) {
65
74
  } else {
66
75
  __append(el, result);
67
76
  }
68
- } else {
77
+ } else if (typeof children === "string") {
69
78
  __append(el, __staticText(children));
79
+ } else {
80
+ __append(el, children);
70
81
  }
71
82
  __exitChildren();
72
83
  if (activeClass) {
@@ -88,22 +99,49 @@ function createLink(currentPath, navigate, factoryOptions) {
88
99
  return el;
89
100
  };
90
101
  }
91
-
92
- // src/router/router-context.ts
93
- var RouterContext = createContext(undefined, "@vertz/ui::RouterContext");
94
- function useRouter() {
102
+ function Link({ href, children, activeClass, className }) {
95
103
  const router = useContext(RouterContext);
96
104
  if (!router) {
97
- throw new Error("useRouter() must be called within RouterContext.Provider");
105
+ throw new Error("Link must be used within a RouterContext.Provider (via createRouter)");
98
106
  }
99
- return router;
100
- }
101
- function useParams() {
102
- const router = useContext(RouterContext);
103
- if (!router) {
104
- throw new Error("useParams() must be called within RouterContext.Provider");
107
+ const safeHref = isSafeUrl(href) ? href : "#";
108
+ const handleClick = (event) => {
109
+ const mouseEvent = event;
110
+ if (mouseEvent.ctrlKey || mouseEvent.metaKey || mouseEvent.shiftKey || mouseEvent.altKey) {
111
+ return;
112
+ }
113
+ mouseEvent.preventDefault();
114
+ router.navigate({ to: safeHref });
115
+ };
116
+ const props = { href: safeHref };
117
+ if (className) {
118
+ props.class = className;
105
119
  }
106
- return router.current?.parsedParams ?? router.current?.params ?? {};
120
+ const el = __element("a", props);
121
+ __on(el, "click", handleClick);
122
+ __enterChildren(el);
123
+ if (typeof children === "function") {
124
+ const result = children();
125
+ if (typeof result === "string") {
126
+ __append(el, __staticText(result));
127
+ } else {
128
+ __append(el, result);
129
+ }
130
+ } else if (typeof children === "string") {
131
+ __append(el, __staticText(children));
132
+ } else {
133
+ __append(el, children);
134
+ }
135
+ __exitChildren();
136
+ if (activeClass) {
137
+ __classList(el, {
138
+ [activeClass]: () => {
139
+ router.current;
140
+ return isBrowser() ? window.location.pathname === safeHref : false;
141
+ }
142
+ });
143
+ }
144
+ return el;
107
145
  }
108
146
 
109
147
  // src/router/outlet.ts
@@ -249,17 +287,44 @@ function buildLevels(matched) {
249
287
  }));
250
288
  }
251
289
  function buildInsideOutFactory(matched, levels, startAt, router) {
252
- let factory = matched[matched.length - 1].route.component;
290
+ const ssrCtx = getSSRContext();
291
+ const wrapForSSR = (route, routeIndex) => {
292
+ if (!ssrCtx)
293
+ return route.component;
294
+ const resolved = ssrCtx.resolvedComponents?.get(route);
295
+ if (resolved)
296
+ return resolved;
297
+ return () => {
298
+ const result = route.component();
299
+ if (result instanceof Promise) {
300
+ if (!ssrCtx.pendingRouteComponents) {
301
+ ssrCtx.pendingRouteComponents = new Map;
302
+ }
303
+ ssrCtx.pendingRouteComponents.set(route, result);
304
+ for (let j = routeIndex + 1;j < matched.length; j++) {
305
+ const childRoute = matched[j].route;
306
+ if (!ssrCtx.pendingRouteComponents.has(childRoute)) {
307
+ const childResult = childRoute.component();
308
+ if (childResult instanceof Promise) {
309
+ ssrCtx.pendingRouteComponents.set(childRoute, childResult);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return result;
315
+ };
316
+ };
317
+ let factory = wrapForSSR(matched[matched.length - 1].route, matched.length - 1);
253
318
  for (let i = matched.length - 2;i >= startAt; i--) {
254
319
  const level = levels[i];
255
320
  const childFactory = factory;
256
321
  level.childSignal.value = childFactory;
257
- const parentRoute = level.route;
322
+ const parentComponent = wrapForSSR(level.route, i);
258
323
  const cs = level.childSignal;
259
324
  factory = () => {
260
325
  let result;
261
326
  OutletContext.Provider({ childComponent: cs, router }, () => {
262
- result = parentRoute.component();
327
+ result = parentComponent();
263
328
  });
264
329
  return result;
265
330
  };
@@ -285,4 +350,4 @@ function useSearchParams(searchSignal) {
285
350
  return searchSignal.value;
286
351
  }
287
352
 
288
- export { createLink, RouterContext, useRouter, useParams, OutletContext, Outlet, RouterView, parseSearchParams, useSearchParams };
353
+ export { createLink, Link, OutletContext, Outlet, RouterView, parseSearchParams, useSearchParams };
@@ -3,10 +3,10 @@ import {
3
3
  __element,
4
4
  __enterChildren,
5
5
  __exitChildren
6
- } from "./chunk-j6qyxfdc.js";
6
+ } from "./chunk-vndfjfdy.js";
7
7
  import {
8
8
  getSSRContext
9
- } from "./chunk-8hsz5y4a.js";
9
+ } from "./chunk-4fwcwxn6.js";
10
10
 
11
11
  // src/component/children.ts
12
12
  var MAX_RESOLVE_DEPTH = 100;
@@ -757,6 +757,143 @@ ${props}
757
757
  }`;
758
758
  }
759
759
 
760
+ // src/css/sanitize.ts
761
+ function sanitizeCssValue(value) {
762
+ return value.replace(/[;{}<>']/g, "").replace(/url\s*\(/gi, "").replace(/expression\s*\(/gi, "").replace(/@import/gi, "");
763
+ }
764
+ function escapeHtmlAttr(value) {
765
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&#39;");
766
+ }
767
+
768
+ // src/css/font.ts
769
+ function font(family, options) {
770
+ return {
771
+ __brand: "FontDescriptor",
772
+ family,
773
+ weight: String(options.weight),
774
+ style: options.style ?? "normal",
775
+ display: options.display ?? "swap",
776
+ src: options.src,
777
+ fallback: options.fallback ?? [],
778
+ subsets: options.subsets ?? ["latin"],
779
+ unicodeRange: options.unicodeRange,
780
+ adjustFontFallback: options.adjustFontFallback ?? true
781
+ };
782
+ }
783
+ function toCssWeight(weight) {
784
+ return sanitizeCssValue(weight).replace("..", " ");
785
+ }
786
+ function buildFontFace(family, src, weight, style, display, unicodeRange) {
787
+ const safeFamily = sanitizeCssValue(family);
788
+ const safeSrc = sanitizeCssValue(src);
789
+ const safeStyle = sanitizeCssValue(style);
790
+ const safeDisplay = sanitizeCssValue(display);
791
+ const lines = [
792
+ "@font-face {",
793
+ ` font-family: '${safeFamily}';`,
794
+ ` font-style: ${safeStyle};`,
795
+ ` font-weight: ${toCssWeight(weight)};`,
796
+ ` font-display: ${safeDisplay};`,
797
+ ` src: url(${safeSrc}) format('woff2');`
798
+ ];
799
+ if (unicodeRange) {
800
+ lines.push(` unicode-range: ${sanitizeCssValue(unicodeRange)};`);
801
+ }
802
+ lines.push("}");
803
+ return lines.join(`
804
+ `);
805
+ }
806
+ var PERCENT_RE = /^\d+\.\d+%$/;
807
+ function sanitizeMetricValue(value) {
808
+ if (!PERCENT_RE.test(value)) {
809
+ throw new Error(`Invalid font metric override value: "${value}"`);
810
+ }
811
+ return value;
812
+ }
813
+ function buildFallbackFontFace(family, metrics) {
814
+ const safeFamily = sanitizeCssValue(family);
815
+ const lines = [
816
+ "@font-face {",
817
+ ` font-family: '${safeFamily} Fallback';`,
818
+ ` src: local('${metrics.fallbackFont}');`,
819
+ ` ascent-override: ${sanitizeMetricValue(metrics.ascentOverride)};`,
820
+ ` descent-override: ${sanitizeMetricValue(metrics.descentOverride)};`,
821
+ ` line-gap-override: ${sanitizeMetricValue(metrics.lineGapOverride)};`,
822
+ ` size-adjust: ${sanitizeMetricValue(metrics.sizeAdjust)};`,
823
+ "}"
824
+ ];
825
+ return lines.join(`
826
+ `);
827
+ }
828
+ function validateWoff2Src(path) {
829
+ if (!path.toLowerCase().endsWith(".woff2")) {
830
+ throw new Error(`Font src "${path}" is not a .woff2 file. Only woff2 format is currently supported.`);
831
+ }
832
+ }
833
+ function compileFonts(fonts, options) {
834
+ const fontFaces = [];
835
+ const cssVars = [];
836
+ const preloadPaths = [];
837
+ const fallbackMetrics = options?.fallbackMetrics;
838
+ for (const [key, descriptor] of Object.entries(fonts)) {
839
+ if (!/^[a-zA-Z0-9-]+$/.test(key)) {
840
+ throw new Error(`Font key "${key}" contains invalid CSS identifier characters. Use only [a-zA-Z0-9-].`);
841
+ }
842
+ const { family, weight, style, display, src, fallback, unicodeRange } = descriptor;
843
+ const adjustFontFallback = descriptor.adjustFontFallback ?? true;
844
+ const metrics = fallbackMetrics?.[key];
845
+ const shouldGenerateFallback = metrics && src && adjustFontFallback !== false;
846
+ const safeFamily = sanitizeCssValue(family);
847
+ const safeFallbacks = fallback.map(sanitizeCssValue);
848
+ const fallbackFontName = shouldGenerateFallback ? `'${safeFamily} Fallback'` : undefined;
849
+ const familyParts = [
850
+ `'${safeFamily}'`,
851
+ ...fallbackFontName ? [fallbackFontName] : [],
852
+ ...safeFallbacks
853
+ ];
854
+ const familyValue = familyParts.join(", ");
855
+ cssVars.push(` --font-${sanitizeCssValue(key)}: ${familyValue};`);
856
+ if (!src)
857
+ continue;
858
+ if (typeof src === "string") {
859
+ validateWoff2Src(src);
860
+ fontFaces.push(buildFontFace(family, src, weight, style, display, unicodeRange));
861
+ preloadPaths.push(src);
862
+ } else {
863
+ for (const entry of src) {
864
+ validateWoff2Src(entry.path);
865
+ const entryWeight = entry.weight != null ? String(entry.weight) : weight;
866
+ const entryStyle = entry.style ?? style;
867
+ fontFaces.push(buildFontFace(family, entry.path, entryWeight, entryStyle, display, unicodeRange));
868
+ }
869
+ const first = src[0];
870
+ if (first) {
871
+ preloadPaths.push(first.path);
872
+ }
873
+ }
874
+ if (shouldGenerateFallback) {
875
+ fontFaces.push(buildFallbackFontFace(family, metrics));
876
+ }
877
+ }
878
+ const fontFaceCss = fontFaces.join(`
879
+
880
+ `);
881
+ const cssVarLines = cssVars;
882
+ const cssVarsCss = cssVars.length > 0 ? `:root {
883
+ ${cssVars.join(`
884
+ `)}
885
+ }` : "";
886
+ const preloadTags = preloadPaths.map((p) => `<link rel="preload" href="${escapeHtmlAttr(p)}" as="font" type="font/woff2" crossorigin>`).join(`
887
+ `);
888
+ const preloadItems = preloadPaths.map((href) => ({
889
+ href,
890
+ as: "font",
891
+ type: "font/woff2",
892
+ crossorigin: true
893
+ }));
894
+ return { fontFaceCss, cssVarsCss, cssVarLines, preloadTags, preloadItems };
895
+ }
896
+
760
897
  // src/css/global-css.ts
761
898
  function globalCss(input) {
762
899
  const rules = [];
@@ -810,16 +947,14 @@ class InlineStyleError extends Error {
810
947
  }
811
948
 
812
949
  // src/css/theme.ts
813
- function sanitizeCssValue(value) {
814
- return value.replace(/[;{}]/g, "").replace(/url\s*\(/gi, "").replace(/expression\s*\(/gi, "").replace(/@import/gi, "");
815
- }
816
950
  function defineTheme(input) {
817
951
  return {
818
952
  colors: input.colors,
819
- spacing: input.spacing
953
+ spacing: input.spacing,
954
+ fonts: input.fonts
820
955
  };
821
956
  }
822
- function compileTheme(theme) {
957
+ function compileTheme(theme, options) {
823
958
  const rootVars = [];
824
959
  const darkVars = [];
825
960
  const tokenPaths = [];
@@ -864,7 +999,22 @@ function compileTheme(theme) {
864
999
  tokenPaths.push(`spacing.${name}`);
865
1000
  }
866
1001
  }
1002
+ let fontFaceCss = "";
1003
+ let preloadTags = "";
1004
+ let preloadItems = [];
1005
+ if (theme.fonts) {
1006
+ const compiled = compileFonts(theme.fonts, {
1007
+ fallbackMetrics: options?.fallbackMetrics
1008
+ });
1009
+ fontFaceCss = compiled.fontFaceCss;
1010
+ preloadTags = compiled.preloadTags;
1011
+ preloadItems = compiled.preloadItems;
1012
+ rootVars.push(...compiled.cssVarLines);
1013
+ }
867
1014
  const blocks = [];
1015
+ if (fontFaceCss) {
1016
+ blocks.push(fontFaceCss);
1017
+ }
868
1018
  if (rootVars.length > 0) {
869
1019
  blocks.push(`:root {
870
1020
  ${rootVars.join(`
@@ -880,7 +1030,9 @@ ${darkVars.join(`
880
1030
  return {
881
1031
  css: blocks.join(`
882
1032
  `),
883
- tokens: tokenPaths
1033
+ tokens: tokenPaths,
1034
+ preloadTags,
1035
+ preloadItems
884
1036
  };
885
1037
  }
886
1038
 
@@ -1017,4 +1169,4 @@ function variants(config) {
1017
1169
  return fn;
1018
1170
  }
1019
1171
 
1020
- export { resolveChildren, children, PROPERTY_MAP, KEYWORD_MAP, DISPLAY_MAP, SPACING_SCALE, RADIUS_SCALE, SHADOW_SCALE, FONT_SIZE_SCALE, FONT_WEIGHT_SCALE, LINE_HEIGHT_SCALE, ALIGNMENT_MAP, SIZE_KEYWORDS, HEIGHT_AXIS_PROPERTIES, COLOR_NAMESPACES, CSS_COLOR_KEYWORDS, CONTENT_MAP, PSEUDO_PREFIXES, PSEUDO_MAP, injectCSS, resetInjectedStyles, getInjectedCSS, css, globalCss, s, defineTheme, compileTheme, ThemeProvider, variants };
1172
+ export { resolveChildren, children, PROPERTY_MAP, KEYWORD_MAP, DISPLAY_MAP, SPACING_SCALE, RADIUS_SCALE, SHADOW_SCALE, FONT_SIZE_SCALE, FONT_WEIGHT_SCALE, LINE_HEIGHT_SCALE, ALIGNMENT_MAP, SIZE_KEYWORDS, HEIGHT_AXIS_PROPERTIES, COLOR_NAMESPACES, CSS_COLOR_KEYWORDS, CONTENT_MAP, PSEUDO_PREFIXES, PSEUDO_MAP, injectCSS, resetInjectedStyles, getInjectedCSS, css, font, compileFonts, globalCss, s, defineTheme, compileTheme, ThemeProvider, variants };
@@ -1,14 +1,17 @@
1
1
  import {
2
2
  executeLoaders,
3
3
  matchRoute
4
- } from "./chunk-83g4h38e.js";
4
+ } from "./chunk-6wd36w21.js";
5
5
  import {
6
6
  prefetchNavData
7
7
  } from "./chunk-jrtrk5z4.js";
8
+ import {
9
+ isBrowser
10
+ } from "./chunk-14eqne2a.js";
8
11
  import {
9
12
  getSSRContext,
10
13
  signal
11
- } from "./chunk-8hsz5y4a.js";
14
+ } from "./chunk-4fwcwxn6.js";
12
15
 
13
16
  // src/router/navigate.ts
14
17
  var DEFAULT_NAV_THRESHOLD_MS = 500;
@@ -59,24 +62,34 @@ function buildSearch(search) {
59
62
  function buildNavigationUrl(to, options) {
60
63
  return `${interpolatePath(to, options?.params)}${buildSearch(options?.search)}`;
61
64
  }
62
- function createRouter(routes, initialUrl, options) {
65
+ function createRouter(routes, initialUrlOrOptions, maybeOptions) {
66
+ const initialUrl = typeof initialUrlOrOptions === "string" ? initialUrlOrOptions : undefined;
67
+ const options = typeof initialUrlOrOptions === "object" ? initialUrlOrOptions : maybeOptions;
63
68
  const ssrCtx = getSSRContext();
64
- const isSSR = ssrCtx !== undefined;
65
- if (isSSR || typeof window === "undefined") {
69
+ if (!isBrowser()) {
70
+ let registerRoutesForDiscovery = function(ctx) {
71
+ if (!ctx.discoveredRoutes) {
72
+ ctx.discoveredRoutes = collectRoutePatterns(routes);
73
+ }
74
+ };
66
75
  const ssrUrl = initialUrl ?? ssrCtx?.url ?? "/";
67
76
  const fallbackMatch = matchRoute(routes, ssrUrl);
68
77
  return {
69
78
  current: {
70
79
  get value() {
71
80
  const ctx = getSSRContext();
72
- if (ctx)
81
+ if (ctx) {
82
+ registerRoutesForDiscovery(ctx);
73
83
  return matchRoute(routes, ctx.url);
84
+ }
74
85
  return fallbackMatch;
75
86
  },
76
87
  peek() {
77
88
  const ctx = getSSRContext();
78
- if (ctx)
89
+ if (ctx) {
90
+ registerRoutesForDiscovery(ctx);
79
91
  return matchRoute(routes, ctx.url);
92
+ }
80
93
  return fallbackMatch;
81
94
  },
82
95
  notify() {}
@@ -129,8 +142,12 @@ function createRouter(routes, initialUrl, options) {
129
142
  const current = {
130
143
  get value() {
131
144
  const ctx = getSSRContext();
132
- if (ctx)
145
+ if (ctx) {
146
+ if (!ctx.discoveredRoutes) {
147
+ ctx.discoveredRoutes = collectRoutePatterns(routes);
148
+ }
133
149
  return matchRoute(routes, ctx.url);
150
+ }
134
151
  return _current.value;
135
152
  },
136
153
  set value(v) {
@@ -138,8 +155,12 @@ function createRouter(routes, initialUrl, options) {
138
155
  },
139
156
  peek() {
140
157
  const ctx = getSSRContext();
141
- if (ctx)
158
+ if (ctx) {
159
+ if (!ctx.discoveredRoutes) {
160
+ ctx.discoveredRoutes = collectRoutePatterns(routes);
161
+ }
142
162
  return matchRoute(routes, ctx.url);
163
+ }
143
164
  return _current.peek();
144
165
  },
145
166
  notify() {
@@ -298,5 +319,23 @@ function createRouter(routes, initialUrl, options) {
298
319
  searchParams
299
320
  };
300
321
  }
322
+ function collectRoutePatterns(routes, prefix = "") {
323
+ const patterns = [];
324
+ for (const route of routes) {
325
+ const fullPattern = joinPatterns(prefix, route.pattern);
326
+ patterns.push(fullPattern);
327
+ if (route.children) {
328
+ patterns.push(...collectRoutePatterns(route.children, fullPattern));
329
+ }
330
+ }
331
+ return patterns;
332
+ }
333
+ function joinPatterns(parent, child) {
334
+ if (!parent || parent === "/")
335
+ return child;
336
+ if (child === "/")
337
+ return parent;
338
+ return `${parent.replace(/\/$/, "")}/${child.replace(/^\//, "")}`;
339
+ }
301
340
 
302
341
  export { createRouter };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  computed,
3
3
  signal
4
- } from "./chunk-8hsz5y4a.js";
4
+ } from "./chunk-4fwcwxn6.js";
5
5
 
6
6
  // src/form/field-state.ts
7
7
  function createFieldState(_name, initialValue) {
@@ -31,14 +31,40 @@ function createFieldState(_name, initialValue) {
31
31
  function formDataToObject(formData, options) {
32
32
  const result = {};
33
33
  const coerce = options?.coerce ?? false;
34
+ const nested = options?.nested ?? false;
34
35
  for (const [key, value] of formData.entries()) {
35
36
  if (typeof value !== "string") {
36
37
  continue;
37
38
  }
38
- result[key] = coerce ? coerceValue(value) : value;
39
+ const coerced = coerce ? coerceValue(value) : value;
40
+ if (nested && key.includes(".")) {
41
+ setNestedValue(result, key, coerced);
42
+ } else {
43
+ result[key] = coerced;
44
+ }
39
45
  }
40
46
  return result;
41
47
  }
48
+ var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
49
+ function setNestedValue(obj, dotPath, value) {
50
+ const segments = dotPath.split(".");
51
+ let current = obj;
52
+ for (let i = 0;i < segments.length - 1; i++) {
53
+ const segment = segments[i];
54
+ if (DANGEROUS_KEYS.has(segment))
55
+ return;
56
+ const nextSegment = segments[i + 1];
57
+ const isNextArray = /^\d+$/.test(nextSegment);
58
+ if (!(segment in current)) {
59
+ current[segment] = isNextArray ? [] : {};
60
+ }
61
+ current = current[segment];
62
+ }
63
+ const lastSegment = segments[segments.length - 1];
64
+ if (DANGEROUS_KEYS.has(lastSegment))
65
+ return;
66
+ current[lastSegment] = value;
67
+ }
42
68
  function coerceValue(value) {
43
69
  if (value === "true")
44
70
  return true;
@@ -79,8 +105,11 @@ function validate(schema, data) {
79
105
  }
80
106
 
81
107
  // src/form/form.ts
108
+ var FIELD_STATE_SIGNALS = new Set(["error", "dirty", "touched", "value"]);
109
+ var FIELD_STATE_METHODS = new Set(["setValue", "reset"]);
82
110
  function form(sdkMethod, options) {
83
111
  const fieldCache = new Map;
112
+ const chainProxyCache = new Map;
84
113
  const submitting = signal(false);
85
114
  const fieldGeneration = signal(0);
86
115
  const dirty = computed(() => {
@@ -99,20 +128,57 @@ function form(sdkMethod, options) {
99
128
  }
100
129
  return true;
101
130
  });
131
+ function resolveNestedInitial(dotPath) {
132
+ const initialObj = typeof options?.initial === "function" ? options.initial() : options?.initial;
133
+ if (!initialObj)
134
+ return;
135
+ const segments = dotPath.split(".");
136
+ let current = initialObj;
137
+ for (const segment of segments) {
138
+ if (current == null || typeof current !== "object")
139
+ return;
140
+ current = current[segment];
141
+ }
142
+ return current;
143
+ }
102
144
  function getOrCreateField(name) {
103
145
  let field = fieldCache.get(name);
104
146
  if (!field) {
105
- const initialObj = typeof options?.initial === "function" ? options.initial() : options?.initial;
106
- const initialValue = initialObj?.[name];
147
+ const initialValue = name.includes(".") ? resolveNestedInitial(name) : (() => {
148
+ const initialObj = typeof options?.initial === "function" ? options.initial() : options?.initial;
149
+ return initialObj?.[name];
150
+ })();
107
151
  field = createFieldState(name, initialValue);
108
152
  fieldCache.set(name, field);
109
153
  fieldGeneration.value++;
110
154
  }
111
155
  return field;
112
156
  }
157
+ function getOrCreateChainProxy(dotPath) {
158
+ let proxy = chainProxyCache.get(dotPath);
159
+ if (proxy)
160
+ return proxy;
161
+ proxy = new Proxy(Object.create(null), {
162
+ get(_target, prop) {
163
+ if (typeof prop === "string") {
164
+ if (FIELD_STATE_SIGNALS.has(prop)) {
165
+ return getOrCreateField(dotPath)[prop];
166
+ }
167
+ if (FIELD_STATE_METHODS.has(prop)) {
168
+ return getOrCreateField(dotPath)[prop];
169
+ }
170
+ const childPath = `${dotPath}.${prop}`;
171
+ return getOrCreateChainProxy(childPath);
172
+ }
173
+ return;
174
+ }
175
+ });
176
+ chainProxyCache.set(dotPath, proxy);
177
+ return proxy;
178
+ }
113
179
  const resolvedSchema = options?.schema ?? sdkMethod.meta?.bodySchema;
114
180
  async function submitPipeline(formData) {
115
- const data = formDataToObject(formData);
181
+ const data = formDataToObject(formData, { nested: true });
116
182
  if (resolvedSchema) {
117
183
  const result2 = validate(resolvedSchema, data);
118
184
  if (!result2.success) {
@@ -227,7 +293,7 @@ function form(sdkMethod, options) {
227
293
  if (knownProperties.has(prop)) {
228
294
  return target[prop];
229
295
  }
230
- return getOrCreateField(prop);
296
+ return getOrCreateChainProxy(prop);
231
297
  }
232
298
  return Reflect.get(target, prop, receiver);
233
299
  }
@@ -0,0 +1,23 @@
1
+ import {
2
+ createContext,
3
+ useContext
4
+ } from "./chunk-4fwcwxn6.js";
5
+
6
+ // src/router/router-context.ts
7
+ var RouterContext = createContext(undefined, "@vertz/ui::RouterContext");
8
+ function useRouter() {
9
+ const router = useContext(RouterContext);
10
+ if (!router) {
11
+ throw new Error("useRouter() must be called within RouterContext.Provider");
12
+ }
13
+ return router;
14
+ }
15
+ function useParams() {
16
+ const router = useContext(RouterContext);
17
+ if (!router) {
18
+ throw new Error("useParams() must be called within RouterContext.Provider");
19
+ }
20
+ return router.current?.parsedParams ?? router.current?.params ?? {};
21
+ }
22
+
23
+ export { RouterContext, useRouter, useParams };
@@ -0,0 +1,7 @@
1
+ // src/dom/events.ts
2
+ function __on(el, event, handler) {
3
+ el.addEventListener(event, handler);
4
+ return () => el.removeEventListener(event, handler);
5
+ }
6
+
7
+ export { __on };
@@ -6,10 +6,10 @@ import {
6
6
  import {
7
7
  getAdapter,
8
8
  isRenderNode
9
- } from "./chunk-h89w580h.js";
9
+ } from "./chunk-afawz764.js";
10
10
  import {
11
11
  domEffect
12
- } from "./chunk-8hsz5y4a.js";
12
+ } from "./chunk-4fwcwxn6.js";
13
13
 
14
14
  // src/hydrate/hydration-context.ts
15
15
  var isHydrating = false;
@@ -371,4 +371,4 @@ function __exitChildren() {
371
371
  }
372
372
  }
373
373
 
374
- export { startHydration, endHydration, getIsHydrating, claimText, claimComment, __text, __child, __insert, __element, __append, __staticText, __enterChildren, __exitChildren };
374
+ export { startHydration, endHydration, getIsHydrating, claimElement, claimText, claimComment, enterChildren, exitChildren, __text, __child, __insert, __element, __append, __staticText, __enterChildren, __exitChildren };