From bdd9d94a46898997b63f9c5f81a7822f62e9dcdc Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 17:21:45 +0100
Subject: [PATCH 01/68] Pkg - Upgrade monorepo stack (tsdown, Nuxt 4, Astro 5,
oxfmt)
* Core - Refactor createStore; Nuxt module compatibility for v4
* Pkg - Build notivue with tsdown; watch keeps dist for workspace consumers
* Pkg - Refresh devDependencies and exports; remove Vite lib config
* Root - pnpm 9, oxfmt, lint-staged; remove Prettier; ignore .cursor
* Playground - Nuxt 4, luxon optimizeDeps, Vite build options
* Tests - Cypress 15; Vite 7 for component testing with Astro/Vite toolchain
* Astro - Astro 5.18, integrations v5, vite ssr.noExternal for notivue
Co-authored-by: Cursor
---
.gitignore | 1 +
.oxfmtrc.json | 13 +++
.prettierrc | 24 -----
CODE_OF_CONDUCT.md | 30 +++----
README.md | 98 ++++++++++-----------
astro-playground/astro.config.mjs | 5 +-
astro-playground/package.json | 20 ++---
astro-playground/src/styles/reset.css | 3 +-
package.json | 33 +++----
packages/notivue/core/createStore.ts | 19 ++--
packages/notivue/nuxt/README.md | 88 +++++++++---------
packages/notivue/nuxt/module.mjs | 2 +-
packages/notivue/package.json | 69 +++++++--------
packages/notivue/tsconfig.json | 2 +-
packages/notivue/tsdown.config.ts | 66 ++++++++++++++
packages/notivue/vite.config.ts | 77 ----------------
playground/nuxt.config.ts | 21 ++---
playground/package.json | 18 ++--
tests/Notivue/config-pause-on-hover.cy.ts | 6 --
tests/Notivue/config-pause-on-touch.cy.ts | 6 --
tests/NotivueKeyboard/actions.cy.ts | 4 -
tests/NotivueKeyboard/entering-stream.cy.ts | 1 -
tests/NotivueKeyboard/leaving-stream.cy.ts | 1 -
tests/NotivueKeyboard/queue.cy.ts | 2 -
tests/package.json | 25 +++---
tests/tsconfig.json | 2 +-
26 files changed, 300 insertions(+), 336 deletions(-)
create mode 100644 .oxfmtrc.json
delete mode 100644 .prettierrc
create mode 100644 packages/notivue/tsdown.config.ts
delete mode 100644 packages/notivue/vite.config.ts
diff --git a/.gitignore b/.gitignore
index 00e1d7a7..97af418e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ pnpm-debug.log*
.vscode
.idea
+.cursor
.DS_Store
node_modules
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 00000000..71b1e70c
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
+ "printWidth": 100,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 3,
+ "trailingComma": "es5",
+ "useTabs": false,
+ "ignorePatterns": [".vscode/**", "node_modules/**", "dist/**", "target/**"],
+ "sortPackageJson": {
+ "sortScripts": true
+ }
+}
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 192e0d23..00000000
--- a/.prettierrc
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "printWidth": 100,
- "semi": false,
- "singleQuote": true,
- "tabWidth": 3,
- "trailingComma": "es5",
- "useTabs": false,
- "plugins": ["prettier-plugin-astro"],
- "overrides": [
- {
- "files": "README.md",
- "options": {
- "tabWidth": 2,
- "trailingComma": "none",
- "printWidth": 90
- }
- }, {
- "files": "*.astro",
- "options": {
- "parser": "astro"
- }
- }
- ]
-}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 96a930ba..2e5d313c 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -17,24 +17,24 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-- Demonstrating empathy and kindness toward other people
-- Being respectful of differing opinions, viewpoints, and experiences
-- Giving and gracefully accepting constructive feedback
-- Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-- Focusing on what is best not just for us as individuals, but for the
- overall community
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the
+ overall community
Examples of unacceptable behavior include:
-- The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-- Trolling, insulting or derogatory comments, and personal or political attacks
-- Public or private harassment
-- Publishing others' private information, such as a physical or email
- address, without their explicit permission
-- Other conduct which could reasonably be considered inappropriate in a
- professional setting
+- The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
## Enforcement Responsibilities
diff --git a/README.md b/README.md
index 1fbb5bf9..ab57b461 100644
--- a/README.md
+++ b/README.md
@@ -92,13 +92,13 @@ import { Notivue, Notification, push } from 'notivue'
- Push
+ Push
-
-
-
+
+
+
-
+
```
@@ -111,37 +111,37 @@ import { Notivue, push } from 'notivue'
- Push
-
-
-
-
-
- {{ item.message }}
-
-
-
-
-
-
-
-
-
-
-
+ Push
+
+
+
+
+
+ {{ item.message }}
+
+
+
+
+
+
+
+
+
+
+
```
@@ -155,14 +155,14 @@ import { Notivue, push } from 'notivue'
```ts
export default defineNuxtConfig({
- modules: ['notivue/nuxt'],
- css: [
- 'notivue/notification.css', // Only needed if using built-in
- 'notivue/animations.css' // Only needed if using default animations
- ],
- notivue: {
- // Options
- }
+ modules: ['notivue/nuxt'],
+ css: [
+ 'notivue/notification.css', // Only needed if using built-in
+ 'notivue/animations.css', // Only needed if using default animations
+ ],
+ notivue: {
+ // Options
+ },
})
```
@@ -170,13 +170,13 @@ export default defineNuxtConfig({
```vue
- Push
+ Push
-
-
-
+
+
+
-
+
```
diff --git a/astro-playground/astro.config.mjs b/astro-playground/astro.config.mjs
index c662b2b3..d8fad88a 100644
--- a/astro-playground/astro.config.mjs
+++ b/astro-playground/astro.config.mjs
@@ -13,7 +13,10 @@ export default defineConfig({
],
vite: {
optimizeDeps: {
- include: ['notivue'],
+ include: ['notivue', 'notivue/astro'],
+ },
+ ssr: {
+ noExternal: ['notivue'],
},
},
})
diff --git a/astro-playground/package.json b/astro-playground/package.json
index d6cf642c..79c0f674 100644
--- a/astro-playground/package.json
+++ b/astro-playground/package.json
@@ -1,25 +1,25 @@
{
"name": "notivue-astro-playground",
- "type": "module",
"private": true,
+ "type": "module",
"scripts": {
- "dev": "astro dev",
- "start": "astro dev",
+ "astro": "astro",
"build": "astro build",
+ "dev": "astro dev",
"preview": "astro preview",
- "astro": "astro"
+ "start": "astro dev"
},
"dependencies": {
- "@astrojs/react": "^3.6.2",
- "@astrojs/vue": "^4.5.0",
- "astro": "^4.15.1",
+ "@astrojs/react": "^5.0.1",
+ "@astrojs/vue": "^5.1.4",
+ "astro": "^5.18.1",
"notivue": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "vue": "^3.4.30"
+ "vue": "^3.5.30"
},
"devDependencies": {
- "@types/react": "^18.3.3",
- "@types/react-dom": "^18.3.0"
+ "@types/react": "^18.3.28",
+ "@types/react-dom": "^18.3.7"
}
}
diff --git a/astro-playground/src/styles/reset.css b/astro-playground/src/styles/reset.css
index ded5d7de..7e3fc53f 100644
--- a/astro-playground/src/styles/reset.css
+++ b/astro-playground/src/styles/reset.css
@@ -8,7 +8,8 @@
}
html {
- font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
+ font-family:
+ system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji';
}
diff --git a/package.json b/package.json
index b7c6044c..d5e957d3 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,29 @@
{
"name": "notivue-monorepo",
"private": true,
- "packageManager": "pnpm@8.14.3",
- "engines": {
- "node": ">=20.0.0"
- },
"scripts": {
- "dev": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground install && pnpm -C playground run dev --host\"",
- "dev:astro": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C astro-playground install && pnpm -C astro-playground run dev --host\"",
"build": "pnpm -C packages/notivue run build",
"build:playground": "pnpm build && pnpm install && pnpm -C playground run build",
+ "dev": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground run dev --host\"",
+ "dev:astro": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C astro-playground run dev --host\"",
+ "format": "oxfmt .",
+ "format:check": "oxfmt --check .",
+ "prepare": "husky",
"test": "pnpm build && pnpm install && pnpm -C tests run test",
- "test:gui": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C tests install && pnpm -C tests run test:gui\"",
- "test:unit": "pnpm -C tests run test:unit",
- "prepare": "husky"
+ "test:gui": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C tests run test:gui\"",
+ "test:unit": "pnpm -C tests run test:unit"
},
"devDependencies": {
- "concurrently": "^8.2.2",
- "husky": "^9.1.5",
- "lint-staged": "^15.2.9",
- "prettier": "^3.3.3",
- "prettier-plugin-astro": "^0.14.1"
+ "concurrently": "^9.1.2",
+ "husky": "^9.1.7",
+ "lint-staged": "^15.4.3",
+ "oxfmt": "^0.42.0"
},
"lint-staged": {
- "*.{js,ts,vue,json,css,md}": "prettier --write"
- }
+ "*.{js,ts,mts,cts,mjs,cjs,vue,json,css,md}": "oxfmt --write"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "packageManager": "pnpm@9.15.9"
}
diff --git a/packages/notivue/core/createStore.ts b/packages/notivue/core/createStore.ts
index b42c8d80..d5c750c1 100644
--- a/packages/notivue/core/createStore.ts
+++ b/packages/notivue/core/createStore.ts
@@ -56,7 +56,9 @@ export function createConfig(userConfig: NotivueConfig, isRunning: Readonly[
- unref(e.message).replace(/\uFEFF/g, '') === unref(item.message).replace(/\uFEFF/g, '') && // prettier-ignore
- unref(e.title) === unref(item.title) &&
- e.type === item.type
- )
+ return this.entries.value.find((e) => {
+ const sameMessage =
+ unref(e.message).replace(/\uFEFF/g, '') ===
+ unref(item.message).replace(/\uFEFF/g, '')
+ return sameMessage && unref(e.title) === unref(item.title) && e.type === item.type
+ })
},
get(id: string) {
return this.entries.value.find((e) => e.id === id)
@@ -256,7 +258,8 @@ export function createAnimations(
const id = el.dataset.notivueItem!
const item = items.get(id)
- if (!el || !item || item.animationAttrs.class === leaveClass) continue // prettier-ignore
+ if (!el || !item) continue
+ if (item.animationAttrs.class === leaveClass) continue
items.update(id, {
positionStyles: {
diff --git a/packages/notivue/nuxt/README.md b/packages/notivue/nuxt/README.md
index 92610e44..def00063 100644
--- a/packages/notivue/nuxt/README.md
+++ b/packages/notivue/nuxt/README.md
@@ -65,14 +65,14 @@ pnpm add notivue
```ts
export default defineNuxtConfig({
- modules: ['notivue/nuxt'],
- css: [
- 'notivue/notification.css', // Only needed if using built-in ]
- 'notivue/animations.css' // Only needed if using default animations
- ],
- notivue: {
- // Options
- }
+ modules: ['notivue/nuxt'],
+ css: [
+ 'notivue/notification.css', // Only needed if using built-in
+ 'notivue/animations.css', // Only needed if using default animations
+ ],
+ notivue: {
+ // Options
+ },
})
```
@@ -90,13 +90,13 @@ import type { NotivueConfig, NotivueItem /*, ... */ } from 'notivue'
```vue
- Push
+ Push
-
-
-
+
+
+
-
+
```
@@ -105,37 +105,37 @@ import type { NotivueConfig, NotivueItem /*, ... */ } from 'notivue'
```vue
- Push
-
-
-
-
-
- {{ item.message }}
-
-
-
-
-
-
-
-
-
-
-
+ Push
+
+
+
+
+
+ {{ item.message }}
+
+
+
+
+
+
+
+
+
+
+
```
diff --git a/packages/notivue/nuxt/module.mjs b/packages/notivue/nuxt/module.mjs
index a9cf2435..9a95a85b 100644
--- a/packages/notivue/nuxt/module.mjs
+++ b/packages/notivue/nuxt/module.mjs
@@ -8,7 +8,7 @@ const module = defineNuxtModule({
name: 'nuxt/notivue',
configKey: 'notivue',
compatibility: {
- nuxt: '>=3.5.0',
+ nuxt: '>=3.5.0 || ^4.0.0',
},
},
diff --git a/packages/notivue/package.json b/packages/notivue/package.json
index 98e22fc1..abff63d1 100644
--- a/packages/notivue/package.json
+++ b/packages/notivue/package.json
@@ -4,41 +4,48 @@
"private": false,
"description": "Powerful toast notification system for Vue and Nuxt",
"keywords": [
- "vue",
- "vuejs",
- "notification",
- "notifications",
"alert",
"alerts",
+ "notification",
+ "notifications",
+ "notivue",
"toast",
"toasts",
- "notivue"
+ "vue",
+ "vuejs"
],
"homepage": "https://notivue.smastrom.io/",
"bugs": {
"url": "https://github.com/smastrom/notivue/issues"
},
- "repository": {
- "type": "git",
- "url": "https://github.com/smastrom/notivue.git",
- "directory": "packages/notivue"
- },
"license": "MIT",
"author": {
"name": "Simone Mastromattei",
"email": "smastrom@proton.me"
},
- "sideEffects": false,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/smastrom/notivue.git",
+ "directory": "packages/notivue"
+ },
+ "files": [
+ "dist/*",
+ "nuxt/*",
+ "shared/exports.js"
+ ],
"type": "module",
+ "sideEffects": false,
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./nuxt": {
+ "types": "./nuxt/index.d.ts",
"import": "./nuxt/module.mjs",
- "require": "./nuxt/module.cjs",
- "types": "./nuxt/index.d.ts"
+ "require": "./nuxt/module.cjs"
},
"./astro": {
"import": "./dist/astro.js",
@@ -55,33 +62,25 @@
"./astro/notification-progress.css": "./dist/Notifications/notifications-progress.css",
"./astro/animations.css": "./dist/core/animations.css"
},
- "module": "dist/index.js",
- "types": "dist/index.d.ts",
- "files": [
- "dist/*",
- "nuxt/*",
- "shared/exports.js"
- ],
"scripts": {
- "prebuild": "cp ../../README.md ../../LICENSE .",
- "build": "rm -rf dist && vue-tsc && vite build && pnpm build:css",
- "postbuild": "rm -rf *.tgz && npm pack && ./scripts/verify-tarball.sh && node ./scripts/verify-exports.js",
+ "build": "rm -rf dist && vue-tsc --noEmit && tsdown && pnpm build:css",
"build:css": "esbuild ./Notifications/notifications.css ./Notifications/notifications-progress.css ./core/animations.css --bundle --outdir=dist --minify --target=chrome58,edge16,firefox57,node12,safari11",
- "watch": "rm -rf dist && concurrently \"vite build --watch\" \"pnpm build:css --watch\""
+ "postbuild": "rm -rf *.tgz && npm pack && ./scripts/verify-tarball.sh && node ./scripts/verify-exports.js",
+ "prebuild": "cp ../../README.md ../../LICENSE .",
+ "watch": "concurrently \"tsdown --watch\" \"pnpm build:css --watch\""
},
"devDependencies": {
- "@nuxt/kit": "^3.13.0",
- "@nuxt/schema": "^3.13.0",
- "@types/node": "^22.5.2",
- "@vitejs/plugin-vue": "5.1.3",
- "concurrently": "^8.2.2",
+ "@nuxt/kit": "^4.4.2",
+ "@nuxt/schema": "^4.4.2",
+ "@types/node": "^22.13.1",
+ "@vitejs/plugin-vue": "6.0.5",
+ "concurrently": "^9.1.2",
"defu": "^6.1.4",
- "esbuild": "^0.23.1",
- "typescript": "^5.5.4",
- "vite": "^5.4.2",
- "vite-plugin-dts": "^4.1.0",
- "vue": "3.4.38",
- "vue-tsc": "^2.1.4"
+ "esbuild": "^0.25.12",
+ "tsdown": "^0.21.4",
+ "typescript": "^5.7.3",
+ "vue": "3.5.13",
+ "vue-tsc": "^3.2.6"
},
"peerDependencies": {
"@nuxt/kit": ">=3.5.0",
diff --git a/packages/notivue/tsconfig.json b/packages/notivue/tsconfig.json
index f69d0129..12ab1090 100644
--- a/packages/notivue/tsconfig.json
+++ b/packages/notivue/tsconfig.json
@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2015",
"module": "ES2020",
- "moduleResolution": "Node",
+ "moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
diff --git a/packages/notivue/tsdown.config.ts b/packages/notivue/tsdown.config.ts
new file mode 100644
index 00000000..1d21bfe7
--- /dev/null
+++ b/packages/notivue/tsdown.config.ts
@@ -0,0 +1,66 @@
+import { writeFileSync } from 'node:fs'
+import { createRequire } from 'node:module'
+import { fileURLToPath } from 'node:url'
+
+import vue from '@vitejs/plugin-vue'
+import { defineConfig } from 'tsdown/config'
+
+const require = createRequire(import.meta.url)
+const { getFunctions, getObjects, getComponents } = require('./shared/exports.js') as {
+ getFunctions: (o?: { omit?: string[] }) => string[]
+ getObjects: (o?: { omit?: string[] }) => string[]
+ getComponents: (o?: { omit?: string[] }) => string[]
+}
+
+const path = (url: string) => fileURLToPath(new URL(url, import.meta.url))
+
+const isWatch = process.argv.includes('--watch')
+
+export default defineConfig({
+ entry: 'index.ts',
+ platform: 'browser',
+ target: 'es2015',
+ minify: isWatch
+ ? false
+ : {
+ compress: {
+ dropConsole: true,
+ },
+ },
+ // Watch must not wipe dist (e.g. monorepo dev:astro); production build still cleans via `pnpm build`.
+ clean: !isWatch,
+ outDir: 'dist',
+ dts: { vue: true },
+ deps: {
+ neverBundle: ['vue'],
+ },
+ alias: {
+ '@/core': path('./core'),
+ '@/shared': path('./shared'),
+ '@/Notivue': path('./Notivue'),
+ '@/NotivueSwipe': path('./NotivueSwipe'),
+ '@/NotivueKeyboard': path('./NotivueKeyboard'),
+ '@/Notifications': path('./Notifications'),
+ '@/astro': path('./astro'),
+ notivue: path('./index.ts'),
+ },
+ plugins: [vue()],
+ hooks: {
+ 'build:done'() {
+ const astroReExports = [
+ ...getFunctions({ omit: ['push', 'createNotivue'] }),
+ ...getComponents({ omit: ['Notivue'] }),
+ ...getObjects(),
+ ]
+ writeFileSync(
+ 'dist/astro.js',
+ [
+ 'export { pushAstro as push } from "./index.js";',
+ 'export { NotivueAstro as Notivue } from "./index.js";',
+ 'export { createNotivueAstro as createNotivue } from "./index.js";',
+ ...astroReExports.map((name) => `export { ${name} } from "./index.js";`),
+ ].join('\n')
+ )
+ },
+ },
+})
diff --git a/packages/notivue/vite.config.ts b/packages/notivue/vite.config.ts
deleted file mode 100644
index 9b40ce0d..00000000
--- a/packages/notivue/vite.config.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { defineConfig } from 'vite'
-import { fileURLToPath } from 'url'
-import { writeFileSync } from 'fs'
-
-import vue from '@vitejs/plugin-vue'
-import dts from 'vite-plugin-dts'
-
-// @ts-ignore
-import { getFunctions, getObjects, getComponents } from './shared/exports'
-
-const path = (url: string) => fileURLToPath(new URL(url, import.meta.url))
-
-const isFinalBundle = !process.argv.includes('--watch')
-
-export default defineConfig({
- resolve: {
- alias: {
- '@/core': path('./core'),
- '@/shared': path('./shared'),
- '@/Notivue': path('./Notivue'),
- '@/NotivueSwipe': path('./NotivueSwipe'),
- '@/NotivueKeyboard': path('./NotivueKeyboard'),
- '@/Notifications': path('./Notifications'),
- '@/astro': path('./astro'),
- notivue: path('./index.ts'),
- },
- },
- esbuild: {
- drop: isFinalBundle ? ['console'] : [],
- minifyIdentifiers: false,
- minifySyntax: false,
- },
- build: {
- emptyOutDir: isFinalBundle,
- target: 'es2015',
- minify: false,
- lib: {
- entry: 'index.ts',
- fileName: 'index',
- formats: ['es'],
- },
- rollupOptions: {
- external: ['vue'],
- output: {
- globals: {
- vue: 'Vue',
- },
- },
- },
- },
- plugins: [
- dts({
- rollupTypes: true,
- }),
- vue(),
- {
- name: 'write-astro-entry',
- closeBundle() {
- const astroReExports = [
- ...getFunctions({ omit: ['push', 'createNotivue'] }),
- ...getComponents({ omit: ['Notivue'] }),
- ...getObjects(),
- ]
-
- writeFileSync(
- 'dist/astro.js',
- [
- 'export { pushAstro as push } from "./index.js";',
- 'export { NotivueAstro as Notivue } from "./index.js";',
- 'export { createNotivueAstro as createNotivue } from "./index.js";',
- ...astroReExports.map((name) => `export { ${name} } from "./index.js";`),
- ].join('\n')
- )
- },
- },
- ],
-})
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 3109e3cb..b04277b6 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -1,6 +1,8 @@
import { getHead } from './utils/head'
export default defineNuxtConfig({
+ // Nuxt 4 defaults to srcDir `app/`; keep sources at project root
+ srcDir: '.',
modules: ['notivue/nuxt'],
ssr: true,
devtools: {
@@ -10,12 +12,8 @@ export default defineNuxtConfig({
componentIslands: true,
},
notivue: {
- // addPlugin: true,
- // startOnCreation: true,
notifications: {
- global: {
- // duration: Infinity,
- },
+ global: {},
},
},
nitro: {
@@ -25,17 +23,16 @@ export default defineNuxtConfig({
head: getHead(),
},
vite: {
- esbuild: {
- minifyIdentifiers: !import.meta.env.DEV,
- minifySyntax: !import.meta.env.DEV,
- },
- build: {
- minify: import.meta.env.DEV ? false : 'esbuild',
- cssMinify: 'lightningcss',
+ optimizeDeps: {
+ include: ['luxon'],
},
css: {
transformer: 'lightningcss',
},
+ build: {
+ minify: import.meta.env.DEV ? false : 'oxc',
+ cssMinify: 'lightningcss',
+ },
},
css: [
'assets/style.css',
diff --git a/playground/package.json b/playground/package.json
index b6f58ca5..9ac6f7f6 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -5,18 +5,18 @@
"build": "nuxt build",
"dev": "nuxi cleanup && nuxt dev --port=5173",
"generate": "nuxt generate",
- "preview": "nuxt preview",
- "postinstall": "nuxt prepare"
- },
- "devDependencies": {
- "@types/luxon": "^3.4.2",
- "@types/node": "^22.5.1",
- "lightningcss": "^1.26.0",
- "nuxt": "^3.13.0",
- "vue": "^3.4.38"
+ "postinstall": "nuxt prepare",
+ "preview": "nuxt preview"
},
"dependencies": {
"luxon": "^3.5.0",
"notivue": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/luxon": "^3.4.2",
+ "@types/node": "^22.13.1",
+ "lightningcss": "^1.29.3",
+ "nuxt": "^4.4.2",
+ "vue": "^3.5.13"
}
}
diff --git a/tests/Notivue/config-pause-on-hover.cy.ts b/tests/Notivue/config-pause-on-hover.cy.ts
index 6363cc95..ba55be98 100644
--- a/tests/Notivue/config-pause-on-hover.cy.ts
+++ b/tests/Notivue/config-pause-on-hover.cy.ts
@@ -10,11 +10,9 @@ describe('Pause on hover', () => {
.clickRandomStatic()
.wait(4000) // Remaining time 2000ms
-
.get('.Notification')
.realMouseMove(0, 0, { position: 'center' })
.wait(4000) // Any value greater than the remaining time
-
.get('.Notification')
.should('exist')
@@ -31,11 +29,9 @@ describe('Pause on hover', () => {
.clickRandomStatic()
.wait(4000) // Remaining: 2000ms
-
.get('.Notification')
.realMouseMove(0, 0, { position: 'center' })
.wait(4000) // Any value greater than the remaining time
-
.get('.Notification')
.should('not.exist')
})
@@ -48,11 +44,9 @@ describe('Pause on hover', () => {
.clickRandomStatic()
.wait(4000) // Remaining time 2000ms
-
.get('.Notification')
.realMouseMove(0, 0, { position: 'center' })
.wait(4000) // Any value greater than the remaining time
-
.get('.Notification')
.should('exist')
})
diff --git a/tests/Notivue/config-pause-on-touch.cy.ts b/tests/Notivue/config-pause-on-touch.cy.ts
index f57c4de5..0a432dae 100644
--- a/tests/Notivue/config-pause-on-touch.cy.ts
+++ b/tests/Notivue/config-pause-on-touch.cy.ts
@@ -10,11 +10,9 @@ describe('Pause on touch', () => {
.clickRandomStatic()
.wait(5000) // Remaining: 1000ms
-
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
.wait(2000) // More than the remaining time
-
.get('.Notification')
.should('exist')
})
@@ -24,11 +22,9 @@ describe('Pause on touch', () => {
.clickRandomStatic()
.wait(5000) // Remaining: 1000ms
-
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
.wait(2000) // More than the remaining time
-
.get('.Notification')
.should('not.exist')
})
@@ -41,11 +37,9 @@ describe('Pause on touch', () => {
.clickRandomStatic()
.wait(5000) // Remaining: 1000ms
-
.get('.Notification')
.trigger('pointerdown', { pointerType: 'touch' })
.wait(2000) // More than the remaining time
-
.get('.Notification')
.should('exist')
})
diff --git a/tests/NotivueKeyboard/actions.cy.ts b/tests/NotivueKeyboard/actions.cy.ts
index 0d8edbb9..852d6b5f 100644
--- a/tests/NotivueKeyboard/actions.cy.ts
+++ b/tests/NotivueKeyboard/actions.cy.ts
@@ -10,7 +10,6 @@ describe('Actions', () => {
.realPress('Tab') // Go last action of first container
.pushCandidateSilently() // id: 3
-
.focused()
.should('have.data', 'notivueContainer', 3)
})
@@ -27,7 +26,6 @@ describe('Actions', () => {
.realPress('Tab')
.realPress('Tab') // Go to action of 2nd container (id: 1)
-
.realPress(Math.random() > 0.5 ? 'Space' : 'Enter')
.focused()
@@ -47,7 +45,6 @@ describe('Actions', () => {
.realPress('Tab')
.realPress('Tab') // Go to action of last container
-
.realPress(Math.random() > 0.5 ? 'Space' : 'Enter')
cy.get('@relatedTarget').should('be.focused').checkLeaveAnnouncement()
@@ -67,7 +64,6 @@ describe('Actions', () => {
.realPress('Tab')
.realPress('Tab') // Go to action of 2nd container
-
.focused()
.realClick()
diff --git a/tests/NotivueKeyboard/entering-stream.cy.ts b/tests/NotivueKeyboard/entering-stream.cy.ts
index 38679149..5649acb3 100644
--- a/tests/NotivueKeyboard/entering-stream.cy.ts
+++ b/tests/NotivueKeyboard/entering-stream.cy.ts
@@ -91,7 +91,6 @@ describe('Entering the stream', () => {
cy.realPress('Tab') // Leave the stream
.realPress('Tab') // Try to enter again
-
.get('@relatedTarget')
.should('be.focused')
})
diff --git a/tests/NotivueKeyboard/leaving-stream.cy.ts b/tests/NotivueKeyboard/leaving-stream.cy.ts
index 93130d3d..1d3f669c 100644
--- a/tests/NotivueKeyboard/leaving-stream.cy.ts
+++ b/tests/NotivueKeyboard/leaving-stream.cy.ts
@@ -41,7 +41,6 @@ describe('Leaving the stream', () => {
}
cy.realPress('Tab') // Leave the stream
-
.get('@relatedTarget')
.should('be.focused')
.checkLeaveAnnouncement()
diff --git a/tests/NotivueKeyboard/queue.cy.ts b/tests/NotivueKeyboard/queue.cy.ts
index 8896d8db..3e2d68ed 100644
--- a/tests/NotivueKeyboard/queue.cy.ts
+++ b/tests/NotivueKeyboard/queue.cy.ts
@@ -8,7 +8,6 @@ describe('Queue', () => {
.realPress('Tab')
.realPress('Tab')
.realPress('Space') // Dismiss first candidate (id: 0)
-
.focused()
.and('have.data', 'notivueContainer', 1)
})
@@ -23,7 +22,6 @@ describe('Queue', () => {
.realPress('Tab')
.realPress('Tab')
.realPress('Space') // Dismiss last candidate (id: 1)
-
.focused()
.and('have.data', 'notivueContainer', 0)
})
diff --git a/tests/package.json b/tests/package.json
index 81ba3f9b..4b826b47 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -2,25 +2,26 @@
"name": "notivue-tests",
"private": true,
"scripts": {
+ "cypress:install": "cypress install",
"test": "cypress run --component --browser chrome",
"test:gui": "cypress open --component --browser chrome",
- "test:unit": "npx vitest run"
+ "test:unit": "vitest run"
},
"dependencies": {
"notivue": "workspace:*"
},
"devDependencies": {
- "@types/node": "^22.5.1",
- "@vitejs/plugin-vue": "^5.1.3",
+ "@types/node": "^22.13.1",
+ "@vitejs/plugin-vue": "^6.0.5",
"@vue/test-utils": "^2.4.6",
- "axe-core": "^4.10.0",
- "cypress": "^13.14.1",
- "cypress-axe": "^1.5.0",
- "cypress-real-events": "^1.13.0",
- "typescript": "^5.5.4",
- "vite": "^5.4.2",
- "vitest": "^2.0.5",
- "vue": "^3.4.38",
- "vue-tsc": "^2.1.4"
+ "axe-core": "^4.10.2",
+ "cypress": "^15.12.0",
+ "cypress-axe": "^1.6.0",
+ "cypress-real-events": "^1.14.0",
+ "typescript": "^5.7.3",
+ "vite": "^7.3.1",
+ "vitest": "^3.0.8",
+ "vue": "^3.5.13",
+ "vue-tsc": "^3.2.6"
}
}
diff --git a/tests/tsconfig.json b/tests/tsconfig.json
index d6c2911d..adc5bde2 100644
--- a/tests/tsconfig.json
+++ b/tests/tsconfig.json
@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
- "moduleResolution": "Node",
+ "moduleResolution": "Bundler",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
From 08223e1a887825eee9c42be4d481cd7a60aa23ac Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 18:02:08 +0100
Subject: [PATCH 02/68] Notification - Tip accent and progress corners (#76,
#78)
* CSS - Draw `--nv-tip-width` with a `::before` border instead of an inset box-shadow so radius and rounding stay clean (#78).
* CSS - Use full `border-radius` when a progress bar is present and `overflow: hidden` on the card so the bar and tip stay inside the rounded shape (#76).
Co-authored-by: Cursor
---
.../Notifications/notifications-progress.css | 8 --------
.../notivue/Notifications/notifications.css | 20 ++++++++++---------
2 files changed, 11 insertions(+), 17 deletions(-)
diff --git a/packages/notivue/Notifications/notifications-progress.css b/packages/notivue/Notifications/notifications-progress.css
index 53a6ba25..db5af81b 100644
--- a/packages/notivue/Notifications/notifications-progress.css
+++ b/packages/notivue/Notifications/notifications-progress.css
@@ -1,7 +1,5 @@
@media (prefers-reduced-motion: no-preference) {
.Notivue__notification:has(.Notivue__progress) {
- border-radius: var(--nv-radius, 0) var(--nv-radius, 0) var(--nv-radius, 0) 0;
-
& .Notivue__content-message {
margin-bottom: var(--nv-progress-height, 4px);
}
@@ -16,17 +14,11 @@
background-color: var(--nv-accent);
animation: Notivue__progress-kf var(--nv-anim-dur) linear forwards;
transform-origin: left;
- border-radius: 0 var(--nv-radius, 0) var(--nv-radius, 0) 0;
}
[dir='rtl'] {
& .Notivue__progress {
transform-origin: right;
- border-radius: var(--nv-radius, 0) 0 0 var(--nv-radius, 0);
- }
-
- & .Notivue__notification:has(.Notivue__progress) {
- border-radius: var(--nv-radius, 0) var(--nv-radius, 0) 0 var(--nv-radius, 0);
}
}
diff --git a/packages/notivue/Notifications/notifications.css b/packages/notivue/Notifications/notifications.css
index 42b15308..b1b907d7 100644
--- a/packages/notivue/Notifications/notifications.css
+++ b/packages/notivue/Notifications/notifications.css
@@ -36,8 +36,6 @@
}
.Notivue__notification {
- --tip-width: calc(var(--tip-width-fx, 1) * var(--nv-tip-width, 0px));
-
box-sizing: border-box;
position: relative;
width: var(--nv-min-width, auto);
@@ -47,10 +45,18 @@
align-items: var(--nv-y-align);
border-radius: var(--nv-radius, 0);
border: var(--nv-border-width, 0) solid var(--nv-border, var(--nv-global-border));
+ overflow: hidden;
- box-shadow:
- var(--nv-shadow, 0 0 rgba(0, 0, 0, 0)),
- inset var(--tip-width) 0 0 var(--nv-accent);
+ box-shadow: var(--nv-shadow, 0 0 rgba(0, 0, 0, 0));
+
+ &::before {
+ content: '';
+ flex-shrink: 0;
+ align-self: stretch;
+ box-sizing: border-box;
+ width: 0;
+ border-inline-start: var(--nv-tip-width, 0px) solid var(--nv-accent);
+ }
& * {
box-sizing: border-box;
@@ -207,10 +213,6 @@
}
[dir='rtl'] {
- & .Notivue__notification {
- --tip-width-fx: -1;
- }
-
& .Notivue__icon {
margin: var(--nv-spacing) var(--nv-spacing) var(--nv-spacing) 0;
}
From 666706eb187024bdacb3ee500bd1a0a98b558603 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 18:12:00 +0100
Subject: [PATCH 03/68] Pkg - Expand README with Astro, headless example, docs
pointer
* Mention Astro in the tagline; add Installation callout linking to full docs.
* Surface headless custom markup example outside a details block.
* Add Astro section with import/CSS paths and Astro 6 not supported note.
Co-authored-by: Cursor
---
README.md | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index ab57b461..e792e30a 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
# Notivue
-### Powerful toast notification system for Vue and Nuxt
+### Powerful toast notification system for Vue, Nuxt and Astro
[Live Demo](https://notivue.smastrom.io) - [Documentation](https://docs.notivue.smastrom.io)
@@ -60,11 +60,13 @@ pnpm add notivue
# bun i notivue
```
+> :bulb: Beyond this quick start — Nuxt/Astro setup, `push` and callbacks, stream config, built-in styling, headless mode, full API — lives in the [documentation](https://docs.notivue.smastrom.io).
+
## Vite
-> :bulb: See [↓ below](#nuxt) for **Nuxt**
+> :bulb: See [↓ below](#nuxt) for **Nuxt** and [↓ Astro](#astro).
**main.js/ts**
@@ -102,8 +104,8 @@ import { Notivue, Notification, push } from 'notivue'
```
-
-Headless, with custom components
+**App.vue (headless — your own markup)**
+_No `notification.css` needed; wire `aria-live` / `role` and `item.clear` yourself._
```vue
- Push
+ Notify
@@ -109,11 +109,11 @@ _No `notification.css` needed; wire `aria-live` / `role` and `item.clear` yourse
```vue
- Push
+ Notify
@@ -170,7 +170,7 @@ export default defineNuxtConfig({
```vue
- Push
+ Notify
diff --git a/astro-playground/astro.config.mjs b/astro-playground/astro.config.mjs
index d8fad88a..45a2a9ad 100644
--- a/astro-playground/astro.config.mjs
+++ b/astro-playground/astro.config.mjs
@@ -3,6 +3,7 @@ import { defineConfig } from 'astro/config'
import vue from '@astrojs/vue'
import react from '@astrojs/react'
+/** Examples use `push` from `notivue/astro` for regression testing; `notify` is the same object. */
export default defineConfig({
integrations: [
vue({
diff --git a/packages/notivue/NotivueKeyboard/NotivueKeyboardImpl.vue b/packages/notivue/NotivueKeyboard/NotivueKeyboardImpl.vue
index 5c112726..1f24647b 100644
--- a/packages/notivue/NotivueKeyboard/NotivueKeyboardImpl.vue
+++ b/packages/notivue/NotivueKeyboard/NotivueKeyboardImpl.vue
@@ -12,13 +12,13 @@ import {
} from 'vue'
import { useNotivue, useStore } from '@/core/useStore'
-import { push } from '@/core/createPush'
+import { notify } from '@/core/createNotify'
import { focusableEls, keyboardInjectionKey, DEFAULT_PROPS } from './constants'
import { useKeyboardFocus } from './useKeyboardFocus'
import { useLastFocused } from './useLastFocused'
import type {
- PushOptions,
+ NotifyOptions,
TabIndexValue,
ContainersTabIndexMap,
NotivueKeyboardProps,
@@ -48,12 +48,12 @@ const sharedOptions = {
},
} as const
-const leavePushOptions = computed(() => ({
+const leaveNotifyOptions = computed(() => ({
message: leaveMessage.value,
...sharedOptions,
}))
-const emptyPushOptions = computed(() => ({
+const emptyNotifyOptions = computed(() => ({
message: emptyMessage.value,
...sharedOptions,
}))
@@ -126,7 +126,7 @@ function onStreamLeave({ announce = true } = {}) {
if (announce && announcementsCount < maxAnnouncements.value) {
announcementsCount++
- push.info(leavePushOptions.value)
+ notify.info(leaveNotifyOptions.value)
}
}
@@ -359,7 +359,7 @@ function onComboKeyDown(e: KeyboardEvent) {
if (candidateContainers.value.length > 0) {
onStreamEnter()
} else {
- push.info(emptyPushOptions.value)
+ notify.info(emptyNotifyOptions.value)
}
}
}
diff --git a/packages/notivue/astro/push.ts b/packages/notivue/astro/push.ts
index b9075139..670cadb2 100644
--- a/packages/notivue/astro/push.ts
+++ b/packages/notivue/astro/push.ts
@@ -34,7 +34,7 @@ export function pushEvent>(
return pushResult
}
-export const push = {
+const astroNotifyApi = {
success: (options: PushOptions) => pushEvent({ ...options, type: 'success' }),
info: (options: PushOptions) => pushEvent({ ...options, type: 'info' }),
error: (options: PushOptions) => pushEvent({ ...options, type: 'error' }),
@@ -48,3 +48,6 @@ export const push = {
window.dispatchEvent(new CustomEvent('notivue:destroy-all'))
},
} as Push
+
+export const push = astroNotifyApi
+export const notify = astroNotifyApi
diff --git a/packages/notivue/core/createInstance.ts b/packages/notivue/core/createInstance.ts
index 572795d2..8ffec587 100644
--- a/packages/notivue/core/createInstance.ts
+++ b/packages/notivue/core/createInstance.ts
@@ -1,9 +1,9 @@
import { readonly, ref } from 'vue'
-import { createPushMock, setPush } from './createPush'
+import { createNotifyMock, setNotify } from './createNotify'
import { createStoreWatchers } from './createStoreWatchers'
-import type { NotivueStore, Push } from 'notivue'
+import type { NotivueStore, Notify } from 'notivue'
export let startInstance: () => void = () => {}
export let stopInstance: () => void = () => {}
@@ -12,10 +12,10 @@ export function createInstance(startOnCreation: boolean) {
const isRunning = ref(startOnCreation)
const isRunningReadonly = readonly(isRunning)
- function setupInstance(store: NotivueStore, push: Push) {
+ function setupInstance(store: NotivueStore, notify: Notify) {
const watchStore = () => createStoreWatchers(store)
- if (startOnCreation) setPush(push)
+ if (startOnCreation) setNotify(notify)
let unwatchStore = startOnCreation ? watchStore() : [() => {}]
@@ -24,7 +24,7 @@ export function createInstance(startOnCreation: boolean) {
startInstance() {
if (isRunning.value) return
- setPush(push)
+ setNotify(notify)
unwatchStore = watchStore()
isRunning.value = true
@@ -36,7 +36,7 @@ export function createInstance(startOnCreation: boolean) {
store.queue.clear()
store.items.clearLifecycleEvents()
- setPush(createPushMock())
+ setNotify(createNotifyMock())
unwatchStore.forEach((unwatch) => unwatch())
isRunning.value = false
diff --git a/packages/notivue/core/createNotify.ts b/packages/notivue/core/createNotify.ts
new file mode 100644
index 00000000..1e65b2a5
--- /dev/null
+++ b/packages/notivue/core/createNotify.ts
@@ -0,0 +1,71 @@
+import { unref } from 'vue'
+
+import { NotificationTypeKeys as NType } from './constants'
+import { createNotifyProxies } from './createStore'
+
+import type { NotificationType, Notify, NotifyOptions, NotifyParameter } from 'notivue'
+
+export const notify = createNotifyMock()
+
+/** @deprecated Use `notify` */
+export const push = notify
+
+export function setNotify(n: Notify) {
+ Object.assign(notify, n)
+}
+
+/** @deprecated Use setNotify */
+export const setPush = setNotify
+
+export function createNotify(proxies: ReturnType): Notify {
+ let createCount = 0
+
+ function dispatch(options: NotifyParameter, type: NotificationType, id = `${createCount++}`) {
+ if (typeof unref(options) === 'string') {
+ options = { message: options } as NotifyOptions
+ }
+
+ proxies.notify({ ...(options as NotifyOptions), id, type })
+
+ return {
+ id,
+ clear: () => proxies.clear(id),
+ destroy: () => proxies.clear(id, { isDestroy: true }),
+ }
+ }
+
+ return {
+ success: (options) => dispatch(options, NType.SUCCESS),
+ error: (options) => dispatch(options, NType.ERROR),
+ warning: (options) => dispatch(options, NType.WARNING),
+ info: (options) => dispatch(options, NType.INFO),
+ promise: (options) => {
+ const { id, clear, destroy } = dispatch(options, NType.PROMISE)
+
+ return {
+ resolve: (options) => dispatch(options, NType.PROMISE_RESOLVE, id),
+ reject: (options) => dispatch(options, NType.PROMISE_REJECT, id),
+ success: (options) => dispatch(options, NType.PROMISE_RESOLVE, id),
+ error: (options) => dispatch(options, NType.PROMISE_REJECT, id),
+ clear,
+ destroy,
+ }
+ },
+ load(options) {
+ return this.promise(options)
+ },
+ clearAll: () => proxies.clearAll(),
+ destroyAll: () => proxies.destroyAll(),
+ }
+}
+
+export function createNotifyMock(): Notify {
+ const noop = new Proxy({}, { get: () => () => {} }) as any
+ return createNotify(noop)
+}
+
+/** @deprecated Use createNotifyMock */
+export const createPushMock = createNotifyMock
+
+/** @deprecated Use createNotify */
+export const createPush = createNotify
diff --git a/packages/notivue/core/createNotivue.ts b/packages/notivue/core/createNotivue.ts
index d0e0c400..f0fcb798 100644
--- a/packages/notivue/core/createNotivue.ts
+++ b/packages/notivue/core/createNotivue.ts
@@ -1,25 +1,26 @@
import type { App, Plugin } from 'vue'
import type { NotivueConfig } from 'notivue'
-import { createPush } from './createPush'
+import { createNotify } from './createNotify'
import { notivueInjectionKey, notivueInstanceInjectionKey } from './symbols'
import { createInstance } from './createInstance'
-import { createPushProxies, createStore } from './createStore'
+import { createNotifyProxies, createStore } from './createStore'
export function createProvides(startOnCreation: boolean, userConfig: NotivueConfig) {
const { setupInstance, isRunning } = createInstance(startOnCreation)
const store = createStore(userConfig, isRunning)
- const proxies = createPushProxies(store)
- const push = Object.freeze(createPush(proxies))
+ const proxies = createNotifyProxies(store)
+ const notify = Object.freeze(createNotify(proxies))
- const instance = setupInstance(store, push)
+ const instance = setupInstance(store, notify)
return {
store,
instance,
- push,
+ notify,
+ push: notify,
}
}
@@ -31,12 +32,13 @@ export function createNotivue(
return {
install(app: App) {
const { startOnCreation = true, ...userConfig } = pluginConfig
- const { store, instance, push } = createProvides(startOnCreation, userConfig)
+ const { store, instance, notify } = createProvides(startOnCreation, userConfig)
app.provide(notivueInstanceInjectionKey, instance)
app.provide(notivueInjectionKey, store)
- app.config.globalProperties.$push ||= push
+ app.config.globalProperties.$notify ||= notify
+ app.config.globalProperties.$push ||= notify
},
}
}
diff --git a/packages/notivue/core/createPush.ts b/packages/notivue/core/createPush.ts
deleted file mode 100644
index 2481861a..00000000
--- a/packages/notivue/core/createPush.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { unref } from 'vue'
-
-import { NotificationTypeKeys as NType } from './constants'
-import { createPushProxies } from './createStore'
-
-import type { NotificationType, Push, PushOptions, PushParameter } from 'notivue'
-
-export const push = createPushMock()
-
-export function setPush(p: Push) {
- Object.assign(push, p)
-}
-
-export function createPush(proxies: ReturnType): Push {
- let createCount = 0
-
- function push(options: PushParameter, type: NotificationType, id = `${createCount++}`) {
- if (typeof unref(options) === 'string') {
- options = { message: options } as PushOptions
- }
-
- proxies.push({ ...(options as PushOptions), id, type })
-
- return {
- id,
- clear: () => proxies.clear(id),
- destroy: () => proxies.clear(id, { isDestroy: true }),
- }
- }
-
- return {
- success: (options) => push(options, NType.SUCCESS),
- error: (options) => push(options, NType.ERROR),
- warning: (options) => push(options, NType.WARNING),
- info: (options) => push(options, NType.INFO),
- promise: (options) => {
- const { id, clear, destroy } = push(options, NType.PROMISE)
-
- return {
- resolve: (options) => push(options, NType.PROMISE_RESOLVE, id),
- reject: (options) => push(options, NType.PROMISE_REJECT, id),
- success: (options) => push(options, NType.PROMISE_RESOLVE, id),
- error: (options) => push(options, NType.PROMISE_REJECT, id),
- clear,
- destroy,
- }
- },
- load(options) {
- return this.promise(options)
- },
- clearAll: () => proxies.clearAll(),
- destroyAll: () => proxies.destroyAll(),
- }
-}
-
-export function createPushMock(): Push {
- const noop = new Proxy({}, { get: () => () => {} }) as any
- return createPush(noop)
-}
diff --git a/packages/notivue/core/createStore.ts b/packages/notivue/core/createStore.ts
index d5c750c1..0a05ca64 100644
--- a/packages/notivue/core/createStore.ts
+++ b/packages/notivue/core/createStore.ts
@@ -14,7 +14,7 @@ import type {
DeepPartial,
StoreItem,
NotivueConfig,
- PushOptionsWithInternals,
+ NotifyOptionsWithInternals,
Obj,
ConfigSlice,
ItemsSlice,
@@ -360,7 +360,7 @@ export function createTimeouts(items: ItemsSlice, animations: AnimationsSlice) {
}
}
-export function createPushProxies({
+export function createNotifyProxies({
config,
items,
queue,
@@ -387,7 +387,7 @@ export function createPushProxies({
animations.playLeave(id, { isUserTriggered: true, isDestroy })
},
- push(options: PushOptionsWithInternals) {
+ notify(options: NotifyOptionsWithInternals) {
const entry = mergeOptions(config.notifications.value, options)
const createdAt = Date.now()
@@ -476,3 +476,6 @@ export function createPushProxies({
},
}
}
+
+/** @deprecated Use createNotifyProxies */
+export const createPushProxies = createNotifyProxies
diff --git a/packages/notivue/core/types.ts b/packages/notivue/core/types.ts
index d85d2839..b631ca11 100644
--- a/packages/notivue/core/types.ts
+++ b/packages/notivue/core/types.ts
@@ -123,78 +123,115 @@ export interface HiddenInternalItemData {
/** Options added internally when creating a notification. */
export type InternalItemData = ExposedInternalItemData & HiddenInternalItemData
-// Push
+// Notify (public API; `push` kept as an alias for backwards compatibility)
-export interface PushProps {
+export interface NotifyProps {
props?: T
}
-export interface PushSpecificOptions {
+/** @deprecated Use NotifyProps */
+export type PushProps = NotifyProps
+
+export interface NotifySpecificOptions {
skipQueue?: boolean
ariaLiveOnly?: boolean
}
-export interface PushCallbacks {
+/** @deprecated Use NotifySpecificOptions */
+export type PushSpecificOptions = NotifySpecificOptions
+
+export interface NotifyCallbacks {
onAutoClear?: (item: NotivueItem) => void
onManualClear?: (item: NotivueItem) => void
}
-/** Defined by the user when calling push() */
-export type PushOptions = NotificationOptions &
- PushProps &
- PushSpecificOptions &
- PushCallbacks
+/** @deprecated Use NotifyCallbacks */
+export type PushCallbacks = NotifyCallbacks
+
+/** Defined by the user when calling notify() */
+export type NotifyOptions = NotificationOptions &
+ NotifyProps &
+ NotifySpecificOptions &
+ NotifyCallbacks
+
+/** @deprecated Use NotifyOptions */
+export type PushOptions = NotifyOptions
-/** Added in background after calling push() */
-export type InternalPushOptions = { id: string; type: NotificationType }
+/** Added in background after calling notify() */
+export type InternalNotifyOptions = { id: string; type: NotificationType }
-export type PushOptionsWithInternals = PushOptions & InternalPushOptions
+/** @deprecated Use InternalNotifyOptions */
+export type InternalPushOptions = InternalNotifyOptions
+
+export type NotifyOptionsWithInternals = NotifyOptions &
+ InternalNotifyOptions
+
+/** @deprecated Use NotifyOptionsWithInternals */
+export type PushOptionsWithInternals = NotifyOptionsWithInternals
/** Final shape of the store item */
export type StoreItem = DeepRequired &
- Required> &
- InternalPushOptions &
+ Required> &
+ InternalNotifyOptions &
InternalItemData &
- PushSpecificOptions &
- PushCallbacks
+ NotifySpecificOptions &
+ NotifyCallbacks
/** Portion of the store item exposed to slot */
export type NotivueItem = Omit, keyof HiddenInternalItemData>
-export type PushParameter =
- | PushOptions
+export type NotifyParameter =
+ | NotifyOptions
| Exclude // NonNullable doesn't work?
-export type PushStatic = (
- options: PushParameter
+/** @deprecated Use NotifyParameter */
+export type PushParameter = NotifyParameter
+
+export type NotifyStatic = (
+ options: NotifyParameter
) => NotificationClearMethods
-export type PushPromiseReturnMethod = (
- options: PushParameter
+/** @deprecated Use NotifyStatic */
+export type PushStatic = NotifyStatic
+
+export type NotifyPromiseReturnMethod = (
+ options: NotifyParameter
) => NotificationClearMethods
-export interface PushPromiseReturn {
- resolve: PushPromiseReturnMethod
- success: PushPromiseReturnMethod
- reject: PushPromiseReturnMethod
- error: PushPromiseReturnMethod
+/** @deprecated Use NotifyPromiseReturnMethod */
+export type PushPromiseReturnMethod = NotifyPromiseReturnMethod
+
+export interface NotifyPromiseReturn {
+ resolve: NotifyPromiseReturnMethod
+ success: NotifyPromiseReturnMethod
+ reject: NotifyPromiseReturnMethod
+ error: NotifyPromiseReturnMethod
}
-export type PushPromise = (
- options: PushParameter
-) => NotificationClearMethods & PushPromiseReturn
+/** @deprecated Use NotifyPromiseReturn */
+export type PushPromiseReturn = NotifyPromiseReturn
+
+export type NotifyPromise = (
+ options: NotifyParameter
+) => NotificationClearMethods & NotifyPromiseReturn
+
+/** @deprecated Use NotifyPromise */
+export type PushPromise = NotifyPromise
-export interface Push {
- success: PushStatic
- error: PushStatic
- info: PushStatic
- warning: PushStatic
- promise: PushPromise
- load: PushPromise
+export interface Notify {
+ success: NotifyStatic
+ error: NotifyStatic
+ info: NotifyStatic
+ warning: NotifyStatic
+ promise: NotifyPromise
+ load: NotifyPromise
clearAll: () => void
destroyAll: () => void
}
+/** @deprecated Use Notify */
+export type Push = Notify
+
export type ConfigSlice = ToRefs & {
update: (newConfig: UpdateParam) => void
}
@@ -238,7 +275,10 @@ export type NotivueNotificationType = NotificationType
// New v2.1.1 aliases
-export type PushClearMethods = NotificationClearMethods
+export type NotifyClearMethods = NotificationClearMethods
+
+/** @deprecated Use NotifyClearMethods */
+export type PushClearMethods = NotifyClearMethods
// New v2.4.0 aliases
@@ -247,6 +287,9 @@ export type NotificationTypes = NotificationType
// Aliases prev 1.2.0
export type NotivueSlot = NotivueItem
-export type UserPushOptions = PushOptions
+export type UserNotifyOptions = NotifyOptions
+
+/** @deprecated Use UserNotifyOptions */
+export type UserPushOptions = UserNotifyOptions
export type ClearFunctions = NotificationClearMethods
export type ClearMethods = NotificationClearMethods
diff --git a/packages/notivue/core/useStore.ts b/packages/notivue/core/useStore.ts
index 042e16e4..4ca71701 100644
--- a/packages/notivue/core/useStore.ts
+++ b/packages/notivue/core/useStore.ts
@@ -2,7 +2,7 @@ import { inject, computed, toRefs, reactive, readonly, ref } from 'vue'
import { isSSR, getSlotItem } from './utils'
import { notivueInjectionKey, notivueInstanceInjectionKey } from './symbols'
-import { push } from './createPush'
+import { notify } from './createNotify'
import { DEFAULT_CONFIG } from './constants'
import type {
@@ -70,16 +70,27 @@ export function useNotivue(): UseNotivueReturn {
}
/**
- * @deprecated
+ * Returns the shared `notify` API (same object as legacy `push`).
*
- * Since version 2.0.0, import `push` directly instead.
+ * ```ts
+ * import { notify } from 'notivue'
+ * ```
+ *
+ * @docs https://docs.notivue.smastrom.io/api/push
+ */
+export function useNotify() {
+ return notify
+}
+
+/**
+ * @deprecated Use `useNotify` or import `notify` / `push` directly.
*
* ```ts
- * import { push } from 'notivue'
+ * import { notify } from 'notivue'
* ```
*/
export function usePush() {
- return push
+ return notify
}
/**
diff --git a/packages/notivue/index.ts b/packages/notivue/index.ts
index 23401181..98f87259 100644
--- a/packages/notivue/index.ts
+++ b/packages/notivue/index.ts
@@ -1,5 +1,5 @@
-export { push } from '@/core/createPush'
-export { push as pushAstro } from './astro/push'
+export { notify, push } from '@/core/createNotify'
+export { notify as notifyAstro, push as pushAstro } from './astro/push'
export { updateConfig } from '@/core/createStore'
export { createNotivue } from '@/core/createNotivue'
@@ -7,7 +7,13 @@ export { createNotivue as createNotivueAstro } from './astro/createNotivue'
export { startInstance, stopInstance } from '@/core/createInstance'
-export { usePush, useNotivue, useNotifications, useNotivueInstance } from '@/core/useStore'
+export {
+ useNotify,
+ usePush,
+ useNotivue,
+ useNotifications,
+ useNotivueInstance,
+} from '@/core/useStore'
export { useNotivueKeyboard } from '@/NotivueKeyboard/useNotivueKeyboard'
export { default as Notivue } from '@/Notivue/Notivue.vue'
diff --git a/packages/notivue/shared/exports.js b/packages/notivue/shared/exports.js
index fac899e9..0c582bbe 100644
--- a/packages/notivue/shared/exports.js
+++ b/packages/notivue/shared/exports.js
@@ -1,11 +1,13 @@
export const exports = {
functions: [
'createNotivue',
+ 'notify',
'push',
'updateConfig',
'startInstance',
'stopInstance',
+ 'useNotify',
'usePush',
'useNotivue',
'useNotivueInstance',
@@ -34,7 +36,7 @@ export const exports = {
'NotificationProgress',
'NotificationsProgress', // Alias
],
- astro: ['NotivueAstro', 'pushAstro', 'createNotivueAstro'],
+ astro: ['NotivueAstro', 'notifyAstro', 'pushAstro', 'createNotivueAstro'],
}
const getExports = (type, omit) => exports[type].filter((name) => !omit.includes(name))
diff --git a/packages/notivue/tsdown.config.ts b/packages/notivue/tsdown.config.ts
index 1d21bfe7..a1abaf92 100644
--- a/packages/notivue/tsdown.config.ts
+++ b/packages/notivue/tsdown.config.ts
@@ -48,13 +48,14 @@ export default defineConfig({
hooks: {
'build:done'() {
const astroReExports = [
- ...getFunctions({ omit: ['push', 'createNotivue'] }),
+ ...getFunctions({ omit: ['notify', 'push', 'createNotivue'] }),
...getComponents({ omit: ['Notivue'] }),
...getObjects(),
]
writeFileSync(
'dist/astro.js',
[
+ 'export { notifyAstro as notify } from "./index.js";',
'export { pushAstro as push } from "./index.js";',
'export { NotivueAstro as Notivue } from "./index.js";',
'export { createNotivueAstro as createNotivue } from "./index.js";',
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index a803f811..84444936 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -1,5 +1,6 @@
import { getHead } from './utils/head'
+/** App sources intentionally use `push` (not `notify`) for regression testing; both APIs are equivalent. */
export default defineNuxtConfig({
srcDir: '.',
modules: ['notivue/nuxt'],
From f0efb71e9f49cc50628c8c6a52cd6c7eab9b40cc Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 18:33:53 +0100
Subject: [PATCH 07/68] Notification - NotificationSwipe as primary name
* Notification - NotificationSwipe export and NotificationSwipeProps; NotivueSwipe remains deprecated alias.
* Tests, Demo, Astro playground - use NotificationSwipe in examples; Cypress CyNotificationSwipeProps.
* Pkg - exports manifest and README feature line document the alias.
Co-authored-by: Cursor
---
README.md | 2 +-
astro-playground/src/components/Notivue.vue | 6 +++---
.../notivue/Notivue/composables/useTouchEvents.ts | 2 +-
packages/notivue/NotivueSwipe/NotivueSwipe.vue | 12 ++++++------
packages/notivue/NotivueSwipe/types.ts | 7 +++++--
packages/notivue/index.ts | 2 ++
packages/notivue/nuxt/README.md | 2 +-
packages/notivue/shared/exports.js | 3 ++-
playground/app.vue | 4 ++--
tests/NotivueSwipe/components/Notivue.vue | 13 ++++++++-----
tests/cypress/support/commands-swipe.ts | 4 ++--
11 files changed, 33 insertions(+), 24 deletions(-)
diff --git a/README.md b/README.md
index 392d2e15..24b55286 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ _Themes, icons, progress bar, and native RTL support_
_Use your own components while Notivue handles the rest_
**💊 Drop-in components to enhance notifications**
-_NotivueSwipe_, _NotivueKeyboard_, all optional and customizable\_
+_NotificationSwipe_ (alias `NotivueSwipe`), _NotivueKeyboard_, all optional and customizable\_
**🌀 Dynamic Notifications**
_Update pending notifications with a breeze_
diff --git a/astro-playground/src/components/Notivue.vue b/astro-playground/src/components/Notivue.vue
index 29aadea4..ca2b79b1 100644
--- a/astro-playground/src/components/Notivue.vue
+++ b/astro-playground/src/components/Notivue.vue
@@ -1,11 +1,11 @@
-
+
-
+
diff --git a/packages/notivue/Notivue/composables/useTouchEvents.ts b/packages/notivue/Notivue/composables/useTouchEvents.ts
index 6e480aef..2acc660d 100644
--- a/packages/notivue/Notivue/composables/useTouchEvents.ts
+++ b/packages/notivue/Notivue/composables/useTouchEvents.ts
@@ -12,7 +12,7 @@ import { isMouse } from '@/Notivue/utils'
* If users keep tapping on the stream, once timeouts
* are resumed, they will pause again after 2 seconds and so on.
*
- * This is never triggered when NotivueSwipe is used as that
+ * This is never triggered when NotificationSwipe is used as that
* pointerdown event is not propagated.
*/
diff --git a/packages/notivue/NotivueSwipe/NotivueSwipe.vue b/packages/notivue/NotivueSwipe/NotivueSwipe.vue
index ec27d836..b3bf65d0 100644
--- a/packages/notivue/NotivueSwipe/NotivueSwipe.vue
+++ b/packages/notivue/NotivueSwipe/NotivueSwipe.vue
@@ -16,18 +16,18 @@ import { isMouse } from '@/Notivue/utils'
import { NotificationTypeKeys as NType } from '@/core/constants'
import { DEFAULT_PROPS, DEBOUNCE, RETURN_DUR } from './constants'
-import type { NotivueSwipeProps } from 'notivue'
+import type { NotificationSwipeProps } from 'notivue'
/**
* MOUSE - Notivue's mouse events (get from 'useMouseEvents') will still handle the pause/resume logic
- * on hover. NotivueSwipe will only additionally pause timeouts while swiping and resume them
+ * on hover. NotificationSwipe only additionally pauses timeouts while swiping and resumes them
* when clearing.
*
* TOUCH / PEN - Notivue's touch events (get from 'useTouchEvents') execution is prevented when
- * using NotivueSwipe. That's because a more granular timeout control is required due
+ * using NotificationSwipe. That's because a more granular timeout control is required due
* to all possible interactions hence the whole touch logic is handled here.
*
- * When releasing, leaving or clearing a notification via Swipe a small debounce time is added to
+ * When releasing, leaving or clearing a notification via swipe, a small debounce time is added to
* improve UX.
*/
@@ -37,7 +37,7 @@ const { items, timeouts, elements, animations } = useStore()
// Props
-const props = withDefaults(defineProps(), DEFAULT_PROPS)
+const props = withDefaults(defineProps(), DEFAULT_PROPS)
const touchOnly = toRef(props, 'touchOnly')
const exclude = toRef(props, 'exclude')
@@ -179,7 +179,7 @@ function onPointerDown(e: PointerEvent) {
/**
* Prevents `useTouchEvents` events to fire, which is what
- * we're looking for so they doen't interfere with NotivueSwipe logic.
+ * we're looking for so they don't interfere with NotificationSwipe logic.
*/
e.stopPropagation()
diff --git a/packages/notivue/NotivueSwipe/types.ts b/packages/notivue/NotivueSwipe/types.ts
index dab73972..542543e1 100644
--- a/packages/notivue/NotivueSwipe/types.ts
+++ b/packages/notivue/NotivueSwipe/types.ts
@@ -1,7 +1,7 @@
import type { NotivueItem } from 'notivue'
-export interface NotivueSwipeProps {
- /** Notivue's exposed notification item. */
+export interface NotificationSwipeProps {
+ /** Notification item from the `` slot (`v-slot="item"`). */
item: NotivueItem
/**
* Whether to enable clear on swipe only on touch interactions.
@@ -43,3 +43,6 @@ export interface NotivueSwipeProps {
*/
destroy?: boolean
}
+
+/** @deprecated Use `NotificationSwipeProps`. */
+export type NotivueSwipeProps = NotificationSwipeProps
diff --git a/packages/notivue/index.ts b/packages/notivue/index.ts
index 98f87259..ebe457b3 100644
--- a/packages/notivue/index.ts
+++ b/packages/notivue/index.ts
@@ -18,6 +18,8 @@ export { useNotivueKeyboard } from '@/NotivueKeyboard/useNotivueKeyboard'
export { default as Notivue } from '@/Notivue/Notivue.vue'
export { default as NotivueAstro } from './astro/Notivue.vue'
+export { default as NotificationSwipe } from '@/NotivueSwipe/NotivueSwipe.vue'
+/** @deprecated Use `NotificationSwipe`. */
export { default as NotivueSwipe } from '@/NotivueSwipe/NotivueSwipe.vue'
export { default as NotivueKeyboard } from '@/NotivueKeyboard/NotivueKeyboard.vue'
diff --git a/packages/notivue/nuxt/README.md b/packages/notivue/nuxt/README.md
index def00063..be8cb5fc 100644
--- a/packages/notivue/nuxt/README.md
+++ b/packages/notivue/nuxt/README.md
@@ -34,7 +34,7 @@ _Themes, icons, progress bar, and native RTL support_
_Use your own components while Notivue handles the rest_
**💊 Drop-in components to enhance notifications**
-_NotivueSwipe, NotivueKeyboard, all optional and customizable_
+_NotificationSwipe_ (alias `NotivueSwipe`), _NotivueKeyboard_, all optional and customizable\_
**🌀 Dynamic Notifications**
_Update pending notifications with a breeze_
diff --git a/packages/notivue/shared/exports.js b/packages/notivue/shared/exports.js
index 0c582bbe..e61599a6 100644
--- a/packages/notivue/shared/exports.js
+++ b/packages/notivue/shared/exports.js
@@ -28,7 +28,8 @@ export const exports = {
],
components: [
'Notivue',
- 'NotivueSwipe',
+ 'NotificationSwipe',
+ 'NotivueSwipe', // Alias
'NotivueKeyboard',
'Notification',
diff --git a/playground/app.vue b/playground/app.vue
index f3ca66bb..9c7ca7e1 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -49,7 +49,7 @@ watch(
:item="item as NotivueItem"
/>
-
+
-
+
diff --git a/tests/NotivueSwipe/components/Notivue.vue b/tests/NotivueSwipe/components/Notivue.vue
index 467ceb04..bb7008bc 100644
--- a/tests/NotivueSwipe/components/Notivue.vue
+++ b/tests/NotivueSwipe/components/Notivue.vue
@@ -1,11 +1,14 @@
@@ -18,7 +19,7 @@ const isPromise = computed(() => notification.item.type === 'promise')
-
+
diff --git a/playground/components/nav/Nav.vue b/playground/components/nav/Nav.vue
index 8191d9a1..0bd61168 100644
--- a/playground/components/nav/Nav.vue
+++ b/playground/components/nav/Nav.vue
@@ -30,9 +30,11 @@ watch(isRunning, (newVal) => {
dir="ltr"
>
- Notivue is now running again. This notice will be dismissed shortly.
-
Notivue has been stopped. Restart it to create notifications.
+ Notivue is running again. This notice will disappear shortly.
+
+
+ Notivue is stopped. Start the instance to show notifications again.
+
@@ -46,7 +48,7 @@ watch(isRunning, (newVal) => {
-
+
@@ -59,7 +61,7 @@ watch(isRunning, (newVal) => {
-
+
diff --git a/playground/components/nav/NavNotificationsThemes.vue b/playground/components/nav/NavNotificationsThemes.vue
index 1b27f4db..2125c47d 100644
--- a/playground/components/nav/NavNotificationsThemes.vue
+++ b/playground/components/nav/NavNotificationsThemes.vue
@@ -38,4 +38,3 @@ function setConfigTheme(themeKey: ThemeNames) {
grid-auto-flow: row;
}
-utils/store
diff --git a/playground/components/nav/NavNotivueConfig.vue b/playground/components/nav/NavNotivueConfig.vue
index d8ee41d0..75ff6ea0 100644
--- a/playground/components/nav/NavNotivueConfig.vue
+++ b/playground/components/nav/NavNotivueConfig.vue
@@ -58,10 +58,10 @@ watch(
toggleRtlIfNeeded()
push.info({
- title: `Swipe to clear ${isEnabled ? 'enabled' : 'disabled'}`,
+ title: `Swipe to dismiss ${isEnabled ? 'on' : 'off'}`,
message: isEnabled
- ? 'Swipe left or right on a notification to clear it.'
- : 'Enable it again to use it.',
+ ? 'Swipe a notification left or right to dismiss it.'
+ : 'Turn "Clear on Swipe" back on to try swipe-to-dismiss.',
})
}
)
@@ -109,12 +109,16 @@ const btnProps = {
No Duplicates
-
- No Limit
- Limit - 1
- Limit - 3
- Limit - 5
- Limit - 10
+
+ No limit
+ Max 1
+ Max 3
+ Max 5
+ Max 10
- Enqueue ({{ enqueuedLength }})
+ Queue ({{ enqueuedLength }})
diff --git a/playground/components/nav/NavPushBuiltIn.vue b/playground/components/nav/NavPushBuiltIn.vue
index ae2f1467..037ee1d7 100644
--- a/playground/components/nav/NavPushBuiltIn.vue
+++ b/playground/components/nav/NavPushBuiltIn.vue
@@ -17,13 +17,13 @@ async function asyncRefMessagePush() {
async function asyncPush() {
if (Math.random() > 0.7) return asyncRefMessagePush()
- const promise = push.promise(messages.value.promise)
+ const pending = push.promise(messages.value.dynamic)
await new Promise((resolve) => setTimeout(resolve, getRandomInt(2000, 4000)))
if (Math.random() > 0.5) {
- promise.resolve(messages.value.success)
+ pending.resolve(messages.value.success)
} else {
- promise.reject(messages.value.error)
+ pending.reject(messages.value.error)
}
}
@@ -48,13 +48,13 @@ async function asyncPush() {
-
+
-
+
diff --git a/playground/components/nav/NavPushHeadless.vue b/playground/components/nav/NavPushHeadless.vue
index dad4ef16..2e63547f 100644
--- a/playground/components/nav/NavPushHeadless.vue
+++ b/playground/components/nav/NavPushHeadless.vue
@@ -19,7 +19,7 @@ function pushFriendRequest() {
resetOptions()
push.info({
- title: 'New Message Request',
+ title: 'New message request',
message: `Stephanie LaGarde wants to send you a message.`,
props: {
name: 'Stephanie LaGarde',
@@ -37,15 +37,15 @@ async function pushFileUpload() {
const props = { isUploadNotifiation: true, fileName: 'excel-sheet.xlsx' }
- const promise = push.promise({
- message: 'Your file is being uploaded...',
+ const pending = push.promise({
+ message: 'Uploading your file…',
props,
})
await new Promise((resolve) => setTimeout(resolve, getRandomInt(2000, 4000)))
- promise.resolve({
- message: 'Your file has been successfully uploaded.',
+ pending.resolve({
+ message: 'Your file was uploaded successfully.',
props,
})
}
@@ -54,7 +54,7 @@ function pushSimple() {
resetOptions()
push.success({
- message: `Your message has been deleted.`,
+ message: 'Your message was deleted.',
props: {
isSimpleNotification: true,
},
@@ -79,10 +79,10 @@ onBeforeUnmount(() => {
-
+
-
+
diff --git a/playground/components/shared/QueueCount.vue b/playground/components/shared/QueueCount.vue
index 22ef5ff4..c1e71b6f 100644
--- a/playground/components/shared/QueueCount.vue
+++ b/playground/components/shared/QueueCount.vue
@@ -20,7 +20,7 @@ const wrapperStyles = computed(() => {
- Enqueued
+ In queue
diff --git a/playground/utils/store.ts b/playground/utils/store.ts
index e76d752e..4581a5e2 100644
--- a/playground/utils/store.ts
+++ b/playground/utils/store.ts
@@ -81,15 +81,15 @@ export function createStore() {
info: {
title: actions.getTitle(state.rtl ? 'معلومات' : 'Info'),
message: actions.getMsg(
- 'Did you know you can directly send a message using the keyboard?',
- 'هل تعلم أنك يمكنك إرسال رسالة باستخدام لوحة المفاتيح؟ اضغط على Enter للإرسال.'
+ 'In the headless section, try the keyboard shortcuts (P or S) to fire sample notifications.',
+ 'في قسم الواجهة المخصصة، جرّب اختصارات لوحة المفاتيح (P أو S) لعرض إشعارات تجريبية.'
),
},
- promise: {
- title: actions.getTitle(state.rtl ? 'إرسال...' : 'Sending...'),
+ dynamic: {
+ title: actions.getTitle(state.rtl ? 'جاري التحديث...' : 'Updating…'),
message: actions.getMsg(
- `We're sending your message, hold on...`,
- 'نحن نرسل رسالتك. سيستغرق ذلك لحظة أو اثنتين ...'
+ 'This notification will resolve to success or error in a moment.',
+ 'سيتحول هذا الإشعار إلى نجاح أو خطأ خلال لحظات.'
),
},
}) as const
From 7b07f806aa4e2c8478073ef730f196c56d3939f3 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 19:09:39 +0100
Subject: [PATCH 09/68] README - Expand quick start (headless, options, swipe)
* Headless Toast with scoped CSS, realistic copy, and notify wrapper.
* createNotivue and Nuxt module examples; installation and Astro callout formatting.
* Custom components as H3; NotificationSwipe with built-in Notification and custom Toast.
* Nuxt swipe snippet mirrors Vite.
Co-authored-by: Cursor
---
README.md | 167 ++++++++++++++++++++++++++++++++++++------------------
1 file changed, 111 insertions(+), 56 deletions(-)
diff --git a/README.md b/README.md
index 24b55286..75af2fa3 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,6 @@
-
-
-
-
-
-
# Notivue
-### Powerful toast notification system for Vue, Nuxt and Astro
+### Powerful toast notification system for Vue
[Live Demo](https://notivue.smastrom.io) - [Documentation](https://docs.notivue.smastrom.io)
@@ -16,12 +10,6 @@
[Nuxt](https://stackblitz.com/edit/nuxt-starter-fnhcmx?file=pages%2Findex.vue) -
[Astro](https://stackblitz.com/edit/withastro-astro-qyesvk?file=src%2Fcomponents%2FVueComponent.vue)
-
-
-
-
-
-
## Features
**🧬 Fully modular with zero dependencies**
@@ -34,7 +22,7 @@ _Themes, icons, progress bar, and native RTL support_
_Use your own components while Notivue handles the rest_
**💊 Drop-in components to enhance notifications**
-_NotificationSwipe_ (alias `NotivueSwipe`), _NotivueKeyboard_, all optional and customizable\_
+_NotificationSwipe_ (alias `NotivueSwipe`), _NotivueKeyboard_, all optional and customizable
**🌀 Dynamic Notifications**
_Update pending notifications with a breeze_
@@ -48,8 +36,6 @@ _Built-in announcements, reduced-motion and keyboard support_
**💫 Nuxt and Astro modules**
_Built-in Nuxt and Astro ad-hoc modules_
-
-
## Installation
```shell
@@ -62,9 +48,7 @@ pnpm add notivue
> :bulb: Beyond this quick start — Nuxt/Astro setup, **`notify`** / callbacks, stream config, built-in styling, headless mode, full API — lives in the [documentation](https://docs.notivue.smastrom.io). The legacy **`push`** name remains exported as an alias.
-
-
-## Vite
+## Quick start (Vite)
> :bulb: See [↓ below](#nuxt) for **Nuxt** and [↓ Astro](#astro).
@@ -79,7 +63,14 @@ import App from './App.vue'
import 'notivue/notification.css' // Only needed if using built-in
import 'notivue/animations.css' // Only needed if using default animations
-const notivue = createNotivue(/* Options */)
+const notivue = createNotivue({
+ position: 'bottom-right',
+ pauseOnHover: true,
+ pauseOnTabChange: true,
+ limit: 4,
+ enqueue: true,
+ // ... other options
+})
const app = createApp(App)
app.use(notivue)
@@ -94,7 +85,12 @@ import { Notivue, Notification, notify } from 'notivue'
- Notify
+
+ Save changes
+
@@ -104,51 +100,106 @@ import { Notivue, Notification, notify } from 'notivue'
```
-**App.vue (headless — your own markup)**
-_No `notification.css` needed; wire `aria-live` / `role` and `item.clear` yourself._
+### Swipe to dismiss
+
+Wrap the notification in **`NotificationSwipe`** so toasts can be cleared with a horizontal swipe. Use the same wrapper around the built-in **`Notification`** or around a **custom component** (see the `` example below). Alias: `NotivueSwipe`.
```vue
- Notify
+
+ Save changes
+
-
-
+
+
+
+
+
+
+
+```
+
+### Custom components — your own markup
+
+**components/Toast.vue**
+
+```vue
+
+
+
+
+
{{ item.message }}
-
-
-
-
-
-
+
{{ item.props.from }}
+
+
+ ×
+
+
+
+```
+
+**App.vue**
+
+```vue
+
+
+
+
+ Record payment
+
+
+
+
+
+
```
-
-
## Nuxt
**nuxt.config.ts**
@@ -161,7 +212,12 @@ export default defineNuxtConfig({
'notivue/animations.css', // Only needed if using default animations
],
notivue: {
- // Options
+ position: 'bottom-right',
+ pauseOnHover: true,
+ pauseOnTabChange: true,
+ limit: 4,
+ enqueue: true,
+ // ... other options
},
})
```
@@ -170,7 +226,12 @@ export default defineNuxtConfig({
```vue
- Notify
+
+ Save changes
+
@@ -180,8 +241,6 @@ export default defineNuxtConfig({
```
-
-
## Astro
> :bulb: Import from **`notivue/astro`** (not `notivue`). CSS: **`notivue/astro/notification.css`** and **`notivue/astro/animations.css`** when you use the built-in UI and default animations.
@@ -190,15 +249,11 @@ export default defineNuxtConfig({
[Installation → Astro](https://docs.notivue.smastrom.io/installation/astro.html) — `createNotivue` in the Vue app entry, Notivue island, `client:only`, view transitions.
-
-
## Thanks
- [Ionic Team](https://ionic.io/) for the icons
- [Uktash Verna](https://github.com/n3r4zzurr0) for the animated spinner
-
-
## License
MIT
From 52e5e8ffaadf240b5aa852cab6463f0241d7ee7b Mon Sep 17 00:00:00 2001
From: smastrom
Date: Tue, 24 Mar 2026 19:02:19 +0100
Subject: [PATCH 10/68] GitHub - Update FUNDING donation links
* Remove Polar; add PayPal hosted button and NOWPayments.
Co-authored-by: Cursor
---
.github/FUNDING.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index f31a6f5c..66787825 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,4 @@
-polar: smastrom
buy_me_a_coffee: smastrom
+custom:
+ - https://www.paypal.com/donate/?hosted_button_id=93WKXA68W9WQJ
+ - https://nowpayments.io/donation/smastrom
From a2ff613dc232c9bf20eaf8acae6dfb8ffd2b3941 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Wed, 25 Mar 2026 09:47:59 +0100
Subject: [PATCH 11/68] Astro - Add playground-astro, retire astro-playground
* Pkg - Register playground-astro in workspace and dev:astro script.
Co-authored-by: Cursor
---
package.json | 2 +-
.../.gitignore | 0
playground-astro/.prettierrc | 17 +++++++++++++++++
.../astro.config.mjs | 0
.../package.json | 6 ++++--
.../public/favicon.svg | 0
.../src/components/AstroComponent.astro | 18 +++++++++---------
.../src/components/Notivue.vue | 0
.../src/components/ReactComponent.tsx | 18 +++++++++---------
.../src/components/VueComponent.vue | 18 +++++++++---------
.../src/env.d.ts | 0
.../src/layouts/Layout.astro | 0
.../src/pages/_app.ts | 0
.../src/pages/about.astro | 0
.../src/pages/index.astro | 0
.../src/styles/reset.css | 0
.../tsconfig.json | 0
pnpm-workspace.yaml | 2 +-
18 files changed, 50 insertions(+), 31 deletions(-)
rename {astro-playground => playground-astro}/.gitignore (100%)
create mode 100644 playground-astro/.prettierrc
rename {astro-playground => playground-astro}/astro.config.mjs (100%)
rename {astro-playground => playground-astro}/package.json (77%)
rename {astro-playground => playground-astro}/public/favicon.svg (100%)
rename {astro-playground => playground-astro}/src/components/AstroComponent.astro (78%)
rename {astro-playground => playground-astro}/src/components/Notivue.vue (100%)
rename {astro-playground => playground-astro}/src/components/ReactComponent.tsx (54%)
rename {astro-playground => playground-astro}/src/components/VueComponent.vue (54%)
rename {astro-playground => playground-astro}/src/env.d.ts (100%)
rename {astro-playground => playground-astro}/src/layouts/Layout.astro (100%)
rename {astro-playground => playground-astro}/src/pages/_app.ts (100%)
rename {astro-playground => playground-astro}/src/pages/about.astro (100%)
rename {astro-playground => playground-astro}/src/pages/index.astro (100%)
rename {astro-playground => playground-astro}/src/styles/reset.css (100%)
rename {astro-playground => playground-astro}/tsconfig.json (100%)
diff --git a/package.json b/package.json
index d5e957d3..9e9116a3 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"build": "pnpm -C packages/notivue run build",
"build:playground": "pnpm build && pnpm install && pnpm -C playground run build",
"dev": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground run dev --host\"",
- "dev:astro": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C astro-playground run dev --host\"",
+ "dev:astro": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground-astro run dev --host\"",
"format": "oxfmt .",
"format:check": "oxfmt --check .",
"prepare": "husky",
diff --git a/astro-playground/.gitignore b/playground-astro/.gitignore
similarity index 100%
rename from astro-playground/.gitignore
rename to playground-astro/.gitignore
diff --git a/playground-astro/.prettierrc b/playground-astro/.prettierrc
new file mode 100644
index 00000000..c712bf98
--- /dev/null
+++ b/playground-astro/.prettierrc
@@ -0,0 +1,17 @@
+{
+ "printWidth": 100,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 3,
+ "trailingComma": "es5",
+ "useTabs": false,
+ "plugins": ["prettier-plugin-astro"],
+ "overrides": [
+ {
+ "files": "*.astro",
+ "options": {
+ "parser": "astro"
+ }
+ }
+ ]
+}
diff --git a/astro-playground/astro.config.mjs b/playground-astro/astro.config.mjs
similarity index 100%
rename from astro-playground/astro.config.mjs
rename to playground-astro/astro.config.mjs
diff --git a/astro-playground/package.json b/playground-astro/package.json
similarity index 77%
rename from astro-playground/package.json
rename to playground-astro/package.json
index 79c0f674..37483ee2 100644
--- a/astro-playground/package.json
+++ b/playground-astro/package.json
@@ -1,5 +1,5 @@
{
- "name": "notivue-astro-playground",
+ "name": "notivue-playground-astro",
"private": true,
"type": "module",
"scripts": {
@@ -20,6 +20,8 @@
},
"devDependencies": {
"@types/react": "^18.3.28",
- "@types/react-dom": "^18.3.7"
+ "@types/react-dom": "^18.3.7",
+ "prettier": "^3.8.1",
+ "prettier-plugin-astro": "^0.14.1"
}
}
diff --git a/astro-playground/public/favicon.svg b/playground-astro/public/favicon.svg
similarity index 100%
rename from astro-playground/public/favicon.svg
rename to playground-astro/public/favicon.svg
diff --git a/astro-playground/src/components/AstroComponent.astro b/playground-astro/src/components/AstroComponent.astro
similarity index 78%
rename from astro-playground/src/components/AstroComponent.astro
rename to playground-astro/src/components/AstroComponent.astro
index dbd1efe9..2c782b30 100644
--- a/astro-playground/src/components/AstroComponent.astro
+++ b/playground-astro/src/components/AstroComponent.astro
@@ -8,17 +8,17 @@
diff --git a/playground/components/nav/NavActions.vue b/playground/components/nav/NavActions.vue
index 6c023985..70465764 100644
--- a/playground/components/nav/NavActions.vue
+++ b/playground/components/nav/NavActions.vue
@@ -3,10 +3,10 @@ const { isRunning, startInstance, stopInstance } = useNotivueInstance()
-
+
-
+
diff --git a/playground/components/nav/NavNotificationsCustomization.vue b/playground/components/nav/NavNotificationsCustomization.vue
index 86de9305..cc2d393f 100644
--- a/playground/components/nav/NavNotificationsCustomization.vue
+++ b/playground/components/nav/NavNotificationsCustomization.vue
@@ -2,8 +2,8 @@
const { state, actions, messages } = useStore()
function clearAllAndPushOne() {
- push.destroyAll()
- push.success(messages.value.success)
+ notify.destroyAll()
+ notify.success(messages.value.success)
}
function toggleRenderTitles() {
diff --git a/playground/components/nav/NavNotivueConfig.vue b/playground/components/nav/NavNotivueConfig.vue
index 75ff6ea0..f50b0f6a 100644
--- a/playground/components/nav/NavNotivueConfig.vue
+++ b/playground/components/nav/NavNotivueConfig.vue
@@ -30,7 +30,7 @@ function toggleQueue() {
}
function toggleNoDupes() {
- push.destroyAll()
+ notify.destroyAll()
config.update((prevConf) => ({
avoidDuplicates: !prevConf.avoidDuplicates,
@@ -38,7 +38,7 @@ function toggleNoDupes() {
if (config.avoidDuplicates.value) state.hasProgress = true
- push.success(messages.value.success)
+ notify.success(messages.value.success)
}
const enqueuedLength = computed(() => queue.value.length)
@@ -57,7 +57,7 @@ watch(
(isEnabled) => {
toggleRtlIfNeeded()
- push.info({
+ notify.info({
title: `Swipe to dismiss ${isEnabled ? 'on' : 'off'}`,
message: isEnabled
? 'Swipe a notification left or right to dismiss it.'
diff --git a/playground/components/nav/NavPushBuiltIn.vue b/playground/components/nav/NavPushBuiltIn.vue
index 037ee1d7..a0e22e2e 100644
--- a/playground/components/nav/NavPushBuiltIn.vue
+++ b/playground/components/nav/NavPushBuiltIn.vue
@@ -3,7 +3,7 @@ const { state, messages } = useStore()
async function asyncRefMessagePush() {
const initialMessage = ref(state.rtl ? 'جاري تحميل الملفات...' : 'Preparing to upload files...')
- const notification = push.promise(initialMessage)
+ const notification = notify.promise(initialMessage)
for (let n = 1; n < 4; n++) {
await new Promise((resolve) => setTimeout(resolve, getRandomInt(1000, 2000)))
@@ -17,7 +17,7 @@ async function asyncRefMessagePush() {
async function asyncPush() {
if (Math.random() > 0.7) return asyncRefMessagePush()
- const pending = push.promise(messages.value.dynamic)
+ const pending = notify.promise(messages.value.dynamic)
await new Promise((resolve) => setTimeout(resolve, getRandomInt(2000, 4000)))
if (Math.random() > 0.5) {
@@ -31,7 +31,7 @@ async function asyncPush() {
{
// console.log('AutoClear!', item)
@@ -45,13 +45,13 @@ async function asyncPush() {
>
-
+
-
+
-
+
diff --git a/playground/components/nav/NavPushHeadless.vue b/playground/components/nav/NavPushHeadless.vue
index 2e63547f..08d99a4b 100644
--- a/playground/components/nav/NavPushHeadless.vue
+++ b/playground/components/nav/NavPushHeadless.vue
@@ -1,6 +1,8 @@
diff --git a/playground/components/nav/NavPushBuiltIn.vue b/playground/components/nav/NavPushBuiltIn.vue
index a0e22e2e..17622813 100644
--- a/playground/components/nav/NavPushBuiltIn.vue
+++ b/playground/components/nav/NavPushBuiltIn.vue
@@ -3,7 +3,7 @@ const { state, messages } = useStore()
async function asyncRefMessagePush() {
const initialMessage = ref(state.rtl ? 'جاري تحميل الملفات...' : 'Preparing to upload files...')
- const notification = notify.promise(initialMessage)
+ const notification = notify.loading(initialMessage)
for (let n = 1; n < 4; n++) {
await new Promise((resolve) => setTimeout(resolve, getRandomInt(1000, 2000)))
@@ -11,19 +11,19 @@ async function asyncRefMessagePush() {
}
await new Promise((resolve) => setTimeout(resolve, getRandomInt(1000, 2000)))
- notification.resolve(state.rtl ? 'تم تحميل جميع الملفات!' : 'All files uploaded!')
+ notification.success(state.rtl ? 'تم تحميل جميع الملفات!' : 'All files uploaded!')
}
async function asyncPush() {
if (Math.random() > 0.7) return asyncRefMessagePush()
- const pending = notify.promise(messages.value.dynamic)
+ const notification = notify.loading(messages.value.dynamic)
await new Promise((resolve) => setTimeout(resolve, getRandomInt(2000, 4000)))
if (Math.random() > 0.5) {
- pending.resolve(messages.value.success)
+ notification.success(messages.value.success)
} else {
- pending.reject(messages.value.error)
+ notification.error(messages.value.error)
}
}
diff --git a/playground/components/nav/NavPushHeadless.vue b/playground/components/nav/NavPushHeadless.vue
index 08d99a4b..dc9586ab 100644
--- a/playground/components/nav/NavPushHeadless.vue
+++ b/playground/components/nav/NavPushHeadless.vue
@@ -39,14 +39,14 @@ async function pushFileUpload() {
const props = { isUploadNotifiation: true, fileName: 'excel-sheet.xlsx' }
- const pending = push.promise({
+ const notification = push.loading({
message: 'Uploading your file…',
props,
})
await new Promise((resolve) => setTimeout(resolve, getRandomInt(2000, 4000)))
- pending.resolve({
+ notification.success({
message: 'Your file was uploaded successfully.',
props,
})
From fa8bdc90f831e24065fea0f31e66509aa87c0608 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Wed, 25 Mar 2026 15:10:07 +0100
Subject: [PATCH 18/68] Pkg - Add docs package to workspace, scripts, and
gitignore
* Pkg - docs:dev, docs:build, docs:preview; include docs in pnpm workspace
* Pkg - oxfmt markdown tab width override
* .gitignore - Ignore docs directory
* README - Align root docs with notify API and v3
Co-authored-by: Cursor
---
.gitignore | 2 +-
.oxfmtrc.json | 10 ++-
README.md | 214 +++++++++++++++++++++-----------------------
package.json | 3 +
pnpm-workspace.yaml | 1 +
5 files changed, 116 insertions(+), 114 deletions(-)
diff --git a/.gitignore b/.gitignore
index 1d00a6d1..4796cc02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ notivue-*.tgz
.nuxt
pnpm-lock.yaml
-docs
+docs/
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index 71b1e70c..c5d3d6e1 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -9,5 +9,13 @@
"ignorePatterns": [".vscode/**", "node_modules/**", "dist/**", "target/**"],
"sortPackageJson": {
"sortScripts": true
- }
+ },
+ "overrides": [
+ {
+ "files": ["*.md"],
+ "options": {
+ "tabWidth": 2
+ }
+ }
+ ]
}
diff --git a/README.md b/README.md
index 2bbd6d30..03ac0672 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
+
+
# Notivue
### Powerful toast notification system for Vue
[Live Demo](https://notivue.smastrom.io) - [Documentation](https://docs.notivue.smastrom.io)
-
-
---
**Examples:** [Custom Components](https://stackblitz.com/edit/vitejs-vite-9jkh73?file=src%2Fcomponents%2FPage.vue) -
@@ -14,29 +14,21 @@
## Features
-**🧬 Fully modular with zero dependencies**
-_Granularly include only the features you need_
+🧬 **Fully modular with zero dependencies** - Zero bloat, only the logic you actually import reaches your production bundle.
-**✅ Beautiful, ready-made notifications included**
-_Themes, icons, progress bar, and native RTL support_
+✅ **Beautiful, ready-made notifications included** - Accessible, CSS-variable-based themes with built-in dark mode, progress bars, and RTL support.
-**🧩 Headless API**
-_Use your own components while Notivue handles the rest_
+🧩 **Headless API** - Total decoupling of logic and UI; bring your own custom components while Notivue manages the stack and lifecycle
-**💊 Drop-in components to enhance notifications**
-_NotificationSwipe_, _NotivueKeyboard_, all optional and customizable
+🌀 **Dynamic Notifications (Promise API)** - Update a single notification from _loading_ to _success_ or _error_ using a reactive handle
-**🌀 Dynamic Notifications**
-_Update pending notifications with a breeze_
+💊 **Drop-in components to enhance notifications** - Optional add-ons like _NotivueSwipe_ for touch gestures and _NotivueKeyboard_ for keyboard navigation
-**🎢 Slick transitions and animations**
-_Customize animations with CSS classes_
+♿️ **Fully accessible (A11y)** - WAI-ARIA compliant with automatic live regions, _reduced-motion_ support and built-in keyboard navigation.
-**♿️ Fully accessible**
-_Built-in announcements, reduced-motion and keyboard support_
+🛡️ **Battle-tested** - 150+ E2E tests ensuring consistent behavior and reliability
-**💫 Nuxt and Astro modules**
-_Built-in Nuxt and Astro ad-hoc modules_
+💫 **Nuxt and Astro modules** - Framework-specific wrappers providing seamless integration and zero boilerplate
## Installation
@@ -66,12 +58,12 @@ import 'notivue/notification.css' // Only needed if using built-in
-
- Save changes
-
-
-
-
-
-
-
+
+ Save changes
+
+
+
+
+
+
+
```
@@ -112,20 +104,20 @@ import { Notivue, Notification, NotificationSwipe, notify } from 'notivue'
-
- Save changes
-
-
-
-
-
-
-
-
-
+
+ Save changes
+
+
+
+
+
+
+
+
+
```
@@ -139,27 +131,25 @@ import type { NotivueItem } from 'notivue'
// Custom props
export interface ToastProps {
- from: string
+ from: string
}
defineProps<{
- item: NotivueItem
+ item: NotivueItem
}>()
-
-
-
- {{ item.message }}
-
-
{{ item.props.from }}
-
-
-
- ×
-
-
+
+
+
+ {{ item.message }}
+
+
{{ item.props.from }}
+
+
+
×
+
```
@@ -172,33 +162,33 @@ import { Notivue, NotificationSwipe, notify } from 'notivue'
import Toast, { type ToastProps } from './components/Toast.vue'
function showNotification(message: string, from: string) {
- notify.success({
- message,
- props: { from },
- })
+ notify.success({
+ message,
+ props: { from },
+ })
}
-
- Record payment
-
-
-
-
-
-
-
-
-
+
+ Record payment
+
+
+
+
+
+
+
+
+
```
@@ -208,19 +198,19 @@ function showNotification(message: string, from: string) {
```ts
export default defineNuxtConfig({
- modules: ['notivue/nuxt'],
- css: [
- 'notivue/notification.css', // Only needed if using built-in
- 'notivue/animations.css', // Only needed if using default animations
- ],
- notivue: {
- position: 'bottom-right',
- pauseOnHover: true,
- pauseOnTabChange: true,
- limit: 4,
- enqueue: true,
- // ... other options
- },
+ modules: ['notivue/nuxt'],
+ css: [
+ 'notivue/notification.css', // Only needed if using built-in
+ 'notivue/animations.css', // Only needed if using default animations
+ ],
+ notivue: {
+ position: 'bottom-right',
+ pauseOnHover: true,
+ pauseOnTabChange: true,
+ limit: 4,
+ enqueue: true,
+ // other options...
+ },
})
```
@@ -228,18 +218,18 @@ export default defineNuxtConfig({
```vue
-
- Save changes
-
-
-
-
-
-
-
+
+ Save changes
+
+
+
+
+
+
+
```
diff --git a/package.json b/package.json
index 9e9116a3..dcc214b2 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,9 @@
"build:playground": "pnpm build && pnpm install && pnpm -C playground run build",
"dev": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground run dev --host\"",
"dev:astro": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C playground-astro run dev --host\"",
+ "docs:build": "pnpm -C docs run build",
+ "docs:dev": "pnpm -C docs run dev",
+ "docs:preview": "pnpm -C docs run preview",
"format": "oxfmt .",
"format:check": "oxfmt --check .",
"prepare": "husky",
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index d2c6f1eb..76afc63d 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,3 +3,4 @@ packages:
- 'tests'
- 'playground'
- 'playground-astro'
+ - 'docs'
From 9bc22f71c357524acc02067305ec7f54c2c93c09 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 13:39:24 +0100
Subject: [PATCH 19/68] Notifications - Remove tabindex=-1 from close button
Co-Authored-By: Claude Opus 4.6
---
packages/notivue/Notifications/Notification.vue | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/notivue/Notifications/Notification.vue b/packages/notivue/Notifications/Notification.vue
index 3fbde44e..f3946436 100644
--- a/packages/notivue/Notifications/Notification.vue
+++ b/packages/notivue/Notifications/Notification.vue
@@ -49,7 +49,6 @@ watch(
:class="Cx.CLOSE"
:aria-label="closeAriaLabel"
type="button"
- tabindex="-1"
@click="item.clear"
>
From 18008eda81705ba16d7ba22018ca657c4022ecb5 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 13:39:53 +0100
Subject: [PATCH 20/68] Core, Astro, Playground - Deprecate isTopAlign,
useNotify/usePush; fix isStreamPaused type
Replace isTopAlign usage with position.value.startsWith('top') across core
and playground. Add @deprecated JSDoc to useNotify, usePush, and astro
load/promise aliases. Change isStreamPaused return type to ComputedRef.
Co-Authored-By: Claude Opus 4.6
---
.../Notivue/composables/useNotivueStyles.ts | 6 +++---
packages/notivue/astro/push.ts | 2 ++
packages/notivue/core/createStore.ts | 2 +-
packages/notivue/core/types.ts | 3 ++-
packages/notivue/core/useStore.ts | 14 ++++++++------
packages/notivue/index.ts | 11 ++++-------
playground/components/shared/QueueCount.vue | 4 ++--
7 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/packages/notivue/Notivue/composables/useNotivueStyles.ts b/packages/notivue/Notivue/composables/useNotivueStyles.ts
index 981f4505..2f50f059 100644
--- a/packages/notivue/Notivue/composables/useNotivueStyles.ts
+++ b/packages/notivue/Notivue/composables/useNotivueStyles.ts
@@ -44,14 +44,14 @@ const baseStyles: Record = {
}
export function useNotivueStyles() {
- const { isTopAlign, position } = useNotivue()
+ const { position } = useNotivue()
/**
* Simulates overflow-hidden only on the opposite side of the current vertical align.
* This will not clip enter animations but will contain the stream vertically.
*/
const offset = computed(() => {
- const isTop = isTopAlign.value
+ const isTop = position.value.startsWith('top')
// IMPORTANT: Order of values must match 'top right bottom left'
const inset = [
@@ -68,7 +68,7 @@ export function useNotivueStyles() {
})
const xAlignment = computed(() => ({
- [isTopAlign.value ? 'top' : 'bottom']: '0',
+ [position.value.startsWith('top') ? 'top' : 'bottom']: '0',
justifyContent: `var(--nv-root-x-align, ${
position.value.endsWith('left')
? 'flex-start'
diff --git a/packages/notivue/astro/push.ts b/packages/notivue/astro/push.ts
index 49aabcec..a0ca0234 100644
--- a/packages/notivue/astro/push.ts
+++ b/packages/notivue/astro/push.ts
@@ -43,7 +43,9 @@ const astroNotifyApi = {
error: (options: PushOptions) => pushEvent({ ...options, type: 'error' }),
warning: (options: PushOptions) => pushEvent({ ...options, type: 'warning' }),
loading: dynamicNotification,
+ /** @deprecated Use `loading`. */
load: dynamicNotification,
+ /** @deprecated Use `loading`. */
promise: dynamicNotification,
clearAll() {
window.dispatchEvent(new CustomEvent('notivue:clear-all'))
diff --git a/packages/notivue/core/createStore.ts b/packages/notivue/core/createStore.ts
index 5a99b9f5..b5e546f8 100644
--- a/packages/notivue/core/createStore.ts
+++ b/packages/notivue/core/createStore.ts
@@ -250,7 +250,7 @@ export function createAnimations(
console.log('Updating positions')
const isReduced = this.isReducedMotion.value || isImmediate
- const isTopAlign = config.position.value.indexOf('top') === 0
+ const isTopAlign = config.position.value.startsWith('top')
const leaveClass = config.animations.value.leave
let accPrevHeights = 0
diff --git a/packages/notivue/core/types.ts b/packages/notivue/core/types.ts
index 9b3ae01a..9cc7c299 100644
--- a/packages/notivue/core/types.ts
+++ b/packages/notivue/core/types.ts
@@ -265,7 +265,8 @@ export interface NotivueComputedEntries {
}
export type UseNotivueReturn = ConfigSlice & {
- isStreamPaused: Readonly[>
+ isStreamPaused: ComputedRef]
+ /** @deprecated Create computed property instead: `computed(() => config.position.value.startsWith('top'))`. */
isTopAlign: ComputedRef
}
diff --git a/packages/notivue/core/useStore.ts b/packages/notivue/core/useStore.ts
index 280cee17..db641a25 100644
--- a/packages/notivue/core/useStore.ts
+++ b/packages/notivue/core/useStore.ts
@@ -1,4 +1,4 @@
-import { inject, computed, toRefs, reactive, readonly, ref } from 'vue'
+import { inject, computed, toRefs, reactive, readonly, ref, type ComputedRef } from 'vue'
import { isSSR, getSlotItem } from './utils'
import { notivueInjectionKey, notivueInstanceInjectionKey } from './symbols'
@@ -56,7 +56,7 @@ export function useNotivue(): UseNotivueReturn {
...toRefs(reactive(DEFAULT_CONFIG)),
update: () => {},
isTopAlign: computed(() => true),
- isStreamPaused: ref(false),
+ isStreamPaused: ref(false) as ComputedRef,
} as UseNotivueReturn
}
@@ -64,30 +64,32 @@ export function useNotivue(): UseNotivueReturn {
return {
...store.config,
- isStreamPaused: readonly(store.timeouts.isStreamPaused),
+ isStreamPaused: readonly(store.timeouts.isStreamPaused) as ComputedRef,
isTopAlign: computed(() => store.config.position.value.indexOf('top') === 0),
}
}
/**
- * Returns the shared `notify` API (same object as legacy `push`).
+ * @deprecated Import `notify` from `notivue` instead of calling `useNotify()` in setup.
*
* ```ts
* import { notify } from 'notivue'
* ```
*
- * @docs https://docs.notivue.smastrom.io/api/notify.html#usenotify
+ * @see https://docs.notivue.smastrom.io/api/notify.html
*/
export function useNotify() {
return notify
}
/**
- * @deprecated Use `useNotify` or import `notify` / `push` directly.
+ * @deprecated Import `notify` from `notivue` instead of `usePush()`.
*
* ```ts
* import { notify } from 'notivue'
* ```
+ *
+ * @see https://docs.notivue.smastrom.io/api/notify.html
*/
export function usePush() {
return notify
diff --git a/packages/notivue/index.ts b/packages/notivue/index.ts
index 7bf952be..f29ab715 100644
--- a/packages/notivue/index.ts
+++ b/packages/notivue/index.ts
@@ -7,13 +7,10 @@ export { createNotivue as createNotivueAstro } from './astro/createNotivue'
export { startInstance, stopInstance } from '@/core/createInstance'
-export {
- useNotify,
- usePush,
- useNotivue,
- useNotifications,
- useNotivueInstance,
-} from '@/core/useStore'
+export { useNotivue, useNotifications, useNotivueInstance } from '@/core/useStore'
+
+/** @deprecated Import `notify` from `notivue` instead of `useNotify()` or `usePush()`. */
+export { useNotify, usePush } from '@/core/useStore'
export { useNotivueKeyboard } from '@/NotivueKeyboard/useNotivueKeyboard'
export { default as Notivue } from '@/Notivue/Notivue.vue'
diff --git a/playground/components/shared/QueueCount.vue b/playground/components/shared/QueueCount.vue
index c1e71b6f..a0794aef 100644
--- a/playground/components/shared/QueueCount.vue
+++ b/playground/components/shared/QueueCount.vue
@@ -1,10 +1,10 @@
@@ -112,9 +112,9 @@ import { Notivue, Notification, NotificationSwipe, notify } from 'notivue'
-
+
-
+
@@ -157,7 +157,7 @@ defineProps<{
```vue
-
+
-
+
diff --git a/playground/app.vue b/playground/app.vue
index 068fc88d..ea294497 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -49,7 +49,7 @@ watch(
:item="item as NotivueItem"
/>
-
+
-
+
diff --git a/tests/NotivueSwipe/components/Notivue.vue b/tests/NotivueSwipe/components/Notivue.vue
index 35406e7c..ddd1e92f 100644
--- a/tests/NotivueSwipe/components/Notivue.vue
+++ b/tests/NotivueSwipe/components/Notivue.vue
@@ -1,14 +1,14 @@
-
+
diff --git a/packages/notivue/NotivueKeyboard/constants.ts b/packages/notivue/NotivueKeyboard/constants.ts
index 2798f04e..08424244 100644
--- a/packages/notivue/NotivueKeyboard/constants.ts
+++ b/packages/notivue/NotivueKeyboard/constants.ts
@@ -1,8 +1,3 @@
-import type { InjectionKey } from 'vue'
-import type { NotivueKeyboardData } from 'notivue'
-
-export const keyboardInjectionKey = Symbol('') as InjectionKey
-
export const focusableEls =
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]'
diff --git a/packages/notivue/NotivueKeyboard/types.ts b/packages/notivue/NotivueKeyboard/types.ts
index f1153ffc..2d59be25 100644
--- a/packages/notivue/NotivueKeyboard/types.ts
+++ b/packages/notivue/NotivueKeyboard/types.ts
@@ -1,24 +1,28 @@
import type { Component, ComputedRef, Ref } from 'vue'
+/** @deprecated No longer used. */
export type TabIndexValue = 0 | -1
+
+/** @deprecated No longer used. */
export type ContainersTabIndexMap = Record
+/** @deprecated No longer used. `useNotivueKeyboard()` is no longer needed. */
export interface NotivueKeyboardData {
- /**
- * Reactive tab index value for the custom notification focusable elements tabindex.
- *
- * Meant to be added to the custom notification elements via `:tabindex="elementsTabIndex"`.
- */
elementsTabIndex: Ref
- /**
- * Reactive map of tab index values for the notification containers.
- *
- * Meant to be passed as prop to Notivue via `:containersTabIndex="containersTabIndex"`.
- */
containersTabIndex: ComputedRef
}
export interface NotivueKeyboardProps {
+ /**
+ * Custom function to determine if a notification container is a candidate
+ * for keyboard navigation. Receives the container element.
+ *
+ * By default, a container is a candidate if it contains at least one
+ * focusable element (buttons, links, inputs, selects, textareas, or elements with `tabindex="0"`).
+ *
+ * @default undefined
+ */
+ isCandidate?: (container: HTMLElement) => boolean
/**
* Key to combine with Control to enter or exit the stream.
*
@@ -59,8 +63,5 @@ export interface NotivueKeyboardProps {
}
export interface NotivueKeyboardSlot {
- default(props: {
- elementsTabIndex: TabIndexValue
- containersTabIndex: ContainersTabIndexMap
- }): Component
+ default(): Component
}
diff --git a/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts b/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
index 2848095d..79798490 100644
--- a/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
+++ b/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
@@ -1,17 +1,14 @@
-import { inject, ref, computed } from 'vue'
-
-import { keyboardInjectionKey } from './constants'
-import { isSSR } from '@/core/utils'
+import { ref, computed } from 'vue'
import type { NotivueKeyboardData } from 'notivue'
+/**
+ * @deprecated NotivueKeyboard now manages focus automatically.
+ * This composable is no longer needed — remove it from your code.
+ */
export function useNotivueKeyboard(): NotivueKeyboardData {
- if (isSSR) {
- return {
- elementsTabIndex: ref(-1),
- containersTabIndex: computed(() => ({})),
- }
+ return {
+ elementsTabIndex: ref(0),
+ containersTabIndex: computed(() => ({})),
}
-
- return inject(keyboardInjectionKey) as NotivueKeyboardData
}
diff --git a/packages/notivue/index.ts b/packages/notivue/index.ts
index 50186697..83a850e9 100644
--- a/packages/notivue/index.ts
+++ b/packages/notivue/index.ts
@@ -11,6 +11,7 @@ export { useNotivue, useNotifications, useNotivueInstance } from '@/core/useStor
/** @deprecated Import `notify` from `notivue` instead of `useNotify()` or `usePush()`. */
export { useNotify, usePush } from '@/core/useStore'
+/** @deprecated NotivueKeyboard now manages focus automatically. This composable is no longer needed. */
export { useNotivueKeyboard } from '@/NotivueKeyboard/useNotivueKeyboard'
export { default as Notivue } from '@/Notivue/Notivue.vue'
From e5b6cebcaa0f45fc0ad3931ce77445bb43b984aa Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 21:58:48 +0100
Subject: [PATCH 28/68] Playground, Tests - Update consumers for new
NotivueKeyboard API
Removes containersTabIndex prop, v-slot destructuring, elementsTabIndex
bindings, and useNotivueKeyboard() usage from all consumers. Buttons
are now naturally focusable without explicit tabindex.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
playground/app.vue | 8 ++------
.../FriendRequestNotification.vue | 15 ++-------------
.../SimpleNotification.vue | 2 +-
tests/NotivueKeyboard/components/Candidate.vue | 18 +++---------------
tests/NotivueKeyboard/components/Notivue.vue | 3 +--
5 files changed, 9 insertions(+), 37 deletions(-)
diff --git a/playground/app.vue b/playground/app.vue
index ea294497..935312c5 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -38,12 +38,8 @@ watch(
-
-
+
+
}>()
-
-const { elementsTabIndex } = useNotivueKeyboard()
@@ -37,17 +35,8 @@ const { elementsTabIndex } = useNotivueKeyboard()
-
- Deny
-
-
- Accept
-
+ Deny
+ Accept
diff --git a/playground/components/custom-notifications/SimpleNotification.vue b/playground/components/custom-notifications/SimpleNotification.vue
index fbc79392..e472b98d 100644
--- a/playground/components/custom-notifications/SimpleNotification.vue
+++ b/playground/components/custom-notifications/SimpleNotification.vue
@@ -24,7 +24,7 @@ defineProps<{
{{ item.message }}
-
+
-import { useNotivueKeyboard, type NotivueItem } from 'notivue'
+import type { NotivueItem } from 'notivue'
const props = defineProps<{
item: NotivueItem
@@ -12,8 +12,6 @@ function onPrimaryAction() {
function onSecondaryAction() {
props.item.clear()
}
-
-const { elementsTabIndex } = useNotivueKeyboard()
@@ -24,20 +22,10 @@ const { elementsTabIndex } = useNotivueKeyboard()
-
+
Primary Action
-
+
Secondary Action
diff --git a/tests/NotivueKeyboard/components/Notivue.vue b/tests/NotivueKeyboard/components/Notivue.vue
index a8d89e74..1b47bbd1 100644
--- a/tests/NotivueKeyboard/components/Notivue.vue
+++ b/tests/NotivueKeyboard/components/Notivue.vue
@@ -57,7 +57,6 @@ onBeforeUnmount(() => {
{
:renderAnnouncement="cyProps.renderAnnouncement"
:maxAnnouncements="cyProps.maxAnnouncements"
>
-
+
From 97d3c77fc6d0387aa156e04acf051b82b775d0db Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 22:23:42 +0100
Subject: [PATCH 29/68] Core - Exclude tabindex="-1" elements from focusable
candidates selector
The focusableEls selector matched all non-disabled buttons regardless of
tabindex, causing elements intentionally excluded from keyboard navigation
(e.g. dismiss buttons with tabindex="-1") to be counted as focusable.
This made their containers qualify as candidates incorrectly.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
packages/notivue/NotivueKeyboard/constants.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/notivue/NotivueKeyboard/constants.ts b/packages/notivue/NotivueKeyboard/constants.ts
index 08424244..13ebe500 100644
--- a/packages/notivue/NotivueKeyboard/constants.ts
+++ b/packages/notivue/NotivueKeyboard/constants.ts
@@ -1,5 +1,5 @@
export const focusableEls =
- 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]'
+ 'a[href]:not([tabindex="-1"]), area[href]:not([tabindex="-1"]), input:not([disabled]):not([tabindex="-1"]), select:not([disabled]):not([tabindex="-1"]), textarea:not([disabled]):not([tabindex="-1"]), button:not([disabled]):not([tabindex="-1"]), [tabindex="0"]'
export const DEFAULT_PROPS = {
comboKey: 'n',
From 3d9e72047736eebbd8ccad9be0f5906006bdf132 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 22:23:53 +0100
Subject: [PATCH 30/68] Tests - Update keyboard tests for new API; add
pause-on-focus tests
- Updates leave message assertion to match new default
- Updates last-candidate action test to expect focus on previous candidate
- Adds config-pause-on-focus.cy.ts with tests for focus pause/resume,
intra-stream focus moves, and dismiss-while-focused scenarios
Co-Authored-By: Claude Opus 4.6 (1M context)
---
tests/Notivue/config-pause-on-focus.cy.ts | 60 ++++++++++++++++++++++
tests/NotivueKeyboard/actions.cy.ts | 8 +--
tests/NotivueKeyboard/props.cy.ts | 2 +-
tests/cypress/support/commands-keyboard.ts | 2 +-
4 files changed, 66 insertions(+), 6 deletions(-)
create mode 100644 tests/Notivue/config-pause-on-focus.cy.ts
diff --git a/tests/Notivue/config-pause-on-focus.cy.ts b/tests/Notivue/config-pause-on-focus.cy.ts
new file mode 100644
index 00000000..7b44e07d
--- /dev/null
+++ b/tests/Notivue/config-pause-on-focus.cy.ts
@@ -0,0 +1,60 @@
+describe('Pause on focus', () => {
+ beforeEach(() => {
+ cy.throwIfDurationMismatch(6000)
+ })
+
+ it('Should pause when focusing a notification element and resume when focus leaves', () => {
+ cy.mountNotivue()
+
+ .clickRandomStatic()
+ .wait(4000) // Remaining time 2000ms
+ .get('.ClearButton')
+ .first()
+ .focus()
+ .wait(4000) // Any value greater than the remaining time
+ .get('.Notification')
+ .should('exist')
+
+ .get('.Success')
+ .focus() // Move focus outside the stream
+ .wait(2500)
+
+ .get('.Notification')
+ .should('not.exist')
+ })
+
+ it('Should not pause if focus moves between elements inside the stream', () => {
+ cy.mountNotivue()
+
+ .clickRandomStatic()
+ .clickRandomStatic()
+ .get('.ClearButton')
+ .first()
+ .focus()
+ .wait(4000) // Paused
+ .get('.ClearButton')
+ .last()
+ .focus() // Focus moves within stream — should stay paused
+ .wait(4000)
+ .get('.Notification')
+ .should('exist')
+ })
+
+ it('Should resume when the last notification is dismissed while focused', () => {
+ cy.mountNotivue()
+
+ .clickRandomStatic()
+ .clickRandomStatic()
+ .get('.ClearButton')
+ .first()
+ .focus()
+ .wait(4000) // Paused, remaining time on 2nd notification still > 0
+ .get('.ClearButton')
+ .first()
+ .click() // Dismiss the focused notification, focus moves outside stream
+
+ .wait(7000) // Wait for remaining notification to expire
+ .get('.Notification')
+ .should('not.exist')
+ })
+})
diff --git a/tests/NotivueKeyboard/actions.cy.ts b/tests/NotivueKeyboard/actions.cy.ts
index 852d6b5f..9c054998 100644
--- a/tests/NotivueKeyboard/actions.cy.ts
+++ b/tests/NotivueKeyboard/actions.cy.ts
@@ -32,22 +32,22 @@ describe('Actions', () => {
.should('have.data', 'notivueContainer', 0)
})
- it('Should leave stream if pressing Space or Enter on action in last container', () => {
+ it('Should focus previous candidate if pressing Space or Enter on action in last container', () => {
cy.mountKeyboard()
.pushCandidate()
.pushCandidate()
- .as('relatedTarget')
.realPress('Tab')
.realPress('Tab')
.realPress('Tab')
.realPress('Tab')
- .realPress('Tab') // Go to action of last container
+ .realPress('Tab') // Go to action of last container (id: 0, sorted last)
.realPress(Math.random() > 0.5 ? 'Space' : 'Enter')
- cy.get('@relatedTarget').should('be.focused').checkLeaveAnnouncement()
+ .focused()
+ .should('have.data', 'notivueContainer', 1) // Falls back to previous candidate
})
it('Should leave stream if clicking with mouse an action in any container', () => {
diff --git a/tests/NotivueKeyboard/props.cy.ts b/tests/NotivueKeyboard/props.cy.ts
index b21fff96..bec449f5 100644
--- a/tests/NotivueKeyboard/props.cy.ts
+++ b/tests/NotivueKeyboard/props.cy.ts
@@ -82,7 +82,7 @@ describe('Props', () => {
.realPress('Escape')
.get('.Notification')
- .should('contain.text', "You're leaving the notifications stream")
+ .should('contain.text', 'You left the notifications stream')
.should('have.length', 1)
})
})
diff --git a/tests/cypress/support/commands-keyboard.ts b/tests/cypress/support/commands-keyboard.ts
index 8e27e632..51c4d4b7 100644
--- a/tests/cypress/support/commands-keyboard.ts
+++ b/tests/cypress/support/commands-keyboard.ts
@@ -35,5 +35,5 @@ Cypress.Commands.add('pushUnqualified', () => cy.get('.PushUnqualified').click()
Cypress.Commands.add('pushCandidateSilently', () => cy.get('body').type('{shift}c'))
Cypress.Commands.add('checkLeaveAnnouncement', () =>
- cy.get('.Notification').first().should('contain.text', "You're leaving the notifications stream")
+ cy.get('.Notification').first().should('contain.text', 'You left the notifications stream')
)
From fec019056f663d46927c3951e9b5e0d1bf660279 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Thu, 26 Mar 2026 22:32:32 +0100
Subject: [PATCH 31/68] Docs - Streamline README features; add key exports
table
Tightens feature descriptions, merges overlapping bullets, adds a
key exports table for quick API reference. Updates subtitle to mention
Vue 3 and TypeScript.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
README.md | 30 +++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index e47d9148..6a26b83b 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@
# Notivue
-### Powerful toast notification system for Vue
+### Toast notification system for Vue 3
+
+Fully typed. Works with Vite, Nuxt, and Astro.
[Live Demo](https://notivue.smastrom.io) - [Documentation](https://docs.notivue.smastrom.io)
@@ -14,21 +16,31 @@
## Features
-🧬 **Fully modular with zero dependencies** - Zero bloat, only the logic you actually import reaches your production bundle.
+🧬 **Modular, zero dependencies** - Only what you import reaches your bundle
+
+✅ **Ready-made notifications** - CSS-variable themes with dark mode, progress bars, and RTL support.
-✅ **Beautiful, ready-made notifications included** - Accessible, CSS-variable-based themes with built-in dark mode, progress bars, and RTL support.
+🧩 **Headless API** - Bring your own components. Notivue handles the stack and lifecycle.
-🧩 **Headless API** - Total decoupling of logic and UI; bring your own custom components while Notivue manages the stack and lifecycle
+🌀 **Promise API** - Update a notification from _loading_ to _success_ or _error_ with a reactive handle.
-🌀 **Dynamic Notifications (Promise API)** - Update a single notification from _loading_ to _success_ or _error_ using a reactive handle
+♿️ **Accessible** - Live regions, reduced-motion, pause-on-hover, pause-on-focus, swipe-to-dismiss, and full keyboard navigation.
-💊 **Drop-in components to enhance notifications** - Optional add-ons like _NotivueSwipe_ for touch gestures and _NotivueKeyboard_ for keyboard navigation
+🛡️ **Battle-tested** - 160+ E2E tests
-♿️ **Fully accessible (A11y)** - WAI-ARIA compliant with automatic live regions, _reduced-motion_ support and built-in keyboard navigation.
+💫 **Nuxt and Astro modules** - Zero-config framework integration
-🛡️ **Battle-tested** - 150+ E2E tests ensuring consistent behavior and reliability
+## Key exports
-💫 **Nuxt and Astro modules** - Framework-specific wrappers providing seamless integration and zero boilerplate
+| Export | What it does |
+| ----------------- | ------------------------------------------------------------------------------------------------------- |
+| `createNotivue` | Plugin factory — pass config, install on the app |
+| `notify` | Push notifications: `notify.success()`, `.error()`, `.warning()`, `.info()`, `.promise()`, `.loading()` |
+| `Notivue` | Renderless stream component — renders the notification list |
+| `Notification` | Built-in notification component with themes |
+| `NotivueSwipe` | Wrapper — adds swipe-to-dismiss |
+| `NotivueKeyboard` | Wrapper — adds full keyboard navigation |
+| `useNotivue` | Composable — reactive access to config |
## Installation
From 6667da459da7ae423ebb3836b26d719c12945c37 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 10:59:30 +0100
Subject: [PATCH 32/68] Docs - Move key exports table below examples; add
useNotifications
Co-Authored-By: Claude Opus 4.6
---
.gitignore | 1 -
README.md | 27 ++++++++++++++-------------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/.gitignore b/.gitignore
index 354b0a3e..664e22be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,6 @@ notivue-*.tgz
.nuxt
pnpm-lock.yaml
-
docs/
.claude/
KEYBOARD_FINDINGS.md
diff --git a/README.md b/README.md
index 6a26b83b..5cbaf3dd 100644
--- a/README.md
+++ b/README.md
@@ -28,19 +28,7 @@ Fully typed. Works with Vite, Nuxt, and Astro.
🛡️ **Battle-tested** - 160+ E2E tests
-💫 **Nuxt and Astro modules** - Zero-config framework integration
-
-## Key exports
-
-| Export | What it does |
-| ----------------- | ------------------------------------------------------------------------------------------------------- |
-| `createNotivue` | Plugin factory — pass config, install on the app |
-| `notify` | Push notifications: `notify.success()`, `.error()`, `.warning()`, `.info()`, `.promise()`, `.loading()` |
-| `Notivue` | Renderless stream component — renders the notification list |
-| `Notification` | Built-in notification component with themes |
-| `NotivueSwipe` | Wrapper — adds swipe-to-dismiss |
-| `NotivueKeyboard` | Wrapper — adds full keyboard navigation |
-| `useNotivue` | Composable — reactive access to config |
+💫 **Nuxt and Astro modules** - Zero-config framework integration |
## Installation
@@ -253,6 +241,19 @@ export default defineNuxtConfig({
[Installation → Astro](https://docs.notivue.smastrom.io/installation/astro.html) — `createNotivue` in the Vue app entry, Notivue island, `client:only`, view transitions.
+## Key exports
+
+| Export | What it does |
+| ------------------ | ------------------------------------------------------------------------------------------------------- |
+| `createNotivue` | Plugin factory — pass config, install on the app |
+| `notify` | Push notifications: `notify.success()`, `.error()`, `.warning()`, `.info()`, `.promise()`, `.loading()` |
+| `Notivue` | Renderless stream component — renders the notification list |
+| `Notification` | Built-in notification component with themes |
+| `NotivueSwipe` | Wrapper — adds swipe-to-dismiss |
+| `NotivueKeyboard` | Wrapper — adds full keyboard navigation |
+| `useNotivue` | Composable — reactive access to config |
+| `useNotifications` | Composable — reactive access to notifications |
+
## Thanks
- [Ionic Team](https://ionic.io/) for the icons
From 36650e26a44fb92ec6f34ef3152c85bc52b6ac9e Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 11:01:05 +0100
Subject: [PATCH 33/68] Core - Improve consumer-facing types; skip per-item
callbacks on clearAll/destroyAll
Add Prettify<> to NotivueItem, NotifyOptions, UseNotivueReturn so IDE
hover shows flat fields instead of opaque intersections. Switch generic
constraint from extends Obj to extends object. Mark legacy aliases as
deprecated. clearAll/destroyAll no longer fire onClear/onDestroy per item.
Co-Authored-By: Claude Opus 4.6
---
packages/notivue/core/createStore.ts | 6 ---
packages/notivue/core/types.ts | 66 +++++++++++++++++-----------
2 files changed, 40 insertions(+), 32 deletions(-)
diff --git a/packages/notivue/core/createStore.ts b/packages/notivue/core/createStore.ts
index 93d1316f..0cabed0f 100644
--- a/packages/notivue/core/createStore.ts
+++ b/packages/notivue/core/createStore.ts
@@ -248,10 +248,6 @@ export function createAnimations(
const { clearAll = '' } = config.animations.value
const onAnimationend = () => {
- items.entries.value.forEach((item) => {
- const slotItem = getSlotItem(item)
- ;(item.onClear ?? item.onManualClear)?.(slotItem)
- })
queue.clear()
items.clear()
}
@@ -395,8 +391,6 @@ export function createNotifyProxies({
destroyAll() {
items.entries.value.forEach((item) => {
window.clearTimeout(item.timeout as number)
- const slotItem = getSlotItem(item)
- ;(item.onDestroy ?? item.onManualClear)?.(slotItem)
})
queue.clear()
items.clear()
diff --git a/packages/notivue/core/types.ts b/packages/notivue/core/types.ts
index 89822dfa..965558f8 100644
--- a/packages/notivue/core/types.ts
+++ b/packages/notivue/core/types.ts
@@ -20,6 +20,8 @@ export type DeepPartial = {
export type Obj = Record
+export type Prettify = { [K in keyof T]: T[K] } & {}
+
// —— Stream: kinds, layout, `createNotivue` config, config patches
/** `data-notivue` value per item. Legacy `promise*` literals are normalized when enqueued. */
@@ -116,12 +118,12 @@ export type InternalItemData = ExposedInternalItemData & HiddenInternalItemData
// —— `notify()` / `push`
-export interface NotifyProps {
+export interface NotifyProps {
props?: T
}
/** @deprecated Use `NotifyProps`. */
-export type PushProps = NotifyProps
+export type PushProps = NotifyProps
export interface NotifySpecificOptions {
skipQueue?: boolean
@@ -134,9 +136,9 @@ export type PushSpecificOptions = NotifySpecificOptions
export interface NotifyCallbacks {
/** Runs when the notification's duration expires (timeout). */
onTimedOut?: (item: NotivueItem) => void
- /** Runs when the user dismisses the notification or `clear()` / `clearAll()` is called. */
+ /** Runs when the user dismisses the notification or `clear()` is called. */
onClear?: (item: NotivueItem) => void
- /** Runs when `destroy()` or `destroyAll()` is called. */
+ /** Runs when `destroy()` is called. */
onDestroy?: (item: NotivueItem) => void
/** @deprecated Use `onTimedOut`. */
@@ -148,43 +150,44 @@ export interface NotifyCallbacks {
/** @deprecated Use `NotifyCallbacks`. */
export type PushCallbacks = NotifyCallbacks
-export type NotifyOptions = NotificationOptions &
- NotifyProps &
- NotifySpecificOptions &
- NotifyCallbacks
+export type NotifyOptions = Prettify<
+ NotificationOptions & NotifyProps & NotifySpecificOptions & NotifyCallbacks
+>
/** @deprecated Use `NotifyOptions`. */
-export type PushOptions = NotifyOptions
+export type PushOptions = NotifyOptions
export type InternalNotifyOptions = { id: string; type: NotificationType }
/** @deprecated Use `InternalNotifyOptions`. */
export type InternalPushOptions = InternalNotifyOptions
-export type NotifyOptionsWithInternals = NotifyOptions &
+export type NotifyOptionsWithInternals = NotifyOptions &
InternalNotifyOptions
/** @deprecated Use `NotifyOptionsWithInternals`. */
-export type PushOptionsWithInternals = NotifyOptionsWithInternals
+export type PushOptionsWithInternals = NotifyOptionsWithInternals
-export type StoreItem = DeepRequired &
+export type StoreItem = DeepRequired &
Required> &
InternalNotifyOptions &
InternalItemData &
NotifySpecificOptions &
NotifyCallbacks
-export type NotivueItem = Omit, keyof HiddenInternalItemData>
+export type NotivueItem = Prettify<
+ Omit, keyof HiddenInternalItemData>
+>
-export type NotifyParameter =
+export type NotifyParameter =
| NotifyOptions
| Exclude
/** @deprecated Use `NotifyParameter`. */
-export type PushParameter = NotifyParameter
+export type PushParameter = NotifyParameter
/** Shared arity for methods that only take options and return clear handles. */
-type NotifyOptionsToClearMethods = (
+type NotifyOptionsToClearMethods = (
options: NotifyParameter
) => NotificationClearMethods
@@ -213,7 +216,7 @@ export type NotifyPromiseReturn = NotifyLoadingReturn
/** @deprecated Use `NotifyLoadingReturn`. */
export type PushPromiseReturn = NotifyLoadingReturn
-export type NotifyLoading = (
+export type NotifyLoading = (
options: NotifyParameter
) => NotificationClearMethods & NotifyLoadingReturn
@@ -273,29 +276,40 @@ export interface NotivueComputedEntries {
queue: ComputedRef
}
-export type UseNotivueReturn = ConfigSlice & {
- isStreamPaused: ComputedRef
- /** @deprecated Create computed property instead: `computed(() => config.position.value.startsWith('top'))`. */
- isTopAlign: ComputedRef
-}
+export type UseNotivueReturn = Prettify<
+ ConfigSlice & {
+ isStreamPaused: ComputedRef
+ /** @deprecated Create computed property instead: `computed(() => config.position.value.startsWith('top'))`. */
+ isTopAlign: ComputedRef
+ }
+>
// —— Extra public names (historical / ergonomics)
+/** @deprecated Use `NotificationOptions`. */
export type NotivueNotificationOptions = NotificationOptions
+/** @deprecated Use `Position`. */
export type NotivuePosition = Position
+/** @deprecated Use `NotificationType`. */
export type NotivueNotificationType = NotificationType
+/** @deprecated Use `NotificationClearMethods`. */
export type NotifyClearMethods = NotificationClearMethods
-/** @deprecated Use `NotifyClearMethods`. */
-export type PushClearMethods = NotifyClearMethods
+/** @deprecated Use `NotificationClearMethods`. */
+export type PushClearMethods = NotificationClearMethods
+/** @deprecated Use `NotificationType`. */
export type NotificationTypes = NotificationType
+/** @deprecated Use `NotivueItem`. */
export type NotivueSlot = NotivueItem
+/** @deprecated Use `NotifyOptions`. */
export type UserNotifyOptions = NotifyOptions
-/** @deprecated Use `UserNotifyOptions`. */
-export type UserPushOptions = UserNotifyOptions
+/** @deprecated Use `NotifyOptions`. */
+export type UserPushOptions = NotifyOptions
+/** @deprecated Use `NotificationClearMethods`. */
export type ClearFunctions = NotificationClearMethods
+/** @deprecated Use `NotificationClearMethods`. */
export type ClearMethods = NotificationClearMethods
From aae1163d3ffa4e1e00d6b0ce4971b727117f401a Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 14:51:38 +0100
Subject: [PATCH 34/68] Playground - Use Nuxt 4 idioms; clean up config
Replace manual isSSR with import.meta.client/import.meta.server,
remove deprecated hid meta attributes, remove redundant srcDir/ssr options.
Co-Authored-By: Claude Opus 4.6
---
playground/app.vue | 3 ++-
playground/utils/head.ts | 10 ----------
playground/utils/misc.ts | 4 +---
3 files changed, 3 insertions(+), 14 deletions(-)
diff --git a/playground/app.vue b/playground/app.vue
index 935312c5..d0de5a11 100644
--- a/playground/app.vue
+++ b/playground/app.vue
@@ -28,8 +28,9 @@ const config = useNotivue()
const themes = { lightTheme, pastelTheme, materialTheme, darkTheme, slateTheme } as const
-!isSSR &&
+if (import.meta.client) {
watchEffect(() => document.documentElement.style.setProperty('--nv-root-width', state.maxWidth))
+}
watch(
() => [config.enqueue.value, config.limit.value],
diff --git a/playground/utils/head.ts b/playground/utils/head.ts
index da77d2ea..464e5772 100644
--- a/playground/utils/head.ts
+++ b/playground/utils/head.ts
@@ -14,48 +14,38 @@ export function getHead() {
},
meta: [
{
- hid: 'description',
name: 'description',
content: description,
},
{
- hid: 'og:title',
property: 'og:title',
content: 'Notivue - ' + description,
},
{
- hid: 'og:description',
property: 'og:description',
content: description,
},
{
- hid: 'og:image',
property: 'og:image',
content: '/og-image.jpg',
},
{
- hid: 'og:url',
property: 'og:url',
content: 'https://notivue.smastrom.io',
},
{
- hid: 'twitter:title',
name: 'twitter:title',
content: 'Notivue - ' + description,
},
{
- hid: 'twitter:description',
name: 'twitter:description',
content: description,
},
-
{
- hid: 'twitter:image',
name: 'twitter:image',
content: '/og-image.jpg',
},
{
- hid: 'twitter:card',
name: 'twitter:card',
content: 'summary_large_image',
},
diff --git a/playground/utils/misc.ts b/playground/utils/misc.ts
index 4ac571d2..73cbc584 100644
--- a/playground/utils/misc.ts
+++ b/playground/utils/misc.ts
@@ -4,9 +4,7 @@ export function getRandomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min) + min)
}
-export const isSSR = typeof window === 'undefined'
-
export function isMobile() {
- if (isSSR) return false
+ if (import.meta.server) return false
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent)
}
From 37c25a47ff0160e082110c33d6d13100a62a27b6 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 14:51:54 +0100
Subject: [PATCH 35/68] Playground - Display package version next to logo
Read version from packages/notivue/package.json at build time via
__NOTIVUE_VERSION__ define; show version badge below the SVG logo.
Co-Authored-By: Claude Opus 4.6
---
.../components/shared/Background.server.vue | 96 ++++++++++++-------
playground/nuxt.config.ts | 11 ++-
2 files changed, 67 insertions(+), 40 deletions(-)
diff --git a/playground/components/shared/Background.server.vue b/playground/components/shared/Background.server.vue
index 47cdd0b7..cf4ec850 100644
--- a/playground/components/shared/Background.server.vue
+++ b/playground/components/shared/Background.server.vue
@@ -1,43 +1,52 @@
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
+
v{{ version }}
+
GitHub
—
@@ -64,10 +73,25 @@
gap: 2rem;
}
+.LogoWrapper {
+ position: relative;
+}
+
.Logo {
overflow: visible;
}
+.Version {
+ position: absolute;
+ right: 0;
+ bottom: -1.25rem;
+ font-size: 0.95rem;
+ font-weight: 600;
+ font-family: var(--app-font-family);
+ opacity: 0.4;
+ color: var(--links-color);
+}
+
nav {
width: 100%;
display: flex;
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 5396b50c..e55965cf 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -1,10 +1,11 @@
+import { readFileSync } from 'node:fs'
+
import { getHead } from './utils/head'
-/** `NavPushHeadless.vue` keeps the legacy `push` export and `Push*` types for alias regression testing. */
+const { version } = JSON.parse(readFileSync('../packages/notivue/package.json', 'utf-8'))
+
export default defineNuxtConfig({
- srcDir: '.',
modules: ['notivue/nuxt'],
- ssr: true,
devtools: {
enabled: false,
},
@@ -23,6 +24,9 @@ export default defineNuxtConfig({
head: getHead(),
},
vite: {
+ define: {
+ __NOTIVUE_VERSION__: JSON.stringify(version),
+ },
optimizeDeps: {
include: ['luxon'],
},
@@ -30,7 +34,6 @@ export default defineNuxtConfig({
transformer: 'lightningcss',
},
build: {
- minify: !import.meta.env.DEV,
cssMinify: 'lightningcss',
},
},
From 03b236e22438c168ca52d1562ba8461b8605a67c Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 15:11:57 +0100
Subject: [PATCH 36/68] Core, Docs - Default renderAnnouncement to false; add
isCandidate example
Leave/empty announcements are now screen-reader-only by default.
Add isCandidate usage example to keyboard navigation docs.
Co-Authored-By: Claude Opus 4.6
---
packages/notivue/NotivueKeyboard/constants.ts | 2 +-
packages/notivue/NotivueKeyboard/types.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/notivue/NotivueKeyboard/constants.ts b/packages/notivue/NotivueKeyboard/constants.ts
index 13ebe500..e8d67aa1 100644
--- a/packages/notivue/NotivueKeyboard/constants.ts
+++ b/packages/notivue/NotivueKeyboard/constants.ts
@@ -7,6 +7,6 @@ export const DEFAULT_PROPS = {
leaveMessage:
'You left the notifications stream. Press Tab or Control + N to navigate it again.',
emptyMessage: 'No notifications to navigate',
- renderAnnouncement: true,
+ renderAnnouncement: false,
maxAnnouncements: 2,
} as const
diff --git a/packages/notivue/NotivueKeyboard/types.ts b/packages/notivue/NotivueKeyboard/types.ts
index 2d59be25..e007705b 100644
--- a/packages/notivue/NotivueKeyboard/types.ts
+++ b/packages/notivue/NotivueKeyboard/types.ts
@@ -51,7 +51,7 @@ export interface NotivueKeyboardProps {
/**
* Whether to render the enter/leave notification or just announce it via screen reader.
*
- * @default true
+ * @default false
*/
renderAnnouncement?: boolean
/**
From e3499a419e30646c97196a9589148f13a7a3269f Mon Sep 17 00:00:00 2001
From: Simone Mastromattei
Date: Fri, 27 Mar 2026 13:07:31 +0100
Subject: [PATCH 37/68] Add Claude Code GitHub Workflow (#82)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* "Claude PR Assistant workflow"
* "Claude Code Review workflow"
* Fix workflow permissions and add fork/concurrency guards
- pull-requests: read → write (both workflows)
- issues: read → write (claude.yml)
- Add fork PR guard to skip review on forked PRs
- Add concurrency cancellation for rapid pushes
Co-Authored-By: Claude Opus 4.6 (1M context)
---------
Co-authored-by: Claude Opus 4.6 (1M context)
---
.github/workflows/claude-code-review.yml | 42 ++++++++++++++++++++
.github/workflows/claude.yml | 50 ++++++++++++++++++++++++
2 files changed, 92 insertions(+)
create mode 100644 .github/workflows/claude-code-review.yml
create mode 100644 .github/workflows/claude.yml
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
new file mode 100644
index 00000000..419e8f48
--- /dev/null
+++ b/.github/workflows/claude-code-review.yml
@@ -0,0 +1,42 @@
+name: Claude Code Review
+
+on:
+ pull_request:
+ types: [opened, synchronize, ready_for_review, reopened]
+ # Optional: Only run on specific file changes
+ # paths:
+ # - "src/**/*.ts"
+ # - "src/**/*.tsx"
+ # - "src/**/*.js"
+ # - "src/**/*.jsx"
+
+jobs:
+ claude-review:
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ runs-on: ubuntu-latest
+ concurrency:
+ group: claude-review-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: read
+ id-token: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code Review
+ id: claude-review
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
+ plugins: 'code-review@claude-code-plugins'
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 00000000..4f24565f
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,50 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+ id-token: write
+ actions: read # Required for Claude to read CI results on PRs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+
+ # This is an optional setting that allows Claude to read CI results on PRs
+ additional_permissions: |
+ actions: read
+
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
+ # prompt: 'Update the pull request description to include a summary of changes.'
+
+ # Optional: Add claude_args to customize behavior and configuration
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ # claude_args: '--allowed-tools Bash(gh pr:*)'
+
From f22fb69fea09f40fb568c6254e657a7787bb36c4 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 15:41:11 +0100
Subject: [PATCH 38/68] Remove automatic Claude Code Review workflow
Keep only the @claude interaction workflow for on-demand use on issues and PRs.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
.github/workflows/claude-code-review.yml | 42 ------------------------
1 file changed, 42 deletions(-)
delete mode 100644 .github/workflows/claude-code-review.yml
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
deleted file mode 100644
index 419e8f48..00000000
--- a/.github/workflows/claude-code-review.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: Claude Code Review
-
-on:
- pull_request:
- types: [opened, synchronize, ready_for_review, reopened]
- # Optional: Only run on specific file changes
- # paths:
- # - "src/**/*.ts"
- # - "src/**/*.tsx"
- # - "src/**/*.js"
- # - "src/**/*.jsx"
-
-jobs:
- claude-review:
- if: ${{ !github.event.pull_request.head.repo.fork }}
- runs-on: ubuntu-latest
- concurrency:
- group: claude-review-${{ github.event.pull_request.number }}
- cancel-in-progress: true
- permissions:
- contents: read
- pull-requests: write
- issues: read
- id-token: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Run Claude Code Review
- id: claude-review
- uses: anthropics/claude-code-action@v1
- with:
- claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
- plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
- plugins: 'code-review@claude-code-plugins'
- prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
- # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://code.claude.com/docs/en/cli-reference for available options
-
From 913f1409d1fdba6268ae48536413538594f30d71 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 15:53:27 +0100
Subject: [PATCH 39/68] Add CodeRabbit configuration
Co-Authored-By: Claude Opus 4.6
---
.coderabbit.yaml | 108 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 108 insertions(+)
create mode 100644 .coderabbit.yaml
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
new file mode 100644
index 00000000..11bf26a6
--- /dev/null
+++ b/.coderabbit.yaml
@@ -0,0 +1,108 @@
+# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
+
+language: en-US
+tone_instructions: >-
+ Be direct and concise. Use imperative mood. Skip filler
+ and marketing language. Assume the reader is a competent
+ developer. State facts, not opinions.
+
+reviews:
+ profile: chill
+ high_level_summary: true
+ collapse_walkthrough: true
+ sequence_diagrams: true
+ poem: false
+
+ path_filters:
+ - '!pnpm-lock.yaml'
+ - '!**/*.snap'
+ - '!docs/public/**'
+ - '!playground/**'
+ - '!playground-astro/**'
+
+ path_instructions:
+ - path: 'packages/notivue/core/**'
+ instructions: |
+ This is the core notification engine. Pay close attention to:
+ - Reactivity correctness (Vue 3 reactive/ref usage)
+ - Type safety (strict TypeScript)
+ - Public API surface: any change here affects all consumers
+ - Backwards compatibility of exported types and functions
+ - path: 'packages/notivue/Notivue/**'
+ instructions: |
+ Main wrapper component with aria-live region for accessibility.
+ Ensure ARIA attributes and live region behavior are correct.
+ - path: 'packages/notivue/Notifications/**'
+ instructions: |
+ Pre-built notification components and themes.
+ Check CSS variable usage, theme consistency, and icon accessibility.
+ - path: 'packages/notivue/nuxt/**'
+ instructions: |
+ Nuxt module integration. Verify module hooks, auto-imports,
+ and compatibility with Nuxt 4 conventions.
+ - path: 'packages/notivue/astro/**'
+ instructions: |
+ Astro integration. Ensure SSR/island compatibility
+ and correct lifecycle handling.
+ - path: 'tests/**'
+ instructions: |
+ Test suite using Vitest (unit) and Cypress (component).
+ Includes accessibility testing via cypress-axe.
+ Ensure tests are deterministic and cover edge cases.
+ - path: 'docs/**'
+ instructions: |
+ VitePress documentation. Check code examples for accuracy,
+ correct imports, and consistency with the actual API.
+
+ auto_review:
+ enabled: true
+ drafts: false
+ ignore_title_keywords:
+ - 'WIP'
+ - 'DO NOT MERGE'
+ base_branches:
+ - main
+ - v3
+
+ tools:
+ eslint:
+ enabled: false
+ biome:
+ enabled: false
+ markdownlint:
+ enabled: true
+ actionlint:
+ enabled: true
+ gitleaks:
+ enabled: true
+
+ finishing_touches:
+ docstrings:
+ enabled: false
+ unit_tests:
+ enabled: false
+ simplify:
+ enabled: false
+
+chat:
+ auto_reply: true
+ allow_non_org_members: false
+
+code_generation:
+ unit_tests:
+ path_instructions:
+ - path: 'tests/**'
+ instructions: |
+ This project uses Cypress component tests as the primary
+ test suite, not unit tests. Include cypress-axe accessibility
+ checks where relevant. Use cypress-real-events for user
+ interaction testing. A small Vitest suite exists in
+ tests/config/ for configuration logic only.
+
+knowledge_base:
+ learnings:
+ scope: local
+ web_search:
+ enabled: true
+ code_guidelines:
+ enabled: true
From 26f247e47c28c8e1a0ccb11eaef5a7d931a63ff6 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 16:05:20 +0100
Subject: [PATCH 40/68] CodeRabbit - Prevent PR title/description edits
Co-Authored-By: Claude Opus 4.6
---
.coderabbit.yaml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index 11bf26a6..c631284b 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -8,11 +8,15 @@ tone_instructions: >-
reviews:
profile: chill
- high_level_summary: true
- collapse_walkthrough: true
sequence_diagrams: true
poem: false
+ # Never edit PR title or description. Reviews go in comments only.
+ high_level_summary: false
+ high_level_summary_placeholder: ''
+ auto_title_placeholder: ''
+ collapse_walkthrough: true
+
path_filters:
- '!pnpm-lock.yaml'
- '!**/*.snap'
From 45ad04579d8bba008df5553fdcdb10462521ac02 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 16:18:33 +0100
Subject: [PATCH 41/68] Remove claude-code-review.yml and add workflow
permissions
Remove redundant claude-code-review.yml (keeping claude.yml only).
Add explicit permissions to publish.yml and tests.yml to follow
the principle of least privilege and resolve CodeQL warnings.
Co-Authored-By: Claude Opus 4.6 (1M context)
---
.github/workflows/publish.yml | 2 ++
.github/workflows/tests.yml | 3 +++
2 files changed, 5 insertions(+)
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index a5c0953a..c3f8accd 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -5,6 +5,8 @@ on:
tags: ['v*']
workflow_dispatch:
+permissions: {}
+
jobs:
tests-workflow:
uses: ./.github/workflows/tests.yml
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a4f28767..6951d8fe 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -11,6 +11,9 @@ on:
- '*'
workflow_call:
+permissions:
+ contents: read
+
jobs:
cypress-run:
runs-on: ubuntu-latest
From 2bc2cf4fb694a2349db7cd9213484bf93d50c93b Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 27 Mar 2026 16:26:40 +0100
Subject: [PATCH 42/68] CodeRabbit - Reorganize config; enable incremental
reviews
Co-Authored-By: Claude Opus 4.6
---
.coderabbit.yaml | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index c631284b..18b180dc 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -10,6 +10,17 @@ reviews:
profile: chill
sequence_diagrams: true
poem: false
+ auto_review:
+ enabled: true
+ drafts: false
+ auto_incremental_review: true
+ auto_pause_after_reviewed_commits: 0
+ ignore_title_keywords:
+ - 'WIP'
+ - 'DO NOT MERGE'
+ base_branches:
+ - main
+ - v3
# Never edit PR title or description. Reviews go in comments only.
high_level_summary: false
@@ -58,16 +69,6 @@ reviews:
VitePress documentation. Check code examples for accuracy,
correct imports, and consistency with the actual API.
- auto_review:
- enabled: true
- drafts: false
- ignore_title_keywords:
- - 'WIP'
- - 'DO NOT MERGE'
- base_branches:
- - main
- - v3
-
tools:
eslint:
enabled: false
From 63dacbc6e78d4374f8052254f01d5a97684ec1d4 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 12:52:38 +0200
Subject: [PATCH 43/68] Remove Claude Code GitHub workflow
Co-authored-by: Cursor
---
.github/workflows/claude.yml | 50 ------------------------------------
1 file changed, 50 deletions(-)
delete mode 100644 .github/workflows/claude.yml
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
deleted file mode 100644
index 4f24565f..00000000
--- a/.github/workflows/claude.yml
+++ /dev/null
@@ -1,50 +0,0 @@
-name: Claude Code
-
-on:
- issue_comment:
- types: [created]
- pull_request_review_comment:
- types: [created]
- issues:
- types: [opened, assigned]
- pull_request_review:
- types: [submitted]
-
-jobs:
- claude:
- if: |
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
- (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
- runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: write
- issues: write
- id-token: write
- actions: read # Required for Claude to read CI results on PRs
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Run Claude Code
- id: claude
- uses: anthropics/claude-code-action@v1
- with:
- claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
-
- # This is an optional setting that allows Claude to read CI results on PRs
- additional_permissions: |
- actions: read
-
- # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
- # prompt: 'Update the pull request description to include a summary of changes.'
-
- # Optional: Add claude_args to customize behavior and configuration
- # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
- # or https://code.claude.com/docs/en/cli-reference for available options
- # claude_args: '--allowed-tools Bash(gh pr:*)'
-
From 520f9e2e40df1561d3acf2aeea0c8e60b164d091 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 12:53:15 +0200
Subject: [PATCH 44/68] NotivueKeyboard - Fix focusable element tabindex
selector
Co-authored-by: Cursor
---
packages/notivue/NotivueKeyboard/constants.ts | 2 +-
packages/notivue/NotivueKeyboard/types.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/notivue/NotivueKeyboard/constants.ts b/packages/notivue/NotivueKeyboard/constants.ts
index e8d67aa1..cb399eb3 100644
--- a/packages/notivue/NotivueKeyboard/constants.ts
+++ b/packages/notivue/NotivueKeyboard/constants.ts
@@ -1,5 +1,5 @@
export const focusableEls =
- 'a[href]:not([tabindex="-1"]), area[href]:not([tabindex="-1"]), input:not([disabled]):not([tabindex="-1"]), select:not([disabled]):not([tabindex="-1"]), textarea:not([disabled]):not([tabindex="-1"]), button:not([disabled]):not([tabindex="-1"]), [tabindex="0"]'
+ 'a[href]:not([tabindex^="-"]), area[href]:not([tabindex^="-"]), input:not([disabled]):not([tabindex^="-"]), select:not([disabled]):not([tabindex^="-"]), textarea:not([disabled]):not([tabindex^="-"]), button:not([disabled]):not([tabindex^="-"]), [tabindex]:not([tabindex^="-"])'
export const DEFAULT_PROPS = {
comboKey: 'n',
diff --git a/packages/notivue/NotivueKeyboard/types.ts b/packages/notivue/NotivueKeyboard/types.ts
index e007705b..68499489 100644
--- a/packages/notivue/NotivueKeyboard/types.ts
+++ b/packages/notivue/NotivueKeyboard/types.ts
@@ -18,7 +18,7 @@ export interface NotivueKeyboardProps {
* for keyboard navigation. Receives the container element.
*
* By default, a container is a candidate if it contains at least one
- * focusable element (buttons, links, inputs, selects, textareas, or elements with `tabindex="0"`).
+ * focusable element (buttons, links, inputs, selects, textareas, or elements with a non-negative `tabindex`).
*
* @default undefined
*/
From 4b45487f0faf996a99e917b2a72c1ae29b35d2b2 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 12:53:46 +0200
Subject: [PATCH 45/68] NotivueKeyboard - Return -1 from deprecated
useNotivueKeyboard shim
Co-authored-by: Cursor
---
packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts b/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
index 79798490..87f6dd0b 100644
--- a/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
+++ b/packages/notivue/NotivueKeyboard/useNotivueKeyboard.ts
@@ -8,7 +8,7 @@ import type { NotivueKeyboardData } from 'notivue'
*/
export function useNotivueKeyboard(): NotivueKeyboardData {
return {
- elementsTabIndex: ref(0),
+ elementsTabIndex: ref(-1),
containersTabIndex: computed(() => ({})),
}
}
From ece05154f7a6ab9f20dbe91c1346f43864f454c4 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 12:58:46 +0200
Subject: [PATCH 46/68] Pkg - Declare vue as peer dependency
Co-authored-by: Cursor
---
packages/notivue/package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/notivue/package.json b/packages/notivue/package.json
index 60da7e51..bf992cf6 100644
--- a/packages/notivue/package.json
+++ b/packages/notivue/package.json
@@ -85,7 +85,8 @@
"peerDependencies": {
"@nuxt/kit": ">=3.5.0",
"@nuxt/schema": ">=3.5.0",
- "defu": ">=6"
+ "defu": ">=6",
+ "vue": ">=3"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
From 5d3871470ea8a173c7efc2f9f1f5d40582c63def Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:04:18 +0200
Subject: [PATCH 47/68] Docs - Add agent workflow guidance
Co-authored-by: Cursor
---
AGENTS.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 AGENTS.md
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..856e0593
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,53 @@
+# Agents
+
+## Project
+
+Notivue is a public OSS Vue 3 toast notification library. Treat `packages/notivue` as the product, with Nuxt and Astro integrations, playgrounds for manual checks, docs for the public contract, and `tests` as the regression suite.
+
+- Keep changes small and library-grade. Prefer existing APIs, file layout, and naming over new patterns.
+- Protect public exports, types, CSS entrypoints, accessibility behavior, SSR compatibility, and backwards compatibility unless the user explicitly asks for a breaking change.
+- For behavior changes, update or add focused tests near the affected area. Cypress component tests are the main suite; Vitest is used for config logic.
+- Never run development servers unless the user explicitly requests them.
+- Use `pnpm` and the repo scripts: `pnpm build`, `pnpm test`, `pnpm test:unit`, `pnpm format:check`, `pnpm dev`, `pnpm dev:astro`.
+
+## Workflow
+
+- **GitHub (PR review):** CodeRabbit comments on pull requests. Triage each thread: fix when valid, dismiss with a short reason when not. Resolve threads after replying.
+- **Local (implementation):** Cursor Agent makes changes, commits, and pushes on request. When answering CodeRabbit on GitHub, post replies on the review thread and note they were written by the agent (see below).
+
+## Writing
+
+- Do not use em-dashes. Use a hyphen, a comma, a colon, or separate sentences instead.
+
+## Git
+
+Match the style on `main`. Do **not** use Conventional Commits (`feat:`, `fix:`, `chore:`, etc.).
+
+**Titles**
+
+- Scoped: `{Area} - {Description}`. Capitalize the part after `-`.
+ - Areas: `Core`, `Notivue`, `Notification`, `Notifications`, `Pkg`, `Demo`, `Playground`, `Astro`, `Tests`, `Docs`, `CodeRabbit`, …
+ - Examples: `Core - Rename useRepositioning to useSizes`, `NotivueKeyboard - Fix focusable element tabindex selector`
+- Multi-scope: `Core, Astro - Cleanup`
+- Releases: `2.4.5 (#63)` (include PR number for version bumps)
+- Small / misc: short imperative is fine (e.g. `Remove Claude Code GitHub workflow`)
+
+**Body (optional)**
+
+- Bullet lines: `* {Area} - …`
+
+**Agent commits**
+
+- Append co-author trailer:
+
+ ```text
+ Co-authored-by: Cursor
+ ```
+
+**GitHub review replies**
+
+- When replying to CodeRabbit (or any PR comment) on the user's behalf, end with:
+
+ ```text
+ Added by Cursor Agent
+ ```
From ba94767c537066a1f2258da7a30522ebf0f1b94d Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:06:44 +0200
Subject: [PATCH 48/68] Astro - Generate declarations for subpath export
Co-authored-by: Cursor
---
packages/notivue/package.json | 2 +-
packages/notivue/scripts/verify-tarball.sh | 2 +-
packages/notivue/tsdown.config.ts | 22 ++++++++++++++--------
3 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/packages/notivue/package.json b/packages/notivue/package.json
index bf992cf6..55fe4190 100644
--- a/packages/notivue/package.json
+++ b/packages/notivue/package.json
@@ -49,7 +49,7 @@
},
"./astro": {
"import": "./dist/astro.js",
- "types": "./dist/index.d.ts"
+ "types": "./dist/astro.d.ts"
},
"./notifications.css": "./dist/Notifications/notifications.css",
"./notifications-progress.css": "./dist/Notifications/notifications-progress.css",
diff --git a/packages/notivue/scripts/verify-tarball.sh b/packages/notivue/scripts/verify-tarball.sh
index 86e03c26..8b873043 100755
--- a/packages/notivue/scripts/verify-tarball.sh
+++ b/packages/notivue/scripts/verify-tarball.sh
@@ -13,7 +13,7 @@ mkdir -p $temp_dir
declare -a files=(
"README.md" "package.json" "LICENSE"
- "dist/index.js" "dist/index.d.ts" "dist/astro.js"
+ "dist/index.js" "dist/index.d.ts" "dist/astro.js" "dist/astro.d.ts"
"dist/core/animations.css" "dist/Notifications/notifications.css" "dist/Notifications/notifications-progress.css"
"nuxt/module.mjs" "nuxt/module.cjs" "nuxt/module.d.ts" "nuxt/module.json"
"shared/exports.js"
diff --git a/packages/notivue/tsdown.config.ts b/packages/notivue/tsdown.config.ts
index a1abaf92..b960c2f8 100644
--- a/packages/notivue/tsdown.config.ts
+++ b/packages/notivue/tsdown.config.ts
@@ -52,16 +52,22 @@ export default defineConfig({
...getComponents({ omit: ['Notivue'] }),
...getObjects(),
]
- writeFileSync(
- 'dist/astro.js',
+ const astroAliases = [
+ ['notifyAstro', 'notify'],
+ ['pushAstro', 'push'],
+ ['NotivueAstro', 'Notivue'],
+ ['createNotivueAstro', 'createNotivue'],
+ ] as const
+ const getAstroExports = (source: './index.js' | './index') =>
[
- 'export { notifyAstro as notify } from "./index.js";',
- 'export { pushAstro as push } from "./index.js";',
- 'export { NotivueAstro as Notivue } from "./index.js";',
- 'export { createNotivueAstro as createNotivue } from "./index.js";',
- ...astroReExports.map((name) => `export { ${name} } from "./index.js";`),
+ ...astroAliases.map(
+ ([name, alias]) => `export { ${name} as ${alias} } from "${source}";`
+ ),
+ ...astroReExports.map((name) => `export { ${name} } from "${source}";`),
].join('\n')
- )
+
+ writeFileSync('dist/astro.js', getAstroExports('./index.js'))
+ writeFileSync('dist/astro.d.ts', getAstroExports('./index'))
},
},
})
From edb96ae8aa9d7a77a9e7deb6c5ed01d8db8a1127 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:18:02 +0200
Subject: [PATCH 49/68] Notifications - Remove blank line before box-shadow
Co-authored-by: Cursor
---
packages/notivue/Notifications/notifications.css | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/notivue/Notifications/notifications.css b/packages/notivue/Notifications/notifications.css
index b3b3996e..65ff55e1 100644
--- a/packages/notivue/Notifications/notifications.css
+++ b/packages/notivue/Notifications/notifications.css
@@ -49,7 +49,6 @@
border-radius: var(--nv-radius, 0);
border: var(--nv-border-width, 0) solid var(--nv-border, var(--nv-global-border));
overflow: hidden;
-
box-shadow: var(--nv-shadow, 0 0 rgba(0, 0, 0, 0));
&::before {
From 185f57c41ce0b73e73bb1038c4eb3b8802c65b0f Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:19:40 +0200
Subject: [PATCH 50/68] Pkg - Drop stale KEYBOARD_FINDINGS from gitignore
Co-authored-by: Cursor
---
.gitignore | 1 -
1 file changed, 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 664e22be..810a0516 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,3 @@ notivue-*.tgz
pnpm-lock.yaml
docs/
.claude/
-KEYBOARD_FINDINGS.md
From 40eb4b1618b142daf7b23445cc005b1057212beb Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:27:04 +0200
Subject: [PATCH 51/68] Core - Align warning ARIA defaults with error severity
Co-authored-by: Cursor
---
packages/notivue/core/constants.ts | 4 ++--
packages/notivue/core/types.ts | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/notivue/core/constants.ts b/packages/notivue/core/constants.ts
index b45813bc..c3b63430 100644
--- a/packages/notivue/core/constants.ts
+++ b/packages/notivue/core/constants.ts
@@ -1,4 +1,4 @@
-import { NotificationType as NTypeU, NotivueConfigRequired, NotificationOptions } from 'notivue'
+import { NotificationType as NTypeU, NotivueConfigRequired } from 'notivue'
export const CLASS_PREFIX = 'Notivue__'
@@ -33,7 +33,7 @@ export const DEFAULT_NOTIFICATION_OPTIONS = {
},
[NotificationTypeKeys.SUCCESS]: {},
[NotificationTypeKeys.ERROR]: { ariaLive: 'assertive', ariaRole: 'alert' },
- [NotificationTypeKeys.WARNING]: { ariaRole: 'alert' },
+ [NotificationTypeKeys.WARNING]: { ariaLive: 'assertive', ariaRole: 'alert' },
[NotificationTypeKeys.INFO]: {},
[NotificationTypeKeys.LOADING]: {},
[NotificationTypeKeys.LOADING_SUCCESS]: {},
diff --git a/packages/notivue/core/types.ts b/packages/notivue/core/types.ts
index 965558f8..113b3d79 100644
--- a/packages/notivue/core/types.ts
+++ b/packages/notivue/core/types.ts
@@ -59,7 +59,9 @@ export interface NotificationOptions {
title?: string | Ref
message?: string | Ref
duration?: number
+ /** Inherited from `notifications.global` (`'polite'`). Overridden to `'assertive'` for `error`, `warning`, and `loading-error`. */
ariaLive?: 'polite' | 'assertive'
+ /** Inherited from `notifications.global` (`'status'`). Overridden to `'alert'` for `error`, `warning`, and `loading-error`. */
ariaRole?: 'alert' | 'status'
}
From 939f2644a2daa2fa18650607f988d5b46c1175a6 Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:29:38 +0200
Subject: [PATCH 52/68] CodeRabbit - Skip minor nits in reviews
* CodeRabbit - Disable markdownlint integration
* CodeRabbit - Instruct reviews to focus on critical and major issues only
Co-authored-by: Cursor
---
.coderabbit.yaml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.coderabbit.yaml b/.coderabbit.yaml
index 18b180dc..73f3c03a 100644
--- a/.coderabbit.yaml
+++ b/.coderabbit.yaml
@@ -8,6 +8,11 @@ tone_instructions: >-
reviews:
profile: chill
+ instructions: >-
+ Do not report minor severity issues or formatting nits (markdown structure,
+ heading levels, trailing punctuation, blank lines in blockquotes). Focus on
+ critical and major issues that affect correctness, security, or public API
+ behavior.
sequence_diagrams: true
poem: false
auto_review:
@@ -75,7 +80,7 @@ reviews:
biome:
enabled: false
markdownlint:
- enabled: true
+ enabled: false
actionlint:
enabled: true
gitleaks:
From 86086c012726cbaab70daf402807ac84b1d51d8f Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:29:39 +0200
Subject: [PATCH 53/68] Docs - Refresh README intro and features list
Co-authored-by: Cursor
---
README.md | 37 ++++++++-----------------------------
1 file changed, 8 insertions(+), 29 deletions(-)
diff --git a/README.md b/README.md
index 5cbaf3dd..efed2e7f 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
-
-
# Notivue
-### Toast notification system for Vue 3
+## Zero-dependency, powerful, and truly customizable toast notification system for Vue.
Fully typed. Works with Vite, Nuxt, and Astro.
@@ -16,19 +14,13 @@ Fully typed. Works with Vite, Nuxt, and Astro.
## Features
-🧬 **Modular, zero dependencies** - Only what you import reaches your bundle
-
-✅ **Ready-made notifications** - CSS-variable themes with dark mode, progress bars, and RTL support.
-
-🧩 **Headless API** - Bring your own components. Notivue handles the stack and lifecycle.
-
-🌀 **Promise API** - Update a notification from _loading_ to _success_ or _error_ with a reactive handle.
-
-♿️ **Accessible** - Live regions, reduced-motion, pause-on-hover, pause-on-focus, swipe-to-dismiss, and full keyboard navigation.
-
-🛡️ **Battle-tested** - 160+ E2E tests
-
-💫 **Nuxt and Astro modules** - Zero-config framework integration |
+- 🧬 **Modular, zero dependencies** - Only what you import reaches your bundle
+- ✅ **Ready-made notifications** - CSS-variable themes with dark mode, progress bars, and RTL support.
+- 🧩 **Headless API** - Bring your own components. Notivue handles the stack and lifecycle.
+- 🌀 **Promise API** - Update a notification from _loading_ to _success_ or _error_ with a reactive handle.
+- ♿️ **Accessible** - Live regions, reduced-motion, pause-on-hover, pause-on-focus, swipe-to-dismiss, and full keyboard navigation.
+- 🛡️ **Battle-tested** - 160+ E2E tests
+- 💫 **Nuxt and Astro modules** - Zero-config framework integration
## Installation
@@ -241,19 +233,6 @@ export default defineNuxtConfig({
[Installation → Astro](https://docs.notivue.smastrom.io/installation/astro.html) — `createNotivue` in the Vue app entry, Notivue island, `client:only`, view transitions.
-## Key exports
-
-| Export | What it does |
-| ------------------ | ------------------------------------------------------------------------------------------------------- |
-| `createNotivue` | Plugin factory — pass config, install on the app |
-| `notify` | Push notifications: `notify.success()`, `.error()`, `.warning()`, `.info()`, `.promise()`, `.loading()` |
-| `Notivue` | Renderless stream component — renders the notification list |
-| `Notification` | Built-in notification component with themes |
-| `NotivueSwipe` | Wrapper — adds swipe-to-dismiss |
-| `NotivueKeyboard` | Wrapper — adds full keyboard navigation |
-| `useNotivue` | Composable — reactive access to config |
-| `useNotifications` | Composable — reactive access to notifications |
-
## Thanks
- [Ionic Team](https://ionic.io/) for the icons
From b82ce86914dfd0f56294ab84144aabd21de99f9e Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:43:37 +0200
Subject: [PATCH 54/68] Pkg - Add oxlint and two-pass format pipeline
* Pkg - Add oxlint with stylistic import and padding rules
* Pkg - Wire lint-staged and format scripts for oxfmt/prettier then oxlint --fix
Co-authored-by: Cursor
---
.oxlintrc.json | 93 +++++++++++++++++++++++++++++++++++++++++++++
oxlint-plugins.mjs | 13 +++++++
package.json | 19 +++++++--
pnpm-workspace.yaml | 5 +++
4 files changed, 126 insertions(+), 4 deletions(-)
create mode 100644 .oxlintrc.json
create mode 100644 oxlint-plugins.mjs
diff --git a/.oxlintrc.json b/.oxlintrc.json
new file mode 100644
index 00000000..0737ecb4
--- /dev/null
+++ b/.oxlintrc.json
@@ -0,0 +1,93 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "plugins": ["typescript", "unicorn", "oxc"],
+ "jsPlugins": ["./oxlint-plugins.mjs"],
+ "categories": {
+ "correctness": "off",
+ "perf": "off",
+ "restriction": "off",
+ "suspicious": "off",
+ "pedantic": "off",
+ "style": "off",
+ "nursery": "off"
+ },
+ "rules": {
+ "stylistic/sort-imports": [
+ "error",
+ {
+ "internalPattern": ["^@/.+"],
+ "newlinesBetween": 1,
+ "customGroups": [
+ { "groupName": "vue-files", "elementNamePattern": "\\.vue$" },
+ { "groupName": "constants", "elementNamePattern": "/constants(/|$)" }
+ ],
+ "groups": [
+ "vue-files",
+ "type",
+ "builtin",
+ "external",
+ "internal",
+ "constants",
+ ["parent", "sibling", "index"],
+ "side-effect",
+ "style",
+ "unknown"
+ ]
+ }
+ ],
+ "stylistic/padding-line-between-statements": [
+ "error",
+ { "blankLine": "always", "prev": "import", "next": "*" },
+ { "blankLine": "any", "prev": "import", "next": "import" },
+ {
+ "blankLine": "always",
+ "prev": ["block-like", "if", "for", "while", "do", "switch", "try"],
+ "next": "*"
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": ["block-like", "if", "for", "while", "do", "switch", "try"]
+ },
+ {
+ "blankLine": "any",
+ "prev": ["block-like", "if", "for", "while", "do", "switch", "try"],
+ "next": ["block-like", "if", "for", "while", "do", "switch", "try"]
+ },
+ { "blankLine": "always", "prev": "expression", "next": ["const", "let"] },
+ { "blankLine": "always", "prev": ["const", "let"], "next": "expression" },
+ { "blankLine": "always", "prev": "const", "next": "let" },
+ { "blankLine": "always", "prev": "let", "next": "const" },
+ {
+ "blankLine": "always",
+ "prev": [
+ { "selector": "ExpressionStatement[expression.type='AssignmentExpression']" },
+ { "selector": "ExpressionStatement[expression.type='UpdateExpression']" }
+ ],
+ "next": [
+ { "selector": "ExpressionStatement[expression.type='CallExpression']" },
+ { "selector": "ExpressionStatement[expression.type='AwaitExpression']" }
+ ]
+ },
+ {
+ "blankLine": "always",
+ "prev": [
+ { "selector": "ExpressionStatement[expression.type='CallExpression']" },
+ { "selector": "ExpressionStatement[expression.type='AwaitExpression']" }
+ ],
+ "next": [
+ { "selector": "ExpressionStatement[expression.type='AssignmentExpression']" },
+ { "selector": "ExpressionStatement[expression.type='UpdateExpression']" }
+ ]
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": ["return", "throw", "break", "continue"]
+ }
+ ]
+ },
+ "env": {
+ "builtin": true
+ }
+}
diff --git a/oxlint-plugins.mjs b/oxlint-plugins.mjs
new file mode 100644
index 00000000..60c7c7ab
--- /dev/null
+++ b/oxlint-plugins.mjs
@@ -0,0 +1,13 @@
+import stylistic from '@stylistic/eslint-plugin'
+import perfectionist from 'eslint-plugin-perfectionist'
+
+const paddingLineBetweenStatements = stylistic.rules['padding-line-between-statements']
+const sortImports = perfectionist.rules['sort-imports']
+
+export default {
+ meta: { name: 'stylistic' },
+ rules: {
+ 'padding-line-between-statements': paddingLineBetweenStatements,
+ 'sort-imports': sortImports,
+ },
+}
diff --git a/package.json b/package.json
index dcc214b2..c1f7b22e 100644
--- a/package.json
+++ b/package.json
@@ -9,21 +9,32 @@
"docs:build": "pnpm -C docs run build",
"docs:dev": "pnpm -C docs run dev",
"docs:preview": "pnpm -C docs run preview",
- "format": "oxfmt .",
- "format:check": "oxfmt --check .",
+ "format": "oxfmt . && pnpm -C playground-astro exec prettier --write \"src/**/*.astro\" && oxlint --fix .",
+ "format:check": "oxfmt --check . && oxlint .",
"prepare": "husky",
"test": "pnpm build && pnpm install && pnpm -C tests run test",
"test:gui": "pnpm build && concurrently \"pnpm -C packages/notivue run watch\" \"pnpm -C tests run test:gui\"",
"test:unit": "pnpm -C tests run test:unit"
},
"devDependencies": {
+ "@stylistic/eslint-plugin": "^5.10.0",
"concurrently": "^9.1.2",
+ "eslint-plugin-perfectionist": "^5.9.0",
"husky": "^9.1.7",
"lint-staged": "^15.4.3",
- "oxfmt": "^0.42.0"
+ "oxfmt": "^0.42.0",
+ "oxlint": "^1.67.0"
},
"lint-staged": {
- "*.{js,ts,mts,cts,mjs,cjs,vue,json,css,md}": "oxfmt --write"
+ "*.{js,ts,mts,cts,mjs,cjs,vue}": [
+ "oxfmt --write",
+ "oxlint --fix"
+ ],
+ "*.{json,css,md,html}": "oxfmt --write",
+ "*.astro": [
+ "pnpm -C playground-astro exec prettier --write --config .prettierrc",
+ "oxlint --fix"
+ ]
},
"engines": {
"node": ">=20.19.0"
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 76afc63d..283c58df 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -4,3 +4,8 @@ packages:
- 'playground'
- 'playground-astro'
- 'docs'
+allowBuilds:
+ '@parcel/watcher': true
+ cypress: true
+ esbuild: true
+ sharp: true
From dc87833949e386e3573daaa6a2860863bbb9c25d Mon Sep 17 00:00:00 2001
From: smastrom
Date: Fri, 29 May 2026 13:43:40 +0200
Subject: [PATCH 55/68] Apply oxfmt and oxlint formatting
Co-authored-by: Cursor
---
README.md | 6 +---
.../notivue/Notifications/Notification.vue | 4 +--
packages/notivue/Notifications/constants.ts | 1 +
packages/notivue/Notifications/icons/index.ts | 16 ++++-----
packages/notivue/Notifications/themes.ts | 3 ++
packages/notivue/Notifications/types.ts | 3 +-
packages/notivue/Notivue/AriaLive.vue | 4 +--
packages/notivue/Notivue/Notivue.vue | 10 +++---
packages/notivue/Notivue/NotivueImpl.vue | 16 ++++-----
.../Notivue/composables/useMouseEvents.ts | 2 +-
.../Notivue/composables/useNotivueStyles.ts | 5 +--
.../Notivue/composables/useReducedMotion.ts | 3 +-
.../notivue/Notivue/composables/useSizes.ts | 5 +--
packages/notivue/Notivue/types.ts | 2 +-
.../NotivueKeyboard/NotivueKeyboard.vue | 10 +++---
.../NotivueKeyboard/NotivueKeyboardImpl.vue | 14 ++++++--
.../NotivueKeyboard/useNotivueKeyboard.ts | 4 +--
.../notivue/NotivueSwipe/NotivueSwipe.vue | 10 +++---
packages/notivue/astro/Notivue.vue | 13 ++++----
packages/notivue/astro/createNotivue.ts | 4 +--
packages/notivue/astro/push.ts | 3 +-
packages/notivue/core/createInstance.ts | 5 +--
packages/notivue/core/createNotify.ts | 6 ++--
packages/notivue/core/createNotivue.ts | 6 ++--
packages/notivue/core/createStore.ts | 33 +++++++++++--------
packages/notivue/core/createStoreWatchers.ts | 4 +--
packages/notivue/core/symbols.ts | 2 +-
packages/notivue/core/useStore.ts | 15 +++++----
packages/notivue/core/utils.ts | 14 +++++---
packages/notivue/nuxt/module.cjs | 1 +
packages/notivue/nuxt/module.d.ts | 4 +--
packages/notivue/scripts/verify-exports.js | 4 ++-
playground-astro/astro.config.mjs | 5 ++-
playground-astro/src/layouts/Layout.astro | 5 ++-
playground-astro/src/pages/_app.ts | 4 +--
playground-astro/src/pages/about.astro | 4 +--
playground-astro/src/pages/index.astro | 5 +--
playground/app.vue | 10 +++---
playground/components/nav/NavPushBuiltIn.vue | 2 ++
playground/components/nav/NavPushHeadless.vue | 9 ++---
playground/components/shared/QueueCount.vue | 1 +
playground/middleware/notify.global.ts | 1 +
playground/utils/misc.ts | 2 ++
playground/utils/store.ts | 2 ++
tests/Notifications/elements.cy.ts | 3 +-
tests/Notifications/themes.cy.ts | 1 +
tests/Notivue/components/Notivue.vue | 7 ++--
tests/Notivue/config-enqueue.cy.ts | 5 +--
tests/Notivue/config-limit.cy.ts | 4 +--
tests/Notivue/push-methods.cy.ts | 3 +-
tests/Notivue/slot-default-options.cy.ts | 3 +-
tests/Notivue/slot-internal-properties.cy.ts | 2 +-
tests/NotivueKeyboard/components/Notivue.vue | 7 ++--
tests/NotivueSwipe/debounce.cy.ts | 7 ++--
tests/config/update-config.test.ts | 8 ++---
tests/cypress.config.ts | 5 +--
tests/cypress/support/commands-keyboard.ts | 6 ++--
.../cypress/support/commands-notifications.ts | 7 ++--
tests/cypress/support/commands-notivue.ts | 8 ++---
tests/cypress/support/commands-swipe.ts | 6 ++--
tests/cypress/support/component.ts | 3 --
tests/vite.config.mts | 1 +
62 files changed, 208 insertions(+), 160 deletions(-)
diff --git a/README.md b/README.md
index efed2e7f..0258d2fd 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
# Notivue
-## Zero-dependency, powerful, and truly customizable toast notification system for Vue.
-
-Fully typed. Works with Vite, Nuxt, and Astro.
+**Zero-dependency, powerful, and truly customizable toast notification system for Vue**
[Live Demo](https://notivue.smastrom.io) - [Documentation](https://docs.notivue.smastrom.io)
@@ -231,8 +229,6 @@ export default defineNuxtConfig({
> **Note:** Astro **6** is not supported yet.
-[Installation → Astro](https://docs.notivue.smastrom.io/installation/astro.html) — `createNotivue` in the Vue app entry, Notivue island, `client:only`, view transitions.
-
## Thanks
- [Ionic Team](https://ionic.io/) for the icons
diff --git a/packages/notivue/Notifications/Notification.vue b/packages/notivue/Notifications/Notification.vue
index f3946436..3750bdc0 100644
--- a/packages/notivue/Notifications/Notification.vue
+++ b/packages/notivue/Notifications/Notification.vue
@@ -1,10 +1,10 @@
diff --git a/playground/middleware/notify.global.ts b/playground/middleware/notify.global.ts
index 99bb5575..c0be9ac1 100644
--- a/playground/middleware/notify.global.ts
+++ b/playground/middleware/notify.global.ts
@@ -1,6 +1,7 @@
export default defineNuxtRouteMiddleware(() => {
updateConfig((currConf) => {
console.log('Current config:', currConf)
+
return {}
})
diff --git a/playground/utils/misc.ts b/playground/utils/misc.ts
index 73cbc584..efa6e8d1 100644
--- a/playground/utils/misc.ts
+++ b/playground/utils/misc.ts
@@ -1,10 +1,12 @@
export function getRandomInt(min: number, max: number) {
min = Math.ceil(min)
max = Math.floor(max)
+
return Math.floor(Math.random() * (max - min) + min)
}
export function isMobile() {
if (import.meta.server) return false
+
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent)
}
diff --git a/playground/utils/store.ts b/playground/utils/store.ts
index 4581a5e2..da448cc2 100644
--- a/playground/utils/store.ts
+++ b/playground/utils/store.ts
@@ -28,6 +28,7 @@ export function createStore() {
},
setTheme(themeName: ThemeNames) {
document.documentElement.setAttribute('data-theme', themeName.replace('Theme', ''))
+
state.theme = themeName
},
toggleOutlinedIcons() {
@@ -38,6 +39,7 @@ export function createStore() {
},
toggleRTL() {
state.rtl = !state.rtl
+
document.documentElement.setAttribute('dir', state.rtl ? 'rtl' : 'ltr')
},
toggleCenterOnMobile() {
diff --git a/tests/Notifications/elements.cy.ts b/tests/Notifications/elements.cy.ts
index bdb853c1..10ab6f07 100644
--- a/tests/Notifications/elements.cy.ts
+++ b/tests/Notifications/elements.cy.ts
@@ -1,8 +1,9 @@
import { NotivueIcons, outlinedIcons } from 'notivue'
-import { Classes as _Classes } from '@/Notifications/constants'
import { DEFAULT_ANIM_DURATION } from '@/support/utils'
+import { Classes as _Classes } from '@/Notifications/constants'
+
const { TRANSITION, ...Classes } = _Classes
const defaultClasses = Object.values(Classes).filter((className) => className !== Classes.DUPLICATE)
diff --git a/tests/Notifications/themes.cy.ts b/tests/Notifications/themes.cy.ts
index 6e790c70..ef2986a9 100644
--- a/tests/Notifications/themes.cy.ts
+++ b/tests/Notifications/themes.cy.ts
@@ -1,4 +1,5 @@
import { lightTheme, darkTheme, pastelTheme, materialTheme, slateTheme } from 'notivue'
+
import { Classes } from '@/Notifications/constants'
describe('Themes', () => {
diff --git a/tests/Notivue/components/Notivue.vue b/tests/Notivue/components/Notivue.vue
index e36730b0..f5e4e464 100644
--- a/tests/Notivue/components/Notivue.vue
+++ b/tests/Notivue/components/Notivue.vue
@@ -1,6 +1,4 @@
-
+