@samline/notify 0.1.0 → 0.1.6

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.
@@ -1,132 +0,0 @@
1
- import { sileo, ToastItem } from '../core/index';
2
- import { animate } from 'motion';
3
-
4
- const POSITIONS = [
5
- 'top-left',
6
- 'top-center',
7
- 'top-right',
8
- 'bottom-left',
9
- 'bottom-center',
10
- 'bottom-right'
11
- ] as const;
12
-
13
- export type Position = typeof POSITIONS[number];
14
-
15
- function createContainer(position: string, root: HTMLElement) {
16
- const c = document.createElement('div');
17
- c.className = 'sileo-toaster';
18
- c.setAttribute('data-position', position);
19
- c.setAttribute('role', 'status');
20
- c.setAttribute('aria-live', 'polite');
21
- return c;
22
- }
23
-
24
- function renderToast(item: ToastItem) {
25
- const el = document.createElement('div');
26
- el.className = 'sileo-toast';
27
- el.dataset.id = item.id;
28
- el.setAttribute('data-type', item.options.type || 'info');
29
- el.style.opacity = '0';
30
- el.style.transform = 'translateY(-6px)';
31
-
32
- const body = document.createElement('div');
33
- body.style.display = 'flex';
34
- body.style.flexDirection = 'column';
35
- body.style.flex = '1';
36
-
37
- const header = document.createElement('div');
38
- header.className = 'sileo-toast-header';
39
- header.textContent = item.options.title || '';
40
-
41
- body.appendChild(header);
42
-
43
- if (item.options.description) {
44
- const desc = document.createElement('div');
45
- desc.className = 'sileo-toast-desc';
46
- desc.textContent = item.options.description;
47
- body.appendChild(desc);
48
- }
49
-
50
- el.appendChild(body);
51
-
52
- if (item.options.button) {
53
- const btn = document.createElement('button');
54
- btn.className = 'sileo-toast-btn';
55
- btn.textContent = item.options.button.title;
56
- btn.addEventListener('click', (e) => {
57
- e.stopPropagation();
58
- try {
59
- if (item.options.button && typeof item.options.button.onClick === 'function') {
60
- item.options.button.onClick();
61
- }
62
- } catch (err) { console.error(err); }
63
- });
64
- el.appendChild(btn);
65
- }
66
-
67
- const close = document.createElement('button');
68
- close.className = 'sileo-toast-close';
69
- close.innerHTML = '×';
70
- close.addEventListener('click', () => sileo.dismiss(item.id));
71
- el.appendChild(close);
72
-
73
- if (typeof item.options.duration === 'number') {
74
- if (item.options.duration > 0) {
75
- setTimeout(() => sileo.dismiss(item.id), item.options.duration);
76
- }
77
- }
78
-
79
- return el;
80
- }
81
-
82
- export function initToasters(root: HTMLElement = document.body, positions: Position[] = ['top-right']) {
83
- const containers: Record<string, HTMLElement> = {};
84
-
85
- positions.forEach((pos) => {
86
- const c = createContainer(pos, root);
87
- root.appendChild(c);
88
- containers[pos] = c;
89
- });
90
-
91
- function rerender(items: ToastItem[]) {
92
- positions.forEach((pos) => {
93
- const container = containers[pos];
94
- const visible = items.filter((t) => (t.options.position || 'top-right') === pos);
95
-
96
- // Diff existing children and animate out removed toasts
97
- const visibleIds = new Set(visible.map((v) => v.id));
98
- const existing = Array.from(container.children) as HTMLElement[];
99
-
100
- existing.forEach((child) => {
101
- const id = child.dataset.id;
102
- if (!id || !visibleIds.has(id)) {
103
- // animate out then remove
104
- animate(child, { opacity: 0, y: -8 }, { duration: 0.18 }).finished.then(() => child.remove());
105
- }
106
- });
107
-
108
- // Add new items
109
- visible.forEach((t) => {
110
- if (!container.querySelector(`[data-id="${t.id}"]`)) {
111
- const node = renderToast(t);
112
- container.appendChild(node);
113
- // animate in
114
- requestAnimationFrame(() => {
115
- animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
116
- });
117
- }
118
- });
119
- });
120
- }
121
-
122
- const unsub = sileo.subscribe(rerender);
123
-
124
- return {
125
- destroy() {
126
- unsub();
127
- Object.values(containers).forEach((c) => c.remove());
128
- }
129
- };
130
- }
131
-
132
- export { POSITIONS };
package/src/vue/index.ts DELETED
@@ -1,61 +0,0 @@
1
- import { App, defineComponent, h, onMounted, ref } from 'vue';
2
- import { notify, sileo } from '../core/index';
3
- import type { ToastItem } from '../core/index';
4
- import { animate } from 'motion';
5
-
6
- export const Toaster = defineComponent({
7
- props: { position: { type: String, default: 'top-right' } },
8
- setup(props) {
9
- const toasts = ref<ToastItem[]>([]);
10
- let unsub: (() => void) | null = null;
11
- onMounted(() => {
12
- unsub = sileo.subscribe((items) => { toasts.value = items.filter((t) => (t.options.position || 'top-right') === props.position); });
13
- });
14
- const handleDismiss = async (id: string) => {
15
- const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
16
- if (el) {
17
- try {
18
- const a = animate(el, { opacity: [1, 0], transform: ['translateY(0px)', 'translateY(-8px)'] }, { duration: 0.15 });
19
- await (a as any).finished;
20
- } catch (e) { /* ignore */ }
21
- }
22
- sileo.dismiss(id);
23
- };
24
- // animate in newly added toasts after render
25
- const lastIds = ref<string[]>([]);
26
- import('vue').then(({ nextTick }) => {
27
- // watch toasts and animate new ones
28
- const stop = (require('vue') as any).watch(toasts, async () => {
29
- await nextTick();
30
- const ids = toasts.value.map(t => t.id);
31
- const added = ids.filter(id => !lastIds.value.includes(id));
32
- added.forEach(id => {
33
- const el = document.querySelector(`[data-id="${id}"]`) as HTMLElement | null;
34
- if (el) {
35
- el.style.opacity = '0';
36
- el.style.transform = 'translateY(-6px)';
37
- try { animate(el, { opacity: [0,1], transform: ['translateY(-6px)','translateY(0px)'] }, { duration: 0.22 }); } catch (e) {}
38
- }
39
- });
40
- lastIds.value = ids;
41
- }, { deep: true });
42
- }).catch(() => {});
43
- return () => h('div', { class: 'sileo-toaster', 'data-position': props.position }, toasts.value.map((t) => h('div', { key: t.id, class: 'sileo-toast', 'data-type': t.options.type }, [
44
- h('div', { style: { flex: '1' } }, [
45
- h('div', { class: 'sileo-toast-header' }, t.options.title),
46
- t.options.description ? h('div', { class: 'sileo-toast-desc' }, t.options.description) : null
47
- ]),
48
- t.options.button ? h('button', { class: 'sileo-toast-btn', onClick: (e: Event) => { e.stopPropagation(); t.options.button!.onClick(); handleDismiss(t.id); } }, t.options.button.title) : null,
49
- h('button', { class: 'sileo-toast-close', onClick: () => handleDismiss(t.id) }, '×')
50
- ])));
51
- }
52
- });
53
-
54
- export default {
55
- install(app: App) {
56
- app.config.globalProperties.$sileo = sileo;
57
- // expose notify as well on the app for convenience
58
- (app.config.globalProperties as any).$notify = notify;
59
- app.component('SileoToaster', Toaster as any);
60
- }
61
- };
@@ -1,10 +0,0 @@
1
- import { existsSync } from 'fs';
2
- import { describe, it, expect } from 'vitest';
3
- import { resolve } from 'path';
4
-
5
- describe('no-bundler UMD bundle', () => {
6
- it('dist/notify.umd.js should exist', () => {
7
- const p = resolve(__dirname, '../../dist/notify.umd.js');
8
- expect(existsSync(p)).toBe(true);
9
- });
10
- });
@@ -1,12 +0,0 @@
1
- import { readFileSync, existsSync } from 'fs';
2
- import { describe, it, expect } from 'vitest';
3
- import { resolve } from 'path';
4
-
5
- describe('svelte toaster smoke', () => {
6
- it('src/svelte/Toaster.svelte contains use:animateIn or motion import', () => {
7
- const p = resolve(__dirname, '../../src/svelte/Toaster.svelte');
8
- expect(existsSync(p)).toBe(true);
9
- const txt = readFileSync(p, 'utf-8');
10
- expect(/use:animateIn|from\s+['"]motion['"]/.test(txt)).toBe(true);
11
- });
12
- });
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2019",
4
- "module": "ESNext",
5
- "declaration": true,
6
- "outDir": "dist",
7
- "strict": true,
8
- "moduleResolution": "Node",
9
- "jsx": "react-jsx",
10
- "jsxImportSource": "react",
11
- "esModuleInterop": true,
12
- "skipLibCheck": true,
13
- "forceConsistentCasingInFileNames": true
14
- },
15
- "include": ["src/**/*"]
16
- }
File without changes