Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/media-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
"@solid-primitives/media": major
"@solid-primitives/event-listener": major
"@solid-primitives/rootless": major
"@solid-primitives/static-store": major
"@solid-primitives/utils": major
---

Migrate to Solid.js v2.0 (beta.7)

## Breaking Changes

**Peer dependency**: `solid-js@^2.0.0-beta.7` and `@solidjs/web@^2.0.0-beta.7` are now required.

### `@solid-primitives/media`

- `isServer` now imported from `@solidjs/web` (not `solid-js/web`)
- Requires Solid.js v2 — `classList` is replaced by `class` with object/array forms in consuming code

### `@solid-primitives/utils`

- `isServer` import moved from `solid-js/web` to `@solidjs/web`
- `createHydratableSignal`: uses `onSettled` (was `onMount`) and `sharedConfig.hydrating` (was `sharedConfig.context`) for hydration detection
- `INTERNAL_OPTIONS`: `{ internal: true }` changed to `{ pureWrite: true }` to match Solid 2.0 `SignalOptions`
- `defaultEquals` now aliases `isEqual` (was `equalFn`)
- `defer`: `AccessorArray<S>` type replaced with `Accessor<S>[]` (type was removed in Solid 2.0)

### `@solid-primitives/static-store`

- `isServer` import moved from `solid-js/web` to `@solidjs/web`
- `createStaticStore`: uses `getObserver` (was `getListener`) and `{ pureWrite: true }` (was `{ internal: true }`)
- Removed explicit `batch()` calls — updates are automatically batched in Solid 2.0
- `createHydratableStaticStore`: uses `onSettled` (was `onMount`) and `sharedConfig.hydrating` (was `sharedConfig.context`)

### `@solid-primitives/rootless`

- `isServer` import moved from `solid-js/web` to `@solidjs/web`
- `createHydratableSingletonRoot`: uses `sharedConfig.hydrating` (was `sharedConfig.context`)
- `createRootPool`: removed `batch()` calls — Solid 2.0 auto-batches on microtasks

### `@solid-primitives/event-listener`

- `isServer` import moved from `solid-js/web` to `@solidjs/web` across all source files
- `createEventListener` and `createRenderEffect` converted to split compute/apply effect pattern required by Solid 2.0
- `eventListener` directive converted to split effect pattern; cleanup is returned from apply phase instead of using `onCleanup`
6 changes: 4 additions & 2 deletions packages/event-listener/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@
"@solid-primitives/utils": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.7",
"solid-js": "^2.0.0-beta.7"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.7",
"solid-js": "2.0.0-beta.7"
}
}
2 changes: 1 addition & 1 deletion packages/event-listener/src/components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isServer } from "solid-js/web";
import { isServer } from "@solidjs/web";
import { keys } from "@solid-primitives/utils";
import { type Component } from "solid-js";
import { makeEventListener } from "./eventListener.js";
Expand Down
35 changes: 23 additions & 12 deletions packages/event-listener/src/eventListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
tryOnCleanup,
} from "@solid-primitives/utils";
import { type Accessor, createEffect, createRenderEffect, createSignal } from "solid-js";
import { isServer } from "solid-js/web";
import { isServer } from "@solidjs/web";
import type {
EventListenerDirectiveProps,
EventMapOf,
Expand Down Expand Up @@ -110,17 +110,28 @@ export function createEventListener(
): void {
if (isServer) return;

const attachListeners = () => {
asArray(access(targets)).forEach(el => {
if (el) asArray(access(type)).forEach(type => makeEventListener(el, type, handler, options));
});
type State = { els: EventTarget[]; types: string[] };

const compute = (): State => ({
els: asArray(access(targets)).filter(Boolean) as EventTarget[],
types: asArray(access(type)) as string[],
});

const apply = ({ els, types }: State) => {
const cleanups: VoidFunction[] = [];
for (const el of els)
for (const t of types) {
el.addEventListener(t, handler, options);
cleanups.push(el.removeEventListener.bind(el, t, handler, options));
}
return () => cleanups.forEach(c => c());
};

// if the target is an accessor the listeners will be added on the first effect (onMount)
// so that when passed a jsx ref it will be availabe
if (typeof targets === "function") createEffect(attachListeners);
// if the target is an accessor the listeners will be added on the first effect (after mount)
// so that when passed a jsx ref it will be available
if (typeof targets === "function") createEffect(compute, apply);
// if the target prop is NOT an accessor, the event listeners can be added right away
else createRenderEffect(attachListeners);
else createRenderEffect(compute, apply);
}

// Possible targets prop shapes:
Expand Down Expand Up @@ -192,9 +203,9 @@ export function createEventSignal(
* <button use:eventListener={["click", () => {...}]}>Click me!</button>
*/
export const eventListener: Directive<EventListenerDirectiveProps> = (target, props) => {
createEffect(() => {
const [type, handler, options] = props();
makeEventListener(target, type, handler, options);
createEffect(props, ([type, handler, options]) => {
target.addEventListener(type, handler, options);
return () => target.removeEventListener(type, handler, options);
});
};

Expand Down
2 changes: 1 addition & 1 deletion packages/event-listener/src/eventListenerMap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type AnyFunction, entries, type Many, type MaybeAccessor } from "@solid-primitives/utils";
import { createEventListener } from "./eventListener.js";
import type { EventMapOf, TargetWithEventMap, EventListenerOptions } from "./types.js";
import { isServer } from "solid-js/web";
import { isServer } from "@solidjs/web";

export type EventHandlersMap<EventMap> = {
[EventName in keyof EventMap]: (event: EventMap[EventName]) => void;
Expand Down
2 changes: 1 addition & 1 deletion packages/event-listener/src/eventListenerStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createCallbackStack } from "@solid-primitives/utils";
import { onCleanup } from "solid-js";
import { makeEventListener } from "./eventListener.js";
import type { EventMapOf, TargetWithEventMap, EventListenerOptions } from "./types.js";
import { isServer } from "solid-js/web";
import { isServer } from "@solidjs/web";

export type EventListenerStackOn<EventMap extends Record<string, any>> = {
<T extends keyof EventMap>(
Expand Down
5 changes: 1 addition & 4 deletions packages/event-listener/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export type TargetWithEventMap =
| Document
| XMLDocument
| HTMLBodyElement
| HTMLFrameSetElement
| HTMLMediaElement
| HTMLVideoElement
| HTMLElement
Expand Down Expand Up @@ -64,9 +63,7 @@ export type EventMapOf<Target> = Target extends Window
? DocumentEventMap
: Target extends HTMLBodyElement
? HTMLBodyElementEventMap
: Target extends HTMLFrameSetElement
? HTMLFrameSetElementEventMap
: Target extends HTMLMediaElement
: Target extends HTMLMediaElement
? HTMLMediaElementEventMap
: Target extends HTMLVideoElement
? HTMLVideoElementEventMap
Expand Down
8 changes: 4 additions & 4 deletions packages/event-listener/test/event-listener-map.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dispatchFakeEvent, event_target } from "./setup.js";
import { describe, test, expect } from "vitest";
import { createRoot, onMount } from "solid-js";
import { createRoot, onSettled } from "solid-js";
import { createEventListenerMap } from "../src/index.js";

describe("createEventListenerMap", () => {
Expand Down Expand Up @@ -34,7 +34,7 @@ describe("createEventListenerMap", () => {
map_1: e => (captured1 = e),
});

onMount(() => {
onSettled(() => {
dispatchFakeEvent("map_0", testEvent);
dispatchFakeEvent("map_1", testEvent1);

Expand All @@ -55,7 +55,7 @@ describe("createEventListenerMap", () => {
map_1: e => (captured1 = e),
});

onMount(() => {
onSettled(() => {
dispose();

dispatchFakeEvent("map_0", testEvent);
Expand All @@ -74,7 +74,7 @@ describe("createEventListenerMap", () => {
map_0: e => (captured = e),
});

onMount(() => {
onSettled(() => {
dispatchFakeEvent("map_0", testEvent);
expect(captured).toBe(testEvent);
dispose();
Expand Down
25 changes: 14 additions & 11 deletions packages/event-listener/test/event-listener.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dispatchFakeEvent, event_target } from "./setup.js";
import { describe, test, expect } from "vitest";
import { createRoot, createSignal, onMount } from "solid-js";
import { createRoot, createSignal, flush, onSettled } from "solid-js";
import {
createEventListener,
createEventSignal,
Expand Down Expand Up @@ -124,14 +124,17 @@ describe("createEventListener", () => {
return dispose;
});

flush();
dispatchFakeEvent("test", testEvent);
expect(captured_times).toBe(1);
setTarget([]);

setTarget([]);
flush();
dispatchFakeEvent("test", testEvent);
expect(captured_times).toBe(1);
setTarget(event_target);

setTarget(event_target);
flush();
dispatchFakeEvent("test", testEvent);
expect(captured_times).toBe(2);
dispose();
Expand Down Expand Up @@ -177,7 +180,7 @@ describe("createEventListener", () => {
count++;
});

onMount(() => {
onSettled(() => {
dispatchFakeEvent("test3", testEvent);
expect(count, "captured count on mount should be 1").toBe(1);

Expand All @@ -197,7 +200,7 @@ describe("createEventSignal", () => {
expect(lastEvent, "returned value is an accessor").toBeTypeOf("function");
expect(lastEvent(), "returned value is undefined").toBeTypeOf("undefined");

onMount(() => {
onSettled(() => {
dispatchFakeEvent("sig_test", testEvent);
expect(lastEvent()).toBe(testEvent);
dispose();
Expand All @@ -221,16 +224,16 @@ describe("eventListener directive", () => {
dispatchFakeEvent("load", testEvent);
expect(captured.length, "event are not listened before the first effect").toBe(0);

onMount(() => {
dispatchFakeEvent("load", testEvent);
expect(captured.length, "one event after mounted should be captured").toBe(1);
expect(captured[0], "event after mounted should be captured").toBe(testEvent);
});

return dispose;
});

flush(); // run effect phase → adds event listener
dispatchFakeEvent("load", testEvent);
expect(captured.length, "one event after mounted should be captured").toBe(1);
expect(captured[0], "event after mounted should be captured").toBe(testEvent);

setProps(["load", e => captured2.push(e)]);
flush(); // re-run effect → removes old listener, adds new one

dispatchFakeEvent("load", testEvent);
expect(captured.length, "events should no longer be captured by the previous handler").toBe(1);
Expand Down
41 changes: 24 additions & 17 deletions packages/media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ npm install @solid-primitives/media
yarn add @solid-primitives/media
```

> **Requires Solid.js v2.0 (beta.7+)**

## `makeMediaQueryListener`

Attaches a MediaQuery listener to window, listeneing to changes to provided query
Expand Down Expand Up @@ -77,20 +79,23 @@ const breakpoints = {
const Example: Component = () => {
const matches = createBreakpoints(breakpoints);

createEffect(() => {
console.log(matches.sm); // true when screen width >= 640px
console.log(matches.lg); // true when screen width >= 1024px
console.log(matches.xl); // true when screen width >= 1280px
});
createEffect(
() => [matches.sm, matches.lg, matches.xl],
([sm, lg, xl]) => {
console.log(sm); // true when screen width >= 640px
console.log(lg); // true when screen width >= 1024px
console.log(xl); // true when screen width >= 1280px
}
);

return (
<div
classList={{
"text-tiny flex flex-column": true, // tiny text with flex column layout
"text-small": matches.sm, // small text with flex column layout
"text-base flex-row": matches.lg, // base text with flex row layout
"text-huge": matches.xl, // huge text with flex row layout
}}
class={[
"text-tiny flex flex-column", // tiny text with flex column layout
matches.sm && "text-small", // small text with flex column layout
matches.lg && "text-base flex-row", // base text with flex row layout
matches.xl && "text-huge", // huge text with flex row layout
]}
>
<Switch fallback={<div>Smallest</div>}>
<Match when={matches.xl}>Extra Large</Match>
Expand Down Expand Up @@ -169,9 +174,10 @@ Provides a signal indicating if the user has requested dark color theme. The set
import { createPrefersDark } from "@solid-primitives/media";

const prefersDark = createPrefersDark();
createEffect(() => {
prefersDark(); // => boolean
});
createEffect(
prefersDark,
dark => console.log("prefers dark:", dark)
);
```

### Server fallback
Expand All @@ -192,9 +198,10 @@ This primitive provides a [singleton root](https://github.com/solidjs-community/
import { usePrefersDark } from "@solid-primitives/media";

const prefersDark = usePrefersDark();
createEffect(() => {
prefersDark(); // => boolean
});
createEffect(
prefersDark,
dark => console.log("prefers dark:", dark)
);
```

> Note: `usePrefersDark` will deopt to `createPrefersDark` if used during hydration. (see issue [#310](https://github.com/solidjs-community/solid-primitives/issues/310))
Expand Down
8 changes: 5 additions & 3 deletions packages/media/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solid-primitives/media",
"version": "2.3.5",
"version": "3.0.0",
"description": "Primitives for media query and device features",
"author": "David Di Biase <dave@solidjs.com>",
"contributors": [
Expand Down Expand Up @@ -68,10 +68,12 @@
"@solid-primitives/utils": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.7",
"solid-js": "^2.0.0-beta.7"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.7",
"solid-js": "2.0.0-beta.7"
}
}
2 changes: 1 addition & 1 deletion packages/media/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Accessor } from "solid-js";
import { isServer } from "solid-js/web";
import { isServer } from "@solidjs/web";
import { makeEventListener } from "@solid-primitives/event-listener";
import { entries, noop, createHydratableSignal } from "@solid-primitives/utils";
import { createHydratableStaticStore } from "@solid-primitives/static-store";
Expand Down
Loading