Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes/improvements/docs #157

Merged
merged 4 commits into from
Dec 2, 2024
Merged
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
5 changes: 5 additions & 0 deletions .changeset/clean-foxes-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runed": patch
---

widen the type of element getter args to `HTMLElement | undefined | null`
5 changes: 5 additions & 0 deletions .changeset/dull-moons-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runed": patch
---

ensure explicit return types for utilities
5 changes: 5 additions & 0 deletions .changeset/tricky-buttons-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runed": patch
---

fix: `isIdle.current` should be readonly
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class AnimationFrames {
});
}

#loop = (timestamp: DOMHighResTimeStamp) => {
#loop = (timestamp: DOMHighResTimeStamp): void => {
if (!this.#running) return;

if (this.#previousTimestamp === null) {
Expand All @@ -76,28 +76,28 @@ export class AnimationFrames {
this.#frame = requestAnimationFrame(this.#loop);
};

start = () => {
start = (): void => {
this.#running = true;
this.#previousTimestamp = 0;
this.#frame = requestAnimationFrame(this.#loop);
};

stop = () => {
stop = (): void => {
if (!this.#frame) return;
this.#running = false;
cancelAnimationFrame(this.#frame);
this.#frame = null;
};

toggle = () => {
toggle = (): void => {
this.#running ? this.stop() : this.start();
};

get fps() {
get fps(): number {
return !this.#running ? 0 : this.#fps;
}

get running() {
get running(): boolean {
return this.#running;
}
}
10 changes: 5 additions & 5 deletions packages/runed/src/lib/utilities/Debounced/Debounced.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { noop } from "$lib/internal/utils/function.js";
/**
* A wrapper over {@link useDebounce} that creates a debounced state.
* It takes a "getter" function which returns the state you want to debounce.
* Everytime this state changes a timer (re)starts, the length of which is
* Every time this state changes a timer (re)starts, the length of which is
* configurable with the `wait` arg. When the timer ends the `current` value
* is updated.
*
Expand Down Expand Up @@ -56,15 +56,15 @@ export class Debounced<T> {
/**
* Cancel the latest timer.
*/
cancel() {
cancel = (): void => {
this.#debounceFn.cancel();
}
};

/**
* Set the `current` value without waiting.
*/
setImmediately(v: T) {
setImmediately = (v: T): void => {
this.cancel();
this.#current = v;
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class ElementRect {
left: 0,
});

constructor(node: MaybeGetter<HTMLElement | undefined>, options: ElementRectOptions = {}) {
constructor(node: MaybeGetter<HTMLElement | undefined | null>, options: ElementRectOptions = {}) {
this.#rect = {
width: options.initialRect?.width ?? 0,
height: options.initialRect?.height ?? 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class ElementSize {
});

constructor(
node: MaybeGetter<HTMLElement | undefined>,
node: MaybeGetter<HTMLElement | undefined | null>,
options: ElementSizeOptions = { box: "border-box" }
) {
this.#size = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export type Transition<StatesT extends string, EventsT extends string> = {
* @see {@link https://runed.dev/docs/utilities/finite-state-machine}
*/
export class FiniteStateMachine<StatesT extends string, EventsT extends string> {
#current = $state() as StatesT;
#current: StatesT = $state()!;
readonly states: Transition<StatesT, EventsT>;
#timeout: Partial<Record<EventsT, NodeJS.Timeout>> = {};

Expand Down Expand Up @@ -93,16 +93,16 @@ export class FiniteStateMachine<StatesT extends string, EventsT extends string>
}

/** Triggers a new event and returns the new state. */
send(event: EventsT, ...args: unknown[]) {
send = (event: EventsT, ...args: unknown[]): StatesT => {
const newState = this.#dispatch(event, ...args);
if (newState && newState !== this.#current) {
this.#transition(newState as StatesT, event, args);
}
return this.#current;
}
};

/** Debounces the triggering of an event. */
async debounce(wait: number = 500, event: EventsT, ...args: unknown[]): Promise<StatesT> {
debounce = async (wait: number = 500, event: EventsT, ...args: unknown[]): Promise<StatesT> => {
if (this.#timeout[event]) {
clearTimeout(this.#timeout[event]);
}
Expand All @@ -112,10 +112,10 @@ export class FiniteStateMachine<StatesT extends string, EventsT extends string>
resolve(this.send(event, ...args));
}, wait);
});
}
};

/** The current state. */
get current() {
get current(): StatesT {
return this.#current;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import type { MaybeGetter } from "$lib/internal/types.js";
* @see {@link https://runed.dev/docs/utilities/is-focus-within}
*/
export class IsFocusWithin {
#node: MaybeGetter<HTMLElement | undefined>;
#node: MaybeGetter<HTMLElement | undefined | null>;
#target = $derived.by(() => extract(this.#node));

constructor(node: MaybeGetter<HTMLElement | undefined>) {
constructor(node: MaybeGetter<HTMLElement | undefined | null>) {
this.#node = node;
}

Expand Down
12 changes: 8 additions & 4 deletions packages/runed/src/lib/utilities/IsIdle/IsIdle.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const DEFAULT_OPTIONS = {
* @see {@link https://runed.dev/docs/utilities/is-idle}
*/
export class IsIdle {
current = $state(false);
#current: boolean = $state(false);
#lastActive = $state(Date.now());

constructor(_options?: IsIdleOptions) {
Expand All @@ -63,19 +63,19 @@ export class IsIdle {
const timeout = $derived(extract(options.timeout));
const events = $derived(extract(options.events));
const detectVisibilityChanges = $derived(extract(options.detectVisibilityChanges));
this.current = options.initialState;
this.#current = options.initialState;

const debouncedReset = useDebounce(
() => {
this.current = true;
this.#current = true;
},
() => timeout
);

debouncedReset();

const handleActivity = () => {
this.current = false;
this.#current = false;
this.#lastActive = Date.now();
debouncedReset();
};
Expand All @@ -101,4 +101,8 @@ export class IsIdle {
get lastActive(): number {
return this.#lastActive;
}

get current(): boolean {
return this.#current;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@ function getValueFromStorage<T>({
storage: Storage | null;
serializer: Serializer<T>;
}): GetValueFromStorageResult<T> {
if (!storage) {
return { found: false, value: null };
}
if (!storage) return { found: false, value: null };

const value = storage.getItem(key);
if (value === null) {
return { found: false, value: null };
}

if (value === null) return { found: false, value: null };

try {
return {
Expand All @@ -57,9 +54,7 @@ function setValueToStorage<T>({
storage: Storage | null;
serializer: Serializer<T>;
}) {
if (!storage) {
return;
}
if (!storage) return;

try {
storage.setItem(key, serializer.serialize(value));
Expand All @@ -71,9 +66,7 @@ function setValueToStorage<T>({
type StorageType = "local" | "session";

function getStorage(storageType: StorageType): Storage | null {
if (typeof window === "undefined") {
return null;
}
if (typeof window === "undefined") return null;

const storageByStorageType = {
local: localStorage,
Expand Down Expand Up @@ -101,7 +94,7 @@ type PersistedStateOptions<T> = {
* @see {@link https://runed.dev/docs/utilities/persisted-state}
*/
export class PersistedState<T> {
#current = $state() as T;
#current: T = $state()!;
#key: string;
#storage: Storage | null;
#serializer: Serializer<T>;
Expand Down Expand Up @@ -135,18 +128,13 @@ export class PersistedState<T> {
});

$effect(() => {
if (!syncTabs) {
return;
}

if (!syncTabs) return;
return addEventListener(window, "storage", this.#handleStorageEvent.bind(this));
});
}

#handleStorageEvent(event: StorageEvent) {
if (event.key !== this.#key || !this.#storage) {
return;
}
if (event.key !== this.#key || !this.#storage) return;

const valueFromStorage = getValueFromStorage({
key: this.#key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { addEventListener } from "$lib/internal/utils/event.js";
* @see {@link https://runed.dev/docs/utilities/pressed-keys}
*/
export class PressedKeys {
#pressedKeys: string[] = $state([]);
#pressedKeys = $state<string[]>([]);

constructor() {
$effect(() => {
Expand All @@ -32,10 +32,10 @@ export class PressedKeys {
});
}

has(...keys: string[]): boolean {
has = (...keys: string[]): boolean => {
const normalizedKeys = keys.map((key) => key.toLowerCase());
return normalizedKeys.every((key) => this.#pressedKeys.includes(key));
}
};

get all(): string[] {
return this.#pressedKeys;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Getter } from "$lib/internal/types.js";
* @see {@link https://runed.dev/docs/utilities/previous}
*/
export class Previous<T> {
#previous: T | undefined = $state();
#previous: T | undefined = $state(undefined);
#curr?: T;

constructor(getter: Getter<T>) {
Expand Down
2 changes: 1 addition & 1 deletion packages/runed/src/lib/utilities/Store/Store.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function isWritable(t: Readable<unknown>): t is Writable<unknown> {
}

export class Store<T extends Readable<unknown>> {
#current = $state() as ReadableValue<T>;
#current: ReadableValue<T> = $state()!;
#store: T;

constructor(store: T) {
Expand Down
18 changes: 17 additions & 1 deletion sites/docs/src/content/utilities/active-element.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
---
title: activeElement
description: An object holding the currently active element.
description: Track and access the currently focused DOM element
category: Elements
---

<script>
import Demo from '$lib/components/demos/active-element.svelte';
</script>

`activeElement` provides reactive access to the currently focused DOM element in your application,
similar to `document.activeElement` but with reactive updates.

- Updates synchronously with DOM focus changes
- Returns `null` when no element is focused
- Safe to use with SSR (Server-Side Rendering)
- Lightweight alternative to manual focus tracking

## Demo

<Demo />
Expand All @@ -24,3 +32,11 @@ import Demo from '$lib/components/demos/active-element.svelte';
{activeElement.current?.localName ?? "No active element found"}
</p>
```

## Type Definition

```ts
interface ActiveElement {
readonly current: Element | null;
}
```
21 changes: 5 additions & 16 deletions sites/docs/src/content/utilities/animation-frames.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
---
title: AnimationFrames
description:
A wrapper over `requestAnimationFrame`, with controls for limiting FPS, and information about the
current frame.
description: A wrapper for requestAnimationFrame with FPS control and frame metrics
category: Browser
---

<script>
import Demo from '$lib/components/demos/animation-frames.svelte';
</script>

`AnimationFrames` provides a declarative API over the browser's
[`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame),
offering FPS limiting capabilities and frame metrics while handling cleanup automatically.

## Demo

<Demo />

## Description

`AnimationFrames` wraps over
[`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame).
While it is not necessary to use it to use `requestAnimationFrame`, it removes some of the
boilerplate, and adds common utilities for it.

- Automatically interrupts the requestAnimationFrame loop once the component is unmounted
- Lets you set an FPS limit
- Lets you get information about the current frame, such as its current timestamp, and the
difference in ms between the last frame and the current one
- Returns information about current FPS

## Usage

```svelte
Expand Down
Loading