diff --git a/.changeset/idle-solid2-migration.md b/.changeset/idle-solid2-migration.md new file mode 100644 index 000000000..057769f3f --- /dev/null +++ b/.changeset/idle-solid2-migration.md @@ -0,0 +1,13 @@ +--- +"@solid-primitives/idle": major +--- + +Migrate to Solid.js v2.0 (beta.10) + +- Updated peer dependencies to `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` +- Changed `isServer` import from `solid-js/web` to `@solidjs/web` +- Replaced `onMount` with `onSettled` +- Removed `batch` calls (Solid 2.0 batches automatically via microtasks) +- Added `INTERNAL_OPTIONS` (`ownedWrite: true`) to signals to prevent owned-scope write warnings +- Used `noop` from `@solid-primitives/utils` for server-side no-op methods +- Fixed default events list: removed duplicate `"wheel"` entry (was listed twice as `"wheel"` and `"mousewheel"`) diff --git a/packages/idle/README.md b/packages/idle/README.md index 896e3ec7c..b761cad9d 100644 --- a/packages/idle/README.md +++ b/packages/idle/README.md @@ -65,7 +65,7 @@ The options are: - **onPrompt**: `(evt: Event) => void`; when the `idleTimeout` expires, before declaring the idle status, `onPrompt` callback is fired, starting the prompt timer. When invoked, the last event fired before the prompt phase will be passed as a parameter. It defaults to an empty function. - **onActive**: `(evt: Event) => void`; callback called when the user resumes activity after having been idle (resuming from prompt phase doesn't trigger `onActive`). The event that triggered the return to activity is passed as a parameter. It defaults to an empty function. - **startManually**: `boolean`; requires the event-listeners to be bound manually by using the `start` method, instead of on mount. It defaults to false. -- **events**: `EventTypeName[]`; a list of the DOM events that will be listened to in order to monitor the user's activity. The events must be of `EventTypeName` type (it can be imported). The list defaults to `['mousemove', 'keydown', 'wheel', 'resize', 'mousewheel', 'mousedown', 'pointerdown', 'touchstart', 'touchmove', 'visibilitychange']` +- **events**: `EventTypeName[]`; a list of the DOM events that will be listened to in order to monitor the user's activity. The events must be of `EventTypeName` type (it can be imported). The list defaults to `['mousemove', 'keydown', 'wheel', 'resize', 'mousedown', 'pointerdown', 'touchstart', 'touchmove', 'visibilitychange']` - **element**: `HTMLElement`; DOM element to which the event listeners will be attached. It defaults to `document`. ## Demo diff --git a/packages/idle/package.json b/packages/idle/package.json index 380c76d1c..f5b96e9fa 100644 --- a/packages/idle/package.json +++ b/packages/idle/package.json @@ -51,11 +51,16 @@ "inactivity", "activity" ], + "dependencies": { + "@solid-primitives/utils": "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/idle/src/createIdleTimer.ts b/packages/idle/src/createIdleTimer.ts index 285fc1307..3473ad5ec 100644 --- a/packages/idle/src/createIdleTimer.ts +++ b/packages/idle/src/createIdleTimer.ts @@ -1,5 +1,6 @@ -import { batch, createSignal, onMount, onCleanup } from "solid-js"; -import { isServer } from "solid-js/web"; +import { createSignal, onSettled, onCleanup } from "solid-js"; +import { isServer } from "@solidjs/web"; +import { INTERNAL_OPTIONS, noop } from "@solid-primitives/utils"; import type { EventTypeName, IdleTimerOptions, IdleTimer } from "./types.js"; const THROTTLE_DELAY: number = 250; @@ -13,7 +14,6 @@ const EVENTS: EventTypeName[] = [ "keydown", "wheel", "resize", - "wheel", "mousedown", "pointerdown", "touchstart", @@ -62,15 +62,15 @@ export const createIdleTimer = ({ return { isIdle: () => false, isPrompted: () => false, - reset: () => {}, - start: () => {}, - stop: () => {}, - triggerIdle: () => {}, + reset: noop, + start: noop, + stop: noop, + triggerIdle: noop, }; } let listenersAreOn = false; - const [isPrompted, setIsPrompted] = createSignal(false); - const [isIdle, setIsIdle] = createSignal(false); + const [isPrompted, setIsPrompted] = createSignal(false, INTERNAL_OPTIONS); + const [isIdle, setIsIdle] = createSignal(false, INTERNAL_OPTIONS); let idle: ReturnType; let prompt: ReturnType; @@ -124,19 +124,15 @@ export const createIdleTimer = ({ function setPromptTimer(evt: Event) { prompt = setTimeout(() => { - batch(() => { - setIsIdle(true); - setIsPrompted(false); - }); + setIsIdle(true); + setIsPrompted(false); onIdle?.(evt); }, promptTimeout); } function cleanState() { - batch(() => { - setIsIdle(false); - setIsPrompted(false); - }); + setIsIdle(false); + setIsPrompted(false); } function startListening(evt: Event = new CustomEvent("manualstart")) { @@ -171,7 +167,7 @@ export const createIdleTimer = ({ addListeners(); } - onMount(() => { + onSettled(() => { if (startManually) return; startListening(new CustomEvent("mount")); }); diff --git a/packages/idle/test/index.test.ts b/packages/idle/test/index.test.ts index bc4a2dc64..e34c8c8bd 100644 --- a/packages/idle/test/index.test.ts +++ b/packages/idle/test/index.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, beforeEach, vi, afterAll, beforeAll } from "vitest"; -import { createRoot } from "solid-js"; +import { createRoot, flush } from "solid-js"; import { createIdleTimer } from "../src/index.js"; beforeAll(() => { @@ -26,16 +26,20 @@ describe("createIdleTimer", () => { return { ...timer, dispose }; }); + flush(); // trigger onSettled to auto-start the timer + vi.advanceTimersByTime(2); expect(timer.isPrompted(), "user is not prompted yet").toBe(false); expect(timer.isIdle(), "user is not idle yet").toBe(false); vi.advanceTimersByTime(5); + flush(); // commit setIsPrompted(true) from idle timeout callback expect(timer.isPrompted(), "user has been prompted").toBe(true); expect(timer.isIdle(), "user is not idle yet").toBe(false); vi.advanceTimersByTime(5); + flush(); // commit setIsIdle(true), setIsPrompted(false) from prompt timeout callback expect(timer.isPrompted(), "user is not in the prompted phase anymore").toBe(false); expect(timer.isIdle(), "user is idle").toBe(true); @@ -56,18 +60,23 @@ describe("createIdleTimer", () => { expect(timer.isIdle(), "user is not idle yet, events are not bound yet").toBe(false); timer.start(); + flush(); // commit cleanState signal writes from startListening vi.advanceTimersByTime(50); + flush(); // commit setIsIdle(true) from idle and prompt timeout callbacks expect(timer.isIdle(), "user is idle, timer should have expired by now").toBe(true); timer.reset(); + flush(); // commit setIsIdle(false) from cleanState in timerReset vi.advanceTimersByTime(4); expect(timer.isIdle(), "user is not idle yet, timers have restarted").toBe(false); vi.advanceTimersByTime(50); + flush(); // commit setIsIdle(true) again from timeout callbacks expect(timer.isIdle(), "user is idle again").toBe(true); timer.stop(); + flush(); // commit setIsIdle(false) from cleanState in stopListening vi.advanceTimersByTime(1); expect(timer.isIdle(), "user is not idle anymore, timers have been cleaned up").toBe(false); @@ -103,6 +112,7 @@ describe("createIdleTimer", () => { expect(timer.isIdle(), "user is not idle yet").toBe(false); timer.triggerIdle(); + flush(); // commit setIsIdle(true) expect(timer.isIdle(), "user is now idle").toBe(true); expect(handleIdle, "onIdle should have been called").toHaveBeenCalled(); @@ -114,12 +124,14 @@ describe("createIdleTimer", () => { expect(handleActive, "onActive should not have been called").not.toHaveBeenCalled(); element.click(); + flush(); // commit setIsIdle(false) from cleanState in timerReset expect(timer.isIdle(), "listenes should still be working and user is not idle anymore").toBe( false, ); expect(handleActive, "onActive should have been called").toHaveBeenCalled(); vi.advanceTimersByTime(15); + flush(); // commit setIsIdle(true) from idle and prompt timeout callbacks expect( timer.isIdle(), "the primitive's flow has not changed, user is idle again after idle timeout expires", @@ -154,13 +166,16 @@ describe("createIdleTimer", () => { expect(currStatus, "events are not bound yet, the status has not changed").toBe("initial"); timer.start(); + flush(); // commit cleanState signal writes from startListening vi.advanceTimersByTime(50); + flush(); // commit setIsPrompted(true) so handleEvent reads it correctly on click expect( currStatus, "timers have started, user should be in the prompt phase, onPrompt should have been called by now", ).toBe("prompted"); vi.advanceTimersByTime(60); + flush(); // commit setIsIdle(true) so handleEvent reads isIdle() correctly on click expect(currStatus, "prompt timer has expired, onIdle should have been called by now").toBe( "idle", ); diff --git a/packages/idle/test/server.test.ts b/packages/idle/test/server.test.ts new file mode 100644 index 000000000..559f1204b --- /dev/null +++ b/packages/idle/test/server.test.ts @@ -0,0 +1,16 @@ +import { describe, it, expect } from "vitest"; +import { createIdleTimer } from "../src/index.js"; + +describe("createIdleTimer", () => { + it("returns a no-op timer on the server", () => { + const timer = createIdleTimer(); + expect(timer.isIdle()).toBe(false); + expect(timer.isPrompted()).toBe(false); + timer.start(); + timer.stop(); + timer.reset(); + timer.triggerIdle(); + expect(timer.isIdle()).toBe(false); + expect(timer.isPrompted()).toBe(false); + }); +}); diff --git a/packages/idle/tsconfig.json b/packages/idle/tsconfig.json index 38c71ce71..dc1970e16 100644 --- a/packages/idle/tsconfig.json +++ b/packages/idle/tsconfig.json @@ -5,7 +5,11 @@ "outDir": "dist", "rootDir": "src" }, - "references": [], + "references": [ + { + "path": "../utils" + } + ], "include": [ "src" ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac080626e..8ffb59b58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -428,10 +428,17 @@ importers: version: 1.9.7 packages/idle: + dependencies: + '@solid-primitives/utils': + 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/immutable: dependencies: