From f68a71f6bfdb157cf7dbe926e867a410953666d0 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 3 May 2026 21:20:16 -0400 Subject: [PATCH 1/5] Migrate to Solid 2.0 --- .changeset/share-solid2-migration.md | 14 ++++++++++++ packages/share/README.md | 7 +++--- packages/share/dev/index.tsx | 2 +- packages/share/package.json | 8 ++++--- packages/share/src/social-share.ts | 2 +- packages/share/src/web-share.ts | 28 ++++++++++++----------- pnpm-lock.yaml | 34 ++++++++++++++++++++++++++-- 7 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 .changeset/share-solid2-migration.md diff --git a/.changeset/share-solid2-migration.md b/.changeset/share-solid2-migration.md new file mode 100644 index 000000000..213276e35 --- /dev/null +++ b/.changeset/share-solid2-migration.md @@ -0,0 +1,14 @@ +--- +"@solid-primitives/share": major +--- + +Migrate to Solid.js v2.0 (beta.10) + +## Breaking Changes + +**Peer dependencies**: `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` are now required. + +### `@solid-primitives/share` + +- `isServer` is now imported from `@solidjs/web` (was `solid-js/web`) +- `createWebShare` — internal `createEffect` converted to the split compute/apply pattern required by Solid 2.0; the `on` helper (removed in Solid 2.0) is no longer used diff --git a/packages/share/README.md b/packages/share/README.md index 87a7a298f..728d59f9a 100644 --- a/packages/share/README.md +++ b/packages/share/README.md @@ -152,9 +152,10 @@ import { createWebShare } from "@solid-primitives/share"; const [data, setData] = createSignal({}); const shareStatus = createWebShare(data); -createEffect(() => { - console.log(shareStatus.status, shareStatus.message); -}); +createEffect( + () => shareStatus.status, + status => console.log("Share status:", status, shareStatus.message), +); ``` ## Changelog diff --git a/packages/share/dev/index.tsx b/packages/share/dev/index.tsx index 08edaec68..cb678bc17 100644 --- a/packages/share/dev/index.tsx +++ b/packages/share/dev/index.tsx @@ -1,4 +1,4 @@ -import { type Component, createEffect, createSignal } from "solid-js"; +import { type Component, createSignal } from "solid-js"; import { createWebShare } from "../src/index.js"; diff --git a/packages/share/package.json b/packages/share/package.json index ebef4a432..e154f8fda 100644 --- a/packages/share/package.json +++ b/packages/share/package.json @@ -1,6 +1,6 @@ { "name": "@solid-primitives/share", - "version": "2.2.4", + "version": "3.0.0", "description": "Primitives to help with sharing content on social media and beyond.", "author": "David Di Biase ", "contributors": [ @@ -52,10 +52,12 @@ "test:ssr": "pnpm run vitest --mode ssr" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.10", + "solid-js": "^2.0.0-beta.10" }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.10", + "solid-js": "2.0.0-beta.10" } } diff --git a/packages/share/src/social-share.ts b/packages/share/src/social-share.ts index 417ea55ff..b8df007ee 100644 --- a/packages/share/src/social-share.ts +++ b/packages/share/src/social-share.ts @@ -1,5 +1,5 @@ import { type Accessor, createSignal } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; import { type Network } from "./networks.js"; export type SharePopupOptions = { diff --git a/packages/share/src/web-share.ts b/packages/share/src/web-share.ts index 948707f9f..14d9b60ee 100644 --- a/packages/share/src/web-share.ts +++ b/packages/share/src/web-share.ts @@ -1,5 +1,5 @@ -import { type Accessor, createEffect, createSignal, on, type OnOptions } from "solid-js"; -import { isServer } from "solid-js/web"; +import { type Accessor, createEffect, createSignal } from "solid-js"; +import { isServer } from "@solidjs/web"; /** * Generates a simple non-reactive WebShare primitive for sharing. @@ -67,24 +67,26 @@ export type ShareStatus = { */ export const createWebShare = ( data: Accessor, - deferInitial: boolean = false, + deferInitial = false, ): ShareStatus => { if (isServer) { return {}; } const [status, setStatus] = createSignal({}); const share = makeWebShare(); + let skipFirst = deferInitial; createEffect( - on( - data, - dataValue => { - setStatus({}); - share(dataValue) - .then(() => setStatus({ status: true })) - .catch(e => setStatus({ status: false, message: e.toString() })); - }, - { defer: deferInitial } satisfies OnOptions as any, - ), + () => data(), + dataValue => { + if (skipFirst) { + skipFirst = false; + return; + } + setStatus({}); + share(dataValue) + .then(() => setStatus({ status: true })) + .catch(e => setStatus({ status: false, message: e.toString() })); + }, ); return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 726871879..b8fdfade8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -851,9 +851,12 @@ importers: packages/share: devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10(solid-js@2.0.0-beta.10) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10 packages/signal-builders: dependencies: @@ -2366,36 +2369,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-wasm@2.3.0': resolution: {integrity: sha512-ejBAX8H0ZGsD8lSICDNyMbSEtPMWgDL0WFCt/0z7hyf5v8Imz4rAM8xY379mBsECkq/Wdqa5WEDLqtjZ+6NxfA==} @@ -2506,36 +2515,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} @@ -2672,56 +2687,67 @@ packages: resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.43.0': resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.43.0': resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.43.0': resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.43.0': resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.43.0': resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.43.0': resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.43.0': resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.43.0': resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.43.0': resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.43.0': resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} @@ -5208,24 +5234,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} From 0576da0802c46b861fae04b2f8e0757ae4df3191 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 17 May 2026 08:38:26 -0400 Subject: [PATCH 2/5] Fixed type issue --- packages/share/src/web-share.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/share/src/web-share.ts b/packages/share/src/web-share.ts index 14d9b60ee..b3ab6fc0e 100644 --- a/packages/share/src/web-share.ts +++ b/packages/share/src/web-share.ts @@ -77,7 +77,7 @@ export const createWebShare = ( let skipFirst = deferInitial; createEffect( () => data(), - dataValue => { + (dataValue: ShareData) => { if (skipFirst) { skipFirst = false; return; From 8555ce7c293b12d5aa8e93726f32ad1c6ee37bec Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 17 May 2026 08:40:36 -0400 Subject: [PATCH 3/5] Updated socials list --- packages/share/src/networks.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/share/src/networks.ts b/packages/share/src/networks.ts index d69d31e21..092b764ab 100644 --- a/packages/share/src/networks.ts +++ b/packages/share/src/networks.ts @@ -12,18 +12,18 @@ */ export type Network = string; -export const BAIDU: Network = "http://cang.baidu.com/do/add?iu=@u&it=@t"; -export const BUFFER: Network = "https://bufferapp.com/add?text=@t&url=@u"; +export const BLUESKY: Network = "https://bsky.app/intent/compose?text=@t%0D%0A@u%0D%0A@d"; +export const BUFFER: Network = "https://buffer.com/add?text=@t&url=@u"; export const EMAIL: Network = "mailto:?subject=@t&body=@u%0D%0A@d"; export const EVERNOTE: Network = "https://www.evernote.com/clip.action?url=@u&title=@t"; -export const BLUESKY: Network = "https://bsky.app/intent/compose?text=@t%0D%0A@u%0D%0A@d"; export const FACEBOOK: Network = "https://www.facebook.com/sharer/sharer.php?u=@u&title=@t&description=@d"e=@q&hashtag=@h"; export const FLIPBOARD: Network = "https://share.flipboard.com/bookmarklet/popout?v=2&url=@u&title=@t"; export const HACKERNEWS: Network = "https://news.ycombinator.com/submitlink?u=@u&t=@t"; -export const INSTAPAPER: Network = "http://www.instapaper.com/edit?url=@u&title=@t&description=@d"; -export const LINE: Network = "http://line.me/R/msg/text/?@t%0D%0A@u%0D%0A@d"; +export const INSTAPAPER: Network = + "https://www.instapaper.com/edit?url=@u&title=@t&description=@d"; +export const LINE: Network = "https://line.me/R/msg/text/?@t%0D%0A@u%0D%0A@d"; export const LINKEDIN: Network = "https://www.linkedin.com/sharing/share-offsite/?url=@u"; export const MESSENGER: Network = "fb-messenger://share/?link=@u"; /** @deprecated Use MESSENGER instead - this will be removed in a future version */ @@ -37,17 +37,19 @@ export const QUORA: Network = "https://www.quora.com/share?url=@u&title=@t"; export const REDDIT: Network = "https://www.reddit.com/submit?url=@u&title=@t"; export const SKYPE: Network = "https://web.skype.com/share?url=@t%0D%0A@u%0D%0A@d"; export const SMS: Network = "sms:?body=@t%0D%0A@u%0D%0A@d"; +/** @deprecated StumbleUpon shut down in 2018 - this will be removed in a future version */ export const STUMBLEUPON: Network = "https://www.stumbleupon.com/submit?url=@u&title=@t"; export const TELEGRAM: Network = "https://t.me/share/url?url=@u&text=@t%0D%0A@d"; export const TUMBLR: Network = "https://www.tumblr.com/share/link?url=@u&name=@t&description=@d"; export const TWITTER: Network = "https://twitter.com/intent/tweet?text=@t&url=@u&hashtags=@h@tu"; -export const X: Network = "https://www.x.com/intent/tweet?text=@t&url=@u&hashtags=@h@tu"; export const VIBER: Network = "viber://forward?text=@t%0D%0A@u%0D%0A@d"; export const VK: Network = "https://vk.com/share.php?url=@u&title=@t&description=@d&image=@m&noparse=true"; -export const WEIBO: Network = "http://service.weibo.com/share/share.php?url=@u&title=@t&pic=@m"; +export const WARPCAST: Network = "https://warpcast.com/~/compose?text=@t%0D%0A@u"; +export const WEIBO: Network = "https://service.weibo.com/share/share.php?url=@u&title=@t&pic=@m"; export const WHATSAPP: Network = "https://api.whatsapp.com/send?text=@t%0D%0A@u%0D%0A@d"; export const WORDPRESS: Network = "https://wordpress.com/press-this.php?u=@u&t=@t&s=@d&i=@m"; +export const X: Network = "https://www.x.com/intent/tweet?text=@t&url=@u&hashtags=@h@tu"; export const XING: Network = "https://www.xing.com/social/share/spi?op=share&url=@u&title=@t"; export const YAMMER: Network = "https://www.yammer.com/messages/new?login=true&status=@t%0D%0A@u%0D%0A@d"; From af953822d943f629e440d983c618a8b8a1da9667 Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 17 May 2026 08:41:47 -0400 Subject: [PATCH 4/5] Update primitives list --- packages/share/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/share/package.json b/packages/share/package.json index e154f8fda..96ab42c71 100644 --- a/packages/share/package.json +++ b/packages/share/package.json @@ -18,7 +18,8 @@ "stage": 3, "list": [ "createSocialShare", - "createWebShare" + "createWebShare", + "makeWebShare" ], "category": "Utilities" }, From 7a8ce0a8e38139b15c0d98d941ba94386a9e6c6f Mon Sep 17 00:00:00 2001 From: David Di Biase <1168397+davedbase@users.noreply.github.com> Date: Sun, 17 May 2026 09:03:43 -0400 Subject: [PATCH 5/5] Better use of action and optimistic for 2.0 --- packages/share/README.md | 77 +++++++++++++++++-------- packages/share/package.json | 1 + packages/share/src/social-share.ts | 33 ++++++----- packages/share/src/web-share.ts | 90 +++++++++++++++--------------- packages/share/test/index.test.ts | 76 +++++++++++++++++++++---- packages/share/test/server.test.ts | 29 ++++++++-- pnpm-lock.yaml | 3 + 7 files changed, 212 insertions(+), 97 deletions(-) diff --git a/packages/share/README.md b/packages/share/README.md index 728d59f9a..86ba4f7a7 100644 --- a/packages/share/README.md +++ b/packages/share/README.md @@ -11,8 +11,8 @@ Primitives for supporting sharing of resources on social media and beyond. - [`createSocialShare`](#createsocialshare) - A primitive for sharing on social media and beyond. -- [`makeWebShare`](#makewebshare) - Generates a simple non-reactive WebShare primitive for sharing. -- [`createWebShare`](#createwebshare) - Creates a reactive status about web share. +- [`makeWebShare`](#makewebshare) - A simple non-reactive base primitive for the Web Share API. +- [`createWebShare`](#createwebshare) - A reactive action-based primitive for the Web Share API with status tracking. ## Installation @@ -31,11 +31,12 @@ pnpm add @solid-primitives/share ```ts import { createSocialShare, BLUESKY } from "@solid-primitives/share"; -const [share, close] = createSocialShare(() => ({ +const { share, close, isSharing } = createSocialShare(() => ({ title: "SolidJS.com", url: "https://www.solidjs.com", description: "Simple and well-behaved reactivity!", })); + share(BLUESKY); ``` @@ -56,7 +57,13 @@ function createSocialShare( popup?: SharePopupOptions; }>, controller: Window = window, -): [share: (network: Network | undefined) => void, close: () => void, isSharing: Accessor]; +): SocialShareResult; + +type SocialShareResult = { + share: (network?: Network) => void; + close: () => void; + isSharing: Accessor; +}; ``` ### Network List @@ -65,9 +72,8 @@ The following are a list of supported networks that may be imported from the sha | Network | `url` | `title` | `description` | Extras/Comments | | ------------- | ------------------ | ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------- | -| Baidu | :heavy_check_mark: | :heavy_check_mark: | :x: | | +| Bluesky | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | Buffer | :heavy_check_mark: | :heavy_check_mark: | :x: | | -| Bluesky | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Email | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | EverNote | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Facebook | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | `hashtags` A list of comma-separated hashtags, only the first one will be used.
`quote` Facebook quote. | @@ -80,25 +86,28 @@ The following are a list of supported networks that may be imported from the sha | Odnoklassniki | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Pinterest | :heavy_check_mark: | :heavy_check_mark: | :x: | `media` URL of an image describing the content. | | Pocket | :heavy_check_mark: | :heavy_check_mark: | :x: | | +| Quora | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Reddit | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Skype | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | SMS | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | -| StumbleUpon | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Telegram | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | Tumblr | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | -| Twitter | :heavy_check_mark: | :heavy_check_mark: | :x: | `hashtags` A list of comma-separated hashtags.
`twitter-user` Twitter user to mention. | -| X | :heavy_check_mark: | :heavy_check_mark: | :x: | `hashtags` A list of comma-separated hashtags.
`twitter-user` X user to mention. | +| Twitter | :heavy_check_mark: | :heavy_check_mark: | :x: | `hashtags` A list of comma-separated hashtags.
`twitterUser` Twitter user to mention. | | Viber | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | VK | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | `media` URL of an image describing the content. | +| Warpcast | :heavy_check_mark: | :heavy_check_mark: | :x: | Farcaster decentralized social network. | | Weibo | :heavy_check_mark: | :heavy_check_mark: | :x: | `media` URL of an image describing the content. | | WhatsApp | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | | Wordpress | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | `media` URL of an image describing the content. | +| X | :heavy_check_mark: | :heavy_check_mark: | :x: | `hashtags` A list of comma-separated hashtags.
`twitterUser` X user to mention. | | Xing | :heavy_check_mark: | :heavy_check_mark: | :x: | | | Yammer | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | -For the networks: `Bluesky`, `Line`, `Skype`, `SMS`, `Telegram`, `Viber`, `WhatsApp` and `Yammer`; the shared content is a string of the form: "`$title` `$url` `$description`" +> **Deprecated:** `STUMBLEUPON` (shut down 2018) and `MESSANGER` (typo — use `MESSENGER`) are still exported for backwards compatibility but will be removed in a future version. + +For the networks `Bluesky`, `Line`, `Skype`, `SMS`, `Telegram`, `Viber`, `WhatsApp`, and `Yammer` the shared content is a string of the form: "`$title` `$url` `$description`". -Note that you can also provide your own custom network by formatting the input string into the share function. The following is a list of properties that will be replaced by the utility: +You can also provide a custom network by formatting a URL string with the following replacement markers: - `@u`: URL - `@t`: Title @@ -106,9 +115,9 @@ Note that you can also provide your own custom network by formatting the input s - `@q`: Quote - `@h`: Hashtags - `@m`: Media -- `@tu`: X User (X specific) +- `@tu`: X/Twitter user mention -The following is an example of X's share string: +Example: ```ts const x: Network = "https://www.x.com/intent/tweet?text=@t&url=@u&hashtags=@h@tu"; @@ -124,7 +133,7 @@ A portion of this primitive was built from https://github.com/nicolasbeauvais/vu ## `makeWebShare` -Generates a simple non-reactive WebShare primitive for sharing. Uses the [WebShare API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API). +A simple non-reactive base primitive wrapping the [Web Share API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API). Returns a `share` function that rejects with a descriptive message if the browser does not support sharing or file sharing. ### How to use it @@ -136,26 +145,50 @@ const share = makeWebShare(); try { await share({ url: "https://solidjs.com" }); } catch (e) { - console.log(e); + console.error(e); } ``` ## `createWebShare` -Creates a reactive status about web share. Uses the [WebShare API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API). +A reactive, action-based primitive for the [Web Share API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API). Call `share` imperatively on a user gesture and observe the result via reactive accessors. ### How to use it ```ts import { createWebShare } from "@solid-primitives/share"; -const [data, setData] = createSignal({}); -const shareStatus = createWebShare(data); +const { share, pending, status, message } = createWebShare(); +``` -createEffect( - () => shareStatus.status, - status => console.log("Share status:", status, shareStatus.message), -); +```tsx + + + +

Share failed: {message()}

+
+``` + +### Definition + +```ts +function createWebShare(): WebShareResult; + +type WebShareResult = { + /** Imperatively trigger the Web Share API with the provided data. */ + share: (data: ShareData) => Promise; + /** True while the share dialog is open / the promise is pending. */ + pending: Accessor; + /** True on success, false on failure, undefined before first share. */ + status: Accessor; + /** The error message if the share failed, otherwise undefined. */ + message: Accessor; +}; ``` ## Changelog diff --git a/packages/share/package.json b/packages/share/package.json index 96ab42c71..d6622384f 100644 --- a/packages/share/package.json +++ b/packages/share/package.json @@ -58,6 +58,7 @@ }, "typesVersions": {}, "devDependencies": { + "@solid-primitives/utils": "workspace:^", "@solidjs/web": "2.0.0-beta.10", "solid-js": "2.0.0-beta.10" } diff --git a/packages/share/src/social-share.ts b/packages/share/src/social-share.ts index b8df007ee..35e8f4f42 100644 --- a/packages/share/src/social-share.ts +++ b/packages/share/src/social-share.ts @@ -1,5 +1,6 @@ import { type Accessor, createSignal } from "solid-js"; import { isServer } from "@solidjs/web"; +import { INTERNAL_OPTIONS } from "@solid-primitives/utils"; import { type Network } from "./networks.js"; export type SharePopupOptions = { @@ -24,8 +25,18 @@ export type SharePopupOptions = { * @param {tag} string A tag to associate with the share * @param {popup} SharePopupOptions An object representing the pop-up window controls * @param {controller} Window Controller to bind the share to - * @return Returns a share, close and is sharing signal. + * @return An object with `share`, `close`, and `isSharing`. */ + +export type SocialShareResult = { + /** Open the share popup for the given network (falls back to `options().network`). */ + share: (network?: Network) => void; + /** Close the share popup. */ + close: () => void; + /** True while the share popup window is open. */ + isSharing: Accessor; +}; + export const createSocialShare = ( options: Accessor<{ network?: Network; @@ -44,23 +55,19 @@ export const createSocialShare = ( description: "", }), controller: Window = isServer ? (globalThis as any) : window, -): [ - share: (network: Network | undefined) => void, - close: () => void, - isSharing: Accessor, -] => { +): SocialShareResult => { if (isServer) { - return [ - () => { + return { + share: () => { /*noop*/ }, - () => { + close: () => { /*noop*/ }, - () => false, - ]; + isSharing: () => false, + }; } - const [isSharing, setIsSharing] = createSignal(false); + const [isSharing, setIsSharing] = createSignal(false, INTERNAL_OPTIONS); let popupInterval: null | ReturnType = null; let popupWindow: null | Window; let popup = { @@ -158,5 +165,5 @@ export const createSocialShare = ( }, 500); setIsSharing(true); }; - return [share, close, isSharing]; + return { share, close, isSharing }; }; diff --git a/packages/share/src/web-share.ts b/packages/share/src/web-share.ts index b3ab6fc0e..8d0e32c16 100644 --- a/packages/share/src/web-share.ts +++ b/packages/share/src/web-share.ts @@ -1,5 +1,6 @@ -import { type Accessor, createEffect, createSignal } from "solid-js"; +import { type Accessor, action, createOptimistic, createSignal } from "solid-js"; import { isServer } from "@solidjs/web"; +import { INTERNAL_OPTIONS } from "@solid-primitives/utils"; /** * Generates a simple non-reactive WebShare primitive for sharing. @@ -40,61 +41,60 @@ export const makeWebShare = () => { return share; }; -export type ShareStatus = { - /** The status of sharing success, failed or pending. */ - status?: boolean; - - /** The reason why sharing failed. */ - message?: string; +export type WebShareResult = { + /** Imperatively trigger the Web Share API with the provided data. */ + share: (data: ShareData) => Promise; + /** True while the share dialog is open / the promise is pending. */ + pending: Accessor; + /** True on success, false on failure, undefined before first share. */ + status: Accessor; + /** The error message if the share failed, otherwise undefined. */ + message: Accessor; }; /** - * Creates a reactive status about web share. + * Creates an action-based Web Share primitive with reactive status tracking. * - * @param data Data signal to share on web. - * @param deferInitial - Sets the value of the web share data from the signal. defaults to false. - * @return A store shows sharing status and failing message. + * @returns An object with a `share` action and reactive `pending`, `status`, and `message` accessors. * * @example * ```ts - * const [data, setData] = createSignal({}); - * const shareStatus = createWebShare(data); + * const { share, pending, status, message } = createWebShare(); * - * createEffect(() => { - * console.log(shareStatus.status, shareStatus.message) - * }) + * // Call imperatively on user gesture: + * * ``` */ -export const createWebShare = ( - data: Accessor, - deferInitial = false, -): ShareStatus => { +export const createWebShare = (): WebShareResult => { if (isServer) { - return {}; + return { + share: () => Promise.resolve(), + pending: () => false, + status: () => undefined, + message: () => undefined, + }; } - const [status, setStatus] = createSignal({}); - const share = makeWebShare(); - let skipFirst = deferInitial; - createEffect( - () => data(), - (dataValue: ShareData) => { - if (skipFirst) { - skipFirst = false; - return; - } - setStatus({}); - share(dataValue) - .then(() => setStatus({ status: true })) - .catch(e => setStatus({ status: false, message: e.toString() })); - }, - ); - return { - get status() { - return status().status; - }, - get message() { - return status().message; - }, - }; + const [pending, setPending] = createOptimistic(false, INTERNAL_OPTIONS); + const [status, setStatus] = createSignal(undefined, INTERNAL_OPTIONS); + const [message, setMessage] = createSignal(undefined, INTERNAL_OPTIONS); + const baseShare = makeWebShare(); + + const share = action(function* (data: ShareData) { + setPending(true); + setStatus(undefined); + setMessage(undefined); + try { + yield baseShare(data); + setStatus(true); + } catch (e: unknown) { + setStatus(false); + setMessage(String(e)); + } + setPending(false); + }); + + return { share, pending, status, message }; }; diff --git a/packages/share/test/index.test.ts b/packages/share/test/index.test.ts index 35736167c..3f8335df1 100644 --- a/packages/share/test/index.test.ts +++ b/packages/share/test/index.test.ts @@ -1,19 +1,73 @@ -import { describe, test, expect, it } from "vitest"; -import { createRoot, createSignal } from "solid-js"; -import { createWebShare } from "../src/index.js"; +import { describe, test, expect, vi } from "vitest"; +import { createRoot, flush } from "solid-js"; +import { createWebShare, createSocialShare } from "../src/index.js"; describe("createWebShare", () => { - test("createWebShare initial values", () => - createRoot(async dispose => { - const [data] = createSignal({}); - const status = createWebShare(data); + test("initial values", () => + createRoot(dispose => { + const { pending, status, message } = createWebShare(); - expect(status.status, "Test starting status should be undefined.").toBe(undefined); - expect(status.message, "Test starting message should be undefined.").toBe(undefined); + expect(pending()).toBe(false); + expect(status()).toBe(undefined); + expect(message()).toBe(undefined); dispose(); })); - // todo: Asynchronous test. - it.todo("Asynchronous test to change data."); + test("share resolves and sets success status", async () => { + await createRoot(async dispose => { + Object.defineProperty(navigator, "share", { + value: vi.fn().mockResolvedValue(undefined), + configurable: true, + }); + + const { share, pending, status, message } = createWebShare(); + + const promise = share({ url: "https://solidjs.com" }); + flush(); + expect(pending()).toBe(true); + + await promise; + flush(); + expect(pending()).toBe(false); + expect(status()).toBe(true); + expect(message()).toBe(undefined); + + dispose(); + }); + }); + + test("share sets failure status on rejection", async () => { + await createRoot(async dispose => { + Object.defineProperty(navigator, "share", { + value: vi.fn().mockRejectedValue("share failed"), + configurable: true, + }); + + const { share, pending, status, message } = createWebShare(); + + await share({ url: "https://solidjs.com" }); + flush(); + expect(pending()).toBe(false); + expect(status()).toBe(false); + expect(message()).toBe("share failed"); + + dispose(); + }); + }); +}); + +describe("createSocialShare", () => { + test("initial values", () => + createRoot(dispose => { + const { share, close, isSharing } = createSocialShare( + () => ({ url: "https://solidjs.com", title: "SolidJS", description: "A reactive library" }), + ); + + expect(typeof share).toBe("function"); + expect(typeof close).toBe("function"); + expect(isSharing()).toBe(false); + + dispose(); + })); }); diff --git a/packages/share/test/server.test.ts b/packages/share/test/server.test.ts index 957099709..981e9a8e5 100644 --- a/packages/share/test/server.test.ts +++ b/packages/share/test/server.test.ts @@ -1,15 +1,32 @@ -import { createRoot, createSignal } from "solid-js"; +import { createRoot } from "solid-js"; import { describe, test, expect } from "vitest"; -import { createWebShare } from "../src/index.js"; +import { createWebShare, createSocialShare } from "../src/index.js"; describe("createWebShare", () => { test("doesn't break in SSR", () => { createRoot(dispose => { - const [data] = createSignal({}); - const status = createWebShare(data); + const { share, pending, status, message } = createWebShare(); - expect(status.status, "Server test starting status should be undefined.").toBe(undefined); - expect(status.message, "Server test starting message should be undefined.").toBe(undefined); + expect(typeof share).toBe("function"); + expect(pending()).toBe(false); + expect(status()).toBe(undefined); + expect(message()).toBe(undefined); + + dispose(); + }); + }); +}); + +describe("createSocialShare", () => { + test("doesn't break in SSR", () => { + createRoot(dispose => { + const { share, close, isSharing } = createSocialShare( + () => ({ url: "https://solidjs.com", title: "SolidJS", description: "A reactive library" }), + ); + + expect(typeof share).toBe("function"); + expect(typeof close).toBe("function"); + expect(isSharing()).toBe(false); dispose(); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86ea765f7..1d4d7c927 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -909,6 +909,9 @@ importers: packages/share: devDependencies: + '@solid-primitives/utils': + specifier: workspace:^ + version: link:../utils '@solidjs/web': specifier: 2.0.0-beta.10 version: 2.0.0-beta.10(solid-js@2.0.0-beta.10)