@bquery/bquery 1.2.0 → 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.
- package/README.md +501 -427
- package/dist/batch-4LAvfLE7.js +13 -0
- package/dist/batch-4LAvfLE7.js.map +1 -0
- package/dist/component/component.d.ts +69 -0
- package/dist/component/component.d.ts.map +1 -0
- package/dist/component/html.d.ts +35 -0
- package/dist/component/html.d.ts.map +1 -0
- package/dist/component/index.d.ts +3 -126
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/props.d.ts +18 -0
- package/dist/component/props.d.ts.map +1 -0
- package/dist/component/types.d.ts +77 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component.es.mjs +90 -59
- package/dist/component.es.mjs.map +1 -1
- package/dist/core/collection.d.ts +36 -0
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/dom.d.ts +6 -0
- package/dist/core/dom.d.ts.map +1 -0
- package/dist/core/element.d.ts +8 -0
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/utils/array.d.ts +74 -0
- package/dist/core/utils/array.d.ts.map +1 -0
- package/dist/core/utils/function.d.ts +70 -0
- package/dist/core/utils/function.d.ts.map +1 -0
- package/dist/core/utils/index.d.ts +70 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/misc.d.ts +63 -0
- package/dist/core/utils/misc.d.ts.map +1 -0
- package/dist/core/utils/number.d.ts +65 -0
- package/dist/core/utils/number.d.ts.map +1 -0
- package/dist/core/utils/object.d.ts +133 -0
- package/dist/core/utils/object.d.ts.map +1 -0
- package/dist/core/utils/string.d.ts +80 -0
- package/dist/core/utils/string.d.ts.map +1 -0
- package/dist/core/utils/type-guards.d.ts +79 -0
- package/dist/core/utils/type-guards.d.ts.map +1 -0
- package/dist/core-COenAZjD.js +145 -0
- package/dist/core-COenAZjD.js.map +1 -0
- package/dist/core.es.mjs +411 -448
- package/dist/core.es.mjs.map +1 -1
- package/dist/full.d.ts +2 -2
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +87 -64
- package/dist/full.es.mjs.map +1 -1
- package/dist/full.iife.js +2 -2
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +2 -2
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +138 -68
- package/dist/index.es.mjs.map +1 -1
- package/dist/motion/animate.d.ts +25 -0
- package/dist/motion/animate.d.ts.map +1 -0
- package/dist/motion/easing.d.ts +30 -0
- package/dist/motion/easing.d.ts.map +1 -0
- package/dist/motion/flip.d.ts +55 -0
- package/dist/motion/flip.d.ts.map +1 -0
- package/dist/motion/index.d.ts +11 -138
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/keyframes.d.ts +21 -0
- package/dist/motion/keyframes.d.ts.map +1 -0
- package/dist/motion/reduced-motion.d.ts +12 -0
- package/dist/motion/reduced-motion.d.ts.map +1 -0
- package/dist/motion/scroll.d.ts +15 -0
- package/dist/motion/scroll.d.ts.map +1 -0
- package/dist/motion/spring.d.ts +42 -0
- package/dist/motion/spring.d.ts.map +1 -0
- package/dist/motion/stagger.d.ts +22 -0
- package/dist/motion/stagger.d.ts.map +1 -0
- package/dist/motion/timeline.d.ts +21 -0
- package/dist/motion/timeline.d.ts.map +1 -0
- package/dist/motion/transition.d.ts +22 -0
- package/dist/motion/transition.d.ts.map +1 -0
- package/dist/motion/types.d.ts +182 -0
- package/dist/motion/types.d.ts.map +1 -0
- package/dist/motion.es.mjs +320 -61
- package/dist/motion.es.mjs.map +1 -1
- package/dist/persisted-Dz_ryNuC.js +278 -0
- package/dist/persisted-Dz_ryNuC.js.map +1 -0
- package/dist/reactive/batch.d.ts +13 -0
- package/dist/reactive/batch.d.ts.map +1 -0
- package/dist/reactive/computed.d.ts +50 -0
- package/dist/reactive/computed.d.ts.map +1 -0
- package/dist/reactive/core.d.ts +60 -0
- package/dist/reactive/core.d.ts.map +1 -0
- package/dist/reactive/effect.d.ts +15 -0
- package/dist/reactive/effect.d.ts.map +1 -0
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +36 -0
- package/dist/reactive/internals.d.ts.map +1 -0
- package/dist/reactive/linked.d.ts +36 -0
- package/dist/reactive/linked.d.ts.map +1 -0
- package/dist/reactive/persisted.d.ts +14 -0
- package/dist/reactive/persisted.d.ts.map +1 -0
- package/dist/reactive/readonly.d.ts +26 -0
- package/dist/reactive/readonly.d.ts.map +1 -0
- package/dist/reactive/signal.d.ts +13 -312
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive/type-guards.d.ts +20 -0
- package/dist/reactive/type-guards.d.ts.map +1 -0
- package/dist/reactive/untrack.d.ts +29 -0
- package/dist/reactive/untrack.d.ts.map +1 -0
- package/dist/reactive/watch.d.ts +42 -0
- package/dist/reactive/watch.d.ts.map +1 -0
- package/dist/reactive.es.mjs +30 -163
- package/dist/reactive.es.mjs.map +1 -1
- package/dist/router/index.d.ts +6 -252
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/links.d.ts +44 -0
- package/dist/router/links.d.ts.map +1 -0
- package/dist/router/match.d.ts +20 -0
- package/dist/router/match.d.ts.map +1 -0
- package/dist/router/navigation.d.ts +45 -0
- package/dist/router/navigation.d.ts.map +1 -0
- package/dist/router/query.d.ts +16 -0
- package/dist/router/query.d.ts.map +1 -0
- package/dist/router/router.d.ts +34 -0
- package/dist/router/router.d.ts.map +1 -0
- package/dist/router/state.d.ts +27 -0
- package/dist/router/state.d.ts.map +1 -0
- package/dist/router/types.d.ts +88 -0
- package/dist/router/types.d.ts.map +1 -0
- package/dist/router/utils.d.ts +65 -0
- package/dist/router/utils.d.ts.map +1 -0
- package/dist/router.es.mjs +168 -132
- package/dist/router.es.mjs.map +1 -1
- package/dist/sanitize-1FBEPAFH.js +272 -0
- package/dist/sanitize-1FBEPAFH.js.map +1 -0
- package/dist/security/constants.d.ts +42 -0
- package/dist/security/constants.d.ts.map +1 -0
- package/dist/security/csp.d.ts +24 -0
- package/dist/security/csp.d.ts.map +1 -0
- package/dist/security/index.d.ts +4 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/sanitize-core.d.ts +13 -0
- package/dist/security/sanitize-core.d.ts.map +1 -0
- package/dist/security/sanitize.d.ts +5 -57
- package/dist/security/sanitize.d.ts.map +1 -1
- package/dist/security/trusted-types.d.ts +25 -0
- package/dist/security/trusted-types.d.ts.map +1 -0
- package/dist/security/types.d.ts +36 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security.es.mjs +50 -277
- package/dist/security.es.mjs.map +1 -1
- package/dist/store/create-store.d.ts +15 -0
- package/dist/store/create-store.d.ts.map +1 -0
- package/dist/store/define-store.d.ts +28 -0
- package/dist/store/define-store.d.ts.map +1 -0
- package/dist/store/devtools.d.ts +22 -0
- package/dist/store/devtools.d.ts.map +1 -0
- package/dist/store/index.d.ts +10 -286
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/mapping.d.ts +28 -0
- package/dist/store/mapping.d.ts.map +1 -0
- package/dist/store/persisted.d.ts +13 -0
- package/dist/store/persisted.d.ts.map +1 -0
- package/dist/store/plugins.d.ts +13 -0
- package/dist/store/plugins.d.ts.map +1 -0
- package/dist/store/registry.d.ts +28 -0
- package/dist/store/registry.d.ts.map +1 -0
- package/dist/store/types.d.ts +71 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/utils.d.ts +28 -0
- package/dist/store/utils.d.ts.map +1 -0
- package/dist/store/watch.d.ts +23 -0
- package/dist/store/watch.d.ts.map +1 -0
- package/dist/store.es.mjs +22 -224
- package/dist/store.es.mjs.map +1 -1
- package/dist/type-guards-DRma3-Kc.js +16 -0
- package/dist/type-guards-DRma3-Kc.js.map +1 -0
- package/dist/untrack-BuEQKH7_.js +6 -0
- package/dist/untrack-BuEQKH7_.js.map +1 -0
- package/dist/view/directives/bind.d.ts +7 -0
- package/dist/view/directives/bind.d.ts.map +1 -0
- package/dist/view/directives/class.d.ts +8 -0
- package/dist/view/directives/class.d.ts.map +1 -0
- package/dist/view/directives/for.d.ts +23 -0
- package/dist/view/directives/for.d.ts.map +1 -0
- package/dist/view/directives/html.d.ts +7 -0
- package/dist/view/directives/html.d.ts.map +1 -0
- package/dist/view/directives/if.d.ts +7 -0
- package/dist/view/directives/if.d.ts.map +1 -0
- package/dist/view/directives/index.d.ts +12 -0
- package/dist/view/directives/index.d.ts.map +1 -0
- package/dist/view/directives/model.d.ts +7 -0
- package/dist/view/directives/model.d.ts.map +1 -0
- package/dist/view/directives/on.d.ts +7 -0
- package/dist/view/directives/on.d.ts.map +1 -0
- package/dist/view/directives/ref.d.ts +7 -0
- package/dist/view/directives/ref.d.ts.map +1 -0
- package/dist/view/directives/show.d.ts +7 -0
- package/dist/view/directives/show.d.ts.map +1 -0
- package/dist/view/directives/style.d.ts +7 -0
- package/dist/view/directives/style.d.ts.map +1 -0
- package/dist/view/directives/text.d.ts +7 -0
- package/dist/view/directives/text.d.ts.map +1 -0
- package/dist/view/evaluate.d.ts +43 -0
- package/dist/view/evaluate.d.ts.map +1 -0
- package/dist/view/index.d.ts +3 -93
- package/dist/view/index.d.ts.map +1 -1
- package/dist/view/mount.d.ts +69 -0
- package/dist/view/mount.d.ts.map +1 -0
- package/dist/view/process.d.ts +26 -0
- package/dist/view/process.d.ts.map +1 -0
- package/dist/view/types.d.ts +36 -0
- package/dist/view/types.d.ts.map +1 -0
- package/dist/view.es.mjs +368 -267
- package/dist/view.es.mjs.map +1 -1
- package/dist/watch-CXyaBC_9.js +58 -0
- package/dist/watch-CXyaBC_9.js.map +1 -0
- package/package.json +132 -132
- package/src/component/component.ts +289 -0
- package/src/component/html.ts +53 -0
- package/src/component/index.ts +40 -414
- package/src/component/props.ts +116 -0
- package/src/component/types.ts +85 -0
- package/src/core/collection.ts +588 -454
- package/src/core/dom.ts +38 -0
- package/src/core/element.ts +746 -740
- package/src/core/index.ts +43 -0
- package/src/core/utils/array.ts +102 -0
- package/src/core/utils/function.ts +110 -0
- package/src/core/utils/index.ts +83 -0
- package/src/core/utils/misc.ts +82 -0
- package/src/core/utils/number.ts +78 -0
- package/src/core/utils/object.ts +206 -0
- package/src/core/utils/string.ts +112 -0
- package/src/core/utils/type-guards.ts +112 -0
- package/src/full.ts +187 -150
- package/src/index.ts +36 -36
- package/src/motion/animate.ts +113 -0
- package/src/motion/easing.ts +40 -0
- package/src/motion/flip.ts +176 -0
- package/src/motion/index.ts +41 -358
- package/src/motion/keyframes.ts +46 -0
- package/src/motion/reduced-motion.ts +17 -0
- package/src/motion/scroll.ts +57 -0
- package/src/motion/spring.ts +150 -0
- package/src/motion/stagger.ts +43 -0
- package/src/motion/timeline.ts +246 -0
- package/src/motion/transition.ts +51 -0
- package/src/motion/types.ts +198 -0
- package/src/reactive/batch.ts +22 -0
- package/src/reactive/computed.ts +92 -0
- package/src/reactive/core.ts +93 -0
- package/src/reactive/effect.ts +43 -0
- package/src/reactive/index.ts +23 -22
- package/src/reactive/internals.ts +105 -0
- package/src/reactive/linked.ts +56 -0
- package/src/reactive/persisted.ts +74 -0
- package/src/reactive/readonly.ts +35 -0
- package/src/reactive/signal.ts +20 -520
- package/src/reactive/type-guards.ts +22 -0
- package/src/reactive/untrack.ts +31 -0
- package/src/reactive/watch.ts +73 -0
- package/src/router/index.ts +41 -718
- package/src/router/links.ts +130 -0
- package/src/router/match.ts +106 -0
- package/src/router/navigation.ts +71 -0
- package/src/router/query.ts +35 -0
- package/src/router/router.ts +211 -0
- package/src/router/state.ts +46 -0
- package/src/router/types.ts +93 -0
- package/src/router/utils.ts +116 -0
- package/src/security/constants.ts +209 -0
- package/src/security/csp.ts +77 -0
- package/src/security/index.ts +4 -12
- package/src/security/sanitize-core.ts +343 -0
- package/src/security/sanitize.ts +66 -625
- package/src/security/trusted-types.ts +69 -0
- package/src/security/types.ts +40 -0
- package/src/store/create-store.ts +329 -0
- package/src/store/define-store.ts +48 -0
- package/src/store/devtools.ts +45 -0
- package/src/store/index.ts +22 -848
- package/src/store/mapping.ts +73 -0
- package/src/store/persisted.ts +61 -0
- package/src/store/plugins.ts +32 -0
- package/src/store/registry.ts +51 -0
- package/src/store/types.ts +94 -0
- package/src/store/utils.ts +141 -0
- package/src/store/watch.ts +52 -0
- package/src/view/directives/bind.ts +23 -0
- package/src/view/directives/class.ts +70 -0
- package/src/view/directives/for.ts +275 -0
- package/src/view/directives/html.ts +19 -0
- package/src/view/directives/if.ts +30 -0
- package/src/view/directives/index.ts +11 -0
- package/src/view/directives/model.ts +56 -0
- package/src/view/directives/on.ts +41 -0
- package/src/view/directives/ref.ts +41 -0
- package/src/view/directives/show.ts +26 -0
- package/src/view/directives/style.ts +47 -0
- package/src/view/directives/text.ts +15 -0
- package/src/view/evaluate.ts +274 -0
- package/src/view/index.ts +112 -1041
- package/src/view/mount.ts +200 -0
- package/src/view/process.ts +92 -0
- package/src/view/types.ts +44 -0
- package/dist/core/utils.d.ts +0 -313
- package/dist/core/utils.d.ts.map +0 -1
- package/src/core/utils.ts +0 -444
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Component factory and registry.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { sanitizeHtml } from '../security/sanitize';
|
|
8
|
+
import { coercePropValue } from './props';
|
|
9
|
+
import type { ComponentDefinition, PropDefinition } from './types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a custom element class for a component definition.
|
|
13
|
+
*
|
|
14
|
+
* This is useful when you want to extend or register the class manually
|
|
15
|
+
* (e.g. with different tag names in tests or custom registries).
|
|
16
|
+
*
|
|
17
|
+
* @template TProps - Type of the component's props
|
|
18
|
+
* @param tagName - The custom element tag name (used for diagnostics)
|
|
19
|
+
* @param definition - The component configuration
|
|
20
|
+
*/
|
|
21
|
+
export const defineComponent = <TProps extends Record<string, unknown>>(
|
|
22
|
+
tagName: string,
|
|
23
|
+
definition: ComponentDefinition<TProps>
|
|
24
|
+
): typeof HTMLElement => {
|
|
25
|
+
class BQueryComponent extends HTMLElement {
|
|
26
|
+
/** Internal state object for the component */
|
|
27
|
+
private readonly state = { ...(definition.state ?? {}) };
|
|
28
|
+
/** Typed props object populated from attributes */
|
|
29
|
+
private props = {} as TProps;
|
|
30
|
+
/** Tracks missing required props for validation during connectedCallback */
|
|
31
|
+
private missingRequiredProps = new Set<string>();
|
|
32
|
+
/** Tracks whether the component has completed its initial mount */
|
|
33
|
+
private hasMounted = false;
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
super();
|
|
37
|
+
this.attachShadow({ mode: 'open' });
|
|
38
|
+
this.syncProps();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the list of attributes to observe for changes.
|
|
43
|
+
*/
|
|
44
|
+
static get observedAttributes(): string[] {
|
|
45
|
+
return Object.keys(definition.props ?? {});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Called when the element is added to the DOM.
|
|
50
|
+
*/
|
|
51
|
+
connectedCallback(): void {
|
|
52
|
+
try {
|
|
53
|
+
// Defer initial render until all required props are present
|
|
54
|
+
// This allows attributes to be set after element creation
|
|
55
|
+
if (this.missingRequiredProps.size > 0) {
|
|
56
|
+
// Component will mount once all required props are satisfied
|
|
57
|
+
// via attributeChangedCallback
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.mount();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.handleError(error as Error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Performs the initial mount of the component.
|
|
68
|
+
* Called when the element is connected and all required props are present.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
private mount(): void {
|
|
72
|
+
if (this.hasMounted) return;
|
|
73
|
+
definition.beforeMount?.call(this);
|
|
74
|
+
definition.connected?.call(this);
|
|
75
|
+
this.render();
|
|
76
|
+
this.hasMounted = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Called when the element is removed from the DOM.
|
|
81
|
+
*/
|
|
82
|
+
disconnectedCallback(): void {
|
|
83
|
+
try {
|
|
84
|
+
definition.disconnected?.call(this);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.handleError(error as Error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Called when an observed attribute changes.
|
|
92
|
+
*/
|
|
93
|
+
attributeChangedCallback(
|
|
94
|
+
_name: string,
|
|
95
|
+
_oldValue: string | null,
|
|
96
|
+
_newValue: string | null
|
|
97
|
+
): void {
|
|
98
|
+
try {
|
|
99
|
+
this.syncProps();
|
|
100
|
+
|
|
101
|
+
if (this.hasMounted) {
|
|
102
|
+
// Component already mounted - trigger update render
|
|
103
|
+
this.render(true);
|
|
104
|
+
} else if (this.isConnected && this.missingRequiredProps.size === 0) {
|
|
105
|
+
// All required props are now satisfied and element is connected
|
|
106
|
+
// Trigger the deferred initial mount
|
|
107
|
+
this.mount();
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.handleError(error as Error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Handles errors during component lifecycle.
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
private handleError(error: Error): void {
|
|
119
|
+
if (definition.onError) {
|
|
120
|
+
definition.onError.call(this, error);
|
|
121
|
+
} else {
|
|
122
|
+
console.error(`bQuery component error in <${tagName}>:`, error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Updates a state property and triggers a re-render.
|
|
128
|
+
*
|
|
129
|
+
* @param key - The state property key
|
|
130
|
+
* @param value - The new value
|
|
131
|
+
*/
|
|
132
|
+
setState(key: string, value: unknown): void {
|
|
133
|
+
this.state[key] = value;
|
|
134
|
+
this.render(true);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets a state property value.
|
|
139
|
+
*
|
|
140
|
+
* @param key - The state property key
|
|
141
|
+
* @returns The current value
|
|
142
|
+
*/
|
|
143
|
+
getState<T = unknown>(key: string): T {
|
|
144
|
+
return this.state[key] as T;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Synchronizes props from attributes.
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
private syncProps(): void {
|
|
152
|
+
const props = definition.props ?? {};
|
|
153
|
+
for (const [key, config] of Object.entries(props) as [string, PropDefinition][]) {
|
|
154
|
+
const attrValue = this.getAttribute(key);
|
|
155
|
+
let value: unknown;
|
|
156
|
+
|
|
157
|
+
if (attrValue == null) {
|
|
158
|
+
if (config.required && config.default === undefined) {
|
|
159
|
+
// Mark as missing instead of throwing - validate during connectedCallback
|
|
160
|
+
this.missingRequiredProps.add(key);
|
|
161
|
+
value = undefined;
|
|
162
|
+
} else {
|
|
163
|
+
value = config.default ?? undefined;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Attribute is present, remove from missing set if it was there
|
|
167
|
+
if (this.missingRequiredProps.has(key)) {
|
|
168
|
+
this.missingRequiredProps.delete(key);
|
|
169
|
+
}
|
|
170
|
+
value = coercePropValue(attrValue, config);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (config.validator && value !== undefined) {
|
|
174
|
+
const isValid = config.validator(value);
|
|
175
|
+
if (!isValid) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`bQuery component: validation failed for prop "${key}" with value ${JSON.stringify(value)}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
(this.props as Record<string, unknown>)[key] = value;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Renders the component to its shadow root.
|
|
188
|
+
* @internal
|
|
189
|
+
*/
|
|
190
|
+
private render(triggerUpdated = false): void {
|
|
191
|
+
try {
|
|
192
|
+
if (triggerUpdated && definition.beforeUpdate) {
|
|
193
|
+
const shouldUpdate = definition.beforeUpdate.call(this, this.props);
|
|
194
|
+
if (shouldUpdate === false) return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const emit = (event: string, detail?: unknown): void => {
|
|
198
|
+
this.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, composed: true }));
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (!this.shadowRoot) return;
|
|
202
|
+
|
|
203
|
+
const markup = definition.render({
|
|
204
|
+
props: this.props,
|
|
205
|
+
state: this.state,
|
|
206
|
+
emit,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const sanitizedMarkup = sanitizeHtml(markup);
|
|
210
|
+
this.shadowRoot.innerHTML = sanitizedMarkup;
|
|
211
|
+
|
|
212
|
+
if (definition.styles) {
|
|
213
|
+
const styleElement = document.createElement('style');
|
|
214
|
+
styleElement.textContent = definition.styles;
|
|
215
|
+
this.shadowRoot.prepend(styleElement);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (triggerUpdated) {
|
|
219
|
+
definition.updated?.call(this);
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.handleError(error as Error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return BQueryComponent;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Defines and registers a custom Web Component.
|
|
232
|
+
*
|
|
233
|
+
* This function creates a new custom element with the given tag name
|
|
234
|
+
* and configuration. The component uses Shadow DOM for encapsulation
|
|
235
|
+
* and automatically re-renders when observed attributes change.
|
|
236
|
+
*
|
|
237
|
+
* @template TProps - Type of the component's props
|
|
238
|
+
* @param tagName - The custom element tag name (must contain a hyphen)
|
|
239
|
+
* @param definition - The component configuration
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* component('counter-button', {
|
|
244
|
+
* props: {
|
|
245
|
+
* start: { type: Number, default: 0 },
|
|
246
|
+
* },
|
|
247
|
+
* state: { count: 0 },
|
|
248
|
+
* styles: `
|
|
249
|
+
* button { padding: 0.5rem 1rem; }
|
|
250
|
+
* `,
|
|
251
|
+
* connected() {
|
|
252
|
+
* // Use event delegation on shadow root so handler survives re-renders
|
|
253
|
+
* const handleClick = (event: Event) => {
|
|
254
|
+
* const target = event.target as HTMLElement | null;
|
|
255
|
+
* if (target?.matches('button')) {
|
|
256
|
+
* this.setState('count', (this.getState('count') as number) + 1);
|
|
257
|
+
* }
|
|
258
|
+
* };
|
|
259
|
+
* this.shadowRoot?.addEventListener('click', handleClick);
|
|
260
|
+
* // Store handler for cleanup
|
|
261
|
+
* (this as any)._handleClick = handleClick;
|
|
262
|
+
* },
|
|
263
|
+
* disconnected() {
|
|
264
|
+
* // Clean up event listener to prevent memory leaks
|
|
265
|
+
* const handleClick = (this as any)._handleClick;
|
|
266
|
+
* if (handleClick) {
|
|
267
|
+
* this.shadowRoot?.removeEventListener('click', handleClick);
|
|
268
|
+
* }
|
|
269
|
+
* },
|
|
270
|
+
* render({ props, state }) {
|
|
271
|
+
* return html`
|
|
272
|
+
* <button>
|
|
273
|
+
* Count: ${state.count}
|
|
274
|
+
* </button>
|
|
275
|
+
* `;
|
|
276
|
+
* },
|
|
277
|
+
* });
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export const component = <TProps extends Record<string, unknown>>(
|
|
281
|
+
tagName: string,
|
|
282
|
+
definition: ComponentDefinition<TProps>
|
|
283
|
+
): void => {
|
|
284
|
+
const elementClass = defineComponent(tagName, definition);
|
|
285
|
+
|
|
286
|
+
if (!customElements.get(tagName)) {
|
|
287
|
+
customElements.define(tagName, elementClass);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged template literal for creating HTML strings.
|
|
3
|
+
*
|
|
4
|
+
* This function handles interpolation of values into HTML templates,
|
|
5
|
+
* converting null/undefined to empty strings.
|
|
6
|
+
*
|
|
7
|
+
* @param strings - Template literal string parts
|
|
8
|
+
* @param values - Interpolated values
|
|
9
|
+
* @returns Combined HTML string
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const name = 'World';
|
|
14
|
+
* const greeting = html`<h1>Hello, ${name}!</h1>`;
|
|
15
|
+
* // Result: '<h1>Hello, World!</h1>'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {
|
|
19
|
+
return strings.reduce((acc, part, index) => `${acc}${part}${values[index] ?? ''}`, '');
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Escapes HTML entities in interpolated values for XSS prevention.
|
|
24
|
+
* Use this when you need to safely embed user content in templates.
|
|
25
|
+
*
|
|
26
|
+
* @param strings - Template literal string parts
|
|
27
|
+
* @param values - Interpolated values to escape
|
|
28
|
+
* @returns Combined HTML string with escaped values
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const userInput = '<script>alert("xss")</script>';
|
|
33
|
+
* const safe = safeHtml`<div>${userInput}</div>`;
|
|
34
|
+
* // Result: '<div><script>alert("xss")</script></div>'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const safeHtml = (strings: TemplateStringsArray, ...values: unknown[]): string => {
|
|
38
|
+
const escapeMap: Record<string, string> = {
|
|
39
|
+
'&': '&',
|
|
40
|
+
'<': '<',
|
|
41
|
+
'>': '>',
|
|
42
|
+
'"': '"',
|
|
43
|
+
"'": ''',
|
|
44
|
+
'`': '`',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const escape = (value: unknown): string => {
|
|
48
|
+
const str = String(value ?? '');
|
|
49
|
+
return str.replace(/[&<>"'`]/g, (char) => escapeMap[char]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return strings.reduce((acc, part, index) => `${acc}${part}${escape(values[index])}`, '');
|
|
53
|
+
};
|