@marianmeres/stuic 2.1.8 → 2.1.9
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/components/Backdrop/Backdrop.svelte +7 -57
- package/dist/components/ModalDialog/ModalDialog.svelte +11 -58
- package/dist/utils/body-scroll-locker.d.ts +9 -0
- package/dist/utils/body-scroll-locker.js +76 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,35 +1,6 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
interface BodyStyles {
|
|
3
|
-
position: string | null;
|
|
4
|
-
top: string | null;
|
|
5
|
-
width: string | null;
|
|
6
|
-
overflow: string | null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function get_body_style(): BodyStyles {
|
|
10
|
-
// const style = window.getComputedStyle(document.body);
|
|
11
|
-
const style = document.body.style; // we want only explicitly defined, not computed
|
|
12
|
-
return {
|
|
13
|
-
position: style.position || null,
|
|
14
|
-
top: style.top || null,
|
|
15
|
-
width: style.width || null,
|
|
16
|
-
overflow: style.overflow || null,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function restore_body_styles(original: BodyStyles) {
|
|
21
|
-
(["position", "top", "width", "overflow"] as (keyof BodyStyles)[]).forEach((k) => {
|
|
22
|
-
if (original[k] !== null) {
|
|
23
|
-
document.body.style[k] = original[k];
|
|
24
|
-
} else {
|
|
25
|
-
document.body.style.removeProperty(k);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
1
|
<script lang="ts">
|
|
32
2
|
import {
|
|
3
|
+
BodyScroll,
|
|
33
4
|
focusTrap as focusTrapAction,
|
|
34
5
|
twMerge,
|
|
35
6
|
waitForNextRepaint,
|
|
@@ -37,7 +8,7 @@
|
|
|
37
8
|
} from "../../index.js";
|
|
38
9
|
import { createClog } from "@marianmeres/clog";
|
|
39
10
|
import { PressedKeys, watch } from "runed";
|
|
40
|
-
import { onDestroy, tick, type Snippet } from "svelte";
|
|
11
|
+
import { onDestroy, onMount, tick, type Snippet } from "svelte";
|
|
41
12
|
import { fade } from "svelte/transition";
|
|
42
13
|
|
|
43
14
|
const clog = createClog("Backdrop").debug;
|
|
@@ -120,36 +91,15 @@
|
|
|
120
91
|
}
|
|
121
92
|
);
|
|
122
93
|
|
|
123
|
-
// lock body scroll when open and restore back
|
|
124
|
-
let _original: BodyStyles = {
|
|
125
|
-
position: null,
|
|
126
|
-
top: null,
|
|
127
|
-
width: null,
|
|
128
|
-
overflow: null,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
function _restore() {
|
|
132
|
-
const scrollY = document.body.style.top;
|
|
133
|
-
restore_body_styles(_original);
|
|
134
|
-
// Restore scroll position
|
|
135
|
-
window.scrollTo(0, parseInt(scrollY || "0") * -1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
94
|
$effect(() => {
|
|
139
95
|
if (noScrollLock) return;
|
|
140
|
-
|
|
141
|
-
_original = get_body_style();
|
|
142
|
-
const scrollY = window.scrollY;
|
|
143
|
-
document.body.style.position = "fixed";
|
|
144
|
-
document.body.style.top = `-${scrollY}px`;
|
|
145
|
-
document.body.style.width = "100%";
|
|
146
|
-
document.body.style.overflow = "hidden";
|
|
147
|
-
} else {
|
|
148
|
-
_restore();
|
|
149
|
-
}
|
|
150
|
-
return _restore; // onDestroy as well
|
|
96
|
+
visible ? BodyScroll.lock() : BodyScroll.unlock();
|
|
151
97
|
});
|
|
152
98
|
|
|
99
|
+
// we need onDestroy as well
|
|
100
|
+
// Note, that this will also reset if nested... (which is not desired, but ignoring)
|
|
101
|
+
onDestroy(BodyScroll.unlock);
|
|
102
|
+
|
|
153
103
|
$effect(() => {
|
|
154
104
|
function onkeydown(e: KeyboardEvent) {
|
|
155
105
|
if (e.key === "Escape" && typeof onEscape === "function") {
|
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
interface BodyStyles {
|
|
3
|
-
position: string | null;
|
|
4
|
-
top: string | null;
|
|
5
|
-
width: string | null;
|
|
6
|
-
overflow: string | null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function get_body_style(): BodyStyles {
|
|
10
|
-
// const style = window.getComputedStyle(document.body);
|
|
11
|
-
const style = document.body.style; // we want only explicitly defined, not computed
|
|
12
|
-
return {
|
|
13
|
-
position: style.position || null,
|
|
14
|
-
top: style.top || null,
|
|
15
|
-
width: style.width || null,
|
|
16
|
-
overflow: style.overflow || null,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function restore_body_styles(original: BodyStyles) {
|
|
21
|
-
(["position", "top", "width", "overflow"] as (keyof BodyStyles)[]).forEach((k) => {
|
|
22
|
-
if (original[k] !== null) {
|
|
23
|
-
document.body.style[k] = original[k];
|
|
24
|
-
} else {
|
|
25
|
-
document.body.style.removeProperty(k);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
1
|
<script lang="ts">
|
|
32
2
|
import { onClickOutside } from "runed";
|
|
33
3
|
import { onDestroy, onMount, tick, type Snippet } from "svelte";
|
|
@@ -36,6 +6,7 @@
|
|
|
36
6
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
37
7
|
import { createClog } from "@marianmeres/clog";
|
|
38
8
|
import { waitForNextRepaint } from "../../utils/paint.js";
|
|
9
|
+
import { BodyScroll } from "../../utils/body-scroll-locker.js";
|
|
39
10
|
|
|
40
11
|
const clog = createClog("ModalDialog").debug;
|
|
41
12
|
|
|
@@ -65,7 +36,9 @@
|
|
|
65
36
|
preClose,
|
|
66
37
|
}: Props = $props();
|
|
67
38
|
|
|
68
|
-
|
|
39
|
+
// important to start as undefined (because of scroll save/restore)
|
|
40
|
+
let visible: boolean | undefined = $state(undefined);
|
|
41
|
+
|
|
69
42
|
let dialog = $state<HTMLDialogElement>()!;
|
|
70
43
|
let box = $state<HTMLDivElement>()!;
|
|
71
44
|
let _opener: undefined | null | HTMLElement = $state();
|
|
@@ -106,36 +79,16 @@
|
|
|
106
79
|
() => !noClickOutsideClose && close()
|
|
107
80
|
);
|
|
108
81
|
|
|
109
|
-
// lock body scroll when open and restore back
|
|
110
|
-
let _original: BodyStyles = {
|
|
111
|
-
position: null,
|
|
112
|
-
top: null,
|
|
113
|
-
width: null,
|
|
114
|
-
overflow: null,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
function _restore() {
|
|
118
|
-
const scrollY = document.body.style.top;
|
|
119
|
-
restore_body_styles(_original);
|
|
120
|
-
// Restore scroll position
|
|
121
|
-
window.scrollTo(0, parseInt(scrollY || "0") * -1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
82
|
$effect(() => {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
document.body.style.position = "fixed";
|
|
129
|
-
document.body.style.top = `-${scrollY}px`;
|
|
130
|
-
document.body.style.width = "100%";
|
|
131
|
-
document.body.style.overflow = "hidden";
|
|
132
|
-
} else {
|
|
133
|
-
_restore();
|
|
134
|
-
}
|
|
135
|
-
// also onDestroy (will also reset if nested... which is not desired, but ignoring currently)
|
|
136
|
-
return _restore;
|
|
83
|
+
// noop if we're undefined ($effect runs immediately as onMount)
|
|
84
|
+
if (visible === undefined) return;
|
|
85
|
+
visible ? BodyScroll.lock() : BodyScroll.unlock();
|
|
137
86
|
});
|
|
138
87
|
|
|
88
|
+
// we need onDestroy as well
|
|
89
|
+
// Note, that this will also reset if nested... (which is not desired, but ignoring)
|
|
90
|
+
onDestroy(BodyScroll.unlock);
|
|
91
|
+
|
|
139
92
|
// $inspect("Modal dialog mounted, is visible:", visible).with(clog);
|
|
140
93
|
</script>
|
|
141
94
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createClog } from "@marianmeres/clog";
|
|
2
|
+
const clog = createClog("BodyScroll").debug;
|
|
3
|
+
/**
|
|
4
|
+
* Helper for "locking" and "unlocking" body scroll (window.scrollY) position
|
|
5
|
+
*/
|
|
6
|
+
export class BodyScroll {
|
|
7
|
+
static lock() {
|
|
8
|
+
const data = document.body.dataset;
|
|
9
|
+
// Only save the scroll position if it hasn't been saved already
|
|
10
|
+
if (data.originalScrollY === undefined) {
|
|
11
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
12
|
+
// Save body styles as serialized json
|
|
13
|
+
data.originalScrollStyleBackup = BodyScroll._get_body_style();
|
|
14
|
+
// Save the original scroll position in dataset
|
|
15
|
+
data.originalScrollY = `${scrollY}`;
|
|
16
|
+
data.scrollLockCount = "1";
|
|
17
|
+
// Apply the fixed positioning
|
|
18
|
+
document.body.style.position = "fixed";
|
|
19
|
+
document.body.style.top = `-${scrollY}px`;
|
|
20
|
+
document.body.style.width = "100%";
|
|
21
|
+
document.body.style.overflow = "hidden";
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Another component already locked the scroll, just increment the counter
|
|
25
|
+
const currentCount = parseInt(data.scrollLockCount, 10);
|
|
26
|
+
data.scrollLockCount = `${currentCount + 1}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
static unlock() {
|
|
30
|
+
const data = document.body.dataset;
|
|
31
|
+
// Only proceed if scroll is currently locked
|
|
32
|
+
if (data.scrollLockCount !== undefined) {
|
|
33
|
+
const count = parseInt(data.scrollLockCount, 10);
|
|
34
|
+
if (count > 1) {
|
|
35
|
+
// Other components still need the lock, just decrement the counter
|
|
36
|
+
data.scrollLockCount = `${count - 1}`;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// This is the last component, restore everything
|
|
40
|
+
const originalScrollY = parseInt(data.originalScrollY, 10);
|
|
41
|
+
BodyScroll._restore_body_styles(data.originalScrollStyleBackup);
|
|
42
|
+
// Remove our data attributes
|
|
43
|
+
delete data.originalScrollY;
|
|
44
|
+
delete data.originalScrollStyleBackup;
|
|
45
|
+
delete data.scrollLockCount;
|
|
46
|
+
// Restore the scroll position
|
|
47
|
+
window.scrollTo(0, originalScrollY);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static _get_body_style() {
|
|
52
|
+
// we want only explicitly defined, not computed
|
|
53
|
+
const style = document.body.style;
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
position: style.position || null,
|
|
56
|
+
top: style.position || null,
|
|
57
|
+
width: style.width || null,
|
|
58
|
+
overflow: style.overflow || null,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
static _restore_body_styles(originalJsonString) {
|
|
62
|
+
let original = { position: null, top: null, width: null, overflow: null };
|
|
63
|
+
try {
|
|
64
|
+
original = JSON.parse(originalJsonString);
|
|
65
|
+
}
|
|
66
|
+
catch (e) { }
|
|
67
|
+
["position", "top", "width", "overflow"].forEach((k) => {
|
|
68
|
+
if (original[k] !== null) {
|
|
69
|
+
document.body.style[k] = original[k];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
document.body.style.removeProperty(k);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED