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
13 changes: 13 additions & 0 deletions .changeset/idle-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -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"`)
2 changes: 1 addition & 1 deletion packages/idle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 7 additions & 2 deletions packages/idle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
32 changes: 14 additions & 18 deletions packages/idle/src/createIdleTimer.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,7 +14,6 @@ const EVENTS: EventTypeName[] = [
"keydown",
"wheel",
"resize",
"wheel",
"mousedown",
"pointerdown",
"touchstart",
Expand Down Expand Up @@ -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<typeof setTimeout>;
let prompt: ReturnType<typeof setTimeout>;
Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -171,7 +167,7 @@ export const createIdleTimer = ({
addListeners();
}

onMount(() => {
onSettled(() => {
if (startManually) return;
startListening(new CustomEvent("mount"));
});
Expand Down
17 changes: 16 additions & 1 deletion packages/idle/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -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(() => {
Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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();
Expand All @@ -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",
Expand Down Expand Up @@ -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",
);
Expand Down
16 changes: 16 additions & 0 deletions packages/idle/test/server.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
6 changes: 5 additions & 1 deletion packages/idle/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
"outDir": "dist",
"rootDir": "src"
},
"references": [],
"references": [
{
"path": "../utils"
}
],
"include": [
"src"
]
Expand Down
11 changes: 9 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.