polymorph-ui-components 0.7.0 → 0.9.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 +4 -3
- package/dist/Button/Button.svelte +5 -5
- package/dist/Carousel/Carousel.svelte +266 -145
- package/dist/Carousel/properties.d.ts +14 -2
- package/dist/Combobox/Combobox.svelte.d.ts +1 -1
- package/dist/CommandMenu/CommandMenu.svelte +3 -3
- package/dist/Gallery/Gallery.svelte +10 -10
- package/dist/Gallery/Gallery.svelte.d.ts +1 -1
- package/dist/Input/Input.svelte +12 -3
- package/dist/Input/properties.d.ts +2 -0
- package/dist/InputButton/InputButton.svelte +13 -5
- package/dist/Loader/Loader.svelte +20 -4
- package/dist/Loader/properties.d.ts +1 -0
- package/dist/Modal/Modal.svelte +4 -5
- package/dist/NumberStepper/NumberStepper.svelte +154 -0
- package/dist/NumberStepper/NumberStepper.svelte.d.ts +4 -0
- package/dist/NumberStepper/properties.d.ts +22 -0
- package/dist/NumberStepper/properties.js +1 -0
- package/dist/Pill/Pill.svelte +2 -1
- package/dist/Pill/properties.d.ts +1 -0
- package/dist/Progress/Progress.svelte +146 -8
- package/dist/Progress/properties.d.ts +9 -0
- package/dist/Select/Select.svelte +11 -1
- package/dist/Select/properties.d.ts +1 -0
- package/dist/Sheet/Sheet.svelte +96 -40
- package/dist/Sheet/properties.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/utils.d.ts +20 -0
- package/dist/utils.js +107 -0
- package/dist-wc/index.js +4085 -3323
- package/package.json +1 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type ProgressMilestone = {
|
|
3
|
+
value: number;
|
|
4
|
+
label?: string;
|
|
5
|
+
};
|
|
1
6
|
export type ProgressProperties = MandatoryProgressProperties & OptionalProgressProperties;
|
|
2
7
|
export type MandatoryProgressProperties = {
|
|
3
8
|
value: number;
|
|
@@ -5,6 +10,10 @@ export type MandatoryProgressProperties = {
|
|
|
5
10
|
export type OptionalProgressProperties = {
|
|
6
11
|
max?: number;
|
|
7
12
|
showLabel?: boolean;
|
|
13
|
+
ariaLabel?: string;
|
|
14
|
+
valueText?: string;
|
|
15
|
+
milestones?: ProgressMilestone[];
|
|
16
|
+
milestone?: Snippet<[ProgressMilestone, boolean]>;
|
|
8
17
|
testId?: string;
|
|
9
18
|
classes?: string;
|
|
10
19
|
};
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
searchable = false,
|
|
12
12
|
placeholder = '',
|
|
13
13
|
disabled = false,
|
|
14
|
+
ariaLabel,
|
|
14
15
|
testId,
|
|
15
16
|
onchange,
|
|
16
17
|
classes
|
|
@@ -240,6 +241,7 @@
|
|
|
240
241
|
onclick={handleTriggerClick}
|
|
241
242
|
onkeydown={handleKeydown}
|
|
242
243
|
role="combobox"
|
|
244
|
+
aria-label={ariaLabel}
|
|
243
245
|
aria-expanded={isOpen}
|
|
244
246
|
aria-haspopup="listbox"
|
|
245
247
|
aria-controls={listboxId}
|
|
@@ -265,6 +267,7 @@
|
|
|
265
267
|
onfocus={handleSearchFocus}
|
|
266
268
|
bind:this={searchInputEl}
|
|
267
269
|
placeholder={value.length === 0 ? placeholder : ''}
|
|
270
|
+
aria-label={ariaLabel}
|
|
268
271
|
{disabled}
|
|
269
272
|
autocomplete="off"
|
|
270
273
|
tabindex={disabled ? -1 : 0}
|
|
@@ -281,6 +284,7 @@
|
|
|
281
284
|
onfocus={handleSearchFocus}
|
|
282
285
|
bind:this={searchInputEl}
|
|
283
286
|
placeholder={searchPlaceholder}
|
|
287
|
+
aria-label={ariaLabel}
|
|
284
288
|
{disabled}
|
|
285
289
|
autocomplete="off"
|
|
286
290
|
tabindex={disabled ? -1 : 0}
|
|
@@ -295,7 +299,13 @@
|
|
|
295
299
|
</div>
|
|
296
300
|
|
|
297
301
|
{#if isOpen && !disabled}
|
|
298
|
-
<div
|
|
302
|
+
<div
|
|
303
|
+
class="select-dropdown"
|
|
304
|
+
role="listbox"
|
|
305
|
+
id={listboxId}
|
|
306
|
+
aria-label={ariaLabel}
|
|
307
|
+
aria-multiselectable={multiple}
|
|
308
|
+
>
|
|
299
309
|
{#if filteredItems.length === 0}
|
|
300
310
|
<div class="select-empty">No results</div>
|
|
301
311
|
{:else}
|
package/dist/Sheet/Sheet.svelte
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { SheetProperties } from './properties';
|
|
3
3
|
import { fly, fade } from 'svelte/transition';
|
|
4
|
+
import { prefersReducedMotion } from 'svelte/motion';
|
|
4
5
|
import { tick } from 'svelte';
|
|
5
6
|
import Button from '../Button/Button.svelte';
|
|
7
|
+
import closeSvg from '../assets/close.svg?raw';
|
|
8
|
+
import { deepActiveElement, focusableElements, lockDocumentScroll } from '../utils';
|
|
6
9
|
|
|
7
10
|
let {
|
|
8
11
|
open = $bindable(false),
|
|
@@ -10,31 +13,57 @@
|
|
|
10
13
|
title,
|
|
11
14
|
showOverlay = true,
|
|
12
15
|
showCloseButton = true,
|
|
16
|
+
closeLabel = 'Close',
|
|
13
17
|
testId,
|
|
14
18
|
content,
|
|
15
19
|
footer,
|
|
20
|
+
closeIcon,
|
|
16
21
|
onclose,
|
|
17
22
|
classes
|
|
18
23
|
}: SheetProperties = $props();
|
|
19
24
|
|
|
20
25
|
let overlayDiv: HTMLDivElement | null = $state(null);
|
|
21
26
|
let sheetPanel: HTMLDivElement | null = $state(null);
|
|
27
|
+
let openerElement: HTMLElement | null = null;
|
|
28
|
+
|
|
29
|
+
let fadeDuration = $derived(prefersReducedMotion.current ? 0 : 200);
|
|
22
30
|
|
|
23
31
|
let flyParams = $derived.by(() => {
|
|
32
|
+
const duration = prefersReducedMotion.current ? 0 : 300;
|
|
24
33
|
switch (side) {
|
|
25
34
|
case 'left':
|
|
26
|
-
return { x: -
|
|
35
|
+
return { x: '-100%', duration };
|
|
27
36
|
case 'right':
|
|
28
|
-
return { x:
|
|
37
|
+
return { x: '100%', duration };
|
|
29
38
|
case 'top':
|
|
30
|
-
return {
|
|
39
|
+
return { y: '-100%', duration };
|
|
31
40
|
case 'bottom':
|
|
32
|
-
return {
|
|
41
|
+
return { y: '100%', duration };
|
|
33
42
|
}
|
|
34
43
|
});
|
|
35
44
|
|
|
45
|
+
function enter() {
|
|
46
|
+
if (openerElement === null) {
|
|
47
|
+
const active = deepActiveElement();
|
|
48
|
+
openerElement = active instanceof HTMLElement ? active : null;
|
|
49
|
+
}
|
|
50
|
+
tick().then(() => {
|
|
51
|
+
if (sheetPanel !== null) {
|
|
52
|
+
sheetPanel.focus();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function restoreFocus() {
|
|
58
|
+
if (openerElement !== null && openerElement.isConnected) {
|
|
59
|
+
openerElement.focus();
|
|
60
|
+
}
|
|
61
|
+
openerElement = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
function close() {
|
|
37
65
|
open = false;
|
|
66
|
+
restoreFocus();
|
|
38
67
|
onclose?.();
|
|
39
68
|
}
|
|
40
69
|
|
|
@@ -44,65 +73,77 @@
|
|
|
44
73
|
}
|
|
45
74
|
}
|
|
46
75
|
|
|
47
|
-
function
|
|
48
|
-
if (
|
|
49
|
-
close();
|
|
76
|
+
function trapTab(event: KeyboardEvent) {
|
|
77
|
+
if (sheetPanel === null) {
|
|
50
78
|
return;
|
|
51
79
|
}
|
|
80
|
+
const focusable = focusableElements(sheetPanel);
|
|
81
|
+
const first = focusable.at(0);
|
|
82
|
+
const last = focusable.at(-1);
|
|
52
83
|
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const last = focusable.item(focusable.length - 1);
|
|
59
|
-
|
|
60
|
-
if (first === null || last === null) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
84
|
+
if (!(first instanceof HTMLElement) || !(last instanceof HTMLElement)) {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
sheetPanel.focus();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
63
89
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
90
|
+
const active = deepActiveElement();
|
|
91
|
+
const inside = active === sheetPanel || focusable.some((element) => element === active);
|
|
92
|
+
if (!inside) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
(event.shiftKey ? last : first).focus();
|
|
95
|
+
} else if (event.shiftKey && (active === first || active === sheetPanel)) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
last.focus();
|
|
98
|
+
} else if (!event.shiftKey && active === last) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
first.focus();
|
|
69
101
|
}
|
|
70
102
|
}
|
|
71
103
|
|
|
72
|
-
function
|
|
73
|
-
|
|
104
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
105
|
+
if (event.key === 'Escape') {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
close();
|
|
108
|
+
} else if (event.key === 'Tab') {
|
|
109
|
+
trapTab(event);
|
|
110
|
+
}
|
|
74
111
|
}
|
|
75
112
|
|
|
76
|
-
function
|
|
77
|
-
|
|
113
|
+
function handleWindowKeyDown(event: KeyboardEvent) {
|
|
114
|
+
if (!open || event.defaultPrevented) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (overlayDiv !== null && event.composedPath().includes(overlayDiv)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
handleKeyDown(event);
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (sheetPanel !== null) {
|
|
84
|
-
sheetPanel.focus();
|
|
85
|
-
}
|
|
86
|
-
});
|
|
123
|
+
function sheetAction(_node: HTMLElement) {
|
|
124
|
+
const unlockScroll = lockDocumentScroll();
|
|
125
|
+
enter();
|
|
87
126
|
return {
|
|
88
127
|
destroy() {
|
|
89
128
|
unlockScroll();
|
|
129
|
+
restoreFocus();
|
|
90
130
|
}
|
|
91
131
|
};
|
|
92
132
|
}
|
|
93
133
|
</script>
|
|
94
134
|
|
|
135
|
+
<svelte:window onkeydown={handleWindowKeyDown} />
|
|
136
|
+
|
|
95
137
|
{#if open}
|
|
96
138
|
<div
|
|
97
139
|
bind:this={overlayDiv}
|
|
98
|
-
use:
|
|
140
|
+
use:sheetAction
|
|
99
141
|
class="sheet-overlay {showOverlay ? 'overlay-active' : 'overlay-inactive'} {classes ?? ''}"
|
|
100
142
|
onclick={handleOverlayClick}
|
|
101
143
|
onkeydown={handleKeyDown}
|
|
102
|
-
role="
|
|
103
|
-
tabindex="-1"
|
|
144
|
+
role="presentation"
|
|
104
145
|
data-pw={testId}
|
|
105
|
-
transition:fade={{ duration:
|
|
146
|
+
transition:fade={{ duration: fadeDuration }}
|
|
106
147
|
>
|
|
107
148
|
<div
|
|
108
149
|
bind:this={sheetPanel}
|
|
@@ -112,20 +153,26 @@
|
|
|
112
153
|
aria-label={title ?? 'Sheet'}
|
|
113
154
|
tabindex="-1"
|
|
114
155
|
transition:fly|global={flyParams}
|
|
156
|
+
onintrostart={enter}
|
|
115
157
|
>
|
|
116
158
|
{#if typeof title === 'string' || showCloseButton}
|
|
117
159
|
<div class="sheet-header">
|
|
118
160
|
{#if typeof title === 'string'}
|
|
119
|
-
<
|
|
161
|
+
<h2 class="sheet-title">{title}</h2>
|
|
120
162
|
{/if}
|
|
121
163
|
{#if showCloseButton}
|
|
122
164
|
<div class="sheet-close-button">
|
|
123
165
|
<Button
|
|
124
166
|
onclick={close}
|
|
125
|
-
ariaLabel=
|
|
167
|
+
ariaLabel={closeLabel}
|
|
126
168
|
{...typeof testId === 'string' ? { testId: `${testId}-close` } : {}}
|
|
127
169
|
>
|
|
128
|
-
|
|
170
|
+
{#if typeof closeIcon === 'function'}
|
|
171
|
+
{@render closeIcon()}
|
|
172
|
+
{:else}
|
|
173
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
174
|
+
{@html closeSvg}
|
|
175
|
+
{/if}
|
|
129
176
|
</Button>
|
|
130
177
|
</div>
|
|
131
178
|
{/if}
|
|
@@ -213,6 +260,8 @@
|
|
|
213
260
|
.sheet-header {
|
|
214
261
|
display: flex;
|
|
215
262
|
align-items: center;
|
|
263
|
+
justify-content: flex-end;
|
|
264
|
+
gap: var(--sheet-header-gap, 8px);
|
|
216
265
|
padding: var(--sheet-header-padding, 16px 20px);
|
|
217
266
|
background-color: var(--sheet-header-background, inherit);
|
|
218
267
|
border-bottom: var(--sheet-header-border-bottom, 1px solid #e4e4e7);
|
|
@@ -221,6 +270,7 @@
|
|
|
221
270
|
|
|
222
271
|
.sheet-title {
|
|
223
272
|
flex: 1;
|
|
273
|
+
margin: var(--sheet-title-margin, 0);
|
|
224
274
|
font-size: var(--sheet-title-font-size, 18px);
|
|
225
275
|
font-weight: var(--sheet-title-font-weight, 600);
|
|
226
276
|
font-family: var(--sheet-title-font-family, inherit);
|
|
@@ -244,9 +294,15 @@
|
|
|
244
294
|
justify-content: center;
|
|
245
295
|
}
|
|
246
296
|
|
|
297
|
+
.sheet-close-button :global(svg) {
|
|
298
|
+
width: var(--sheet-close-icon-size, var(--sheet-close-button-font-size, 16px));
|
|
299
|
+
height: var(--sheet-close-icon-size, var(--sheet-close-button-font-size, 16px));
|
|
300
|
+
}
|
|
301
|
+
|
|
247
302
|
.sheet-content {
|
|
248
303
|
flex: 1;
|
|
249
304
|
overflow-y: var(--sheet-content-overflow-y, auto);
|
|
305
|
+
overscroll-behavior: var(--sheet-content-overscroll-behavior, contain);
|
|
250
306
|
padding: var(--sheet-content-padding, 20px);
|
|
251
307
|
scrollbar-width: var(--sheet-scrollbar-width, none);
|
|
252
308
|
}
|
|
@@ -10,8 +10,10 @@ export type OptionalSheetProperties = {
|
|
|
10
10
|
title?: string;
|
|
11
11
|
showOverlay?: boolean;
|
|
12
12
|
showCloseButton?: boolean;
|
|
13
|
+
closeLabel?: string;
|
|
13
14
|
testId?: string;
|
|
14
15
|
footer?: Snippet;
|
|
16
|
+
closeIcon?: Snippet;
|
|
15
17
|
classes?: string;
|
|
16
18
|
};
|
|
17
19
|
export type SheetEventProperties = {
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export { default as Resizable } from './Resizable/Resizable.svelte';
|
|
|
65
65
|
export { default as Draggable } from './Draggable/Draggable.svelte';
|
|
66
66
|
export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
|
|
67
67
|
export { default as Gallery } from './Gallery/Gallery.svelte';
|
|
68
|
+
export { default as NumberStepper } from './NumberStepper/NumberStepper.svelte';
|
|
68
69
|
export { ChatController } from './Chat/controller.svelte';
|
|
69
70
|
export { partyOf } from './Chat/roles';
|
|
70
71
|
export type * from './Button/properties';
|
|
@@ -129,4 +130,5 @@ export type * from './Resizable/properties';
|
|
|
129
130
|
export type * from './Draggable/properties';
|
|
130
131
|
export type * from './ChatBubble/properties';
|
|
131
132
|
export type * from './Gallery/properties';
|
|
133
|
+
export type * from './NumberStepper/properties';
|
|
132
134
|
export { validateInput } from './utils';
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,7 @@ export { default as Resizable } from './Resizable/Resizable.svelte';
|
|
|
65
65
|
export { default as Draggable } from './Draggable/Draggable.svelte';
|
|
66
66
|
export { default as ChatBubble } from './ChatBubble/ChatBubble.svelte';
|
|
67
67
|
export { default as Gallery } from './Gallery/Gallery.svelte';
|
|
68
|
+
export { default as NumberStepper } from './NumberStepper/NumberStepper.svelte';
|
|
68
69
|
export { ChatController } from './Chat/controller.svelte';
|
|
69
70
|
export { partyOf } from './Chat/roles';
|
|
70
71
|
export { validateInput } from './utils';
|
package/dist/utils.d.ts
CHANGED
|
@@ -26,3 +26,23 @@ export declare function hslToHsv(hDeg: number, sPct: number, lPct: number): Hsv;
|
|
|
26
26
|
export declare function isValidHex(hex: string): boolean;
|
|
27
27
|
export declare function clampInt(v: string, min: number, max: number): number;
|
|
28
28
|
export declare function createDebouncer(delay: number): <T extends unknown[]>(callback: (...args: T) => void, ...args: T) => void;
|
|
29
|
+
/**
|
|
30
|
+
* @description The focused element in the same document or shadow root as `node`.
|
|
31
|
+
* `document.activeElement` stops at the shadow host, so focus checks inside web components need this.
|
|
32
|
+
*/
|
|
33
|
+
export declare function activeElementOf(node: Node): Element | null;
|
|
34
|
+
/**
|
|
35
|
+
* @description The focused element, following open shadow roots down to the innermost one.
|
|
36
|
+
*/
|
|
37
|
+
export declare function deepActiveElement(): Element | null;
|
|
38
|
+
/**
|
|
39
|
+
* @description Focusable elements inside `container`, in document order across the composed tree:
|
|
40
|
+
* content assigned to slots and open shadow roots are included, inert, hidden and undisplayed elements are not.
|
|
41
|
+
*/
|
|
42
|
+
export declare function focusableElements(container: Element): HTMLElement[];
|
|
43
|
+
/**
|
|
44
|
+
* @description Stops the page behind an overlay from scrolling and returns the release function.
|
|
45
|
+
* Locks are counted, so closing a nested overlay keeps the page locked until the last one closes,
|
|
46
|
+
* and releasing restores whatever inline overflow the page had before.
|
|
47
|
+
*/
|
|
48
|
+
export declare function lockDocumentScroll(): () => void;
|
package/dist/utils.js
CHANGED
|
@@ -292,3 +292,110 @@ export function createDebouncer(delay) {
|
|
|
292
292
|
}
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
|
+
// ── Focus & scrolling ───────────────────────────────────────────
|
|
296
|
+
/**
|
|
297
|
+
* @description The focused element in the same document or shadow root as `node`.
|
|
298
|
+
* `document.activeElement` stops at the shadow host, so focus checks inside web components need this.
|
|
299
|
+
*/
|
|
300
|
+
export function activeElementOf(node) {
|
|
301
|
+
const root = node.getRootNode();
|
|
302
|
+
if (root instanceof Document || root instanceof ShadowRoot) {
|
|
303
|
+
return root.activeElement;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* @description The focused element, following open shadow roots down to the innermost one.
|
|
309
|
+
*/
|
|
310
|
+
export function deepActiveElement() {
|
|
311
|
+
let active = document.activeElement;
|
|
312
|
+
while (active !== null &&
|
|
313
|
+
active.shadowRoot !== null &&
|
|
314
|
+
active.shadowRoot.activeElement !== null) {
|
|
315
|
+
active = active.shadowRoot.activeElement;
|
|
316
|
+
}
|
|
317
|
+
return active;
|
|
318
|
+
}
|
|
319
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
320
|
+
function isRendered(element) {
|
|
321
|
+
return typeof element.checkVisibility !== 'function' || element.checkVisibility();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* @description Focusable elements inside `container`, in document order across the composed tree:
|
|
325
|
+
* content assigned to slots and open shadow roots are included, inert, hidden and undisplayed elements are not.
|
|
326
|
+
*/
|
|
327
|
+
export function focusableElements(container) {
|
|
328
|
+
const found = [];
|
|
329
|
+
const visit = (element) => {
|
|
330
|
+
if (element.hasAttribute('inert') || element.hasAttribute('hidden')) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (element instanceof HTMLSlotElement) {
|
|
334
|
+
const assigned = element.assignedElements({ flatten: true });
|
|
335
|
+
(assigned.length > 0 ? assigned : Array.from(element.children)).forEach(visit);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (element instanceof HTMLElement &&
|
|
339
|
+
element.matches(FOCUSABLE_SELECTOR) &&
|
|
340
|
+
isRendered(element)) {
|
|
341
|
+
found.push(element);
|
|
342
|
+
}
|
|
343
|
+
Array.from((element.shadowRoot ?? element).children).forEach(visit);
|
|
344
|
+
};
|
|
345
|
+
Array.from(container.children).forEach(visit);
|
|
346
|
+
return found;
|
|
347
|
+
}
|
|
348
|
+
let scrollLocks = 0;
|
|
349
|
+
let releaseDocumentScroll = null;
|
|
350
|
+
function lockOverflow(element) {
|
|
351
|
+
const { style } = element;
|
|
352
|
+
const saved = ['overflow-x', 'overflow-y'].map((property) => ({
|
|
353
|
+
property,
|
|
354
|
+
value: style.getPropertyValue(property),
|
|
355
|
+
priority: style.getPropertyPriority(property)
|
|
356
|
+
}));
|
|
357
|
+
saved.forEach(({ property }) => style.setProperty(property, 'hidden', 'important'));
|
|
358
|
+
return () => {
|
|
359
|
+
saved.forEach(({ property, value, priority }) => {
|
|
360
|
+
if (value === '') {
|
|
361
|
+
style.removeProperty(property);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
style.setProperty(property, value, priority);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* @description The element whose overflow scrolls the page: `<body>` unless the page gives `<html>` an overflow.
|
|
371
|
+
* Hiding overflow on the other one would turn it into a scroll container and unstick sticky headers.
|
|
372
|
+
*/
|
|
373
|
+
function viewportOverflowElement() {
|
|
374
|
+
const root = document.documentElement;
|
|
375
|
+
const { overflowX, overflowY } = getComputedStyle(root);
|
|
376
|
+
const rootIsVisible = [overflowX, overflowY].every((value) => value === 'visible' || value === '');
|
|
377
|
+
return rootIsVisible ? document.body : root;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* @description Stops the page behind an overlay from scrolling and returns the release function.
|
|
381
|
+
* Locks are counted, so closing a nested overlay keeps the page locked until the last one closes,
|
|
382
|
+
* and releasing restores whatever inline overflow the page had before.
|
|
383
|
+
*/
|
|
384
|
+
export function lockDocumentScroll() {
|
|
385
|
+
if (scrollLocks === 0) {
|
|
386
|
+
releaseDocumentScroll = lockOverflow(viewportOverflowElement());
|
|
387
|
+
}
|
|
388
|
+
scrollLocks += 1;
|
|
389
|
+
let released = false;
|
|
390
|
+
return () => {
|
|
391
|
+
if (released) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
released = true;
|
|
395
|
+
scrollLocks -= 1;
|
|
396
|
+
if (scrollLocks === 0) {
|
|
397
|
+
releaseDocumentScroll?.();
|
|
398
|
+
releaseDocumentScroll = null;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|