@marianmeres/stuic 1.33.0 → 1.34.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/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte +23 -21
- package/dist/components/AlertConfirmPrompt/AlertConfirmPrompt.svelte.d.ts +12 -10
- package/dist/components/AlertConfirmPrompt/acp-icons.d.ts +1 -0
- package/dist/components/AlertConfirmPrompt/acp-icons.js +7 -25
- package/dist/components/Notifications/Notifications.svelte +316 -0
- package/dist/components/Notifications/Notifications.svelte.d.ts +61 -0
- package/dist/components/Notifications/notifications-icons.d.ts +3 -0
- package/dist/components/Notifications/notifications-icons.js +115 -0
- package/dist/components/Notifications/notifications.d.ts +79 -0
- package/dist/components/Notifications/notifications.js +225 -0
- package/dist/components/Switch/Switch.svelte +4 -4
- package/dist/components/Thc/Thc.svelte +1 -1
- package/dist/components/Thc/Thc.svelte.d.ts +1 -0
- package/dist/components/X/X.svelte +3 -4
- package/dist/components/X/X.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/package.json +2 -1
|
@@ -70,17 +70,19 @@ export class AlertConfirmPromptConfig {
|
|
|
70
70
|
`.trim()
|
|
71
71
|
};
|
|
72
72
|
// main userland configuration
|
|
73
|
-
static
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
static class = {
|
|
74
|
+
dialog: "",
|
|
75
|
+
icon: "",
|
|
76
|
+
contentBlock: "",
|
|
77
|
+
title: "",
|
|
78
|
+
content: "",
|
|
79
|
+
inputBox: "",
|
|
80
|
+
inputField: "",
|
|
81
|
+
menu: "",
|
|
82
|
+
// menuLi: '',
|
|
83
|
+
button: "",
|
|
84
|
+
spinnerBox: ""
|
|
85
|
+
};
|
|
84
86
|
// 'info' | 'success' | 'warn' | 'error'
|
|
85
87
|
// userlang variant fine tuning
|
|
86
88
|
static variant = {
|
|
@@ -200,56 +202,56 @@ onMount(() => {
|
|
|
200
202
|
$:
|
|
201
203
|
_dialogClass = twMerge(`
|
|
202
204
|
${AlertConfirmPromptConfig.preset.dialog}
|
|
203
|
-
${AlertConfirmPromptConfig.
|
|
205
|
+
${AlertConfirmPromptConfig.class.dialog}
|
|
204
206
|
${dialog?.class?.dialog || ""}
|
|
205
207
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.dialog || ""}
|
|
206
208
|
`);
|
|
207
209
|
$:
|
|
208
210
|
_iconClass = twMerge(`
|
|
209
211
|
${AlertConfirmPromptConfig.preset.icon}
|
|
210
|
-
${AlertConfirmPromptConfig.
|
|
212
|
+
${AlertConfirmPromptConfig.class.icon}
|
|
211
213
|
${dialog?.class?.icon || ""}
|
|
212
214
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.icon || ""}
|
|
213
215
|
`);
|
|
214
216
|
$:
|
|
215
217
|
_contentBlockClass = twMerge(`
|
|
216
218
|
${AlertConfirmPromptConfig.preset.contentBlock}
|
|
217
|
-
${AlertConfirmPromptConfig.
|
|
219
|
+
${AlertConfirmPromptConfig.class.contentBlock}
|
|
218
220
|
${dialog?.class?.contentBlock || ""}
|
|
219
221
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.contentBlock || ""}
|
|
220
222
|
`);
|
|
221
223
|
$:
|
|
222
224
|
_titleClass = twMerge(`
|
|
223
225
|
${AlertConfirmPromptConfig.preset.title}
|
|
224
|
-
${AlertConfirmPromptConfig.
|
|
226
|
+
${AlertConfirmPromptConfig.class.title}
|
|
225
227
|
${dialog?.class?.title || ""}
|
|
226
228
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.title || ""}
|
|
227
229
|
`);
|
|
228
230
|
$:
|
|
229
231
|
_contentClass = twMerge(`
|
|
230
232
|
${AlertConfirmPromptConfig.preset.content}
|
|
231
|
-
${AlertConfirmPromptConfig.
|
|
233
|
+
${AlertConfirmPromptConfig.class.content}
|
|
232
234
|
${dialog?.class?.content || ""}
|
|
233
235
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.content || ""}
|
|
234
236
|
`);
|
|
235
237
|
$:
|
|
236
238
|
_inputBoxClass = twMerge(`
|
|
237
239
|
${AlertConfirmPromptConfig.preset.inputBox}
|
|
238
|
-
${AlertConfirmPromptConfig.
|
|
240
|
+
${AlertConfirmPromptConfig.class.inputBox}
|
|
239
241
|
${dialog?.class?.inputBox || ""}
|
|
240
242
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.inputBox || ""}
|
|
241
243
|
`);
|
|
242
244
|
$:
|
|
243
245
|
_inputFieldClass = twMerge(`
|
|
244
246
|
${AlertConfirmPromptConfig.preset.inputField}
|
|
245
|
-
${AlertConfirmPromptConfig.
|
|
247
|
+
${AlertConfirmPromptConfig.class.inputField}
|
|
246
248
|
${dialog?.class?.inputField || ""}
|
|
247
249
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.inputField || ""}
|
|
248
250
|
`);
|
|
249
251
|
$:
|
|
250
252
|
_menuClass = twMerge(`
|
|
251
253
|
${AlertConfirmPromptConfig.preset.menu}
|
|
252
|
-
${AlertConfirmPromptConfig.
|
|
254
|
+
${AlertConfirmPromptConfig.class.menu}
|
|
253
255
|
${dialog?.class?.menu || ""}
|
|
254
256
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.menu || ""}
|
|
255
257
|
`);
|
|
@@ -260,14 +262,14 @@ $:
|
|
|
260
262
|
$:
|
|
261
263
|
_buttonClass = twMerge(`
|
|
262
264
|
${AlertConfirmPromptConfig.preset.button}
|
|
263
|
-
${AlertConfirmPromptConfig.
|
|
265
|
+
${AlertConfirmPromptConfig.class.button}
|
|
264
266
|
${dialog?.class?.button || ""}
|
|
265
267
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.button || ""}
|
|
266
268
|
`);
|
|
267
269
|
$:
|
|
268
270
|
_spinnerBoxClass = twMerge(`
|
|
269
271
|
${AlertConfirmPromptConfig.preset.spinnerBox}
|
|
270
|
-
${AlertConfirmPromptConfig.
|
|
272
|
+
${AlertConfirmPromptConfig.class.spinnerBox}
|
|
271
273
|
${dialog?.class?.spinnerBox || ""}
|
|
272
274
|
${AlertConfirmPromptConfig.variant?.[dialog?.variant]?.spinnerBox || ""}
|
|
273
275
|
`);
|
|
@@ -14,16 +14,18 @@ export declare class AlertConfirmPromptConfig {
|
|
|
14
14
|
button: string;
|
|
15
15
|
spinnerBox: string;
|
|
16
16
|
};
|
|
17
|
-
static
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
static class: {
|
|
18
|
+
dialog: string;
|
|
19
|
+
icon: string;
|
|
20
|
+
contentBlock: string;
|
|
21
|
+
title: string;
|
|
22
|
+
content: string;
|
|
23
|
+
inputBox: string;
|
|
24
|
+
inputField: string;
|
|
25
|
+
menu: string;
|
|
26
|
+
button: string;
|
|
27
|
+
spinnerBox: string;
|
|
28
|
+
};
|
|
27
29
|
static variant: {
|
|
28
30
|
info: {
|
|
29
31
|
dialog: string;
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
// taken from @marianmeres/icons-fns
|
|
2
|
-
function
|
|
3
|
-
// Backward compatible signature support: fn(cls, size, style)
|
|
4
|
-
if (props === null || props === undefined)
|
|
5
|
-
props = {};
|
|
6
|
-
if (typeof props !== 'object')
|
|
7
|
-
props = { class: props || '' };
|
|
8
|
-
if (arguments.length > 1)
|
|
9
|
-
props.size ??= arguments[1];
|
|
10
|
-
if (arguments.length > 2)
|
|
11
|
-
props.style ??= arguments[2];
|
|
12
|
-
//
|
|
13
|
-
const { size, class: cls, style, strokeWidth } = props;
|
|
14
|
-
let attrs = Object.entries(props)
|
|
15
|
-
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
16
|
-
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
17
|
-
.join(' ');
|
|
18
|
-
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
|
|
19
|
-
}
|
|
20
|
-
function iconFeatherCheckCircle(props) {
|
|
2
|
+
export function iconFeatherAlertTriangle(props) {
|
|
21
3
|
// Backward compatible signature support: fn(cls, size, style)
|
|
22
4
|
if (props === null || props === undefined)
|
|
23
5
|
props = {};
|
|
@@ -33,9 +15,9 @@ function iconFeatherCheckCircle(props) {
|
|
|
33
15
|
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
34
16
|
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
35
17
|
.join(' ');
|
|
36
|
-
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="
|
|
18
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>`;
|
|
37
19
|
}
|
|
38
|
-
function
|
|
20
|
+
function iconFeatherAlertCircle(props) {
|
|
39
21
|
// Backward compatible signature support: fn(cls, size, style)
|
|
40
22
|
if (props === null || props === undefined)
|
|
41
23
|
props = {};
|
|
@@ -51,9 +33,9 @@ function iconFeatherHelpCircle(props) {
|
|
|
51
33
|
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
52
34
|
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
53
35
|
.join(' ');
|
|
54
|
-
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><
|
|
36
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
|
|
55
37
|
}
|
|
56
|
-
function
|
|
38
|
+
function iconFeatherCheckCircle(props) {
|
|
57
39
|
// Backward compatible signature support: fn(cls, size, style)
|
|
58
40
|
if (props === null || props === undefined)
|
|
59
41
|
props = {};
|
|
@@ -69,7 +51,7 @@ function iconFeatherXCircle(props) {
|
|
|
69
51
|
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
70
52
|
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
71
53
|
.join(' ');
|
|
72
|
-
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><
|
|
54
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`;
|
|
73
55
|
}
|
|
74
56
|
function iconFeatherXOctagon(props) {
|
|
75
57
|
// Backward compatible signature support: fn(cls, size, style)
|
|
@@ -128,7 +110,7 @@ function iconFeatherRotateCw(props) {
|
|
|
128
110
|
export const acpDefaultIcons = {
|
|
129
111
|
info: () => iconFeatherInfo({}),
|
|
130
112
|
success: () => iconFeatherCheckCircle({}),
|
|
131
|
-
warn: () =>
|
|
113
|
+
warn: () => iconFeatherAlertTriangle({}),
|
|
132
114
|
error: () => iconFeatherXOctagon({}),
|
|
133
115
|
spinner: () => iconFeatherRotateCw({ size: 32, class: 'opacity-50' }),
|
|
134
116
|
};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<script context="module">import { fade } from "svelte/transition";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import Thc from "../Thc/Thc.svelte";
|
|
4
|
+
import X from "../X/X.svelte";
|
|
5
|
+
import { notificationsDefaultIcons } from "./notifications-icons.js";
|
|
6
|
+
import { createClog } from "@marianmeres/clog";
|
|
7
|
+
const X_POSITIONS = ["left", "center", "right"];
|
|
8
|
+
const Y_POSITIONS = ["top", "center", "bottom"];
|
|
9
|
+
const DEFAULT = {
|
|
10
|
+
posX: "center",
|
|
11
|
+
posXMobile: "center",
|
|
12
|
+
posY: "top",
|
|
13
|
+
posYMobile: "bottom"
|
|
14
|
+
};
|
|
15
|
+
const XMAP = { left: "sm:items-start", center: "sm:items-center", right: "sm:items-end" };
|
|
16
|
+
const XMAP_M = { left: "items-start", center: "items-center", right: "items-end" };
|
|
17
|
+
const YMAP = { top: "sm:items-start", center: "sm:items-center", bottom: "sm:items-end" };
|
|
18
|
+
const YMAP_M = { top: "items-start", center: "items-center", bottom: "items-end" };
|
|
19
|
+
export class NotificationsConfig {
|
|
20
|
+
static preset = {
|
|
21
|
+
wrap: `
|
|
22
|
+
fixed z-10
|
|
23
|
+
`,
|
|
24
|
+
wrapInner: `
|
|
25
|
+
p-4 space-y-4
|
|
26
|
+
`,
|
|
27
|
+
notification: {
|
|
28
|
+
box: `
|
|
29
|
+
relative flex
|
|
30
|
+
pointer-events-auto
|
|
31
|
+
w-full max-w-lg
|
|
32
|
+
rounded-md
|
|
33
|
+
shadow-lg
|
|
34
|
+
bg-gray-700 text-white
|
|
35
|
+
`,
|
|
36
|
+
count: `
|
|
37
|
+
absolute -top-2 -right-2
|
|
38
|
+
w-auto h-auto
|
|
39
|
+
flex items-center justify-center
|
|
40
|
+
px-2 py-1 rounded-full
|
|
41
|
+
leading-none text-xs
|
|
42
|
+
bg-black text-white
|
|
43
|
+
`,
|
|
44
|
+
icon: `
|
|
45
|
+
flex items-start justify-center
|
|
46
|
+
pt-4 pr-0 pb-4 pl-4
|
|
47
|
+
text-gray-200
|
|
48
|
+
`,
|
|
49
|
+
content: `
|
|
50
|
+
flex-1
|
|
51
|
+
flex flex-col justify-center
|
|
52
|
+
text-sm
|
|
53
|
+
pl-4 pr-1 py-3
|
|
54
|
+
`,
|
|
55
|
+
button: `
|
|
56
|
+
flex flex-col items-center justify-center
|
|
57
|
+
leading-none
|
|
58
|
+
px-3
|
|
59
|
+
hover:bg-black/20
|
|
60
|
+
group
|
|
61
|
+
rounded-tr-md rounded-br-md
|
|
62
|
+
`,
|
|
63
|
+
x: `
|
|
64
|
+
opacity-75 group-hover:opacity-100
|
|
65
|
+
`
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
static presetByType = {
|
|
69
|
+
info: {
|
|
70
|
+
box: ``,
|
|
71
|
+
count: ``,
|
|
72
|
+
icon: ``,
|
|
73
|
+
content: ``,
|
|
74
|
+
button: ``,
|
|
75
|
+
x: ``
|
|
76
|
+
},
|
|
77
|
+
success: {
|
|
78
|
+
box: ``,
|
|
79
|
+
// e.g. bg-green-800
|
|
80
|
+
count: ``,
|
|
81
|
+
icon: ``,
|
|
82
|
+
content: ``,
|
|
83
|
+
button: ``,
|
|
84
|
+
x: ``
|
|
85
|
+
},
|
|
86
|
+
warn: {
|
|
87
|
+
box: ``,
|
|
88
|
+
// e.g. bg-yellow-800
|
|
89
|
+
count: ``,
|
|
90
|
+
icon: ``,
|
|
91
|
+
content: ``,
|
|
92
|
+
button: ``,
|
|
93
|
+
x: ``
|
|
94
|
+
},
|
|
95
|
+
error: {
|
|
96
|
+
box: `bg-red-700`,
|
|
97
|
+
// e.g. bg-red-800
|
|
98
|
+
count: ``,
|
|
99
|
+
icon: ``,
|
|
100
|
+
content: ``,
|
|
101
|
+
button: ``,
|
|
102
|
+
x: ``
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
static class = {
|
|
106
|
+
wrap: "",
|
|
107
|
+
wrapInner: "",
|
|
108
|
+
notification: {
|
|
109
|
+
box: ``,
|
|
110
|
+
count: ``,
|
|
111
|
+
icon: ``,
|
|
112
|
+
content: ``,
|
|
113
|
+
button: ``,
|
|
114
|
+
x: ``
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
static classByType = {
|
|
118
|
+
info: {
|
|
119
|
+
box: ``,
|
|
120
|
+
count: ``,
|
|
121
|
+
icon: ``,
|
|
122
|
+
content: ``,
|
|
123
|
+
button: ``,
|
|
124
|
+
x: ``
|
|
125
|
+
},
|
|
126
|
+
success: {
|
|
127
|
+
box: ``,
|
|
128
|
+
count: ``,
|
|
129
|
+
icon: ``,
|
|
130
|
+
content: ``,
|
|
131
|
+
button: ``,
|
|
132
|
+
x: ``
|
|
133
|
+
},
|
|
134
|
+
warn: {
|
|
135
|
+
box: ``,
|
|
136
|
+
count: ``,
|
|
137
|
+
icon: ``,
|
|
138
|
+
content: ``,
|
|
139
|
+
button: ``,
|
|
140
|
+
x: ``
|
|
141
|
+
},
|
|
142
|
+
error: {
|
|
143
|
+
box: ``,
|
|
144
|
+
count: ``,
|
|
145
|
+
icon: ``,
|
|
146
|
+
content: ``,
|
|
147
|
+
button: ``,
|
|
148
|
+
x: ``
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
static iconFn = {
|
|
152
|
+
info: void 0,
|
|
153
|
+
success: void 0,
|
|
154
|
+
warn: void 0,
|
|
155
|
+
error: void 0
|
|
156
|
+
};
|
|
157
|
+
// conveniently hoisted THC option, since all content is rendered via THC
|
|
158
|
+
static forceAsHtml = void 0;
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<script>const clog = createClog("Notifications");
|
|
163
|
+
export let notifications;
|
|
164
|
+
let _class = "";
|
|
165
|
+
export { _class as class };
|
|
166
|
+
export let ariaCloseLabel = "Discard";
|
|
167
|
+
export let posX = DEFAULT.posX;
|
|
168
|
+
export let posXMobile = DEFAULT.posXMobile;
|
|
169
|
+
export let posY = DEFAULT.posY;
|
|
170
|
+
export let posYMobile = DEFAULT.posYMobile;
|
|
171
|
+
let x, y, xMobile, yMobile;
|
|
172
|
+
$:
|
|
173
|
+
x = X_POSITIONS.includes(posX) ? posX : DEFAULT.posX;
|
|
174
|
+
$:
|
|
175
|
+
xMobile = X_POSITIONS.includes(posXMobile) ? posXMobile : DEFAULT.posXMobile;
|
|
176
|
+
$:
|
|
177
|
+
y = Y_POSITIONS.includes(posY) ? posY : DEFAULT.posY;
|
|
178
|
+
$:
|
|
179
|
+
yMobile = Y_POSITIONS.includes(posYMobile) ? posYMobile : DEFAULT.posYMobile;
|
|
180
|
+
$:
|
|
181
|
+
_wrapClass = twMerge(`
|
|
182
|
+
${NotificationsConfig.preset.wrap}
|
|
183
|
+
${NotificationsConfig.class.wrap}
|
|
184
|
+
flex flex-row inset-0
|
|
185
|
+
pointer-events-none bg-transparent
|
|
186
|
+
${YMAP_M[yMobile]} ${YMAP[y]}
|
|
187
|
+
`);
|
|
188
|
+
$:
|
|
189
|
+
_wrapInnerClass = twMerge(`
|
|
190
|
+
${NotificationsConfig.preset.wrapInner}
|
|
191
|
+
${NotificationsConfig.class.wrapInner}
|
|
192
|
+
flex flex-col w-full
|
|
193
|
+
pointer-events-none bg-transparent
|
|
194
|
+
${XMAP_M[xMobile]} ${XMAP[x]}
|
|
195
|
+
`);
|
|
196
|
+
const _boxClass = (n) => twMerge(`
|
|
197
|
+
${NotificationsConfig?.preset?.notification?.box}
|
|
198
|
+
${NotificationsConfig?.class?.notification?.box}
|
|
199
|
+
${NotificationsConfig?.presetByType?.[n.type]?.box || ""}
|
|
200
|
+
${NotificationsConfig?.classByType?.[n.type]?.box || ""}
|
|
201
|
+
${n.class?.box || ""}
|
|
202
|
+
`);
|
|
203
|
+
const _countClass = (n) => twMerge(`
|
|
204
|
+
${NotificationsConfig?.preset?.notification?.count}
|
|
205
|
+
${NotificationsConfig?.class?.notification?.count}
|
|
206
|
+
${NotificationsConfig?.presetByType?.[n.type]?.count || ""}
|
|
207
|
+
${NotificationsConfig?.classByType?.[n.type]?.count || ""}
|
|
208
|
+
${n.class?.count || ""}
|
|
209
|
+
`);
|
|
210
|
+
const _iconClass = (n) => twMerge(`
|
|
211
|
+
${NotificationsConfig?.preset?.notification?.icon}
|
|
212
|
+
${NotificationsConfig?.class?.notification?.icon}
|
|
213
|
+
${NotificationsConfig?.presetByType?.[n.type]?.icon || ""}
|
|
214
|
+
${NotificationsConfig?.classByType?.[n.type]?.icon || ""}
|
|
215
|
+
${n.class?.icon || ""}
|
|
216
|
+
`);
|
|
217
|
+
const _contentClass = (n) => twMerge(`
|
|
218
|
+
${NotificationsConfig?.preset?.notification?.content}
|
|
219
|
+
${NotificationsConfig?.class?.notification?.content}
|
|
220
|
+
${NotificationsConfig?.presetByType?.[n.type]?.content || ""}
|
|
221
|
+
${NotificationsConfig?.classByType?.[n.type]?.content || ""}
|
|
222
|
+
${n.class?.content || ""}
|
|
223
|
+
`);
|
|
224
|
+
const _buttonClass = (n) => twMerge(`
|
|
225
|
+
${NotificationsConfig?.preset?.notification?.button}
|
|
226
|
+
${NotificationsConfig?.class?.notification?.button}
|
|
227
|
+
${NotificationsConfig?.presetByType?.[n.type]?.button || ""}
|
|
228
|
+
${NotificationsConfig?.classByType?.[n.type]?.button || ""}
|
|
229
|
+
${n.class?.button || ""}
|
|
230
|
+
`);
|
|
231
|
+
const _xClass = (n) => twMerge(`
|
|
232
|
+
${NotificationsConfig?.preset?.notification?.x}
|
|
233
|
+
${NotificationsConfig?.class?.notification?.x}
|
|
234
|
+
${NotificationsConfig?.presetByType?.[n.type]?.x || ""}
|
|
235
|
+
${NotificationsConfig?.classByType?.[n.type]?.x || ""}
|
|
236
|
+
${n.class?.x || ""}
|
|
237
|
+
`);
|
|
238
|
+
const _iconFn = (n) => {
|
|
239
|
+
let iconFn = false;
|
|
240
|
+
if (n?.iconFn === true) {
|
|
241
|
+
iconFn = // either runtime config
|
|
242
|
+
NotificationsConfig.iconFn[n?.type] || // or fixed default
|
|
243
|
+
notificationsDefaultIcons[n?.type];
|
|
244
|
+
} else if (n?.iconFn) {
|
|
245
|
+
iconFn = n.iconFn;
|
|
246
|
+
} else {
|
|
247
|
+
iconFn = false;
|
|
248
|
+
}
|
|
249
|
+
return iconFn;
|
|
250
|
+
};
|
|
251
|
+
const _isFn = (v) => typeof v === "function";
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<!-- {#if $notifications.length} -->
|
|
255
|
+
<div class={_wrapClass} aria-live="assertive">
|
|
256
|
+
<div class={_wrapInnerClass}>
|
|
257
|
+
{#if $notifications.length}
|
|
258
|
+
{#each $notifications as n}
|
|
259
|
+
{@const iconFn = _iconFn(n)}
|
|
260
|
+
<!-- use your own component -->
|
|
261
|
+
{#if n?.component}
|
|
262
|
+
<svelte:component
|
|
263
|
+
this={n.component.component || n.component}
|
|
264
|
+
{...n.component.props || {}}
|
|
265
|
+
notification={n}
|
|
266
|
+
{notifications}
|
|
267
|
+
/>
|
|
268
|
+
{:else}
|
|
269
|
+
<!-- svelte-ignore
|
|
270
|
+
a11y-click-events-have-key-events
|
|
271
|
+
a11y-no-noninteractive-element-interactions
|
|
272
|
+
a11y-mouse-events-have-key-events -->
|
|
273
|
+
<div
|
|
274
|
+
transition:fade|global={{ duration: 200 }}
|
|
275
|
+
class={_boxClass(n)}
|
|
276
|
+
class:cursor-pointer={typeof n.onClick === 'function'}
|
|
277
|
+
data-notification-type={n.type}
|
|
278
|
+
data-notification-multiple={n.count > 1 ? true : undefined}
|
|
279
|
+
role="alert"
|
|
280
|
+
on:mouseover={() => notifications.event(n.id, notifications.EVENT.MOUSEOVER)}
|
|
281
|
+
on:mouseout={() => notifications.event(n.id, notifications.EVENT.MOUSEOUT)}
|
|
282
|
+
on:click={() => notifications.event(n.id, notifications.EVENT.CLICK)}
|
|
283
|
+
>
|
|
284
|
+
{#if n.count > 1}
|
|
285
|
+
<div class={_countClass(n)}>
|
|
286
|
+
{n.count}
|
|
287
|
+
</div>
|
|
288
|
+
{/if}
|
|
289
|
+
|
|
290
|
+
{#if _isFn(iconFn)}
|
|
291
|
+
<div class={_iconClass(n)}>{@html iconFn()}</div>
|
|
292
|
+
{/if}
|
|
293
|
+
|
|
294
|
+
<div class={_contentClass(n)}>
|
|
295
|
+
<Thc
|
|
296
|
+
thc={n.content}
|
|
297
|
+
forceAsHtml={n.forceAsHtml ?? NotificationsConfig.forceAsHtml}
|
|
298
|
+
notification={n}
|
|
299
|
+
{notifications}
|
|
300
|
+
/>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<button
|
|
304
|
+
class={_buttonClass(n)}
|
|
305
|
+
aria-label={ariaCloseLabel}
|
|
306
|
+
on:click|preventDefault|stopPropagation={() => notifications.remove(n.id)}
|
|
307
|
+
>
|
|
308
|
+
<X class={_xClass(n)} />
|
|
309
|
+
</button>
|
|
310
|
+
</div>
|
|
311
|
+
{/if}
|
|
312
|
+
{/each}
|
|
313
|
+
{/if}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
<!-- {/if} -->
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { NotificationType, createNotificationsStore } from './notifications.js';
|
|
3
|
+
interface NotifClasses {
|
|
4
|
+
box: string;
|
|
5
|
+
count: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
content: string;
|
|
8
|
+
button: string;
|
|
9
|
+
x: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class NotificationsConfig {
|
|
12
|
+
static preset: {
|
|
13
|
+
wrap: string;
|
|
14
|
+
wrapInner: string;
|
|
15
|
+
notification: {
|
|
16
|
+
box: string;
|
|
17
|
+
count: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
content: string;
|
|
20
|
+
button: string;
|
|
21
|
+
x: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
static presetByType: Record<NotificationType, NotifClasses>;
|
|
25
|
+
static class: {
|
|
26
|
+
wrap: string;
|
|
27
|
+
wrapInner: string;
|
|
28
|
+
notification: {
|
|
29
|
+
box: string;
|
|
30
|
+
count: string;
|
|
31
|
+
icon: string;
|
|
32
|
+
content: string;
|
|
33
|
+
button: string;
|
|
34
|
+
x: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
static classByType: Record<NotificationType, NotifClasses>;
|
|
38
|
+
static iconFn: Record<NotificationType, undefined | (() => string)>;
|
|
39
|
+
static forceAsHtml: boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
declare const __propDef: {
|
|
42
|
+
props: {
|
|
43
|
+
notifications: ReturnType<typeof createNotificationsStore>;
|
|
44
|
+
class?: string | undefined;
|
|
45
|
+
ariaCloseLabel?: string | undefined;
|
|
46
|
+
posX?: "center" | "left" | "right" | undefined;
|
|
47
|
+
posXMobile?: "center" | "left" | "right" | undefined;
|
|
48
|
+
posY?: "center" | "top" | "bottom" | undefined;
|
|
49
|
+
posYMobile?: "center" | "top" | "bottom" | undefined;
|
|
50
|
+
};
|
|
51
|
+
events: {
|
|
52
|
+
[evt: string]: CustomEvent<any>;
|
|
53
|
+
};
|
|
54
|
+
slots: {};
|
|
55
|
+
};
|
|
56
|
+
export type NotificationsProps = typeof __propDef.props;
|
|
57
|
+
export type NotificationsEvents = typeof __propDef.events;
|
|
58
|
+
export type NotificationsSlots = typeof __propDef.slots;
|
|
59
|
+
export default class Notifications extends SvelteComponent<NotificationsProps, NotificationsEvents, NotificationsSlots> {
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// taken from @marianmeres/icons-fns
|
|
2
|
+
export function iconFeatherAlertTriangle(props) {
|
|
3
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
4
|
+
if (props === null || props === undefined)
|
|
5
|
+
props = {};
|
|
6
|
+
if (typeof props !== 'object')
|
|
7
|
+
props = { class: props || '' };
|
|
8
|
+
if (arguments.length > 1)
|
|
9
|
+
props.size ??= arguments[1];
|
|
10
|
+
if (arguments.length > 2)
|
|
11
|
+
props.style ??= arguments[2];
|
|
12
|
+
//
|
|
13
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
14
|
+
let attrs = Object.entries(props)
|
|
15
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
16
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
17
|
+
.join(' ');
|
|
18
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>`;
|
|
19
|
+
}
|
|
20
|
+
function iconFeatherAlertCircle(props) {
|
|
21
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
22
|
+
if (props === null || props === undefined)
|
|
23
|
+
props = {};
|
|
24
|
+
if (typeof props !== 'object')
|
|
25
|
+
props = { class: props || '' };
|
|
26
|
+
if (arguments.length > 1)
|
|
27
|
+
props.size ??= arguments[1];
|
|
28
|
+
if (arguments.length > 2)
|
|
29
|
+
props.style ??= arguments[2];
|
|
30
|
+
//
|
|
31
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
32
|
+
let attrs = Object.entries(props)
|
|
33
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
34
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
35
|
+
.join(' ');
|
|
36
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>`;
|
|
37
|
+
}
|
|
38
|
+
function iconFeatherCheckCircle(props) {
|
|
39
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
40
|
+
if (props === null || props === undefined)
|
|
41
|
+
props = {};
|
|
42
|
+
if (typeof props !== 'object')
|
|
43
|
+
props = { class: props || '' };
|
|
44
|
+
if (arguments.length > 1)
|
|
45
|
+
props.size ??= arguments[1];
|
|
46
|
+
if (arguments.length > 2)
|
|
47
|
+
props.style ??= arguments[2];
|
|
48
|
+
//
|
|
49
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
50
|
+
let attrs = Object.entries(props)
|
|
51
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
52
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
53
|
+
.join(' ');
|
|
54
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`;
|
|
55
|
+
}
|
|
56
|
+
function iconFeatherXOctagon(props) {
|
|
57
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
58
|
+
if (props === null || props === undefined)
|
|
59
|
+
props = {};
|
|
60
|
+
if (typeof props !== 'object')
|
|
61
|
+
props = { class: props || '' };
|
|
62
|
+
if (arguments.length > 1)
|
|
63
|
+
props.size ??= arguments[1];
|
|
64
|
+
if (arguments.length > 2)
|
|
65
|
+
props.style ??= arguments[2];
|
|
66
|
+
//
|
|
67
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
68
|
+
let attrs = Object.entries(props)
|
|
69
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
70
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
71
|
+
.join(' ');
|
|
72
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;
|
|
73
|
+
}
|
|
74
|
+
function iconFeatherInfo(props) {
|
|
75
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
76
|
+
if (props === null || props === undefined)
|
|
77
|
+
props = {};
|
|
78
|
+
if (typeof props !== 'object')
|
|
79
|
+
props = { class: props || '' };
|
|
80
|
+
if (arguments.length > 1)
|
|
81
|
+
props.size ??= arguments[1];
|
|
82
|
+
if (arguments.length > 2)
|
|
83
|
+
props.style ??= arguments[2];
|
|
84
|
+
//
|
|
85
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
86
|
+
let attrs = Object.entries(props)
|
|
87
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
88
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
89
|
+
.join(' ');
|
|
90
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>`;
|
|
91
|
+
}
|
|
92
|
+
function iconFeatherRotateCw(props) {
|
|
93
|
+
// Backward compatible signature support: fn(cls, size, style)
|
|
94
|
+
if (props === null || props === undefined)
|
|
95
|
+
props = {};
|
|
96
|
+
if (typeof props !== 'object')
|
|
97
|
+
props = { class: props || '' };
|
|
98
|
+
if (arguments.length > 1)
|
|
99
|
+
props.size ??= arguments[1];
|
|
100
|
+
if (arguments.length > 2)
|
|
101
|
+
props.style ??= arguments[2];
|
|
102
|
+
//
|
|
103
|
+
const { size, class: cls, style, strokeWidth } = props;
|
|
104
|
+
let attrs = Object.entries(props)
|
|
105
|
+
.filter(([k, v]) => !/^class|size|style|strokeWidth$/.test(k))
|
|
106
|
+
.reduce((m, [k, v]) => [...m, `${k}="${v}"`], [])
|
|
107
|
+
.join(' ');
|
|
108
|
+
return `<svg ${style ? `style="${style}" ` : ''}${cls ? `class="${cls}" ` : ''}width="${size || 24}" height="${size || 24}" stroke-width="${strokeWidth ?? 2}" ${attrs ? `${attrs} ` : ''}viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>`;
|
|
109
|
+
}
|
|
110
|
+
export const notificationsDefaultIcons = {
|
|
111
|
+
info: () => iconFeatherInfo({}),
|
|
112
|
+
success: () => iconFeatherCheckCircle({}),
|
|
113
|
+
warn: () => iconFeatherAlertTriangle({}),
|
|
114
|
+
error: () => iconFeatherXOctagon({}),
|
|
115
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { THC } from '../Thc/Thc.svelte';
|
|
2
|
+
export type NotificationsSortOrder = 'asc' | 'desc';
|
|
3
|
+
export type NotificationOnEventHandler = (eventName: string, self: Notification, all: Notification[], data: any) => void;
|
|
4
|
+
export type NotificationType = 'info' | 'success' | 'warn' | 'error' | string;
|
|
5
|
+
interface ComponentWrap {
|
|
6
|
+
component: Function;
|
|
7
|
+
props?: any;
|
|
8
|
+
}
|
|
9
|
+
export interface NotificationInput extends Record<string, any> {
|
|
10
|
+
id: any;
|
|
11
|
+
type: NotificationType;
|
|
12
|
+
content: THC;
|
|
13
|
+
on: NotificationOnEventHandler;
|
|
14
|
+
onClick: (self: Notification, all: Notification[], data: any) => void;
|
|
15
|
+
ttl: number;
|
|
16
|
+
count: number;
|
|
17
|
+
component?: Function | ComponentWrap;
|
|
18
|
+
iconFn: (() => string) | boolean;
|
|
19
|
+
class?: Partial<{
|
|
20
|
+
box: string;
|
|
21
|
+
count: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
content: string;
|
|
24
|
+
button: string;
|
|
25
|
+
x: string;
|
|
26
|
+
}>;
|
|
27
|
+
forceAsHtml: boolean | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface Notification extends NotificationInput {
|
|
30
|
+
created: Date;
|
|
31
|
+
}
|
|
32
|
+
export type NotificationCreateParam = string | Partial<NotificationInput>;
|
|
33
|
+
export interface NotiticationsCreateStoreOptions {
|
|
34
|
+
maxCapacity: number;
|
|
35
|
+
defaultType: string;
|
|
36
|
+
defaultTtl: number;
|
|
37
|
+
sortOrder?: NotificationsSortOrder;
|
|
38
|
+
defaultIcons?: Record<NotificationType, () => string> | boolean;
|
|
39
|
+
logger: (...v: any) => void;
|
|
40
|
+
}
|
|
41
|
+
export declare const NOTIFICATION_EVENT: {
|
|
42
|
+
CLICK: string;
|
|
43
|
+
CREATE: string;
|
|
44
|
+
REMOVE: string;
|
|
45
|
+
AUTO_DISPOSE: string;
|
|
46
|
+
MOUSEOVER: string;
|
|
47
|
+
MOUSEOUT: string;
|
|
48
|
+
};
|
|
49
|
+
export declare const createNotificationsStore: (initial?: NotificationCreateParam[], opts?: Partial<NotiticationsCreateStoreOptions>) => {
|
|
50
|
+
subscribe: (cb: CallableFunction) => () => void;
|
|
51
|
+
get: () => Notification[];
|
|
52
|
+
add: (notif: NotificationCreateParam[] | NotificationCreateParam) => void;
|
|
53
|
+
event: (id: string, eventName: string, data?: any) => boolean;
|
|
54
|
+
find: (id: string) => Notification | null;
|
|
55
|
+
remove: (id: string) => boolean;
|
|
56
|
+
options: {
|
|
57
|
+
maxCapacity?: number | undefined;
|
|
58
|
+
defaultType?: string | undefined;
|
|
59
|
+
defaultTtl?: number | undefined;
|
|
60
|
+
sortOrder?: NotificationsSortOrder | undefined;
|
|
61
|
+
defaultIcons?: boolean | Record<string, () => string> | undefined;
|
|
62
|
+
logger?: ((...v: any) => void) | undefined;
|
|
63
|
+
};
|
|
64
|
+
EVENT: {
|
|
65
|
+
CLICK: string;
|
|
66
|
+
CREATE: string;
|
|
67
|
+
REMOVE: string;
|
|
68
|
+
AUTO_DISPOSE: string;
|
|
69
|
+
MOUSEOVER: string;
|
|
70
|
+
MOUSEOUT: string;
|
|
71
|
+
};
|
|
72
|
+
setMaxCapacity: (v: number) => void;
|
|
73
|
+
setSortOrder: (v: string) => void;
|
|
74
|
+
info: (content: THC, n?: Partial<NotificationInput>) => void;
|
|
75
|
+
success: (content: THC, n?: Partial<NotificationInput>) => void;
|
|
76
|
+
warn: (content: THC, n?: Partial<NotificationInput>) => void;
|
|
77
|
+
error: (content: THC, n?: Partial<NotificationInput>) => void;
|
|
78
|
+
};
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createClog } from '@marianmeres/clog';
|
|
2
|
+
import { createStore } from '@marianmeres/store';
|
|
3
|
+
import { createTicker } from '@marianmeres/ticker';
|
|
4
|
+
const isFn = (v) => typeof v === 'function';
|
|
5
|
+
const DEFAULT_OPTIONS = {
|
|
6
|
+
maxCapacity: 5,
|
|
7
|
+
defaultTtl: 10,
|
|
8
|
+
defaultType: 'info',
|
|
9
|
+
sortOrder: 'asc',
|
|
10
|
+
defaultIcons: true,
|
|
11
|
+
logger: createClog('notifications'),
|
|
12
|
+
};
|
|
13
|
+
export const NOTIFICATION_EVENT = {
|
|
14
|
+
CLICK: 'click',
|
|
15
|
+
CREATE: 'create',
|
|
16
|
+
// `remove` programatically, or e.g. by clicking on X
|
|
17
|
+
REMOVE: 'remove',
|
|
18
|
+
// triggered when auto disposed by ttl expiration
|
|
19
|
+
AUTO_DISPOSE: 'auto_dispose',
|
|
20
|
+
// usefull for detecting interacion (so internally may notify as "seen")
|
|
21
|
+
MOUSEOVER: 'mouseover',
|
|
22
|
+
MOUSEOUT: 'mouseout',
|
|
23
|
+
};
|
|
24
|
+
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
|
|
25
|
+
const _strHash = (str) => `${str || ''}`.split('').reduce((a, b) => {
|
|
26
|
+
a = (a << 5) - a + b.charCodeAt(0);
|
|
27
|
+
return a & a;
|
|
28
|
+
}, 0);
|
|
29
|
+
const _id = (type, content) => {
|
|
30
|
+
const str = content?.component
|
|
31
|
+
? 'component'
|
|
32
|
+
: content?.html || content?.text || content;
|
|
33
|
+
return ['id', type, _strHash(str)].join('-');
|
|
34
|
+
};
|
|
35
|
+
export const createNotificationsStore = (initial = [], opts = {}) => {
|
|
36
|
+
if (!Array.isArray(initial))
|
|
37
|
+
initial = [initial];
|
|
38
|
+
// merge provided with defaults
|
|
39
|
+
opts = { ...DEFAULT_OPTIONS, ...(opts || {}) };
|
|
40
|
+
const _log = (...args) => isFn(opts.logger) && opts.logger.apply(null, [...args]);
|
|
41
|
+
const _setOption = (k, v) => {
|
|
42
|
+
// _log(`INFO: setting option '${k} = ${v}'`);
|
|
43
|
+
if (/^maxCapacity|defaultTtl$/.test(k)) {
|
|
44
|
+
v = parseInt(v, 10);
|
|
45
|
+
if (isNaN(v) || v < 0) {
|
|
46
|
+
_log(`WARN: invalid '${k}' option, falling back to default`);
|
|
47
|
+
opts[k] = DEFAULT_OPTIONS[k];
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
opts[k] = v;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
opts[k] = v;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// sanitize options
|
|
58
|
+
['maxCapacity', 'defaultTtl'].forEach((k) => _setOption(k, opts[k]));
|
|
59
|
+
const _factory = (notification) => {
|
|
60
|
+
let notif = typeof notification === 'string' ? { id: 0, content: notification } : notification;
|
|
61
|
+
// ignore invalid (empty) notifs
|
|
62
|
+
if (!notif.content) {
|
|
63
|
+
_log(`WARN: ignoring empty notification`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
notif.type ||= opts.defaultType; //
|
|
67
|
+
notif.id ||= _id(notif.type, [notif.content].join());
|
|
68
|
+
notif.created = new Date(notif.created || Date.now());
|
|
69
|
+
notif.count ??= 1;
|
|
70
|
+
//
|
|
71
|
+
if (notif.ttl === undefined)
|
|
72
|
+
notif.ttl = opts.defaultTtl;
|
|
73
|
+
//
|
|
74
|
+
if (notif.iconFn === undefined) {
|
|
75
|
+
if (typeof opts.defaultIcons === 'boolean') {
|
|
76
|
+
notif.iconFn = opts.defaultIcons;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
notif.iconFn = opts.defaultIcons?.[notif.type];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return notif;
|
|
83
|
+
};
|
|
84
|
+
const _findIndexById = (notifs, id) => notifs.findIndex((n) => n.id === id);
|
|
85
|
+
const _removeByIdx = (notifs, idx) => {
|
|
86
|
+
if (idx > -1) {
|
|
87
|
+
notifs.splice(idx, 1);
|
|
88
|
+
notifs = [...notifs];
|
|
89
|
+
}
|
|
90
|
+
return notifs;
|
|
91
|
+
};
|
|
92
|
+
const _add = (notifs, notification, onAddHook = null) => {
|
|
93
|
+
const notif = _factory(notification);
|
|
94
|
+
// return early on invalid
|
|
95
|
+
if (!notif)
|
|
96
|
+
return notifs;
|
|
97
|
+
const _isDesc = opts.sortOrder === 'desc';
|
|
98
|
+
const idx = _findIndexById(notifs, notif.id);
|
|
99
|
+
if (idx > -1) {
|
|
100
|
+
notifs[idx].count++;
|
|
101
|
+
notifs[idx].created = new Date(Math.max(notifs[idx].created.valueOf(), notif.created.valueOf()));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
notifs.push(notif);
|
|
105
|
+
notifs.sort((a, b) => {
|
|
106
|
+
let _a = a.created.valueOf();
|
|
107
|
+
let _b = b.created.valueOf();
|
|
108
|
+
return _isDesc ? _b - _a : _a - _b;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (isFn(onAddHook))
|
|
112
|
+
onAddHook?.(notif);
|
|
113
|
+
// keep only `maxCapacity` in the queue
|
|
114
|
+
if (opts.maxCapacity && notifs.length > opts.maxCapacity) {
|
|
115
|
+
notifs = _isDesc
|
|
116
|
+
? notifs.slice(0, opts.maxCapacity)
|
|
117
|
+
: notifs.slice(-1 * opts.maxCapacity);
|
|
118
|
+
}
|
|
119
|
+
return [...notifs];
|
|
120
|
+
};
|
|
121
|
+
// main internal store
|
|
122
|
+
let notifs = [];
|
|
123
|
+
initial.forEach((n) => (notifs = _add(notifs, n)));
|
|
124
|
+
const _store = createStore(notifs);
|
|
125
|
+
// auto dispose feature
|
|
126
|
+
const ticker = createTicker(1_000);
|
|
127
|
+
const _tickerInit = () => {
|
|
128
|
+
const _tickerUnsub = ticker.start().subscribe((ts) => {
|
|
129
|
+
if (ts) {
|
|
130
|
+
const { disposed, kept } = _store.get().reduce((memo, n) => {
|
|
131
|
+
if (n.ttl) {
|
|
132
|
+
const expiry = n.created.valueOf() + n.ttl * 1000;
|
|
133
|
+
expiry >= Date.now() ? memo.kept.push(n) : memo.disposed.push(n);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
memo.kept.push(n);
|
|
137
|
+
}
|
|
138
|
+
return memo;
|
|
139
|
+
}, { disposed: [], kept: [] });
|
|
140
|
+
if (disposed.length) {
|
|
141
|
+
disposed.forEach((n) => event(n.id, NOTIFICATION_EVENT.AUTO_DISPOSE));
|
|
142
|
+
_store.set(kept);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return () => {
|
|
147
|
+
ticker.stop();
|
|
148
|
+
_tickerUnsub();
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
//
|
|
152
|
+
const findById = (id) => {
|
|
153
|
+
const notifs = _store.get();
|
|
154
|
+
const idx = _findIndexById(notifs, id);
|
|
155
|
+
return idx > -1 ? notifs[idx] : null;
|
|
156
|
+
};
|
|
157
|
+
const event = (id, eventName, data = null) => {
|
|
158
|
+
const n = findById(id);
|
|
159
|
+
if (n) {
|
|
160
|
+
if (isFn(n.on)) {
|
|
161
|
+
n.on(eventName, n, _store.get(), data);
|
|
162
|
+
}
|
|
163
|
+
if (eventName === NOTIFICATION_EVENT.CLICK && isFn(n.onClick)) {
|
|
164
|
+
n.onClick(n, _store.get(), data);
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
};
|
|
170
|
+
// we need to keep track of subscriptions count, so we can do the cleanup
|
|
171
|
+
let _subsCount = 0;
|
|
172
|
+
let _tickerDestroy;
|
|
173
|
+
const subscribe = (cb) => {
|
|
174
|
+
if (!_subsCount++)
|
|
175
|
+
_tickerDestroy = _tickerInit();
|
|
176
|
+
const unsub = _store.subscribe(cb);
|
|
177
|
+
return () => {
|
|
178
|
+
if (!--_subsCount)
|
|
179
|
+
_tickerDestroy();
|
|
180
|
+
unsub();
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
const add = (notif) => {
|
|
184
|
+
if (!Array.isArray(notif))
|
|
185
|
+
notif = [notif];
|
|
186
|
+
let notifs = _store.get();
|
|
187
|
+
notif.forEach((n) => (notifs = _add(notifs, n, (_n) => event(_n.id, NOTIFICATION_EVENT.CREATE))));
|
|
188
|
+
_store.set(notifs);
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
subscribe,
|
|
192
|
+
//
|
|
193
|
+
get: () => _store.get(),
|
|
194
|
+
//
|
|
195
|
+
add,
|
|
196
|
+
//
|
|
197
|
+
event,
|
|
198
|
+
//
|
|
199
|
+
find: findById,
|
|
200
|
+
//
|
|
201
|
+
remove: (id) => {
|
|
202
|
+
let notifs = _store.get();
|
|
203
|
+
const idx = _findIndexById(notifs, id);
|
|
204
|
+
if (idx > -1) {
|
|
205
|
+
const notif = notifs[idx];
|
|
206
|
+
event(id, NOTIFICATION_EVENT.REMOVE);
|
|
207
|
+
_store.set(_removeByIdx(notifs, idx));
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
},
|
|
212
|
+
//
|
|
213
|
+
options: { ...opts },
|
|
214
|
+
//
|
|
215
|
+
EVENT: NOTIFICATION_EVENT,
|
|
216
|
+
// some options setters (for playground mostly)
|
|
217
|
+
setMaxCapacity: (v) => _setOption('maxCapacity', v),
|
|
218
|
+
setSortOrder: (v) => _setOption('sortOrder', v),
|
|
219
|
+
// sugar
|
|
220
|
+
info: (content, n) => add({ ...(n || {}), type: 'info', content }),
|
|
221
|
+
success: (content, n) => add({ ...(n || {}), type: 'success', content }),
|
|
222
|
+
warn: (content, n) => add({ ...(n || {}), type: 'warn', content }),
|
|
223
|
+
error: (content, n) => add({ ...(n || {}), type: 'error', content }),
|
|
224
|
+
};
|
|
225
|
+
};
|
|
@@ -11,8 +11,8 @@ export class SwitchConfig {
|
|
|
11
11
|
hover:brightness-[1.05] active:brightness-[0.95]
|
|
12
12
|
disabled:!cursor-not-allowed disabled:!opacity-50 disabled:hover:brightness-100
|
|
13
13
|
|
|
14
|
-
bg-
|
|
15
|
-
data-[checked=true]:bg-
|
|
14
|
+
bg-gray-400 dark:bg-gray-400
|
|
15
|
+
data-[checked=true]:bg-stuic-primary dark:data-[checked=true]:bg-stuic-primary
|
|
16
16
|
`.trim();
|
|
17
17
|
static presetsSize = {
|
|
18
18
|
xs: "h-4 w-7",
|
|
@@ -30,8 +30,8 @@ export class SwitchConfig {
|
|
|
30
30
|
translate-x-1 rounded-full
|
|
31
31
|
transition-all duration-100
|
|
32
32
|
shadow
|
|
33
|
-
bg-white dark:bg-
|
|
34
|
-
text-black dark:text-
|
|
33
|
+
bg-white dark:bg-white
|
|
34
|
+
text-black dark:text-black
|
|
35
35
|
`.trim();
|
|
36
36
|
static presetsSizeDot = {
|
|
37
37
|
// size + translate-x = width
|
|
@@ -11,7 +11,7 @@ export let thc;
|
|
|
11
11
|
{:else if thc?.html}
|
|
12
12
|
{@html thc.html}
|
|
13
13
|
{:else if thc?.component}
|
|
14
|
-
<svelte:component this={thc.component} {...thc?.props || {}} />
|
|
14
|
+
<svelte:component this={thc.component} {...thc?.props || {}} {...$$restProps || {}} />
|
|
15
15
|
{:else}
|
|
16
16
|
<!-- cast to string as the last resort -->
|
|
17
17
|
{thc}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
<script>import { twMerge } from "tailwind-merge";
|
|
2
2
|
let _class = "";
|
|
3
3
|
export { _class as class };
|
|
4
|
-
export let
|
|
4
|
+
export let strokeWidth = 2;
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<svg
|
|
8
8
|
fill="none"
|
|
9
9
|
viewBox="0 0 24 24"
|
|
10
|
-
stroke-width=
|
|
10
|
+
stroke-width={strokeWidth}
|
|
11
11
|
stroke="currentColor"
|
|
12
|
-
|
|
13
|
-
class={twMerge(`inline ${_class}`)}
|
|
12
|
+
class={twMerge(`inline size-6 ${_class}`)}
|
|
14
13
|
>
|
|
15
14
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
|
16
15
|
</svg>
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { default as FieldCheckbox } from './components/Input/FieldCheckbox.svelt
|
|
|
13
13
|
export { default as FieldRadios } from './components/Input/FieldRadios.svelte';
|
|
14
14
|
export { default as FieldSelect } from './components/Input/FieldSelect.svelte';
|
|
15
15
|
export { default as Fieldset } from './components/Input/Fieldset.svelte';
|
|
16
|
+
export { createNotificationsStore, NOTIFICATION_EVENT, type NotiticationsCreateStoreOptions, type NotificationCreateParam, type Notification, type NotificationInput, type NotificationType, type NotificationOnEventHandler, type NotificationsSortOrder, } from './components/Notifications/notifications.js';
|
|
17
|
+
export { default as Notifications } from './components/Notifications/Notifications.svelte';
|
|
16
18
|
export { default as Popover } from './components/Popover/Popover.svelte';
|
|
17
19
|
export { default as Switch, SwitchConfig } from './components/Switch/Switch.svelte';
|
|
18
20
|
export { default as Thc } from './components/Thc/Thc.svelte';
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,9 @@ export { default as FieldRadios } from './components/Input/FieldRadios.svelte';
|
|
|
23
23
|
export { default as FieldSelect } from './components/Input/FieldSelect.svelte';
|
|
24
24
|
export { default as Fieldset } from './components/Input/Fieldset.svelte';
|
|
25
25
|
//
|
|
26
|
+
export { createNotificationsStore, NOTIFICATION_EVENT, } from './components/Notifications/notifications.js';
|
|
27
|
+
export { default as Notifications } from './components/Notifications/Notifications.svelte';
|
|
28
|
+
//
|
|
26
29
|
export { default as Popover } from './components/Popover/Popover.svelte';
|
|
27
30
|
//
|
|
28
31
|
export { default as Switch, SwitchConfig } from './components/Switch/Switch.svelte';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/stuic",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run package && node ./scripts/date.js",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"@marianmeres/clog": "^1.0.1",
|
|
66
66
|
"@marianmeres/store": "^1.5.0",
|
|
67
67
|
"@marianmeres/switch-store": "^1.3.1",
|
|
68
|
+
"@marianmeres/ticker": "^1.5.0",
|
|
68
69
|
"esm-env": "^1.0.0",
|
|
69
70
|
"tailwind-merge": "^2.1.0"
|
|
70
71
|
}
|