@x33025/sveltely 0.1.7 → 0.1.8
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/actions/LoaderOverlay.svelte +44 -0
- package/dist/actions/LoaderOverlay.svelte.d.ts +6 -0
- package/dist/actions/loader.d.ts +11 -0
- package/dist/actions/loader.js +117 -0
- package/dist/components/Library/Loader/Loader.demo.svelte +74 -0
- package/dist/components/Library/Loader/Loader.demo.svelte.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/style.css +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Spinner from '../components/Library/Spinner/Spinner.svelte';
|
|
3
|
+
import VStack from '../components/Library/VStack/VStack.svelte';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
let { text }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div class="sveltely-loader-overlay" role="status" aria-live="polite" aria-label={text}>
|
|
13
|
+
<VStack align="center" justify="center" gap="var(--sveltely-gap)">
|
|
14
|
+
<Spinner />
|
|
15
|
+
<span class="sveltely-loader-text">{text}</span>
|
|
16
|
+
</VStack>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.sveltely-loader-overlay {
|
|
21
|
+
position: absolute;
|
|
22
|
+
inset: 0;
|
|
23
|
+
z-index: 20;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
border-radius: inherit;
|
|
28
|
+
background: var(--sveltely-background-color, white);
|
|
29
|
+
color: var(--sveltely-primary-color, black);
|
|
30
|
+
padding: var(--sveltely-padding-y, 0.75rem) var(--sveltely-padding-x, 0.75rem);
|
|
31
|
+
pointer-events: auto;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sveltely-loader-text {
|
|
36
|
+
max-width: 100%;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
color: var(--sveltely-secondary-color, currentColor);
|
|
39
|
+
font-size: calc(var(--sveltely-font-size, 1rem) * 0.875);
|
|
40
|
+
line-height: 1.25;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Action } from 'svelte/action';
|
|
2
|
+
type LoaderTrigger = boolean | PromiseLike<unknown> | null | undefined;
|
|
3
|
+
export type LoaderOptions = LoaderTrigger | [text: string, loading: LoaderTrigger] | {
|
|
4
|
+
text?: string;
|
|
5
|
+
loading?: LoaderTrigger;
|
|
6
|
+
active?: LoaderTrigger;
|
|
7
|
+
promise?: PromiseLike<unknown> | null;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare const loader: Action<HTMLElement, LoaderOptions>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { mount, unmount } from 'svelte';
|
|
2
|
+
import LoaderOverlay from './LoaderOverlay.svelte';
|
|
3
|
+
const DEFAULT_TEXT = 'Loading';
|
|
4
|
+
const isPromiseLike = (value) => typeof value === 'object' &&
|
|
5
|
+
value !== null &&
|
|
6
|
+
'then' in value &&
|
|
7
|
+
typeof value.then === 'function';
|
|
8
|
+
const parseOptions = (value = false) => {
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
return {
|
|
11
|
+
text: value[0] || DEFAULT_TEXT,
|
|
12
|
+
loading: value[1],
|
|
13
|
+
disabled: false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'object' && value !== null && !isPromiseLike(value)) {
|
|
17
|
+
return {
|
|
18
|
+
text: value.text || DEFAULT_TEXT,
|
|
19
|
+
loading: value.loading ?? value.active ?? value.promise ?? false,
|
|
20
|
+
disabled: value.disabled ?? false
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
text: DEFAULT_TEXT,
|
|
25
|
+
loading: value,
|
|
26
|
+
disabled: false
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export const loader = (node, initialOptions = false) => {
|
|
30
|
+
if (typeof document === 'undefined') {
|
|
31
|
+
return {
|
|
32
|
+
update() { },
|
|
33
|
+
destroy() { }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const originalPosition = node.style.position;
|
|
37
|
+
const originalAriaBusy = node.getAttribute('aria-busy');
|
|
38
|
+
let options = parseOptions(initialOptions);
|
|
39
|
+
let overlay = null;
|
|
40
|
+
let overlayText = '';
|
|
41
|
+
let promiseVersion = 0;
|
|
42
|
+
let appliedPosition = false;
|
|
43
|
+
const ensureHostPosition = () => {
|
|
44
|
+
if (getComputedStyle(node).position !== 'static')
|
|
45
|
+
return;
|
|
46
|
+
node.style.position = 'relative';
|
|
47
|
+
appliedPosition = true;
|
|
48
|
+
};
|
|
49
|
+
const resetHostPosition = () => {
|
|
50
|
+
if (!appliedPosition)
|
|
51
|
+
return;
|
|
52
|
+
if (node.style.position === 'relative') {
|
|
53
|
+
node.style.position = originalPosition;
|
|
54
|
+
}
|
|
55
|
+
appliedPosition = false;
|
|
56
|
+
};
|
|
57
|
+
const show = () => {
|
|
58
|
+
ensureHostPosition();
|
|
59
|
+
node.setAttribute('aria-busy', 'true');
|
|
60
|
+
if (overlay && overlayText === options.text) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (overlay) {
|
|
64
|
+
void unmount(overlay);
|
|
65
|
+
}
|
|
66
|
+
overlayText = options.text;
|
|
67
|
+
overlay = mount(LoaderOverlay, {
|
|
68
|
+
target: node,
|
|
69
|
+
props: {
|
|
70
|
+
text: options.text
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const hide = () => {
|
|
75
|
+
if (overlay) {
|
|
76
|
+
void unmount(overlay);
|
|
77
|
+
overlay = null;
|
|
78
|
+
overlayText = '';
|
|
79
|
+
}
|
|
80
|
+
if (originalAriaBusy === null) {
|
|
81
|
+
node.removeAttribute('aria-busy');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
node.setAttribute('aria-busy', originalAriaBusy);
|
|
85
|
+
}
|
|
86
|
+
resetHostPosition();
|
|
87
|
+
};
|
|
88
|
+
const render = () => {
|
|
89
|
+
const currentVersion = ++promiseVersion;
|
|
90
|
+
if (options.disabled || !options.loading) {
|
|
91
|
+
hide();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
show();
|
|
95
|
+
if (!isPromiseLike(options.loading))
|
|
96
|
+
return;
|
|
97
|
+
options.loading.then(() => {
|
|
98
|
+
if (currentVersion === promiseVersion)
|
|
99
|
+
hide();
|
|
100
|
+
}, () => {
|
|
101
|
+
if (currentVersion === promiseVersion)
|
|
102
|
+
hide();
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
render();
|
|
106
|
+
return {
|
|
107
|
+
update(nextOptions = false) {
|
|
108
|
+
options = parseOptions(nextOptions);
|
|
109
|
+
render();
|
|
110
|
+
},
|
|
111
|
+
destroy() {
|
|
112
|
+
promiseVersion++;
|
|
113
|
+
hide();
|
|
114
|
+
resetHostPosition();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export const demo = {
|
|
3
|
+
name: 'Loader',
|
|
4
|
+
description: 'Overlay loading state for any component.',
|
|
5
|
+
columnSpan: 1,
|
|
6
|
+
rowSpan: 2
|
|
7
|
+
};
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { loader } from '../../../actions/loader';
|
|
12
|
+
import Button from '../Button';
|
|
13
|
+
|
|
14
|
+
let pending = $state<Promise<void> | null>(null);
|
|
15
|
+
|
|
16
|
+
const startLoading = () => {
|
|
17
|
+
const next = new Promise<void>((resolve) => {
|
|
18
|
+
setTimeout(resolve, 1600);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
pending = next;
|
|
22
|
+
next.finally(() => {
|
|
23
|
+
if (pending === next) {
|
|
24
|
+
pending = null;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div class="loader-demo" use:loader={{ text: 'Loading preview...', promise: pending }}>
|
|
31
|
+
<div class="loader-demo-copy">
|
|
32
|
+
<strong>Preview panel</strong>
|
|
33
|
+
<span>Click the button to cover this component with the loader overlay.</span>
|
|
34
|
+
</div>
|
|
35
|
+
<Button label="Show loader" onclick={startLoading} disabled={pending !== null} />
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.loader-demo {
|
|
40
|
+
display: flex;
|
|
41
|
+
min-height: 8rem;
|
|
42
|
+
width: 100%;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
align-items: flex-start;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
gap: var(--sveltely-gap);
|
|
47
|
+
border: 1px solid var(--sveltely-border-color);
|
|
48
|
+
border-radius: var(--sveltely-border-radius);
|
|
49
|
+
background: color-mix(
|
|
50
|
+
in oklab,
|
|
51
|
+
var(--sveltely-background-color) 94%,
|
|
52
|
+
var(--sveltely-primary-color)
|
|
53
|
+
);
|
|
54
|
+
padding: var(--sveltely-padding-y) var(--sveltely-padding-x);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.loader-demo-copy {
|
|
58
|
+
display: flex;
|
|
59
|
+
min-width: 0;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: calc(var(--sveltely-gap) * 0.5);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.loader-demo-copy strong {
|
|
65
|
+
font-size: var(--sveltely-font-size);
|
|
66
|
+
line-height: 1.25;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.loader-demo-copy span {
|
|
70
|
+
color: var(--sveltely-secondary-color);
|
|
71
|
+
font-size: calc(var(--sveltely-font-size) * 0.875);
|
|
72
|
+
line-height: 1.3;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const demo: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
columnSpan: number;
|
|
5
|
+
rowSpan: number;
|
|
6
|
+
};
|
|
7
|
+
declare const Loader: import("svelte").Component<Record<string, never>, {}, "">;
|
|
8
|
+
type Loader = ReturnType<typeof Loader>;
|
|
9
|
+
export default Loader;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { motion, hover } from './actions/motion';
|
|
|
2
2
|
export { portalHost, portalContent } from './actions/portal';
|
|
3
3
|
export { tooltip } from './actions/tooltip';
|
|
4
4
|
export type { TooltipOptions } from './actions/tooltip';
|
|
5
|
+
export { loader } from './actions/loader';
|
|
6
|
+
export type { LoaderOptions } from './actions/loader';
|
|
5
7
|
export { LayoutAlignment, LayoutJustification, LayoutSize } from './style/layout';
|
|
6
8
|
export type { LayoutAlignmentValue, LayoutJustificationValue, LayoutProps, LayoutSizeValue } from './style/layout';
|
|
7
9
|
export { LabelOrientation } from './style/label';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { motion, hover } from './actions/motion';
|
|
2
2
|
export { portalHost, portalContent } from './actions/portal';
|
|
3
3
|
export { tooltip } from './actions/tooltip';
|
|
4
|
+
export { loader } from './actions/loader';
|
|
4
5
|
export { LayoutAlignment, LayoutJustification, LayoutSize } from './style/layout';
|
|
5
6
|
export { LabelOrientation } from './style/label';
|
|
6
7
|
export { ImageFit, ImageLoading } from './style/media';
|
package/dist/style.css
CHANGED