diff --git a/.changeset/match-solid2-migration.md b/.changeset/match-solid2-migration.md new file mode 100644 index 000000000..074775305 --- /dev/null +++ b/.changeset/match-solid2-migration.md @@ -0,0 +1,11 @@ +--- +"@solid-primitives/match": major +--- + +Migrate to Solid.js v2.0 (beta.13) + +## Breaking Changes + +**Peer dependencies**: `solid-js@^2.0.0-beta.13` and `@solidjs/web@^2.0.0-beta.13` are now required. + +- `JSX` types are now sourced from `@solidjs/web` per Solid 2.0 conventions diff --git a/packages/match/CHANGELOG.md b/packages/match/CHANGELOG.md new file mode 100644 index 000000000..9712b0c3a --- /dev/null +++ b/packages/match/CHANGELOG.md @@ -0,0 +1,12 @@ +# @solid-primitives/match + +## 0.1.0 + +### Major Changes + +Migrate to Solid.js v2.0 (beta.13) + +**Peer dependencies**: `solid-js@^2.0.0-beta.13` and `@solidjs/web@^2.0.0-beta.13` are now required. + +- `JSX` types are now sourced from `@solidjs/web` per Solid 2.0 conventions +- Signal writes are batched by default in Solid 2.0 — call `flush()` after signal writes in tests before reading reactive values diff --git a/packages/match/README.md b/packages/match/README.md index 87daa782a..70f589f2e 100644 --- a/packages/match/README.md +++ b/packages/match/README.md @@ -83,6 +83,8 @@ Use the `partial` prop to only handle some of the union members: /> ``` +> **Note:** `partial` is a TypeScript-only escape hatch — it switches the `case` mapped type from required to optional keys. It has no runtime effect; unmatched values fall through to `fallback` regardless of whether `partial` is set. + ### Fallback Provide a fallback element when no match is found or the value is `null`/`undefined`: @@ -133,6 +135,8 @@ Use the `partial` prop to only handle some of the union members: /> ``` +> **Note:** `partial` is a TypeScript-only escape hatch — it has no runtime effect. See [`MatchTag` partial matching](#partial-matching) for details. + ### Fallback Provide a fallback element when no match is found or the value is `null`/`undefined`: @@ -148,6 +152,10 @@ Provide a fallback element when no match is found or the value is `null`/`undefi /> ``` +## `MatchField` (deprecated) + +`MatchField` is an alias for `MatchTag` kept for backwards compatibility. Use `MatchTag` in new code. + ## Demo [Deployed example](https://primitives.solidjs.community/playground/match) | [Source code](https://github.com/solidjs-community/solid-primitives/tree/main/packages/match/dev) diff --git a/packages/match/package.json b/packages/match/package.json index be1ae5b39..e5f4d1a61 100644 --- a/packages/match/package.json +++ b/packages/match/package.json @@ -52,9 +52,11 @@ "test:ssr": "pnpm run vitest --mode ssr" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.13", + "solid-js": "^2.0.0-beta.13" }, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.13", + "solid-js": "2.0.0-beta.13" } } diff --git a/packages/match/src/index.ts b/packages/match/src/index.ts index d0304be45..e339da44c 100644 --- a/packages/match/src/index.ts +++ b/packages/match/src/index.ts @@ -1,4 +1,5 @@ -import { type Accessor, type JSX, createMemo } from "solid-js"; +import { type Accessor, createMemo } from "solid-js"; +import type { JSX } from "@solidjs/web"; /** * Control-flow component for matching discriminated union (tagged union) members. @@ -15,42 +16,53 @@ import { type Accessor, type JSX, createMemo } from "solid-js"; * * const [value, setValue] = createSignal({type: 'foo', foo: 'foo-value'}) * - * <>{v().foo}, * bar: v => <>{v().bar}, * }} /> * ``` */ -export function MatchTag(props: { +// Tag values are constrained to string | number rather than PropertyKey because symbol keys +// cannot be expressed in inline object literals, making symbol-tagged unions impossible to +// match in practice. The narrower constraint surfaces the error at the tag field rather than +// inside the mapped type where it is harder to diagnose. +export function MatchTag(props: { on: T | null | undefined; tag: Tag; case: { [K in T[Tag]]: (v: Accessor) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial?: false; }): JSX.Element; -export function MatchTag(props: { +export function MatchTag(props: { on: T | null | undefined; case: { [K in T["type"]]: (v: Accessor) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial?: false; }): JSX.Element; -export function MatchTag(props: { +export function MatchTag(props: { on: T | null | undefined; tag: Tag; case: { [K in T[Tag]]?: (v: Accessor) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial: true; }): JSX.Element; -export function MatchTag(props: { +export function MatchTag(props: { on: T | null | undefined; case: { [K in T["type"]]?: (v: Accessor) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial: true; }): JSX.Element; export function MatchTag(props: any): any { + const value = () => props.on; const kind = createMemo(() => props.on?.[props.tag ?? "type"]); - return createMemo(() => props.case[kind()]?.(() => props.on) ?? props.fallback); + return createMemo(() => props.case[kind()]?.(value) ?? props.fallback); } + +/** @deprecated Use {@link MatchTag} instead. */ export { MatchTag as MatchField }; /** @@ -62,22 +74,24 @@ export { MatchTag as MatchField }; * * const [value, setValue] = createSignal('foo') * - *

foo

, * bar: () =>

bar

, * }} /> * ``` */ -export function MatchValue(props: { +export function MatchValue(props: { on: T | null | undefined; case: { [K in T]: (v: K) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial?: false; }): JSX.Element; -export function MatchValue(props: { +export function MatchValue(props: { on: T | null | undefined; case: { [K in T]?: (v: K) => JSX.Element }; fallback?: JSX.Element; + /** Type-only — no runtime effect. Relaxes `case` to allow partial union coverage. */ partial: true; }): JSX.Element; export function MatchValue(props: any): any { diff --git a/packages/match/test/index.test.tsx b/packages/match/test/index.test.tsx index 19f0fbee5..79d336a0b 100644 --- a/packages/match/test/index.test.tsx +++ b/packages/match/test/index.test.tsx @@ -36,9 +36,11 @@ v.describe("Match", () => { v.expect(data.result()).toEqual(undefined); setValue({ type: "foo", foo: "foo-value" }); + s.flush(); v.expect(data.result()).toEqual(<>foo-value); setValue({ type: "bar", bar: "bar-value" }); + s.flush(); v.expect(data.result()).toEqual(<>bar-value); data.dispose(); @@ -78,9 +80,11 @@ v.describe("Match", () => { v.expect(data.result()).toEqual(undefined); setValue({ kind: "foo", foo: "foo-value" }); + s.flush(); v.expect(data.result()).toEqual(<>foo-value); setValue({ kind: "bar", bar: "bar-value" }); + s.flush(); v.expect(data.result()).toEqual(<>bar-value); data.dispose(); @@ -119,9 +123,11 @@ v.describe("Match", () => { v.expect(data.result()).toEqual(undefined); setValue({ type: "foo", foo: "foo-value" }); + s.flush(); v.expect(data.result()).toEqual(<>foo-value); setValue({ type: "bar", bar: "bar-value" }); + s.flush(); v.expect(data.result()).toEqual(undefined); data.dispose(); @@ -161,9 +167,11 @@ v.describe("Match", () => { v.expect(data.result()).toEqual(<>fallback); setValue({ type: "foo", foo: "foo-value" }); + s.flush(); v.expect(data.result()).toEqual(<>foo-value); setValue(undefined); + s.flush(); v.expect(data.result()).toEqual(<>fallback); data.dispose(); @@ -189,13 +197,17 @@ v.describe("MatchValue", () => { )), })); v.expect(data.result()).toEqual(undefined); + setValue("foo"); + s.flush(); v.expect(data.result()).toEqual( <>

foo

, ); + setValue("bar"); + s.flush(); v.expect(data.result()).toEqual( <>

bar

@@ -222,13 +234,17 @@ v.describe("MatchValue", () => { )), })); v.expect(data.result()).toEqual(undefined); + setValue("foo"); + s.flush(); v.expect(data.result()).toEqual( <>

foo

, ); + setValue("bar"); + s.flush(); v.expect(data.result()).toEqual(undefined); data.dispose(); }); @@ -256,13 +272,17 @@ v.describe("MatchValue", () => {

fallback

, ); + setValue("foo"); + s.flush(); v.expect(data.result()).toEqual( <>

foo

, ); + setValue(undefined); + s.flush(); v.expect(data.result()).toEqual( <>

fallback

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee1923c5f..7dcc97f65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -612,9 +612,12 @@ importers: packages/match: devDependencies: + '@solidjs/web': + specifier: 2.0.0-beta.13 + version: 2.0.0-beta.13(solid-js@2.0.0-beta.13) solid-js: - specifier: ^1.9.7 - version: 1.9.7 + specifier: 2.0.0-beta.13 + version: 2.0.0-beta.13 packages/media: dependencies: