@bquery/bquery 1.7.0 → 1.8.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 (262) hide show
  1. package/README.md +760 -716
  2. package/dist/{a11y-C5QOVvRn.js → a11y-DVBCy09c.js} +3 -3
  3. package/dist/a11y-DVBCy09c.js.map +1 -0
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/component/library.d.ts.map +1 -1
  6. package/dist/{component-CuuTijA6.js → component-L3-JfOFz.js} +5 -5
  7. package/dist/component-L3-JfOFz.js.map +1 -0
  8. package/dist/component.es.mjs +1 -1
  9. package/dist/{config-BW35FKuA.js → config-DhT9auRm.js} +1 -1
  10. package/dist/{config-BW35FKuA.js.map → config-DhT9auRm.js.map} +1 -1
  11. package/dist/{constraints-3lV9yyBw.js → constraints-D5RHQLmP.js} +1 -1
  12. package/dist/constraints-D5RHQLmP.js.map +1 -0
  13. package/dist/core/collection.d.ts +86 -0
  14. package/dist/core/collection.d.ts.map +1 -1
  15. package/dist/core/element.d.ts +28 -0
  16. package/dist/core/element.d.ts.map +1 -1
  17. package/dist/core/shared.d.ts +6 -0
  18. package/dist/core/shared.d.ts.map +1 -1
  19. package/dist/core-DdtZHzsS.js +168 -0
  20. package/dist/core-DdtZHzsS.js.map +1 -0
  21. package/dist/{core-Cjl7GUu8.js → core-EMYSLzaT.js} +289 -259
  22. package/dist/core-EMYSLzaT.js.map +1 -0
  23. package/dist/core.es.mjs +48 -47
  24. package/dist/{custom-directives-7wAShnnd.js → custom-directives-Dr4C5lVV.js} +1 -1
  25. package/dist/custom-directives-Dr4C5lVV.js.map +1 -0
  26. package/dist/{devtools-D2fQLhDN.js → devtools-BhB2iDPT.js} +2 -2
  27. package/dist/devtools-BhB2iDPT.js.map +1 -0
  28. package/dist/devtools.es.mjs +1 -1
  29. package/dist/{dnd-B8EgyzaI.js → dnd-NwZBYh4l.js} +1 -1
  30. package/dist/dnd-NwZBYh4l.js.map +1 -0
  31. package/dist/dnd.es.mjs +1 -1
  32. package/dist/{env-NeVmr4Gf.js → env-CTdvLaH2.js} +1 -1
  33. package/dist/env-CTdvLaH2.js.map +1 -0
  34. package/dist/forms/create-form.d.ts.map +1 -1
  35. package/dist/forms/index.d.ts +3 -2
  36. package/dist/forms/index.d.ts.map +1 -1
  37. package/dist/forms/types.d.ts +46 -0
  38. package/dist/forms/types.d.ts.map +1 -1
  39. package/dist/forms/use-field.d.ts +34 -0
  40. package/dist/forms/use-field.d.ts.map +1 -0
  41. package/dist/forms/validators.d.ts +25 -0
  42. package/dist/forms/validators.d.ts.map +1 -1
  43. package/dist/forms-UcRHsYxC.js +227 -0
  44. package/dist/forms-UcRHsYxC.js.map +1 -0
  45. package/dist/forms.es.mjs +14 -12
  46. package/dist/full.d.ts +17 -26
  47. package/dist/full.d.ts.map +1 -1
  48. package/dist/full.es.mjs +206 -181
  49. package/dist/full.iife.js +33 -33
  50. package/dist/full.iife.js.map +1 -1
  51. package/dist/full.umd.js +33 -33
  52. package/dist/full.umd.js.map +1 -1
  53. package/dist/function-Cybd57JV.js +33 -0
  54. package/dist/function-Cybd57JV.js.map +1 -0
  55. package/dist/{i18n-BnnhTFOS.js → i18n-kuF6Ekj6.js} +3 -3
  56. package/dist/i18n-kuF6Ekj6.js.map +1 -0
  57. package/dist/i18n.es.mjs +1 -1
  58. package/dist/index.es.mjs +251 -228
  59. package/dist/media/breakpoints.d.ts.map +1 -1
  60. package/dist/media/types.d.ts +2 -2
  61. package/dist/media/types.d.ts.map +1 -1
  62. package/dist/{media-Di2Ta22s.js → media-i-fB5WxI.js} +3 -3
  63. package/dist/media-i-fB5WxI.js.map +1 -0
  64. package/dist/media.es.mjs +1 -1
  65. package/dist/{motion-qPj_TYGv.js → motion-BJsAuULb.js} +2 -2
  66. package/dist/motion-BJsAuULb.js.map +1 -0
  67. package/dist/motion.es.mjs +1 -1
  68. package/dist/{mount-SM07RUa6.js → mount-B4Y8bk8Z.js} +5 -5
  69. package/dist/mount-B4Y8bk8Z.js.map +1 -0
  70. package/dist/{platform-CPbCprb6.js → platform-Dw2gE3zI.js} +3 -3
  71. package/dist/{platform-CPbCprb6.js.map → platform-Dw2gE3zI.js.map} +1 -1
  72. package/dist/platform.es.mjs +2 -2
  73. package/dist/plugin/registry.d.ts.map +1 -1
  74. package/dist/{plugin-cPoOHFLY.js → plugin-C2WuC8SF.js} +20 -18
  75. package/dist/plugin-C2WuC8SF.js.map +1 -0
  76. package/dist/plugin.es.mjs +1 -1
  77. package/dist/reactive/async-data.d.ts +28 -3
  78. package/dist/reactive/async-data.d.ts.map +1 -1
  79. package/dist/reactive/computed.d.ts +3 -0
  80. package/dist/reactive/computed.d.ts.map +1 -1
  81. package/dist/reactive/effect.d.ts +3 -0
  82. package/dist/reactive/effect.d.ts.map +1 -1
  83. package/dist/reactive/http.d.ts +194 -0
  84. package/dist/reactive/http.d.ts.map +1 -0
  85. package/dist/reactive/index.d.ts +2 -2
  86. package/dist/reactive/index.d.ts.map +1 -1
  87. package/dist/reactive/pagination.d.ts +126 -0
  88. package/dist/reactive/pagination.d.ts.map +1 -0
  89. package/dist/reactive/polling.d.ts +55 -0
  90. package/dist/reactive/polling.d.ts.map +1 -0
  91. package/dist/reactive/readonly.d.ts +20 -1
  92. package/dist/reactive/readonly.d.ts.map +1 -1
  93. package/dist/reactive/rest.d.ts +293 -0
  94. package/dist/reactive/rest.d.ts.map +1 -0
  95. package/dist/reactive/scope.d.ts +140 -0
  96. package/dist/reactive/scope.d.ts.map +1 -0
  97. package/dist/reactive/signal.d.ts +16 -2
  98. package/dist/reactive/signal.d.ts.map +1 -1
  99. package/dist/reactive/to-value.d.ts +57 -0
  100. package/dist/reactive/to-value.d.ts.map +1 -0
  101. package/dist/reactive/websocket.d.ts +285 -0
  102. package/dist/reactive/websocket.d.ts.map +1 -0
  103. package/dist/reactive-DwkhUJfP.js +1148 -0
  104. package/dist/reactive-DwkhUJfP.js.map +1 -0
  105. package/dist/reactive.es.mjs +38 -19
  106. package/dist/{registry-CWf368tT.js → registry-B08iilIh.js} +1 -1
  107. package/dist/{registry-CWf368tT.js.map → registry-B08iilIh.js.map} +1 -1
  108. package/dist/router/constraints.d.ts.map +1 -1
  109. package/dist/router/index.d.ts +1 -1
  110. package/dist/router/index.d.ts.map +1 -1
  111. package/dist/router/router.d.ts.map +1 -1
  112. package/dist/router/state.d.ts +25 -2
  113. package/dist/router/state.d.ts.map +1 -1
  114. package/dist/router-CQikC9Ed.js +492 -0
  115. package/dist/router-CQikC9Ed.js.map +1 -0
  116. package/dist/router.es.mjs +9 -8
  117. package/dist/ssr/hydrate.d.ts.map +1 -1
  118. package/dist/{ssr-B2qd_WBB.js → ssr-_dAcGdzu.js} +4 -4
  119. package/dist/ssr-_dAcGdzu.js.map +1 -0
  120. package/dist/ssr.es.mjs +1 -1
  121. package/dist/store/persisted.d.ts.map +1 -1
  122. package/dist/{store-DWpyH6p5.js → store-Cb3gPRve.js} +7 -7
  123. package/dist/store-Cb3gPRve.js.map +1 -0
  124. package/dist/store.es.mjs +2 -2
  125. package/dist/storybook.es.mjs.map +1 -1
  126. package/dist/{testing-CsqjNUyy.js → testing-C5Sjfsna.js} +8 -8
  127. package/dist/testing-C5Sjfsna.js.map +1 -0
  128. package/dist/testing.es.mjs +1 -1
  129. package/dist/{type-guards-Do9DWgNp.js → type-guards-BMX2c0LP.js} +1 -1
  130. package/dist/{type-guards-Do9DWgNp.js.map → type-guards-BMX2c0LP.js.map} +1 -1
  131. package/dist/untrack-D0fnO5k2.js +36 -0
  132. package/dist/untrack-D0fnO5k2.js.map +1 -0
  133. package/dist/view/custom-directives.d.ts.map +1 -1
  134. package/dist/view.es.mjs +4 -4
  135. package/package.json +177 -177
  136. package/src/a11y/announce.ts +131 -131
  137. package/src/a11y/audit.ts +314 -314
  138. package/src/a11y/index.ts +68 -68
  139. package/src/a11y/media-preferences.ts +255 -255
  140. package/src/a11y/roving-tab-index.ts +164 -164
  141. package/src/a11y/skip-link.ts +255 -255
  142. package/src/a11y/trap-focus.ts +184 -184
  143. package/src/a11y/types.ts +183 -183
  144. package/src/component/component.ts +599 -599
  145. package/src/component/html.ts +153 -153
  146. package/src/component/index.ts +52 -52
  147. package/src/component/library.ts +540 -542
  148. package/src/component/scope.ts +212 -212
  149. package/src/component/types.ts +310 -310
  150. package/src/core/collection.ts +876 -707
  151. package/src/core/element.ts +1015 -981
  152. package/src/core/env.ts +60 -60
  153. package/src/core/index.ts +49 -49
  154. package/src/core/shared.ts +77 -62
  155. package/src/core/utils/index.ts +148 -148
  156. package/src/devtools/devtools.ts +410 -410
  157. package/src/devtools/index.ts +48 -48
  158. package/src/devtools/types.ts +104 -104
  159. package/src/dnd/draggable.ts +296 -296
  160. package/src/dnd/droppable.ts +228 -228
  161. package/src/dnd/index.ts +62 -62
  162. package/src/dnd/sortable.ts +307 -307
  163. package/src/dnd/types.ts +293 -293
  164. package/src/forms/create-form.ts +320 -278
  165. package/src/forms/index.ts +70 -65
  166. package/src/forms/types.ts +203 -154
  167. package/src/forms/use-field.ts +231 -0
  168. package/src/forms/validators.ts +294 -265
  169. package/src/full.ts +554 -480
  170. package/src/i18n/formatting.ts +67 -67
  171. package/src/i18n/i18n.ts +200 -200
  172. package/src/i18n/index.ts +67 -67
  173. package/src/i18n/translate.ts +182 -182
  174. package/src/i18n/types.ts +171 -171
  175. package/src/index.ts +108 -108
  176. package/src/media/battery.ts +116 -116
  177. package/src/media/breakpoints.ts +129 -131
  178. package/src/media/clipboard.ts +80 -80
  179. package/src/media/device-sensors.ts +158 -158
  180. package/src/media/geolocation.ts +119 -119
  181. package/src/media/index.ts +76 -76
  182. package/src/media/media-query.ts +92 -92
  183. package/src/media/network.ts +115 -115
  184. package/src/media/types.ts +177 -177
  185. package/src/media/viewport.ts +84 -84
  186. package/src/motion/index.ts +57 -57
  187. package/src/motion/morph.ts +151 -151
  188. package/src/motion/parallax.ts +120 -120
  189. package/src/motion/reduced-motion.ts +66 -66
  190. package/src/motion/types.ts +271 -271
  191. package/src/motion/typewriter.ts +164 -164
  192. package/src/plugin/index.ts +37 -37
  193. package/src/plugin/registry.ts +284 -269
  194. package/src/plugin/types.ts +137 -137
  195. package/src/reactive/async-data.ts +250 -29
  196. package/src/reactive/computed.ts +144 -130
  197. package/src/reactive/effect.ts +29 -6
  198. package/src/reactive/http.ts +790 -0
  199. package/src/reactive/index.ts +60 -0
  200. package/src/reactive/pagination.ts +317 -0
  201. package/src/reactive/polling.ts +179 -0
  202. package/src/reactive/readonly.ts +52 -8
  203. package/src/reactive/rest.ts +859 -0
  204. package/src/reactive/scope.ts +276 -0
  205. package/src/reactive/signal.ts +61 -1
  206. package/src/reactive/to-value.ts +71 -0
  207. package/src/reactive/websocket.ts +849 -0
  208. package/src/router/bq-link.ts +279 -279
  209. package/src/router/constraints.ts +204 -201
  210. package/src/router/index.ts +49 -49
  211. package/src/router/match.ts +312 -312
  212. package/src/router/path-pattern.ts +52 -52
  213. package/src/router/query.ts +38 -38
  214. package/src/router/router.ts +421 -402
  215. package/src/router/state.ts +51 -3
  216. package/src/router/types.ts +139 -139
  217. package/src/router/use-route.ts +68 -68
  218. package/src/router/utils.ts +157 -157
  219. package/src/security/index.ts +12 -12
  220. package/src/ssr/hydrate.ts +84 -82
  221. package/src/ssr/index.ts +70 -70
  222. package/src/ssr/render.ts +508 -508
  223. package/src/ssr/serialize.ts +296 -296
  224. package/src/ssr/types.ts +81 -81
  225. package/src/store/create-store.ts +467 -467
  226. package/src/store/index.ts +27 -27
  227. package/src/store/persisted.ts +245 -249
  228. package/src/store/types.ts +247 -247
  229. package/src/store/utils.ts +135 -135
  230. package/src/storybook/index.ts +480 -480
  231. package/src/testing/index.ts +42 -42
  232. package/src/testing/testing.ts +593 -593
  233. package/src/testing/types.ts +170 -170
  234. package/src/view/custom-directives.ts +28 -30
  235. package/src/view/evaluate.ts +292 -292
  236. package/src/view/process.ts +108 -108
  237. package/dist/a11y-C5QOVvRn.js.map +0 -1
  238. package/dist/component-CuuTijA6.js.map +0 -1
  239. package/dist/constraints-3lV9yyBw.js.map +0 -1
  240. package/dist/core-Cjl7GUu8.js.map +0 -1
  241. package/dist/core-DnlyjbF2.js +0 -112
  242. package/dist/core-DnlyjbF2.js.map +0 -1
  243. package/dist/custom-directives-7wAShnnd.js.map +0 -1
  244. package/dist/devtools-D2fQLhDN.js.map +0 -1
  245. package/dist/dnd-B8EgyzaI.js.map +0 -1
  246. package/dist/env-NeVmr4Gf.js.map +0 -1
  247. package/dist/forms-C3yovgH9.js +0 -141
  248. package/dist/forms-C3yovgH9.js.map +0 -1
  249. package/dist/i18n-BnnhTFOS.js.map +0 -1
  250. package/dist/media-Di2Ta22s.js.map +0 -1
  251. package/dist/motion-qPj_TYGv.js.map +0 -1
  252. package/dist/mount-SM07RUa6.js.map +0 -1
  253. package/dist/plugin-cPoOHFLY.js.map +0 -1
  254. package/dist/reactive-Cfv0RK6x.js +0 -233
  255. package/dist/reactive-Cfv0RK6x.js.map +0 -1
  256. package/dist/router-BrthaP_z.js +0 -473
  257. package/dist/router-BrthaP_z.js.map +0 -1
  258. package/dist/ssr-B2qd_WBB.js.map +0 -1
  259. package/dist/store-DWpyH6p5.js.map +0 -1
  260. package/dist/testing-CsqjNUyy.js.map +0 -1
  261. package/dist/untrack-DJVQQ2WM.js +0 -33
  262. package/dist/untrack-DJVQQ2WM.js.map +0 -1
@@ -1,157 +1,157 @@
1
- /**
2
- * Router utilities.
3
- * @module bquery/router
4
- */
5
-
6
- import { computed, type ReadonlySignal } from '../reactive/index';
7
- import { getRouteConstraintRegex } from './constraints';
8
- import { isParamChar, isParamStart, readConstraint } from './path-pattern';
9
- import { getActiveRouter, routeSignal } from './state';
10
- import type { RouteDefinition } from './types';
11
-
12
- // ============================================================================
13
- // Utilities
14
- // ============================================================================
15
-
16
- /**
17
- * Flattens nested routes into a single array with full paths.
18
- * Does NOT include the router base - base is only for browser history.
19
- * @internal
20
- */
21
- export const flattenRoutes = (routes: RouteDefinition[], parentPath = ''): RouteDefinition[] => {
22
- const result: RouteDefinition[] = [];
23
-
24
- for (const route of routes) {
25
- const fullPath = route.path === '*' ? '*' : `${parentPath}${route.path}`.replace(/\/+/g, '/');
26
-
27
- result.push({
28
- ...route,
29
- path: fullPath,
30
- });
31
-
32
- if (route.children) {
33
- result.push(...flattenRoutes(route.children, fullPath));
34
- }
35
- }
36
-
37
- return result;
38
- };
39
-
40
- /**
41
- * Resolves a route by name and params.
42
- *
43
- * @param name - The route name
44
- * @param params - Route params to interpolate
45
- * @returns The resolved path
46
- * @throws {Error} If no router is initialized, the route name is unknown,
47
- * a required path param is missing from `params`, a param value does not satisfy
48
- * its route regex constraint, or a route param constraint has invalid syntax
49
- *
50
- * @example
51
- * ```ts
52
- * import { resolve } from 'bquery/router';
53
- *
54
- * const path = resolve('user', { id: '42' });
55
- * // Returns '/user/42' if route is defined as { name: 'user', path: '/user/:id' }
56
- * ```
57
- */
58
- export const resolve = (name: string, params: Record<string, string> = {}): string => {
59
- const activeRouter = getActiveRouter();
60
- if (!activeRouter) {
61
- throw new Error('bQuery router: No router initialized.');
62
- }
63
-
64
- const route = activeRouter.routes.find((r) => r.name === name);
65
- if (!route) {
66
- throw new Error(`bQuery router: Route "${name}" not found.`);
67
- }
68
-
69
- let path = '';
70
- for (let i = 0; i < route.path.length; ) {
71
- if (route.path[i] === ':' && isParamStart(route.path[i + 1])) {
72
- let nameEnd = i + 2;
73
- while (nameEnd < route.path.length && isParamChar(route.path[nameEnd])) {
74
- nameEnd++;
75
- }
76
-
77
- let nextIndex = nameEnd;
78
- let constraint: string | null = null;
79
- if (route.path[nameEnd] === '(') {
80
- const parsedConstraint = readConstraint(route.path, nameEnd);
81
- if (!parsedConstraint) {
82
- throw new Error(
83
- `bQuery router: Invalid constraint syntax in path "${route.path}" for route "${name}".`
84
- );
85
- }
86
- constraint = parsedConstraint.constraint;
87
- nextIndex = parsedConstraint.endIndex;
88
- }
89
-
90
- const key = route.path.slice(i + 1, nameEnd);
91
- const value = params[key];
92
- if (value === undefined) {
93
- throw new Error(`bQuery router: Missing required param "${key}" for route "${name}".`);
94
- }
95
- if (constraint && !getRouteConstraintRegex(constraint).test(value)) {
96
- throw new Error(
97
- `bQuery router: Param "${key}" with value "${value}" does not satisfy the route constraint "${constraint}" for route "${name}".`
98
- );
99
- }
100
-
101
- path += encodeURIComponent(value);
102
- i = nextIndex;
103
- continue;
104
- }
105
-
106
- path += route.path[i];
107
- i++;
108
- }
109
-
110
- return path;
111
- };
112
-
113
- /**
114
- * Checks if a path matches the current route.
115
- *
116
- * @param path - Path to check
117
- * @param exact - Whether to match exactly (default: false)
118
- * @returns True if the path matches
119
- *
120
- * @example
121
- * ```ts
122
- * import { isActive } from 'bquery/router';
123
- *
124
- * if (isActive('/dashboard')) {
125
- * // Highlight nav item
126
- * }
127
- * ```
128
- */
129
- export const isActive = (path: string, exact = false): boolean => {
130
- const current = routeSignal.value.path;
131
- return exact ? current === path : current.startsWith(path);
132
- };
133
-
134
- /**
135
- * Creates a computed signal that checks if a path is active.
136
- *
137
- * @param path - Path to check
138
- * @param exact - Whether to match exactly
139
- * @returns A reactive signal
140
- *
141
- * @example
142
- * ```ts
143
- * import { isActiveSignal } from 'bquery/router';
144
- * import { effect } from 'bquery/reactive';
145
- *
146
- * const dashboardActive = isActiveSignal('/dashboard');
147
- * effect(() => {
148
- * navItem.classList.toggle('active', dashboardActive.value);
149
- * });
150
- * ```
151
- */
152
- export const isActiveSignal = (path: string, exact = false): ReadonlySignal<boolean> => {
153
- return computed(() => {
154
- const current = routeSignal.value.path;
155
- return exact ? current === path : current.startsWith(path);
156
- });
157
- };
1
+ /**
2
+ * Router utilities.
3
+ * @module bquery/router
4
+ */
5
+
6
+ import { computed, type ReadonlySignal } from '../reactive/index';
7
+ import { getRouteConstraintRegex } from './constraints';
8
+ import { isParamChar, isParamStart, readConstraint } from './path-pattern';
9
+ import { getActiveRouter, routeSignal } from './state';
10
+ import type { RouteDefinition } from './types';
11
+
12
+ // ============================================================================
13
+ // Utilities
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Flattens nested routes into a single array with full paths.
18
+ * Does NOT include the router base - base is only for browser history.
19
+ * @internal
20
+ */
21
+ export const flattenRoutes = (routes: RouteDefinition[], parentPath = ''): RouteDefinition[] => {
22
+ const result: RouteDefinition[] = [];
23
+
24
+ for (const route of routes) {
25
+ const fullPath = route.path === '*' ? '*' : `${parentPath}${route.path}`.replace(/\/+/g, '/');
26
+
27
+ result.push({
28
+ ...route,
29
+ path: fullPath,
30
+ });
31
+
32
+ if (route.children) {
33
+ result.push(...flattenRoutes(route.children, fullPath));
34
+ }
35
+ }
36
+
37
+ return result;
38
+ };
39
+
40
+ /**
41
+ * Resolves a route by name and params.
42
+ *
43
+ * @param name - The route name
44
+ * @param params - Route params to interpolate
45
+ * @returns The resolved path
46
+ * @throws {Error} If no router is initialized, the route name is unknown,
47
+ * a required path param is missing from `params`, a param value does not satisfy
48
+ * its route regex constraint, or a route param constraint has invalid syntax
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { resolve } from 'bquery/router';
53
+ *
54
+ * const path = resolve('user', { id: '42' });
55
+ * // Returns '/user/42' if route is defined as { name: 'user', path: '/user/:id' }
56
+ * ```
57
+ */
58
+ export const resolve = (name: string, params: Record<string, string> = {}): string => {
59
+ const activeRouter = getActiveRouter();
60
+ if (!activeRouter) {
61
+ throw new Error('bQuery router: No router initialized.');
62
+ }
63
+
64
+ const route = activeRouter.routes.find((r) => r.name === name);
65
+ if (!route) {
66
+ throw new Error(`bQuery router: Route "${name}" not found.`);
67
+ }
68
+
69
+ let path = '';
70
+ for (let i = 0; i < route.path.length; ) {
71
+ if (route.path[i] === ':' && isParamStart(route.path[i + 1])) {
72
+ let nameEnd = i + 2;
73
+ while (nameEnd < route.path.length && isParamChar(route.path[nameEnd])) {
74
+ nameEnd++;
75
+ }
76
+
77
+ let nextIndex = nameEnd;
78
+ let constraint: string | null = null;
79
+ if (route.path[nameEnd] === '(') {
80
+ const parsedConstraint = readConstraint(route.path, nameEnd);
81
+ if (!parsedConstraint) {
82
+ throw new Error(
83
+ `bQuery router: Invalid constraint syntax in path "${route.path}" for route "${name}".`
84
+ );
85
+ }
86
+ constraint = parsedConstraint.constraint;
87
+ nextIndex = parsedConstraint.endIndex;
88
+ }
89
+
90
+ const key = route.path.slice(i + 1, nameEnd);
91
+ const value = params[key];
92
+ if (value === undefined) {
93
+ throw new Error(`bQuery router: Missing required param "${key}" for route "${name}".`);
94
+ }
95
+ if (constraint && !getRouteConstraintRegex(constraint).test(value)) {
96
+ throw new Error(
97
+ `bQuery router: Param "${key}" with value "${value}" does not satisfy the route constraint "${constraint}" for route "${name}".`
98
+ );
99
+ }
100
+
101
+ path += encodeURIComponent(value);
102
+ i = nextIndex;
103
+ continue;
104
+ }
105
+
106
+ path += route.path[i];
107
+ i++;
108
+ }
109
+
110
+ return path;
111
+ };
112
+
113
+ /**
114
+ * Checks if a path matches the current route.
115
+ *
116
+ * @param path - Path to check
117
+ * @param exact - Whether to match exactly (default: false)
118
+ * @returns True if the path matches
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * import { isActive } from 'bquery/router';
123
+ *
124
+ * if (isActive('/dashboard')) {
125
+ * // Highlight nav item
126
+ * }
127
+ * ```
128
+ */
129
+ export const isActive = (path: string, exact = false): boolean => {
130
+ const current = routeSignal.value.path;
131
+ return exact ? current === path : current.startsWith(path);
132
+ };
133
+
134
+ /**
135
+ * Creates a computed signal that checks if a path is active.
136
+ *
137
+ * @param path - Path to check
138
+ * @param exact - Whether to match exactly
139
+ * @returns A reactive signal
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * import { isActiveSignal } from 'bquery/router';
144
+ * import { effect } from 'bquery/reactive';
145
+ *
146
+ * const dashboardActive = isActiveSignal('/dashboard');
147
+ * effect(() => {
148
+ * navItem.classList.toggle('active', dashboardActive.value);
149
+ * });
150
+ * ```
151
+ */
152
+ export const isActiveSignal = (path: string, exact = false): ReadonlySignal<boolean> => {
153
+ return computed(() => {
154
+ const current = routeSignal.value.path;
155
+ return exact ? current === path : current.startsWith(path);
156
+ });
157
+ };
@@ -1,12 +1,12 @@
1
- /**
2
- * Security module providing sanitization, CSP compatibility, and Trusted Types.
3
- *
4
- * @module bquery/security
5
- */
6
-
7
- export { generateNonce, hasCSPDirective } from './csp';
8
- export { escapeHtml, sanitizeHtml as sanitize, sanitizeHtml, stripTags } from './sanitize';
9
- export { trusted } from './trusted-html';
10
- export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
11
- export type { SanitizedHtml, TrustedHtml } from './trusted-html';
12
- export type { SanitizeOptions } from './types';
1
+ /**
2
+ * Security module providing sanitization, CSP compatibility, and Trusted Types.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ export { generateNonce, hasCSPDirective } from './csp';
8
+ export { escapeHtml, sanitizeHtml as sanitize, sanitizeHtml, stripTags } from './sanitize';
9
+ export { trusted } from './trusted-html';
10
+ export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
11
+ export type { SanitizedHtml, TrustedHtml } from './trusted-html';
12
+ export type { SanitizeOptions } from './types';
@@ -1,82 +1,84 @@
1
- /**
2
- * Hydration support for server-rendered DOM.
3
- *
4
- * Enables the client-side view system to reuse existing server-rendered DOM
5
- * elements instead of re-rendering them, by attaching reactive bindings
6
- * to the pre-existing DOM structure.
7
- *
8
- * @module bquery/ssr
9
- */
10
-
11
- import type { BindingContext, MountOptions, View } from '../view/types';
12
- import { mount } from '../view/mount';
13
-
14
- /**
15
- * Extended mount options that include hydration mode.
16
- */
17
- export type HydrateMountOptions = MountOptions & {
18
- /**
19
- * When present, must be `true` so the mount operation reuses existing DOM elements
20
- * instead of re-rendering them. Reactive bindings (effects) are
21
- * still attached so the DOM updates reactively from that point on.
22
- *
23
- * @default true
24
- */
25
- hydrate?: true;
26
- };
27
-
28
- /**
29
- * Mounts a reactive view with optional hydration support.
30
- *
31
- * When `hydrate: true` is set, the existing server-rendered DOM is preserved
32
- * and reactive bindings are attached on top. The DOM is NOT re-rendered;
33
- * instead, effects begin tracking signals so future changes update the DOM.
34
- *
35
- * This is the client-side counterpart to `renderToString()`. The typical flow:
36
- * 1. Server: `renderToString(template, data)` → send HTML to client
37
- * 2. Client: `hydrateMount('#app', reactiveContext, { hydrate: true })`
38
- *
39
- * Under the hood, `hydrateMount` simply delegates to the standard `mount()`
40
- * function. The `mount()` function already processes existing DOM elements
41
- * and attaches reactive effects to them — it does not clear/replace content.
42
- * The `hydrate` flag is a semantic marker indicating developer intent and
43
- * ensures the existing DOM structure is preserved.
44
- *
45
- * @param selector - CSS selector or Element to hydrate
46
- * @param context - Binding context with signals, computed values, and functions
47
- * @param options - Mount options with `hydrate: true`
48
- * @returns The mounted View instance
49
- *
50
- * @example
51
- * ```ts
52
- * import { hydrateMount } from '@bquery/bquery/ssr';
53
- * import { signal, computed } from '@bquery/bquery/reactive';
54
- *
55
- * // Server rendered:
56
- * // <div id="app"><h1>Welcome</h1><p>Hello, World!</p></div>
57
- *
58
- * // Client hydration — attaches reactivity to existing DOM:
59
- * const name = signal('World');
60
- * const greeting = computed(() => `Hello, ${name.value}!`);
61
- *
62
- * const view = hydrateMount('#app', { name, greeting }, { hydrate: true });
63
- *
64
- * // Now updating `name.value` will reactively update the DOM
65
- * name.value = 'Alice'; // <p> updates to "Hello, Alice!"
66
- * ```
67
- */
68
- export const hydrateMount = (
69
- selector: string | Element,
70
- context: BindingContext,
71
- options: HydrateMountOptions = {}
72
- ): View => {
73
- const { hydrate = true, ...mountOptions } = options;
74
-
75
- if (!hydrate) {
76
- throw new Error('bQuery ssr: hydrateMount() requires { hydrate: true } when options are provided.');
77
- }
78
-
79
- // Hydration uses the standard mount which processes existing DOM
80
- // and attaches reactive effects without clearing content.
81
- return mount(selector, context, mountOptions);
82
- };
1
+ /**
2
+ * Hydration support for server-rendered DOM.
3
+ *
4
+ * Enables the client-side view system to reuse existing server-rendered DOM
5
+ * elements instead of re-rendering them, by attaching reactive bindings
6
+ * to the pre-existing DOM structure.
7
+ *
8
+ * @module bquery/ssr
9
+ */
10
+
11
+ import type { BindingContext, MountOptions, View } from '../view/types';
12
+ import { mount } from '../view/mount';
13
+
14
+ /**
15
+ * Extended mount options that include hydration mode.
16
+ */
17
+ export type HydrateMountOptions = MountOptions & {
18
+ /**
19
+ * When present, must be `true` so the mount operation reuses existing DOM elements
20
+ * instead of re-rendering them. Reactive bindings (effects) are
21
+ * still attached so the DOM updates reactively from that point on.
22
+ *
23
+ * @default true
24
+ */
25
+ hydrate?: true;
26
+ };
27
+
28
+ /**
29
+ * Mounts a reactive view with optional hydration support.
30
+ *
31
+ * When `hydrate: true` is set, the existing server-rendered DOM is preserved
32
+ * and reactive bindings are attached on top. The DOM is NOT re-rendered;
33
+ * instead, effects begin tracking signals so future changes update the DOM.
34
+ *
35
+ * This is the client-side counterpart to `renderToString()`. The typical flow:
36
+ * 1. Server: `renderToString(template, data)` → send HTML to client
37
+ * 2. Client: `hydrateMount('#app', reactiveContext, { hydrate: true })`
38
+ *
39
+ * Under the hood, `hydrateMount` simply delegates to the standard `mount()`
40
+ * function. The `mount()` function already processes existing DOM elements
41
+ * and attaches reactive effects to them — it does not clear/replace content.
42
+ * The `hydrate` flag is a semantic marker indicating developer intent and
43
+ * ensures the existing DOM structure is preserved.
44
+ *
45
+ * @param selector - CSS selector or Element to hydrate
46
+ * @param context - Binding context with signals, computed values, and functions
47
+ * @param options - Mount options with `hydrate: true`
48
+ * @returns The mounted View instance
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { hydrateMount } from '@bquery/bquery/ssr';
53
+ * import { signal, computed } from '@bquery/bquery/reactive';
54
+ *
55
+ * // Server rendered:
56
+ * // <div id="app"><h1>Welcome</h1><p>Hello, World!</p></div>
57
+ *
58
+ * // Client hydration — attaches reactivity to existing DOM:
59
+ * const name = signal('World');
60
+ * const greeting = computed(() => `Hello, ${name.value}!`);
61
+ *
62
+ * const view = hydrateMount('#app', { name, greeting }, { hydrate: true });
63
+ *
64
+ * // Now updating `name.value` will reactively update the DOM
65
+ * name.value = 'Alice'; // <p> updates to "Hello, Alice!"
66
+ * ```
67
+ */
68
+ export const hydrateMount = (
69
+ selector: string | Element,
70
+ context: BindingContext,
71
+ options: HydrateMountOptions = {}
72
+ ): View => {
73
+ const { hydrate = true, ...mountOptions } = options;
74
+
75
+ if (!hydrate) {
76
+ throw new Error(
77
+ 'bQuery ssr: hydrateMount() requires { hydrate: true } when options are provided.'
78
+ );
79
+ }
80
+
81
+ // Hydration uses the standard mount which processes existing DOM
82
+ // and attaches reactive effects without clearing content.
83
+ return mount(selector, context, mountOptions);
84
+ };
package/src/ssr/index.ts CHANGED
@@ -1,70 +1,70 @@
1
- /**
2
- * SSR / Pre-rendering module for bQuery.js.
3
- *
4
- * Provides server-side rendering, hydration, and store state serialization
5
- * utilities for bQuery applications. Enables rendering bQuery templates
6
- * to HTML strings on the server, serializing store state for client pickup,
7
- * and hydrating the pre-rendered DOM on the client.
8
- *
9
- * ## Features
10
- *
11
- * - **`renderToString(template, data)`** — Server-side render a bQuery
12
- * template to an `SSRResult` containing an `html` string with directive evaluation.
13
- * - **`hydrateMount(selector, context, { hydrate: true })`** — Reuse
14
- * existing server-rendered DOM and attach reactive bindings.
15
- * - **`serializeStoreState(options?)`** — Serialize store state into a
16
- * `<script>` tag for client-side pickup.
17
- * - **`deserializeStoreState()`** — Read serialized state on the client.
18
- * - **`hydrateStore(id, state)` / `hydrateStores(stateMap)`** — Apply
19
- * server state to client stores.
20
- *
21
- * ## Usage
22
- *
23
- * ### Server
24
- * ```ts
25
- * import { renderToString, serializeStoreState } from '@bquery/bquery/ssr';
26
- *
27
- * const { html } = renderToString(
28
- * '<div id="app"><h1 bq-text="title"></h1></div>',
29
- * { title: 'Welcome' }
30
- * );
31
- *
32
- * const { scriptTag } = serializeStoreState();
33
- *
34
- * // Send to client: html + scriptTag
35
- * ```
36
- *
37
- * ### Client
38
- * ```ts
39
- * import { hydrateMount, deserializeStoreState, hydrateStores } from '@bquery/bquery/ssr';
40
- * import { signal } from '@bquery/bquery/reactive';
41
- *
42
- * // Restore store state from SSR
43
- * const ssrState = deserializeStoreState();
44
- * hydrateStores(ssrState);
45
- *
46
- * // Hydrate the DOM with reactive bindings
47
- * const title = signal('Welcome');
48
- * hydrateMount('#app', { title }, { hydrate: true });
49
- * ```
50
- *
51
- * @module bquery/ssr
52
- */
53
-
54
- export { hydrateMount } from './hydrate';
55
- export type { HydrateMountOptions } from './hydrate';
56
- export { renderToString } from './render';
57
- export {
58
- deserializeStoreState,
59
- hydrateStore,
60
- hydrateStores,
61
- serializeStoreState,
62
- } from './serialize';
63
- export type { SerializeResult } from './serialize';
64
- export type {
65
- DeserializedStoreState,
66
- HydrationOptions,
67
- RenderOptions,
68
- SSRResult,
69
- SerializeOptions,
70
- } from './types';
1
+ /**
2
+ * SSR / Pre-rendering module for bQuery.js.
3
+ *
4
+ * Provides server-side rendering, hydration, and store state serialization
5
+ * utilities for bQuery applications. Enables rendering bQuery templates
6
+ * to HTML strings on the server, serializing store state for client pickup,
7
+ * and hydrating the pre-rendered DOM on the client.
8
+ *
9
+ * ## Features
10
+ *
11
+ * - **`renderToString(template, data)`** — Server-side render a bQuery
12
+ * template to an `SSRResult` containing an `html` string with directive evaluation.
13
+ * - **`hydrateMount(selector, context, { hydrate: true })`** — Reuse
14
+ * existing server-rendered DOM and attach reactive bindings.
15
+ * - **`serializeStoreState(options?)`** — Serialize store state into a
16
+ * `<script>` tag for client-side pickup.
17
+ * - **`deserializeStoreState()`** — Read serialized state on the client.
18
+ * - **`hydrateStore(id, state)` / `hydrateStores(stateMap)`** — Apply
19
+ * server state to client stores.
20
+ *
21
+ * ## Usage
22
+ *
23
+ * ### Server
24
+ * ```ts
25
+ * import { renderToString, serializeStoreState } from '@bquery/bquery/ssr';
26
+ *
27
+ * const { html } = renderToString(
28
+ * '<div id="app"><h1 bq-text="title"></h1></div>',
29
+ * { title: 'Welcome' }
30
+ * );
31
+ *
32
+ * const { scriptTag } = serializeStoreState();
33
+ *
34
+ * // Send to client: html + scriptTag
35
+ * ```
36
+ *
37
+ * ### Client
38
+ * ```ts
39
+ * import { hydrateMount, deserializeStoreState, hydrateStores } from '@bquery/bquery/ssr';
40
+ * import { signal } from '@bquery/bquery/reactive';
41
+ *
42
+ * // Restore store state from SSR
43
+ * const ssrState = deserializeStoreState();
44
+ * hydrateStores(ssrState);
45
+ *
46
+ * // Hydrate the DOM with reactive bindings
47
+ * const title = signal('Welcome');
48
+ * hydrateMount('#app', { title }, { hydrate: true });
49
+ * ```
50
+ *
51
+ * @module bquery/ssr
52
+ */
53
+
54
+ export { hydrateMount } from './hydrate';
55
+ export type { HydrateMountOptions } from './hydrate';
56
+ export { renderToString } from './render';
57
+ export {
58
+ deserializeStoreState,
59
+ hydrateStore,
60
+ hydrateStores,
61
+ serializeStoreState,
62
+ } from './serialize';
63
+ export type { SerializeResult } from './serialize';
64
+ export type {
65
+ DeserializedStoreState,
66
+ HydrationOptions,
67
+ RenderOptions,
68
+ SSRResult,
69
+ SerializeOptions,
70
+ } from './types';