@bquery/bquery 1.1.2 → 1.3.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.
Files changed (307) hide show
  1. package/README.md +501 -323
  2. package/dist/batch-4LAvfLE7.js +13 -0
  3. package/dist/batch-4LAvfLE7.js.map +1 -0
  4. package/dist/component/component.d.ts +69 -0
  5. package/dist/component/component.d.ts.map +1 -0
  6. package/dist/component/html.d.ts +35 -0
  7. package/dist/component/html.d.ts.map +1 -0
  8. package/dist/component/index.d.ts +3 -126
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/props.d.ts +18 -0
  11. package/dist/component/props.d.ts.map +1 -0
  12. package/dist/component/types.d.ts +77 -0
  13. package/dist/component/types.d.ts.map +1 -0
  14. package/dist/component.es.mjs +90 -59
  15. package/dist/component.es.mjs.map +1 -1
  16. package/dist/core/collection.d.ts +36 -0
  17. package/dist/core/collection.d.ts.map +1 -1
  18. package/dist/core/dom.d.ts +6 -0
  19. package/dist/core/dom.d.ts.map +1 -0
  20. package/dist/core/element.d.ts +8 -0
  21. package/dist/core/element.d.ts.map +1 -1
  22. package/dist/core/index.d.ts +1 -0
  23. package/dist/core/index.d.ts.map +1 -1
  24. package/dist/core/utils/array.d.ts +74 -0
  25. package/dist/core/utils/array.d.ts.map +1 -0
  26. package/dist/core/utils/function.d.ts +70 -0
  27. package/dist/core/utils/function.d.ts.map +1 -0
  28. package/dist/core/utils/index.d.ts +70 -0
  29. package/dist/core/utils/index.d.ts.map +1 -0
  30. package/dist/core/utils/misc.d.ts +63 -0
  31. package/dist/core/utils/misc.d.ts.map +1 -0
  32. package/dist/core/utils/number.d.ts +65 -0
  33. package/dist/core/utils/number.d.ts.map +1 -0
  34. package/dist/core/utils/object.d.ts +133 -0
  35. package/dist/core/utils/object.d.ts.map +1 -0
  36. package/dist/core/utils/string.d.ts +80 -0
  37. package/dist/core/utils/string.d.ts.map +1 -0
  38. package/dist/core/utils/type-guards.d.ts +79 -0
  39. package/dist/core/utils/type-guards.d.ts.map +1 -0
  40. package/dist/core-COenAZjD.js +145 -0
  41. package/dist/core-COenAZjD.js.map +1 -0
  42. package/dist/core.es.mjs +411 -448
  43. package/dist/core.es.mjs.map +1 -1
  44. package/dist/full.d.ts +8 -2
  45. package/dist/full.d.ts.map +1 -1
  46. package/dist/full.es.mjs +86 -40
  47. package/dist/full.es.mjs.map +1 -1
  48. package/dist/full.iife.js +6 -1
  49. package/dist/full.iife.js.map +1 -1
  50. package/dist/full.umd.js +6 -1
  51. package/dist/full.umd.js.map +1 -1
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.es.mjs +137 -44
  55. package/dist/index.es.mjs.map +1 -1
  56. package/dist/motion/animate.d.ts +25 -0
  57. package/dist/motion/animate.d.ts.map +1 -0
  58. package/dist/motion/easing.d.ts +30 -0
  59. package/dist/motion/easing.d.ts.map +1 -0
  60. package/dist/motion/flip.d.ts +55 -0
  61. package/dist/motion/flip.d.ts.map +1 -0
  62. package/dist/motion/index.d.ts +11 -138
  63. package/dist/motion/index.d.ts.map +1 -1
  64. package/dist/motion/keyframes.d.ts +21 -0
  65. package/dist/motion/keyframes.d.ts.map +1 -0
  66. package/dist/motion/reduced-motion.d.ts +12 -0
  67. package/dist/motion/reduced-motion.d.ts.map +1 -0
  68. package/dist/motion/scroll.d.ts +15 -0
  69. package/dist/motion/scroll.d.ts.map +1 -0
  70. package/dist/motion/spring.d.ts +42 -0
  71. package/dist/motion/spring.d.ts.map +1 -0
  72. package/dist/motion/stagger.d.ts +22 -0
  73. package/dist/motion/stagger.d.ts.map +1 -0
  74. package/dist/motion/timeline.d.ts +21 -0
  75. package/dist/motion/timeline.d.ts.map +1 -0
  76. package/dist/motion/transition.d.ts +22 -0
  77. package/dist/motion/transition.d.ts.map +1 -0
  78. package/dist/motion/types.d.ts +182 -0
  79. package/dist/motion/types.d.ts.map +1 -0
  80. package/dist/motion.es.mjs +320 -61
  81. package/dist/motion.es.mjs.map +1 -1
  82. package/dist/persisted-Dz_ryNuC.js +278 -0
  83. package/dist/persisted-Dz_ryNuC.js.map +1 -0
  84. package/dist/reactive/batch.d.ts +13 -0
  85. package/dist/reactive/batch.d.ts.map +1 -0
  86. package/dist/reactive/computed.d.ts +50 -0
  87. package/dist/reactive/computed.d.ts.map +1 -0
  88. package/dist/reactive/core.d.ts +60 -0
  89. package/dist/reactive/core.d.ts.map +1 -0
  90. package/dist/reactive/effect.d.ts +15 -0
  91. package/dist/reactive/effect.d.ts.map +1 -0
  92. package/dist/reactive/index.d.ts +2 -2
  93. package/dist/reactive/index.d.ts.map +1 -1
  94. package/dist/reactive/internals.d.ts +36 -0
  95. package/dist/reactive/internals.d.ts.map +1 -0
  96. package/dist/reactive/linked.d.ts +36 -0
  97. package/dist/reactive/linked.d.ts.map +1 -0
  98. package/dist/reactive/persisted.d.ts +14 -0
  99. package/dist/reactive/persisted.d.ts.map +1 -0
  100. package/dist/reactive/readonly.d.ts +26 -0
  101. package/dist/reactive/readonly.d.ts.map +1 -0
  102. package/dist/reactive/signal.d.ts +13 -305
  103. package/dist/reactive/signal.d.ts.map +1 -1
  104. package/dist/reactive/type-guards.d.ts +20 -0
  105. package/dist/reactive/type-guards.d.ts.map +1 -0
  106. package/dist/reactive/untrack.d.ts +29 -0
  107. package/dist/reactive/untrack.d.ts.map +1 -0
  108. package/dist/reactive/watch.d.ts +42 -0
  109. package/dist/reactive/watch.d.ts.map +1 -0
  110. package/dist/reactive.es.mjs +30 -154
  111. package/dist/reactive.es.mjs.map +1 -1
  112. package/dist/router/index.d.ts +41 -0
  113. package/dist/router/index.d.ts.map +1 -0
  114. package/dist/router/links.d.ts +44 -0
  115. package/dist/router/links.d.ts.map +1 -0
  116. package/dist/router/match.d.ts +20 -0
  117. package/dist/router/match.d.ts.map +1 -0
  118. package/dist/router/navigation.d.ts +45 -0
  119. package/dist/router/navigation.d.ts.map +1 -0
  120. package/dist/router/query.d.ts +16 -0
  121. package/dist/router/query.d.ts.map +1 -0
  122. package/dist/router/router.d.ts +34 -0
  123. package/dist/router/router.d.ts.map +1 -0
  124. package/dist/router/state.d.ts +27 -0
  125. package/dist/router/state.d.ts.map +1 -0
  126. package/dist/router/types.d.ts +88 -0
  127. package/dist/router/types.d.ts.map +1 -0
  128. package/dist/router/utils.d.ts +65 -0
  129. package/dist/router/utils.d.ts.map +1 -0
  130. package/dist/router.es.mjs +202 -0
  131. package/dist/router.es.mjs.map +1 -0
  132. package/dist/sanitize-1FBEPAFH.js +272 -0
  133. package/dist/sanitize-1FBEPAFH.js.map +1 -0
  134. package/dist/security/constants.d.ts +42 -0
  135. package/dist/security/constants.d.ts.map +1 -0
  136. package/dist/security/csp.d.ts +24 -0
  137. package/dist/security/csp.d.ts.map +1 -0
  138. package/dist/security/index.d.ts +4 -2
  139. package/dist/security/index.d.ts.map +1 -1
  140. package/dist/security/sanitize-core.d.ts +13 -0
  141. package/dist/security/sanitize-core.d.ts.map +1 -0
  142. package/dist/security/sanitize.d.ts +5 -57
  143. package/dist/security/sanitize.d.ts.map +1 -1
  144. package/dist/security/trusted-types.d.ts +25 -0
  145. package/dist/security/trusted-types.d.ts.map +1 -0
  146. package/dist/security/types.d.ts +36 -0
  147. package/dist/security/types.d.ts.map +1 -0
  148. package/dist/security.es.mjs +50 -277
  149. package/dist/security.es.mjs.map +1 -1
  150. package/dist/store/create-store.d.ts +15 -0
  151. package/dist/store/create-store.d.ts.map +1 -0
  152. package/dist/store/define-store.d.ts +28 -0
  153. package/dist/store/define-store.d.ts.map +1 -0
  154. package/dist/store/devtools.d.ts +22 -0
  155. package/dist/store/devtools.d.ts.map +1 -0
  156. package/dist/store/index.d.ts +12 -0
  157. package/dist/store/index.d.ts.map +1 -0
  158. package/dist/store/mapping.d.ts +28 -0
  159. package/dist/store/mapping.d.ts.map +1 -0
  160. package/dist/store/persisted.d.ts +13 -0
  161. package/dist/store/persisted.d.ts.map +1 -0
  162. package/dist/store/plugins.d.ts +13 -0
  163. package/dist/store/plugins.d.ts.map +1 -0
  164. package/dist/store/registry.d.ts +28 -0
  165. package/dist/store/registry.d.ts.map +1 -0
  166. package/dist/store/types.d.ts +71 -0
  167. package/dist/store/types.d.ts.map +1 -0
  168. package/dist/store/utils.d.ts +28 -0
  169. package/dist/store/utils.d.ts.map +1 -0
  170. package/dist/store/watch.d.ts +23 -0
  171. package/dist/store/watch.d.ts.map +1 -0
  172. package/dist/store.es.mjs +27 -0
  173. package/dist/store.es.mjs.map +1 -0
  174. package/dist/type-guards-DRma3-Kc.js +16 -0
  175. package/dist/type-guards-DRma3-Kc.js.map +1 -0
  176. package/dist/untrack-BuEQKH7_.js +6 -0
  177. package/dist/untrack-BuEQKH7_.js.map +1 -0
  178. package/dist/view/directives/bind.d.ts +7 -0
  179. package/dist/view/directives/bind.d.ts.map +1 -0
  180. package/dist/view/directives/class.d.ts +8 -0
  181. package/dist/view/directives/class.d.ts.map +1 -0
  182. package/dist/view/directives/for.d.ts +23 -0
  183. package/dist/view/directives/for.d.ts.map +1 -0
  184. package/dist/view/directives/html.d.ts +7 -0
  185. package/dist/view/directives/html.d.ts.map +1 -0
  186. package/dist/view/directives/if.d.ts +7 -0
  187. package/dist/view/directives/if.d.ts.map +1 -0
  188. package/dist/view/directives/index.d.ts +12 -0
  189. package/dist/view/directives/index.d.ts.map +1 -0
  190. package/dist/view/directives/model.d.ts +7 -0
  191. package/dist/view/directives/model.d.ts.map +1 -0
  192. package/dist/view/directives/on.d.ts +7 -0
  193. package/dist/view/directives/on.d.ts.map +1 -0
  194. package/dist/view/directives/ref.d.ts +7 -0
  195. package/dist/view/directives/ref.d.ts.map +1 -0
  196. package/dist/view/directives/show.d.ts +7 -0
  197. package/dist/view/directives/show.d.ts.map +1 -0
  198. package/dist/view/directives/style.d.ts +7 -0
  199. package/dist/view/directives/style.d.ts.map +1 -0
  200. package/dist/view/directives/text.d.ts +7 -0
  201. package/dist/view/directives/text.d.ts.map +1 -0
  202. package/dist/view/evaluate.d.ts +43 -0
  203. package/dist/view/evaluate.d.ts.map +1 -0
  204. package/dist/view/index.d.ts +111 -0
  205. package/dist/view/index.d.ts.map +1 -0
  206. package/dist/view/mount.d.ts +69 -0
  207. package/dist/view/mount.d.ts.map +1 -0
  208. package/dist/view/process.d.ts +26 -0
  209. package/dist/view/process.d.ts.map +1 -0
  210. package/dist/view/types.d.ts +36 -0
  211. package/dist/view/types.d.ts.map +1 -0
  212. package/dist/view.es.mjs +426 -0
  213. package/dist/view.es.mjs.map +1 -0
  214. package/dist/watch-CXyaBC_9.js +58 -0
  215. package/dist/watch-CXyaBC_9.js.map +1 -0
  216. package/package.json +26 -14
  217. package/src/component/component.ts +289 -0
  218. package/src/component/html.ts +53 -0
  219. package/src/component/index.ts +40 -414
  220. package/src/component/props.ts +116 -0
  221. package/src/component/types.ts +85 -0
  222. package/src/core/collection.ts +588 -454
  223. package/src/core/dom.ts +38 -0
  224. package/src/core/element.ts +746 -740
  225. package/src/core/index.ts +43 -0
  226. package/src/core/utils/array.ts +102 -0
  227. package/src/core/utils/function.ts +110 -0
  228. package/src/core/utils/index.ts +83 -0
  229. package/src/core/utils/misc.ts +82 -0
  230. package/src/core/utils/number.ts +78 -0
  231. package/src/core/utils/object.ts +206 -0
  232. package/src/core/utils/string.ts +112 -0
  233. package/src/core/utils/type-guards.ts +112 -0
  234. package/src/full.ts +187 -106
  235. package/src/index.ts +36 -27
  236. package/src/motion/animate.ts +113 -0
  237. package/src/motion/easing.ts +40 -0
  238. package/src/motion/flip.ts +176 -0
  239. package/src/motion/index.ts +41 -358
  240. package/src/motion/keyframes.ts +46 -0
  241. package/src/motion/reduced-motion.ts +17 -0
  242. package/src/motion/scroll.ts +57 -0
  243. package/src/motion/spring.ts +150 -0
  244. package/src/motion/stagger.ts +43 -0
  245. package/src/motion/timeline.ts +246 -0
  246. package/src/motion/transition.ts +51 -0
  247. package/src/motion/types.ts +198 -0
  248. package/src/reactive/batch.ts +22 -0
  249. package/src/reactive/computed.ts +92 -0
  250. package/src/reactive/core.ts +93 -0
  251. package/src/reactive/effect.ts +43 -0
  252. package/src/reactive/index.ts +23 -22
  253. package/src/reactive/internals.ts +105 -0
  254. package/src/reactive/linked.ts +56 -0
  255. package/src/reactive/persisted.ts +74 -0
  256. package/src/reactive/readonly.ts +35 -0
  257. package/src/reactive/signal.ts +20 -506
  258. package/src/reactive/type-guards.ts +22 -0
  259. package/src/reactive/untrack.ts +31 -0
  260. package/src/reactive/watch.ts +73 -0
  261. package/src/router/index.ts +41 -0
  262. package/src/router/links.ts +130 -0
  263. package/src/router/match.ts +106 -0
  264. package/src/router/navigation.ts +71 -0
  265. package/src/router/query.ts +35 -0
  266. package/src/router/router.ts +211 -0
  267. package/src/router/state.ts +46 -0
  268. package/src/router/types.ts +93 -0
  269. package/src/router/utils.ts +116 -0
  270. package/src/security/constants.ts +209 -0
  271. package/src/security/csp.ts +77 -0
  272. package/src/security/index.ts +4 -12
  273. package/src/security/sanitize-core.ts +343 -0
  274. package/src/security/sanitize.ts +66 -625
  275. package/src/security/trusted-types.ts +69 -0
  276. package/src/security/types.ts +40 -0
  277. package/src/store/create-store.ts +329 -0
  278. package/src/store/define-store.ts +48 -0
  279. package/src/store/devtools.ts +45 -0
  280. package/src/store/index.ts +22 -0
  281. package/src/store/mapping.ts +73 -0
  282. package/src/store/persisted.ts +61 -0
  283. package/src/store/plugins.ts +32 -0
  284. package/src/store/registry.ts +51 -0
  285. package/src/store/types.ts +94 -0
  286. package/src/store/utils.ts +141 -0
  287. package/src/store/watch.ts +52 -0
  288. package/src/view/directives/bind.ts +23 -0
  289. package/src/view/directives/class.ts +70 -0
  290. package/src/view/directives/for.ts +275 -0
  291. package/src/view/directives/html.ts +19 -0
  292. package/src/view/directives/if.ts +30 -0
  293. package/src/view/directives/index.ts +11 -0
  294. package/src/view/directives/model.ts +56 -0
  295. package/src/view/directives/on.ts +41 -0
  296. package/src/view/directives/ref.ts +41 -0
  297. package/src/view/directives/show.ts +26 -0
  298. package/src/view/directives/style.ts +47 -0
  299. package/src/view/directives/text.ts +15 -0
  300. package/src/view/evaluate.ts +274 -0
  301. package/src/view/index.ts +112 -0
  302. package/src/view/mount.ts +200 -0
  303. package/src/view/process.ts +92 -0
  304. package/src/view/types.ts +44 -0
  305. package/dist/core/utils.d.ts +0 -313
  306. package/dist/core/utils.d.ts.map +0 -1
  307. package/src/core/utils.ts +0 -444
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Internal router state (active router and current route signal).
3
+ * @module bquery/router
4
+ */
5
+
6
+ import { computed, signal, type ReadonlySignal, type Signal } from '../reactive/index';
7
+ import type { Route, Router } from './types';
8
+
9
+ // ============================================================================
10
+ // Internal State
11
+ // ============================================================================
12
+
13
+ /** @internal */
14
+ let activeRouter: Router | null = null;
15
+
16
+ /** @internal */
17
+ export const routeSignal: Signal<Route> = signal<Route>({
18
+ path: '',
19
+ params: {},
20
+ query: {},
21
+ matched: null,
22
+ hash: '',
23
+ });
24
+
25
+ /**
26
+ * Reactive signal containing the current route.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { currentRoute } from 'bquery/router';
31
+ * import { effect } from 'bquery/reactive';
32
+ *
33
+ * effect(() => {
34
+ * document.title = `Page: ${currentRoute.value.path}`;
35
+ * });
36
+ * ```
37
+ */
38
+ export const currentRoute: ReadonlySignal<Route> = computed(() => routeSignal.value);
39
+
40
+ /** @internal */
41
+ export const getActiveRouter = (): Router | null => activeRouter;
42
+
43
+ /** @internal */
44
+ export const setActiveRouter = (router: Router | null): void => {
45
+ activeRouter = router;
46
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Router types and public contracts.
3
+ * @module bquery/router
4
+ */
5
+
6
+ import type { ReadonlySignal } from '../reactive/index';
7
+
8
+ /**
9
+ * Represents a parsed route with matched params.
10
+ */
11
+ export type Route = {
12
+ /** The current path (e.g., '/user/42') */
13
+ path: string;
14
+ /** Extracted route params (e.g., { id: '42' }) */
15
+ params: Record<string, string>;
16
+ /**
17
+ * Query string params.
18
+ * Each key maps to a single string value by default.
19
+ * Only keys that appear multiple times in the query string become arrays.
20
+ * @example
21
+ * // ?foo=1 → { foo: '1' }
22
+ * // ?tag=a&tag=b → { tag: ['a', 'b'] }
23
+ * // ?x=1&y=2&x=3 → { x: ['1', '3'], y: '2' }
24
+ */
25
+ query: Record<string, string | string[]>;
26
+ /** The matched route definition */
27
+ matched: RouteDefinition | null;
28
+ /** Hash fragment without # */
29
+ hash: string;
30
+ };
31
+
32
+ /**
33
+ * Route definition for configuration.
34
+ */
35
+ export type RouteDefinition = {
36
+ /** Path pattern (e.g., '/user/:id', '/posts/*') */
37
+ path: string;
38
+ /** Component loader (sync or async) */
39
+ component: () => unknown | Promise<unknown>;
40
+ /** Optional route name for programmatic navigation */
41
+ name?: string;
42
+ /** Optional metadata */
43
+ meta?: Record<string, unknown>;
44
+ /** Nested child routes */
45
+ children?: RouteDefinition[];
46
+ };
47
+
48
+ /**
49
+ * Router configuration options.
50
+ */
51
+ export type RouterOptions = {
52
+ /** Array of route definitions */
53
+ routes: RouteDefinition[];
54
+ /** Base path for all routes (default: '') */
55
+ base?: string;
56
+ /** Use hash-based routing instead of history (default: false) */
57
+ hash?: boolean;
58
+ };
59
+
60
+ /**
61
+ * Navigation guard function type.
62
+ */
63
+ export type NavigationGuard = (to: Route, from: Route) => boolean | void | Promise<boolean | void>;
64
+
65
+ /**
66
+ * Router instance returned by createRouter.
67
+ */
68
+ export type Router = {
69
+ /** Navigate to a path */
70
+ push: (path: string) => Promise<void>;
71
+ /** Replace current history entry */
72
+ replace: (path: string) => Promise<void>;
73
+ /** Go back in history */
74
+ back: () => void;
75
+ /** Go forward in history */
76
+ forward: () => void;
77
+ /** Go to a specific history entry */
78
+ go: (delta: number) => void;
79
+ /** Add a beforeEach guard */
80
+ beforeEach: (guard: NavigationGuard) => () => void;
81
+ /** Add an afterEach hook */
82
+ afterEach: (hook: (to: Route, from: Route) => void) => () => void;
83
+ /** Current route (reactive) */
84
+ currentRoute: ReadonlySignal<Route>;
85
+ /** All route definitions */
86
+ routes: RouteDefinition[];
87
+ /** Base path for all routes */
88
+ base: string;
89
+ /** Whether hash-based routing is enabled */
90
+ hash: boolean;
91
+ /** Destroy the router and cleanup listeners */
92
+ destroy: () => void;
93
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Router utilities.
3
+ * @module bquery/router
4
+ */
5
+
6
+ import { computed, type ReadonlySignal } from '../reactive/index';
7
+ import { getActiveRouter, routeSignal } from './state';
8
+ import type { RouteDefinition } from './types';
9
+
10
+ // ============================================================================
11
+ // Utilities
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Flattens nested routes into a single array with full paths.
16
+ * Does NOT include the router base - base is only for browser history.
17
+ * @internal
18
+ */
19
+ export const flattenRoutes = (routes: RouteDefinition[], parentPath = ''): RouteDefinition[] => {
20
+ const result: RouteDefinition[] = [];
21
+
22
+ for (const route of routes) {
23
+ const fullPath = route.path === '*' ? '*' : `${parentPath}${route.path}`.replace(/\/+/g, '/');
24
+
25
+ result.push({
26
+ ...route,
27
+ path: fullPath,
28
+ });
29
+
30
+ if (route.children) {
31
+ result.push(...flattenRoutes(route.children, fullPath));
32
+ }
33
+ }
34
+
35
+ return result;
36
+ };
37
+
38
+ /**
39
+ * Resolves a route by name and params.
40
+ *
41
+ * @param name - The route name
42
+ * @param params - Route params to interpolate
43
+ * @returns The resolved path
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { resolve } from 'bquery/router';
48
+ *
49
+ * const path = resolve('user', { id: '42' });
50
+ * // Returns '/user/42' if route is defined as { name: 'user', path: '/user/:id' }
51
+ * ```
52
+ */
53
+ export const resolve = (name: string, params: Record<string, string> = {}): string => {
54
+ const activeRouter = getActiveRouter();
55
+ if (!activeRouter) {
56
+ throw new Error('bQuery router: No router initialized.');
57
+ }
58
+
59
+ const route = activeRouter.routes.find((r) => r.name === name);
60
+ if (!route) {
61
+ throw new Error(`bQuery router: Route "${name}" not found.`);
62
+ }
63
+
64
+ let path = route.path;
65
+ for (const [key, value] of Object.entries(params)) {
66
+ path = path.replace(`:${key}`, encodeURIComponent(value));
67
+ }
68
+
69
+ return path;
70
+ };
71
+
72
+ /**
73
+ * Checks if a path matches the current route.
74
+ *
75
+ * @param path - Path to check
76
+ * @param exact - Whether to match exactly (default: false)
77
+ * @returns True if the path matches
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { isActive } from 'bquery/router';
82
+ *
83
+ * if (isActive('/dashboard')) {
84
+ * // Highlight nav item
85
+ * }
86
+ * ```
87
+ */
88
+ export const isActive = (path: string, exact = false): boolean => {
89
+ const current = routeSignal.value.path;
90
+ return exact ? current === path : current.startsWith(path);
91
+ };
92
+
93
+ /**
94
+ * Creates a computed signal that checks if a path is active.
95
+ *
96
+ * @param path - Path to check
97
+ * @param exact - Whether to match exactly
98
+ * @returns A reactive signal
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { isActiveSignal } from 'bquery/router';
103
+ * import { effect } from 'bquery/reactive';
104
+ *
105
+ * const dashboardActive = isActiveSignal('/dashboard');
106
+ * effect(() => {
107
+ * navItem.classList.toggle('active', dashboardActive.value);
108
+ * });
109
+ * ```
110
+ */
111
+ export const isActiveSignal = (path: string, exact = false): ReadonlySignal<boolean> => {
112
+ return computed(() => {
113
+ const current = routeSignal.value.path;
114
+ return exact ? current === path : current.startsWith(path);
115
+ });
116
+ };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Security constants and safe lists.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ /**
8
+ * Trusted Types policy name.
9
+ */
10
+ export const POLICY_NAME = 'bquery-sanitizer';
11
+
12
+ /**
13
+ * Default allowed HTML tags considered safe.
14
+ */
15
+ export const DEFAULT_ALLOWED_TAGS = new Set([
16
+ 'a',
17
+ 'abbr',
18
+ 'address',
19
+ 'article',
20
+ 'aside',
21
+ 'b',
22
+ 'bdi',
23
+ 'bdo',
24
+ 'blockquote',
25
+ 'br',
26
+ 'button',
27
+ 'caption',
28
+ 'cite',
29
+ 'code',
30
+ 'col',
31
+ 'colgroup',
32
+ 'data',
33
+ 'dd',
34
+ 'del',
35
+ 'details',
36
+ 'dfn',
37
+ 'div',
38
+ 'dl',
39
+ 'dt',
40
+ 'em',
41
+ 'figcaption',
42
+ 'figure',
43
+ 'footer',
44
+ 'form',
45
+ 'h1',
46
+ 'h2',
47
+ 'h3',
48
+ 'h4',
49
+ 'h5',
50
+ 'h6',
51
+ 'header',
52
+ 'hgroup',
53
+ 'hr',
54
+ 'i',
55
+ 'img',
56
+ 'input',
57
+ 'ins',
58
+ 'kbd',
59
+ 'label',
60
+ 'legend',
61
+ 'li',
62
+ 'main',
63
+ 'mark',
64
+ 'nav',
65
+ 'ol',
66
+ 'optgroup',
67
+ 'option',
68
+ 'p',
69
+ 'picture',
70
+ 'pre',
71
+ 'progress',
72
+ 'q',
73
+ 'rp',
74
+ 'rt',
75
+ 'ruby',
76
+ 's',
77
+ 'samp',
78
+ 'section',
79
+ 'select',
80
+ 'small',
81
+ 'source',
82
+ 'span',
83
+ 'strong',
84
+ 'sub',
85
+ 'summary',
86
+ 'sup',
87
+ 'table',
88
+ 'tbody',
89
+ 'td',
90
+ 'textarea',
91
+ 'tfoot',
92
+ 'th',
93
+ 'thead',
94
+ 'time',
95
+ 'tr',
96
+ 'u',
97
+ 'ul',
98
+ 'var',
99
+ 'wbr',
100
+ ]);
101
+
102
+ /**
103
+ * Explicitly dangerous tags that should never be allowed.
104
+ * These are checked even if somehow added to allowTags.
105
+ */
106
+ export const DANGEROUS_TAGS = new Set([
107
+ 'script',
108
+ 'iframe',
109
+ 'frame',
110
+ 'frameset',
111
+ 'object',
112
+ 'embed',
113
+ 'applet',
114
+ 'link',
115
+ 'meta',
116
+ 'style',
117
+ 'base',
118
+ 'template',
119
+ 'slot',
120
+ 'math',
121
+ 'svg',
122
+ 'foreignobject',
123
+ 'noscript',
124
+ ]);
125
+
126
+ /**
127
+ * Reserved IDs that could cause DOM clobbering attacks.
128
+ * These are prevented to avoid overwriting global browser objects.
129
+ */
130
+ export const RESERVED_IDS = new Set([
131
+ // Global objects
132
+ 'document',
133
+ 'window',
134
+ 'location',
135
+ 'top',
136
+ 'self',
137
+ 'parent',
138
+ 'frames',
139
+ 'history',
140
+ 'navigator',
141
+ 'screen',
142
+ // Dangerous functions
143
+ 'alert',
144
+ 'confirm',
145
+ 'prompt',
146
+ 'eval',
147
+ 'function',
148
+ // Document properties
149
+ 'cookie',
150
+ 'domain',
151
+ 'referrer',
152
+ 'body',
153
+ 'head',
154
+ 'forms',
155
+ 'images',
156
+ 'links',
157
+ 'scripts',
158
+ // DOM traversal properties
159
+ 'children',
160
+ 'parentnode',
161
+ 'firstchild',
162
+ 'lastchild',
163
+ // Content manipulation
164
+ 'innerhtml',
165
+ 'outerhtml',
166
+ 'textcontent',
167
+ ]);
168
+
169
+ /**
170
+ * Default allowed attributes considered safe.
171
+ * Note: 'style' is excluded by default because inline CSS can be abused for:
172
+ * - UI redressing attacks
173
+ * - Data exfiltration via url() in CSS
174
+ * - CSS injection vectors
175
+ * If you need to allow inline styles, add 'style' to allowAttributes in your
176
+ * sanitizeHtml options, but ensure you implement proper CSS value validation.
177
+ */
178
+ export const DEFAULT_ALLOWED_ATTRIBUTES = new Set([
179
+ 'alt',
180
+ 'class',
181
+ 'dir',
182
+ 'height',
183
+ 'hidden',
184
+ 'href',
185
+ 'id',
186
+ 'lang',
187
+ 'loading',
188
+ 'name',
189
+ 'rel',
190
+ 'role',
191
+ 'src',
192
+ 'srcset',
193
+ 'tabindex',
194
+ 'target',
195
+ 'title',
196
+ 'type',
197
+ 'width',
198
+ 'aria-*',
199
+ ]);
200
+
201
+ /**
202
+ * Dangerous attribute prefixes to always remove.
203
+ */
204
+ export const DANGEROUS_ATTR_PREFIXES = ['on', 'formaction', 'xlink:', 'xmlns:'];
205
+
206
+ /**
207
+ * Dangerous URL protocols to block.
208
+ */
209
+ export const DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:', 'file:'];
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Content Security Policy helpers.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ /** Maximum allowed nonce length to prevent memory issues */
8
+ const MAX_NONCE_LENGTH = 1024;
9
+
10
+ /** Chunk size for building strings to avoid argument limit in String.fromCharCode */
11
+ const CHUNK_SIZE = 8192;
12
+
13
+ /**
14
+ * Generate a nonce for inline scripts/styles.
15
+ * Use with Content-Security-Policy nonce directives.
16
+ *
17
+ * @param length - Nonce length in bytes (default: 16, max: 1024)
18
+ * @returns Cryptographically random nonce string
19
+ * @throws {Error} If crypto.getRandomValues or btoa are not available
20
+ * @throws {RangeError} If length is invalid (negative, non-integer, or exceeds maximum)
21
+ */
22
+ export const generateNonce = (length: number = 16): string => {
23
+ // Validate length parameter
24
+ if (!Number.isInteger(length) || length < 1) {
25
+ throw new RangeError('generateNonce length must be a positive integer');
26
+ }
27
+ if (length > MAX_NONCE_LENGTH) {
28
+ throw new RangeError(`generateNonce length must not exceed ${MAX_NONCE_LENGTH}`);
29
+ }
30
+
31
+ // Check for required globals in browser/crypto environments
32
+ if (
33
+ typeof globalThis.crypto === 'undefined' ||
34
+ typeof globalThis.crypto.getRandomValues !== 'function'
35
+ ) {
36
+ throw new Error(
37
+ 'generateNonce requires crypto.getRandomValues (not available in this environment)'
38
+ );
39
+ }
40
+ if (typeof globalThis.btoa !== 'function') {
41
+ throw new Error('generateNonce requires btoa (not available in this environment)');
42
+ }
43
+
44
+ const array = new Uint8Array(length);
45
+ globalThis.crypto.getRandomValues(array);
46
+
47
+ // Build string in chunks to avoid argument limit in String.fromCharCode
48
+ let binaryString = '';
49
+ for (let i = 0; i < array.length; i += CHUNK_SIZE) {
50
+ const chunk = array.subarray(i, Math.min(i + CHUNK_SIZE, array.length));
51
+ binaryString += String.fromCharCode(...chunk);
52
+ }
53
+
54
+ return globalThis.btoa(binaryString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
55
+ };
56
+
57
+ /**
58
+ * Check if a CSP header is present with specific directive.
59
+ * Useful for feature detection and fallback strategies.
60
+ *
61
+ * @param directive - The CSP directive to check (e.g., 'script-src')
62
+ * @returns True if the directive appears to be enforced
63
+ */
64
+ export const hasCSPDirective = (directive: string): boolean => {
65
+ // Guard for non-DOM environments (SSR, tests, etc.)
66
+ if (typeof document === 'undefined') {
67
+ return false;
68
+ }
69
+
70
+ // Check meta tag
71
+ const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
72
+ if (meta) {
73
+ const content = meta.getAttribute('content') ?? '';
74
+ return content.includes(directive);
75
+ }
76
+ return false;
77
+ };
@@ -4,15 +4,7 @@
4
4
  * @module bquery/security
5
5
  */
6
6
 
7
- export {
8
- createTrustedHtml,
9
- escapeHtml,
10
- generateNonce,
11
- getTrustedTypesPolicy,
12
- hasCSPDirective,
13
- isTrustedTypesSupported,
14
- sanitizeHtml as sanitize,
15
- sanitizeHtml,
16
- stripTags,
17
- } from './sanitize';
18
- export type { SanitizeOptions } from './sanitize';
7
+ export { generateNonce, hasCSPDirective } from './csp';
8
+ export { escapeHtml, sanitizeHtml as sanitize, sanitizeHtml, stripTags } from './sanitize';
9
+ export { createTrustedHtml, getTrustedTypesPolicy, isTrustedTypesSupported } from './trusted-types';
10
+ export type { SanitizeOptions } from './types';