@bquery/bquery 1.3.0 → 1.5.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 +546 -501
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/index.d.ts +2 -0
- package/dist/component/index.d.ts.map +1 -1
- package/dist/component/library.d.ts +34 -0
- package/dist/component/library.d.ts.map +1 -0
- package/dist/component/types.d.ts +10 -6
- package/dist/component/types.d.ts.map +1 -1
- package/dist/component-CY5MVoYN.js +531 -0
- package/dist/component-CY5MVoYN.js.map +1 -0
- package/dist/component.es.mjs +6 -184
- package/dist/config-DRmZZno3.js +40 -0
- package/dist/config-DRmZZno3.js.map +1 -0
- package/dist/core/collection.d.ts +19 -3
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +23 -4
- 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/function.d.ts +21 -4
- package/dist/core/utils/function.d.ts.map +1 -1
- package/dist/core-CK2Mfpf4.js +648 -0
- package/dist/core-CK2Mfpf4.js.map +1 -0
- package/dist/core-DPdbItcq.js +112 -0
- package/dist/core-DPdbItcq.js.map +1 -0
- package/dist/core.es.mjs +45 -1218
- package/dist/full.d.ts +6 -6
- package/dist/full.d.ts.map +1 -1
- package/dist/full.es.mjs +98 -92
- package/dist/full.iife.js +173 -3
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +173 -3
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +143 -139
- package/dist/motion/transition.d.ts +1 -1
- package/dist/motion/transition.d.ts.map +1 -1
- package/dist/motion/types.d.ts +11 -1
- package/dist/motion/types.d.ts.map +1 -1
- package/dist/motion-C5DRdPnO.js +415 -0
- package/dist/motion-C5DRdPnO.js.map +1 -0
- package/dist/motion.es.mjs +25 -361
- package/dist/object-qGpWr6-J.js +38 -0
- package/dist/object-qGpWr6-J.js.map +1 -0
- package/dist/platform/announcer.d.ts +59 -0
- package/dist/platform/announcer.d.ts.map +1 -0
- package/dist/platform/config.d.ts +92 -0
- package/dist/platform/config.d.ts.map +1 -0
- package/dist/platform/cookies.d.ts +45 -0
- package/dist/platform/cookies.d.ts.map +1 -0
- package/dist/platform/index.d.ts +8 -0
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/meta.d.ts +62 -0
- package/dist/platform/meta.d.ts.map +1 -0
- package/dist/platform/storage.d.ts.map +1 -1
- package/dist/platform-B7JhGBc7.js +361 -0
- package/dist/platform-B7JhGBc7.js.map +1 -0
- package/dist/platform.es.mjs +11 -243
- package/dist/reactive/async-data.d.ts +114 -0
- package/dist/reactive/async-data.d.ts.map +1 -0
- package/dist/reactive/core.d.ts +12 -0
- package/dist/reactive/core.d.ts.map +1 -1
- package/dist/reactive/effect.d.ts.map +1 -1
- package/dist/reactive/index.d.ts +2 -2
- package/dist/reactive/index.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +6 -0
- package/dist/reactive/internals.d.ts.map +1 -1
- package/dist/reactive/signal.d.ts +2 -0
- package/dist/reactive/signal.d.ts.map +1 -1
- package/dist/reactive-BDya-ia8.js +253 -0
- package/dist/reactive-BDya-ia8.js.map +1 -0
- package/dist/reactive.es.mjs +18 -34
- package/dist/router-CijiICxt.js +188 -0
- package/dist/router-CijiICxt.js.map +1 -0
- package/dist/router.es.mjs +11 -200
- package/dist/sanitize-jyJ2ryE2.js +302 -0
- package/dist/sanitize-jyJ2ryE2.js.map +1 -0
- package/dist/security/constants.d.ts.map +1 -1
- package/dist/security/sanitize-core.d.ts.map +1 -1
- package/dist/security.es.mjs +10 -56
- package/dist/store-CPK9E62U.js +262 -0
- package/dist/store-CPK9E62U.js.map +1 -0
- package/dist/store.es.mjs +12 -25
- package/dist/view/evaluate.d.ts.map +1 -1
- package/dist/view-Cdi0g-qo.js +396 -0
- package/dist/view-Cdi0g-qo.js.map +1 -0
- package/dist/view.es.mjs +10 -424
- package/package.json +136 -132
- package/src/component/component.ts +319 -289
- package/src/component/index.ts +42 -40
- package/src/component/library.ts +504 -0
- package/src/component/types.ts +91 -85
- package/src/core/collection.ts +44 -4
- package/src/core/element.ts +33 -5
- package/src/core/index.ts +1 -0
- package/src/core/utils/function.ts +56 -15
- package/src/full.ts +223 -187
- package/src/motion/transition.ts +97 -51
- package/src/motion/types.ts +208 -198
- package/src/platform/announcer.ts +208 -0
- package/src/platform/config.ts +163 -0
- package/src/platform/cookies.ts +165 -0
- package/src/platform/index.ts +39 -18
- package/src/platform/meta.ts +168 -0
- package/src/platform/storage.ts +8 -1
- package/src/reactive/async-data.ts +486 -0
- package/src/reactive/core.ts +21 -0
- package/src/reactive/effect.ts +18 -7
- package/src/reactive/index.ts +37 -23
- package/src/reactive/internals.ts +18 -1
- package/src/reactive/signal.ts +29 -20
- package/src/security/constants.ts +211 -209
- package/src/security/sanitize-core.ts +22 -1
- package/src/view/evaluate.ts +29 -13
- package/dist/batch-4LAvfLE7.js +0 -13
- package/dist/batch-4LAvfLE7.js.map +0 -1
- package/dist/component.es.mjs.map +0 -1
- package/dist/core-COenAZjD.js +0 -145
- package/dist/core-COenAZjD.js.map +0 -1
- package/dist/core.es.mjs.map +0 -1
- package/dist/full.es.mjs.map +0 -1
- package/dist/index.es.mjs.map +0 -1
- package/dist/motion.es.mjs.map +0 -1
- package/dist/persisted-Dz_ryNuC.js +0 -278
- package/dist/persisted-Dz_ryNuC.js.map +0 -1
- package/dist/platform.es.mjs.map +0 -1
- package/dist/reactive.es.mjs.map +0 -1
- package/dist/router.es.mjs.map +0 -1
- package/dist/sanitize-1FBEPAFH.js +0 -272
- package/dist/sanitize-1FBEPAFH.js.map +0 -1
- package/dist/security.es.mjs.map +0 -1
- package/dist/store.es.mjs.map +0 -1
- package/dist/type-guards-DRma3-Kc.js +0 -16
- package/dist/type-guards-DRma3-Kc.js.map +0 -1
- package/dist/untrack-BuEQKH7_.js +0 -6
- package/dist/untrack-BuEQKH7_.js.map +0 -1
- package/dist/view.es.mjs.map +0 -1
- package/dist/watch-CXyaBC_9.js +0 -58
- package/dist/watch-CXyaBC_9.js.map +0 -1
package/src/component/index.ts
CHANGED
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal Web Component helper for building custom elements.
|
|
3
|
-
*
|
|
4
|
-
* This module provides a declarative API for defining Web Components
|
|
5
|
-
* without complex build steps. Features include:
|
|
6
|
-
* - Type-safe props with automatic attribute coercion
|
|
7
|
-
* - Reactive state management
|
|
8
|
-
* - Shadow DOM encapsulation with scoped styles
|
|
9
|
-
* - Lifecycle hooks (connected, disconnected)
|
|
10
|
-
* - Event emission helpers
|
|
11
|
-
*
|
|
12
|
-
* @module bquery/component
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { component, html } from 'bquery/component';
|
|
17
|
-
*
|
|
18
|
-
* component('user-card', {
|
|
19
|
-
* props: {
|
|
20
|
-
* username: { type: String, required: true },
|
|
21
|
-
* avatar: { type: String, default: '/default-avatar.png' },
|
|
22
|
-
* },
|
|
23
|
-
* styles: `
|
|
24
|
-
* .card { padding: 1rem; border: 1px solid #ccc; }
|
|
25
|
-
* `,
|
|
26
|
-
* render({ props }) {
|
|
27
|
-
* return html`
|
|
28
|
-
* <div class="card">
|
|
29
|
-
* <img src="${props.avatar}" alt="${props.username}" />
|
|
30
|
-
* <h3>${props.username}</h3>
|
|
31
|
-
* </div>
|
|
32
|
-
* `;
|
|
33
|
-
* },
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
export { component, defineComponent } from './component';
|
|
39
|
-
export { html, safeHtml } from './html';
|
|
40
|
-
export
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Web Component helper for building custom elements.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a declarative API for defining Web Components
|
|
5
|
+
* without complex build steps. Features include:
|
|
6
|
+
* - Type-safe props with automatic attribute coercion
|
|
7
|
+
* - Reactive state management
|
|
8
|
+
* - Shadow DOM encapsulation with scoped styles
|
|
9
|
+
* - Lifecycle hooks (connected, disconnected)
|
|
10
|
+
* - Event emission helpers
|
|
11
|
+
*
|
|
12
|
+
* @module bquery/component
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { component, html } from 'bquery/component';
|
|
17
|
+
*
|
|
18
|
+
* component('user-card', {
|
|
19
|
+
* props: {
|
|
20
|
+
* username: { type: String, required: true },
|
|
21
|
+
* avatar: { type: String, default: '/default-avatar.png' },
|
|
22
|
+
* },
|
|
23
|
+
* styles: `
|
|
24
|
+
* .card { padding: 1rem; border: 1px solid #ccc; }
|
|
25
|
+
* `,
|
|
26
|
+
* render({ props }) {
|
|
27
|
+
* return html`
|
|
28
|
+
* <div class="card">
|
|
29
|
+
* <img src="${props.avatar}" alt="${props.username}" />
|
|
30
|
+
* <h3>${props.username}</h3>
|
|
31
|
+
* </div>
|
|
32
|
+
* `;
|
|
33
|
+
* },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export { component, defineComponent } from './component';
|
|
39
|
+
export { html, safeHtml } from './html';
|
|
40
|
+
export { registerDefaultComponents } from './library';
|
|
41
|
+
export type { DefaultComponentLibraryOptions, RegisteredDefaultComponents } from './library';
|
|
42
|
+
export type { ComponentDefinition, ComponentRenderContext, PropDefinition } from './types';
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default component library based on native Web Components.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getBqueryConfig } from '../platform/config';
|
|
8
|
+
import { escapeHtml } from '../security';
|
|
9
|
+
import { component } from './component';
|
|
10
|
+
import { html } from './html';
|
|
11
|
+
|
|
12
|
+
/** Options for registering the default component library. */
|
|
13
|
+
export interface DefaultComponentLibraryOptions {
|
|
14
|
+
/** Prefix used for all registered component tags. Defaults to `bq`. */
|
|
15
|
+
prefix?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Tag names returned by registerDefaultComponents(). */
|
|
19
|
+
export interface RegisteredDefaultComponents {
|
|
20
|
+
/** Button component tag name. */
|
|
21
|
+
button: string;
|
|
22
|
+
/** Card component tag name. */
|
|
23
|
+
card: string;
|
|
24
|
+
/** Input component tag name. */
|
|
25
|
+
input: string;
|
|
26
|
+
/** Textarea component tag name. */
|
|
27
|
+
textarea: string;
|
|
28
|
+
/** Checkbox component tag name. */
|
|
29
|
+
checkbox: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const baseStyles = `
|
|
33
|
+
:host {
|
|
34
|
+
color: inherit;
|
|
35
|
+
font: inherit;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const controlStyles = `
|
|
40
|
+
${baseStyles}
|
|
41
|
+
.field {
|
|
42
|
+
display: inline-flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: 0.375rem;
|
|
45
|
+
width: 100%;
|
|
46
|
+
}
|
|
47
|
+
.label {
|
|
48
|
+
color: #334155;
|
|
49
|
+
font-size: 0.875rem;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
}
|
|
52
|
+
.control {
|
|
53
|
+
border: 1px solid #cbd5e1;
|
|
54
|
+
border-radius: 0.75rem;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
font: inherit;
|
|
57
|
+
min-height: 2.75rem;
|
|
58
|
+
outline: none;
|
|
59
|
+
padding: 0.75rem 0.875rem;
|
|
60
|
+
width: 100%;
|
|
61
|
+
background: #fff;
|
|
62
|
+
color: #0f172a;
|
|
63
|
+
transition: border-color 160ms ease, box-shadow 160ms ease;
|
|
64
|
+
}
|
|
65
|
+
.control:focus {
|
|
66
|
+
border-color: #2563eb;
|
|
67
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
|
68
|
+
}
|
|
69
|
+
.control:disabled {
|
|
70
|
+
background: #f8fafc;
|
|
71
|
+
color: #94a3b8;
|
|
72
|
+
cursor: not-allowed;
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const escapeProp = (value: string): string => escapeHtml(value);
|
|
77
|
+
|
|
78
|
+
const handlerStore = new WeakMap<HTMLElement, Record<string, EventListener>>();
|
|
79
|
+
|
|
80
|
+
const readHandler = (element: HTMLElement, key: string): EventListener | undefined => {
|
|
81
|
+
return handlerStore.get(element)?.[key];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const storeHandler = (element: HTMLElement, key: string, value: EventListener): void => {
|
|
85
|
+
const handlers = handlerStore.get(element) ?? {};
|
|
86
|
+
handlers[key] = value;
|
|
87
|
+
handlerStore.set(element, handlers);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getShadowLabelText = (element: HTMLElement): string => {
|
|
91
|
+
return element.shadowRoot?.querySelector('.label')?.textContent ?? '';
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect a value-only input update, patch the live control in place, and
|
|
96
|
+
* return whether the component can skip a full shadow DOM re-render.
|
|
97
|
+
*
|
|
98
|
+
* @param element - The host custom element whose shadow DOM is being updated
|
|
99
|
+
* @param props - The next reflected input props for the pending update
|
|
100
|
+
*/
|
|
101
|
+
const canSkipInputRender = (
|
|
102
|
+
element: HTMLElement,
|
|
103
|
+
props: {
|
|
104
|
+
label: string;
|
|
105
|
+
type: string;
|
|
106
|
+
value: string;
|
|
107
|
+
placeholder: string;
|
|
108
|
+
name: string;
|
|
109
|
+
disabled: boolean;
|
|
110
|
+
}
|
|
111
|
+
): boolean => {
|
|
112
|
+
const control = element.shadowRoot?.querySelector('input.control') as HTMLInputElement | null;
|
|
113
|
+
if (!control) return false;
|
|
114
|
+
|
|
115
|
+
if (getShadowLabelText(element) !== props.label) return false;
|
|
116
|
+
if ((control.getAttribute('type') ?? 'text') !== props.type) return false;
|
|
117
|
+
if ((control.getAttribute('placeholder') ?? '') !== props.placeholder) return false;
|
|
118
|
+
if ((control.getAttribute('name') ?? '') !== props.name) return false;
|
|
119
|
+
if (control.disabled !== props.disabled) return false;
|
|
120
|
+
|
|
121
|
+
if (control.value !== props.value) {
|
|
122
|
+
control.value = props.value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Detect a value-only textarea update, patch the live control in place, and
|
|
130
|
+
* return whether the component can skip a full shadow DOM re-render.
|
|
131
|
+
*
|
|
132
|
+
* @param element - The host custom element whose shadow DOM is being updated
|
|
133
|
+
* @param props - The next reflected textarea props for the pending update
|
|
134
|
+
*/
|
|
135
|
+
const canSkipTextareaRender = (
|
|
136
|
+
element: HTMLElement,
|
|
137
|
+
props: {
|
|
138
|
+
label: string;
|
|
139
|
+
value: string;
|
|
140
|
+
placeholder: string;
|
|
141
|
+
name: string;
|
|
142
|
+
rows: number;
|
|
143
|
+
disabled: boolean;
|
|
144
|
+
}
|
|
145
|
+
): boolean => {
|
|
146
|
+
const control = element.shadowRoot?.querySelector(
|
|
147
|
+
'textarea.control'
|
|
148
|
+
) as HTMLTextAreaElement | null;
|
|
149
|
+
if (!control) return false;
|
|
150
|
+
|
|
151
|
+
if (getShadowLabelText(element) !== props.label) return false;
|
|
152
|
+
if ((control.getAttribute('placeholder') ?? '') !== props.placeholder) return false;
|
|
153
|
+
if ((control.getAttribute('name') ?? '') !== props.name) return false;
|
|
154
|
+
if (control.getAttribute('rows') !== String(props.rows)) return false;
|
|
155
|
+
if (control.disabled !== props.disabled) return false;
|
|
156
|
+
|
|
157
|
+
if (control.value !== props.value) {
|
|
158
|
+
control.value = props.value;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const renderTextareaControl = (props: {
|
|
164
|
+
value: string;
|
|
165
|
+
placeholder: string;
|
|
166
|
+
name: string;
|
|
167
|
+
rows: number;
|
|
168
|
+
disabled: boolean;
|
|
169
|
+
}): string => {
|
|
170
|
+
return [
|
|
171
|
+
'<textarea',
|
|
172
|
+
' part="control"',
|
|
173
|
+
' class="control"',
|
|
174
|
+
` placeholder="${escapeProp(props.placeholder)}"`,
|
|
175
|
+
` name="${escapeProp(props.name)}"`,
|
|
176
|
+
` rows="${props.rows}"`,
|
|
177
|
+
props.disabled ? ' disabled' : '',
|
|
178
|
+
`>${escapeProp(props.value)}</textarea>`,
|
|
179
|
+
].join('');
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Register a default set of foundational UI components.
|
|
184
|
+
*
|
|
185
|
+
* The library is intentionally small and dependency-free, providing common
|
|
186
|
+
* primitives that can be themed via shadow parts and CSS custom properties.
|
|
187
|
+
*
|
|
188
|
+
* @param options - Optional registration settings such as a custom tag prefix
|
|
189
|
+
* @returns The registered tag names for each component
|
|
190
|
+
*/
|
|
191
|
+
export const registerDefaultComponents = (
|
|
192
|
+
options: DefaultComponentLibraryOptions = {}
|
|
193
|
+
): RegisteredDefaultComponents => {
|
|
194
|
+
const prefix = options.prefix ?? getBqueryConfig().components?.prefix ?? 'bq';
|
|
195
|
+
const tags: RegisteredDefaultComponents = {
|
|
196
|
+
button: `${prefix}-button`,
|
|
197
|
+
card: `${prefix}-card`,
|
|
198
|
+
input: `${prefix}-input`,
|
|
199
|
+
textarea: `${prefix}-textarea`,
|
|
200
|
+
checkbox: `${prefix}-checkbox`,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
component<{
|
|
204
|
+
label: string;
|
|
205
|
+
variant: string;
|
|
206
|
+
size: string;
|
|
207
|
+
type: string;
|
|
208
|
+
disabled: boolean;
|
|
209
|
+
}>(tags.button, {
|
|
210
|
+
props: {
|
|
211
|
+
label: { type: String, default: '' },
|
|
212
|
+
variant: { type: String, default: 'primary' },
|
|
213
|
+
size: { type: String, default: 'md' },
|
|
214
|
+
type: { type: String, default: 'button' },
|
|
215
|
+
disabled: { type: Boolean, default: false },
|
|
216
|
+
},
|
|
217
|
+
styles: `
|
|
218
|
+
${baseStyles}
|
|
219
|
+
button {
|
|
220
|
+
appearance: none;
|
|
221
|
+
border: 0;
|
|
222
|
+
border-radius: 999px;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
display: inline-flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
font: inherit;
|
|
228
|
+
font-weight: 600;
|
|
229
|
+
gap: 0.5rem;
|
|
230
|
+
min-height: 2.5rem;
|
|
231
|
+
padding: 0.65rem 1rem;
|
|
232
|
+
transition: transform 160ms ease, opacity 160ms ease, background 160ms ease;
|
|
233
|
+
background: #2563eb;
|
|
234
|
+
color: #fff;
|
|
235
|
+
}
|
|
236
|
+
button[data-variant='secondary'] {
|
|
237
|
+
background: #e2e8f0;
|
|
238
|
+
color: #0f172a;
|
|
239
|
+
}
|
|
240
|
+
button[data-size='sm'] {
|
|
241
|
+
min-height: 2.125rem;
|
|
242
|
+
padding: 0.5rem 0.875rem;
|
|
243
|
+
}
|
|
244
|
+
button[data-size='lg'] {
|
|
245
|
+
min-height: 3rem;
|
|
246
|
+
padding: 0.875rem 1.25rem;
|
|
247
|
+
}
|
|
248
|
+
button:hover:not(:disabled) {
|
|
249
|
+
transform: translateY(-1px);
|
|
250
|
+
}
|
|
251
|
+
button:disabled {
|
|
252
|
+
cursor: not-allowed;
|
|
253
|
+
opacity: 0.6;
|
|
254
|
+
}
|
|
255
|
+
`,
|
|
256
|
+
render: ({ props }) => html`
|
|
257
|
+
<button
|
|
258
|
+
part="button"
|
|
259
|
+
type="${escapeProp(props.type)}"
|
|
260
|
+
data-variant="${escapeProp(props.variant)}"
|
|
261
|
+
data-size="${escapeProp(props.size)}"
|
|
262
|
+
${props.disabled ? 'disabled' : ''}
|
|
263
|
+
>
|
|
264
|
+
<slot>${escapeProp(props.label)}</slot>
|
|
265
|
+
</button>
|
|
266
|
+
`,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
component<{ title: string; footer: string; elevated: boolean }>(tags.card, {
|
|
270
|
+
props: {
|
|
271
|
+
title: { type: String, default: '' },
|
|
272
|
+
footer: { type: String, default: '' },
|
|
273
|
+
elevated: { type: Boolean, default: true },
|
|
274
|
+
},
|
|
275
|
+
styles: `
|
|
276
|
+
${baseStyles}
|
|
277
|
+
article {
|
|
278
|
+
background: #fff;
|
|
279
|
+
border: 1px solid #e2e8f0;
|
|
280
|
+
border-radius: 1rem;
|
|
281
|
+
box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
|
|
282
|
+
color: #0f172a;
|
|
283
|
+
display: block;
|
|
284
|
+
padding: 1rem;
|
|
285
|
+
}
|
|
286
|
+
article[data-elevated='false'] {
|
|
287
|
+
box-shadow: none;
|
|
288
|
+
}
|
|
289
|
+
header, footer {
|
|
290
|
+
color: #475569;
|
|
291
|
+
font-size: 0.95rem;
|
|
292
|
+
font-weight: 600;
|
|
293
|
+
}
|
|
294
|
+
header {
|
|
295
|
+
margin-bottom: 0.75rem;
|
|
296
|
+
}
|
|
297
|
+
footer {
|
|
298
|
+
margin-top: 0.75rem;
|
|
299
|
+
}
|
|
300
|
+
`,
|
|
301
|
+
render: ({ props }) => html`
|
|
302
|
+
<article part="card" data-elevated="${String(props.elevated)}">
|
|
303
|
+
${props.title ? `<header part="header">${escapeProp(props.title)}</header>` : ''}
|
|
304
|
+
<section part="body"><slot></slot></section>
|
|
305
|
+
${props.footer ? `<footer part="footer">${escapeProp(props.footer)}</footer>` : ''}
|
|
306
|
+
</article>
|
|
307
|
+
`,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
component<{
|
|
311
|
+
label: string;
|
|
312
|
+
type: string;
|
|
313
|
+
value: string;
|
|
314
|
+
placeholder: string;
|
|
315
|
+
name: string;
|
|
316
|
+
disabled: boolean;
|
|
317
|
+
}>(tags.input, {
|
|
318
|
+
props: {
|
|
319
|
+
label: { type: String, default: '' },
|
|
320
|
+
type: { type: String, default: 'text' },
|
|
321
|
+
value: { type: String, default: '' },
|
|
322
|
+
placeholder: { type: String, default: '' },
|
|
323
|
+
name: { type: String, default: '' },
|
|
324
|
+
disabled: { type: Boolean, default: false },
|
|
325
|
+
},
|
|
326
|
+
styles: controlStyles,
|
|
327
|
+
/**
|
|
328
|
+
* Skip the full shadow DOM re-render when only the reflected input value
|
|
329
|
+
* changed, because the live control has already been patched in place.
|
|
330
|
+
*/
|
|
331
|
+
beforeUpdate(props) {
|
|
332
|
+
if (canSkipInputRender(this, props)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
},
|
|
337
|
+
connected() {
|
|
338
|
+
const handleInput = (event: Event) => {
|
|
339
|
+
const target = event.target as HTMLInputElement | null;
|
|
340
|
+
if (!target?.matches('input')) return;
|
|
341
|
+
event.stopPropagation();
|
|
342
|
+
this.setAttribute('value', target.value);
|
|
343
|
+
this.dispatchEvent(
|
|
344
|
+
new CustomEvent('input', {
|
|
345
|
+
detail: { value: target.value },
|
|
346
|
+
bubbles: true,
|
|
347
|
+
composed: true,
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
storeHandler(this, '__bqueryInputHandler', handleInput);
|
|
352
|
+
this.shadowRoot?.addEventListener('input', handleInput);
|
|
353
|
+
},
|
|
354
|
+
disconnected() {
|
|
355
|
+
const handleInput = readHandler(this, '__bqueryInputHandler');
|
|
356
|
+
if (handleInput) {
|
|
357
|
+
this.shadowRoot?.removeEventListener('input', handleInput);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
render: ({ props }) => html`
|
|
361
|
+
<label part="field" class="field">
|
|
362
|
+
${props.label ? `<span part="label" class="label">${escapeProp(props.label)}</span>` : ''}
|
|
363
|
+
<input
|
|
364
|
+
part="control"
|
|
365
|
+
class="control"
|
|
366
|
+
type="${escapeProp(props.type)}"
|
|
367
|
+
value="${escapeProp(props.value)}"
|
|
368
|
+
placeholder="${escapeProp(props.placeholder)}"
|
|
369
|
+
name="${escapeProp(props.name)}"
|
|
370
|
+
${props.disabled ? 'disabled' : ''}
|
|
371
|
+
/>
|
|
372
|
+
</label>
|
|
373
|
+
`,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
component<{
|
|
377
|
+
label: string;
|
|
378
|
+
value: string;
|
|
379
|
+
placeholder: string;
|
|
380
|
+
name: string;
|
|
381
|
+
rows: number;
|
|
382
|
+
disabled: boolean;
|
|
383
|
+
}>(tags.textarea, {
|
|
384
|
+
props: {
|
|
385
|
+
label: { type: String, default: '' },
|
|
386
|
+
value: { type: String, default: '' },
|
|
387
|
+
placeholder: { type: String, default: '' },
|
|
388
|
+
name: { type: String, default: '' },
|
|
389
|
+
rows: { type: Number, default: 4 },
|
|
390
|
+
disabled: { type: Boolean, default: false },
|
|
391
|
+
},
|
|
392
|
+
styles: `${controlStyles}
|
|
393
|
+
textarea.control {
|
|
394
|
+
min-height: 6rem;
|
|
395
|
+
resize: vertical;
|
|
396
|
+
}
|
|
397
|
+
`,
|
|
398
|
+
/**
|
|
399
|
+
* Skip the full shadow DOM re-render when only the reflected textarea value
|
|
400
|
+
* changed, because the live control has already been patched in place.
|
|
401
|
+
*/
|
|
402
|
+
beforeUpdate(props) {
|
|
403
|
+
if (canSkipTextareaRender(this, props)) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
},
|
|
408
|
+
connected() {
|
|
409
|
+
const handleInput = (event: Event) => {
|
|
410
|
+
const target = event.target as HTMLTextAreaElement | null;
|
|
411
|
+
if (!target?.matches('textarea')) return;
|
|
412
|
+
event.stopPropagation();
|
|
413
|
+
this.setAttribute('value', target.value);
|
|
414
|
+
this.dispatchEvent(
|
|
415
|
+
new CustomEvent('input', {
|
|
416
|
+
detail: { value: target.value },
|
|
417
|
+
bubbles: true,
|
|
418
|
+
composed: true,
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
};
|
|
422
|
+
storeHandler(this, '__bqueryTextareaHandler', handleInput);
|
|
423
|
+
this.shadowRoot?.addEventListener('input', handleInput);
|
|
424
|
+
},
|
|
425
|
+
disconnected() {
|
|
426
|
+
const handleInput = readHandler(this, '__bqueryTextareaHandler');
|
|
427
|
+
if (handleInput) {
|
|
428
|
+
this.shadowRoot?.removeEventListener('input', handleInput);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
render: ({ props }) => html`
|
|
432
|
+
<label part="field" class="field">
|
|
433
|
+
${props.label ? `<span part="label" class="label">${escapeProp(props.label)}</span>` : ''}
|
|
434
|
+
${renderTextareaControl(props)}
|
|
435
|
+
</label>
|
|
436
|
+
`,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
component<{ label: string; checked: boolean; disabled: boolean }>(tags.checkbox, {
|
|
440
|
+
props: {
|
|
441
|
+
label: { type: String, default: '' },
|
|
442
|
+
checked: { type: Boolean, default: false },
|
|
443
|
+
disabled: { type: Boolean, default: false },
|
|
444
|
+
},
|
|
445
|
+
styles: `
|
|
446
|
+
${baseStyles}
|
|
447
|
+
label {
|
|
448
|
+
align-items: center;
|
|
449
|
+
color: #0f172a;
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
display: inline-flex;
|
|
452
|
+
gap: 0.625rem;
|
|
453
|
+
}
|
|
454
|
+
input {
|
|
455
|
+
accent-color: #2563eb;
|
|
456
|
+
block-size: 1rem;
|
|
457
|
+
inline-size: 1rem;
|
|
458
|
+
}
|
|
459
|
+
input:disabled {
|
|
460
|
+
cursor: not-allowed;
|
|
461
|
+
}
|
|
462
|
+
`,
|
|
463
|
+
connected() {
|
|
464
|
+
const handleChange = (event: Event) => {
|
|
465
|
+
const target = event.target as HTMLInputElement | null;
|
|
466
|
+
if (!target?.matches('input[type="checkbox"]')) return;
|
|
467
|
+
event.stopPropagation();
|
|
468
|
+
if (target.checked) {
|
|
469
|
+
this.setAttribute('checked', 'true');
|
|
470
|
+
} else {
|
|
471
|
+
this.removeAttribute('checked');
|
|
472
|
+
}
|
|
473
|
+
this.dispatchEvent(
|
|
474
|
+
new CustomEvent('change', {
|
|
475
|
+
detail: { checked: target.checked },
|
|
476
|
+
bubbles: true,
|
|
477
|
+
composed: true,
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
};
|
|
481
|
+
storeHandler(this, '__bqueryCheckboxHandler', handleChange);
|
|
482
|
+
this.shadowRoot?.addEventListener('change', handleChange);
|
|
483
|
+
},
|
|
484
|
+
disconnected() {
|
|
485
|
+
const handleChange = readHandler(this, '__bqueryCheckboxHandler');
|
|
486
|
+
if (handleChange) {
|
|
487
|
+
this.shadowRoot?.removeEventListener('change', handleChange);
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
render: ({ props }) => html`
|
|
491
|
+
<label part="label">
|
|
492
|
+
<input
|
|
493
|
+
part="control"
|
|
494
|
+
type="checkbox"
|
|
495
|
+
${props.checked ? 'checked' : ''}
|
|
496
|
+
${props.disabled ? 'disabled' : ''}
|
|
497
|
+
/>
|
|
498
|
+
<span part="text"><slot>${escapeProp(props.label)}</slot></span>
|
|
499
|
+
</label>
|
|
500
|
+
`,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
return tags;
|
|
504
|
+
};
|