@bquery/bquery 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -23
- package/dist/a11y/announce.d.ts +43 -0
- package/dist/a11y/announce.d.ts.map +1 -0
- package/dist/a11y/audit.d.ts +42 -0
- package/dist/a11y/audit.d.ts.map +1 -0
- package/dist/a11y/index.d.ts +53 -0
- package/dist/a11y/index.d.ts.map +1 -0
- package/dist/a11y/media-preferences.d.ts +77 -0
- package/dist/a11y/media-preferences.d.ts.map +1 -0
- package/dist/a11y/roving-tab-index.d.ts +38 -0
- package/dist/a11y/roving-tab-index.d.ts.map +1 -0
- package/dist/a11y/skip-link.d.ts +37 -0
- package/dist/a11y/skip-link.d.ts.map +1 -0
- package/dist/a11y/trap-focus.d.ts +49 -0
- package/dist/a11y/trap-focus.d.ts.map +1 -0
- package/dist/a11y/types.d.ts +152 -0
- package/dist/a11y/types.d.ts.map +1 -0
- package/dist/a11y-C5QOVvRn.js +421 -0
- package/dist/a11y-C5QOVvRn.js.map +1 -0
- package/dist/a11y.es.mjs +14 -0
- package/dist/component/component.d.ts +13 -5
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/html.d.ts +40 -3
- package/dist/component/html.d.ts.map +1 -1
- package/dist/component/index.d.ts +3 -2
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/library.d.ts.map +1 -1
- package/dist/component/scope.d.ts +138 -0
- package/dist/component/scope.d.ts.map +1 -0
- package/dist/component/types.d.ts +184 -17
- package/dist/component/types.d.ts.map +1 -1
- package/dist/component-CuuTijA6.js +684 -0
- package/dist/component-CuuTijA6.js.map +1 -0
- package/dist/component.es.mjs +10 -6
- package/dist/{config-DRmZZno3.js → config-BW35FKuA.js} +4 -4
- package/dist/config-BW35FKuA.js.map +1 -0
- package/dist/constraints-3lV9yyBw.js +100 -0
- package/dist/constraints-3lV9yyBw.js.map +1 -0
- package/dist/core/collection.d.ts +48 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +92 -0
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/env.d.ts +18 -0
- package/dist/core/env.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/shared.d.ts +8 -0
- package/dist/core/shared.d.ts.map +1 -1
- package/dist/core/utils/index.d.ts +52 -41
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core-Cjl7GUu8.js +717 -0
- package/dist/core-Cjl7GUu8.js.map +1 -0
- package/dist/{core-DPdbItcq.js → core-DnlyjbF2.js} +1 -1
- package/dist/{core-DPdbItcq.js.map → core-DnlyjbF2.js.map} +1 -1
- package/dist/core.es.mjs +45 -44
- package/dist/custom-directives-7wAShnnd.js +9 -0
- package/dist/custom-directives-7wAShnnd.js.map +1 -0
- package/dist/devtools/devtools.d.ts +212 -0
- package/dist/devtools/devtools.d.ts.map +1 -0
- package/dist/devtools/index.d.ts +20 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/devtools/types.d.ts +69 -0
- package/dist/devtools/types.d.ts.map +1 -0
- package/dist/devtools-D2fQLhDN.js +122 -0
- package/dist/devtools-D2fQLhDN.js.map +1 -0
- package/dist/devtools.es.mjs +19 -0
- package/dist/dnd/draggable.d.ts +51 -0
- package/dist/dnd/draggable.d.ts.map +1 -0
- package/dist/dnd/droppable.d.ts +38 -0
- package/dist/dnd/droppable.d.ts.map +1 -0
- package/dist/dnd/index.d.ts +47 -0
- package/dist/dnd/index.d.ts.map +1 -0
- package/dist/dnd/sortable.d.ts +43 -0
- package/dist/dnd/sortable.d.ts.map +1 -0
- package/dist/dnd/types.d.ts +250 -0
- package/dist/dnd/types.d.ts.map +1 -0
- package/dist/dnd-B8EgyzaI.js +244 -0
- package/dist/dnd-B8EgyzaI.js.map +1 -0
- package/dist/dnd.es.mjs +6 -0
- package/dist/env-NeVmr4Gf.js +19 -0
- package/dist/env-NeVmr4Gf.js.map +1 -0
- package/dist/forms/create-form.d.ts +49 -0
- package/dist/forms/create-form.d.ts.map +1 -0
- package/dist/forms/index.d.ts +39 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/types.d.ts +139 -0
- package/dist/forms/types.d.ts.map +1 -0
- package/dist/forms/validators.d.ts +179 -0
- package/dist/forms/validators.d.ts.map +1 -0
- package/dist/forms-C3yovgH9.js +141 -0
- package/dist/forms-C3yovgH9.js.map +1 -0
- package/dist/forms.es.mjs +14 -0
- package/dist/full.d.ts +37 -9
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +186 -91
- package/dist/full.iife.js +47 -31
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +47 -31
- package/dist/full.umd.js.map +1 -1
- package/dist/i18n/formatting.d.ts +40 -0
- package/dist/i18n/formatting.d.ts.map +1 -0
- package/dist/i18n/i18n.d.ts +48 -0
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +57 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/translate.d.ts +83 -0
- package/dist/i18n/translate.d.ts.map +1 -0
- package/dist/i18n/types.d.ts +156 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n-BnnhTFOS.js +89 -0
- package/dist/i18n-BnnhTFOS.js.map +1 -0
- package/dist/i18n.es.mjs +6 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.mjs +233 -138
- package/dist/media/battery.d.ts +35 -0
- package/dist/media/battery.d.ts.map +1 -0
- package/dist/media/breakpoints.d.ts +51 -0
- package/dist/media/breakpoints.d.ts.map +1 -0
- package/dist/media/clipboard.d.ts +30 -0
- package/dist/media/clipboard.d.ts.map +1 -0
- package/dist/media/device-sensors.d.ts +54 -0
- package/dist/media/device-sensors.d.ts.map +1 -0
- package/dist/media/geolocation.d.ts +38 -0
- package/dist/media/geolocation.d.ts.map +1 -0
- package/dist/media/index.d.ts +42 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/media-query.d.ts +36 -0
- package/dist/media/media-query.d.ts.map +1 -0
- package/dist/media/network.d.ts +35 -0
- package/dist/media/network.d.ts.map +1 -0
- package/dist/media/types.d.ts +173 -0
- package/dist/media/types.d.ts.map +1 -0
- package/dist/media/viewport.d.ts +32 -0
- package/dist/media/viewport.d.ts.map +1 -0
- package/dist/media-Di2Ta22s.js +340 -0
- package/dist/media-Di2Ta22s.js.map +1 -0
- package/dist/media.es.mjs +12 -0
- package/dist/motion/index.d.ts +7 -3
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/morph.d.ts +27 -0
- package/dist/motion/morph.d.ts.map +1 -0
- package/dist/motion/parallax.d.ts +30 -0
- package/dist/motion/parallax.d.ts.map +1 -0
- package/dist/motion/reduced-motion.d.ts +36 -3
- package/dist/motion/reduced-motion.d.ts.map +1 -1
- package/dist/motion/types.d.ts +58 -0
- package/dist/motion/types.d.ts.map +1 -1
- package/dist/motion/typewriter.d.ts +31 -0
- package/dist/motion/typewriter.d.ts.map +1 -0
- package/dist/motion-qPj_TYGv.js +530 -0
- package/dist/motion-qPj_TYGv.js.map +1 -0
- package/dist/motion.es.mjs +27 -23
- package/dist/mount-SM07RUa6.js +403 -0
- package/dist/mount-SM07RUa6.js.map +1 -0
- package/dist/{object-qGpWr6-J.js → object-BCk-1c8T.js} +5 -4
- package/dist/{object-qGpWr6-J.js.map → object-BCk-1c8T.js.map} +1 -1
- package/dist/{platform-B7JhGBc7.js → platform-CPbCprb6.js} +3 -3
- package/dist/platform-CPbCprb6.js.map +1 -0
- package/dist/platform.es.mjs +2 -2
- package/dist/plugin/index.d.ts +22 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/registry.d.ts +108 -0
- package/dist/plugin/registry.d.ts.map +1 -0
- package/dist/plugin/types.d.ts +110 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin-cPoOHFLY.js +64 -0
- package/dist/plugin-cPoOHFLY.js.map +1 -0
- package/dist/plugin.es.mjs +9 -0
- package/dist/reactive/computed.d.ts +7 -0
- package/dist/reactive/computed.d.ts.map +1 -1
- package/dist/reactive-Cfv0RK6x.js +233 -0
- package/dist/reactive-Cfv0RK6x.js.map +1 -0
- package/dist/reactive.es.mjs +18 -17
- package/dist/registry-CWf368tT.js +26 -0
- package/dist/registry-CWf368tT.js.map +1 -0
- package/dist/router/bq-link.d.ts +112 -0
- package/dist/router/bq-link.d.ts.map +1 -0
- package/dist/router/constraints.d.ts +9 -0
- package/dist/router/constraints.d.ts.map +1 -0
- package/dist/router/index.d.ts +14 -6
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/match.d.ts +0 -1
- package/dist/router/match.d.ts.map +1 -1
- package/dist/router/path-pattern.d.ts +14 -0
- package/dist/router/path-pattern.d.ts.map +1 -0
- package/dist/router/query.d.ts.map +1 -1
- package/dist/router/router.d.ts +3 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/types.d.ts +48 -4
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/use-route.d.ts +50 -0
- package/dist/router/use-route.d.ts.map +1 -0
- package/dist/router/utils.d.ts +3 -0
- package/dist/router/utils.d.ts.map +1 -1
- package/dist/router-BrthaP_z.js +473 -0
- package/dist/router-BrthaP_z.js.map +1 -0
- package/dist/router.es.mjs +13 -10
- package/dist/{sanitize-jyJ2ryE2.js → sanitize-B1V4JswB.js} +95 -83
- package/dist/sanitize-B1V4JswB.js.map +1 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/sanitize.d.ts +4 -1
- package/dist/security/sanitize.d.ts.map +1 -1
- package/dist/security/trusted-html.d.ts +53 -0
- package/dist/security/trusted-html.d.ts.map +1 -0
- package/dist/security.es.mjs +10 -9
- package/dist/ssr/hydrate.d.ts +65 -0
- package/dist/ssr/hydrate.d.ts.map +1 -0
- package/dist/ssr/index.d.ts +59 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/render.d.ts +62 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/serialize.d.ts +118 -0
- package/dist/ssr/serialize.d.ts.map +1 -0
- package/dist/ssr/types.d.ts +70 -0
- package/dist/ssr/types.d.ts.map +1 -0
- package/dist/ssr-B2qd_WBB.js +248 -0
- package/dist/ssr-B2qd_WBB.js.map +1 -0
- package/dist/ssr.es.mjs +9 -0
- package/dist/store/create-store.d.ts.map +1 -1
- package/dist/store/define-store.d.ts +1 -1
- package/dist/store/define-store.d.ts.map +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/mapping.d.ts +1 -1
- package/dist/store/mapping.d.ts.map +1 -1
- package/dist/store/persisted.d.ts +38 -4
- package/dist/store/persisted.d.ts.map +1 -1
- package/dist/store/types.d.ts +140 -3
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/utils.d.ts +2 -2
- package/dist/store/utils.d.ts.map +1 -1
- package/dist/store/watch.d.ts +1 -1
- package/dist/store/watch.d.ts.map +1 -1
- package/dist/store-DWpyH6p5.js +338 -0
- package/dist/store-DWpyH6p5.js.map +1 -0
- package/dist/store.es.mjs +11 -10
- package/dist/storybook/index.d.ts +37 -0
- package/dist/storybook/index.d.ts.map +1 -0
- package/dist/storybook.es.mjs +151 -0
- package/dist/storybook.es.mjs.map +1 -0
- package/dist/testing/index.d.ts +23 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/testing.d.ts +156 -0
- package/dist/testing/testing.d.ts.map +1 -0
- package/dist/testing/types.d.ts +134 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing-CsqjNUyy.js +224 -0
- package/dist/testing-CsqjNUyy.js.map +1 -0
- package/dist/testing.es.mjs +9 -0
- package/dist/type-guards-Do9DWgNp.js +44 -0
- package/dist/type-guards-Do9DWgNp.js.map +1 -0
- package/dist/untrack-DJVQQ2WM.js +33 -0
- package/dist/untrack-DJVQQ2WM.js.map +1 -0
- package/dist/view/custom-directives.d.ts +20 -0
- package/dist/view/custom-directives.d.ts.map +1 -0
- package/dist/view/evaluate.d.ts.map +1 -1
- package/dist/view/process.d.ts.map +1 -1
- package/dist/view.es.mjs +11 -10
- package/package.json +52 -11
- package/src/a11y/announce.ts +131 -0
- package/src/a11y/audit.ts +314 -0
- package/src/a11y/index.ts +68 -0
- package/src/a11y/media-preferences.ts +255 -0
- package/src/a11y/roving-tab-index.ts +164 -0
- package/src/a11y/skip-link.ts +255 -0
- package/src/a11y/trap-focus.ts +184 -0
- package/src/a11y/types.ts +183 -0
- package/src/component/component.ts +345 -65
- package/src/component/html.ts +153 -53
- package/src/component/index.ts +12 -2
- package/src/component/library.ts +66 -28
- package/src/component/scope.ts +212 -0
- package/src/component/types.ts +238 -19
- package/src/core/collection.ts +707 -628
- package/src/core/element.ts +981 -774
- package/src/core/env.ts +60 -0
- package/src/core/index.ts +49 -48
- package/src/core/shared.ts +62 -13
- package/src/core/utils/index.ts +148 -83
- package/src/devtools/devtools.ts +410 -0
- package/src/devtools/index.ts +48 -0
- package/src/devtools/types.ts +104 -0
- package/src/dnd/draggable.ts +296 -0
- package/src/dnd/droppable.ts +228 -0
- package/src/dnd/index.ts +62 -0
- package/src/dnd/sortable.ts +307 -0
- package/src/dnd/types.ts +293 -0
- package/src/forms/create-form.ts +278 -0
- package/src/forms/index.ts +65 -0
- package/src/forms/types.ts +154 -0
- package/src/forms/validators.ts +265 -0
- package/src/full.ts +260 -3
- package/src/i18n/formatting.ts +67 -0
- package/src/i18n/i18n.ts +200 -0
- package/src/i18n/index.ts +67 -0
- package/src/i18n/translate.ts +182 -0
- package/src/i18n/types.ts +171 -0
- package/src/index.ts +108 -36
- package/src/media/battery.ts +116 -0
- package/src/media/breakpoints.ts +131 -0
- package/src/media/clipboard.ts +80 -0
- package/src/media/device-sensors.ts +158 -0
- package/src/media/geolocation.ts +119 -0
- package/src/media/index.ts +76 -0
- package/src/media/media-query.ts +92 -0
- package/src/media/network.ts +115 -0
- package/src/media/types.ts +177 -0
- package/src/media/viewport.ts +84 -0
- package/src/motion/index.ts +57 -48
- package/src/motion/morph.ts +151 -0
- package/src/motion/parallax.ts +120 -0
- package/src/motion/reduced-motion.ts +66 -17
- package/src/motion/transition.ts +97 -97
- package/src/motion/types.ts +63 -0
- package/src/motion/typewriter.ts +164 -0
- package/src/platform/announcer.ts +208 -208
- package/src/platform/config.ts +163 -163
- package/src/platform/cookies.ts +165 -165
- package/src/platform/index.ts +39 -39
- package/src/platform/meta.ts +168 -168
- package/src/plugin/index.ts +37 -0
- package/src/plugin/registry.ts +269 -0
- package/src/plugin/types.ts +137 -0
- package/src/reactive/async-data.ts +486 -486
- package/src/reactive/computed.ts +130 -92
- package/src/reactive/index.ts +37 -37
- package/src/reactive/signal.ts +29 -29
- package/src/router/bq-link.ts +279 -0
- package/src/router/constraints.ts +201 -0
- package/src/router/index.ts +49 -41
- package/src/router/match.ts +312 -106
- package/src/router/path-pattern.ts +52 -0
- package/src/router/query.ts +38 -35
- package/src/router/router.ts +402 -211
- package/src/router/types.ts +139 -93
- package/src/router/use-route.ts +68 -0
- package/src/router/utils.ts +157 -116
- package/src/security/constants.ts +211 -211
- package/src/security/index.ts +12 -10
- package/src/security/sanitize.ts +6 -2
- package/src/security/trusted-html.ts +71 -0
- package/src/ssr/hydrate.ts +82 -0
- package/src/ssr/index.ts +70 -0
- package/src/ssr/render.ts +508 -0
- package/src/ssr/serialize.ts +296 -0
- package/src/ssr/types.ts +81 -0
- package/src/store/create-store.ts +467 -329
- package/src/store/define-store.ts +2 -1
- package/src/store/index.ts +27 -22
- package/src/store/mapping.ts +2 -1
- package/src/store/persisted.ts +249 -61
- package/src/store/types.ts +247 -94
- package/src/store/utils.ts +135 -141
- package/src/store/watch.ts +2 -1
- package/src/storybook/index.ts +480 -0
- package/src/testing/index.ts +42 -0
- package/src/testing/testing.ts +593 -0
- package/src/testing/types.ts +170 -0
- package/src/view/custom-directives.ts +30 -0
- package/src/view/evaluate.ts +292 -290
- package/src/view/process.ts +108 -92
- package/dist/component-CY5MVoYN.js +0 -531
- package/dist/component-CY5MVoYN.js.map +0 -1
- package/dist/config-DRmZZno3.js.map +0 -1
- package/dist/core-CK2Mfpf4.js +0 -648
- package/dist/core-CK2Mfpf4.js.map +0 -1
- package/dist/motion-C5DRdPnO.js +0 -415
- package/dist/motion-C5DRdPnO.js.map +0 -1
- package/dist/platform-B7JhGBc7.js.map +0 -1
- package/dist/reactive-BDya-ia8.js +0 -253
- package/dist/reactive-BDya-ia8.js.map +0 -1
- package/dist/router-CijiICxt.js +0 -188
- package/dist/router-CijiICxt.js.map +0 -1
- package/dist/sanitize-jyJ2ryE2.js.map +0 -1
- package/dist/store-CPK9E62U.js +0 -262
- package/dist/store-CPK9E62U.js.map +0 -1
- package/dist/view-Cdi0g-qo.js +0 -396
- package/dist/view-Cdi0g-qo.js.map +0 -1
package/src/component/html.ts
CHANGED
|
@@ -1,53 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
import {
|
|
2
|
+
isTrustedHtml,
|
|
3
|
+
type SanitizedHtml,
|
|
4
|
+
toSanitizedHtml,
|
|
5
|
+
unwrapTrustedHtml,
|
|
6
|
+
} from '../security/trusted-html';
|
|
7
|
+
const BOOLEAN_ATTRIBUTE_MARKER: unique symbol = Symbol('bquery.booleanAttribute');
|
|
8
|
+
const BOOLEAN_ATTRIBUTE_NAME = /^[^\0-\x20"'/>=]+$/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Public shape of a boolean HTML attribute created by {@link bool}.
|
|
12
|
+
*
|
|
13
|
+
* This type is returned from {@link bool} and can be interpolated into
|
|
14
|
+
* {@link html} / {@link safeHtml} templates to conditionally include or omit
|
|
15
|
+
* an attribute by name. The internal marker property used for runtime checks
|
|
16
|
+
* remains private and is not part of the public API.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const disabled = bool('disabled', isDisabled);
|
|
21
|
+
* const button = html`<button ${disabled}>Click</button>`;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface BooleanAttribute {
|
|
25
|
+
readonly enabled: boolean;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface BooleanAttributeValue extends BooleanAttribute {
|
|
30
|
+
readonly [BOOLEAN_ATTRIBUTE_MARKER]: true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isBooleanAttributeValue = (value: unknown): value is BooleanAttributeValue => {
|
|
34
|
+
if (typeof value !== 'object' || value === null) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const candidate = value as Partial<BooleanAttributeValue>;
|
|
39
|
+
return (
|
|
40
|
+
candidate[BOOLEAN_ATTRIBUTE_MARKER] === true &&
|
|
41
|
+
typeof candidate.enabled === 'boolean' &&
|
|
42
|
+
typeof candidate.name === 'string'
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const stringifyTemplateValue = (value: unknown): string => {
|
|
47
|
+
if (isBooleanAttributeValue(value)) {
|
|
48
|
+
return value.enabled ? value.name : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return String(value ?? '');
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const escapeMap: Record<string, string> = {
|
|
55
|
+
'&': '&',
|
|
56
|
+
'<': '<',
|
|
57
|
+
'>': '>',
|
|
58
|
+
'"': '"',
|
|
59
|
+
"'": ''',
|
|
60
|
+
'`': '`',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const escapeTemplateValue = (value: unknown): string => {
|
|
64
|
+
if (isBooleanAttributeValue(value)) {
|
|
65
|
+
return value.enabled ? value.name : '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return stringifyTemplateValue(value).replace(/[&<>"'`]/g, (char) => escapeMap[char]);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a boolean-attribute marker for the {@link html} and {@link safeHtml} template tags.
|
|
73
|
+
*
|
|
74
|
+
* When the condition is truthy, the attribute name is rendered without a value.
|
|
75
|
+
* When the condition is falsy, an empty string is rendered and any surrounding
|
|
76
|
+
* template-literal whitespace is preserved.
|
|
77
|
+
*
|
|
78
|
+
* @param name - HTML attribute name to emit
|
|
79
|
+
* @param enabled - Whether the boolean attribute should be present
|
|
80
|
+
* @returns Internal marker consumed by template tags
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* html`<button ${bool('disabled', isDisabled)}>Save</button>`;
|
|
85
|
+
* // Result when isDisabled = true: '<button disabled>Save</button>'
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export const bool = (name: string, enabled: unknown): BooleanAttribute => {
|
|
89
|
+
if (!BOOLEAN_ATTRIBUTE_NAME.test(name)) {
|
|
90
|
+
throw new TypeError(`Invalid boolean attribute name: ${name}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const attribute: BooleanAttributeValue = {
|
|
94
|
+
[BOOLEAN_ATTRIBUTE_MARKER]: true,
|
|
95
|
+
enabled: Boolean(enabled),
|
|
96
|
+
name,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return Object.freeze(attribute);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Tagged template literal for creating HTML strings.
|
|
104
|
+
*
|
|
105
|
+
* This function handles interpolation of values into HTML templates,
|
|
106
|
+
* converting null/undefined to empty strings.
|
|
107
|
+
*
|
|
108
|
+
* @param strings - Template literal string parts
|
|
109
|
+
* @param values - Interpolated values
|
|
110
|
+
* @returns Combined HTML string
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const name = 'World';
|
|
115
|
+
* const greeting = html`<h1>Hello, ${name}!</h1>`;
|
|
116
|
+
* // Result: '<h1>Hello, World!</h1>'
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {
|
|
120
|
+
return strings.reduce(
|
|
121
|
+
(acc, part, index) => `${acc}${part}${stringifyTemplateValue(values[index])}`,
|
|
122
|
+
''
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Escapes HTML entities in interpolated values for XSS prevention.
|
|
128
|
+
* Use this when you need to safely embed user content in templates.
|
|
129
|
+
*
|
|
130
|
+
* @param strings - Template literal string parts
|
|
131
|
+
* @param values - Interpolated values to escape
|
|
132
|
+
* @returns Branded escaped HTML string safe for bQuery template composition
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const userInput = '<script>alert("xss")</script>';
|
|
137
|
+
* const safe = safeHtml`<div>${userInput}</div>`;
|
|
138
|
+
* // Result: '<div><script>alert("xss")</script></div>'
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export const safeHtml = (strings: TemplateStringsArray, ...values: unknown[]): SanitizedHtml => {
|
|
142
|
+
const escape = (value: unknown): string => {
|
|
143
|
+
if (isTrustedHtml(value)) return unwrapTrustedHtml(value);
|
|
144
|
+
return escapeTemplateValue(value);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return toSanitizedHtml(
|
|
148
|
+
strings.reduce(
|
|
149
|
+
(acc, part, index) => `${acc}${part}${index < values.length ? escape(values[index]) : ''}`,
|
|
150
|
+
''
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
};
|
package/src/component/index.ts
CHANGED
|
@@ -36,7 +36,17 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
export { component, defineComponent } from './component';
|
|
39
|
-
export { html, safeHtml } from './html';
|
|
39
|
+
export { bool, html, safeHtml } from './html';
|
|
40
40
|
export { registerDefaultComponents } from './library';
|
|
41
|
+
export { useComputed, useEffect, useSignal } from './scope';
|
|
41
42
|
export type { DefaultComponentLibraryOptions, RegisteredDefaultComponents } from './library';
|
|
42
|
-
export type {
|
|
43
|
+
export type {
|
|
44
|
+
AttributeChange,
|
|
45
|
+
ComponentDefinition,
|
|
46
|
+
ComponentRenderContext,
|
|
47
|
+
ComponentStateKey,
|
|
48
|
+
ComponentSignalLike,
|
|
49
|
+
ComponentSignals,
|
|
50
|
+
PropDefinition,
|
|
51
|
+
ShadowMode,
|
|
52
|
+
} from './types';
|
package/src/component/library.ts
CHANGED
|
@@ -87,20 +87,25 @@ const storeHandler = (element: HTMLElement, key: string, value: EventListener):
|
|
|
87
87
|
handlerStore.set(element, handlers);
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
const getShadowLabelText = (element: HTMLElement): string => {
|
|
91
|
-
return element.shadowRoot?.querySelector('.label')?.textContent ?? '';
|
|
92
|
-
};
|
|
93
|
-
|
|
94
90
|
/**
|
|
95
91
|
* Detect a value-only input update, patch the live control in place, and
|
|
96
92
|
* return whether the component can skip a full shadow DOM re-render.
|
|
97
93
|
*
|
|
98
94
|
* @param element - The host custom element whose shadow DOM is being updated
|
|
99
|
-
* @param
|
|
95
|
+
* @param newProps - The next reflected input props for the pending update
|
|
96
|
+
* @param oldProps - The previous reflected input props from the last render
|
|
100
97
|
*/
|
|
101
98
|
const canSkipInputRender = (
|
|
102
99
|
element: HTMLElement,
|
|
103
|
-
|
|
100
|
+
newProps: {
|
|
101
|
+
label: string;
|
|
102
|
+
type: string;
|
|
103
|
+
value: string;
|
|
104
|
+
placeholder: string;
|
|
105
|
+
name: string;
|
|
106
|
+
disabled: boolean;
|
|
107
|
+
},
|
|
108
|
+
oldProps: {
|
|
104
109
|
label: string;
|
|
105
110
|
type: string;
|
|
106
111
|
value: string;
|
|
@@ -109,17 +114,29 @@ const canSkipInputRender = (
|
|
|
109
114
|
disabled: boolean;
|
|
110
115
|
}
|
|
111
116
|
): boolean => {
|
|
112
|
-
|
|
117
|
+
if (oldProps.label !== newProps.label) return false;
|
|
118
|
+
if (oldProps.type !== newProps.type) return false;
|
|
119
|
+
if (oldProps.placeholder !== newProps.placeholder) return false;
|
|
120
|
+
if (oldProps.name !== newProps.name) return false;
|
|
121
|
+
if (oldProps.disabled !== newProps.disabled) return false;
|
|
122
|
+
|
|
123
|
+
// Verify shadow DOM still matches expected non-value props before skipping re-render
|
|
124
|
+
const shadowRoot = element.shadowRoot;
|
|
125
|
+
if (!shadowRoot) return false;
|
|
126
|
+
|
|
127
|
+
const labelEl = shadowRoot.querySelector('.label');
|
|
128
|
+
if ((labelEl?.textContent ?? '') !== newProps.label) return false;
|
|
129
|
+
|
|
130
|
+
const control = shadowRoot.querySelector('input.control') as HTMLInputElement | null;
|
|
113
131
|
if (!control) return false;
|
|
114
132
|
|
|
115
|
-
if (
|
|
116
|
-
if (
|
|
117
|
-
if (
|
|
118
|
-
if (
|
|
119
|
-
if (control.disabled !== props.disabled) return false;
|
|
133
|
+
if (control.type !== newProps.type) return false;
|
|
134
|
+
if (control.placeholder !== newProps.placeholder) return false;
|
|
135
|
+
if (control.name !== newProps.name) return false;
|
|
136
|
+
if (control.disabled !== newProps.disabled) return false;
|
|
120
137
|
|
|
121
|
-
if (control.value !==
|
|
122
|
-
control.value =
|
|
138
|
+
if (control.value !== newProps.value) {
|
|
139
|
+
control.value = newProps.value;
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
return true;
|
|
@@ -130,11 +147,20 @@ const canSkipInputRender = (
|
|
|
130
147
|
* return whether the component can skip a full shadow DOM re-render.
|
|
131
148
|
*
|
|
132
149
|
* @param element - The host custom element whose shadow DOM is being updated
|
|
133
|
-
* @param
|
|
150
|
+
* @param newProps - The next reflected textarea props for the pending update
|
|
151
|
+
* @param oldProps - The previous reflected textarea props from the last render
|
|
134
152
|
*/
|
|
135
153
|
const canSkipTextareaRender = (
|
|
136
154
|
element: HTMLElement,
|
|
137
|
-
|
|
155
|
+
newProps: {
|
|
156
|
+
label: string;
|
|
157
|
+
value: string;
|
|
158
|
+
placeholder: string;
|
|
159
|
+
name: string;
|
|
160
|
+
rows: number;
|
|
161
|
+
disabled: boolean;
|
|
162
|
+
},
|
|
163
|
+
oldProps: {
|
|
138
164
|
label: string;
|
|
139
165
|
value: string;
|
|
140
166
|
placeholder: string;
|
|
@@ -143,19 +169,31 @@ const canSkipTextareaRender = (
|
|
|
143
169
|
disabled: boolean;
|
|
144
170
|
}
|
|
145
171
|
): boolean => {
|
|
146
|
-
|
|
172
|
+
if (oldProps.label !== newProps.label) return false;
|
|
173
|
+
if (oldProps.placeholder !== newProps.placeholder) return false;
|
|
174
|
+
if (oldProps.name !== newProps.name) return false;
|
|
175
|
+
if (oldProps.rows !== newProps.rows) return false;
|
|
176
|
+
if (oldProps.disabled !== newProps.disabled) return false;
|
|
177
|
+
|
|
178
|
+
// Verify shadow DOM still matches expected non-value props before skipping re-render
|
|
179
|
+
const shadowRoot = element.shadowRoot;
|
|
180
|
+
if (!shadowRoot) return false;
|
|
181
|
+
|
|
182
|
+
const labelEl = shadowRoot.querySelector('.label');
|
|
183
|
+
if ((labelEl?.textContent ?? '') !== newProps.label) return false;
|
|
184
|
+
|
|
185
|
+
const control = shadowRoot.querySelector(
|
|
147
186
|
'textarea.control'
|
|
148
187
|
) as HTMLTextAreaElement | null;
|
|
149
188
|
if (!control) return false;
|
|
150
189
|
|
|
151
|
-
if (
|
|
152
|
-
if (
|
|
153
|
-
if ((control.
|
|
154
|
-
if (control.
|
|
155
|
-
if (control.disabled !== props.disabled) return false;
|
|
190
|
+
if (control.placeholder !== newProps.placeholder) return false;
|
|
191
|
+
if (control.name !== newProps.name) return false;
|
|
192
|
+
if (Number(control.rows) !== newProps.rows) return false;
|
|
193
|
+
if (control.disabled !== newProps.disabled) return false;
|
|
156
194
|
|
|
157
|
-
if (control.value !==
|
|
158
|
-
control.value =
|
|
195
|
+
if (control.value !== newProps.value) {
|
|
196
|
+
control.value = newProps.value;
|
|
159
197
|
}
|
|
160
198
|
return true;
|
|
161
199
|
};
|
|
@@ -328,8 +366,8 @@ export const registerDefaultComponents = (
|
|
|
328
366
|
* Skip the full shadow DOM re-render when only the reflected input value
|
|
329
367
|
* changed, because the live control has already been patched in place.
|
|
330
368
|
*/
|
|
331
|
-
beforeUpdate(
|
|
332
|
-
if (canSkipInputRender(this,
|
|
369
|
+
beforeUpdate(newProps, oldProps) {
|
|
370
|
+
if (canSkipInputRender(this, newProps, oldProps)) {
|
|
333
371
|
return false;
|
|
334
372
|
}
|
|
335
373
|
return true;
|
|
@@ -399,8 +437,8 @@ export const registerDefaultComponents = (
|
|
|
399
437
|
* Skip the full shadow DOM re-render when only the reflected textarea value
|
|
400
438
|
* changed, because the live control has already been patched in place.
|
|
401
439
|
*/
|
|
402
|
-
beforeUpdate(
|
|
403
|
-
if (canSkipTextareaRender(this,
|
|
440
|
+
beforeUpdate(newProps, oldProps) {
|
|
441
|
+
if (canSkipTextareaRender(this, newProps, oldProps)) {
|
|
404
442
|
return false;
|
|
405
443
|
}
|
|
406
444
|
return true;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-scoped reactive primitives.
|
|
3
|
+
*
|
|
4
|
+
* Provides `useSignal`, `useComputed`, and `useEffect` that automatically
|
|
5
|
+
* dispose when their owning component disconnects from the DOM.
|
|
6
|
+
*
|
|
7
|
+
* @module bquery/component
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Computed } from '../reactive/computed';
|
|
11
|
+
import { computed } from '../reactive/computed';
|
|
12
|
+
import { detectDevEnvironment } from '../core/env';
|
|
13
|
+
import type { Signal } from '../reactive/core';
|
|
14
|
+
import { signal } from '../reactive/core';
|
|
15
|
+
import { effect } from '../reactive/effect';
|
|
16
|
+
import type { CleanupFn } from '../reactive/index';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Holds disposable resources created inside a component scope.
|
|
20
|
+
* All registered disposers run when the component disconnects.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export interface ComponentScope {
|
|
24
|
+
/** Register a cleanup function to run on dispose */
|
|
25
|
+
addDisposer(fn: CleanupFn): void;
|
|
26
|
+
/** Dispose all registered resources */
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Currently active component scope. @internal */
|
|
31
|
+
let currentScope: ComponentScope | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sets the active component scope.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function setCurrentScope(scope: ComponentScope | undefined): ComponentScope | undefined {
|
|
38
|
+
const previousScope = currentScope;
|
|
39
|
+
currentScope = scope;
|
|
40
|
+
return previousScope;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns the active component scope, or undefined if none.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function getCurrentScope(): ComponentScope | undefined {
|
|
48
|
+
return currentScope;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new component scope that tracks disposable resources.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function createComponentScope(): ComponentScope {
|
|
56
|
+
const disposers: CleanupFn[] = [];
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
addDisposer(fn: CleanupFn): void {
|
|
60
|
+
disposers.push(fn);
|
|
61
|
+
},
|
|
62
|
+
dispose(): void {
|
|
63
|
+
for (const fn of disposers) {
|
|
64
|
+
try {
|
|
65
|
+
fn();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (
|
|
68
|
+
detectDevEnvironment() &&
|
|
69
|
+
typeof console !== 'undefined' &&
|
|
70
|
+
typeof console.error === 'function'
|
|
71
|
+
) {
|
|
72
|
+
console.error('bQuery component: Error disposing scoped resource', error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
disposers.length = 0;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Creates a reactive signal scoped to the current component.
|
|
83
|
+
*
|
|
84
|
+
* The signal is automatically disposed when the component disconnects
|
|
85
|
+
* from the DOM, removing all subscribers and preventing memory leaks.
|
|
86
|
+
*
|
|
87
|
+
* Must be called during a component lifecycle hook such as `connected`,
|
|
88
|
+
* `beforeMount`, `onAdopted`, or `onAttributeChanged`.
|
|
89
|
+
*
|
|
90
|
+
* Do not create scoped primitives from `render()`. Repeated renders can
|
|
91
|
+
* accumulate render-scoped resources until disconnect; prefer lifecycle hooks.
|
|
92
|
+
*
|
|
93
|
+
* @template T - The type of the signal value
|
|
94
|
+
* @param initialValue - The initial value of the signal
|
|
95
|
+
* @returns A new Signal instance that auto-disposes with the component
|
|
96
|
+
* @throws {Error} If called outside a component scope
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* import { component, html, useSignal } from '@bquery/bquery/component';
|
|
101
|
+
*
|
|
102
|
+
* component('my-counter', {
|
|
103
|
+
* connected() {
|
|
104
|
+
* const count = useSignal(0);
|
|
105
|
+
* // count.dispose() is called automatically on disconnect
|
|
106
|
+
* },
|
|
107
|
+
* render({ state }) {
|
|
108
|
+
* return html`<span>${state.count}</span>`;
|
|
109
|
+
* },
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function useSignal<T>(initialValue: T): Signal<T> {
|
|
114
|
+
const scope = currentScope;
|
|
115
|
+
if (!scope) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
'bQuery component: useSignal() must be called inside a component lifecycle hook. Avoid calling it directly from render()'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const s = signal(initialValue);
|
|
121
|
+
scope.addDisposer(() => s.dispose());
|
|
122
|
+
return s;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a computed value scoped to the current component.
|
|
127
|
+
*
|
|
128
|
+
* The computed value's internal effect is automatically cleaned up
|
|
129
|
+
* when the component disconnects from the DOM.
|
|
130
|
+
*
|
|
131
|
+
* Must be called during a component lifecycle hook such as `connected`,
|
|
132
|
+
* `beforeMount`, `onAdopted`, or `onAttributeChanged`.
|
|
133
|
+
*
|
|
134
|
+
* Do not create scoped primitives from `render()`. Repeated renders can
|
|
135
|
+
* accumulate render-scoped resources until disconnect; prefer lifecycle hooks.
|
|
136
|
+
*
|
|
137
|
+
* @template T - The type of the computed value
|
|
138
|
+
* @param fn - Derivation function that reads reactive sources
|
|
139
|
+
* @returns A new Computed instance that auto-cleans-up with the component
|
|
140
|
+
* @throws {Error} If called outside a component scope
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { component, html, useSignal, useComputed } from '@bquery/bquery/component';
|
|
145
|
+
*
|
|
146
|
+
* component('my-doubler', {
|
|
147
|
+
* connected() {
|
|
148
|
+
* const count = useSignal(1);
|
|
149
|
+
* const doubled = useComputed(() => count.value * 2);
|
|
150
|
+
* },
|
|
151
|
+
* render({ state }) {
|
|
152
|
+
* return html`<span>${state.doubled}</span>`;
|
|
153
|
+
* },
|
|
154
|
+
* });
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export function useComputed<T>(fn: () => T): Computed<T> {
|
|
158
|
+
const scope = currentScope;
|
|
159
|
+
if (!scope) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'bQuery component: useComputed() must be called inside a component lifecycle hook. Avoid calling it directly from render()'
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const c = computed(fn);
|
|
165
|
+
scope.addDisposer(() => c.dispose());
|
|
166
|
+
return c;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Creates a side effect scoped to the current component.
|
|
171
|
+
*
|
|
172
|
+
* The effect runs immediately and re-runs when its reactive dependencies
|
|
173
|
+
* change. It is automatically disposed when the component disconnects
|
|
174
|
+
* from the DOM.
|
|
175
|
+
*
|
|
176
|
+
* Must be called during a component lifecycle hook such as `connected`,
|
|
177
|
+
* `beforeMount`, `onAdopted`, or `onAttributeChanged`.
|
|
178
|
+
*
|
|
179
|
+
* Do not create scoped primitives from `render()`. Repeated renders can
|
|
180
|
+
* accumulate render-scoped resources until disconnect; prefer lifecycle hooks.
|
|
181
|
+
*
|
|
182
|
+
* @param fn - The effect function; may return a cleanup function
|
|
183
|
+
* @returns A cleanup function to manually stop the effect early
|
|
184
|
+
* @throws {Error} If called outside a component scope
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* import { component, useSignal, useEffect } from '@bquery/bquery/component';
|
|
189
|
+
*
|
|
190
|
+
* component('my-logger', {
|
|
191
|
+
* connected() {
|
|
192
|
+
* const count = useSignal(0);
|
|
193
|
+
* useEffect(() => {
|
|
194
|
+
* console.log('Count changed:', count.value);
|
|
195
|
+
* return () => console.log('Cleanup');
|
|
196
|
+
* });
|
|
197
|
+
* },
|
|
198
|
+
* render() { return '<p>Logger</p>'; },
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export function useEffect(fn: () => void | CleanupFn): CleanupFn {
|
|
203
|
+
const scope = currentScope;
|
|
204
|
+
if (!scope) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
'bQuery component: useEffect() must be called inside a component lifecycle hook. Avoid calling it directly from render()'
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const cleanup = effect(fn);
|
|
210
|
+
scope.addDisposer(cleanup);
|
|
211
|
+
return cleanup;
|
|
212
|
+
}
|