diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a57f24..3699b29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,9 +45,15 @@ jobs: name: forgedialog-sbom path: forgedialog-sbom.json + # The core first: the wrapper packages depend on it, so a consumer who + # installs one of them the moment it appears must find the core already + # there. `--workspaces` publishes forgedialog-react and forgedialog-vue, + # each of which needs its own trusted publisher entry on npmjs.com. - name: Publish to npm with OIDC if: vars.NPM_PUBLISH_ENABLED == 'true' - run: npm publish + run: | + npm publish + npm publish --workspaces - name: Skip npm publishing until trusted publishing is enabled if: vars.NPM_PUBLISH_ENABLED != 'true' run: | diff --git a/.gitignore b/.gitignore index 6fa881d..c8133f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules dist +packages/*/dist coverage playwright-report test-results diff --git a/.prettierignore b/.prettierignore index 36270d1..b7f8734 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ dist +packages/*/dist coverage etc node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 473e060..5df70d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,38 @@ # Changelog -## Unreleased +## 0.8.0 -- `homepage` now points at the documentation site rather than the README anchor, so the npm - page links to the reference instead of back to GitHub. +### Breaking + +- The React and Vue adapters are no longer part of this package. `forgedialog/react` and + `forgedialog/vue` are gone; install [`forgedialog-react`](https://www.npmjs.com/package/forgedialog-react) + or [`forgedialog-vue`](https://www.npmjs.com/package/forgedialog-vue) instead. The hook itself is + unchanged, so the migration is the import: + + ```diff + - import { useForgeDialog } from 'forgedialog/react'; + + import { useForgeDialog } from 'forgedialog-react'; + ``` + + ```sh + npm install forgedialog-react # or forgedialog-vue + ``` + +- `forgedialog` no longer declares `react` or `vue` as peer dependencies. They were optional, but + they still put two frameworks in the dependency tree of a library that advertises having none, + and every tool that reads peer dependencies had to be told to ignore them. + +`forgedialog/svelte` and `forgedialog/web-component` are unchanged and stay in the core: neither +needs a framework runtime, so neither costs a peer dependency. + +### Other + +- The published tarball is smaller: 63 files and 117.6 KiB, down from 71 files and 120.3 KiB, + because the React and Vue builds are no longer in it. +- `npm run check:release` now checks every package a release publishes, including that each + wrapper's dependency range on the core is one this release satisfies. +- `homepage` now points at the documentation site rather than the README anchor, so the npm page + links to the reference instead of back to GitHub. ## 0.7.0 diff --git a/README.md b/README.md index 9852314..240835a 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,21 @@ const data = await flow.result; Tree-shakable entry points are available at `forgedialog/core`, `forgedialog/interactions`, `forgedialog/animations`, `forgedialog/appearance`, `forgedialog/presentation`, and `forgedialog/workflows`. `forgedialog/interactions` exposes the -standalone draggable controller without pulling in dialog APIs. Framework integrations are exported from `forgedialog/react`, -`forgedialog/vue`, `forgedialog/svelte`, and `forgedialog/web-component`. +standalone draggable controller without pulling in dialog APIs. + +React and Vue integrations are separate packages, so installing `forgedialog` never asks a project +about a framework it does not use: + +```sh +npm install forgedialog forgedialog-react # or forgedialog-vue +``` + +```ts +import { useForgeDialog } from 'forgedialog-react'; +``` + +Svelte and Web Components need nothing beyond the browser, so they stay in the core as +`forgedialog/svelte` and `forgedialog/web-component`. Single-purpose entry points minimize simple dialogs further: diff --git a/docs/architecture.md b/docs/architecture.md index 45ec7d3..f3269af 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -26,7 +26,10 @@ Public consumers should import only what they need: - `forgedialog/interactions` and `/animations` to opt into advanced runtime capabilities. - `forgedialog/presentation` for drawers, sheets, lightboxes, loading, toast, and commands. - `forgedialog/workflows` for branching async wizards. -- `forgedialog/react`, `/vue`, `/svelte`, or `/web-component` for lifecycle adapters. +- `forgedialog/svelte` or `/web-component` for lifecycle adapters that need no framework + runtime. The React and Vue adapters are the separate `forgedialog-react` and `forgedialog-vue` + packages, in `packages/` — a wrapper that needs a peer dependency does not belong in a core that + advertises having none. The main `forgedialog` entry includes the complete API and default CSS. Subpath entries are side-effect free; styles can be loaded explicitly from `forgedialog/style.css`. diff --git a/docs/releasing.md b/docs/releasing.md index a610752..5824ab4 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -47,6 +47,22 @@ publisher with: > exist first. `forgedialog` is published — 0.7.0 went out on 1 September 2026 — so that page is > available and this can be set up now. The manual first publish described below is history for > this package; it is kept because a fork starting from an unpublished name still needs it. +> +> **`forgedialog-react` and `forgedialog-vue` have not been published yet**, so neither has a +> settings page and neither can have a trusted publisher configured. Their first release has to be +> the manual publish described below; after that, add the same publisher entry to each of them and +> every later release goes through Actions with the core. + +### 2b. One trusted publisher per package + +A release publishes three packages — `forgedialog`, `forgedialog-react`, and `forgedialog-vue` — +and npm scopes trusted publishing to a single package. The same entry from step 2 has to be added +on **each** package's settings page; the workflow filename and environment are identical for all +three, only the package differs. + +Miss one and the release half-succeeds: the core goes out, then `npm publish --workspaces` fails on +the wrapper, leaving a published core beside wrappers that never shipped. `npm run check:release` +does not catch this — it checks versions, not npm's publisher configuration. ### 3. Enable publishing in the workflow @@ -95,9 +111,13 @@ name that has never been published, where step 2 has no settings page to configu ```sh npm run validate:all npm run check:release -npm publish # prompts for auth; use a granular access token scoped to this package +npm publish # the core first: the wrappers depend on it +npm publish --workspaces # forgedialog-react and forgedialog-vue ``` +The order matters. A consumer who installs `forgedialog-react` the moment it appears has to find +the core version it asks for already on the registry. + `publishConfig.access` is already `public`, so no `--access` flag is needed. Afterwards, add the trusted publisher and set `NPM_PUBLISH_ENABLED=true` so later releases go through Actions, and delete the token you used. diff --git a/package-lock.json b/package-lock.json index b166057..f9958d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,16 @@ { "name": "forgedialog", - "version": "0.5.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "forgedialog", - "version": "0.5.0", + "version": "0.8.0", "license": "MIT", + "workspaces": [ + "packages/*" + ], "devDependencies": { "@arethetypeswrong/cli": "^0.18.5", "@axe-core/playwright": "^4.12.1", @@ -15,8 +18,6 @@ "@microsoft/api-extractor": "^7.58.12", "@playwright/test": "^1.61.1", "@types/node": "^20.14.0", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", "@vitest/coverage-v8": "^4.1.10", @@ -26,31 +27,16 @@ "jsdom": "^24.1.0", "prettier": "^3.3.2", "publint": "^0.3.21", - "react": "^19.2.8", - "react-dom": "^19.2.8", "serve": "^14.2.6", "tsup": "^8.1.0", "typescript": "^5.5.3", "vite": "^8.1.5", "vitest": "^4.1.10", - "vue": "^3.5.40", "webpack": "^5.108.4", "webpack-cli": "^7.2.1" }, "engines": { "node": ">=20" - }, - "peerDependencies": { - "react": ">=18", - "vue": ">=3" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "vue": { - "optional": true - } } }, "node_modules/@andrewbranch/untar.js": { @@ -166,7 +152,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -176,7 +162,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -186,7 +172,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -212,7 +198,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -1379,7 +1365,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -3065,7 +3051,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -3079,7 +3065,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3092,14 +3078,14 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@vue/compiler-dom": { "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.40", @@ -3110,7 +3096,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -3128,14 +3114,14 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@vue/compiler-ssr": { "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.40", @@ -3146,7 +3132,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/shared": "3.5.40" @@ -3156,7 +3142,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.40", @@ -3167,7 +3153,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/reactivity": "3.5.40", @@ -3180,7 +3166,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/compiler-ssr": "3.5.40", @@ -3192,7 +3178,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@webassemblyjs/ast": { @@ -4328,7 +4314,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-urls": { @@ -5119,6 +5105,35 @@ "dev": true, "license": "ISC" }, + "node_modules/forgedialog": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/forgedialog/-/forgedialog-0.7.0.tgz", + "integrity": "sha512-bki0lgubD7d0+Hm5KwWt/XbCtDM5qMBQub2EcqLdCFjjUTxBq1vSZFRageWD6XOyiOxeoCPLbhCSIvS7yQuOLA==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "react": ">=18", + "vue": ">=3" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/forgedialog-react": { + "resolved": "packages/react", + "link": true + }, + "node_modules/forgedialog-vue": { + "resolved": "packages/vue", + "link": true + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -6377,7 +6392,7 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -6679,7 +6694,7 @@ "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -7036,7 +7051,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -7204,7 +7219,7 @@ "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -7428,7 +7443,7 @@ "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8130,7 +8145,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -8666,6 +8681,490 @@ } } }, + "node_modules/tsup/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/tsup/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -9041,7 +9540,7 @@ "version": "3.5.40", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.40", @@ -9545,6 +10044,44 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "packages/react": { + "name": "forgedialog-react", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "forgedialog": ">=0.7.0 <1.0.0" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "packages/vue": { + "name": "forgedialog-vue", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "forgedialog": ">=0.7.0 <1.0.0" + }, + "devDependencies": { + "vue": "^3.5.40" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "vue": ">=3" + } } } } diff --git a/package.json b/package.json index abc3182..1ad84a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "forgedialog", - "version": "0.7.0", + "version": "0.8.0", "description": "A modern, lightweight, and highly customizable JavaScript dialog library for alerts, confirmations, modals, drawers, and interactive workflows.", "type": "module", "license": "MIT", @@ -31,6 +31,9 @@ "./dist/appearance.mjs", "./dist/appearance.cjs" ], + "workspaces": [ + "packages/*" + ], "files": [ "dist", "!dist/**/*.map", @@ -145,26 +148,6 @@ "default": "./dist/workflows.cjs" } }, - "./react": { - "import": { - "types": "./dist/react.d.ts", - "default": "./dist/react.mjs" - }, - "require": { - "types": "./dist/react.d.cts", - "default": "./dist/react.cjs" - } - }, - "./vue": { - "import": { - "types": "./dist/vue.d.ts", - "default": "./dist/vue.mjs" - }, - "require": { - "types": "./dist/vue.d.cts", - "default": "./dist/vue.cjs" - } - }, "./svelte": { "import": { "types": "./dist/svelte.d.ts", @@ -206,7 +189,7 @@ "size": "npm run build && node scripts/check-size.mjs", "analyze": "node scripts/analyze-bundle.mjs", "test:treeshake": "node scripts/check-treeshaking.mjs", - "check:package": "publint && attw --pack . --ignore-rules=no-resolution", + "check:package": "publint && attw --pack . --ignore-rules=no-resolution && npm run build --workspaces && npm run check:package --workspaces", "api:check": "api-extractor run", "api:update": "api-extractor run --local", "api:subpaths": "node scripts/check-subpath-apis.mjs", @@ -218,19 +201,21 @@ "check:release": "node scripts/check-release.mjs", "changeset": "changeset", "version-packages": "changeset version", - "release": "npm run validate:all && npm run check:release && npm publish", - "typecheck": "tsc --noEmit", - "lint": "eslint src tests e2e", + "release": "npm run validate:all && npm run check:release && npm publish && npm publish --workspaces", + "typecheck": "tsc --noEmit && npm run typecheck --workspaces", + "lint": "eslint src tests e2e packages/*/src packages/*/tests", "format": "prettier --write .", "format:check": "prettier --check .", - "validate": "npm run typecheck && npm run lint && npm run test:coverage && npm run size && npm run test:treeshake && npm run check:package && npm run api:check && npm run api:subpaths && npm run test:consumers && npm run test:capabilities && npm run test:csp && npm run package:size", + "validate": "npm run typecheck && npm run lint && npm run test:coverage && npm run test:packages && npm run size && npm run test:treeshake && npm run check:package && npm run api:check && npm run api:subpaths && npm run test:consumers && npm run test:capabilities && npm run test:csp && npm run package:size", "validate:all": "npm run validate && npm run test:e2e", "demo": "npm run build && serve .", "site:build": "npm run build && node scripts/build-site.mjs && node scripts/check-site.mjs", "site:icons": "node scripts/build-icons.mjs", "site": "npm run site:build && serve site", "prepublishOnly": "npm run build", - "site:check": "node scripts/check-site.mjs" + "site:check": "node scripts/check-site.mjs", + "build:packages": "npm run build --workspaces", + "test:packages": "npm run test --workspaces" }, "keywords": [ "dialog", @@ -247,8 +232,6 @@ "@microsoft/api-extractor": "^7.58.12", "@playwright/test": "^1.61.1", "@types/node": "^20.14.0", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", "@vitest/coverage-v8": "^4.1.10", @@ -258,32 +241,14 @@ "jsdom": "^24.1.0", "prettier": "^3.3.2", "publint": "^0.3.21", - "react": "^19.2.8", - "react-dom": "^19.2.8", "serve": "^14.2.6", "tsup": "^8.1.0", "typescript": "^5.5.3", "vite": "^8.1.5", "vitest": "^4.1.10", - "vue": "^3.5.40", "webpack": "^5.108.4", "webpack-cli": "^7.2.1" }, - "peerDependencies": { - "react": ">=18", - "vue": ">=3" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "vue": { - "optional": true - } - }, - "overrides": { - "esbuild": "^0.28.1" - }, "allowScripts": { "esbuild@0.28.1": true } diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md new file mode 100644 index 0000000..0bf93db --- /dev/null +++ b/packages/react/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.0 + +- First release as its own package. The code is the adapter previously published as + `forgedialog/react` in `forgedialog@0.7.0`, unchanged in behaviour; only its home moved, so the + core no longer declares a peer dependency on a framework most projects using it do not have. diff --git a/packages/react/LICENSE b/packages/react/LICENSE new file mode 100644 index 0000000..daefb05 --- /dev/null +++ b/packages/react/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ForgeDialog contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react/README.md b/packages/react/README.md new file mode 100644 index 0000000..a7ad40e --- /dev/null +++ b/packages/react/README.md @@ -0,0 +1,51 @@ +# forgedialog-react + +The React binding for [Forge Dialog](https://forgedialog.konexforge.com). + +`useForgeDialog()` opens dialogs through the library's `open()` and ties their lifetime to the +component's: a dialog still open when the component unmounts is destroyed rather than left mounted. + +```sh +npm install forgedialog forgedialog-react +``` + +```tsx +import { useForgeDialog } from 'forgedialog-react'; + +function DeleteButton({ onConfirm }: { onConfirm: () => void }) { + const { open } = useForgeDialog(); + + return ( + + ); +} +``` + +`useForgeDialog()` returns: + +| Member | Type | Notes | +| -------- | -------------------------------------------------- | ---------------------------------------------- | +| `open` | `(options: DialogOptions) => DialogInstance` | Stable across renders. | +| `active` | `RefObject \| null>` | The open dialog, or `null` once it has closed. | + +Every option is the core's — see the [API reference](https://forgedialog.konexforge.com/docs). +Import the stylesheet from the core as usual: `import 'forgedialog/style.css'`. + +React and `react-dom` are peer dependencies, so this package uses the copy your application already +has. `forgedialog` is a real dependency and is installed for you. + +MIT licensed. Source, issues, and changelog: [github.com/cmm-cmm/ForgeDialog](https://github.com/cmm-cmm/ForgeDialog). diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..5f74d8f --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,68 @@ +{ + "name": "forgedialog-react", + "version": "0.1.0", + "description": "React hook for Forge Dialog.", + "keywords": [ + "forgedialog", + "dialog", + "modal", + "react" + ], + "license": "MIT", + "author": "KonexForge", + "homepage": "https://forgedialog.konexforge.com", + "bugs": "https://github.com/cmm-cmm/ForgeDialog/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/cmm-cmm/ForgeDialog.git", + "directory": "packages/react" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist", + "CHANGELOG.md" + ], + "sideEffects": false, + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "prepublishOnly": "npm run build", + "check:package": "publint && attw --pack . --ignore-rules=no-resolution" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "dependencies": { + "forgedialog": ">=0.7.0 <1.0.0" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "react": "^19.2.8", + "react-dom": "^19.2.8" + } +} diff --git a/src/adapters/react.ts b/packages/react/src/index.ts similarity index 84% rename from src/adapters/react.ts rename to packages/react/src/index.ts index 64df5cc..a83f089 100644 --- a/src/adapters/react.ts +++ b/packages/react/src/index.ts @@ -1,6 +1,5 @@ import { useCallback, useEffect, useRef } from 'react'; -import { open } from '../api/open'; -import type { DialogInstance, DialogOptions } from '../types'; +import { open, type DialogInstance, type DialogOptions } from 'forgedialog'; export function useForgeDialog() { const active = useRef | null>(null); diff --git a/packages/react/tests/use-forge-dialog.test.ts b/packages/react/tests/use-forge-dialog.test.ts new file mode 100644 index 0000000..5d055b9 --- /dev/null +++ b/packages/react/tests/use-forge-dialog.test.ts @@ -0,0 +1,36 @@ +import { act, createElement } from 'react'; +import { createRoot } from 'react-dom/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { useForgeDialog } from '../src/index'; + +(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = + true; + +afterEach(() => { + document.querySelectorAll('dialog').forEach((dialog) => dialog.remove()); +}); + +describe('useForgeDialog', () => { + it('clears the active dialog once it closes, and destroys it on unmount', async () => { + const host = document.createElement('div'); + document.body.appendChild(host); + let api: ReturnType | undefined; + function Harness() { + api = useForgeDialog(); + return null; + } + const root = createRoot(host); + await act(async () => root.render(createElement(Harness))); + + const closed = api!.open({ title: 'React dialog' }); + await closed.close(); + expect(api!.active.current).toBeNull(); + + // A dialog still open when the component goes away has to be torn down, or + // it outlives the tree that owns it. + const instance = api!.open({ title: 'React dialog 2' }); + const destroy = vi.spyOn(instance, 'destroy'); + await act(async () => root.unmount()); + expect(destroy).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000..4447890 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx" + }, + "include": ["src", "tests"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts new file mode 100644 index 0000000..d98b653 --- /dev/null +++ b/packages/react/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + target: 'es2019', + dts: true, + sourcemap: true, + clean: true, + // The framework and the core stay external: a wrapper that bundled either + // would ship a second copy of it into every consumer. + external: ['react', 'react-dom', 'vue', 'forgedialog'], +}); diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts new file mode 100644 index 0000000..a741874 --- /dev/null +++ b/packages/react/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + include: ['tests/**/*.test.ts'], + }, +}); diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md new file mode 100644 index 0000000..f2ed785 --- /dev/null +++ b/packages/vue/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## 0.1.0 + +- First release as its own package. The code is the adapter previously published as + `forgedialog/vue` in `forgedialog@0.7.0`, unchanged in behaviour; only its home moved, so the + core no longer declares a peer dependency on a framework most projects using it do not have. diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE new file mode 100644 index 0000000..daefb05 --- /dev/null +++ b/packages/vue/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ForgeDialog contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/vue/README.md b/packages/vue/README.md new file mode 100644 index 0000000..57a122b --- /dev/null +++ b/packages/vue/README.md @@ -0,0 +1,51 @@ +# forgedialog-vue + +The Vue binding for [Forge Dialog](https://forgedialog.konexforge.com). + +`useForgeDialog()` opens dialogs through the library's `open()` and ties their lifetime to the +component's: a dialog still open when the component unmounts is destroyed rather than left mounted. + +```sh +npm install forgedialog forgedialog-vue +``` + +```vue + + + +``` + +`useForgeDialog()` returns: + +| Member | Type | Notes | +| -------- | -------------------------------------------------- | ---------------------------------------------- | +| `open` | `(options: DialogOptions) => DialogInstance` | Opens a dialog. | +| `active` | `ShallowRef \| null>` | The open dialog, or `null` once it has closed. | + +Every option is the core's — see the [API reference](https://forgedialog.konexforge.com/docs). +Import the stylesheet from the core as usual: `import 'forgedialog/style.css'`. + +Vue is a peer dependency, so this package uses the copy your application already has. +`forgedialog` is a real dependency and is installed for you. + +MIT licensed. Source, issues, and changelog: [github.com/cmm-cmm/ForgeDialog](https://github.com/cmm-cmm/ForgeDialog). diff --git a/packages/vue/package.json b/packages/vue/package.json new file mode 100644 index 0000000..1866810 --- /dev/null +++ b/packages/vue/package.json @@ -0,0 +1,64 @@ +{ + "name": "forgedialog-vue", + "version": "0.1.0", + "description": "Vue composable for Forge Dialog.", + "keywords": [ + "forgedialog", + "dialog", + "modal", + "vue" + ], + "license": "MIT", + "author": "KonexForge", + "homepage": "https://forgedialog.konexforge.com", + "bugs": "https://github.com/cmm-cmm/ForgeDialog/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/cmm-cmm/ForgeDialog.git", + "directory": "packages/vue" + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist", + "CHANGELOG.md" + ], + "sideEffects": false, + "scripts": { + "build": "tsup", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "prepublishOnly": "npm run build", + "check:package": "publint && attw --pack . --ignore-rules=no-resolution" + }, + "peerDependencies": { + "vue": ">=3" + }, + "dependencies": { + "forgedialog": ">=0.7.0 <1.0.0" + }, + "devDependencies": { + "vue": "^3.5.40" + } +} diff --git a/src/adapters/vue.ts b/packages/vue/src/index.ts similarity index 83% rename from src/adapters/vue.ts rename to packages/vue/src/index.ts index 4804c1d..1af593b 100644 --- a/src/adapters/vue.ts +++ b/packages/vue/src/index.ts @@ -1,6 +1,5 @@ import { onUnmounted, shallowRef } from 'vue'; -import { open } from '../api/open'; -import type { DialogInstance, DialogOptions } from '../types'; +import { open, type DialogInstance, type DialogOptions } from 'forgedialog'; export function useForgeDialog() { const active = shallowRef | null>(null); diff --git a/packages/vue/tests/use-forge-dialog.test.ts b/packages/vue/tests/use-forge-dialog.test.ts new file mode 100644 index 0000000..4ab393a --- /dev/null +++ b/packages/vue/tests/use-forge-dialog.test.ts @@ -0,0 +1,36 @@ +import { createApp, defineComponent, nextTick } from 'vue'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { useForgeDialog } from '../src/index'; + +afterEach(() => { + document.querySelectorAll('dialog').forEach((dialog) => dialog.remove()); +}); + +describe('useForgeDialog', () => { + it('clears the active dialog once it closes, and destroys it on unmount', async () => { + const host = document.createElement('div'); + document.body.appendChild(host); + let api: ReturnType | undefined; + const app = createApp( + defineComponent({ + setup() { + api = useForgeDialog(); + return () => null; + }, + }), + ); + app.mount(host); + await nextTick(); + + const closed = api!.open({ title: 'Vue dialog' }); + await closed.close(); + expect(api!.active.value).toBeNull(); + + // A dialog still open when the component goes away has to be torn down, or + // it outlives the tree that owns it. + const instance = api!.open({ title: 'Vue dialog 2' }); + const destroy = vi.spyOn(instance, 'destroy'); + app.unmount(); + expect(destroy).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json new file mode 100644 index 0000000..4447890 --- /dev/null +++ b/packages/vue/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx" + }, + "include": ["src", "tests"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/vue/tsup.config.ts b/packages/vue/tsup.config.ts new file mode 100644 index 0000000..d98b653 --- /dev/null +++ b/packages/vue/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + target: 'es2019', + dts: true, + sourcemap: true, + clean: true, + // The framework and the core stay external: a wrapper that bundled either + // would ship a second copy of it into every consumer. + external: ['react', 'react-dom', 'vue', 'forgedialog'], +}); diff --git a/packages/vue/vitest.config.ts b/packages/vue/vitest.config.ts new file mode 100644 index 0000000..a741874 --- /dev/null +++ b/packages/vue/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + include: ['tests/**/*.test.ts'], + }, +}); diff --git a/scripts/check-release.mjs b/scripts/check-release.mjs index a9586c0..25bc04f 100644 --- a/scripts/check-release.mjs +++ b/scripts/check-release.mjs @@ -1,60 +1,132 @@ import { readFileSync } from 'node:fs'; /** - * Pre-publish guard. `publint` and `attw` already check the shape of the + * Pre-publish guard. `publint` and `attw` already check the shape of each * package; this checks the things that only matter at the moment of release: - * that the version being published is the one the tag promises, and that it is - * not already on the registry. + * that the version being published is the one the tag promises, that it is not + * already on the registry, and that the wrapper packages ask for a core that + * the release actually satisfies. + * + * It covers every package the release publishes — the core and the workspaces + * under `packages/` — because a release that publishes three things and checks + * one is a release that can half-succeed. * * Pass a tag explicitly, or let it read GITHUB_REF_NAME on a tag build. * Without a tag it only runs the registry check, so it is safe to run locally. */ -const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); +const read = (path) => JSON.parse(readFileSync(new URL(path, import.meta.url), 'utf8')); + +const root = read('../package.json'); +const workspaces = root.workspaces.map((pattern) => pattern.replace('/*', '')); +const packages = [ + { dir: '.', pkg: root }, + ...workspaces.flatMap((dir) => + ['react', 'vue'].map((name) => ({ + dir: `${dir}/${name}`, + pkg: read(`../${dir}/${name}/package.json`), + })), + ), +]; + const problems = []; const tag = process.argv[2] ?? (process.env.GITHUB_REF_TYPE === 'tag' ? process.env.GITHUB_REF_NAME : ''); -for (const field of ['name', 'version', 'license', 'repository']) { - if (!pkg[field]) problems.push(`package.json is missing "${field}"`); -} +for (const { dir, pkg } of packages) { + const where = dir === '.' ? 'package.json' : `${dir}/package.json`; -if (pkg.private) problems.push('package.json is marked private and cannot be published'); + for (const field of ['name', 'version', 'license', 'repository']) { + if (!pkg[field]) problems.push(`${where} is missing "${field}"`); + } + if (pkg.private) problems.push(`${where} is marked private and cannot be published`); -if (tag) { - const expected = tag.replace(/^v/, ''); - if (expected !== pkg.version) { - problems.push(`tag ${tag} does not match package.json version ${pkg.version}`); + // The tag names the core's version. The wrapper packages carry their own, so + // the tag says nothing about them and is not checked against them. + if (tag && dir === '.') { + const expected = tag.replace(/^v/, ''); + if (expected !== pkg.version) { + problems.push(`tag ${tag} does not match ${where} version ${pkg.version}`); + } } -} -// A version already on the registry is immutable, so publishing would fail -// halfway through the release rather than here. -const response = await fetch(`https://registry.npmjs.org/${pkg.name}`, { - headers: { accept: 'application/json' }, -}); - -if (response.status === 404) { - console.log(`${pkg.name} is not published yet; ${pkg.version} would be the first release`); -} else if (response.ok) { - const meta = await response.json(); - const versions = Object.keys(meta.versions ?? {}); - if (versions.includes(pkg.version)) { - problems.push(`${pkg.name}@${pkg.version} is already published and cannot be republished`); + // A wrapper that asks for a core range the release does not satisfy would + // install a different core than the one it was tested against. + const wanted = pkg.dependencies?.[root.name]; + if (wanted && !satisfies(root.version, wanted)) { + problems.push( + `${where} depends on ${root.name}@${wanted}, which this release (${root.version}) does not satisfy`, + ); + } + + // A version already on the registry is immutable, so publishing would fail + // halfway through the release rather than here. + const response = await fetch(`https://registry.npmjs.org/${pkg.name}`, { + headers: { accept: 'application/json' }, + }); + + if (response.status === 404) { + console.log(`${pkg.name} is not published yet; ${pkg.version} would be the first release`); + } else if (response.ok) { + const meta = await response.json(); + if (Object.keys(meta.versions ?? {}).includes(pkg.version)) { + problems.push(`${pkg.name}@${pkg.version} is already published and cannot be republished`); + } else { + console.log(`${pkg.name}: latest published is ${meta['dist-tags']?.latest ?? 'unknown'}`); + } } else { - console.log(`${pkg.name}: latest published is ${meta['dist-tags']?.latest ?? 'unknown'}`); + // Do not fail the release on a registry hiccup; npm publish will still + // refuse a duplicate version on its own. + console.warn( + `could not reach the registry for ${pkg.name} (HTTP ${response.status}); skipping the version check`, + ); } -} else { - // Do not fail the release on a registry hiccup; npm publish will still refuse - // a duplicate version on its own. - console.warn( - `could not reach the registry (HTTP ${response.status}); skipping the version check`, - ); +} + +/** + * Enough of semver for the ranges this repository writes: a comma-free list of + * space-separated comparators, all of which must hold. Deliberately not a + * dependency — the release check should not need one to run. + */ +function satisfies(version, range) { + const actual = version.split('.').map(Number); + const compare = (other) => { + const parts = other.split('.').map(Number); + for (let i = 0; i < 3; i += 1) { + if ((actual[i] ?? 0) !== (parts[i] ?? 0)) return (actual[i] ?? 0) - (parts[i] ?? 0); + } + return 0; + }; + return range + .trim() + .split(/\s+/) + .every((comparator) => { + const [, operator, target] = comparator.match(/^([<>=^~]*)(.+)$/); + switch (operator) { + case '>=': + return compare(target) >= 0; + case '>': + return compare(target) > 0; + case '<=': + return compare(target) <= 0; + case '<': + return compare(target) < 0; + case '': + case '=': + return compare(target) === 0; + default: + // A caret or tilde range is not written here on purpose, because on a + // 0.x core it pins the minor and would exclude the release it ships + // beside. Refuse rather than guess at its meaning. + throw new Error(`check-release does not understand the range "${range}"`); + } + }); } if (problems.length) { for (const problem of problems) console.error(`release check: ${problem}`); process.exitCode = 1; } else { - console.log(`release check passed for ${pkg.name}@${pkg.version}${tag ? ` (tag ${tag})` : ''}`); + const names = packages.map(({ pkg }) => `${pkg.name}@${pkg.version}`).join(', '); + console.log(`release check passed for ${names}${tag ? ` (tag ${tag})` : ''}`); } diff --git a/site/docs.html b/site/docs.html index e4d2532..9d83ce3 100644 --- a/site/docs.html +++ b/site/docs.html @@ -255,12 +255,15 @@

Entry points

- forgedialog/react - useForgeDialog() for React. + forgedialog-react + + A separate package. useForgeDialog() for React, with React as its + peer dependency rather than the core's. + - forgedialog/vue - useForgeDialog() for Vue. + forgedialog-vue + A separate package. useForgeDialog() for Vue. forgedialog/svelte @@ -1361,10 +1364,16 @@

Framework adapters

Each adapter is a thin wrapper over open() that ties a dialog's lifetime to the component's, so nothing is left mounted after unmount.

+

+ React and Vue ship as their own packages — + npm install forgedialog forgedialog-react — so a project that uses neither + is never asked about them. Svelte and Web Components need no framework runtime, so they + stay in the core. +

// React and Vue share the same hook shape.
-import { useForgeDialog } from 'forgedialog/react';
+import { useForgeDialog } from 'forgedialog-react';
 const { open } = useForgeDialog();
 await open({ title: 'Hi' }).whenClosed();
 
diff --git a/site/vi/docs.html b/site/vi/docs.html
index 53697ce..67145dc 100644
--- a/site/vi/docs.html
+++ b/site/vi/docs.html
@@ -260,12 +260,15 @@ 

Điểm vào

- forgedialog/react - useForgeDialog() cho React. + forgedialog-react + + Một gói riêng. useForgeDialog() cho React, với React là peer + dependency của gói này chứ không phải của lõi. + - forgedialog/vue - useForgeDialog() cho Vue. + forgedialog-vue + Một gói riêng. useForgeDialog() cho Vue. forgedialog/svelte @@ -1364,10 +1367,16 @@

Adapter cho framework

Mỗi adapter là một lớp bọc mỏng quanh open(), gắn vòng đời hộp thoại vào vòng đời component, nên không còn gì sót lại sau khi component bị gỡ.

+

+ React và Vue được phát hành thành gói riêng — + npm install forgedialog forgedialog-react — nên dự án không dùng framework + nào trong hai cái đó sẽ không bao giờ bị hỏi tới chúng. Svelte và Web Components không + cần runtime của framework nên vẫn nằm trong lõi. +

// React and Vue share the same hook shape.
-import { useForgeDialog } from 'forgedialog/react';
+import { useForgeDialog } from 'forgedialog-react';
 const { open } = useForgeDialog();
 await open({ title: 'Hi' }).whenClosed();
 
diff --git a/tests/adapters/adapters.test.ts b/tests/adapters/adapters.test.ts
index d094c60..3e88303 100644
--- a/tests/adapters/adapters.test.ts
+++ b/tests/adapters/adapters.test.ts
@@ -1,62 +1,12 @@
-import { act, createElement } from 'react';
-import { createRoot } from 'react-dom/client';
-import { createApp, defineComponent, nextTick } from 'vue';
 import { afterEach, describe, expect, it, vi } from 'vitest';
-import { useForgeDialog as useReactForgeDialog } from '../../src/adapters/react';
 import { dialogTrigger } from '../../src/adapters/svelte';
-import { useForgeDialog as useVueForgeDialog } from '../../src/adapters/vue';
 import { defineForgeDialog } from '../../src/adapters/web-component';
 
-(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT =
-  true;
-
 afterEach(() => {
   document.querySelectorAll('dialog').forEach((dialog) => dialog.remove());
 });
 
 describe('framework adapters', () => {
-  it('destroys the active React dialog when its component unmounts', async () => {
-    const host = document.createElement('div');
-    document.body.appendChild(host);
-    let api: ReturnType | undefined;
-    function Harness() {
-      api = useReactForgeDialog();
-      return null;
-    }
-    const root = createRoot(host);
-    await act(async () => root.render(createElement(Harness)));
-    const closed = api!.open({ title: 'React dialog' });
-    await closed.close();
-    expect(api!.active.current).toBeNull();
-    const instance = api!.open({ title: 'React dialog 2' });
-    const destroy = vi.spyOn(instance, 'destroy');
-    await act(async () => root.unmount());
-    expect(destroy).toHaveBeenCalledOnce();
-  });
-
-  it('destroys the active Vue dialog when its component unmounts', async () => {
-    const host = document.createElement('div');
-    document.body.appendChild(host);
-    let api: ReturnType | undefined;
-    const app = createApp(
-      defineComponent({
-        setup() {
-          api = useVueForgeDialog();
-          return () => null;
-        },
-      }),
-    );
-    app.mount(host);
-    await nextTick();
-    const closed = api!.open({ title: 'Vue dialog' });
-    await closed.close();
-    expect(api!.active.value).toBeNull();
-    const instance = api!.open({ title: 'Vue dialog 2' });
-    const destroy = vi.spyOn(instance, 'destroy');
-    app.unmount();
-    expect(destroy).toHaveBeenCalledOnce();
-  });
-
   it('destroys every dialog opened by a Svelte action', async () => {
     const trigger = document.createElement('button');
     document.body.appendChild(trigger);
diff --git a/tsup.config.ts b/tsup.config.ts
index 5712a32..afdf636 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -39,13 +39,10 @@ export default defineConfig([
       appearance: 'src/appearance-entry.ts',
       presentation: 'src/presentation-entry.ts',
       workflows: 'src/workflows-entry.ts',
-      react: 'src/adapters/react.ts',
-      vue: 'src/adapters/vue.ts',
       svelte: 'src/adapters/svelte.ts',
       'web-component': 'src/adapters/web-component.ts',
     },
     format: ['esm', 'cjs'],
-    external: ['react', 'vue'],
     clean: false,
   },
   {