From a6ab4fc06959cb6dcba6a2df9c2007e4d5c93f2c Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:16:08 -0400 Subject: [PATCH] feat: `useMounted` (#22) --- .changeset/fluffy-flies-bathe.md | 5 + .github/workflows/ci.yml | 4 + .../runed/src/lib/functions/box/box.svelte.ts | 34 +++--- .../src/lib/functions/box/box.test.svelte.ts | 2 - .../src/lib/functions/box/new-box.svelte.ts | 114 ------------------ packages/runed/src/lib/functions/index.ts | 3 +- .../src/lib/functions/useMounted/index.ts | 1 + .../functions/useMounted/useMounted.svelte.ts | 16 +++ sites/docs/content/functions/use-mounted.md | 52 ++++++++ sites/docs/src/lib/components/demos/index.ts | 1 + .../lib/components/demos/use-mounted.svelte | 7 ++ sites/docs/src/lib/config/navigation.ts | 5 + 12 files changed, 110 insertions(+), 134 deletions(-) create mode 100644 .changeset/fluffy-flies-bathe.md delete mode 100644 packages/runed/src/lib/functions/box/new-box.svelte.ts create mode 100644 packages/runed/src/lib/functions/useMounted/index.ts create mode 100644 packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts create mode 100644 sites/docs/content/functions/use-mounted.md create mode 100644 sites/docs/src/lib/components/demos/use-mounted.svelte diff --git a/.changeset/fluffy-flies-bathe.md b/.changeset/fluffy-flies-bathe.md new file mode 100644 index 00000000..6242c361 --- /dev/null +++ b/.changeset/fluffy-flies-bathe.md @@ -0,0 +1,5 @@ +--- +"runed": minor +--- + +feat: `useMounted` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17b52637..86f1b852 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,11 @@ on: push: branches: - main + paths-ignore: + - '.changeset/**' pull_request: + paths-ignore: + - '.changeset/**' concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.sha }} diff --git a/packages/runed/src/lib/functions/box/box.svelte.ts b/packages/runed/src/lib/functions/box/box.svelte.ts index 51e87388..85c8d94e 100644 --- a/packages/runed/src/lib/functions/box/box.svelte.ts +++ b/packages/runed/src/lib/functions/box/box.svelte.ts @@ -100,12 +100,12 @@ function boxWith(getter: () => T, setter?: (v: T) => void) { export type BoxFrom = T extends WritableBox - ? WritableBox - : T extends ReadableBox - ? ReadableBox - : T extends Getter - ? ReadableBox - : WritableBox; + ? WritableBox + : T extends ReadableBox + ? ReadableBox + : T extends Getter + ? ReadableBox + : WritableBox; /** * Creates a box from either a static value, a box, or a getter function. @@ -131,16 +131,16 @@ type BoxFlatten> = Expand< }, never > & - RemoveValues< - { - readonly [K in keyof R]: R[K] extends WritableBox - ? never - : R[K] extends ReadableBox - ? T - : never; - }, - never - > + RemoveValues< + { + readonly [K in keyof R]: R[K] extends WritableBox + ? never + : R[K] extends ReadableBox + ? T + : never; + }, + never + > > & RemoveValues< { @@ -193,7 +193,7 @@ function boxFlatten>(boxes: R): BoxFlatten * const countReadonly = box.readonly(count) // ReadableBox */ function toReadonlyBox(b: ReadableBox): ReadableBox { - if (!box.isWritableBox(b)) return b + if (!box.isWritableBox(b)) return b; return { [BoxSymbol]: true, diff --git a/packages/runed/src/lib/functions/box/box.test.svelte.ts b/packages/runed/src/lib/functions/box/box.test.svelte.ts index 0b967870..bf7a820f 100644 --- a/packages/runed/src/lib/functions/box/box.test.svelte.ts +++ b/packages/runed/src/lib/functions/box/box.test.svelte.ts @@ -141,7 +141,6 @@ describe("box.readonly", () => { }); }); - describe("box types", () => { test("box without initial value", () => { const count = box(); @@ -189,4 +188,3 @@ describe("box types", () => { expectTypeOf(readonlyCount).not.toMatchTypeOf>(); }); }); - diff --git a/packages/runed/src/lib/functions/box/new-box.svelte.ts b/packages/runed/src/lib/functions/box/new-box.svelte.ts deleted file mode 100644 index f988b324..00000000 --- a/packages/runed/src/lib/functions/box/new-box.svelte.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable ts/consistent-type-definitions */ - -import type { Getter, Setter } from "$lib/internal/types.js"; - -const BoxSymbol = Symbol('box'); - -interface Box { - [BoxSymbol]: true - value: T, -} - -interface ReadonlyBox extends Box { - readonly value: T, -} - -export function box(): Box -export function box(initial: T): Box -export function box(initial?: T) { - let value = $state(initial) - - return { - get value() { - return value - }, - set value(v) { - value = v; - }, - [BoxSymbol]: true - } -} - -box.isBox = function isBox(value: unknown): value is Box { - return typeof value === 'object' && value !== null && (BoxSymbol in value); -} - -function isWritable(obj: T, key: keyof T) { - const desc = Object.getOwnPropertyDescriptor(obj, key) || {} - return Boolean(desc.writable) -} - -box.isReadonly = function boxIsReadonly(value: unknown): value is ReadonlyBox { - return box.isBox(value) && !isWritable(value, 'value') -} - -box.isWritable = function boxIsWritable(value: unknown): value is Box { - return box.isBox(value) && isWritable(value, 'value') -} - -function boxFrom(value: T): T extends ReadonlyBox ? T : Box { - // eslint-disable-next-line ts/no-explicit-any -- I'm fucking something up here - if (box.isBox(value)) return value as any; - // eslint-disable-next-line ts/no-explicit-any - return box(value) as any; -} -box.from = boxFrom - -function boxWith(get: Getter): ReadonlyBox -function boxWith(get: Getter, set: Setter): Box -function boxWith(get: Getter, set?: Setter) { - const value = $derived.by(get) - - if (set) { - return { - get value() { - return value; - }, - set value(v) { - set(v) - }, - [BoxSymbol]: true - } - } else { - return { - get value() { - return value; - }, - [BoxSymbol]: true - } - } -} -box.with = boxWith - -// Usage examples -function acceptsReadonly(disabled: boolean | ReadonlyBox) { - const disabledBox = box.from(disabled); - // @ts-expect-error -- testing - disabledBox.value = false - if (box.isWritable(disabledBox)) { - disabledBox.value = true - } -} - -function acceptsMutable(disabled: boolean | Box) { - const disabledBox = box.from(disabled); - disabledBox.value = false -} - -let disabled = $state(false); - -acceptsReadonly( - box.with(() => disabled) -); - -acceptsReadonly( - box(false) -); - -acceptsMutable( - box.with(() => disabled, (v) => disabled = v) -) - -acceptsMutable( - box(false) -) \ No newline at end of file diff --git a/packages/runed/src/lib/functions/index.ts b/packages/runed/src/lib/functions/index.ts index eb795d2b..b9550f29 100644 --- a/packages/runed/src/lib/functions/index.ts +++ b/packages/runed/src/lib/functions/index.ts @@ -1,5 +1,6 @@ +export * from "./box/index.js"; export * from "./useActiveElement/index.js"; export * from "./useDebounce/index.js"; export * from "./useElementSize/index.js"; export * from "./useEventListener/index.js"; -export * from "./box/index.js"; +export * from "./useMounted/index.js"; diff --git a/packages/runed/src/lib/functions/useMounted/index.ts b/packages/runed/src/lib/functions/useMounted/index.ts new file mode 100644 index 00000000..9375b664 --- /dev/null +++ b/packages/runed/src/lib/functions/useMounted/index.ts @@ -0,0 +1 @@ +export * from "./useMounted.svelte.js"; diff --git a/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts b/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts new file mode 100644 index 00000000..6d7d7f54 --- /dev/null +++ b/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts @@ -0,0 +1,16 @@ +import { untrack } from "svelte"; +import { type ReadableBox, box } from "../box/box.svelte.js"; + +/** + * Returns a box with the mounted state of the component + * that invokes this function. + */ +export function useMounted(): ReadableBox { + const isMounted = box(false); + + $effect(() => { + untrack(() => (isMounted.value = true)); + }); + + return box.readonly(isMounted); +} diff --git a/sites/docs/content/functions/use-mounted.md b/sites/docs/content/functions/use-mounted.md new file mode 100644 index 00000000..44777133 --- /dev/null +++ b/sites/docs/content/functions/use-mounted.md @@ -0,0 +1,52 @@ +--- +title: useMounted +description: A function that returns the mounted state of the component it's called in. +--- + + + +## Demo + + + +## Usage + +```svelte + +``` + +Which is a shorthand for one of the following: + +```svelte + +``` + +or + +```svelte + +``` diff --git a/sites/docs/src/lib/components/demos/index.ts b/sites/docs/src/lib/components/demos/index.ts index bb2f1433..de1b4c1b 100644 --- a/sites/docs/src/lib/components/demos/index.ts +++ b/sites/docs/src/lib/components/demos/index.ts @@ -2,3 +2,4 @@ export { default as UseActiveElementDemo } from "./use-active-element.svelte"; export { default as UseDebounceDemo } from "./use-debounce.svelte"; export { default as UseElementSizeDemo } from "./use-element-size.svelte"; export { default as UseEventListenerDemo } from "./use-event-listener.svelte"; +export { default as UseMountedDemo } from "./use-mounted.svelte"; diff --git a/sites/docs/src/lib/components/demos/use-mounted.svelte b/sites/docs/src/lib/components/demos/use-mounted.svelte new file mode 100644 index 00000000..0d23b70f --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-mounted.svelte @@ -0,0 +1,7 @@ + + +

{isMounted.value ? "mounted" : "not mounted"}

diff --git a/sites/docs/src/lib/config/navigation.ts b/sites/docs/src/lib/config/navigation.ts index 64246ff8..73f3fc4e 100644 --- a/sites/docs/src/lib/config/navigation.ts +++ b/sites/docs/src/lib/config/navigation.ts @@ -86,6 +86,11 @@ export const navigation: Navigation = { href: "/docs/functions/use-event-listener", items: [], }, + { + title: "useMounted", + href: "/docs/functions/use-mounted", + items: [], + }, ], }, ],