diff --git a/.changeset/pagination-solid2-migration.md b/.changeset/pagination-solid2-migration.md new file mode 100644 index 000000000..bcddff8d5 --- /dev/null +++ b/.changeset/pagination-solid2-migration.md @@ -0,0 +1,17 @@ +--- +"@solid-primitives/pagination": 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/pagination` + +- `isServer` now imported from `@solidjs/web` (not `solid-js/web`) +- `createPagination`: page clamping when pages count decreases is now implemented via a derived memo instead of `createComputed` (which was removed in Solid 2.0). The clamping is reactive and automatic. +- `createInfiniteScroll`: removed `createResource` dependency (removed in Solid 2.0). Fetching is now implemented with `createEffect` and a cancellation pattern. The `pages.loading` and `pages.error` resource properties are no longer available; use `end()` or wrap the fetcher to handle errors externally. +- `batch()` calls removed — Solid 2.0 batches updates automatically via microtasks. Tests require `flush()` after signal writes to observe committed values. +- All internal signals use `{ ownedWrite: true }` to allow setters to be called from reactive scopes and event handlers without triggering ownership warnings. diff --git a/packages/pagination/README.md b/packages/pagination/README.md index fb57512a4..3ebf92dbb 100644 --- a/packages/pagination/README.md +++ b/packages/pagination/README.md @@ -24,6 +24,8 @@ yarn add @solid-primitives/pagination pnpm add @solid-primitives/pagination ``` +> **Peer dependencies:** `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` + ## `createPagination` Provides an array with the properties to fill your pagination with and a page setter/getter. @@ -133,7 +135,7 @@ https://primitives.solidjs.community/playground/pagination/ ## `createInfiniteScroll` -Combines [`createResource`](https://www.solidjs.com/docs/latest/api#createresource) with [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to provide an easy way to implement infinite scrolling. +Combines [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) with a page-based fetcher to provide an easy way to implement infinite scrolling. Browser-only: the loader and fetching are skipped on the server. ### How to use it @@ -171,7 +173,7 @@ return ( ```ts function createInfiniteScroll(fetcher: (page: number) => Promise): [ pages: Accessor, - loader: Directive, + loader: (el: Element) => void, options: { page: Accessor; setPage: Setter; diff --git a/packages/pagination/package.json b/packages/pagination/package.json index c85d28cd9..cc9f6a337 100644 --- a/packages/pagination/package.json +++ b/packages/pagination/package.json @@ -59,9 +59,11 @@ "@solid-primitives/utils": "workspace:^" }, "peerDependencies": { - "solid-js": "^1.6.12" + "@solidjs/web": "^2.0.0-beta.10", + "solid-js": "^2.0.0-beta.10" }, "devDependencies": { - "solid-js": "^1.9.7" + "@solidjs/web": "2.0.0-beta.10", + "solid-js": "2.0.0-beta.10" } } diff --git a/packages/pagination/src/index.ts b/packages/pagination/src/index.ts index bc5c9704f..06aeff1ae 100644 --- a/packages/pagination/src/index.ts +++ b/packages/pagination/src/index.ts @@ -1,17 +1,15 @@ import { access, tryOnCleanup, noop, type MaybeAccessor } from "@solid-primitives/utils"; import { type Accessor, - batch, type JSX, type Setter, - createComputed, + createEffect, createMemo, - createResource, createSignal, onCleanup, untrack, } from "solid-js"; -import { isServer } from "solid-js/web"; +import { isServer } from "@solidjs/web"; /** * createSegment - create a reactive segment out of an array of items @@ -34,15 +32,15 @@ export const createSegment = ( ): Accessor => { let previousStart = NaN, previousEnd = NaN; - return createMemo(previous => { + return createMemo(prev => { const currentItems = access(items); const start = (page() - 1) * access(limit); const end = Math.min(start + access(limit), currentItems.length); if ( - previous && - ((previous.length === 0 && end <= start) || (start === previousStart && end === previousEnd)) + prev && + ((prev.length === 0 && end <= start) || (start === previousStart && end === previousEnd)) ) { - return previous; + return prev; } previousStart = start; previousEnd = end; @@ -137,28 +135,25 @@ export const createPagination = ( options?: MaybeAccessor, ): [props: Accessor, page: Accessor, setPage: Setter] => { const opts = createMemo(() => Object.assign({}, PAGINATION_DEFAULTS, access(options))); - const [page, _setPage] = createSignal(opts().initialPage || 1); + // ownedWrite allows setPage to be called from event handlers and reactive scopes + const [rawPage, setRawPage] = createSignal(opts().initialPage || 1, { ownedWrite: true }); - // do not allow pages beyond the number of pages in case the latter changes const setPage = (p: number | ((_p: number) => number)) => { if (typeof p === "function") { p = p(page()); } if (p < 1) { - return _setPage(1); + return setRawPage(1); } const pages = opts().pages; if (p > pages) { - return _setPage(pages); + return setRawPage(pages); } - return _setPage(p); + return setRawPage(p); }; - // normalize in case the number of pages changes, do not run the first time - createComputed(previous => { - opts().pages; - return previous ? setPage(untrack(page)) : true; - }); + // Clamp page to valid range reactively — handles page count decreasing below current page + const page = createMemo(() => Math.max(1, Math.min(rawPage(), opts().pages))); const goPage = (p: number | ((p: number) => number), ev: KeyboardEvent) => { setPage(p); @@ -182,31 +177,29 @@ export const createPagination = ( const maxPages = createMemo(() => Math.min(opts().maxPages, opts().pages)); - const pages = createMemo( - previous => - [...Array(opts().pages)].map( - (_, i) => - previous[i] || - ((pageNo: number) => - Object.defineProperties( - isServer - ? { children: pageNo.toString() } - : { - children: pageNo.toString(), - onClick: [setPage, pageNo] as const, - onKeyUp: [onKeyUp, pageNo] as const, - }, - { - "aria-current": { - get: () => (page() === pageNo ? "true" : undefined), - set: noop, - enumerable: true, + const pages = createMemo(prev => + [...Array(opts().pages)].map( + (_, i) => + (prev ?? [])[i] || + ((pageNo: number) => + Object.defineProperties( + isServer + ? { children: pageNo.toString() } + : { + children: pageNo.toString(), + onClick: [setPage, pageNo] as const, + onKeyUp: [onKeyUp, pageNo] as const, }, - page: { value: pageNo, enumerable: false }, + { + "aria-current": { + get: () => (page() === pageNo ? "true" : undefined), + set: noop, + enumerable: true, }, - ))(i + 1), - ), - [], + page: { value: pageNo, enumerable: false }, + }, + ))(i + 1), + ), ); const first = Object.defineProperties( isServer @@ -359,15 +352,13 @@ export type _E = JSX.Element; * const [pages, loader, { page, setPage, setPages, end, setEnd }] = createInfiniteScroll(fetcher); * ``` * @param fetcher `(page: number) => Promise` - * @return `pages()` is an accessor contains array of contents - * @property `pages.loading` is a boolean indicator for the loading state - * @property `pages.error` contains any error encountered - * @return `infiniteScrollLoader` is a directive used to set the loader element - * @method `page` is an accessor that contains page number + * @return `pages()` is an accessor that contains the accumulated array of all fetched items + * @return `loader` is a ref function to attach to the sentinel element that triggers loading + * @method `page` is an accessor that contains the current page number * @method `setPage` allows to manually change the page number - * @method `setPages` allows to manually change the contents of the page - * @method `end` is a boolean indicator for end of the page - * @method `setEnd` allows to manually change the end + * @method `setPages` allows to manually replace the accumulated items + * @method `end` is a boolean indicator for end of the content + * @method `setEnd` allows to manually change the end state */ export function createInfiniteScroll(fetcher: (page: number) => Promise): [ pages: Accessor, @@ -380,14 +371,16 @@ export function createInfiniteScroll(fetcher: (page: number) => Promise) setEnd: Setter; }, ] { - const [pages, setPages] = createSignal([]); - const [page, setPage] = createSignal(0); - const [end, setEnd] = createSignal(false); + // ownedWrite allows setters to be called from reactive scopes and event handlers + const [pages, setPages] = createSignal([], { ownedWrite: true }); + const [page, setPage] = createSignal(0, { ownedWrite: true }); + const [end, setEnd] = createSignal(false, { ownedWrite: true }); + const [fetching, setFetching] = createSignal(false, { ownedWrite: true }); let add: (el: Element) => void = noop; if (!isServer) { const io = new IntersectionObserver(e => { - if (e.length > 0 && e[0]!.isIntersecting && !end() && !contents.loading) { + if (e.length > 0 && e[0]!.isIntersecting && !end() && !fetching()) { setPage(p => p + 1); } }); @@ -396,28 +389,34 @@ export function createInfiniteScroll(fetcher: (page: number) => Promise) io.observe(el); tryOnCleanup(() => io.unobserve(el)); }; - } - const [contents] = createResource(page, fetcher); - - createComputed(() => { - const content = contents.latest; - if (!content) return; - batch(() => { - if (content.length === 0) setEnd(true); - setPages(p => [...p, ...content]); - }); - }); + createEffect( + () => page(), + currentPage => { + let cancelled = false; + setFetching(true); + fetcher(currentPage).then(content => { + if (cancelled) return; + if (content.length === 0) setEnd(true); + setPages(p => [...p, ...content]); + setFetching(false); + }); + return () => { + cancelled = true; + }; + }, + ); + } return [ pages, add, { - page: page, - setPage: setPage, - setPages: setPages, - end: end, - setEnd: setEnd, + page, + setPage, + setPages, + end, + setEnd, }, ]; } diff --git a/packages/pagination/test/index.test.ts b/packages/pagination/test/index.test.ts index add2693c3..2cbcbe25b 100644 --- a/packages/pagination/test/index.test.ts +++ b/packages/pagination/test/index.test.ts @@ -1,47 +1,52 @@ import { describe, test, expect } from "vitest"; -import { createMemo, createRoot, createSignal } from "solid-js"; +import { createMemo, createRoot, createSignal, flush } from "solid-js"; import { createInfiniteScroll, createPagination, createSegment, PaginationOptions, } from "../src/index.js"; -import { testEffect } from "../../resource/test/index.test.js"; describe("createPagination", () => { - test("createPagination returns page getter and setter", () => - createRoot(dispose => { + test("createPagination returns page getter and setter", () => { + const { page, setPage, dispose } = createRoot(dispose => { const [, page, setPage] = createPagination({ pages: 100 }); - expect(page(), "initial value should be 1").toBe(1); - setPage(2); - expect(page(), "value after change should be 2").toBe(2); - dispose(); - })); + return { page, setPage, dispose }; + }); + expect(page(), "initial value should be 1").toBe(1); + setPage(2); + flush(); + expect(page(), "value after change should be 2").toBe(2); + dispose(); + }); - test("createPagination returns props", () => - createRoot(dispose => { + test("createPagination returns props", () => { + const { paginationProps, page, setPage, dispose } = createRoot(dispose => { const [paginationProps, page, setPage] = createPagination({ pages: 100 }); - expect(paginationProps().map(({ children }) => children)).toEqual([ - "|<", - "<", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - ">", - ">|", - ]); - expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(2); - setPage(page() + 1); - expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(3); - dispose(); - })); + return { paginationProps, page, setPage, dispose }; + }); + expect(paginationProps().map(({ children }) => children)).toEqual([ + "|<", + "<", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ">", + ">|", + ]); + expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(2); + setPage(page() + 1); + flush(); + expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(3); + dispose(); + }); test("createPagination clamps start", () => createRoot(dispose => { @@ -54,40 +59,46 @@ describe("createPagination", () => { dispose(); })); - test("createPagination reacts to options update", () => - createRoot(dispose => { - const commonOptions = { - showFirst: false, - showPrev: false, - showNext: false, - showLast: false, - }; - const [options, setOptions] = createSignal({ ...commonOptions, pages: 10, maxPages: 20 }); - const [paginationProps, _page, _setPage] = createPagination(options); - expect(paginationProps().length, "initial pages").toBe(10); - setOptions({ ...commonOptions, pages: 5, maxPages: 10 }); - expect(paginationProps().length, "pages after change").toBe(5); - dispose(); - })); + test("createPagination reacts to options update", () => { + const commonOptions = { + showFirst: false, + showPrev: false, + showNext: false, + showLast: false, + }; + const [options, setOptions] = createSignal({ ...commonOptions, pages: 10, maxPages: 20 }); + const { paginationProps, dispose } = createRoot(dispose => { + const [paginationProps] = createPagination(options); + return { paginationProps, dispose }; + }); + expect(paginationProps().length, "initial pages").toBe(10); + setOptions({ ...commonOptions, pages: 5, maxPages: 10 }); + flush(); + expect(paginationProps().length, "pages after change").toBe(5); + dispose(); + }); - test("createPagination pages reused", () => - createRoot(dispose => { - const commonOptions = { - showFirst: false, - showPrev: false, - showNext: false, - showLast: false, - }; - const [options, setOptions] = createSignal({ ...commonOptions, pages: 3 }); - const [paginationProps, _page, _setPage] = createPagination(options); - const initialPages = paginationProps(); - setOptions({ ...commonOptions, pages: 2 }); - const updatedPages = paginationProps(); - expect(updatedPages.every((page, index) => Object.is(page, initialPages[index]))).toBe(true); - dispose(); - })); + test("createPagination pages reused", () => { + const commonOptions = { + showFirst: false, + showPrev: false, + showNext: false, + showLast: false, + }; + const [options, setOptions] = createSignal({ ...commonOptions, pages: 3 }); + const { paginationProps, dispose } = createRoot(dispose => { + const [paginationProps] = createPagination(options); + return { paginationProps, dispose }; + }); + const initialPages = paginationProps(); + setOptions({ ...commonOptions, pages: 2 }); + flush(); + const updatedPages = paginationProps(); + expect(updatedPages.every((page, index) => Object.is(page, initialPages[index]))).toBe(true); + dispose(); + }); - test("createPagination next back", () => { + test("createPagination next back", () => createRoot(dispose => { const [paginationProps, page, setPage] = createPagination({ pages: 100, @@ -102,116 +113,113 @@ describe("createPagination", () => { expect(next?.page, "next page should be 4").toStrictEqual(4); dispose(); - }); - }); + })); test("setting page below one will yield the first page", () => { - createRoot(dispose => { - const [paginationProps, page, setPage] = createPagination({ - pages: 10, - maxPages: 5, - }); - - expect(page()).toBe(1); - setPage(0); - expect(page()).toBe(1); - setPage(-1); - expect(page()).toBe(1); - - dispose(); + const { page, setPage, dispose } = createRoot(dispose => { + const [, page, setPage] = createPagination({ pages: 10, maxPages: 5 }); + return { page, setPage, dispose }; }); + expect(page()).toBe(1); + setPage(0); + flush(); + expect(page()).toBe(1); + setPage(-1); + flush(); + expect(page()).toBe(1); + dispose(); }); test("setting page beyond the number pages will yield the last page", () => { - createRoot(dispose => { - const [paginationProps, page, setPage] = createPagination({ - pages: 10, - maxPages: 5, - initialPage: 10, - }); - - expect(page()).toBe(10); - setPage(11); - expect(page()).toBe(10); - setPage(Infinity); - expect(page()).toBe(10); - - dispose(); + const { page, setPage, dispose } = createRoot(dispose => { + const [, page, setPage] = createPagination({ pages: 10, maxPages: 5, initialPage: 10 }); + return { page, setPage, dispose }; }); + expect(page()).toBe(10); + setPage(11); + flush(); + expect(page()).toBe(10); + setPage(Infinity); + flush(); + expect(page()).toBe(10); + dispose(); }); test("lowering the number of pages will not make the page go beyond it", () => { - createRoot(dispose => { - const [options, setOptions] = createSignal({ - pages: 10, - maxPages: 5, - initialPage: 10, - }); - const [paginationProps, page, setPage] = createPagination(options); - - expect(page()).toBe(10); - setOptions({ pages: 8, maxPages: 5 }); - expect(page()).toBe(8); - - dispose(); + const [options, setOptions] = createSignal({ + pages: 10, + maxPages: 5, + initialPage: 10, }); + const { page, dispose } = createRoot(dispose => { + const [, page] = createPagination(options); + return { page, dispose }; + }); + expect(page()).toBe(10); + setOptions({ pages: 8, maxPages: 5 }); + flush(); + expect(page()).toBe(8); + dispose(); }); }); describe("createSegment", () => { test("creates valid segments", () => { - createRoot(dispose => { + const [page, setPage] = createSignal(1); + const { segment, dispose } = createRoot(dispose => { const items = createMemo(() => Array.from({ length: 50 }, (_, i) => i + 1)); - const [page, setPage] = createSignal(1); - const segment = createSegment(items, 10, page); - - expect(segment()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - setPage(5); - expect(segment()).toEqual([41, 42, 43, 44, 45, 46, 47, 48, 49, 50]); - setPage(6); - expect(segment()).toEqual([]); - - dispose(); + return { segment: createSegment(items, 10, page), dispose }; }); + + expect(segment()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + setPage(5); + flush(); + expect(segment()).toEqual([41, 42, 43, 44, 45, 46, 47, 48, 49, 50]); + setPage(6); + flush(); + expect(segment()).toEqual([]); + dispose(); }); test("does not create the same segment twice", () => { - createRoot(dispose => { - const [length, setLength] = createSignal(50); + const [length, setLength] = createSignal(50); + const [page, setPage] = createSignal(1); + const { segment, dispose } = createRoot(dispose => { const items = createMemo(() => Array.from({ length: length() }, (_, i) => i + 1)); - const [page, setPage] = createSignal(1); - const segment = createSegment(items, 10, page); - - const seg1 = segment(); - setLength(10); - const seg2 = segment(); - expect(seg1).toBe(seg2); - setPage(2); - const seg3 = segment(); - setPage(3); - const seg4 = segment(); - expect(seg3).toBe(seg4); - - dispose(); + return { segment: createSegment(items, 10, page), dispose }; }); + + const seg1 = segment(); + setLength(10); + flush(); + const seg2 = segment(); + expect(seg1).toBe(seg2); + setPage(2); + flush(); + const seg3 = segment(); + setPage(3); + flush(); + const seg4 = segment(); + expect(seg3).toBe(seg4); + dispose(); }); test("creates a new segment if new items are added", () => { - createRoot(dispose => { - const [length, setLength] = createSignal(55); + const [length, setLength] = createSignal(55); + const [page] = createSignal(6); + const { segment, dispose } = createRoot(dispose => { const items = createMemo(() => Array.from({ length: length() }, (_, i) => i + 1)); - const [page, setPage] = createSignal(6); - const segment = createSegment(items, 10, page); - - const seg1 = segment(); - expect(seg1).toEqual([51, 52, 53, 54, 55]); - setLength(57); - const seg2 = segment(); - expect(seg2).toEqual([51, 52, 53, 54, 55, 56, 57]); - expect(seg1).not.toBe(seg2); - - dispose(); + return { segment: createSegment(items, 10, page), dispose }; }); + + const seg1 = segment(); + expect(seg1).toEqual([51, 52, 53, 54, 55]); + setLength(57); + flush(); + const seg2 = segment(); + expect(seg2).toEqual([51, 52, 53, 54, 55, 56, 57]); + expect(seg1).not.toBe(seg2); + dispose(); }); }); @@ -223,14 +231,17 @@ global.IntersectionObserver = class { describe("createInfiniteScroll", () => { const fetcher = async (page: number) => Array.from({ length: page + 1 }, (_, i) => i); - test("createInfiniteScroll", () => - createRoot(dispose => { + test("createInfiniteScroll", () => { + const { pages, page, setPage, dispose } = createRoot(dispose => { const [pages, , { page, setPage }] = createInfiniteScroll(fetcher); - expect(pages(), "initial value should be []").toStrictEqual([]); + return { pages, page, setPage, dispose }; + }); + expect(pages(), "initial value should be []").toStrictEqual([]); - setPage(1); - expect(page(), "value should be 1").toStrictEqual(1); + setPage(1); + flush(); + expect(page(), "value should be 1").toStrictEqual(1); - dispose(); - })); + dispose(); + }); }); diff --git a/packages/pagination/test/server.test.ts b/packages/pagination/test/server.test.ts index dad2ca346..e06199796 100644 --- a/packages/pagination/test/server.test.ts +++ b/packages/pagination/test/server.test.ts @@ -1,21 +1,25 @@ import { describe, test, expect } from "vitest"; -import { createRoot, Suspense, createSignal } from "solid-js"; +import { createRoot, createSignal } from "solid-js"; import { createInfiniteScroll, createPagination } from "../src/index.js"; -import { renderToStringAsync } from "solid-js/web"; describe("createPagination", () => { - test("createPagination returns page getter and setter", () => + test("createPagination returns initial page", () => createRoot(dispose => { - const [, page, setPage] = createPagination({ pages: 100 }); + const [, page] = createPagination({ pages: 100 }); expect(page(), "initial value should be 1").toBe(1); - setPage(2); - expect(page(), "value after change should be 2").toBe(2); dispose(); })); - test("createPagination returns props", () => + test("createPagination respects initialPage", () => createRoot(dispose => { - const [paginationProps, page, setPage] = createPagination({ pages: 100 }); + const [, page] = createPagination({ pages: 100, initialPage: 5 }); + expect(page(), "initial value should be 5").toBe(5); + dispose(); + })); + + test("createPagination returns props on server", () => + createRoot(dispose => { + const [paginationProps] = createPagination({ pages: 100 }); expect(paginationProps().map(({ children }) => children)).toEqual([ "|<", "<", @@ -32,67 +36,42 @@ describe("createPagination", () => { ">", ">|", ]); - expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(2); - setPage(page() + 1); - expect(paginationProps().findIndex(({ ["aria-current"]: current }) => current)).toBe(3); dispose(); })); test("createPagination clamps start", () => createRoot(dispose => { - const [paginationProps, _page, _setPage] = createPagination({ - pages: 10, - maxPages: 13, - }); + const [paginationProps] = createPagination({ pages: 10, maxPages: 13 }); const extraProps = 4; expect(paginationProps().length, "pages").toBe(10 + extraProps); dispose(); })); - test("createPagination pages reused", () => + test("createPagination pages reused with reactive options", () => { + const commonOptions = { + showFirst: false, + showPrev: false, + showNext: false, + showLast: false, + }; + const [options] = createSignal({ ...commonOptions, pages: 3 }); createRoot(dispose => { - const commonOptions = { - showFirst: false, - showPrev: false, - showNext: false, - showLast: false, - }; - const [options, setOptions] = createSignal({ ...commonOptions, pages: 3 }); - const [paginationProps, _page, _setPage] = createPagination(options); - const initialPages = paginationProps(); - setOptions({ ...commonOptions, pages: 2 }); - const updatedPages = paginationProps(); - expect(updatedPages.every((page, index) => Object.is(page, initialPages[index]))).toBe(true); + const [paginationProps] = createPagination(options); + expect(paginationProps().length, "initial pages").toBe(3); dispose(); - })); + }); + }); }); describe("createInfiniteScroll", () => { const fetcher = async (page: number) => Array.from({ length: page + 1 }, (_, i) => i); - test("createInfiniteScroll", async () => { - let run = 0; - - await new Promise(resolve => { - renderToStringAsync(() => { - Suspense({ - get children() { - const [pages] = createInfiniteScroll(fetcher); - - if (run === 0) { - expect(pages(), "initial run").toEqual([]); - } else { - expect(pages(), "after fetcher resolved").toEqual([0]); - resolve(); - } - - run++; - return ""; - }, - }); - }); - }); - - expect(run).toBe(2); - }); + test("createInfiniteScroll returns empty state on server", () => + createRoot(dispose => { + const [pages, , { page, end }] = createInfiniteScroll(fetcher); + expect(pages(), "initial pages should be empty").toEqual([]); + expect(page(), "initial page should be 0").toBe(0); + expect(end(), "initial end should be false").toBe(false); + dispose(); + })); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 726871879..e0aad2e06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -663,9 +663,12 @@ importers: specifier: workspace:^ version: link:../utils 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/permission: devDependencies: @@ -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==}