@brytdesigns/web-component-core 1.1.7 → 3.0.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/dist/main/animation/awaitAllAnimations.js +28 -32
- package/dist/main/animation/awaitAllAnimations.js.map +1 -1
- package/dist/main/animation/content.js +156 -0
- package/dist/main/animation/content.js.map +1 -0
- package/dist/main/animation/controlsToPromise.js +6 -5
- package/dist/main/animation/controlsToPromise.js.map +1 -1
- package/dist/main/animation/getTransitionConfig.js +52 -51
- package/dist/main/animation/getTransitionConfig.js.map +1 -1
- package/dist/main/animation/index.d.ts +61 -1
- package/dist/main/animation/index.js +5 -5
- package/dist/main/focusTrap/index.d.ts +1 -1
- package/dist/main/focusTrap/index.js +10 -14
- package/dist/main/focusTrap/index.js.map +1 -1
- package/dist/main/hooks/index.d.ts +14 -9
- package/dist/main/hooks/index.js +5 -6
- package/dist/main/hooks/internal/getValueFromPath.js +9 -10
- package/dist/main/hooks/internal/getValueFromPath.js.map +1 -1
- package/dist/main/hooks/makePathContextHooks.js +58 -68
- package/dist/main/hooks/makePathContextHooks.js.map +1 -1
- package/dist/main/hooks/makePathDataArrayComponent.js +61 -67
- package/dist/main/hooks/makePathDataArrayComponent.js.map +1 -1
- package/dist/main/hooks/makePathDataArrayItemComponent.js +17 -21
- package/dist/main/hooks/makePathDataArrayItemComponent.js.map +1 -1
- package/dist/main/hooks/makePathDataComponent.js +31 -33
- package/dist/main/hooks/makePathDataComponent.js.map +1 -1
- package/dist/main/index.d.ts +304 -27
- package/dist/main/index.js +28 -7
- package/dist/main/promise/abortablePromise.js +10 -15
- package/dist/main/promise/abortablePromise.js.map +1 -1
- package/dist/main/promise/index.js +2 -3
- package/dist/main/utils/a11y.js +133 -0
- package/dist/main/utils/a11y.js.map +1 -0
- package/dist/main/utils/body-scroll-lock.js +40 -18
- package/dist/main/utils/body-scroll-lock.js.map +1 -1
- package/dist/main/utils/checkLoaded.js +4 -4
- package/dist/main/utils/checkLoaded.js.map +1 -1
- package/dist/main/utils/createRemoteContext.js +85 -0
- package/dist/main/utils/createRemoteContext.js.map +1 -0
- package/dist/main/utils/disclosure.js +52 -0
- package/dist/main/utils/disclosure.js.map +1 -0
- package/dist/main/utils/formatValue.js +14 -18
- package/dist/main/utils/formatValue.js.map +1 -1
- package/dist/main/utils/getContextFromProvider.js +15 -10
- package/dist/main/utils/getContextFromProvider.js.map +1 -1
- package/dist/main/utils/getTemplateContent.js +6 -7
- package/dist/main/utils/getTemplateContent.js.map +1 -1
- package/dist/main/utils/guarded-effect.js +72 -0
- package/dist/main/utils/guarded-effect.js.map +1 -0
- package/dist/main/utils/index.d.ts +237 -20
- package/dist/main/utils/index.js +15 -12
- package/dist/main/utils/invokeOnLoaded.js +13 -13
- package/dist/main/utils/invokeOnLoaded.js.map +1 -1
- package/dist/main/utils/isCustomElement.js +4 -3
- package/dist/main/utils/isCustomElement.js.map +1 -1
- package/dist/main/utils/nested-overlays.js +36 -0
- package/dist/main/utils/nested-overlays.js.map +1 -0
- package/dist/main/utils/solid-element.js +48 -88
- package/dist/main/utils/solid-element.js.map +1 -1
- package/dist/main/utils/toHyphenated.js +4 -3
- package/dist/main/utils/toHyphenated.js.map +1 -1
- package/package.json +16 -15
- package/dist/esm/animation/index.js +0 -97
- package/dist/esm/animation/index.js.map +0 -1
- package/dist/esm/focusTrap/index.js +0 -18
- package/dist/esm/focusTrap/index.js.map +0 -1
- package/dist/esm/hooks/index.js +0 -235
- package/dist/esm/hooks/index.js.map +0 -1
- package/dist/esm/index.js +0 -547
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/promise/index.js +0 -20
- package/dist/esm/promise/index.js.map +0 -1
- package/dist/esm/utils/index.js +0 -236
- package/dist/esm/utils/index.js.map +0 -1
- package/dist/main/animation/index.js.map +0 -1
- package/dist/main/hooks/index.js.map +0 -1
- package/dist/main/hooks/internal/index.js +0 -3
- package/dist/main/hooks/internal/index.js.map +0 -1
- package/dist/main/hooks/internal/types.js +0 -3
- package/dist/main/hooks/internal/types.js.map +0 -1
- package/dist/main/index.js.map +0 -1
- package/dist/main/promise/index.js.map +0 -1
- package/dist/main/utils/createWithElementContext.js +0 -64
- package/dist/main/utils/createWithElementContext.js.map +0 -1
- package/dist/main/utils/index.js.map +0 -1
package/dist/main/index.d.ts
CHANGED
|
@@ -1,15 +1,73 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
3
|
import { BodyScrollOptions } from 'body-scroll-lock-upgrade';
|
|
4
|
-
import { ComponentType as
|
|
4
|
+
import { ComponentType as ComponentRegisterComponentType, ICustomElement, createContext, provide as _provide } from 'component-register';
|
|
5
5
|
import { FocusTrap, Options } from 'focus-trap';
|
|
6
6
|
import { AnimationGeneratorType, AnimationPlaybackControlsWithThen, Easing, RepeatType } from 'motion';
|
|
7
|
-
import { Accessor } from 'solid-js';
|
|
7
|
+
import { Accessor, EffectFunction, EffectOptions } from 'solid-js';
|
|
8
8
|
|
|
9
|
-
export declare const createFocusTrap: (element: HTMLElement, options?: Accessor<Options>) => Accessor<FocusTrap>;
|
|
10
|
-
export declare function abortablePromise(promise: Promise<any>, signal: AbortSignal): Promise<unknown>;
|
|
11
|
-
export declare function controlsToPromise(controls: AnimationPlaybackControlsWithThen): Promise<unknown>;
|
|
12
9
|
export declare function awaitAllAnimations(promises: AnimationPlaybackControlsWithThen[]): Promise<unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the visitor has asked their device to reduce motion. Read on every
|
|
12
|
+
* open and close, so changing the setting applies without a reload.
|
|
13
|
+
*/
|
|
14
|
+
export declare function shouldReduceMotion(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the direction this animation was started for has since been left.
|
|
17
|
+
*
|
|
18
|
+
* Toggling mid-animation re-runs the caller's effect, whose cleanup calls
|
|
19
|
+
* `animation.complete()` — which resolves the promise, so the `.then` below
|
|
20
|
+
* fires as a microtask *after* the new direction has already applied its
|
|
21
|
+
* styles, and would undo them. Callers flip their own flag in that cleanup;
|
|
22
|
+
* the default is for callers that never re-enter.
|
|
23
|
+
*/
|
|
24
|
+
export type IsStale = () => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the browser supports `hidden="until-found"`, which keeps a closed
|
|
27
|
+
* content's text reachable by find-in-page and text-fragment links.
|
|
28
|
+
*/
|
|
29
|
+
export declare function supportsHiddenUntilFound(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Puts the content in its open end state, without animating.
|
|
32
|
+
*
|
|
33
|
+
* `flow-root` rather than `block`, so the panel is always a block formatting
|
|
34
|
+
* context and always contains its children's margins.
|
|
35
|
+
*
|
|
36
|
+
* Without it the box model changed underneath the animation. The measurement
|
|
37
|
+
* runs before `overflow: hidden` is applied, so a child margin collapsed out
|
|
38
|
+
* of the panel and the target came back short; the animation then set
|
|
39
|
+
* `overflow: hidden`, which *makes* the panel a BFC, so the same content
|
|
40
|
+
* suddenly needed more room than the target allowed and stayed clipped — and
|
|
41
|
+
* snapped into place when `overflow` was removed at the end. A `<p>` with
|
|
42
|
+
* default margins inside a plain `<div>` measured 24px and rendered 56px.
|
|
43
|
+
*
|
|
44
|
+
* `flow-root` is the modern way to ask for a BFC without clipping, which is
|
|
45
|
+
* exactly what is wanted here: `overflow: hidden` during the animation now
|
|
46
|
+
* changes nothing about the box, only what is painted outside it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function showContent(element: HTMLElement): void;
|
|
49
|
+
/**
|
|
50
|
+
* Puts the content in its closed end state, without animating.
|
|
51
|
+
*
|
|
52
|
+
* Where supported, the content is closed with `hidden="until-found"` rather
|
|
53
|
+
* than `display: none`, so find-in-page can still match its text. The inline
|
|
54
|
+
* styles zero the box, which `until-found` alone leaves its padding, and keep
|
|
55
|
+
* it a block: `until-found` hides content through `content-visibility`, which
|
|
56
|
+
* does not apply to an inline box, and custom elements are inline by default.
|
|
57
|
+
*/
|
|
58
|
+
export declare function hideContent(element: HTMLElement): void;
|
|
59
|
+
/**
|
|
60
|
+
* Calls `callback` when find-in-page or a text-fragment link matches text
|
|
61
|
+
* inside the closed content. It fires before the browser removes `hidden` and
|
|
62
|
+
* scrolls to the match, so layout changes made synchronously in `callback`
|
|
63
|
+
* are settled by the time the browser scrolls. Returns a cleanup function.
|
|
64
|
+
*/
|
|
65
|
+
export declare function onContentRevealed(element: HTMLElement, callback: () => void): () => void;
|
|
66
|
+
/** Animates the content open from zero height. */
|
|
67
|
+
export declare function expandContent(element: HTMLElement, isStale?: IsStale): import("motion").AnimationPlaybackControlsWithThen;
|
|
68
|
+
/** Animates the content closed to zero height. */
|
|
69
|
+
export declare function collapseContent(element: HTMLElement, isStale?: IsStale): import("motion").AnimationPlaybackControlsWithThen;
|
|
70
|
+
export declare function controlsToPromise(controls: AnimationPlaybackControlsWithThen): Promise<unknown>;
|
|
13
71
|
export declare function getTransitionConfig(style: CSSStyleDeclaration): {
|
|
14
72
|
bounce: number | undefined;
|
|
15
73
|
bounceDamping: number | undefined;
|
|
@@ -34,30 +92,248 @@ export declare function getTransitionConfig(style: CSSStyleDeclaration): {
|
|
|
34
92
|
velocity: number | undefined;
|
|
35
93
|
visualDuration: number | undefined;
|
|
36
94
|
};
|
|
37
|
-
export declare
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
95
|
+
export declare const createFocusTrap: (element: HTMLElement, options?: Accessor<Options>) => import("solid-js").SourceAccessor<FocusTrap>;
|
|
96
|
+
/**
|
|
97
|
+
* Shared accessibility helpers.
|
|
98
|
+
*
|
|
99
|
+
* Everything here was either duplicated across leaf packages or missing
|
|
100
|
+
* entirely while each overlay reimplemented a piece of it.
|
|
101
|
+
*/
|
|
102
|
+
/**
|
|
103
|
+
* Visually hidden, still in the accessibility tree.
|
|
104
|
+
*
|
|
105
|
+
* Byte-identical to the copies that lived in `keen-slider-scrollbar`,
|
|
106
|
+
* `keen-slider-navigation-arrows` and `keen-slider-navigation-dots` — three
|
|
107
|
+
* copies of the same clip-rect incantation, none of which could be fixed
|
|
108
|
+
* without finding the other two.
|
|
109
|
+
*/
|
|
110
|
+
export declare const srOnly = "border-width:0 !important;clip:rect(1px, 1px, 1px, 1px) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;margin-top:-1px !important;margin-bottom:-1px !important;margin-right:-1px !important;margin-left:-1px !important;overflow:hidden !important;padding-top:0 !important;padding-bottom:0 !important;padding-right:0 !important;padding-left:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important;";
|
|
111
|
+
/**
|
|
112
|
+
* The panel belonging to this overlay — `dialog-content`, `drawer-content` or
|
|
113
|
+
* `modal-panel` — skipping the panels of overlays nested inside it.
|
|
114
|
+
*
|
|
115
|
+
* Same shape as `findDisclosureContent`, and for the same reason: a plain
|
|
116
|
+
* `querySelector` finds a nested overlay's panel first whenever the nested one
|
|
117
|
+
* is authored above its parent's own content.
|
|
118
|
+
*/
|
|
119
|
+
export declare function findOverlayPanel(context: HTMLElement, panelTag: string, selector?: string): HTMLElement | null;
|
|
120
|
+
export type FocusReturn = {
|
|
121
|
+
/** Remembers what holds focus now, to restore later. */
|
|
122
|
+
record: () => void;
|
|
123
|
+
/** Restores it. Returns whether focus was actually moved. */
|
|
124
|
+
restore: () => boolean;
|
|
41
125
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Remembers where focus came from when an overlay opens, and puts it back when
|
|
128
|
+
* the overlay closes.
|
|
129
|
+
*
|
|
130
|
+
* `focus-trap` already does this, but only while a trap is active — so an
|
|
131
|
+
* overlay with `should-trap-focus="false"` dropped focus on the floor, and a
|
|
132
|
+
* keyboard user was returned to the top of the document. Keeping it separate
|
|
133
|
+
* means focus return is a property of opening and closing rather than a
|
|
134
|
+
* side effect of trapping.
|
|
135
|
+
*
|
|
136
|
+
* Focus already inside the overlay is not recorded: that is where focus is
|
|
137
|
+
* going, not where it came from, and restoring to it would strand the user in
|
|
138
|
+
* a hidden subtree.
|
|
139
|
+
*/
|
|
140
|
+
export declare function createFocusReturn(element: HTMLElement): FocusReturn;
|
|
141
|
+
export type EscapeHandlerOptions = {
|
|
142
|
+
/** The overlay root — the element carrying `is-open`. */
|
|
143
|
+
element: HTMLElement;
|
|
144
|
+
/** Called when Escape should close this overlay. */
|
|
145
|
+
onEscape: () => void;
|
|
146
|
+
/** Checked at key time, so toggling the attribute takes effect at once. */
|
|
147
|
+
isEnabled?: () => boolean;
|
|
148
|
+
/** Overridable for tests; defaults to the three overlay context tags. */
|
|
149
|
+
selector?: string;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Closes the topmost overlay on Escape. Returns its own teardown.
|
|
153
|
+
*
|
|
154
|
+
* This exists because Escape was previously `focus-trap`'s `escapeDeactivates`
|
|
155
|
+
* option, which has two consequences nobody wanted: Escape did nothing at all
|
|
156
|
+
* unless focus trapping was *also* on, and the two settings could not be
|
|
157
|
+
* configured independently even though they are different promises to the
|
|
158
|
+
* user.
|
|
159
|
+
*
|
|
160
|
+
* Only the innermost open overlay responds, so Escape inside a nested dialog
|
|
161
|
+
* closes that dialog rather than collapsing the whole stack.
|
|
162
|
+
*/
|
|
163
|
+
export declare function createEscapeHandler({ element, onEscape, isEnabled, selector, }: EscapeHandlerOptions): () => void;
|
|
164
|
+
export type Politeness = "polite" | "assertive";
|
|
165
|
+
/**
|
|
166
|
+
* Announces a message to assistive tech through a single shared live region.
|
|
167
|
+
*
|
|
168
|
+
* One region for the whole page, not one per component: a live region has to
|
|
169
|
+
* be in the DOM *before* its content changes for the change to be announced,
|
|
170
|
+
* so a component that creates one and fills it in the same tick announces
|
|
171
|
+
* nothing. Creating it once, on first use, sidesteps that.
|
|
172
|
+
*
|
|
173
|
+
* The text is cleared and set on a later tick for the same reason in reverse —
|
|
174
|
+
* writing identical text is not a change, so a screen reader stays silent when
|
|
175
|
+
* the same message is announced twice in a row.
|
|
176
|
+
*/
|
|
177
|
+
export declare function announce(message: string, politeness?: Politeness): void;
|
|
178
|
+
export declare const disableBodyScroll: (element: ICustomElement & HTMLElement, options?: BodyScrollOptions) => void;
|
|
179
|
+
export declare const enableBodyScroll: (element: ICustomElement & HTMLElement) => void;
|
|
48
180
|
export declare function checkLoaded(): boolean;
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
}
|
|
181
|
+
export type ContextRef = {
|
|
182
|
+
id: symbol;
|
|
183
|
+
};
|
|
184
|
+
export declare class RemoteContextError extends Error {
|
|
185
|
+
readonly target: unknown;
|
|
186
|
+
constructor(message: string, target: unknown);
|
|
187
|
+
}
|
|
188
|
+
export type RemoteContextOptions = {
|
|
189
|
+
/**
|
|
190
|
+
* Map a `target` to the element that actually provides the context.
|
|
191
|
+
*
|
|
192
|
+
* Synchronous by design — it answers "which element, given the DOM as it is
|
|
193
|
+
* now". Waiting for the element to appear is the helper's job, and it
|
|
194
|
+
* re-runs `resolve` on each mutation, so a strategy never has to implement
|
|
195
|
+
* retrying itself.
|
|
196
|
+
*
|
|
197
|
+
* Defaults to `document.querySelector(target)`, falling back to the host
|
|
198
|
+
* element when `target` is empty.
|
|
199
|
+
*/
|
|
200
|
+
resolve?: (target: string | undefined, host: HTMLElement) => Element | null;
|
|
201
|
+
/** Label used in warnings and errors. Defaults to the context's symbol. */
|
|
202
|
+
name?: string;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Build a hook that resolves one context from a *lateral* element.
|
|
206
|
+
*
|
|
207
|
+
* ```ts
|
|
208
|
+
* export const useDrawerAt = createRemoteContext<DrawerContext>(DrawerContextState);
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
export declare function createRemoteContext<T>(context: ContextRef, { resolve, name }?: RemoteContextOptions): (target: () => string | undefined, host: HTMLElement) => import("solid-js").SourceAccessor<T & ({} | null)>;
|
|
52
212
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
|
|
213
|
+
* ARIA wiring for disclosure widgets (accordion and collapsible): the
|
|
214
|
+
* trigger's button reports whether its content is open and which content it
|
|
215
|
+
* controls.
|
|
216
|
+
*/
|
|
217
|
+
/**
|
|
218
|
+
* The element a trigger exposes to assistive tech: the trigger itself when it
|
|
219
|
+
* has `role="button"`, otherwise the first button inside it.
|
|
220
|
+
*/
|
|
221
|
+
export declare function findDisclosureControl(trigger: HTMLElement): HTMLElement | null;
|
|
222
|
+
/**
|
|
223
|
+
* The content belonging to the same item as `trigger`, skipping the content of items
|
|
224
|
+
* nested inside it.
|
|
225
|
+
*/
|
|
226
|
+
export declare function findDisclosureContent(trigger: HTMLElement, itemTag: string, contentTag: string): HTMLElement | null;
|
|
227
|
+
/** Returns the element's id, assigning a unique `${prefix}-N` if it has none. */
|
|
228
|
+
export declare function ensureId(element: HTMLElement, prefix: string): string;
|
|
229
|
+
/**
|
|
230
|
+
* Points the control's `aria-controls` at the content, unless the markup already
|
|
231
|
+
* set one. Returns whether the control is linked.
|
|
232
|
+
*/
|
|
233
|
+
export declare function linkDisclosureControl(control: HTMLElement, content: HTMLElement | null, idPrefix: string): boolean;
|
|
56
234
|
export type Format = "currency" | "number" | "boolean" | "invert_boolean" | string;
|
|
57
235
|
export declare function formatValue(format: Format, value: unknown): any;
|
|
236
|
+
/**
|
|
237
|
+
* Read a `component-register` context off an element, synchronously.
|
|
238
|
+
*
|
|
239
|
+
* The imperative counterpart to `createRemoteContext`: no waiting, no effect,
|
|
240
|
+
* callable from a plain `<script>` in a Liquid template when you already know
|
|
241
|
+
* the element is there. Deliberately kept separate — forcing an async,
|
|
242
|
+
* effect-shaped API on this use case would break theme scripts for no gain.
|
|
243
|
+
*/
|
|
244
|
+
export declare function getContextFromProvider<T>(context: ReturnType<typeof createContext>, element: Element): T & ({} | null);
|
|
58
245
|
export declare function getTemplateContent(element: HTMLElement, state: string): DocumentFragment | null;
|
|
59
|
-
|
|
60
|
-
|
|
246
|
+
/**
|
|
247
|
+
* `createEffect` whose **apply phase cannot take the page down**.
|
|
248
|
+
*
|
|
249
|
+
* ## Why this exists
|
|
250
|
+
*
|
|
251
|
+
* Solid 2 treats the two phases of an effect very differently, and only one
|
|
252
|
+
* of them is dangerous. Measured against the installed `2.0.0-rc.14`, in
|
|
253
|
+
* `tests/component/reactivity-halt.test.ts`:
|
|
254
|
+
*
|
|
255
|
+
* | throw site | rest of the page |
|
|
256
|
+
* | ----------------- | --------------------------- |
|
|
257
|
+
* | component setup | unaffected |
|
|
258
|
+
* | compute phase | unaffected — the run is skipped and logged |
|
|
259
|
+
* | **apply phase** | **every computation on the page stops, permanently** |
|
|
260
|
+
*
|
|
261
|
+
* An uncaught apply-phase throw calls the runtime's `haltReactivity`, which
|
|
262
|
+
* is module-level global state. On a Shopify page every Bryt element shares
|
|
263
|
+
* one `solid-js` singleton, so one bad attribute on one element freezes every
|
|
264
|
+
* drawer, modal, slider and the cart until the shopper reloads.
|
|
265
|
+
*
|
|
266
|
+
* ## Why not an error boundary
|
|
267
|
+
*
|
|
268
|
+
* Because it does not work. `createErrorBoundary` is documented as catching
|
|
269
|
+
* this, and it does not in rc.14: the characterization test drives a
|
|
270
|
+
* subscribed boundary around the throwing effect and the fallback never runs
|
|
271
|
+
* while the rest of the page still freezes. Upstream's own guidance in
|
|
272
|
+
* `@solidjs/signals`' `createEffect` docblock is the half that holds —
|
|
273
|
+
*
|
|
274
|
+
* > The **effect phase is different**: it is your own imperative code, so
|
|
275
|
+
* > handle failures with `try/catch` where they occur.
|
|
276
|
+
*
|
|
277
|
+
* — which is all this function is. If a later release makes boundaries catch
|
|
278
|
+
* it, the characterization test starts failing and says so.
|
|
279
|
+
*
|
|
280
|
+
* The compute phase is deliberately NOT guarded. It is already non-fatal, and
|
|
281
|
+
* swallowing a compute error would hide a genuine reactivity bug behind an
|
|
282
|
+
* effect that silently stops updating.
|
|
283
|
+
*
|
|
284
|
+
* @param compute the tracking phase — reads signals, returns the value
|
|
285
|
+
* @param apply the imperative phase, may return a cleanup function
|
|
286
|
+
* @param options forwarded to `createEffect`; `name` is used in the report
|
|
287
|
+
*/
|
|
288
|
+
export declare function createGuardedEffect<const T>(compute: (prev: T | undefined) => T, apply: EffectFunction<NoInfer<T>, T>, options?: EffectOptions): void;
|
|
289
|
+
export declare function invokeOnLoaded(fn: () => void, { signal }: {
|
|
290
|
+
signal?: AbortSignal;
|
|
291
|
+
}): void;
|
|
292
|
+
export declare function isCustomElement(element: any): element is ICustomElement & HTMLElement;
|
|
293
|
+
/**
|
|
294
|
+
* Closing an overlay has to close the overlays inside it.
|
|
295
|
+
*
|
|
296
|
+
* A dialog nested in another is hidden along with its parent, but
|
|
297
|
+
* nothing changes its own `isOpen`. It keeps reporting `is-open="true"` while
|
|
298
|
+
* invisible, never fires `drawer:close`, never releases what it holds, and
|
|
299
|
+
* reappears still open when the parent is opened again.
|
|
300
|
+
*
|
|
301
|
+
* These components have no concept of a parent — no containment check anywhere
|
|
302
|
+
* in either package — so the relationship is established here, at the point
|
|
303
|
+
* where it is needed, rather than threaded through context.
|
|
304
|
+
*
|
|
305
|
+
* Writing a *descendant's* attribute is allowed in Solid 2. Writing the
|
|
306
|
+
* component's own prop is what `REACTIVE_WRITE_IN_OWNED_SCOPE` forbids, which
|
|
307
|
+
* is why this takes the element rather than a setter.
|
|
308
|
+
*/
|
|
309
|
+
/**
|
|
310
|
+
* Both kinds, not just the caller's own. A modal inside a drawer is hidden by
|
|
311
|
+
* the drawer closing exactly as another drawer would be, and is left in the
|
|
312
|
+
* same wrong state.
|
|
313
|
+
*/
|
|
314
|
+
export declare const OVERLAY_SELECTOR = "dialog-context, drawer-context, modal-context";
|
|
315
|
+
export declare function closeNestedOverlays(root: HTMLElement, selector?: string): HTMLElement[];
|
|
316
|
+
export type ComponentType<T> = ComponentRegisterComponentType<T>;
|
|
317
|
+
/**
|
|
318
|
+
* `component-register` types `options.element` as a bare `ICustomElement`, but
|
|
319
|
+
* at runtime it is always also an `HTMLElement`. Every component in this repo
|
|
320
|
+
* needs the DOM half.
|
|
321
|
+
*/
|
|
322
|
+
export type CorrectComponentOptions = {
|
|
323
|
+
element: HTMLElement & ICustomElement;
|
|
324
|
+
};
|
|
325
|
+
export type CorrectComponentType<T> = (props: T, options: CorrectComponentOptions) => unknown;
|
|
326
|
+
export type Middleware<T> = (Component: ComponentType<T>) => ComponentType<T>;
|
|
327
|
+
export declare function customElement<T extends object>(tagName: string, props: T, Component: CorrectComponentType<T>, ...rest: Middleware<T>[]): import("component-register/types/utils").ComponentType<any>;
|
|
328
|
+
export declare function customShadowlessElement<T extends object>(tagName: string, props: T, Component: CorrectComponentType<T>, ...rest: Middleware<T>[]): import("component-register/types/utils").ComponentType<any>;
|
|
329
|
+
/**
|
|
330
|
+
* @deprecated The narrowing is built into `customElement` and
|
|
331
|
+
* `customShadowlessElement` now, so this is a type-level identity. It is kept
|
|
332
|
+
* only so packages still on Solid 1 keep compiling during the migration;
|
|
333
|
+
* delete the call rather than porting it.
|
|
334
|
+
*/
|
|
335
|
+
export declare const correctElementType: <T>(component: CorrectComponentType<T>) => CorrectComponentType<T>;
|
|
336
|
+
export declare function toHyphenated(str: string): string;
|
|
61
337
|
export type Path<T> = T extends readonly (infer U)[] ? `${number}` | `${number}.${Path<U>}` : T extends object ? {
|
|
62
338
|
[K in keyof T & (string | number)]: T[K] extends object | any[] ? `${K}` | `${K}.${Path<T[K]>}` : `${K}`;
|
|
63
339
|
}[keyof T & (string | number)] : never;
|
|
@@ -89,10 +365,6 @@ export type BaseOptions<RootData = any, Props = any> = {
|
|
|
89
365
|
name: string;
|
|
90
366
|
Component?: CorrectComponentType<Props>;
|
|
91
367
|
};
|
|
92
|
-
export declare const makePathDataComponent: <RootData = any, Props = any>({ useRootData, hooks, name, }: BaseOptions<RootData>) => CorrectComponentType<{
|
|
93
|
-
propertyName: Path<RootData>;
|
|
94
|
-
format?: Format;
|
|
95
|
-
} & Props>;
|
|
96
368
|
export declare const makePathDataArrayComponent: <RootData = any, Props = any>({ useRootData, hooks, name, arrayItemName, }: BaseOptions<RootData> & {
|
|
97
369
|
arrayItemName: string;
|
|
98
370
|
}) => CorrectComponentType<{
|
|
@@ -102,5 +374,10 @@ export declare const makePathDataArrayComponent: <RootData = any, Props = any>({
|
|
|
102
374
|
export declare const makePathDataArrayItemComponent: <RootData = any, Props extends {
|
|
103
375
|
itemIndex: Path<RootData>;
|
|
104
376
|
} = any>({ hooks, name, }: BaseOptions<RootData, Props>) => CorrectComponentType<Props>;
|
|
377
|
+
export declare const makePathDataComponent: <RootData = any, Props = any>({ useRootData, hooks, name, }: BaseOptions<RootData>) => CorrectComponentType<{
|
|
378
|
+
propertyName: Path<RootData>;
|
|
379
|
+
format?: Format;
|
|
380
|
+
} & Props>;
|
|
381
|
+
export declare function abortablePromise(promise: Promise<any>, signal: AbortSignal): Promise<unknown>;
|
|
105
382
|
|
|
106
383
|
export {};
|
package/dist/main/index.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { awaitAllAnimations } from "./animation/awaitAllAnimations.js";
|
|
2
|
+
import { collapseContent, expandContent, hideContent, onContentRevealed, shouldReduceMotion, showContent, supportsHiddenUntilFound } from "./animation/content.js";
|
|
3
|
+
import { controlsToPromise } from "./animation/controlsToPromise.js";
|
|
4
|
+
import { getTransitionConfig } from "./animation/getTransitionConfig.js";
|
|
5
|
+
import "./animation/index.js";
|
|
6
|
+
import { createFocusTrap } from "./focusTrap/index.js";
|
|
7
|
+
import { OVERLAY_SELECTOR, closeNestedOverlays } from "./utils/nested-overlays.js";
|
|
8
|
+
import { announce, createEscapeHandler, createFocusReturn, findOverlayPanel, srOnly } from "./utils/a11y.js";
|
|
9
|
+
import { createGuardedEffect } from "./utils/guarded-effect.js";
|
|
10
|
+
import { disableBodyScroll, enableBodyScroll } from "./utils/body-scroll-lock.js";
|
|
11
|
+
import { checkLoaded } from "./utils/checkLoaded.js";
|
|
12
|
+
import { RemoteContextError, createRemoteContext } from "./utils/createRemoteContext.js";
|
|
13
|
+
import { ensureId, findDisclosureContent, findDisclosureControl, linkDisclosureControl } from "./utils/disclosure.js";
|
|
14
|
+
import { formatValue } from "./utils/formatValue.js";
|
|
15
|
+
import { getContextFromProvider } from "./utils/getContextFromProvider.js";
|
|
16
|
+
import { getTemplateContent } from "./utils/getTemplateContent.js";
|
|
17
|
+
import { invokeOnLoaded } from "./utils/invokeOnLoaded.js";
|
|
18
|
+
import { isCustomElement } from "./utils/isCustomElement.js";
|
|
19
|
+
import { correctElementType, customElement, customShadowlessElement } from "./utils/solid-element.js";
|
|
20
|
+
import { toHyphenated } from "./utils/toHyphenated.js";
|
|
21
|
+
import "./utils/index.js";
|
|
22
|
+
import { makeFullPropertyPathContext } from "./hooks/makePathContextHooks.js";
|
|
23
|
+
import { makePathDataArrayComponent } from "./hooks/makePathDataArrayComponent.js";
|
|
24
|
+
import { makePathDataArrayItemComponent } from "./hooks/makePathDataArrayItemComponent.js";
|
|
25
|
+
import { makePathDataComponent } from "./hooks/makePathDataComponent.js";
|
|
26
|
+
import "./hooks/index.js";
|
|
27
|
+
import { abortablePromise } from "./promise/abortablePromise.js";
|
|
28
|
+
export { OVERLAY_SELECTOR, RemoteContextError, abortablePromise, announce, awaitAllAnimations, checkLoaded, closeNestedOverlays, collapseContent, controlsToPromise, correctElementType, createEscapeHandler, createFocusReturn, createFocusTrap, createGuardedEffect, createRemoteContext, customElement, customShadowlessElement, disableBodyScroll, enableBodyScroll, ensureId, expandContent, findDisclosureContent, findDisclosureControl, findOverlayPanel, formatValue, getContextFromProvider, getTemplateContent, getTransitionConfig, hideContent, invokeOnLoaded, isCustomElement, linkDisclosureControl, makeFullPropertyPathContext, makePathDataArrayComponent, makePathDataArrayItemComponent, makePathDataComponent, onContentRevealed, shouldReduceMotion, showContent, srOnly, supportsHiddenUntilFound, toHyphenated };
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
+
//#region src/promise/abortablePromise.ts
|
|
1
2
|
function abortablePromise(promise, signal) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
rej(signal.reason);
|
|
10
|
-
},
|
|
11
|
-
{ once: true }
|
|
12
|
-
);
|
|
13
|
-
promise.then(res);
|
|
14
|
-
});
|
|
3
|
+
if (signal.aborted) return new Promise((_, rej) => rej(signal.reason));
|
|
4
|
+
return new Promise((res, rej) => {
|
|
5
|
+
signal.addEventListener("abort", () => {
|
|
6
|
+
rej(signal.reason);
|
|
7
|
+
}, { once: true });
|
|
8
|
+
promise.then(res, rej);
|
|
9
|
+
});
|
|
15
10
|
}
|
|
16
|
-
|
|
11
|
+
//#endregion
|
|
17
12
|
export { abortablePromise };
|
|
18
|
-
|
|
13
|
+
|
|
19
14
|
//# sourceMappingURL=abortablePromise.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"abortablePromise.js","names":[],"sources":["../../../src/promise/abortablePromise.ts"],"sourcesContent":["export function abortablePromise(promise: Promise<any>, signal: AbortSignal) {\n\tif (signal.aborted) {\n\t\treturn new Promise((_, rej) => rej(signal.reason));\n\t}\n\n\treturn new Promise((res, rej) => {\n\t\tsignal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\trej(signal.reason);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t\t// Both settlement paths are forwarded. The previous `promise.then(res)`\n\t\t// dropped rejection, so a rejected input left the returned promise pending\n\t\t// forever unless the signal happened to abort. The `async` executor it sat\n\t\t// in compounded that: a throw inside an async executor rejects the\n\t\t// executor's own discarded promise, never the one being constructed.\n\t\tpromise.then(res, rej);\n\t});\n}\n"],"mappings":";AAAA,SAAgB,iBAAiB,SAAuB,QAAqB;CAC5E,IAAI,OAAO,SACV,OAAO,IAAI,SAAS,GAAG,QAAQ,IAAI,OAAO,MAAM,CAAC;CAGlD,OAAO,IAAI,SAAS,KAAK,QAAQ;EAChC,OAAO,iBACN,eACM;GACL,IAAI,OAAO,MAAM;EAClB,GACA,EAAE,MAAM,KAAK,CACd;EAMA,QAAQ,KAAK,KAAK,GAAG;CACtB,CAAC;AACF"}
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import { abortablePromise } from "./abortablePromise.js";
|
|
2
|
+
export { abortablePromise };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { OVERLAY_SELECTOR } from "./nested-overlays.js";
|
|
2
|
+
//#region src/utils/a11y.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shared accessibility helpers.
|
|
5
|
+
*
|
|
6
|
+
* Everything here was either duplicated across leaf packages or missing
|
|
7
|
+
* entirely while each overlay reimplemented a piece of it.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Visually hidden, still in the accessibility tree.
|
|
11
|
+
*
|
|
12
|
+
* Byte-identical to the copies that lived in `keen-slider-scrollbar`,
|
|
13
|
+
* `keen-slider-navigation-arrows` and `keen-slider-navigation-dots` — three
|
|
14
|
+
* copies of the same clip-rect incantation, none of which could be fixed
|
|
15
|
+
* without finding the other two.
|
|
16
|
+
*/
|
|
17
|
+
var srOnly = `border-width:0 !important;clip:rect(1px, 1px, 1px, 1px) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;margin-top:-1px !important;margin-bottom:-1px !important;margin-right:-1px !important;margin-left:-1px !important;overflow:hidden !important;padding-top:0 !important;padding-bottom:0 !important;padding-right:0 !important;padding-left:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important;`;
|
|
18
|
+
/**
|
|
19
|
+
* The panel belonging to this overlay — `dialog-content`, `drawer-content` or
|
|
20
|
+
* `modal-panel` — skipping the panels of overlays nested inside it.
|
|
21
|
+
*
|
|
22
|
+
* Same shape as `findDisclosureContent`, and for the same reason: a plain
|
|
23
|
+
* `querySelector` finds a nested overlay's panel first whenever the nested one
|
|
24
|
+
* is authored above its parent's own content.
|
|
25
|
+
*/
|
|
26
|
+
function findOverlayPanel(context, panelTag, selector = OVERLAY_SELECTOR) {
|
|
27
|
+
for (const panel of Array.from(context.querySelectorAll(panelTag))) if (panel.closest(selector) === context) return panel;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Remembers where focus came from when an overlay opens, and puts it back when
|
|
32
|
+
* the overlay closes.
|
|
33
|
+
*
|
|
34
|
+
* `focus-trap` already does this, but only while a trap is active — so an
|
|
35
|
+
* overlay with `should-trap-focus="false"` dropped focus on the floor, and a
|
|
36
|
+
* keyboard user was returned to the top of the document. Keeping it separate
|
|
37
|
+
* means focus return is a property of opening and closing rather than a
|
|
38
|
+
* side effect of trapping.
|
|
39
|
+
*
|
|
40
|
+
* Focus already inside the overlay is not recorded: that is where focus is
|
|
41
|
+
* going, not where it came from, and restoring to it would strand the user in
|
|
42
|
+
* a hidden subtree.
|
|
43
|
+
*/
|
|
44
|
+
function createFocusReturn(element) {
|
|
45
|
+
let previous = null;
|
|
46
|
+
return {
|
|
47
|
+
record() {
|
|
48
|
+
const active = document.activeElement;
|
|
49
|
+
if (!(active instanceof HTMLElement) || active === document.body || element.contains(active)) {
|
|
50
|
+
previous = null;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
previous = active;
|
|
54
|
+
},
|
|
55
|
+
restore() {
|
|
56
|
+
const target = previous;
|
|
57
|
+
previous = null;
|
|
58
|
+
if (!target || !target.isConnected) return false;
|
|
59
|
+
target.focus();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether this overlay is the innermost open one.
|
|
66
|
+
*
|
|
67
|
+
* Nesting is established by containment rather than a registry, matching
|
|
68
|
+
* `closeNestedOverlays` — these components have no concept of a parent.
|
|
69
|
+
*/
|
|
70
|
+
function isTopmostOpenOverlay(element, selector) {
|
|
71
|
+
if (element.getAttribute("is-open") !== "true") return false;
|
|
72
|
+
for (const nested of Array.from(element.querySelectorAll(selector))) if (nested.getAttribute("is-open") === "true") return false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Closes the topmost overlay on Escape. Returns its own teardown.
|
|
77
|
+
*
|
|
78
|
+
* This exists because Escape was previously `focus-trap`'s `escapeDeactivates`
|
|
79
|
+
* option, which has two consequences nobody wanted: Escape did nothing at all
|
|
80
|
+
* unless focus trapping was *also* on, and the two settings could not be
|
|
81
|
+
* configured independently even though they are different promises to the
|
|
82
|
+
* user.
|
|
83
|
+
*
|
|
84
|
+
* Only the innermost open overlay responds, so Escape inside a nested dialog
|
|
85
|
+
* closes that dialog rather than collapsing the whole stack.
|
|
86
|
+
*/
|
|
87
|
+
function createEscapeHandler({ element, onEscape, isEnabled, selector = OVERLAY_SELECTOR }) {
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
document.addEventListener("keydown", (event) => {
|
|
90
|
+
if (event.key !== "Escape") return;
|
|
91
|
+
if (isEnabled && !isEnabled()) return;
|
|
92
|
+
if (!isTopmostOpenOverlay(element, selector)) return;
|
|
93
|
+
event.stopPropagation();
|
|
94
|
+
onEscape();
|
|
95
|
+
}, { signal: controller.signal });
|
|
96
|
+
return () => controller.abort();
|
|
97
|
+
}
|
|
98
|
+
var liveRegion = null;
|
|
99
|
+
/** The one shared live region, created on first use. */
|
|
100
|
+
function getLiveRegion() {
|
|
101
|
+
if (liveRegion?.isConnected) return liveRegion;
|
|
102
|
+
liveRegion = document.createElement("div");
|
|
103
|
+
liveRegion.setAttribute("data-bryt-announcer", "");
|
|
104
|
+
liveRegion.setAttribute("aria-atomic", "true");
|
|
105
|
+
liveRegion.setAttribute("style", srOnly);
|
|
106
|
+
document.body.appendChild(liveRegion);
|
|
107
|
+
return liveRegion;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Announces a message to assistive tech through a single shared live region.
|
|
111
|
+
*
|
|
112
|
+
* One region for the whole page, not one per component: a live region has to
|
|
113
|
+
* be in the DOM *before* its content changes for the change to be announced,
|
|
114
|
+
* so a component that creates one and fills it in the same tick announces
|
|
115
|
+
* nothing. Creating it once, on first use, sidesteps that.
|
|
116
|
+
*
|
|
117
|
+
* The text is cleared and set on a later tick for the same reason in reverse —
|
|
118
|
+
* writing identical text is not a change, so a screen reader stays silent when
|
|
119
|
+
* the same message is announced twice in a row.
|
|
120
|
+
*/
|
|
121
|
+
function announce(message, politeness = "polite") {
|
|
122
|
+
if (!message) return;
|
|
123
|
+
const region = getLiveRegion();
|
|
124
|
+
region.setAttribute("aria-live", politeness);
|
|
125
|
+
region.textContent = "";
|
|
126
|
+
window.setTimeout(() => {
|
|
127
|
+
if (region.isConnected) region.textContent = message;
|
|
128
|
+
}, 50);
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { announce, createEscapeHandler, createFocusReturn, findOverlayPanel, srOnly };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=a11y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y.js","names":[],"sources":["../../../src/utils/a11y.ts"],"sourcesContent":["import { OVERLAY_SELECTOR } from \"./nested-overlays.js\";\n\n/**\n * Shared accessibility helpers.\n *\n * Everything here was either duplicated across leaf packages or missing\n * entirely while each overlay reimplemented a piece of it.\n */\n\n/**\n * Visually hidden, still in the accessibility tree.\n *\n * Byte-identical to the copies that lived in `keen-slider-scrollbar`,\n * `keen-slider-navigation-arrows` and `keen-slider-navigation-dots` — three\n * copies of the same clip-rect incantation, none of which could be fixed\n * without finding the other two.\n */\nexport const srOnly = `border-width:0 !important;clip:rect(1px, 1px, 1px, 1px) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;margin-top:-1px !important;margin-bottom:-1px !important;margin-right:-1px !important;margin-left:-1px !important;overflow:hidden !important;padding-top:0 !important;padding-bottom:0 !important;padding-right:0 !important;padding-left:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important;`;\n\n/**\n * The panel belonging to this overlay — `dialog-content`, `drawer-content` or\n * `modal-panel` — skipping the panels of overlays nested inside it.\n *\n * Same shape as `findDisclosureContent`, and for the same reason: a plain\n * `querySelector` finds a nested overlay's panel first whenever the nested one\n * is authored above its parent's own content.\n */\nexport function findOverlayPanel(\n\tcontext: HTMLElement,\n\tpanelTag: string,\n\tselector: string = OVERLAY_SELECTOR,\n): HTMLElement | null {\n\tfor (const panel of Array.from(\n\t\tcontext.querySelectorAll<HTMLElement>(panelTag),\n\t)) {\n\t\tif (panel.closest(selector) === context) return panel;\n\t}\n\treturn null;\n}\n\nexport type FocusReturn = {\n\t/** Remembers what holds focus now, to restore later. */\n\trecord: () => void;\n\t/** Restores it. Returns whether focus was actually moved. */\n\trestore: () => boolean;\n};\n\n/**\n * Remembers where focus came from when an overlay opens, and puts it back when\n * the overlay closes.\n *\n * `focus-trap` already does this, but only while a trap is active — so an\n * overlay with `should-trap-focus=\"false\"` dropped focus on the floor, and a\n * keyboard user was returned to the top of the document. Keeping it separate\n * means focus return is a property of opening and closing rather than a\n * side effect of trapping.\n *\n * Focus already inside the overlay is not recorded: that is where focus is\n * going, not where it came from, and restoring to it would strand the user in\n * a hidden subtree.\n */\nexport function createFocusReturn(element: HTMLElement): FocusReturn {\n\tlet previous: HTMLElement | null = null;\n\n\treturn {\n\t\trecord() {\n\t\t\tconst active = document.activeElement;\n\t\t\tif (\n\t\t\t\t!(active instanceof HTMLElement) ||\n\t\t\t\tactive === document.body ||\n\t\t\t\telement.contains(active)\n\t\t\t) {\n\t\t\t\tprevious = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevious = active;\n\t\t},\n\n\t\trestore() {\n\t\t\tconst target = previous;\n\t\t\tprevious = null;\n\t\t\t// A trigger rendered by a section that re-rendered while the overlay\n\t\t\t// was open is gone; focusing it throws nothing but moves nothing\n\t\t\t// either, and the browser would leave focus on <body>.\n\t\t\tif (!target || !target.isConnected) return false;\n\t\t\ttarget.focus();\n\t\t\treturn true;\n\t\t},\n\t};\n}\n\nexport type EscapeHandlerOptions = {\n\t/** The overlay root — the element carrying `is-open`. */\n\telement: HTMLElement;\n\t/** Called when Escape should close this overlay. */\n\tonEscape: () => void;\n\t/** Checked at key time, so toggling the attribute takes effect at once. */\n\tisEnabled?: () => boolean;\n\t/** Overridable for tests; defaults to the three overlay context tags. */\n\tselector?: string;\n};\n\n/**\n * Whether this overlay is the innermost open one.\n *\n * Nesting is established by containment rather than a registry, matching\n * `closeNestedOverlays` — these components have no concept of a parent.\n */\nfunction isTopmostOpenOverlay(element: HTMLElement, selector: string) {\n\tif (element.getAttribute(\"is-open\") !== \"true\") return false;\n\n\tfor (const nested of Array.from(\n\t\telement.querySelectorAll<HTMLElement>(selector),\n\t)) {\n\t\tif (nested.getAttribute(\"is-open\") === \"true\") return false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Closes the topmost overlay on Escape. Returns its own teardown.\n *\n * This exists because Escape was previously `focus-trap`'s `escapeDeactivates`\n * option, which has two consequences nobody wanted: Escape did nothing at all\n * unless focus trapping was *also* on, and the two settings could not be\n * configured independently even though they are different promises to the\n * user.\n *\n * Only the innermost open overlay responds, so Escape inside a nested dialog\n * closes that dialog rather than collapsing the whole stack.\n */\nexport function createEscapeHandler({\n\telement,\n\tonEscape,\n\tisEnabled,\n\tselector = OVERLAY_SELECTOR,\n}: EscapeHandlerOptions): () => void {\n\tconst controller = new AbortController();\n\n\tdocument.addEventListener(\n\t\t\"keydown\",\n\t\t(event: KeyboardEvent) => {\n\t\t\tif (event.key !== \"Escape\") return;\n\t\t\tif (isEnabled && !isEnabled()) return;\n\t\t\tif (!isTopmostOpenOverlay(element, selector)) return;\n\n\t\t\t// Stops an ancestor overlay's handler seeing the same key, in case one\n\t\t\t// is bound on a container rather than the document.\n\t\t\tevent.stopPropagation();\n\t\t\tonEscape();\n\t\t},\n\t\t{ signal: controller.signal },\n\t);\n\n\treturn () => controller.abort();\n}\n\nexport type Politeness = \"polite\" | \"assertive\";\n\nlet liveRegion: HTMLElement | null = null;\n\n/** The one shared live region, created on first use. */\nfunction getLiveRegion(): HTMLElement {\n\tif (liveRegion?.isConnected) return liveRegion;\n\n\tliveRegion = document.createElement(\"div\");\n\t// A stable hook, so a test can find the region without guessing at which\n\t// `[aria-live]` on the page belongs to it.\n\tliveRegion.setAttribute(\"data-bryt-announcer\", \"\");\n\tliveRegion.setAttribute(\"aria-atomic\", \"true\");\n\tliveRegion.setAttribute(\"style\", srOnly);\n\tdocument.body.appendChild(liveRegion);\n\n\treturn liveRegion;\n}\n\n/**\n * Announces a message to assistive tech through a single shared live region.\n *\n * One region for the whole page, not one per component: a live region has to\n * be in the DOM *before* its content changes for the change to be announced,\n * so a component that creates one and fills it in the same tick announces\n * nothing. Creating it once, on first use, sidesteps that.\n *\n * The text is cleared and set on a later tick for the same reason in reverse —\n * writing identical text is not a change, so a screen reader stays silent when\n * the same message is announced twice in a row.\n */\nexport function announce(message: string, politeness: Politeness = \"polite\") {\n\tif (!message) return;\n\n\tconst region = getLiveRegion();\n\tregion.setAttribute(\"aria-live\", politeness);\n\tregion.textContent = \"\";\n\n\twindow.setTimeout(() => {\n\t\t// Another announcement may have replaced the region in the meantime.\n\t\tif (region.isConnected) region.textContent = message;\n\t}, 50);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,IAAa,SAAS;;;;;;;;;AAUtB,SAAgB,iBACf,SACA,UACA,WAAmB,kBACE;CACrB,KAAK,MAAM,SAAS,MAAM,KACzB,QAAQ,iBAA8B,QAAQ,CAC/C,GACC,IAAI,MAAM,QAAQ,QAAQ,MAAM,SAAS,OAAO;CAEjD,OAAO;AACR;;;;;;;;;;;;;;;AAuBA,SAAgB,kBAAkB,SAAmC;CACpE,IAAI,WAA+B;CAEnC,OAAO;EACN,SAAS;GACR,MAAM,SAAS,SAAS;GACxB,IACC,EAAE,kBAAkB,gBACpB,WAAW,SAAS,QACpB,QAAQ,SAAS,MAAM,GACtB;IACD,WAAW;IACX;GACD;GACA,WAAW;EACZ;EAEA,UAAU;GACT,MAAM,SAAS;GACf,WAAW;GAIX,IAAI,CAAC,UAAU,CAAC,OAAO,aAAa,OAAO;GAC3C,OAAO,MAAM;GACb,OAAO;EACR;CACD;AACD;;;;;;;AAmBA,SAAS,qBAAqB,SAAsB,UAAkB;CACrE,IAAI,QAAQ,aAAa,SAAS,MAAM,QAAQ,OAAO;CAEvD,KAAK,MAAM,UAAU,MAAM,KAC1B,QAAQ,iBAA8B,QAAQ,CAC/C,GACC,IAAI,OAAO,aAAa,SAAS,MAAM,QAAQ,OAAO;CAGvD,OAAO;AACR;;;;;;;;;;;;;AAcA,SAAgB,oBAAoB,EACnC,SACA,UACA,WACA,WAAW,oBACyB;CACpC,MAAM,aAAa,IAAI,gBAAgB;CAEvC,SAAS,iBACR,YACC,UAAyB;EACzB,IAAI,MAAM,QAAQ,UAAU;EAC5B,IAAI,aAAa,CAAC,UAAU,GAAG;EAC/B,IAAI,CAAC,qBAAqB,SAAS,QAAQ,GAAG;EAI9C,MAAM,gBAAgB;EACtB,SAAS;CACV,GACA,EAAE,QAAQ,WAAW,OAAO,CAC7B;CAEA,aAAa,WAAW,MAAM;AAC/B;AAIA,IAAI,aAAiC;;AAGrC,SAAS,gBAA6B;CACrC,IAAI,YAAY,aAAa,OAAO;CAEpC,aAAa,SAAS,cAAc,KAAK;CAGzC,WAAW,aAAa,uBAAuB,EAAE;CACjD,WAAW,aAAa,eAAe,MAAM;CAC7C,WAAW,aAAa,SAAS,MAAM;CACvC,SAAS,KAAK,YAAY,UAAU;CAEpC,OAAO;AACR;;;;;;;;;;;;;AAcA,SAAgB,SAAS,SAAiB,aAAyB,UAAU;CAC5E,IAAI,CAAC,SAAS;CAEd,MAAM,SAAS,cAAc;CAC7B,OAAO,aAAa,aAAa,UAAU;CAC3C,OAAO,cAAc;CAErB,OAAO,iBAAiB;EAEvB,IAAI,OAAO,aAAa,OAAO,cAAc;CAC9C,GAAG,EAAE;AACN"}
|