diff --git a/.changeset/scroll-solid2-migration.md b/.changeset/scroll-solid2-migration.md new file mode 100644 index 000000000..094a0b00f --- /dev/null +++ b/.changeset/scroll-solid2-migration.md @@ -0,0 +1,19 @@ +--- +"@solid-primitives/scroll": major +--- + +Migrate to Solid.js v2.0 (beta.10) + +## Breaking Changes + +**Peer dependency**: `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` are now required. + +### `@solid-primitives/scroll` + +- `isServer` now imported from `@solidjs/web` (not `solid-js/web`) +- `onMount` replaced with `onSettled` for post-render position refresh +- `sharedConfig.context` replaced with `sharedConfig.hydrating` for hydration detection +- Internal signal pattern replaced: Solid 2.0's `createSignal(fn)` creates a derived signal rather than storing a function value; now uses a version counter to drive memo re-evaluation on scroll events +- Signal uses `{ ownedWrite: true }` to allow writes from DOM event handlers within reactive scopes +- Tests updated: `createComputed` removed (no longer in Solid 2.0), replaced with direct reactive reads and `flush()` for synchronous assertions; `createSignal` in tests uses `{ ownedWrite: true }` +- README: `onMount` example updated to `onSettled` diff --git a/packages/scroll/README.md b/packages/scroll/README.md index ed2ed67ac..4a83147ae 100644 --- a/packages/scroll/README.md +++ b/packages/scroll/README.md @@ -45,10 +45,10 @@ createEffect(() => { ```tsx let ref: HTMLDivElement | undefined; -// pass as function +// pass as function — preferred, handles ref population automatically const scroll = createScrollPosition(() => ref); -// or wrap with onMount -onMount(() => { +// or wrap with onSettled +onSettled(() => { const scroll = createScrollPosition(ref!); }); diff --git a/packages/scroll/package.json b/packages/scroll/package.json index 130c6d923..8406cb779 100644 --- a/packages/scroll/package.json +++ b/packages/scroll/package.json @@ -58,10 +58,12 @@ "@solid-primitives/static-store": "workspace:^" }, "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/scroll/src/index.ts b/packages/scroll/src/index.ts index 92f44ef32..055e02e36 100644 --- a/packages/scroll/src/index.ts +++ b/packages/scroll/src/index.ts @@ -1,8 +1,8 @@ import { createEventListener } from "@solid-primitives/event-listener"; import { createHydratableSingletonRoot } from "@solid-primitives/rootless"; import { createDerivedStaticStore } from "@solid-primitives/static-store"; -import { type Accessor, createSignal, onMount, sharedConfig } from "solid-js"; -import { isServer } from "solid-js/web"; +import { type Accessor, createSignal, onSettled, sharedConfig } from "solid-js"; +import { isServer } from "@solidjs/web"; export function getScrollParent(node: Element | null): Element { if (isServer) { @@ -71,21 +71,27 @@ export function createScrollPosition( target = target || window; - const isFn = typeof target === "function", - isHydrating = sharedConfig.context, - getTargetPos = isFn - ? () => getScrollPosition((target as Extract)()) - : () => getScrollPosition(target as Element | Window), - // changing the calc signal will trigger the derived store to update - [calc, setCalc] = createSignal(isHydrating ? () => FALLBACK_SCROLL_POSITION : getTargetPos, { - equals: false, - }), - trigger = () => setCalc(() => getTargetPos), - pos = createDerivedStaticStore(() => calc()()); - - // update the position on mount if we are hydrating (initial pos is null) - // or if target is a function (which means it could be a ref that will be populated onMount) - if (isHydrating || isFn) onMount(trigger); + const isFn = typeof target === "function"; + const isHydrating = sharedConfig.hydrating; + + const getPos = (): Position => + getScrollPosition( + isFn ? (target as Accessor)() : (target as Element | Window), + ); + + // Plain integer counter — avoids Solid 2.0 createSignal(fn) derived-signal semantics. + // ownedWrite allows writes from DOM event handlers inside reactive scopes. + let tick = 1; + const [version, setVersion] = createSignal(0, { ownedWrite: true }); + const trigger = () => void setVersion(tick++); + + const pos = createDerivedStaticStore(() => { + const v = version(); + return isHydrating && v === 0 ? { ...FALLBACK_SCROLL_POSITION } : getPos(); + }); + + // Refs aren't populated until mount; also refreshes position post-hydration. + if (isHydrating || isFn) onSettled(trigger); createEventListener(target, "scroll", trigger, { passive: true }); diff --git a/packages/scroll/test/index.test.ts b/packages/scroll/test/index.test.ts index 7c8534a46..9df9cf771 100644 --- a/packages/scroll/test/index.test.ts +++ b/packages/scroll/test/index.test.ts @@ -1,4 +1,4 @@ -import { createComputed, createRoot, createSignal } from "solid-js"; +import { createRoot, createSignal, flush } from "solid-js"; import { describe, expect, it } from "vitest"; import { createScrollPosition, getScrollPosition } from "../src/index.js"; @@ -20,28 +20,25 @@ describe("getScrollPosition", () => { describe("createScrollPosition", () => { it("will observe scroll events", () => createRoot(dispose => { - const expectedX = [0, 100, 42]; - const actualX: number[] = []; - const expectedY = [0, 34, 11]; - const actualY: number[] = []; - const target = document.createElement("div"); - const scroll = createScrollPosition(target); - createComputed(() => { - actualX.push(scroll.x); - actualY.push(scroll.y); - }); + expect(scroll.x).toBe(0); + expect(scroll.y).toBe(0); Object.assign(target, { scrollTop: 34, scrollLeft: 100 }); target.dispatchEvent(new Event("scroll")); + flush(); + + expect(scroll.x).toBe(100); + expect(scroll.y).toBe(34); Object.assign(target, { scrollTop: 11, scrollLeft: 42 }); target.dispatchEvent(new Event("scroll")); + flush(); - expect(actualX).toEqual(expectedX); - expect(actualY).toEqual(expectedY); + expect(scroll.x).toBe(42); + expect(scroll.y).toBe(11); dispose(); })); @@ -54,15 +51,17 @@ describe("createScrollPosition", () => { const div2 = document.createElement("div"); Object.assign(div2, { scrollTop: 11, scrollLeft: 42 }); - const [target, setTarget] = createSignal(div1); + const [target, setTarget] = createSignal(div1, { ownedWrite: true }); const scroll = createScrollPosition(target); expect(scroll).toEqual({ x: 100, y: 34 }); setTarget(div2); + flush(); expect(scroll).toEqual({ x: 42, y: 11 }); setTarget(); + flush(); expect(scroll).toEqual({ x: 0, y: 0 }); dispose(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f59c63a9c..21dfe127b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -829,9 +829,12 @@ importers: specifier: workspace:^ version: link:../static-store 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/selection: devDependencies: