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
16 changes: 16 additions & 0 deletions .changeset/date-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@solid-primitives/date": 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.

- `isServer` is now imported from `@solidjs/web` (not `solid-js/web`)
- `createStore` is now imported from `solid-js` (not `solid-js/store`)
- `@solid-primitives/timer` and `@solid-primitives/memo` dependencies removed; `TimeoutSource` type is now defined locally
- `createDateNow`: `createEffect` converted to split `(compute, apply)` form required by Solid 2.0; cleanup returned from apply phase instead of `onCleanup`
- `createCountdown`: `createComputed` (removed in Solid 2.0) replaced with `createRenderEffect(compute, apply)`
- `createDate`: `createWritableMemo` (removed in Solid 2.0) replaced with `createSignal(fn)` overload
4 changes: 4 additions & 0 deletions packages/date/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ Collection of reactive primitives and utility functions, providing easier ways t
npm install @solid-primitives/date
# or
yarn add @solid-primitives/date
# or
pnpm add @solid-primitives/date
```

**Peer dependencies:** `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10`

## Reactive Primitives:

### `createDate`
Expand Down
8 changes: 4 additions & 4 deletions packages/date/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,17 @@
"primitives"
],
"devDependencies": {
"@solid-primitives/event-listener": "workspace:^",
"@solidjs/web": "2.0.0-beta.10",
"date-fns": "^2.30.0",
"solid-js": "^1.9.7"
"solid-js": "2.0.0-beta.10"
},
"dependencies": {
"@solid-primitives/memo": "workspace:^",
"@solid-primitives/timer": "workspace:^",
"@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": {}
}
55 changes: 37 additions & 18 deletions packages/date/src/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { access, type MaybeAccessor, accessWith } from "@solid-primitives/utils";
import { createWritableMemo } from "@solid-primitives/memo";
import { createPolled, type TimeoutSource } from "@solid-primitives/timer";
import { type Accessor, createComputed, createMemo, createSignal } from "solid-js";
import { createStore, type Store } from "solid-js/store";
import { access, type MaybeAccessor, accessWith, noop } from "@solid-primitives/utils";
import { isServer } from "@solidjs/web";
import {
type Accessor,
createEffect,
createMemo,
createRenderEffect,
createSignal,
createStore,
type Store,
} from "solid-js";
import { DEFAULT_MESSAGES, HOUR, MINUTE } from "./variables.js";
import {
formatDate,
Expand All @@ -15,8 +21,9 @@ import type {
Countdown,
DateInit,
DateSetter,
TimeAgoOptions,
GetUpdateInterval,
TimeAgoOptions,
TimeoutSource,
} from "./types.js";

/**
Expand All @@ -26,8 +33,8 @@ import type {
* @returns [`Date` signal, setter function]
*/
export const createDate = (init: MaybeAccessor<DateInit>): [Accessor<Date>, DateSetter] => {
const [date, setDate] = createWritableMemo(() => getDate(access(init)));
const setter: DateSetter = input => setDate(prev => getDate(accessWith(input, prev)));
const [date, setDate] = createSignal<Date>(() => getDate(access(init)));
const setter: DateSetter = input => setDate((prev: Date) => getDate(accessWith(input, prev)));
return [date, setter];
};

Expand Down Expand Up @@ -56,17 +63,26 @@ export const createDate = (init: MaybeAccessor<DateInit>): [Accessor<Date>, Date
export function createDateNow(
interval: TimeoutSource = MINUTE / 2,
): [Accessor<Date>, VoidFunction] {
const [track, trigger] = createSignal(undefined, { equals: false });
const memo = createPolled(
() => {
track();
return new Date();
if (isServer) {
const d = new Date();
return [() => d, noop];
}

const [now, setNow] = createSignal(new Date(), {
equals: (a, b) => a.getTime() === b.getTime(),
});
const update = () => setNow(new Date());

createEffect(
() => (typeof interval === "function" ? interval() : interval),
ms => {
if (ms === false) return;
const id = setInterval(update, ms);
return () => clearInterval(id);
},
interval,
undefined,
{ equals: (a, b) => a.getTime() === b.getTime() },
);
return [memo, trigger];

return [now, update];
}

/**
Expand Down Expand Up @@ -213,7 +229,10 @@ export function createCountdown(
if (b !== undefined) difference = createTimeDifference(a, b)[0];
else difference = a as Accessor<number>;
const [countdown, setCountdown] = createStore<Countdown>(getCountdown(difference()));
createComputed(() => setCountdown(getCountdown(difference())));
createRenderEffect(
() => difference(),
diff => setCountdown(getCountdown(diff)),
);
return countdown;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/date/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { type TimeoutSource } from "@solid-primitives/timer";
import type { TimeoutSource } from "@solid-primitives/timer";
import type { Accessor } from "solid-js";

export type { TimeoutSource };

export type MessageFormatter<T = number> = (value: T, isPast: boolean) => string;
export type RelativeFormatter = (now: Date, target: Date, diff: number) => string;
Expand Down
175 changes: 96 additions & 79 deletions packages/date/test/date-difference.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from "vitest";
import { createRoot, createSignal } from "solid-js";
import { createRoot, createSignal, flush } from "solid-js";
import {
createTimeAgo,
RelativeFormatMessages,
Expand All @@ -13,43 +13,50 @@ import {

describe("createTimeAgo", () => {
it("formats timeago correctly", () => {
createRoot(dispose => {
const [date, setDate] = createSignal<number>(Date.now());
const [timeago] = createTimeAgo(date, {
interval: 0,
});
const [date, setDate] = createSignal<number>(Date.now());

expect(timeago()).toBe("just now");
const { timeago, dispose } = createRoot(dispose => {
const [timeago] = createTimeAgo(date, { interval: 0 });
return { timeago, dispose };
});

setDate(p => p - 3 * MINUTE);
expect(timeago()).toBe("3 minutes ago");
expect(timeago()).toBe("just now");

setDate(p => p - 2 * HOUR);
expect(timeago()).toBe("2 hours ago");
setDate(p => p - 3 * MINUTE);
flush();
expect(timeago()).toBe("3 minutes ago");

setDate(p => p + 2 * WEEK);
expect(timeago()).toBe("in 2 weeks");
setDate(p => p - 2 * HOUR);
flush();
expect(timeago()).toBe("2 hours ago");

setDate(p => p + 2 * MONTH);
expect(timeago()).toBe("in 2 months");
setDate(p => p + 2 * WEEK);
flush();
expect(timeago()).toBe("in 2 weeks");

setDate(p => p - YEAR - 2 * MONTH);
expect(timeago()).toBe("last year");
setDate(p => p + 2 * MONTH);
flush();
expect(timeago()).toBe("in 2 months");

setDate(p => p - YEAR);
expect(timeago()).toBe("2 years ago");
setDate(p => p - YEAR - 2 * MONTH);
flush();
expect(timeago()).toBe("last year");

dispose();
});
setDate(p => p - YEAR);
flush();
expect(timeago()).toBe("2 years ago");

dispose();
});

it("custom relative formatter", () => {
createRoot(dispose => {
const [date, setDate] = createSignal<number>(Date.now());
const [date, setDate] = createSignal<number>(Date.now());

let captured_target: Date | undefined;
let captured_now: Date | undefined;
let captured_diff: number | undefined;

let captured_target;
let captured_now;
let captured_diff;
const { timeago, target, now, dispose } = createRoot(dispose => {
const [timeago, { target, now }] = createTimeAgo(date, {
interval: 0,
relativeFormatter: (now, target, diff) => {
Expand All @@ -59,28 +66,30 @@ describe("createTimeAgo", () => {
return "relative";
},
});
return { timeago, target, now, dispose };
});

expect(timeago(), "'now' should still be 'just now'").toBe("just now");
expect(timeago(), "'now' should still be 'just now'").toBe("just now");

setDate(p => p - 3 * MINUTE);
expect(timeago(), "custom formatter was correctly applied").toBe("relative");
setDate(p => p - 3 * MINUTE);
flush();
expect(timeago(), "custom formatter was correctly applied").toBe("relative");

expect(target(), "captured target should match the returned one").toBe(captured_target);
expect(now(), "captured now should match the returned one").toBe(captured_now);
expect(
captured_diff,
"captured diff should be the same as calculated from now() and target()",
).toBe(target().getTime() - now().getTime());
expect(target(), "captured target should match the returned one").toBe(captured_target);
expect(now(), "captured now should match the returned one").toBe(captured_now);
expect(
captured_diff,
"captured diff should be the same as calculated from now() and target()",
).toBe(target().getTime() - now().getTime());

dispose();
});
dispose();
});

it("custom absolute formatter", () => {
createRoot(dispose => {
const [date, setDate] = createSignal<number>(Date.now());
const [date, setDate] = createSignal<number>(Date.now());

let capturedDate;
let capturedDate: Date | undefined;
const { timeago, target, dispose } = createRoot(dispose => {
const [timeago, { target }] = createTimeAgo(date, {
interval: 0,
max: HOUR,
Expand All @@ -89,60 +98,68 @@ describe("createTimeAgo", () => {
return "absolute";
},
});
return { timeago, target, dispose };
});

expect(timeago()).toBe("just now");
expect(timeago()).toBe("just now");

setDate(p => p - 3 * MINUTE);
expect(timeago()).toBe("3 minutes ago");
setDate(p => p - 3 * MINUTE);
flush();
expect(timeago()).toBe("3 minutes ago");

setDate(p => p - 2 * HOUR);
expect(timeago(), "absolute formatter should be appled").toBe("absolute");
expect(capturedDate, "captured date should math the target()").toBe(target());
setDate(p => p - 2 * HOUR);
flush();
expect(timeago(), "absolute formatter should be applied").toBe("absolute");
expect(capturedDate, "captured date should match the target()").toBe(target());

dispose();
});
dispose();
});

it("custom messages", () => {
createRoot(dispose => {
const [date, setDate] = createSignal<number>(Date.now());

const messages: Partial<RelativeFormatMessages> = {
justNow: "NOW",
future: n => `in the next ${n}`,
day: (n, past) => `${n} DAY${n > 1 ? "S" : ""}`,
week: (n, past) => (n === 1 ? "week" : `${n} weeks`),
};

const [timeago] = createTimeAgo(date, {
interval: 0,
messages,
});
const [date, setDate] = createSignal<number>(Date.now());

const messages: Partial<RelativeFormatMessages> = {
justNow: "NOW",
future: n => `in the next ${n}`,
day: (n, _past) => `${n} DAY${n > 1 ? "S" : ""}`,
week: (n, _past) => (n === 1 ? "week" : `${n} weeks`),
};

const { timeago, dispose } = createRoot(dispose => {
const [timeago] = createTimeAgo(date, { interval: 0, messages });
return { timeago, dispose };
});

expect(timeago()).toBe("NOW");
expect(timeago()).toBe("NOW");

setDate(p => p - 3 * MINUTE);
expect(timeago()).toBe("3 minutes ago");
setDate(p => p - 3 * MINUTE);
flush();
expect(timeago()).toBe("3 minutes ago");

setDate(p => p - 2 * DAY);
expect(timeago()).toBe("2 DAYS ago");
setDate(p => p - 2 * DAY);
flush();
expect(timeago()).toBe("2 DAYS ago");

setDate(p => p + 1 * WEEK + 2 * DAY);
expect(timeago()).toBe("in the next week");
setDate(p => p + 1 * WEEK + 2 * DAY);
flush();
expect(timeago()).toBe("in the next week");

setDate(p => p + 1 * WEEK);
expect(timeago()).toBe("in the next 2 weeks");
setDate(p => p + 1 * WEEK);
flush();
expect(timeago()).toBe("in the next 2 weeks");

setDate(p => p + 2 * MONTH);
expect(timeago()).toBe("in the next 2 months");
setDate(p => p + 2 * MONTH);
flush();
expect(timeago()).toBe("in the next 2 months");

setDate(p => p - YEAR - 2 * MONTH);
expect(timeago()).toBe("last year");
setDate(p => p - YEAR - 2 * MONTH);
flush();
expect(timeago()).toBe("last year");

setDate(p => p - YEAR);
expect(timeago()).toBe("2 years ago");
setDate(p => p - YEAR);
flush();
expect(timeago()).toBe("2 years ago");

dispose();
});
dispose();
});
});
Loading