@dennykuo/glass-toast 0.0.0-stage → 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 (unpublished)
4
+
5
+ ### Added
6
+
7
+ - Added a [Traditional Chinese documentation site](https://dennykuo.github.io/glass-toast/) and an [interactive demo](https://dennykuo.github.io/glass-toast/demo.html), with links from both READMEs.
8
+ - Added `register({ closeLabel })` to localize the close button's accessible name. The default is `Close`.
9
+
10
+ ### Fixed
11
+
12
+ - Closing a notification preserves focus when the user moves it elsewhere during the exit animation.
13
+ - Improved action button text contrast in dark mode and when hovering over an error action in light mode.
14
+ - Long action labels wrap on narrow screens, leaving room for the message and close button. The close icon is easier to see in light mode.
15
+ - Clearing or evicting notifications cancels their timers. Clearing all notifications restores focus to the trigger when focus was inside a toast and the trigger is still available.
16
+ - Screen reader messages update separate live regions while the visible buttons remain available for interaction.
17
+ - Added CommonJS type declarations for the main entry point so TypeScript can resolve types from both ESM and CommonJS.
18
+ - Added a declaration for the stylesheet entry so strict TypeScript projects can import it without a global CSS shim.
19
+
20
+ ### Changed
21
+
22
+ - The supported Node.js range for the package and development tools is now `^22.13.0 || >=24.0.0`.
23
+
24
+ ## 0.1.0 (2026-10-08)
25
+
26
+ - Initial stable npm release of the dependency-free toast library, with ESM and UMD builds, TypeScript declarations, and a standalone stylesheet.
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,211 @@
1
- # Temporary Holding Version
1
+ # @dennykuo/glass-toast
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **English** | [繁體中文](README.zh-TW.md)
4
+
5
+ [Documentation (繁體中文)](https://dennykuo.github.io/glass-toast/) · [Interactive demo](https://dennykuo.github.io/glass-toast/demo.html)
6
+
7
+ Glassmorphism toast notifications — vanilla JS, zero runtime dependencies, no Tailwind or framework required. Works out of the box.
8
+
9
+ - 4 variants (success / error / warning / info): colored icon pill, entrance glow-ring pulse, and a countdown progress bar
10
+ - 3 positions (top-right / bottom-right / bottom-center), each with its own stacking container
11
+ - Frosted-glass shell (`backdrop-filter` + layered shadows); the stack collapses smoothly when a toast is dismissed
12
+ - Auto-dismiss and the progress bar pause on hover and keyboard focus. Press `Esc` to close the focused toast.
13
+ - Built-in dark appearance (**dark frosted glass with light text** — keeps the glass look without a glaring white card at night), picked up automatically from `prefers-color-scheme`, `[data-theme="dark"]`, or `.dark`
14
+ - Full `prefers-reduced-motion` support (plain fade, no movement, no glow; the countdown bar stays because it conveys remaining time)
15
+ - Separate live regions announce regular messages as `status` and errors as `alert`. Localize the close button’s accessible name with `closeLabel`.
16
+ - Inline `action` button (e.g. "Retry"); errors stay longer by default (10 s); at most 4 toasts per position, the oldest one closes when a fifth arrives
17
+ - SPA-friendly: containers and styles are rebuilt automatically after `<body>` / `<head>` swaps (tested with Astro View Transitions)
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @dennykuo/glass-toast@0.1.1
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ### 1. Side-effect entry (simplest)
28
+
29
+ ```js
30
+ import '@dennykuo/glass-toast/auto';
31
+
32
+ // Styles are injected and window.toast is ready
33
+ toast.success('Saved');
34
+ toast.error('Connection failed', { position: 'bottom-center' });
35
+ ```
36
+
37
+ `/auto` is an ES module. CommonJS can load it with `require()` on supported Node versions (22.13+ within 22.x, or 24+). It sets up the global and injects styles when a browser DOM is present.
38
+
39
+ ### 2. Named imports (tree-shakable, no globals)
40
+
41
+ ```js
42
+ import { success, show } from '@dennykuo/glass-toast';
43
+
44
+ success('Saved'); // styles are injected on first use
45
+ show('Custom', { type: 'warning', duration: 8000 });
46
+ ```
47
+
48
+ With an explicit stylesheet (recommended for CSP / SSR):
49
+
50
+ ```js
51
+ import '@dennykuo/glass-toast/style.css';
52
+ import { register, success } from '@dennykuo/glass-toast';
53
+
54
+ register({ global: false, injectStyles: false }); // turn off auto-injection
55
+ success('Saved');
56
+ ```
57
+
58
+ ### 3. `<script>` tag (UMD, global `GlassToast`)
59
+
60
+ ```html
61
+ <script src="https://unpkg.com/@dennykuo/glass-toast@0.1.1"></script>
62
+ <script>
63
+ GlassToast.register(); // sets window.toast and injects styles
64
+ toast.info('Hello');
65
+ </script>
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `show(message, options?) => dismiss`
71
+
72
+ Returns a function that closes the toast. The shortcuts `success` / `error` / `warning` / `info` have the same signature (minus `type`).
73
+
74
+ | option | type | default | description |
75
+ |---|---|---|---|
76
+ | `type` | `'success' \| 'error' \| 'warning' \| 'info'` | `'info'` | Variant |
77
+ | `position` | `'top-right' \| 'bottom-right' \| 'bottom-center'` | `'top-right'` | Where the toast appears |
78
+ | `persistent` | `boolean` | `false` | When `true`, the toast never auto-dismisses and shows no progress bar |
79
+ | `duration` | `number` | `10000` for `error`, `5500` otherwise | Milliseconds before auto-dismiss; `≤ 0` is treated as `persistent` |
80
+ | `action` | `{ label, onClick }` | — | Inline action button; runs `onClick`, then closes the toast. Without an explicit `duration`, toasts with an action are `persistent` (they need a user decision) |
81
+
82
+ Messages are written with `textContent` and never parsed as HTML, so any string is safe (`action.label` too).
83
+
84
+ ```js
85
+ import { error } from '@dennykuo/glass-toast';
86
+
87
+ error('Upload failed. Retry?', {
88
+ action: { label: 'Retry', onClick: () => retryUpload() },
89
+ });
90
+ ```
91
+
92
+ ### `dismissAll(options?)`
93
+
94
+ Clears every toast. **By default it only clears; later `show()` calls work as usual.** Pass `{ suppress: true }` to also block later toasts — useful when a session expires and you hard-navigate to the login page, so nothing flashes between the redirect and unload. A full page reload unblocks automatically; in an SPA without a reload, call `resume()`:
95
+
96
+ ```js
97
+ dismissAll(); // clear only (default)
98
+ dismissAll({ suppress: true }); // clear and block later show() calls (before a hard navigation)
99
+ resume(); // unblock manually in an SPA
100
+ ```
101
+
102
+ ### `register(options?) => api`
103
+
104
+ Sets up the global and injects styles (the `/auto` entry is the same as calling this with no options).
105
+
106
+ | option | default | description |
107
+ |---|---|---|
108
+ | `global` | `true` | Assign the API to `target.toast` |
109
+ | `injectStyles` | `true` | `false` turns off automatic style injection entirely |
110
+ | `target` | `window` | Object that receives the global |
111
+ | `closeLabel` | `'Close'` | The close button's `aria-label`; set it to your UI language, e.g. `register({ closeLabel: 'Schließen' })` |
112
+
113
+ ### `ensureStyles()`
114
+
115
+ Injects the styles idempotently. `show()` does a cheap re-check every time, so it also recovers if an SPA navigation removes the injected `<style>` from `<head>`. No-op during SSR (no `document`).
116
+
117
+ ## Theming (CSS variables)
118
+
119
+ All colors go through `--gt-*` custom properties scoped to `.glass-toast`, so nothing leaks onto `:root`. Override them in your own stylesheet:
120
+
121
+ ```css
122
+ .glass-toast { --gt-text: #333; --gt-radius: 0.5rem; }
123
+ .glass-toast--success { --gt-icon: #0a7d4f; --gt-ring: 10, 125, 79; }
124
+ ```
125
+
126
+ | variable | default (light) | description |
127
+ |---|---|---|
128
+ | `--gt-bg` | `rgba(255,255,255,.8)` | Shell background |
129
+ | `--gt-bg-opaque` | `rgba(255,255,255,.96)` | Fallback background when `backdrop-filter` is unsupported |
130
+ | `--gt-border` | `rgba(229,231,235,.4)` | Shell border |
131
+ | `--gt-text` | `#4a5565` | Message text |
132
+ | `--gt-blur` | `24px` | Frosted-glass blur radius |
133
+ | `--gt-radius` | `1rem` | Shell corner radius |
134
+ | `--gt-shadow` | layered shadow | Shell shadow (the glow ring is drawn on top) |
135
+ | `--gt-close-*` | grays | Close button bg / border / text (plus `-hover` versions) |
136
+ | `--gt-focus-ring` | `#2563eb` (dark: `#93c5fd`) | Focus-visible ring for the close and action buttons |
137
+ | `--gt-action-*` | follows the appearance | Action button bg / border / text (plus `-bg-hover`); light mode uses variant-colored text (error uses red-700), dark mode uses message text and a neutral hover background |
138
+ | `--gt-pill-bg` / `--gt-icon` / `--gt-bar` | per variant | Icon pill / icon color / progress bar |
139
+ | `--gt-ring` | per variant | Glow-ring color as an `R, G, B` triplet (fed to `el.animate()`) |
140
+
141
+ ### Dark appearance
142
+
143
+ Dark mode is **dark frosted glass** (a low-luminance translucent background, `rgba(28,28,30,.72)`, with light text) — it keeps the glass look without a glaring white card at night. Variant pills and icons reuse the light values. The shell, close button and focus ring are overridden; action buttons use the message text color and a neutral hover background so small text stays legible.
144
+
145
+ It applies when any of these is true (an explicit `data-theme="light"` or `.light` on the page wins over the OS setting):
146
+
147
+ 1. `@media (prefers-color-scheme: dark)`
148
+ 2. `<html data-theme="dark">`
149
+ 3. `<html class="dark">` (Tailwind convention)
150
+
151
+ All dark overrides use `:where()` to keep the same specificity as the base rules, so your overrides never have to fight the package's selectors.
152
+
153
+ ## CSP / SSR notes
154
+
155
+ Use `persistent: true` for important messages or actions available only in the toast, or provide another way to access them after it closes. See [WCAG 2.2.1 Timing Adjustable](https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html).
156
+
157
+ - Strict CSP (no inline `<style>`): import `'@dennykuo/glass-toast/style.css'` explicitly and call `register({ injectStyles: false })`
158
+ - Already loading the stylesheet yourself and want to avoid a duplicate: add a `data-glass-toast` attribute to your `<link>` / `<style>` and auto-injection will detect it and skip
159
+ - SSR: every entry point is a safe no-op without `document`; call `show()` on the client only
160
+
161
+ ## Development
162
+
163
+ Use Node 24 (`nvm use`; CI reads `.nvmrc`). Node 22.13+ is also supported on the 22.x line.
164
+
165
+ ```bash
166
+ npm ci --ignore-scripts # install from the lockfile without dependency lifecycle scripts
167
+ npx playwright install chromium firefox webkit # one-time browser setup
168
+ npm run dev # docs site: / docs, /demo.html interactive playground
169
+ npm test # vitest + jsdom (includes documentation coverage guards)
170
+ npm run test:browser # Chromium, Firefox, and WebKit at desktop and mobile widths
171
+ npm run lint # ESLint, including docs scripts
172
+ npm run build # dist/: ESM + UMD + CSS + .d.ts / .d.cts
173
+ npm run check:package # lint the tarball and type-check the README examples against it
174
+ npm run build:docs # dist-docs/: static docs site (relative paths, ready for GitHub Pages)
175
+ ```
176
+
177
+ The docs site lives entirely in `docs/` (the site content is currently in Traditional Chinese):
178
+
179
+ ```
180
+ docs/
181
+ index.html Docs (home page)
182
+ demo.html Interactive playground
183
+ partials/ Shared HTML fragments, inserted at build time by vite.config.js
184
+ site.css Shared styles
185
+ index.js Docs page interactions
186
+ demo.js Demo page interactions
187
+ site.js Shared scripts
188
+ public/appearance.js Restores appearance before the page is painted
189
+ scrollspy.js Table-of-contents scroll sync (pure function, test/scrollspy.test.js)
190
+ vite.config.js Docs site config (picked up automatically by `vite docs`)
191
+ ```
192
+
193
+ Pages sit flat in the `docs/` root, so links inside partials always use `./`. The package name and version come from `package.json` via `%PKG_NAME%` and friends.
194
+
195
+ CI checks pushes to `main` and `develop`, plus pull requests. A `main` run checks the package once and deploys its docs artifact when that version exists on npm. If the run skips deployment, run **CI** on `main` after publishing. The docs use the package version for both npm and CDN examples.
196
+
197
+ The development-only `minimatch` override selects v11 while ESLint still requests v10,
198
+ to address [GHSA-g3hv-75wg-m249](https://github.com/isaacs/minimatch/security/advisories/GHSA-g3hv-75wg-m249).
199
+ Its file selection, ignore patterns, and error reporting have been checked with this project.
200
+ Revisit the override when updating ESLint; remove it once upstream dependencies include the fix.
201
+
202
+ Documentation coverage is guarded by tests that scan the source for the public API, the `show()` / `register()` options, and every `--gt-*` variable:
203
+
204
+ - `test/docs.test.js` checks that `docs/index.html` has a matching `data-doc` entry for each
205
+ - `test/readme.test.js` checks that both `README.md` and `README.zh-TW.md` mention every export and option, and link to each other
206
+
207
+ When you add an API, update all of them.
208
+
209
+ ## License
210
+
211
+ MIT © Denny
@@ -0,0 +1,211 @@
1
+ # @dennykuo/glass-toast
2
+
3
+ [English](README.md) | **繁體中文**
4
+
5
+ [文件站](https://dennykuo.github.io/glass-toast/) · [互動 Demo](https://dennykuo.github.io/glass-toast/demo.html)
6
+
7
+ 毛玻璃質感的 toast 通知元件 — vanilla JS、零 runtime 相依、不需要 Tailwind 或任何框架,開箱即用。
8
+
9
+ - 4 種變體(success / error / warning / info):彩色圖示藥丸 + 進場 glow ring 脈衝 + 倒數進度條
10
+ - 3 個位置(top-right / bottom-right / bottom-center),各自獨立堆疊容器
11
+ - 毛玻璃外殼(backdrop-filter + 多層陰影),dismiss 時堆疊平滑坍縮回流
12
+ - 滑鼠移上或鍵盤焦點進入時暫停倒數,按 `Esc` 可關閉聚焦中的通知。
13
+ - 內建 dark 外觀(**深色毛玻璃 + 淺字**,保留玻璃身分、避免夜間眩光),自動命中 `prefers-color-scheme` / `[data-theme="dark"]` / `.dark` 三種慣例
14
+ - `prefers-reduced-motion` 完整降級(純淡入、無位移、無 glow;倒數進度條保留,仍提供時間指示)
15
+ - a11y:`role="status"`(一般)/ `role="alert"`(error)+ 對應 `aria-live`(error 為 assertive,其餘 polite),圖示對藥丸底達 ≥3:1 對比;關閉按鈕名稱可用 `closeLabel` 在地化
16
+ - 內嵌 `action` 按鈕(如「重試」);error 未指定時停留較久(10s),同位置最多同時 4 則、超量自動收合最舊
17
+ - SPA 友善:容器與樣式在 body/head 被替換後自動重建(Astro ViewTransitions 實測)
18
+
19
+ ## 安裝
20
+
21
+ ```bash
22
+ npm install @dennykuo/glass-toast@0.1.1
23
+ ```
24
+
25
+ ## 快速開始
26
+
27
+ ### 1. side-effect 入口(最省事)
28
+
29
+ ```js
30
+ import '@dennykuo/glass-toast/auto';
31
+
32
+ // 樣式已注入、window.toast 已掛載
33
+ toast.success('儲存成功');
34
+ toast.error('連線失敗', { position: 'bottom-center' });
35
+ ```
36
+
37
+ `/auto` 是 ES 模組。在支援的 Node 版本(22.x 自 22.13 起,或 24 以上),CommonJS 也可以用 `require()` 載入。有瀏覽器 DOM 時才會掛載全域與注入樣式。
38
+
39
+ ### 2. 具名 import(可 tree-shake、不碰 window)
40
+
41
+ ```js
42
+ import { success, show } from '@dennykuo/glass-toast';
43
+
44
+ success('儲存成功'); // 首次使用時自動注入樣式
45
+ show('自訂', { type: 'warning', duration: 8000 });
46
+ ```
47
+
48
+ 搭配顯式樣式(正規做法,CSP / SSR 友善):
49
+
50
+ ```js
51
+ import '@dennykuo/glass-toast/style.css';
52
+ import { register, success } from '@dennykuo/glass-toast';
53
+
54
+ register({ global: false, injectStyles: false }); // 停用自動注入
55
+ success('儲存成功');
56
+ ```
57
+
58
+ ### 3. `<script>` 直接引入(UMD,全域 `GlassToast`)
59
+
60
+ ```html
61
+ <script src="https://unpkg.com/@dennykuo/glass-toast@0.1.1"></script>
62
+ <script>
63
+ GlassToast.register(); // 掛 window.toast + 注入樣式
64
+ toast.info('哈囉');
65
+ </script>
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `show(message, options?) => dismiss`
71
+
72
+ 回傳手動關閉函式。快捷方法 `success` / `error` / `warning` / `info` 同簽名(省略 `type`)。
73
+
74
+ | option | 型別 | 預設 | 說明 |
75
+ |---|---|---|---|
76
+ | `type` | `'success' \| 'error' \| 'warning' \| 'info'` | `'info'` | 變體 |
77
+ | `position` | `'top-right' \| 'bottom-right' \| 'bottom-center'` | `'top-right'` | 顯示位置 |
78
+ | `persistent` | `boolean` | `false` | `true` 時不自動消失、無進度條 |
79
+ | `duration` | `number` | `error` 為 `10000`、其餘 `5500` | 自動消失毫秒數;`≤ 0` 視為 `persistent` |
80
+ | `action` | `{ label, onClick }` | — | 內嵌操作按鈕;點擊執行 `onClick` 後自動關閉。未顯式給 `duration` 時預設 `persistent`(需使用者操作) |
81
+
82
+ 訊息走 `textContent`,不解析 HTML,塞什麼字串都安全(`action.label` 亦然)。
83
+
84
+ ```js
85
+ import { error } from '@dennykuo/glass-toast';
86
+
87
+ error('上傳失敗,是否重試?', {
88
+ action: { label: '重試', onClick: () => retryUpload() },
89
+ });
90
+ ```
91
+
92
+ ### `dismissAll(options?)`
93
+
94
+ 清空所有 toast。**預設只清空、不影響後續 `show()`**。若要鎖住後續 toast(供「session 過期 → 硬導航登入頁」情境,避免 redirect 與 unload 之間閃現新 toast)傳 `{ suppress: true }`;整頁重載後自動解鎖,SPA 不重載時用 `resume()` 解鎖:
95
+
96
+ ```js
97
+ dismissAll(); // 只清空(預設)
98
+ dismissAll({ suppress: true }); // 清空並鎖住後續 show()(硬導航前)
99
+ resume(); // SPA 不重載時手動解鎖
100
+ ```
101
+
102
+ ### `register(options?) => api`
103
+
104
+ 掛載全域 + 注入樣式(`/auto` 入口等價於無參數呼叫本函式)。
105
+
106
+ | option | 預設 | 說明 |
107
+ |---|---|---|
108
+ | `global` | `true` | 掛 `target.toast` |
109
+ | `injectStyles` | `true` | `false` 時全面停用自動樣式注入 |
110
+ | `target` | `window` | 全域掛載目標 |
111
+ | `closeLabel` | `'Close'` | 關閉按鈕的 `aria-label`;非英語介面請換成在地語言,例如 `register({ closeLabel: '關閉通知' })` |
112
+
113
+ ### `ensureStyles()`
114
+
115
+ 冪等注入樣式(`show()` 每次都會 cheap re-check,SPA 換頁把 `<head>` 注入的樣式拔掉也會自我修復)。SSR(無 `document`)為 no-op。
116
+
117
+ ## 樣式客製(CSS 變數)
118
+
119
+ 所有顏色走 `--gt-*` custom properties,scope 在 `.glass-toast` 上、不污染 `:root`。在你的 stylesheet 覆蓋即可:
120
+
121
+ ```css
122
+ .glass-toast { --gt-text: #333; --gt-radius: 0.5rem; }
123
+ .glass-toast--success { --gt-icon: #0a7d4f; --gt-ring: 10, 125, 79; }
124
+ ```
125
+
126
+ | 變數 | 預設(light) | 說明 |
127
+ |---|---|---|
128
+ | `--gt-bg` | `rgba(255,255,255,.8)` | 外殼底色 |
129
+ | `--gt-bg-opaque` | `rgba(255,255,255,.96)` | backdrop-filter 不支援時的後備底色 |
130
+ | `--gt-border` | `rgba(229,231,235,.4)` | 外殼邊線 |
131
+ | `--gt-text` | `#4a5565` | 訊息文字 |
132
+ | `--gt-blur` | `24px` | 毛玻璃模糊半徑 |
133
+ | `--gt-radius` | `1rem` | 外殼圓角 |
134
+ | `--gt-shadow` | 多層陰影 | 外殼陰影(glow ring 疊加其上) |
135
+ | `--gt-close-*` | gray 系 | 關閉鈕 bg / border / text(含 `-hover`) |
136
+ | `--gt-focus-ring` | `#2563eb`(dark `#93c5fd`) | 關閉鈕 / action 鈕 focus-visible ring |
137
+ | `--gt-action-*` | 隨外觀調整 | action 鈕 bg / border / text(含 `-bg-hover`);淺色使用變體文字色(error 為 red-700),深色使用訊息文字色與中性 hover 底色 |
138
+ | `--gt-pill-bg` / `--gt-icon` / `--gt-bar` | 依變體 | 圖示藥丸底 / 圖示色 / 進度條色 |
139
+ | `--gt-ring` | 依變體 | glow ring 的 `R, G, B` 三元組(餵給 `el.animate()`) |
140
+
141
+ ### Dark 外觀
142
+
143
+ dark 為**深色毛玻璃**(低亮度半透明底 `rgba(28,28,30,.72)` + 淺字),保留玻璃身分、避免夜間白卡眩光;變體藥丸/圖示沿用 base。除外殼、關閉鈕與 focus ring 外,action 改用訊息文字色與中性 hover 底色,維持小字的可讀性。
144
+
145
+ 以下三種慣例任一成立即套用(頁面明示 `data-theme="light"` / `.light` 時 OS 深色讓路):
146
+
147
+ 1. `@media (prefers-color-scheme: dark)`
148
+ 2. `<html data-theme="dark">`
149
+ 3. `<html class="dark">`(Tailwind 慣例)
150
+
151
+ dark 覆寫全部走 `:where()` 壓成與 base 相同的 specificity,你的覆蓋規則永遠不需要跟套件比權重。
152
+
153
+ ## CSP / SSR 註記
154
+
155
+ 若通知是取得重要訊息或執行操作的唯一方式,請使用 `persistent: true`,或另外提供可在通知關閉後使用的管道。參考 [WCAG 2.2.1 計時可調整](https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html)。
156
+
157
+ - 嚴格 CSP(禁 inline `<style>`):改顯式 `import '@dennykuo/glass-toast/style.css'` + `register({ injectStyles: false })`
158
+ - 已自行載入 style.css 且想避免重複注入:在你的 `<link>` / `<style>` 加 `data-glass-toast` 屬性即可被偵測跳過
159
+ - SSR:所有入口在無 `document` 環境安全 no-op,`show()` 請在 client 端呼叫
160
+
161
+ ## 開發
162
+
163
+ 建議使用 Node 24(`nvm use`;CI 讀取 `.nvmrc`)。22.x 系列最低為 Node 22.13。
164
+
165
+ ```bash
166
+ npm ci --ignore-scripts # 依 lockfile 安裝,不執行依賴的生命週期腳本
167
+ npx playwright install chromium firefox webkit # 首次安裝瀏覽器
168
+ npm run dev # 文件站:/ 文件、/demo.html 互動 playground
169
+ npm test # vitest + jsdom(含文件覆蓋率守門)
170
+ npm run test:browser # Chromium、Firefox、WebKit,涵蓋桌機與手機寬度
171
+ npm run lint # ESLint,含文件站腳本
172
+ npm run build # dist/:ESM + UMD + CSS + .d.ts / .d.cts
173
+ npm run check:package # 檢查 tarball 並用乾淨安裝驗證 README 範例型別
174
+ npm run build:docs # dist-docs/:文件站靜態檔(相對路徑,可直接部署到 GitHub Pages)
175
+ ```
176
+
177
+ 文件站整個放在 `docs/`:
178
+
179
+ ```
180
+ docs/
181
+ index.html 文件(首頁)
182
+ demo.html 互動 playground
183
+ partials/ 共用 HTML 片段,由 vite.config.js 在建置時插入
184
+ site.css 共用樣式
185
+ index.js 文件首頁互動
186
+ demo.js Demo 互動
187
+ site.js 共用腳本
188
+ public/appearance.js 首次繪製前套用外觀
189
+ scrollspy.js 文件目錄的捲動同步判斷(純函式,test/scrollspy.test.js)
190
+ vite.config.js 文件站設定(vite docs 自動讀取)
191
+ ```
192
+
193
+ 頁面一律平放在 `docs/` 根目錄,partial 裡的站內連結都寫 `./`;套件名稱與版本以 `%PKG_NAME%` 等從 `package.json` 帶入。
194
+
195
+ CI 檢查 `main`、`develop` 的推送和 PR。`main` 每次只跑一輪驗證,該版本已在 npm 發布時會部署同次驗證的文件站。若因版本未發布而略過部署,發布後可在 `main` 手動執行 **CI**。文件站的 npm 和 CDN 範例使用同一個套件版本。
196
+
197
+ 開發依賴暫以 `minimatch` override 選用 v11,處理 ESLint 仍要求 v10 時的
198
+ [GHSA-g3hv-75wg-m249](https://github.com/isaacs/minimatch/security/advisories/GHSA-g3hv-75wg-m249)。
199
+ 已核對本專案的檔案選取、忽略規則與錯誤偵測。後續更新 ESLint 時需重新檢查,
200
+ 待上游直接採用修補版後移除 override。
201
+
202
+ 文件覆蓋率由測試守門,從原始碼掃出公開 API、`show()` / `register()` 選項與所有 `--gt-*` 變數:
203
+
204
+ - `test/docs.test.js`:逐一確認 `docs/index.html` 有對應的 `data-doc` 條目
205
+ - `test/readme.test.js`:確認 `README.md`(英文)與 `README.zh-TW.md` 都提到每個 export 與選項,且互相連結
206
+
207
+ 新增 API 時記得每一份都要補。
208
+
209
+ ## License
210
+
211
+ MIT © Denny
@@ -0,0 +1 @@
1
+ export * from "./toast.cjs";
package/dist/auto.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./toast.js";
package/dist/auto.js ADDED
@@ -0,0 +1,4 @@
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-CaMQl-_l.js";
2
+ // src/auto.js
3
+ e();
4
+ 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-announcer{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.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:#667085;--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);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.75rem;padding:.875rem 1rem;display:grid;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-action-text:#b91c1c;--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;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);min-width:0;max-width:100%;min-height:2rem;color:var(--gt-action-text,var(--gt-icon));font:inherit;overflow-wrap:anywhere;cursor:pointer;appearance:none;border-radius:.5rem;grid-area:2/2;place-self:center start;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1.25;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;grid-area:1/-2;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}.glass-toast:has(.glass-toast__action){grid-template-columns:auto minmax(0,1fr) minmax(0,35%) auto}.glass-toast:has(.glass-toast__action) .glass-toast__action{grid-area:1/3}}@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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}}: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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { t as dismissAll, n as ensureStyles, r as error, i as info, a as register, o as resume, s as show, c as success, l as warning } from "./toast-CaMQl-_l.js";
@@ -0,0 +1,2 @@
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-announcer{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.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:#667085;--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);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.75rem;padding:.875rem 1rem;display:grid;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-action-text:#b91c1c;--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;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);min-width:0;max-width:100%;min-height:2rem;color:var(--gt-action-text,var(--gt-icon));font:inherit;overflow-wrap:anywhere;cursor:pointer;appearance:none;border-radius:.5rem;grid-area:2/2;place-self:center start;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1.25;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;grid-area:1/-2;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}.glass-toast:has(.glass-toast__action){grid-template-columns:auto minmax(0,1fr) minmax(0,35%) auto}.glass-toast:has(.glass-toast__action) .glass-toast__action{grid-area:1/3}}@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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}}: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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}`,r=280,i=12,a=1400,o={error:1e4},s=5500,c=4,l=`info`,u=`top-right`,d=`Close`,f={success:`16, 185, 129`,error:`239, 68, 68`,warning:`245, 158, 11`,info:`76, 112, 144`},p={"top-right":`glass-toast-container--top-right`,"bottom-right":`glass-toast-container--bottom-right`,"bottom-center":`glass-toast-container--bottom-center`},m=`0 4px 24px -4px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04)`,h=!0,g=d;function _(){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 v=()=>window.matchMedia?.(`(prefers-reduced-motion: reduce)`).matches,y=new Map,b=new WeakMap,x;function S(){for(let e of Object.values(x??{}))cancelAnimationFrame(e.frame),e.frame=void 0,e.pending.length=0,e.el.textContent=``}function C(){if(typeof document<`u`&&document.body&&!(x?.status.el.isConnected&&x.alert.el.isConnected)){S();for(let e of Object.values(x??{}))e.el.remove();x=Object.fromEntries([`status`,`alert`].map(e=>{let t=document.createElement(`div`);return t.className=`glass-toast-announcer`,t.setAttribute(`role`,e),t.setAttribute(`aria-atomic`,`true`),document.body.appendChild(t),[e,{el:t,pending:[],frame:void 0}]}))}}function w(e,t,n){C();let r=x[t===`error`?`alert`:`status`];r.pending.push({toast:e,message:n}),r.frame===void 0&&(r.el.textContent=``,r.frame=requestAnimationFrame(()=>{r.frame=requestAnimationFrame(()=>{r.frame=void 0;let e=r.pending.splice(0);r.el.isConnected&&(r.el.textContent=e.filter(({toast:e})=>e.isConnected&&!e.dataset.dismissing).map(({message:e})=>e).join(`
2
+ `))})}))}var T=!1;function E(e){let t=y.get(e);if(t&&t.isConnected)return t;let n=document.createElement(`div`);return n.className=`glass-toast-container ${p[e]}`,n.dataset.toastContainer=e,document.body.appendChild(n),y.set(e,n),n}function D(e){let t=[...e.children].filter(e=>!e.dataset.dismissing);for(;t.length>=c;){let e=t.shift();b.has(e)?b.get(e).dismiss():e.remove()}}function O(e){if(e&&e.isConnected&&typeof e.focus==`function`&&(e.focus(),document.activeElement===e))return;let t=document.querySelector(`[data-toast-container] > .glass-toast:not([data-dismissing]) .glass-toast__close`);t&&t.focus()}function k(e,{type:n,position:r,persistent:i,duration:a,action:o}){let s=v(),c=document.createElement(`div`);c.dataset.position=r,c.className=[`glass-toast`,`glass-toast--${n}`,s?`glass-toast--faded`:`glass-toast--animated glass-toast--enter`].join(` `);let l=document.createElement(`span`);l.className=`glass-toast__icon`,l.innerHTML=t[n];let u=document.createElement(`p`);u.className=`glass-toast__message`,u.textContent=e,c.append(l,u);let d=null;o&&o.label&&(d=document.createElement(`button`),d.type=`button`,d.className=`glass-toast__action`,d.textContent=o.label,c.appendChild(d));let f=document.createElement(`button`);if(f.type=`button`,f.className=`glass-toast__close`,f.setAttribute(`aria-label`,g),f.innerHTML=t.close,c.appendChild(f),!i){let e=document.createElement(`div`);e.className=`glass-toast__bar`,e.style.width=`100%`,e.style.transition=`width ${a}ms linear`,c.appendChild(e)}return{el:c,btn:f,actionBtn:d}}function A(e,t){if(v()||typeof e.animate!=`function`)return;let n=getComputedStyle(e),r=n.getPropertyValue(`--gt-ring`).trim().replace(/\s*,\s*/g,`, `)||f[t],i=n.getPropertyValue(`--gt-shadow`).trim()||m;return 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 j(e,t){let n=e.parentElement;e.remove(),n&&n.children.length===0&&(n.remove(),y.get(t)===n&&y.delete(t))}function M(e){if(v()){e.classList.add(`glass-toast--faded`);return}e.classList.add(`glass-toast--leaving`);let t=e.offsetHeight;e.style.height=`${t}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`}function N(e,t={}){if(T)return()=>{};let{type:n=l,position:i=u,persistent:a=!1,action:c}=t;Object.hasOwn(f,n)||(n=l),Object.hasOwn(p,i)||(i=u);let d=t.duration!==void 0,m=d?t.duration:o[n]??s;(c&&c.label&&!d||m<=0)&&(a=!0),h&&_();let g=E(i);D(g);let{el:y,btn:x,actionBtn:S}=k(e,{type:n,position:i,persistent:a,duration:m,action:c}),C=typeof document<`u`?document.activeElement:null;g.appendChild(y);let N=y.querySelector(`.glass-toast__bar`),P,F,I,L,R,z=!1,B=()=>{clearTimeout(P),cancelAnimationFrame(I),cancelAnimationFrame(L),R?.cancel()},V=()=>{z||(z=!0,B(),clearTimeout(F),b.delete(y),j(y,i))},H=()=>{z||y.dataset.dismissing||(y.isConnected?(y.dataset.dismissing=`true`,B(),M(y),F=setTimeout(()=>{let e=y.contains(document.activeElement);V(),e&&O(C)},r)):V())};b.set(y,{dismiss:H,remove:V,invoker:C}),w(y,n,e);let U=v()?`glass-toast--faded`:`glass-toast--enter`;if(I=requestAnimationFrame(()=>{I=requestAnimationFrame(()=>{y.isConnected?(y.classList.remove(U),R=A(y,n)):V()})}),x.addEventListener(`click`,H),S&&S.addEventListener(`click`,()=>{c.onClick?.(),H()}),y.addEventListener(`keydown`,e=>{e.key===`Escape`&&(e.stopPropagation(),H())}),!a){let e=m,t=Date.now();P=setTimeout(H,e);let n=!1,r=!1,i=!1,a=()=>{N.style.transition=`width ${e}ms linear`,N.style.width=`0%`};L=requestAnimationFrame(()=>{L=requestAnimationFrame(a)});let o=()=>{if(n||z||y.dataset.dismissing)return;n=!0,clearTimeout(P),cancelAnimationFrame(L),e-=Date.now()-t;let r=N.getBoundingClientRect().width;N.style.transition=`none`,N.style.width=`${r}px`},s=()=>{!n||z||y.dataset.dismissing||(n=!1,e<=0?H():(t=Date.now(),P=setTimeout(H,e),L=requestAnimationFrame(a)))},c=()=>{!r&&!i&&s()};y.addEventListener(`mouseenter`,()=>{r=!0,o()}),y.addEventListener(`mouseleave`,()=>{r=!1,c()}),y.addEventListener(`focusin`,()=>{i=!0,o()}),y.addEventListener(`focusout`,e=>{y.contains(e.relatedTarget)||(i=!1,c())})}return H}function P({suppress:e=!1}={}){e&&(T=!0),S();let t;for(let e of y.values()){for(let n of[...e.children]){let e=b.get(n);n.contains(document.activeElement)&&(t=e),e?.remove()}e.remove()}y.clear(),t&&O(t.invoker)}function F(){T=!1}function I(e,t){return N(e,{...t,type:`success`})}function L(e,t){return N(e,{...t,type:`error`})}function R(e,t){return N(e,{...t,type:`warning`})}function z(e,t){return N(e,{...t,type:`info`})}var B={show:N,success:I,error:L,warning:R,info:z,dismissAll:P,resume:F};function V({global:e=!0,injectStyles:t=!0,target:n,closeLabel:r}={}){if(h=t,g=typeof r==`string`&&r.trim()?r:d,t&&_(),C(),e){let e=n??(typeof window<`u`?window:void 0);e&&(e.toast=B)}return B}e.dismissAll=P,e.ensureStyles=_,e.error=L,e.info=z,e.register=V,e.resume=F,e.show=N,e.success=I,e.warning=R});
@@ -0,0 +1,7 @@
1
+ export namespace ICONS {
2
+ let success: string;
3
+ let error: string;
4
+ let warning: string;
5
+ let info: string;
6
+ let close: string;
7
+ }
@@ -0,0 +1,7 @@
1
+ export namespace ICONS {
2
+ let success: string;
3
+ let error: string;
4
+ let warning: string;
5
+ let info: string;
6
+ let close: string;
7
+ }
@@ -0,0 +1 @@
1
+ export { show, success, error, warning, info, dismissAll, resume, register, ensureStyles } from "./toast.cjs";
@@ -0,0 +1 @@
1
+ export { show, success, error, warning, info, dismissAll, resume, register, ensureStyles } from "./toast.js";
@@ -0,0 +1,259 @@
1
+ // 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-announcer{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.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:#667085;--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);grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.75rem;padding:.875rem 1rem;display:grid;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-action-text:#b91c1c;--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;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);min-width:0;max-width:100%;min-height:2rem;color:var(--gt-action-text,var(--gt-icon));font:inherit;overflow-wrap:anywhere;cursor:pointer;appearance:none;border-radius:.5rem;grid-area:2/2;place-self:center start;padding:.3125rem .75rem;font-size:.8125rem;font-weight:600;line-height:1.25;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;grid-area:1/-2;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}.glass-toast:has(.glass-toast__action){grid-template-columns:auto minmax(0,1fr) minmax(0,35%) auto}.glass-toast:has(.glass-toast__action) .glass-toast__action{grid-area:1/3}}@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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}}: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;--gt-action-text:var(--gt-text);--gt-action-bg-hover:var(--gt-close-bg-hover)}", n = 280, r = 12, i = 1400, a = { error: 1e4 }, o = 5500, s = 4, c = "info", l = "top-right", u = "Close", d = {
9
+ success: "16, 185, 129",
10
+ error: "239, 68, 68",
11
+ warning: "245, 158, 11",
12
+ info: "76, 112, 144"
13
+ }, f = {
14
+ "top-right": "glass-toast-container--top-right",
15
+ "bottom-right": "glass-toast-container--bottom-right",
16
+ "bottom-center": "glass-toast-container--bottom-center"
17
+ }, p = "0 4px 24px -4px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04)", m = !0, h = u;
18
+ function g() {
19
+ if (typeof document > "u" || document.querySelector("style[data-glass-toast], link[data-glass-toast]")) return;
20
+ let e = document.createElement("style");
21
+ e.setAttribute("data-glass-toast", ""), e.textContent = t, document.head.prepend(e);
22
+ }
23
+ var _ = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches, v = /* @__PURE__ */ new Map(), y = /* @__PURE__ */ new WeakMap(), b;
24
+ function x() {
25
+ for (let e of Object.values(b ?? {})) cancelAnimationFrame(e.frame), e.frame = void 0, e.pending.length = 0, e.el.textContent = "";
26
+ }
27
+ function S() {
28
+ if (typeof document < "u" && document.body && !(b?.status.el.isConnected && b.alert.el.isConnected)) {
29
+ x();
30
+ for (let e of Object.values(b ?? {})) e.el.remove();
31
+ b = Object.fromEntries(["status", "alert"].map((e) => {
32
+ let t = document.createElement("div");
33
+ return t.className = "glass-toast-announcer", t.setAttribute("role", e), t.setAttribute("aria-atomic", "true"), document.body.appendChild(t), [e, {
34
+ el: t,
35
+ pending: [],
36
+ frame: void 0
37
+ }];
38
+ }));
39
+ }
40
+ }
41
+ function C(e, t, n) {
42
+ S();
43
+ let r = b[t === "error" ? "alert" : "status"];
44
+ r.pending.push({
45
+ toast: e,
46
+ message: n
47
+ }), r.frame === void 0 && (r.el.textContent = "", r.frame = requestAnimationFrame(() => {
48
+ r.frame = requestAnimationFrame(() => {
49
+ r.frame = void 0;
50
+ let e = r.pending.splice(0);
51
+ r.el.isConnected && (r.el.textContent = e.filter(({ toast: e }) => e.isConnected && !e.dataset.dismissing).map(({ message: e }) => e).join("\n"));
52
+ });
53
+ }));
54
+ }
55
+ var w = !1;
56
+ function T(e) {
57
+ let t = v.get(e);
58
+ if (t && t.isConnected) return t;
59
+ let n = document.createElement("div");
60
+ return n.className = `glass-toast-container ${f[e]}`, n.dataset.toastContainer = e, document.body.appendChild(n), v.set(e, n), n;
61
+ }
62
+ function E(e) {
63
+ let t = [...e.children].filter((e) => !e.dataset.dismissing);
64
+ for (; t.length >= s;) {
65
+ let e = t.shift();
66
+ y.has(e) ? y.get(e).dismiss() : e.remove();
67
+ }
68
+ }
69
+ function D(e) {
70
+ if (e && e.isConnected && typeof e.focus == "function" && (e.focus(), document.activeElement === e)) return;
71
+ let t = document.querySelector("[data-toast-container] > .glass-toast:not([data-dismissing]) .glass-toast__close");
72
+ t && t.focus();
73
+ }
74
+ function O(t, { type: n, position: r, persistent: i, duration: a, action: o }) {
75
+ let s = _(), c = document.createElement("div");
76
+ c.dataset.position = r, c.className = [
77
+ "glass-toast",
78
+ `glass-toast--${n}`,
79
+ s ? "glass-toast--faded" : "glass-toast--animated glass-toast--enter"
80
+ ].join(" ");
81
+ let l = document.createElement("span");
82
+ l.className = "glass-toast__icon", l.innerHTML = e[n];
83
+ let u = document.createElement("p");
84
+ u.className = "glass-toast__message", u.textContent = t, c.append(l, u);
85
+ let d = null;
86
+ o && o.label && (d = document.createElement("button"), d.type = "button", d.className = "glass-toast__action", d.textContent = o.label, c.appendChild(d));
87
+ let f = document.createElement("button");
88
+ if (f.type = "button", f.className = "glass-toast__close", f.setAttribute("aria-label", h), f.innerHTML = e.close, c.appendChild(f), !i) {
89
+ let e = document.createElement("div");
90
+ e.className = "glass-toast__bar", e.style.width = "100%", e.style.transition = `width ${a}ms linear`, c.appendChild(e);
91
+ }
92
+ return {
93
+ el: c,
94
+ btn: f,
95
+ actionBtn: d
96
+ };
97
+ }
98
+ function k(e, t) {
99
+ if (_() || typeof e.animate != "function") return;
100
+ let n = getComputedStyle(e), r = n.getPropertyValue("--gt-ring").trim().replace(/\s*,\s*/g, ", ") || d[t], a = n.getPropertyValue("--gt-shadow").trim() || p;
101
+ return e.animate([
102
+ {
103
+ boxShadow: `0 0 18px 6px rgba(${r}, 0.75), ${a}`,
104
+ offset: 0
105
+ },
106
+ {
107
+ boxShadow: `0 0 22px 8px rgba(${r}, 0.7), ${a}`,
108
+ offset: .35
109
+ },
110
+ {
111
+ boxShadow: `0 0 56px 26px rgba(${r}, 0), ${a}`,
112
+ offset: 1
113
+ }
114
+ ], {
115
+ duration: i,
116
+ easing: "ease-out",
117
+ fill: "none"
118
+ });
119
+ }
120
+ function A(e, t) {
121
+ let n = e.parentElement;
122
+ e.remove(), n && n.children.length === 0 && (n.remove(), v.get(t) === n && v.delete(t));
123
+ }
124
+ function j(e) {
125
+ if (_()) {
126
+ e.classList.add("glass-toast--faded");
127
+ return;
128
+ }
129
+ e.classList.add("glass-toast--leaving");
130
+ let t = e.offsetHeight;
131
+ e.style.height = `${t}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`;
132
+ }
133
+ function M(e, t = {}) {
134
+ if (w) return () => {};
135
+ let { type: r = c, position: i = l, persistent: s = !1, action: u } = t;
136
+ Object.hasOwn(d, r) || (r = c), Object.hasOwn(f, i) || (i = l);
137
+ let p = t.duration !== void 0, h = p ? t.duration : a[r] ?? o;
138
+ (u && u.label && !p || h <= 0) && (s = !0), m && g();
139
+ let v = T(i);
140
+ E(v);
141
+ let { el: b, btn: x, actionBtn: S } = O(e, {
142
+ type: r,
143
+ position: i,
144
+ persistent: s,
145
+ duration: h,
146
+ action: u
147
+ }), M = typeof document < "u" ? document.activeElement : null;
148
+ v.appendChild(b);
149
+ let N = b.querySelector(".glass-toast__bar"), P, F, I, L, R, z = !1, B = () => {
150
+ clearTimeout(P), cancelAnimationFrame(I), cancelAnimationFrame(L), R?.cancel();
151
+ }, V = () => {
152
+ z || (z = !0, B(), clearTimeout(F), y.delete(b), A(b, i));
153
+ }, H = () => {
154
+ z || b.dataset.dismissing || (b.isConnected ? (b.dataset.dismissing = "true", B(), j(b), F = setTimeout(() => {
155
+ let e = b.contains(document.activeElement);
156
+ V(), e && D(M);
157
+ }, n)) : V());
158
+ };
159
+ y.set(b, {
160
+ dismiss: H,
161
+ remove: V,
162
+ invoker: M
163
+ }), C(b, r, e);
164
+ let U = _() ? "glass-toast--faded" : "glass-toast--enter";
165
+ if (I = requestAnimationFrame(() => {
166
+ I = requestAnimationFrame(() => {
167
+ b.isConnected ? (b.classList.remove(U), R = k(b, r)) : V();
168
+ });
169
+ }), x.addEventListener("click", H), S && S.addEventListener("click", () => {
170
+ u.onClick?.(), H();
171
+ }), b.addEventListener("keydown", (e) => {
172
+ e.key === "Escape" && (e.stopPropagation(), H());
173
+ }), !s) {
174
+ let e = h, t = Date.now();
175
+ P = setTimeout(H, e);
176
+ let n = !1, r = !1, i = !1, a = () => {
177
+ N.style.transition = `width ${e}ms linear`, N.style.width = "0%";
178
+ };
179
+ L = requestAnimationFrame(() => {
180
+ L = requestAnimationFrame(a);
181
+ });
182
+ let o = () => {
183
+ if (n || z || b.dataset.dismissing) return;
184
+ n = !0, clearTimeout(P), cancelAnimationFrame(L), e -= Date.now() - t;
185
+ let r = N.getBoundingClientRect().width;
186
+ N.style.transition = "none", N.style.width = `${r}px`;
187
+ }, s = () => {
188
+ !n || z || b.dataset.dismissing || (n = !1, e <= 0 ? H() : (t = Date.now(), P = setTimeout(H, e), L = requestAnimationFrame(a)));
189
+ }, c = () => {
190
+ !r && !i && s();
191
+ };
192
+ b.addEventListener("mouseenter", () => {
193
+ r = !0, o();
194
+ }), b.addEventListener("mouseleave", () => {
195
+ r = !1, c();
196
+ }), b.addEventListener("focusin", () => {
197
+ i = !0, o();
198
+ }), b.addEventListener("focusout", (e) => {
199
+ b.contains(e.relatedTarget) || (i = !1, c());
200
+ });
201
+ }
202
+ return H;
203
+ }
204
+ function N({ suppress: e = !1 } = {}) {
205
+ e && (w = !0), x();
206
+ let t;
207
+ for (let e of v.values()) {
208
+ for (let n of [...e.children]) {
209
+ let e = y.get(n);
210
+ n.contains(document.activeElement) && (t = e), e?.remove();
211
+ }
212
+ e.remove();
213
+ }
214
+ v.clear(), t && D(t.invoker);
215
+ }
216
+ function P() {
217
+ w = !1;
218
+ }
219
+ function F(e, t) {
220
+ return M(e, {
221
+ ...t,
222
+ type: "success"
223
+ });
224
+ }
225
+ function I(e, t) {
226
+ return M(e, {
227
+ ...t,
228
+ type: "error"
229
+ });
230
+ }
231
+ function L(e, t) {
232
+ return M(e, {
233
+ ...t,
234
+ type: "warning"
235
+ });
236
+ }
237
+ function R(e, t) {
238
+ return M(e, {
239
+ ...t,
240
+ type: "info"
241
+ });
242
+ }
243
+ var z = {
244
+ show: M,
245
+ success: F,
246
+ error: I,
247
+ warning: L,
248
+ info: R,
249
+ dismissAll: N,
250
+ resume: P
251
+ };
252
+ function B({ global: e = !0, injectStyles: t = !0, target: n, closeLabel: r } = {}) {
253
+ if (m = t, h = typeof r == "string" && r.trim() ? r : u, t && g(), S(), e) {
254
+ let e = n ?? (typeof window < "u" ? window : void 0);
255
+ e && (e.toast = z);
256
+ }
257
+ return z;
258
+ }
259
+ export { B as a, F as c, R as i, L as l, g as n, P as o, I as r, M as s, N as t };
@@ -0,0 +1,93 @@
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
+ * @param {string} [options.closeLabel='Close'] 關閉按鈕的 aria-label,套用到之後顯示的 toast;
41
+ * 空字串或非字串時退回預設,避免產生沒有名稱的按鈕
42
+ * @returns {ToastApi}
43
+ */
44
+ export function register({ global, injectStyles, target, closeLabel: label }?: {
45
+ global?: boolean;
46
+ injectStyles?: boolean;
47
+ target?: any;
48
+ closeLabel?: string;
49
+ }): ToastApi;
50
+ export type ToastType = "success" | "error" | "warning" | "info";
51
+ export type ToastPosition = "top-right" | "bottom-right" | "bottom-center";
52
+ export type ToastAction = {
53
+ /**
54
+ * 按鈕文字
55
+ */
56
+ label: string;
57
+ /**
58
+ * 點擊後執行(執行完自動 dismiss)
59
+ */
60
+ onClick: () => void;
61
+ };
62
+ export type ToastOptions = {
63
+ /**
64
+ * 變體
65
+ */
66
+ type?: ToastType;
67
+ /**
68
+ * 顯示位置
69
+ */
70
+ position?: ToastPosition;
71
+ /**
72
+ * true 時不自動消失、無進度條
73
+ */
74
+ persistent?: boolean;
75
+ /**
76
+ * 自動消失毫秒數(未指定時 error 為 10000、其餘 5500);
77
+ * ≤ 0 視為 persistent
78
+ */
79
+ duration?: number;
80
+ /**
81
+ * 內嵌操作按鈕(如「重試」);未顯式給 duration 時預設 persistent
82
+ */
83
+ action?: ToastAction;
84
+ };
85
+ export type ToastApi = {
86
+ show: typeof show;
87
+ success: typeof success;
88
+ error: typeof error;
89
+ warning: typeof warning;
90
+ info: typeof info;
91
+ dismissAll: typeof dismissAll;
92
+ resume: typeof resume;
93
+ };
@@ -0,0 +1,93 @@
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
+ * @param {string} [options.closeLabel='Close'] 關閉按鈕的 aria-label,套用到之後顯示的 toast;
41
+ * 空字串或非字串時退回預設,避免產生沒有名稱的按鈕
42
+ * @returns {ToastApi}
43
+ */
44
+ export function register({ global, injectStyles, target, closeLabel: label }?: {
45
+ global?: boolean;
46
+ injectStyles?: boolean;
47
+ target?: any;
48
+ closeLabel?: string;
49
+ }): ToastApi;
50
+ export type ToastType = "success" | "error" | "warning" | "info";
51
+ export type ToastPosition = "top-right" | "bottom-right" | "bottom-center";
52
+ export type ToastAction = {
53
+ /**
54
+ * 按鈕文字
55
+ */
56
+ label: string;
57
+ /**
58
+ * 點擊後執行(執行完自動 dismiss)
59
+ */
60
+ onClick: () => void;
61
+ };
62
+ export type ToastOptions = {
63
+ /**
64
+ * 變體
65
+ */
66
+ type?: ToastType;
67
+ /**
68
+ * 顯示位置
69
+ */
70
+ position?: ToastPosition;
71
+ /**
72
+ * true 時不自動消失、無進度條
73
+ */
74
+ persistent?: boolean;
75
+ /**
76
+ * 自動消失毫秒數(未指定時 error 為 10000、其餘 5500);
77
+ * ≤ 0 視為 persistent
78
+ */
79
+ duration?: number;
80
+ /**
81
+ * 內嵌操作按鈕(如「重試」);未顯式給 duration 時預設 persistent
82
+ */
83
+ action?: ToastAction;
84
+ };
85
+ export type ToastApi = {
86
+ show: typeof show;
87
+ success: typeof success;
88
+ error: typeof error;
89
+ warning: typeof warning;
90
+ info: typeof info;
91
+ dismissAll: typeof dismissAll;
92
+ resume: typeof resume;
93
+ };
package/package.json CHANGED
@@ -1,6 +1,97 @@
1
1
  {
2
2
  "name": "@dennykuo/glass-toast",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
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://dennykuo.github.io/glass-toast/",
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
+ "./docs/**"
32
+ ],
33
+ "main": "./dist/glass-toast.umd.cjs",
34
+ "module": "./dist/glass-toast.js",
35
+ "types": "./dist/index.d.ts",
36
+ "unpkg": "./dist/glass-toast.umd.cjs",
37
+ "jsdelivr": "./dist/glass-toast.umd.cjs",
38
+ "exports": {
39
+ ".": {
40
+ "import": {
41
+ "types": "./dist/index.d.ts",
42
+ "default": "./dist/glass-toast.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/index.d.cts",
46
+ "default": "./dist/glass-toast.umd.cjs"
47
+ }
48
+ },
49
+ "./auto": {
50
+ "types": "./dist/auto.d.ts",
51
+ "import": "./dist/auto.js",
52
+ "default": "./dist/auto.js"
53
+ },
54
+ "./style.css": {
55
+ "types": "./dist/glass-toast.css.d.ts",
56
+ "default": "./dist/glass-toast.css"
57
+ },
58
+ "./package.json": "./package.json"
59
+ },
60
+ "files": [
61
+ "dist",
62
+ "README.md",
63
+ "README.zh-TW.md",
64
+ "CHANGELOG.md",
65
+ "LICENSE"
66
+ ],
67
+ "scripts": {
68
+ "dev": "vite docs",
69
+ "build:docs": "vite build docs",
70
+ "build": "vite build && vite build --mode umd && node scripts/build-css.mjs && tsc -p tsconfig.json && node scripts/build-cts.mjs",
71
+ "test": "vitest run",
72
+ "test:browser": "npm run build && playwright test",
73
+ "test:watch": "vitest",
74
+ "lint": "eslint src test scripts docs",
75
+ "check:package": "node scripts/check-package.mjs",
76
+ "prepublishOnly": "npm run build && npm run check:package"
77
+ },
78
+ "engines": {
79
+ "node": "^22.13.0 || >=24.0.0"
80
+ },
81
+ "overrides": {
82
+ "minimatch": "^11.0.1"
83
+ },
84
+ "devDependencies": {
85
+ "@arethetypeswrong/cli": "^0.18.5",
86
+ "@eslint/js": "^10.0.1",
87
+ "@playwright/test": "^1.64.1",
88
+ "eslint": "^10.14.0",
89
+ "globals": "^16.5.0",
90
+ "jsdom": "^29.1.1",
91
+ "lightningcss": "^1.32.0",
92
+ "publint": "^0.3.25",
93
+ "typescript": "^5.7.0",
94
+ "vite": "^8.3.5",
95
+ "vitest": "^4.1.12"
96
+ }
97
+ }