@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/motion/types.ts
CHANGED
|
@@ -1,198 +1,208 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Motion module types.
|
|
3
|
-
*
|
|
4
|
-
* @module bquery/motion
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Options for view transitions.
|
|
9
|
-
*/
|
|
10
|
-
export interface TransitionOptions {
|
|
11
|
-
/** The DOM update function to execute during transition */
|
|
12
|
-
update: () => void
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*/
|
|
28
|
-
export interface
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
*/
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
*/
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
*
|
|
131
|
-
*/
|
|
132
|
-
export interface
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
*/
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
*/
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
*/
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
|
|
197
|
-
*/
|
|
198
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shared Motion module types.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/motion
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for view transitions.
|
|
9
|
+
*/
|
|
10
|
+
export interface TransitionOptions {
|
|
11
|
+
/** The DOM update function to execute during transition */
|
|
12
|
+
update: () => void | Promise<void>;
|
|
13
|
+
/** Skip the transition when reduced motion is preferred. */
|
|
14
|
+
skipOnReducedMotion?: boolean;
|
|
15
|
+
/** Classes applied to the root element while the transition is active. */
|
|
16
|
+
classes?: string[];
|
|
17
|
+
/** Transition types added when supported by the browser. */
|
|
18
|
+
types?: string[];
|
|
19
|
+
/** Called after the transition becomes ready. */
|
|
20
|
+
onReady?: () => void;
|
|
21
|
+
/** Called after the transition finishes. */
|
|
22
|
+
onFinish?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Captured element bounds for FLIP animations.
|
|
27
|
+
*/
|
|
28
|
+
export interface ElementBounds {
|
|
29
|
+
top: number;
|
|
30
|
+
left: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* FLIP animation configuration options.
|
|
37
|
+
*/
|
|
38
|
+
export interface FlipOptions {
|
|
39
|
+
/** Animation duration in milliseconds */
|
|
40
|
+
duration?: number;
|
|
41
|
+
/** CSS easing function */
|
|
42
|
+
easing?: string;
|
|
43
|
+
/** Callback when animation completes */
|
|
44
|
+
onComplete?: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stagger delay function signature.
|
|
49
|
+
*/
|
|
50
|
+
export type StaggerFunction = (index: number, total: number) => number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extended options for group FLIP animations.
|
|
54
|
+
*/
|
|
55
|
+
export interface FlipGroupOptions extends FlipOptions {
|
|
56
|
+
/** Optional stagger delay function */
|
|
57
|
+
stagger?: StaggerFunction;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Spring physics configuration.
|
|
62
|
+
*/
|
|
63
|
+
export interface SpringConfig {
|
|
64
|
+
/** Spring stiffness (default: 100) */
|
|
65
|
+
stiffness?: number;
|
|
66
|
+
/** Damping coefficient (default: 10) */
|
|
67
|
+
damping?: number;
|
|
68
|
+
/** Mass of the object (default: 1) */
|
|
69
|
+
mass?: number;
|
|
70
|
+
/** Velocity threshold for completion (default: 0.01) */
|
|
71
|
+
precision?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Spring instance for animating values.
|
|
76
|
+
*/
|
|
77
|
+
export interface Spring {
|
|
78
|
+
/** Start animating to target value */
|
|
79
|
+
to(target: number): Promise<void>;
|
|
80
|
+
/** Get current animated value */
|
|
81
|
+
current(): number;
|
|
82
|
+
/** Stop the animation */
|
|
83
|
+
stop(): void;
|
|
84
|
+
/** Subscribe to value changes */
|
|
85
|
+
onChange(callback: (value: number) => void): () => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Web Animations helper configuration.
|
|
90
|
+
*/
|
|
91
|
+
export interface AnimateOptions {
|
|
92
|
+
/** Keyframes to animate */
|
|
93
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes;
|
|
94
|
+
/** Animation options (duration, easing, etc.) */
|
|
95
|
+
options?: KeyframeAnimationOptions;
|
|
96
|
+
/** Commit final styles to the element (default: true) */
|
|
97
|
+
commitStyles?: boolean;
|
|
98
|
+
/** Respect prefers-reduced-motion (default: true) */
|
|
99
|
+
respectReducedMotion?: boolean;
|
|
100
|
+
/** Callback when animation completes */
|
|
101
|
+
onFinish?: () => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stagger helper configuration.
|
|
106
|
+
*/
|
|
107
|
+
export interface StaggerOptions {
|
|
108
|
+
/** Start delay in milliseconds (default: 0) */
|
|
109
|
+
start?: number;
|
|
110
|
+
/** Origin index or keyword (default: 'start') */
|
|
111
|
+
from?: 'start' | 'center' | 'end' | number;
|
|
112
|
+
/** Optional easing function for normalized distance */
|
|
113
|
+
easing?: EasingFunction;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Easing function signature.
|
|
118
|
+
*/
|
|
119
|
+
export type EasingFunction = (t: number) => number;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sequence step configuration.
|
|
123
|
+
*/
|
|
124
|
+
export interface SequenceStep extends AnimateOptions {
|
|
125
|
+
/** Target element to animate */
|
|
126
|
+
target: Element;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sequence run configuration.
|
|
131
|
+
*/
|
|
132
|
+
export interface SequenceOptions {
|
|
133
|
+
/** Optional stagger delay between steps */
|
|
134
|
+
stagger?: StaggerFunction;
|
|
135
|
+
/** Callback when sequence completes */
|
|
136
|
+
onFinish?: () => void;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Timeline step configuration.
|
|
141
|
+
*/
|
|
142
|
+
export interface TimelineStep {
|
|
143
|
+
/** Target element to animate */
|
|
144
|
+
target: Element;
|
|
145
|
+
/** Keyframes to animate */
|
|
146
|
+
keyframes: Keyframe[] | PropertyIndexedKeyframes;
|
|
147
|
+
/** Animation options for this step */
|
|
148
|
+
options?: KeyframeAnimationOptions;
|
|
149
|
+
/** Absolute or relative start time in milliseconds */
|
|
150
|
+
at?: number | `+=${number}` | `-=${number}`;
|
|
151
|
+
/** Optional label for debugging */
|
|
152
|
+
label?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Timeline configuration.
|
|
157
|
+
*/
|
|
158
|
+
export interface TimelineConfig {
|
|
159
|
+
/** Commit final styles when timeline completes (default: true) */
|
|
160
|
+
commitStyles?: boolean;
|
|
161
|
+
/** Respect prefers-reduced-motion (default: true) */
|
|
162
|
+
respectReducedMotion?: boolean;
|
|
163
|
+
/** Callback when timeline completes */
|
|
164
|
+
onFinish?: () => void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Timeline controls.
|
|
169
|
+
*/
|
|
170
|
+
export interface TimelineControls {
|
|
171
|
+
/** Play all steps */
|
|
172
|
+
play(): Promise<void>;
|
|
173
|
+
/** Pause animations */
|
|
174
|
+
pause(): void;
|
|
175
|
+
/** Resume animations */
|
|
176
|
+
resume(): void;
|
|
177
|
+
/** Stop and cancel animations */
|
|
178
|
+
stop(): void;
|
|
179
|
+
/** Seek to a specific time in milliseconds */
|
|
180
|
+
seek(time: number): void;
|
|
181
|
+
/** Add a step to the timeline */
|
|
182
|
+
add(step: TimelineStep): void;
|
|
183
|
+
/** Total timeline duration in milliseconds */
|
|
184
|
+
duration(): number;
|
|
185
|
+
/** Subscribe to finish events */
|
|
186
|
+
onFinish(callback: () => void): () => void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Scroll animation configuration.
|
|
191
|
+
*/
|
|
192
|
+
export interface ScrollAnimateOptions extends AnimateOptions {
|
|
193
|
+
/** IntersectionObserver root */
|
|
194
|
+
root?: Element | Document | null;
|
|
195
|
+
/** Root margin for observer */
|
|
196
|
+
rootMargin?: string;
|
|
197
|
+
/** Intersection thresholds */
|
|
198
|
+
threshold?: number | number[];
|
|
199
|
+
/** Trigger only once (default: true) */
|
|
200
|
+
once?: boolean;
|
|
201
|
+
/** Callback when element enters the viewport */
|
|
202
|
+
onEnter?: (element: Element) => void;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Cleanup function for scroll animations.
|
|
207
|
+
*/
|
|
208
|
+
export type ScrollAnimateCleanup = () => void;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility live-region announcer helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module bquery/platform
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { effect, signal, type Signal } from '../reactive/signal';
|
|
8
|
+
import { getBqueryConfig } from './config';
|
|
9
|
+
|
|
10
|
+
/** Options for creating an announcer. */
|
|
11
|
+
export interface UseAnnouncerOptions {
|
|
12
|
+
/** Live region politeness. */
|
|
13
|
+
politeness?: 'polite' | 'assertive';
|
|
14
|
+
/** Whether the live region should be atomic. */
|
|
15
|
+
atomic?: boolean;
|
|
16
|
+
/** Delay before applying the message. */
|
|
17
|
+
delay?: number;
|
|
18
|
+
/** Delay after which the message is cleared automatically. */
|
|
19
|
+
clearDelay?: number;
|
|
20
|
+
/** Optional element id for the live region. */
|
|
21
|
+
id?: string;
|
|
22
|
+
/** Optional CSS class name. */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Optional container used to append the live region. */
|
|
25
|
+
container?: HTMLElement;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Runtime options for a single announcement. */
|
|
29
|
+
export interface AnnounceOptions {
|
|
30
|
+
/** Override politeness for this specific announcement. */
|
|
31
|
+
politeness?: 'polite' | 'assertive';
|
|
32
|
+
/** Override the message delay for this specific announcement. */
|
|
33
|
+
delay?: number;
|
|
34
|
+
/** Override the auto-clear delay for this specific announcement. */
|
|
35
|
+
clearDelay?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Returned announcer API. */
|
|
39
|
+
export interface AnnouncerHandle {
|
|
40
|
+
/** The live region element or null outside the DOM. */
|
|
41
|
+
element: HTMLElement | null;
|
|
42
|
+
/** Reactive message signal. */
|
|
43
|
+
message: Signal<string>;
|
|
44
|
+
/** Announce a message to assistive technologies. */
|
|
45
|
+
announce: (value: string, options?: AnnounceOptions) => void;
|
|
46
|
+
/** Clear the current announcement. */
|
|
47
|
+
clear: () => void;
|
|
48
|
+
/** Remove the live region if it was created by this announcer. */
|
|
49
|
+
destroy: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const visuallyHiddenStyle = [
|
|
53
|
+
'position:absolute',
|
|
54
|
+
'width:1px',
|
|
55
|
+
'height:1px',
|
|
56
|
+
'padding:0',
|
|
57
|
+
'margin:-1px',
|
|
58
|
+
'overflow:hidden',
|
|
59
|
+
'clip:rect(0, 0, 0, 0)',
|
|
60
|
+
'white-space:nowrap',
|
|
61
|
+
'border:0',
|
|
62
|
+
].join(';');
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create or reuse an accessible live region.
|
|
66
|
+
*
|
|
67
|
+
* @param options - Live region configuration
|
|
68
|
+
* @returns An announcer handle with announce(), clear(), and destroy()
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const announcer = useAnnouncer();
|
|
73
|
+
* announcer.announce('Saved successfully');
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const useAnnouncer = (options: UseAnnouncerOptions = {}): AnnouncerHandle => {
|
|
77
|
+
const defaults = getBqueryConfig().announcer;
|
|
78
|
+
const resolvedOptions: Required<
|
|
79
|
+
Pick<UseAnnouncerOptions, 'politeness' | 'atomic' | 'delay' | 'clearDelay'>
|
|
80
|
+
> &
|
|
81
|
+
UseAnnouncerOptions = {
|
|
82
|
+
politeness: defaults?.politeness ?? 'polite',
|
|
83
|
+
atomic: defaults?.atomic ?? true,
|
|
84
|
+
delay: defaults?.delay ?? 16,
|
|
85
|
+
clearDelay: defaults?.clearDelay ?? 1000,
|
|
86
|
+
...options,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const message = signal('');
|
|
90
|
+
|
|
91
|
+
if (typeof document === 'undefined') {
|
|
92
|
+
return {
|
|
93
|
+
element: null,
|
|
94
|
+
message,
|
|
95
|
+
announce(value: string) {
|
|
96
|
+
message.value = value;
|
|
97
|
+
},
|
|
98
|
+
clear() {
|
|
99
|
+
message.value = '';
|
|
100
|
+
},
|
|
101
|
+
destroy() {
|
|
102
|
+
message.value = '';
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const existing = resolvedOptions.id ? document.getElementById(resolvedOptions.id) : null;
|
|
108
|
+
const element = (existing ?? document.createElement('div')) as HTMLElement;
|
|
109
|
+
const created = !existing;
|
|
110
|
+
|
|
111
|
+
if (resolvedOptions.id) {
|
|
112
|
+
element.id = resolvedOptions.id;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (resolvedOptions.className) {
|
|
116
|
+
element.className = resolvedOptions.className;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
element.setAttribute('aria-live', resolvedOptions.politeness);
|
|
120
|
+
element.setAttribute('aria-atomic', String(resolvedOptions.atomic));
|
|
121
|
+
element.setAttribute('role', resolvedOptions.politeness === 'assertive' ? 'alert' : 'status');
|
|
122
|
+
element.setAttribute('data-bquery-announcer', 'true');
|
|
123
|
+
if (!element.getAttribute('style')) {
|
|
124
|
+
element.setAttribute('style', visuallyHiddenStyle);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (created) {
|
|
128
|
+
const parent = resolvedOptions.container ?? document.body ?? document.documentElement;
|
|
129
|
+
if (!parent) {
|
|
130
|
+
return {
|
|
131
|
+
element: null,
|
|
132
|
+
message,
|
|
133
|
+
announce(value: string) {
|
|
134
|
+
message.value = value;
|
|
135
|
+
},
|
|
136
|
+
clear() {
|
|
137
|
+
message.value = '';
|
|
138
|
+
},
|
|
139
|
+
destroy() {
|
|
140
|
+
message.value = '';
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
parent.appendChild(element);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const disposeMessageEffect = effect(() => {
|
|
148
|
+
element.textContent = message.value;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let messageTimer: ReturnType<typeof setTimeout> | undefined;
|
|
152
|
+
let clearTimer: ReturnType<typeof setTimeout> | undefined;
|
|
153
|
+
let destroyed = false;
|
|
154
|
+
|
|
155
|
+
const clearTimers = (): void => {
|
|
156
|
+
if (messageTimer) {
|
|
157
|
+
clearTimeout(messageTimer);
|
|
158
|
+
messageTimer = undefined;
|
|
159
|
+
}
|
|
160
|
+
if (clearTimer) {
|
|
161
|
+
clearTimeout(clearTimer);
|
|
162
|
+
clearTimer = undefined;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const clear = (): void => {
|
|
167
|
+
if (destroyed) return;
|
|
168
|
+
clearTimers();
|
|
169
|
+
message.value = '';
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const announce = (value: string, announceOptions: AnnounceOptions = {}): void => {
|
|
173
|
+
if (destroyed) return;
|
|
174
|
+
const politeness = announceOptions.politeness ?? resolvedOptions.politeness;
|
|
175
|
+
const delay = announceOptions.delay ?? resolvedOptions.delay;
|
|
176
|
+
const clearDelay = announceOptions.clearDelay ?? resolvedOptions.clearDelay;
|
|
177
|
+
|
|
178
|
+
clearTimers();
|
|
179
|
+
|
|
180
|
+
element.setAttribute('aria-live', politeness);
|
|
181
|
+
element.setAttribute('role', politeness === 'assertive' ? 'alert' : 'status');
|
|
182
|
+
message.value = '';
|
|
183
|
+
|
|
184
|
+
messageTimer = setTimeout(() => {
|
|
185
|
+
if (destroyed) return;
|
|
186
|
+
message.value = value;
|
|
187
|
+
if (clearDelay > 0) {
|
|
188
|
+
clearTimer = setTimeout(() => {
|
|
189
|
+
if (destroyed) return;
|
|
190
|
+
message.value = '';
|
|
191
|
+
}, clearDelay);
|
|
192
|
+
}
|
|
193
|
+
}, delay);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const destroy = (): void => {
|
|
197
|
+
if (destroyed) return;
|
|
198
|
+
destroyed = true;
|
|
199
|
+
clearTimers();
|
|
200
|
+
message.value = '';
|
|
201
|
+
disposeMessageEffect();
|
|
202
|
+
if (created) {
|
|
203
|
+
element.remove();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return { element, message, announce, clear, destroy };
|
|
208
|
+
};
|