@marianmeres/stuic 2.1.8 → 2.1.10
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/AlertConfirmPrompt/Current.svelte +10 -6
- package/dist/components/AlertConfirmPrompt/alert-confirm-prompt-stack.svelte.d.ts +3 -0
- 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
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
return out;
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
let CmpButtonOk = $derived(current.CmpButtonOk ?? Button);
|
|
69
|
+
let CmpButtonCancel = $derived(current.CmpButtonCancel ?? Button);
|
|
70
|
+
let CmpButtonCustom = $derived(current.CmpButtonCustom ?? Button);
|
|
71
|
+
|
|
68
72
|
let inputEl = $state<any>();
|
|
69
73
|
let okButtonEl = $state<any>();
|
|
70
74
|
|
|
@@ -189,28 +193,28 @@
|
|
|
189
193
|
<menu class={twMerge(_classMenu, classMenu)}>
|
|
190
194
|
{#if current.type !== ALERT}
|
|
191
195
|
<li class={twMerge(_classMenuLi, classMenuLi)}>
|
|
192
|
-
<
|
|
196
|
+
<CmpButtonCancel
|
|
193
197
|
class={twMerge("cancel", _classButton, classButton)}
|
|
194
198
|
disabled={isPending}
|
|
195
199
|
onclick={createOnClick("cancel", current.onCancel)}
|
|
196
200
|
>
|
|
197
201
|
<Thc thc={current.labelCancel} {forceAsHtml} />
|
|
198
|
-
</
|
|
202
|
+
</CmpButtonCancel>
|
|
199
203
|
</li>
|
|
200
204
|
{/if}
|
|
201
205
|
{#if current.labelCustom && typeof current.onCustom === "function"}
|
|
202
206
|
<li class={twMerge(_classMenuLi, classMenuLi)}>
|
|
203
|
-
<
|
|
207
|
+
<CmpButtonCustom
|
|
204
208
|
class={twMerge("custom", _classButton, classButton)}
|
|
205
209
|
disabled={isPending}
|
|
206
210
|
onclick={createOnClick("custom", current.onCustom)}
|
|
207
211
|
>
|
|
208
212
|
<Thc thc={current.labelCustom} {forceAsHtml} />
|
|
209
|
-
</
|
|
213
|
+
</CmpButtonCustom>
|
|
210
214
|
</li>
|
|
211
215
|
{/if}
|
|
212
216
|
<li class={twMerge(_classMenuLi, classMenuLi)}>
|
|
213
|
-
<
|
|
217
|
+
<CmpButtonOk
|
|
214
218
|
class={twMerge("ok", _classButton, classButton)}
|
|
215
219
|
variant="primary"
|
|
216
220
|
disabled={isPending}
|
|
@@ -218,7 +222,7 @@
|
|
|
218
222
|
bind:el={okButtonEl}
|
|
219
223
|
>
|
|
220
224
|
<Thc thc={current.labelOk} {forceAsHtml} />
|
|
221
|
-
</
|
|
225
|
+
</CmpButtonOk>
|
|
222
226
|
</li>
|
|
223
227
|
</menu>
|
|
224
228
|
{#if isPending}
|
|
@@ -25,6 +25,9 @@ export interface AlertConfirmPromptObj extends Record<string, any> {
|
|
|
25
25
|
variant: AlertConfirmPromptVariant;
|
|
26
26
|
iconFn: (() => string) | boolean;
|
|
27
27
|
forceAsHtml?: boolean;
|
|
28
|
+
CmpButtonOk?: any;
|
|
29
|
+
CmpButtonCancel?: any;
|
|
30
|
+
CmpButtonCustom?: any;
|
|
28
31
|
}
|
|
29
32
|
export declare class AlertConfirmPromptStack {
|
|
30
33
|
#private;
|
|
@@ -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