@dennykuo/glass-toast 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +162 -2
- package/dist/auto.d.ts +1 -0
- package/dist/auto.js +5 -0
- package/dist/glass-toast.css +1 -0
- package/dist/glass-toast.js +2 -0
- package/dist/glass-toast.umd.cjs +1 -0
- package/dist/icons.d.ts +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/toast-Bh3vjjkJ.js +237 -0
- package/dist/toast.d.ts +90 -0
- package/package.json +75 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,163 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dennykuo/glass-toast
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
毛玻璃質感的 toast 通知元件 — vanilla JS、零 runtime 相依、不需要 Tailwind 或任何框架,開箱即用。
|
|
4
|
+
|
|
5
|
+
- 4 種變體(success / error / warning / info):彩色圖示藥丸 + 進場 glow ring 脈衝 + 倒數進度條
|
|
6
|
+
- 3 個位置(top-right / bottom-right / bottom-center),各自獨立堆疊容器
|
|
7
|
+
- 毛玻璃外殼(backdrop-filter + 多層陰影),dismiss 時堆疊平滑坍縮回流
|
|
8
|
+
- hover **與鍵盤焦點**皆暫停自動消失(含進度條同步暫停);`Esc` 關閉聚焦中的 toast、關閉後焦點交還觸發者(WCAG 2.2.1)
|
|
9
|
+
- 內建 dark 外觀(**深色毛玻璃 + 淺字**,保留玻璃身分、避免夜間眩光),自動命中 `prefers-color-scheme` / `[data-theme="dark"]` / `.dark` 三種慣例
|
|
10
|
+
- `prefers-reduced-motion` 完整降級(純淡入、無位移、無 glow;倒數進度條保留,仍提供時間指示)
|
|
11
|
+
- a11y:`role="status"`(一般)/ `role="alert"`(error)+ 對應 `aria-live`(error 為 assertive,其餘 polite),圖示對藥丸底達 ≥3:1 對比
|
|
12
|
+
- 內嵌 `action` 按鈕(如「重試」);error 未指定時停留較久(10s),同位置最多同時 4 則、超量自動收合最舊
|
|
13
|
+
- SPA 友善:容器與樣式在 body/head 被替換後自動重建(Astro ViewTransitions 實測)
|
|
14
|
+
|
|
15
|
+
## 安裝
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @dennykuo/glass-toast
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 快速開始
|
|
22
|
+
|
|
23
|
+
### 1. side-effect 入口(最省事)
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import '@dennykuo/glass-toast/auto';
|
|
27
|
+
|
|
28
|
+
// 樣式已注入、window.toast 已掛載
|
|
29
|
+
toast.success('儲存成功');
|
|
30
|
+
toast.error('連線失敗', { position: 'bottom-center' });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. 具名 import(可 tree-shake、不碰 window)
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { success, show } from '@dennykuo/glass-toast';
|
|
37
|
+
|
|
38
|
+
success('儲存成功'); // 首次使用時自動注入樣式
|
|
39
|
+
show('自訂', { type: 'warning', duration: 8000 });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
搭配顯式樣式(正規做法,CSP / SSR 友善):
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import '@dennykuo/glass-toast/style.css';
|
|
46
|
+
import { register, success } from '@dennykuo/glass-toast';
|
|
47
|
+
|
|
48
|
+
register({ global: false, injectStyles: false }); // 停用自動注入
|
|
49
|
+
success('儲存成功');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. `<script>` 直接引入(UMD,全域 `GlassToast`)
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<script src="https://unpkg.com/@dennykuo/glass-toast"></script>
|
|
56
|
+
<script>
|
|
57
|
+
GlassToast.register(); // 掛 window.toast + 注入樣式
|
|
58
|
+
toast.info('哈囉');
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `show(message, options?) => dismiss`
|
|
65
|
+
|
|
66
|
+
回傳手動關閉函式。快捷方法 `success` / `error` / `warning` / `info` 同簽名(省略 `type`)。
|
|
67
|
+
|
|
68
|
+
| option | 型別 | 預設 | 說明 |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `type` | `'success' \| 'error' \| 'warning' \| 'info'` | `'info'` | 變體 |
|
|
71
|
+
| `position` | `'top-right' \| 'bottom-right' \| 'bottom-center'` | `'top-right'` | 顯示位置 |
|
|
72
|
+
| `persistent` | `boolean` | `false` | `true` 時不自動消失、無進度條 |
|
|
73
|
+
| `duration` | `number` | `error` 為 `10000`、其餘 `5500` | 自動消失毫秒數;`≤ 0` 視為 `persistent` |
|
|
74
|
+
| `action` | `{ label, onClick }` | — | 內嵌操作按鈕;點擊執行 `onClick` 後自動關閉。未顯式給 `duration` 時預設 `persistent`(需使用者操作) |
|
|
75
|
+
|
|
76
|
+
訊息走 `textContent`,不解析 HTML,塞什麼字串都安全(`action.label` 亦然)。
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import { error } from '@dennykuo/glass-toast';
|
|
80
|
+
|
|
81
|
+
error('上傳失敗,是否重試?', {
|
|
82
|
+
action: { label: '重試', onClick: () => retryUpload() },
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `dismissAll(options?)`
|
|
87
|
+
|
|
88
|
+
清空所有 toast。**預設只清空、不影響後續 `show()`**。若要鎖住後續 toast(供「session 過期 → 硬導航登入頁」情境,避免 redirect 與 unload 之間閃現新 toast)傳 `{ suppress: true }`;整頁重載後自動解鎖,SPA 不重載時用 `resume()` 解鎖:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
dismissAll(); // 只清空(預設)
|
|
92
|
+
dismissAll({ suppress: true }); // 清空並鎖住後續 show()(硬導航前)
|
|
93
|
+
resume(); // SPA 不重載時手動解鎖
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `register(options?) => api`
|
|
97
|
+
|
|
98
|
+
掛載全域 + 注入樣式(`/auto` 入口等價於無參數呼叫本函式)。
|
|
99
|
+
|
|
100
|
+
| option | 預設 | 說明 |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `global` | `true` | 掛 `target.toast` |
|
|
103
|
+
| `injectStyles` | `true` | `false` 時全面停用自動樣式注入 |
|
|
104
|
+
| `target` | `window` | 全域掛載目標 |
|
|
105
|
+
|
|
106
|
+
### `ensureStyles()`
|
|
107
|
+
|
|
108
|
+
冪等注入樣式(`show()` 每次都會 cheap re-check,SPA 換頁把 `<head>` 注入的樣式拔掉也會自我修復)。SSR(無 `document`)為 no-op。
|
|
109
|
+
|
|
110
|
+
## 樣式客製(CSS 變數)
|
|
111
|
+
|
|
112
|
+
所有顏色走 `--gt-*` custom properties,scope 在 `.glass-toast` 上、不污染 `:root`。在你的 stylesheet 覆蓋即可:
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
.glass-toast { --gt-text: #333; --gt-radius: 0.5rem; }
|
|
116
|
+
.glass-toast--success { --gt-icon: #0a7d4f; --gt-ring: 10, 125, 79; }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| 變數 | 預設(light) | 說明 |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `--gt-bg` | `rgba(255,255,255,.8)` | 外殼底色 |
|
|
122
|
+
| `--gt-bg-opaque` | `rgba(255,255,255,.96)` | backdrop-filter 不支援時的後備底色 |
|
|
123
|
+
| `--gt-border` | `rgba(229,231,235,.4)` | 外殼邊線 |
|
|
124
|
+
| `--gt-text` | `#4a5565` | 訊息文字 |
|
|
125
|
+
| `--gt-blur` | `24px` | 毛玻璃模糊半徑 |
|
|
126
|
+
| `--gt-radius` | `1rem` | 外殼圓角 |
|
|
127
|
+
| `--gt-shadow` | 多層陰影 | 外殼陰影(glow ring 疊加其上) |
|
|
128
|
+
| `--gt-close-*` | gray 系 | 關閉鈕 bg / border / text(含 `-hover`) |
|
|
129
|
+
| `--gt-focus-ring` | `#2563eb`(dark `#93c5fd`) | 關閉鈕 / action 鈕 focus-visible ring |
|
|
130
|
+
| `--gt-action-*` | 承變體色 | action 鈕 bg / border / text(含 `-bg-hover`);預設透明底 + 變體色字 |
|
|
131
|
+
| `--gt-pill-bg` / `--gt-icon` / `--gt-bar` | 依變體 | 圖示藥丸底 / 圖示色 / 進度條色 |
|
|
132
|
+
| `--gt-ring` | 依變體 | glow ring 的 `R, G, B` 三元組(餵給 `el.animate()`) |
|
|
133
|
+
|
|
134
|
+
### Dark 外觀
|
|
135
|
+
|
|
136
|
+
dark 為**深色毛玻璃**(低亮度半透明底 `rgba(28,28,30,.72)` + 淺字),保留玻璃身分、避免夜間白卡眩光;變體藥丸/圖示沿用 base(淺色亮片 + 700 階圖示在深底上仍清晰),只覆寫外殼、關閉鈕與 focus ring。
|
|
137
|
+
|
|
138
|
+
以下三種慣例任一成立即套用(頁面明示 `data-theme="light"` / `.light` 時 OS 深色讓路):
|
|
139
|
+
|
|
140
|
+
1. `@media (prefers-color-scheme: dark)`
|
|
141
|
+
2. `<html data-theme="dark">`
|
|
142
|
+
3. `<html class="dark">`(Tailwind 慣例)
|
|
143
|
+
|
|
144
|
+
dark 覆寫全部走 `:where()` 壓成與 base 相同的 specificity,你的覆蓋規則永遠不需要跟套件比權重。
|
|
145
|
+
|
|
146
|
+
## CSP / SSR 註記
|
|
147
|
+
|
|
148
|
+
- 嚴格 CSP(禁 inline `<style>`):改顯式 `import '@dennykuo/glass-toast/style.css'` + `register({ injectStyles: false })`
|
|
149
|
+
- 已自行載入 style.css 且想避免重複注入:在你的 `<link>` / `<style>` 加 `data-glass-toast` 屬性即可被偵測跳過
|
|
150
|
+
- SSR:所有入口在無 `document` 環境安全 no-op,`show()` 請在 client 端呼叫
|
|
151
|
+
|
|
152
|
+
## 開發
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm run dev # vite demo(互動 playground:四變體 × 三位置 × 深淺外觀 × 三種背景底圖)
|
|
156
|
+
npm test # vitest + jsdom(58 tests)
|
|
157
|
+
npm run lint # eslint
|
|
158
|
+
npm run build # dist/:ESM + UMD + CSS + .d.ts
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT © Denny
|
package/dist/auto.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./toast.js";
|
package/dist/auto.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c } from "./toast-Bh3vjjkJ.js";
|
|
2
|
+
//#region src/auto.js
|
|
3
|
+
e();
|
|
4
|
+
//#endregion
|
|
5
|
+
export { c as dismissAll, i as ensureStyles, o as error, n as info, e as register, a as resume, s as show, t as success, r as warning };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.glass-toast-container,.glass-toast,.glass-toast *{box-sizing:border-box}.glass-toast-container{z-index:2147483647;pointer-events:none;flex-direction:column;gap:.75rem;display:flex;position:fixed}.glass-toast-container--top-right{align-items:flex-end;top:1rem;right:1rem}.glass-toast-container--bottom-right{align-items:flex-end;bottom:1rem;right:1rem}.glass-toast-container--bottom-center{align-items:center;bottom:1rem;left:50%;transform:translate(-50%)}.glass-toast{--gt-bg:#fffc;--gt-bg-opaque:#fffffff5;--gt-border:#e5e7eb66;--gt-text:#4a5565;--gt-blur:24px;--gt-radius:1rem;--gt-shadow:0 4px 24px -4px #00000014, 0 1px 2px #0000000a;--gt-close-bg:#f9fafb;--gt-close-border:#e5e7eb;--gt-close-text:#99a1af;--gt-close-bg-hover:#f3f4f6;--gt-close-border-hover:#d1d5dc;--gt-close-text-hover:#4a5565;--gt-focus-ring:#2563eb;pointer-events:auto;background-color:var(--gt-bg);width:24rem;max-width:calc(100vw - 2rem);backdrop-filter:blur(var(--gt-blur));border:1px solid var(--gt-border);border-radius:var(--gt-radius);box-shadow:var(--gt-shadow);align-items:center;gap:.75rem;padding:.875rem 1rem;display:flex;position:relative;overflow:hidden}@supports not (backdrop-filter:blur(1px)){.glass-toast{background-color:var(--gt-bg-opaque)}}.glass-toast--success{--gt-pill-bg:#d0fae5;--gt-icon:#047857;--gt-bar:#00bb7f;--gt-ring:16, 185, 129}.glass-toast--error{--gt-pill-bg:#ffe2e2;--gt-icon:#dc2626;--gt-bar:#ff6568;--gt-ring:239, 68, 68}.glass-toast--warning{--gt-pill-bg:#fef3c6;--gt-icon:#b45309;--gt-bar:#fcbb00;--gt-ring:245, 158, 11}.glass-toast--info{--gt-pill-bg:#e2eaf1;--gt-icon:#3a5b7a;--gt-bar:#6f92ad;--gt-ring:76, 112, 144}.glass-toast--animated{transition:all .28s cubic-bezier(0,0,.2,1)}.glass-toast--leaving{transition-timing-function:cubic-bezier(.4,0,1,1)}.glass-toast--enter,.glass-toast--faded{opacity:0}.glass-toast--enter[data-position=top-right],.glass-toast--enter[data-position=bottom-right]{transform:translate(2rem)}.glass-toast--enter[data-position=bottom-center]{transform:translateY(2rem)}.glass-toast__icon{background-color:var(--gt-pill-bg);width:1.75rem;height:1.75rem;color:var(--gt-icon);border-radius:.5rem;flex-shrink:0;justify-content:center;align-items:center;display:flex}.glass-toast__icon svg{flex-shrink:0;width:18px;height:18px;display:block}.glass-toast__message{min-width:0;color:var(--gt-text);overflow-wrap:anywhere;-webkit-line-clamp:5;line-clamp:5;-webkit-box-orient:vertical;flex:1;margin:0;font-size:.9375rem;line-height:1.625;display:-webkit-box;overflow:hidden}.glass-toast__action{border:1px solid var(--gt-action-border,var(--gt-border));background-color:var(--gt-action-bg,transparent);color:var(--gt-action-text,var(--gt-icon));font:inherit;white-space:nowrap;cursor:pointer;appearance:none;border-radius:.5rem;flex-shrink:0;align-self:center;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1;transition:background-color .15s cubic-bezier(.4,0,.2,1),border-color .15s cubic-bezier(.4,0,.2,1)}.glass-toast__action:hover{background-color:var(--gt-action-bg-hover,var(--gt-pill-bg))}.glass-toast__action:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close{border:1px solid var(--gt-close-border);background-color:var(--gt-close-bg);width:2rem;height:2rem;color:var(--gt-close-text);font:inherit;cursor:pointer;appearance:none;border-radius:9999px;flex-shrink:0;justify-content:center;align-self:center;align-items:center;padding:0;transition:all .15s cubic-bezier(.4,0,.2,1);display:flex}.glass-toast__close:hover{background-color:var(--gt-close-bg-hover);border-color:var(--gt-close-border-hover);color:var(--gt-close-text-hover)}.glass-toast__close:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close svg{width:.875rem;height:.875rem;display:block}.glass-toast__bar{background-color:var(--gt-bar);opacity:.4;border-radius:9999px;height:2px;position:absolute;bottom:0;left:0}@media (width>=768px){.glass-toast__message{font-size:14px}}@media (prefers-color-scheme:dark){:where(:root:not([data-theme=light]):not(.light)) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}}:where([data-theme=dark],.dark) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.GlassToast={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t={success:`<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clip-rule="evenodd"/></svg>`,error:`<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd"/></svg>`,warning:`<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.168 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 6a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 6zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/></svg>`,info:`<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/></svg>`,close:`<svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"/></svg>`},n=`.glass-toast-container,.glass-toast,.glass-toast *{box-sizing:border-box}.glass-toast-container{z-index:2147483647;pointer-events:none;flex-direction:column;gap:.75rem;display:flex;position:fixed}.glass-toast-container--top-right{align-items:flex-end;top:1rem;right:1rem}.glass-toast-container--bottom-right{align-items:flex-end;bottom:1rem;right:1rem}.glass-toast-container--bottom-center{align-items:center;bottom:1rem;left:50%;transform:translate(-50%)}.glass-toast{--gt-bg:#fffc;--gt-bg-opaque:#fffffff5;--gt-border:#e5e7eb66;--gt-text:#4a5565;--gt-blur:24px;--gt-radius:1rem;--gt-shadow:0 4px 24px -4px #00000014, 0 1px 2px #0000000a;--gt-close-bg:#f9fafb;--gt-close-border:#e5e7eb;--gt-close-text:#99a1af;--gt-close-bg-hover:#f3f4f6;--gt-close-border-hover:#d1d5dc;--gt-close-text-hover:#4a5565;--gt-focus-ring:#2563eb;pointer-events:auto;background-color:var(--gt-bg);width:24rem;max-width:calc(100vw - 2rem);-webkit-backdrop-filter:blur(var(--gt-blur));backdrop-filter:blur(var(--gt-blur));border:1px solid var(--gt-border);border-radius:var(--gt-radius);box-shadow:var(--gt-shadow);align-items:center;gap:.75rem;padding:.875rem 1rem;display:flex;position:relative;overflow:hidden}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.glass-toast{background-color:var(--gt-bg-opaque)}}.glass-toast--success{--gt-pill-bg:#d0fae5;--gt-icon:#047857;--gt-bar:#00bb7f;--gt-ring:16, 185, 129}.glass-toast--error{--gt-pill-bg:#ffe2e2;--gt-icon:#dc2626;--gt-bar:#ff6568;--gt-ring:239, 68, 68}.glass-toast--warning{--gt-pill-bg:#fef3c6;--gt-icon:#b45309;--gt-bar:#fcbb00;--gt-ring:245, 158, 11}.glass-toast--info{--gt-pill-bg:#e2eaf1;--gt-icon:#3a5b7a;--gt-bar:#6f92ad;--gt-ring:76, 112, 144}.glass-toast--animated{transition:all .28s cubic-bezier(0,0,.2,1)}.glass-toast--leaving{transition-timing-function:cubic-bezier(.4,0,1,1)}.glass-toast--enter,.glass-toast--faded{opacity:0}.glass-toast--enter[data-position=top-right],.glass-toast--enter[data-position=bottom-right]{transform:translate(2rem)}.glass-toast--enter[data-position=bottom-center]{transform:translateY(2rem)}.glass-toast__icon{background-color:var(--gt-pill-bg);width:1.75rem;height:1.75rem;color:var(--gt-icon);border-radius:.5rem;flex-shrink:0;justify-content:center;align-items:center;display:flex}.glass-toast__icon svg{flex-shrink:0;width:18px;height:18px;display:block}.glass-toast__message{min-width:0;color:var(--gt-text);overflow-wrap:anywhere;-webkit-line-clamp:5;line-clamp:5;-webkit-box-orient:vertical;flex:1;margin:0;font-size:.9375rem;line-height:1.625;display:-webkit-box;overflow:hidden}.glass-toast__action{border:1px solid var(--gt-action-border,var(--gt-border));background-color:var(--gt-action-bg,transparent);color:var(--gt-action-text,var(--gt-icon));font:inherit;white-space:nowrap;cursor:pointer;appearance:none;border-radius:.5rem;flex-shrink:0;align-self:center;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1;transition:background-color .15s cubic-bezier(.4,0,.2,1),border-color .15s cubic-bezier(.4,0,.2,1)}.glass-toast__action:hover{background-color:var(--gt-action-bg-hover,var(--gt-pill-bg))}.glass-toast__action:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close{border:1px solid var(--gt-close-border);background-color:var(--gt-close-bg);width:2rem;height:2rem;color:var(--gt-close-text);font:inherit;cursor:pointer;appearance:none;border-radius:9999px;flex-shrink:0;justify-content:center;align-self:center;align-items:center;padding:0;transition:all .15s cubic-bezier(.4,0,.2,1);display:flex}.glass-toast__close:hover{background-color:var(--gt-close-bg-hover);border-color:var(--gt-close-border-hover);color:var(--gt-close-text-hover)}.glass-toast__close:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close svg{width:.875rem;height:.875rem;display:block}.glass-toast__bar{background-color:var(--gt-bar);opacity:.4;border-radius:9999px;height:2px;position:absolute;bottom:0;left:0}@media (width>=768px){.glass-toast__message{font-size:14px}}@media (prefers-color-scheme:dark){:where(:root:not([data-theme=light]):not(.light)) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}}:where([data-theme=dark],.dark) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}`,r=280,i=12,a=1400,o={error:1e4},s=5500,c=4,l=`info`,u=`top-right`,d={success:{role:`status`,live:`polite`,ringRgb:`16, 185, 129`},error:{role:`alert`,live:`assertive`,ringRgb:`239, 68, 68`},warning:{role:`status`,live:`polite`,ringRgb:`245, 158, 11`},info:{role:`status`,live:`polite`,ringRgb:`76, 112, 144`}},f={"top-right":`glass-toast-container--top-right`,"bottom-right":`glass-toast-container--bottom-right`,"bottom-center":`glass-toast-container--bottom-center`},p=`0 4px 24px -4px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04)`,m=!0;function h(){if(typeof document>`u`||document.querySelector(`style[data-glass-toast], link[data-glass-toast]`))return;let e=document.createElement(`style`);e.setAttribute(`data-glass-toast`,``),e.textContent=n,document.head.prepend(e)}var g=()=>window.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches,_=new Map,v=!1;function y(e){let t=_.get(e);if(t&&t.isConnected)return t;let n=document.createElement(`div`);return n.className=`glass-toast-container ${f[e]}`,n.dataset.toastContainer=e,document.body.appendChild(n),_.set(e,n),n}function b(e){let t=[...e.children].filter(e=>!e.dataset.dismissing);for(;t.length>=c;){let e=t.shift();typeof e.__dismiss==`function`?e.__dismiss():e.remove()}}function x(e){if(e&&e.isConnected&&typeof e.focus==`function`){e.focus();return}let t=document.querySelector(`.glass-toast__close`);t&&t.focus()}function S(e,{type:n,position:r,persistent:i,duration:a,action:o}){let s=d[n],c=g(),l=document.createElement(`div`);l.setAttribute(`role`,s.role),l.setAttribute(`aria-live`,s.live),l.dataset.position=r,l.className=[`glass-toast`,`glass-toast--${n}`,c?`glass-toast--faded`:`glass-toast--animated glass-toast--enter`].join(` `);let u=document.createElement(`span`);u.className=`glass-toast__icon`,u.innerHTML=t[n];let f=document.createElement(`p`);f.className=`glass-toast__message`,f.textContent=e,l.append(u,f);let p=null;o&&o.label&&(p=document.createElement(`button`),p.type=`button`,p.className=`glass-toast__action`,p.textContent=o.label,l.appendChild(p));let m=document.createElement(`button`);if(m.type=`button`,m.className=`glass-toast__close`,m.setAttribute(`aria-label`,`關閉通知`),m.innerHTML=t.close,l.appendChild(m),!i){let e=document.createElement(`div`);e.className=`glass-toast__bar`,e.style.width=`100%`,e.style.transition=`width ${a}ms linear`,l.appendChild(e),requestAnimationFrame(()=>{requestAnimationFrame(()=>{e.style.width=`0%`})})}return{el:l,btn:m,actionBtn:p}}function C(e,t){if(g()||typeof e.animate!=`function`)return;let n=getComputedStyle(e),r=n.getPropertyValue(`--gt-ring`).trim().replace(/\s*,\s*/g,`, `)||d[t].ringRgb,i=n.getPropertyValue(`--gt-shadow`).trim()||p;e.animate([{boxShadow:`0 0 18px 6px rgba(${r}, 0.75), ${i}`,offset:0},{boxShadow:`0 0 22px 8px rgba(${r}, 0.7), ${i}`,offset:.35},{boxShadow:`0 0 56px 26px rgba(${r}, 0), ${i}`,offset:1}],{duration:a,easing:`ease-out`,fill:`none`})}function w(e,t){e.remove();let n=_.get(t);n&&n.children.length===0&&(n.remove(),_.delete(t))}function T(e,t,n){if(e.dataset.dismissing)return;e.dataset.dismissing=`true`;let a=e.contains(document.activeElement),o=()=>{w(e,t),a&&x(n)};if(g()){e.classList.add(`glass-toast--faded`),setTimeout(o,r);return}e.classList.add(`glass-toast--leaving`);let s=e.offsetHeight;e.style.height=`${s}px`,e.offsetHeight,e.classList.add(`glass-toast--enter`),e.style.height=`0px`,e.style.paddingTop=`0`,e.style.paddingBottom=`0`,e.style.borderTopWidth=`0`,e.style.borderBottomWidth=`0`,e.style.marginTop=`-${i}px`,setTimeout(o,r)}function E(e,t={}){if(v)return()=>{};let{type:n=l,position:r=u,persistent:i=!1,action:a}=t;d[n]||(n=l),f[r]||(r=u);let c=t.duration!==void 0,p=c?t.duration:o[n]??s;(a&&a.label&&!c||p<=0)&&(i=!0),m&&h();let _=y(r);b(_);let{el:x,btn:w,actionBtn:E}=S(e,{type:n,position:r,persistent:i,duration:p,action:a}),D=typeof document<`u`?document.activeElement:null;_.appendChild(x);let O=g()?`glass-toast--faded`:`glass-toast--enter`;requestAnimationFrame(()=>{requestAnimationFrame(()=>{x.classList.remove(O),C(x,n)})});let k=()=>T(x,r,D);if(x.__dismiss=k,w.addEventListener(`click`,k),E&&E.addEventListener(`click`,()=>{a.onClick?.(),k()}),x.addEventListener(`keydown`,e=>{e.key===`Escape`&&(e.stopPropagation(),k())}),!i){let e=x.querySelector(`.glass-toast__bar`),t=p,n=Date.now(),r=setTimeout(k,t),i=!1,a=!1,o=!1,s=()=>{if(!i&&(i=!0,clearTimeout(r),t-=Date.now()-n,e)){let t=e.getBoundingClientRect().width;e.style.transition=`none`,e.style.width=`${t}px`}},c=()=>{if(i){if(i=!1,t<=0){k();return}n=Date.now(),r=setTimeout(k,t),e&&requestAnimationFrame(()=>{e.style.transition=`width ${t}ms linear`,e.style.width=`0%`})}},l=()=>{!a&&!o&&c()};x.addEventListener(`mouseenter`,()=>{a=!0,s()}),x.addEventListener(`mouseleave`,()=>{a=!1,l()}),x.addEventListener(`focusin`,()=>{o=!0,s()}),x.addEventListener(`focusout`,e=>{x.contains(e.relatedTarget)||(o=!1,l())})}return k}function D({suppress:e=!1}={}){e&&(v=!0);for(let e of _.values())e.remove();_.clear()}function O(){v=!1}function k(e,t){return E(e,{...t,type:`success`})}function A(e,t){return E(e,{...t,type:`error`})}function j(e,t){return E(e,{...t,type:`warning`})}function M(e,t){return E(e,{...t,type:`info`})}var N={show:E,success:k,error:A,warning:j,info:M,dismissAll:D,resume:O};function P({global:e=!0,injectStyles:t=!0,target:n}={}){if(m=t,t&&h(),e){let e=n??(typeof window<`u`?window:void 0);e&&(e.toast=N)}return N}e.dismissAll=D,e.ensureStyles=h,e.error=A,e.info=M,e.register=P,e.resume=O,e.show=E,e.success=k,e.warning=j});
|
package/dist/icons.d.ts
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { show, success, error, warning, info, dismissAll, resume, register, ensureStyles } from "./toast.js";
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
//#region src/icons.js
|
|
2
|
+
var e = {
|
|
3
|
+
success: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z\" clip-rule=\"evenodd\"/></svg>",
|
|
4
|
+
error: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\"/></svg>",
|
|
5
|
+
warning: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.168 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 6a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 6zm0 9a1 1 0 100-2 1 1 0 000 2z\" clip-rule=\"evenodd\"/></svg>",
|
|
6
|
+
info: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z\" clip-rule=\"evenodd\"/></svg>",
|
|
7
|
+
close: "<svg viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z\"/></svg>"
|
|
8
|
+
}, t = ".glass-toast-container,.glass-toast,.glass-toast *{box-sizing:border-box}.glass-toast-container{z-index:2147483647;pointer-events:none;flex-direction:column;gap:.75rem;display:flex;position:fixed}.glass-toast-container--top-right{align-items:flex-end;top:1rem;right:1rem}.glass-toast-container--bottom-right{align-items:flex-end;bottom:1rem;right:1rem}.glass-toast-container--bottom-center{align-items:center;bottom:1rem;left:50%;transform:translate(-50%)}.glass-toast{--gt-bg:#fffc;--gt-bg-opaque:#fffffff5;--gt-border:#e5e7eb66;--gt-text:#4a5565;--gt-blur:24px;--gt-radius:1rem;--gt-shadow:0 4px 24px -4px #00000014, 0 1px 2px #0000000a;--gt-close-bg:#f9fafb;--gt-close-border:#e5e7eb;--gt-close-text:#99a1af;--gt-close-bg-hover:#f3f4f6;--gt-close-border-hover:#d1d5dc;--gt-close-text-hover:#4a5565;--gt-focus-ring:#2563eb;pointer-events:auto;background-color:var(--gt-bg);width:24rem;max-width:calc(100vw - 2rem);-webkit-backdrop-filter:blur(var(--gt-blur));backdrop-filter:blur(var(--gt-blur));border:1px solid var(--gt-border);border-radius:var(--gt-radius);box-shadow:var(--gt-shadow);align-items:center;gap:.75rem;padding:.875rem 1rem;display:flex;position:relative;overflow:hidden}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.glass-toast{background-color:var(--gt-bg-opaque)}}.glass-toast--success{--gt-pill-bg:#d0fae5;--gt-icon:#047857;--gt-bar:#00bb7f;--gt-ring:16, 185, 129}.glass-toast--error{--gt-pill-bg:#ffe2e2;--gt-icon:#dc2626;--gt-bar:#ff6568;--gt-ring:239, 68, 68}.glass-toast--warning{--gt-pill-bg:#fef3c6;--gt-icon:#b45309;--gt-bar:#fcbb00;--gt-ring:245, 158, 11}.glass-toast--info{--gt-pill-bg:#e2eaf1;--gt-icon:#3a5b7a;--gt-bar:#6f92ad;--gt-ring:76, 112, 144}.glass-toast--animated{transition:all .28s cubic-bezier(0,0,.2,1)}.glass-toast--leaving{transition-timing-function:cubic-bezier(.4,0,1,1)}.glass-toast--enter,.glass-toast--faded{opacity:0}.glass-toast--enter[data-position=top-right],.glass-toast--enter[data-position=bottom-right]{transform:translate(2rem)}.glass-toast--enter[data-position=bottom-center]{transform:translateY(2rem)}.glass-toast__icon{background-color:var(--gt-pill-bg);width:1.75rem;height:1.75rem;color:var(--gt-icon);border-radius:.5rem;flex-shrink:0;justify-content:center;align-items:center;display:flex}.glass-toast__icon svg{flex-shrink:0;width:18px;height:18px;display:block}.glass-toast__message{min-width:0;color:var(--gt-text);overflow-wrap:anywhere;-webkit-line-clamp:5;line-clamp:5;-webkit-box-orient:vertical;flex:1;margin:0;font-size:.9375rem;line-height:1.625;display:-webkit-box;overflow:hidden}.glass-toast__action{border:1px solid var(--gt-action-border,var(--gt-border));background-color:var(--gt-action-bg,transparent);color:var(--gt-action-text,var(--gt-icon));font:inherit;white-space:nowrap;cursor:pointer;appearance:none;border-radius:.5rem;flex-shrink:0;align-self:center;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1;transition:background-color .15s cubic-bezier(.4,0,.2,1),border-color .15s cubic-bezier(.4,0,.2,1)}.glass-toast__action:hover{background-color:var(--gt-action-bg-hover,var(--gt-pill-bg))}.glass-toast__action:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close{border:1px solid var(--gt-close-border);background-color:var(--gt-close-bg);width:2rem;height:2rem;color:var(--gt-close-text);font:inherit;cursor:pointer;appearance:none;border-radius:9999px;flex-shrink:0;justify-content:center;align-self:center;align-items:center;padding:0;transition:all .15s cubic-bezier(.4,0,.2,1);display:flex}.glass-toast__close:hover{background-color:var(--gt-close-bg-hover);border-color:var(--gt-close-border-hover);color:var(--gt-close-text-hover)}.glass-toast__close:focus-visible{outline:2px solid var(--gt-focus-ring);outline-offset:2px}.glass-toast__close svg{width:.875rem;height:.875rem;display:block}.glass-toast__bar{background-color:var(--gt-bar);opacity:.4;border-radius:9999px;height:2px;position:absolute;bottom:0;left:0}@media (width>=768px){.glass-toast__message{font-size:14px}}@media (prefers-color-scheme:dark){:where(:root:not([data-theme=light]):not(.light)) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}}:where([data-theme=dark],.dark) .glass-toast{--gt-bg:#1c1c1eb8;--gt-bg-opaque:#1c1c1eeb;--gt-border:#ffffff1a;--gt-text:#e5e7eb;--gt-close-bg:#ffffff0f;--gt-close-border:#ffffff1f;--gt-close-text:#cbd5e1;--gt-close-bg-hover:#ffffff1f;--gt-close-border-hover:#ffffff38;--gt-close-text-hover:#f1f5f9;--gt-focus-ring:#93c5fd}", n = 280, r = 12, i = 1400, a = { error: 1e4 }, o = 5500, s = 4, c = "info", l = "top-right", u = {
|
|
9
|
+
success: {
|
|
10
|
+
role: "status",
|
|
11
|
+
live: "polite",
|
|
12
|
+
ringRgb: "16, 185, 129"
|
|
13
|
+
},
|
|
14
|
+
error: {
|
|
15
|
+
role: "alert",
|
|
16
|
+
live: "assertive",
|
|
17
|
+
ringRgb: "239, 68, 68"
|
|
18
|
+
},
|
|
19
|
+
warning: {
|
|
20
|
+
role: "status",
|
|
21
|
+
live: "polite",
|
|
22
|
+
ringRgb: "245, 158, 11"
|
|
23
|
+
},
|
|
24
|
+
info: {
|
|
25
|
+
role: "status",
|
|
26
|
+
live: "polite",
|
|
27
|
+
ringRgb: "76, 112, 144"
|
|
28
|
+
}
|
|
29
|
+
}, d = {
|
|
30
|
+
"top-right": "glass-toast-container--top-right",
|
|
31
|
+
"bottom-right": "glass-toast-container--bottom-right",
|
|
32
|
+
"bottom-center": "glass-toast-container--bottom-center"
|
|
33
|
+
}, f = "0 4px 24px -4px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04)", p = !0;
|
|
34
|
+
function m() {
|
|
35
|
+
if (typeof document > "u" || document.querySelector("style[data-glass-toast], link[data-glass-toast]")) return;
|
|
36
|
+
let e = document.createElement("style");
|
|
37
|
+
e.setAttribute("data-glass-toast", ""), e.textContent = t, document.head.prepend(e);
|
|
38
|
+
}
|
|
39
|
+
var h = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches, g = /* @__PURE__ */ new Map(), _ = !1;
|
|
40
|
+
function v(e) {
|
|
41
|
+
let t = g.get(e);
|
|
42
|
+
if (t && t.isConnected) return t;
|
|
43
|
+
let n = document.createElement("div");
|
|
44
|
+
return n.className = `glass-toast-container ${d[e]}`, n.dataset.toastContainer = e, document.body.appendChild(n), g.set(e, n), n;
|
|
45
|
+
}
|
|
46
|
+
function y(e) {
|
|
47
|
+
let t = [...e.children].filter((e) => !e.dataset.dismissing);
|
|
48
|
+
for (; t.length >= s;) {
|
|
49
|
+
let e = t.shift();
|
|
50
|
+
typeof e.__dismiss == "function" ? e.__dismiss() : e.remove();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function b(e) {
|
|
54
|
+
if (e && e.isConnected && typeof e.focus == "function") {
|
|
55
|
+
e.focus();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let t = document.querySelector(".glass-toast__close");
|
|
59
|
+
t && t.focus();
|
|
60
|
+
}
|
|
61
|
+
function x(t, { type: n, position: r, persistent: i, duration: a, action: o }) {
|
|
62
|
+
let s = u[n], c = h(), l = document.createElement("div");
|
|
63
|
+
l.setAttribute("role", s.role), l.setAttribute("aria-live", s.live), l.dataset.position = r, l.className = [
|
|
64
|
+
"glass-toast",
|
|
65
|
+
`glass-toast--${n}`,
|
|
66
|
+
c ? "glass-toast--faded" : "glass-toast--animated glass-toast--enter"
|
|
67
|
+
].join(" ");
|
|
68
|
+
let d = document.createElement("span");
|
|
69
|
+
d.className = "glass-toast__icon", d.innerHTML = e[n];
|
|
70
|
+
let f = document.createElement("p");
|
|
71
|
+
f.className = "glass-toast__message", f.textContent = t, l.append(d, f);
|
|
72
|
+
let p = null;
|
|
73
|
+
o && o.label && (p = document.createElement("button"), p.type = "button", p.className = "glass-toast__action", p.textContent = o.label, l.appendChild(p));
|
|
74
|
+
let m = document.createElement("button");
|
|
75
|
+
if (m.type = "button", m.className = "glass-toast__close", m.setAttribute("aria-label", "關閉通知"), m.innerHTML = e.close, l.appendChild(m), !i) {
|
|
76
|
+
let e = document.createElement("div");
|
|
77
|
+
e.className = "glass-toast__bar", e.style.width = "100%", e.style.transition = `width ${a}ms linear`, l.appendChild(e), requestAnimationFrame(() => {
|
|
78
|
+
requestAnimationFrame(() => {
|
|
79
|
+
e.style.width = "0%";
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
el: l,
|
|
85
|
+
btn: m,
|
|
86
|
+
actionBtn: p
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function S(e, t) {
|
|
90
|
+
if (h() || typeof e.animate != "function") return;
|
|
91
|
+
let n = getComputedStyle(e), r = n.getPropertyValue("--gt-ring").trim().replace(/\s*,\s*/g, ", ") || u[t].ringRgb, a = n.getPropertyValue("--gt-shadow").trim() || f;
|
|
92
|
+
e.animate([
|
|
93
|
+
{
|
|
94
|
+
boxShadow: `0 0 18px 6px rgba(${r}, 0.75), ${a}`,
|
|
95
|
+
offset: 0
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
boxShadow: `0 0 22px 8px rgba(${r}, 0.7), ${a}`,
|
|
99
|
+
offset: .35
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
boxShadow: `0 0 56px 26px rgba(${r}, 0), ${a}`,
|
|
103
|
+
offset: 1
|
|
104
|
+
}
|
|
105
|
+
], {
|
|
106
|
+
duration: i,
|
|
107
|
+
easing: "ease-out",
|
|
108
|
+
fill: "none"
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function C(e, t) {
|
|
112
|
+
e.remove();
|
|
113
|
+
let n = g.get(t);
|
|
114
|
+
n && n.children.length === 0 && (n.remove(), g.delete(t));
|
|
115
|
+
}
|
|
116
|
+
function w(e, t, i) {
|
|
117
|
+
if (e.dataset.dismissing) return;
|
|
118
|
+
e.dataset.dismissing = "true";
|
|
119
|
+
let a = e.contains(document.activeElement), o = () => {
|
|
120
|
+
C(e, t), a && b(i);
|
|
121
|
+
};
|
|
122
|
+
if (h()) {
|
|
123
|
+
e.classList.add("glass-toast--faded"), setTimeout(o, n);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
e.classList.add("glass-toast--leaving");
|
|
127
|
+
let s = e.offsetHeight;
|
|
128
|
+
e.style.height = `${s}px`, e.offsetHeight, e.classList.add("glass-toast--enter"), e.style.height = "0px", e.style.paddingTop = "0", e.style.paddingBottom = "0", e.style.borderTopWidth = "0", e.style.borderBottomWidth = "0", e.style.marginTop = `-${r}px`, setTimeout(o, n);
|
|
129
|
+
}
|
|
130
|
+
function T(e, t = {}) {
|
|
131
|
+
if (_) return () => {};
|
|
132
|
+
let { type: n = c, position: r = l, persistent: i = !1, action: s } = t;
|
|
133
|
+
u[n] || (n = c), d[r] || (r = l);
|
|
134
|
+
let f = t.duration !== void 0, g = f ? t.duration : a[n] ?? o;
|
|
135
|
+
(s && s.label && !f || g <= 0) && (i = !0), p && m();
|
|
136
|
+
let b = v(r);
|
|
137
|
+
y(b);
|
|
138
|
+
let { el: C, btn: T, actionBtn: E } = x(e, {
|
|
139
|
+
type: n,
|
|
140
|
+
position: r,
|
|
141
|
+
persistent: i,
|
|
142
|
+
duration: g,
|
|
143
|
+
action: s
|
|
144
|
+
}), D = typeof document < "u" ? document.activeElement : null;
|
|
145
|
+
b.appendChild(C);
|
|
146
|
+
let O = h() ? "glass-toast--faded" : "glass-toast--enter";
|
|
147
|
+
requestAnimationFrame(() => {
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
C.classList.remove(O), S(C, n);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
let k = () => w(C, r, D);
|
|
153
|
+
if (C.__dismiss = k, T.addEventListener("click", k), E && E.addEventListener("click", () => {
|
|
154
|
+
s.onClick?.(), k();
|
|
155
|
+
}), C.addEventListener("keydown", (e) => {
|
|
156
|
+
e.key === "Escape" && (e.stopPropagation(), k());
|
|
157
|
+
}), !i) {
|
|
158
|
+
let e = C.querySelector(".glass-toast__bar"), t = g, n = Date.now(), r = setTimeout(k, t), i = !1, a = !1, o = !1, s = () => {
|
|
159
|
+
if (!i && (i = !0, clearTimeout(r), t -= Date.now() - n, e)) {
|
|
160
|
+
let t = e.getBoundingClientRect().width;
|
|
161
|
+
e.style.transition = "none", e.style.width = `${t}px`;
|
|
162
|
+
}
|
|
163
|
+
}, c = () => {
|
|
164
|
+
if (i) {
|
|
165
|
+
if (i = !1, t <= 0) {
|
|
166
|
+
k();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
n = Date.now(), r = setTimeout(k, t), e && requestAnimationFrame(() => {
|
|
170
|
+
e.style.transition = `width ${t}ms linear`, e.style.width = "0%";
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, l = () => {
|
|
174
|
+
!a && !o && c();
|
|
175
|
+
};
|
|
176
|
+
C.addEventListener("mouseenter", () => {
|
|
177
|
+
a = !0, s();
|
|
178
|
+
}), C.addEventListener("mouseleave", () => {
|
|
179
|
+
a = !1, l();
|
|
180
|
+
}), C.addEventListener("focusin", () => {
|
|
181
|
+
o = !0, s();
|
|
182
|
+
}), C.addEventListener("focusout", (e) => {
|
|
183
|
+
C.contains(e.relatedTarget) || (o = !1, l());
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return k;
|
|
187
|
+
}
|
|
188
|
+
function E({ suppress: e = !1 } = {}) {
|
|
189
|
+
e && (_ = !0);
|
|
190
|
+
for (let e of g.values()) e.remove();
|
|
191
|
+
g.clear();
|
|
192
|
+
}
|
|
193
|
+
function D() {
|
|
194
|
+
_ = !1;
|
|
195
|
+
}
|
|
196
|
+
function O(e, t) {
|
|
197
|
+
return T(e, {
|
|
198
|
+
...t,
|
|
199
|
+
type: "success"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function k(e, t) {
|
|
203
|
+
return T(e, {
|
|
204
|
+
...t,
|
|
205
|
+
type: "error"
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function A(e, t) {
|
|
209
|
+
return T(e, {
|
|
210
|
+
...t,
|
|
211
|
+
type: "warning"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function j(e, t) {
|
|
215
|
+
return T(e, {
|
|
216
|
+
...t,
|
|
217
|
+
type: "info"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
var M = {
|
|
221
|
+
show: T,
|
|
222
|
+
success: O,
|
|
223
|
+
error: k,
|
|
224
|
+
warning: A,
|
|
225
|
+
info: j,
|
|
226
|
+
dismissAll: E,
|
|
227
|
+
resume: D
|
|
228
|
+
};
|
|
229
|
+
function N({ global: e = !0, injectStyles: t = !0, target: n } = {}) {
|
|
230
|
+
if (p = t, t && m(), e) {
|
|
231
|
+
let e = n ?? (typeof window < "u" ? window : void 0);
|
|
232
|
+
e && (e.toast = M);
|
|
233
|
+
}
|
|
234
|
+
return M;
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
export { N as a, O as c, j as i, A as l, m as n, D as o, k as r, T as s, E as t };
|
package/dist/toast.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 確保套件樣式已載入(冪等;SSR 無 document 時 no-op)。
|
|
3
|
+
* 顯式 import style.css 的專案可在自己的 <link> / <style> 加 data-glass-toast
|
|
4
|
+
* 屬性讓本函式跳過注入,或呼叫 register({ injectStyles: false }) 全面停用。
|
|
5
|
+
*/
|
|
6
|
+
export function ensureStyles(): void;
|
|
7
|
+
/**
|
|
8
|
+
* 顯示一則 toast。
|
|
9
|
+
* @param {string} message 訊息文字
|
|
10
|
+
* @param {ToastOptions} [options]
|
|
11
|
+
* @returns {() => void} 手動 dismiss 函式
|
|
12
|
+
*/
|
|
13
|
+
export function show(message: string, options?: ToastOptions): () => void;
|
|
14
|
+
/**
|
|
15
|
+
* 清空所有 toast。預設只清空、不影響後續 show()。
|
|
16
|
+
* 若要鎖住後續 show()(供 session 過期硬導航前,避免 redirect 與 unload 之間
|
|
17
|
+
* 閃現新 toast)傳 { suppress: true };頁面重載後自動解鎖,SPA 不重載時呼叫 resume()。
|
|
18
|
+
* @param {{ suppress?: boolean }} [options]
|
|
19
|
+
*/
|
|
20
|
+
export function dismissAll({ suppress }?: {
|
|
21
|
+
suppress?: boolean;
|
|
22
|
+
}): void;
|
|
23
|
+
/** 解除 dismissAll() 造成的 show() 靜音鎖 */
|
|
24
|
+
export function resume(): void;
|
|
25
|
+
/** @param {string} message @param {Omit<ToastOptions, 'type'>} [options] */
|
|
26
|
+
export function success(message: string, options?: Omit<ToastOptions, "type">): () => void;
|
|
27
|
+
/** @param {string} message @param {Omit<ToastOptions, 'type'>} [options] */
|
|
28
|
+
export function error(message: string, options?: Omit<ToastOptions, "type">): () => void;
|
|
29
|
+
/** @param {string} message @param {Omit<ToastOptions, 'type'>} [options] */
|
|
30
|
+
export function warning(message: string, options?: Omit<ToastOptions, "type">): () => void;
|
|
31
|
+
/** @param {string} message @param {Omit<ToastOptions, 'type'>} [options] */
|
|
32
|
+
export function info(message: string, options?: Omit<ToastOptions, "type">): () => void;
|
|
33
|
+
/**
|
|
34
|
+
* 掛載全域 toast + 注入樣式。import '@dennykuo/glass-toast/auto' 等價
|
|
35
|
+
* register() 預設值;只 import 具名 API 的專案不需要呼叫。
|
|
36
|
+
* @param {Object} [options]
|
|
37
|
+
* @param {boolean} [options.global=true] 是否掛 target.toast
|
|
38
|
+
* @param {boolean} [options.injectStyles=true] false 時全面停用自動樣式注入(CSP / 顯式 import style.css 場景)
|
|
39
|
+
* @param {Object} [options.target=window] 全域掛載目標
|
|
40
|
+
* @returns {ToastApi}
|
|
41
|
+
*/
|
|
42
|
+
export function register({ global, injectStyles, target }?: {
|
|
43
|
+
global?: boolean;
|
|
44
|
+
injectStyles?: boolean;
|
|
45
|
+
target?: any;
|
|
46
|
+
}): ToastApi;
|
|
47
|
+
export type ToastType = "success" | "error" | "warning" | "info";
|
|
48
|
+
export type ToastPosition = "top-right" | "bottom-right" | "bottom-center";
|
|
49
|
+
export type ToastAction = {
|
|
50
|
+
/**
|
|
51
|
+
* 按鈕文字
|
|
52
|
+
*/
|
|
53
|
+
label: string;
|
|
54
|
+
/**
|
|
55
|
+
* 點擊後執行(執行完自動 dismiss)
|
|
56
|
+
*/
|
|
57
|
+
onClick: () => void;
|
|
58
|
+
};
|
|
59
|
+
export type ToastOptions = {
|
|
60
|
+
/**
|
|
61
|
+
* 變體
|
|
62
|
+
*/
|
|
63
|
+
type?: ToastType;
|
|
64
|
+
/**
|
|
65
|
+
* 顯示位置
|
|
66
|
+
*/
|
|
67
|
+
position?: ToastPosition;
|
|
68
|
+
/**
|
|
69
|
+
* true 時不自動消失、無進度條
|
|
70
|
+
*/
|
|
71
|
+
persistent?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* 自動消失毫秒數(未指定時 error 為 10000、其餘 5500);
|
|
74
|
+
* ≤ 0 視為 persistent
|
|
75
|
+
*/
|
|
76
|
+
duration?: number;
|
|
77
|
+
/**
|
|
78
|
+
* 內嵌操作按鈕(如「重試」);未顯式給 duration 時預設 persistent
|
|
79
|
+
*/
|
|
80
|
+
action?: ToastAction;
|
|
81
|
+
};
|
|
82
|
+
export type ToastApi = {
|
|
83
|
+
show: typeof show;
|
|
84
|
+
success: typeof success;
|
|
85
|
+
error: typeof error;
|
|
86
|
+
warning: typeof warning;
|
|
87
|
+
info: typeof info;
|
|
88
|
+
dismissAll: typeof dismissAll;
|
|
89
|
+
resume: typeof resume;
|
|
90
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dennykuo/glass-toast",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency glassmorphism toast notifications — vanilla JS, framework-free, Tailwind-free. 毛玻璃質感 toast 通知,零相依開箱即用。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Denny",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dennykuo/glass-toast.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/dennykuo/glass-toast#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/dennykuo/glass-toast/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"toast",
|
|
21
|
+
"notification",
|
|
22
|
+
"snackbar",
|
|
23
|
+
"vanilla-js",
|
|
24
|
+
"glassmorphism",
|
|
25
|
+
"zero-dependency"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/*.css",
|
|
29
|
+
"./dist/auto.js",
|
|
30
|
+
"./src/auto.js"
|
|
31
|
+
],
|
|
32
|
+
"main": "./dist/glass-toast.umd.cjs",
|
|
33
|
+
"module": "./dist/glass-toast.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"unpkg": "./dist/glass-toast.umd.cjs",
|
|
36
|
+
"jsdelivr": "./dist/glass-toast.umd.cjs",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/glass-toast.js",
|
|
41
|
+
"require": "./dist/glass-toast.umd.cjs"
|
|
42
|
+
},
|
|
43
|
+
"./auto": {
|
|
44
|
+
"types": "./dist/auto.d.ts",
|
|
45
|
+
"import": "./dist/auto.js",
|
|
46
|
+
"default": "./dist/auto.js"
|
|
47
|
+
},
|
|
48
|
+
"./style.css": "./dist/glass-toast.css",
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "vite demo",
|
|
58
|
+
"build": "vite build && vite build --mode umd && node scripts/build-css.mjs && tsc -p tsconfig.json",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"lint": "eslint src test scripts",
|
|
62
|
+
"prepublishOnly": "npm run build"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=22"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@eslint/js": "^9.39.2",
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"globals": "^16.5.0",
|
|
71
|
+
"jsdom": "^29.1.1",
|
|
72
|
+
"lightningcss": "^1.32.0",
|
|
73
|
+
"typescript": "^5.7.0",
|
|
74
|
+
"vite": "^8.0.14",
|
|
75
|
+
"vitest": "^4.1.7"
|
|
76
|
+
}
|
|
77
|
+
}
|