INIT
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Hunter Johnston <https://github.com/huntabyte>
|
||||
Copyright (c) 2024 Thomas G. Lopes <https://github.com/tglide>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+449
@@ -0,0 +1,449 @@
|
||||
# Svelte Toolbelt
|
||||
|
||||
Utilities for Svelte 5 that I find useful and will use in the various projects I work on. It's maintained by me, for me.
|
||||
|
||||
For more robust and feature-rich utilities, I recommend checking out/using [runed](https://runed.dev).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install svelte-toolbelt
|
||||
```
|
||||
|
||||
## Box
|
||||
|
||||
### `box`
|
||||
|
||||
Initializes a writable boxed state.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { box } from "runed";
|
||||
const count = box(0);
|
||||
</script>
|
||||
|
||||
<button onclick={() => count.current++}>
|
||||
clicks: {count.current}
|
||||
</button>
|
||||
```
|
||||
|
||||
### `box.with`
|
||||
|
||||
Creates reactive state using getter and setter functions. If a setter function is provided, the box
|
||||
is writable. If not, the box is readonly.
|
||||
|
||||
Useful for passing synced reactive values across boundaries.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { type WritableBox, box } from "runed";
|
||||
function useCounter(count: WritableBox<number>) {
|
||||
return {
|
||||
increment() {
|
||||
count.current++;
|
||||
},
|
||||
// We pass a box that doubles the count value
|
||||
double: box.with(() => count.current * 2)
|
||||
};
|
||||
}
|
||||
let count = $state(0);
|
||||
// We pass count to box.with so it stays in sync
|
||||
const { double, increment } = useCounter(
|
||||
box.with(
|
||||
() => count.current,
|
||||
(v) => (count = v)
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<button onclick={increment}>
|
||||
clicks: {count}
|
||||
double: {double.current}
|
||||
</button>
|
||||
```
|
||||
|
||||
### `box.from`
|
||||
|
||||
Creates a box from an existing box, a getter function, or a static value.
|
||||
|
||||
Useful for receiving arguments that may or may not be reactive.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { box } from "runed";
|
||||
function useCounter(_count: WritableBox<number> | number) {
|
||||
const count = box.from(_count);
|
||||
return {
|
||||
count,
|
||||
increment() {
|
||||
count.current++;
|
||||
},
|
||||
// We pass a box that doubles the count value
|
||||
double: box.with(() => count.current * 2)
|
||||
};
|
||||
}
|
||||
const counter1 = useCounter(1);
|
||||
console.log(counter1.count.current); // 1
|
||||
console.log(counter1.double.current); // 2
|
||||
const counter2 = useCounter(box(2));
|
||||
console.log(counter2.count.current); // 2
|
||||
console.log(counter2.double.current); // 4
|
||||
function useDouble(_count: number | (() => number) | ReadableBox<number>) {
|
||||
const count = box.from(_count);
|
||||
return box.with(() => count.current * 2);
|
||||
}
|
||||
const double1 = useDouble(1);
|
||||
console.log(double1.current); // 2
|
||||
const double2 = useDouble(box(2));
|
||||
console.log(double2.current); // 4
|
||||
const double3 = useDouble(() => counter1.count.current);
|
||||
console.log(double3.current); // 2
|
||||
</script>
|
||||
```
|
||||
|
||||
### `box.flatten`
|
||||
|
||||
Transforms any boxes within an object to reactive properties, removing the need to access each
|
||||
property with `.current`.
|
||||
|
||||
```ts
|
||||
const count = box(1);
|
||||
const flat = box.flatten({
|
||||
count,
|
||||
double: box.with(() => count.current * 2),
|
||||
increment() {
|
||||
count.current++;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(flat.count); // 1
|
||||
console.log(flat.double); // 2
|
||||
flat.increment();
|
||||
console.log(flat.count); // 2
|
||||
```
|
||||
|
||||
### `box.readonly`
|
||||
|
||||
Creates a readonly box from a writable box that remains in sync with the original box.
|
||||
|
||||
```ts
|
||||
const count = box(1);
|
||||
const readonlyCount = box.readonly(count);
|
||||
console.log(readonlyCount.current); // 1
|
||||
count.current++;
|
||||
console.log(readonlyCount.current); // 2
|
||||
|
||||
readonlyCount.current = 3; // Error: Cannot assign to read only property 'value' of object
|
||||
```
|
||||
|
||||
### `box.isBox`
|
||||
|
||||
Checks if a value is a `Box`.
|
||||
|
||||
```ts
|
||||
const count = box(1);
|
||||
console.log(box.isBox(count)); // true
|
||||
console.log(box.isBox(1)); // false
|
||||
```
|
||||
|
||||
### `box.isWritableBox`
|
||||
|
||||
Checks if a value is a `WritableBox`.
|
||||
|
||||
```ts
|
||||
const count = box(1);
|
||||
const double = box.with(() => count.current * 2);
|
||||
console.log(box.isWritableBox(count)); // true
|
||||
console.log(box.isWritableBox(double)); // false
|
||||
```
|
||||
|
||||
### `unbox`
|
||||
|
||||
Unboxes the value from a box.
|
||||
|
||||
```ts
|
||||
const count = box(1);
|
||||
const double = box.with(() => count.current * 2);
|
||||
console.log(unbox(double)); // 2
|
||||
```
|
||||
|
||||
## Utils
|
||||
|
||||
### `afterSleep`
|
||||
|
||||
Executes a callback after a specified number of milliseconds.
|
||||
|
||||
```ts
|
||||
afterSleep(1000, () => console.log("Hello, world!"));
|
||||
```
|
||||
|
||||
### `afterTick`
|
||||
|
||||
Executes a callback after the next tick.
|
||||
|
||||
```ts
|
||||
afterTick(() => console.log("Hello, world!"));
|
||||
```
|
||||
|
||||
### `composeHandlers`
|
||||
|
||||
Composes event handlers into a single function that can be called with an event.
|
||||
|
||||
If the previous handler cancels the event using `event.preventDefault()`, the handlers
|
||||
that follow will not be called.
|
||||
|
||||
```ts
|
||||
import { composeHandlers } from "svelte-toolbelt";
|
||||
const handler1 = () => console.log("Handler 1");
|
||||
const handler2 = () => console.log("Handler 2");
|
||||
const composedHandler = composeHandlers(handler1, handler2);
|
||||
const event = new MouseEvent("click", { cancelable: true });
|
||||
console.log(composedHandler(event)); // Handler 1, Handler 2
|
||||
```
|
||||
|
||||
### `cssToStyleObj`
|
||||
|
||||
Converts a CSS string to a style object.
|
||||
|
||||
```ts
|
||||
const css = "color: red; font-size: 16px;";
|
||||
const styleObj = cssToStyleObj(css);
|
||||
console.log(styleObj); // { color: "red", fontSize: "16px" }
|
||||
```
|
||||
|
||||
### `executeCallbacks`
|
||||
|
||||
Executes an array of callback functions with the same arguments.
|
||||
|
||||
```ts
|
||||
const callback1 = () => console.log("Callback 1");
|
||||
const callback2 = () => console.log("Callback 2");
|
||||
console.log(executeCallbacks(callback1, callback2)); // Callback 1, Callback 2
|
||||
```
|
||||
|
||||
### `addEventListener`
|
||||
|
||||
Adds an event listener to the specified target element(s) for the given event(s), and returns a function to remove it.
|
||||
|
||||
```ts
|
||||
import { addEventListener } from "svelte-toolbelt";
|
||||
const target = document.getElementById("my-element");
|
||||
const event = "click";
|
||||
const handler = () => console.log("Clicked!");
|
||||
const removeListener = addEventListener(target, event, handler);
|
||||
|
||||
// Later, remove the listener
|
||||
removeListener();
|
||||
```
|
||||
|
||||
### `mergeProps`
|
||||
|
||||
Merges props into a single object.
|
||||
|
||||
```ts
|
||||
import { mergeProps } from "svelte-toolbelt";
|
||||
const props1 = { a: 1 };
|
||||
const props2 = { b: 2 };
|
||||
const result = mergeProps(props1, props2);
|
||||
console.log(result); // { a: 1, b: 2 }
|
||||
```
|
||||
|
||||
#### Event Handlers
|
||||
|
||||
Event handlers are chained in the order they're passed. If a handler calls `event.preventDefault()`, subsequent handlers in the chain are not executed.
|
||||
|
||||
```ts
|
||||
const props1 = { onclick: (e: MouseEvent) => console.log("First click") };
|
||||
const props2 = { onclick: (e: MouseEvent) => console.log("Second click") };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2);
|
||||
mergedProps.onclick(new MouseEvent("click")); // Logs: "First click" then "Second click"
|
||||
```
|
||||
|
||||
If `preventDefault()` is called:
|
||||
|
||||
```ts
|
||||
const props1 = { onclick: (e: MouseEvent) => console.log("First click") };
|
||||
const props2 = {
|
||||
onclick: (e: MouseEvent) => {
|
||||
console.log("Second click");
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
const props3 = { onclick: (e: MouseEvent) => console.log("Third click") };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2, props3);
|
||||
mergedProps.onclick(new MouseEvent("click")); // Logs: "First click" then "Second click" only
|
||||
```
|
||||
|
||||
Since `props2` called `event.preventDefault()`, `props3`'s `onclick` handler will not be called.
|
||||
|
||||
#### Non-Event Handler Functions
|
||||
|
||||
Non-event handler functions are also chained, but without the ability to prevent subsequent functions from executing:
|
||||
|
||||
```ts
|
||||
const props1 = { doSomething: () => console.log("Action 1") };
|
||||
const props2 = { doSomething: () => console.log("Action 2") };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2);
|
||||
mergedProps.doSomething(); // Logs: "Action 1" then "Action 2"
|
||||
```
|
||||
|
||||
#### Classes
|
||||
|
||||
Class names are merged using [`clsx`](https://www.npmjs.com/package/clsx):
|
||||
|
||||
```ts
|
||||
const props1 = { class: "text-lg font-bold" };
|
||||
const props2 = { class: ["bg-blue-500", "hover:bg-blue-600"] };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2);
|
||||
console.log(mergedProps.class); // "text-lg font-bold bg-blue-500 hover:bg-blue-600"
|
||||
```
|
||||
|
||||
#### Styles
|
||||
|
||||
Style objects and strings are merged, with later properties overriding earlier ones:
|
||||
|
||||
```ts
|
||||
const props1 = { style: { color: "red", fontSize: "16px" } };
|
||||
const props2 = { style: "background-color: blue; font-weight: bold;" };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2);
|
||||
console.log(mergedProps.style);
|
||||
// "color: red; font-size: 16px; background-color: blue; font-weight: bold;"
|
||||
```
|
||||
|
||||
```ts
|
||||
import { mergeProps } from "bits-ui";
|
||||
|
||||
const props1 = { style: "--foo: red" };
|
||||
const props2 = { style: { "--foo": "green", color: "blue" } };
|
||||
|
||||
const mergedProps = mergeProps(props1, props2);
|
||||
|
||||
console.log(mergedProps.style); // "--foo: green; color: blue;"
|
||||
```
|
||||
|
||||
### `onDestroyEffect`
|
||||
|
||||
Executes a callback when a component is destroyed.
|
||||
|
||||
### `onMountEffect`
|
||||
|
||||
Executes a callback when a component is mounted.
|
||||
|
||||
### `styleToString`
|
||||
|
||||
Converts a style object to a CSS string.
|
||||
|
||||
```ts
|
||||
const style = { color: "red", fontSize: "16px" };
|
||||
const css = styleToString(style);
|
||||
console.log(css); // "color: red; font-size: 16px;"
|
||||
```
|
||||
|
||||
### `srOnlyStyles`
|
||||
|
||||
An object of styles that can be used to hide content from the DOM but still be accessible to screen readers.
|
||||
|
||||
```ts
|
||||
export const srOnlyStyles: StyleProperties = {
|
||||
position: "absolute",
|
||||
width: "1px",
|
||||
height: "1px",
|
||||
padding: "0",
|
||||
margin: "-1px",
|
||||
overflow: "hidden",
|
||||
clip: "rect(0, 0, 0, 0)",
|
||||
whiteSpace: "nowrap",
|
||||
borderWidth: "0",
|
||||
transform: "translateX(-100%)"
|
||||
};
|
||||
```
|
||||
|
||||
### `srOnlyStylesString`
|
||||
|
||||
A string representation of `srOnlyStyles`.
|
||||
|
||||
### `useRefById`
|
||||
|
||||
Finds the node with the given boxed `id` and sets it to the boxed `ref`. Reactive using `$effect` to ensure when the `id` or `deps` change, an update is triggered and the node is re-found.
|
||||
|
||||
#### Props
|
||||
|
||||
```ts
|
||||
type UseRefByIdProps = {
|
||||
/**
|
||||
* The ID of the node to find.
|
||||
*/
|
||||
id: Box<string>;
|
||||
|
||||
/**
|
||||
* The ref to set the node to.
|
||||
*/
|
||||
ref: WritableBox<HTMLElement | null>;
|
||||
|
||||
/**
|
||||
* A reactive condition that will cause the node to be set.
|
||||
*/
|
||||
deps?: Getter<unknown>;
|
||||
|
||||
/**
|
||||
* A callback fired when the ref changes.
|
||||
*/
|
||||
onRefChange?: (node: HTMLElement | null) => void;
|
||||
|
||||
/**
|
||||
* A function that returns the root node to search for the element by ID.
|
||||
* Defaults to `() => (typeof document !== "undefined" ? document : undefined)
|
||||
*/
|
||||
getRootNode?: Getter<Document | ShadowRoot | undefined>;
|
||||
};
|
||||
```
|
||||
|
||||
### `useOnChange`
|
||||
|
||||
A simple helper function to react to changes to reactive state. This is useful for syncing a read-only dependency that may change with some writable state in your app.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { useOnChange } from "svelte-toolbelt";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let myData = $state(data);
|
||||
|
||||
useOnChange(
|
||||
() => myData,
|
||||
(newData) => (myData = newData)
|
||||
);
|
||||
</script>
|
||||
```
|
||||
|
||||
### `attachRef`
|
||||
|
||||
Creates a Svelte attachment that attaches a DOM element to a ref.
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { attachRef } from "svelte-toolbelt";
|
||||
|
||||
const ref = box<Element | null>(null);
|
||||
</script>
|
||||
|
||||
<div {...attachRef(ref)}>Content</div>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { attachRef } from "svelte-toolbelt";
|
||||
|
||||
let ref = $state<HTMLElement | null>(null);
|
||||
</script>
|
||||
|
||||
<div {...attachRef((node) => (ref = node))}>Content</div>
|
||||
```
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import type { Getter, MaybeBoxOrGetter } from "../types.js";
|
||||
export declare const BoxSymbol: unique symbol;
|
||||
export declare const isWritableSymbol: unique symbol;
|
||||
export type ReadableBox<T> = {
|
||||
readonly [BoxSymbol]: true;
|
||||
readonly current: T;
|
||||
};
|
||||
export type WritableBox<T> = ReadableBox<T> & {
|
||||
readonly [isWritableSymbol]: true;
|
||||
current: T;
|
||||
};
|
||||
/**
|
||||
* Creates a readonly box
|
||||
*
|
||||
* @param getter Function to get the value of the box
|
||||
* @returns A box with a `current` property whose value is the result of the getter.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function boxWith<T>(getter: () => T): ReadableBox<T>;
|
||||
/**
|
||||
* Creates a writable box
|
||||
*
|
||||
* @param getter Function to get the value of the box
|
||||
* @param setter Function to set the value of the box
|
||||
* @returns A box with a `current` property which can be set to a new value.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function boxWith<T>(getter: () => T, setter: (v: T) => void): WritableBox<T>;
|
||||
/**
|
||||
* @returns Whether the value is a Box
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function isBox(value: unknown): value is ReadableBox<unknown>;
|
||||
/**
|
||||
* @returns Whether the value is a WritableBox
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function isWritableBox(value: unknown): value is WritableBox<unknown>;
|
||||
/**
|
||||
* Creates a box from either a static value, a box, or a getter function.
|
||||
* Useful when you want to receive any of these types of values and generate a boxed version of it.
|
||||
*
|
||||
* @returns A box with a `current` property whose value.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function boxFrom<T>(value: T | WritableBox<T>): WritableBox<T>;
|
||||
declare function boxFrom<T>(value: ReadableBox<T>): ReadableBox<T>;
|
||||
declare function boxFrom<T>(value: Getter<T>): ReadableBox<T>;
|
||||
declare function boxFrom<T>(value: MaybeBoxOrGetter<T>): ReadableBox<T>;
|
||||
declare function boxFrom<T>(value: T): WritableBox<T>;
|
||||
type GetKeys<T, U> = {
|
||||
[K in keyof T]: T[K] extends U ? K : never;
|
||||
}[keyof T];
|
||||
type RemoveValues<T, U> = Omit<T, GetKeys<T, U>>;
|
||||
type BoxFlatten<R extends Record<string, unknown>> = Expand<RemoveValues<{
|
||||
[K in keyof R]: R[K] extends WritableBox<infer T> ? T : never;
|
||||
}, never> & RemoveValues<{
|
||||
readonly [K in keyof R]: R[K] extends WritableBox<infer _> ? never : R[K] extends ReadableBox<infer T> ? T : never;
|
||||
}, never>> & RemoveValues<{
|
||||
[K in keyof R]: R[K] extends ReadableBox<infer _> ? never : R[K];
|
||||
}, never>;
|
||||
/**
|
||||
* Function that gets an object of boxes, and returns an object of reactive values
|
||||
*
|
||||
* @example
|
||||
* const count = box(0)
|
||||
* const flat = box.flatten({ count, double: box.with(() => count.current) })
|
||||
* // type of flat is { count: number, readonly double: number }
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function boxFlatten<R extends Record<string, unknown>>(boxes: R): BoxFlatten<R>;
|
||||
/**
|
||||
* Function that converts a box to a readonly box.
|
||||
*
|
||||
* @example
|
||||
* const count = box(0) // WritableBox<number>
|
||||
* const countReadonly = box.readonly(count) // ReadableBox<number>
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function toReadonlyBox<T>(b: ReadableBox<T>): ReadableBox<T>;
|
||||
/**
|
||||
* Creates a writable box.
|
||||
*
|
||||
* @returns A box with a `current` property which can be set to a new value.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function simpleBox<T>(): WritableBox<T | undefined>;
|
||||
/**
|
||||
* Creates a writable box with an initial value.
|
||||
*
|
||||
* @param initialValue The initial value of the box.
|
||||
* @returns A box with a `current` property which can be set to a new value.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
declare function simpleBox<T>(initialValue: T): WritableBox<T>;
|
||||
export { boxWith, isBox, isWritableBox, boxFrom, boxFlatten, toReadonlyBox, simpleBox };
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { isFunction, isObject } from "../utils/is.js";
|
||||
export const BoxSymbol = Symbol("box");
|
||||
export const isWritableSymbol = Symbol("is-writable");
|
||||
function boxWith(getter, setter) {
|
||||
const derived = $derived.by(getter);
|
||||
if (setter) {
|
||||
return {
|
||||
[BoxSymbol]: true,
|
||||
[isWritableSymbol]: true,
|
||||
get current() {
|
||||
return derived;
|
||||
},
|
||||
set current(v) {
|
||||
setter(v);
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
[BoxSymbol]: true,
|
||||
get current() {
|
||||
return getter();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @returns Whether the value is a Box
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
function isBox(value) {
|
||||
return isObject(value) && BoxSymbol in value;
|
||||
}
|
||||
/**
|
||||
* @returns Whether the value is a WritableBox
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
function isWritableBox(value) {
|
||||
return isBox(value) && isWritableSymbol in value;
|
||||
}
|
||||
function boxFrom(value) {
|
||||
if (isBox(value))
|
||||
return value;
|
||||
if (isFunction(value))
|
||||
return boxWith(value);
|
||||
return simpleBox(value);
|
||||
}
|
||||
/**
|
||||
* Function that gets an object of boxes, and returns an object of reactive values
|
||||
*
|
||||
* @example
|
||||
* const count = box(0)
|
||||
* const flat = box.flatten({ count, double: box.with(() => count.current) })
|
||||
* // type of flat is { count: number, readonly double: number }
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
function boxFlatten(boxes) {
|
||||
return Object.entries(boxes).reduce((acc, [key, b]) => {
|
||||
if (!isBox(b)) {
|
||||
return Object.assign(acc, { [key]: b });
|
||||
}
|
||||
if (isWritableBox(b)) {
|
||||
Object.defineProperty(acc, key, {
|
||||
get() {
|
||||
return b.current;
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
set(v) {
|
||||
b.current = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
Object.defineProperty(acc, key, {
|
||||
get() {
|
||||
return b.current;
|
||||
}
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
/**
|
||||
* Function that converts a box to a readonly box.
|
||||
*
|
||||
* @example
|
||||
* const count = box(0) // WritableBox<number>
|
||||
* const countReadonly = box.readonly(count) // ReadableBox<number>
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
function toReadonlyBox(b) {
|
||||
if (!isWritableBox(b))
|
||||
return b;
|
||||
return {
|
||||
[BoxSymbol]: true,
|
||||
get current() {
|
||||
return b.current;
|
||||
}
|
||||
};
|
||||
}
|
||||
function simpleBox(initialValue) {
|
||||
let current = $state(initialValue);
|
||||
return {
|
||||
[BoxSymbol]: true,
|
||||
[isWritableSymbol]: true,
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
set current(v) {
|
||||
current = v;
|
||||
}
|
||||
};
|
||||
}
|
||||
export { boxWith, isBox, isWritableBox, boxFrom, boxFlatten, toReadonlyBox, simpleBox };
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { boxFrom, boxWith, boxFlatten, toReadonlyBox, type WritableBox } from "./box-extras.svelte.js";
|
||||
/**
|
||||
* Creates a writable box.
|
||||
*
|
||||
* @returns A box with a `current` property which can be set to a new value.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
export declare function box<T>(): WritableBox<T | undefined>;
|
||||
/**
|
||||
* Creates a writable box with an initial value.
|
||||
*
|
||||
* @param initialValue The initial value of the box.
|
||||
* @returns A box with a `current` property which can be set to a new value.
|
||||
* Useful to pass state to other functions.
|
||||
*
|
||||
* @see {@link https://runed.dev/docs/functions/box}
|
||||
*/
|
||||
export declare function box<T>(initialValue: T): WritableBox<T>;
|
||||
export declare namespace box {
|
||||
export var from: typeof boxFrom;
|
||||
var _a: typeof boxWith;
|
||||
export var flatten: typeof boxFlatten;
|
||||
export var readonly: typeof toReadonlyBox;
|
||||
export var isBox: typeof import("./box-extras.svelte.js").isBox;
|
||||
export var isWritableBox: typeof import("./box-extras.svelte.js").isWritableBox;
|
||||
export { _a as with };
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { boxFrom, boxWith, boxFlatten, toReadonlyBox, isBox, isWritableBox, BoxSymbol, isWritableSymbol } from "./box-extras.svelte.js";
|
||||
export function box(initialValue) {
|
||||
let current = $state(initialValue);
|
||||
return {
|
||||
[BoxSymbol]: true,
|
||||
[isWritableSymbol]: true,
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
set current(v) {
|
||||
current = v;
|
||||
}
|
||||
};
|
||||
}
|
||||
box.from = boxFrom;
|
||||
box.with = boxWith;
|
||||
box.flatten = boxFlatten;
|
||||
box.readonly = toReadonlyBox;
|
||||
box.isBox = isBox;
|
||||
box.isWritableBox = isWritableBox;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type * as CSS from "csstype";
|
||||
|
||||
declare module "csstype" {
|
||||
interface Properties {
|
||||
// Allow any CSS Custom Properties
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[index: `--${string}`]: any;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
export { box } from "./box/box.svelte.js";
|
||||
export { boxWith, isBox, isWritableBox, boxFrom, boxFlatten, toReadonlyBox, simpleBox, type WritableBox, type ReadableBox } from "./box/box-extras.svelte.js";
|
||||
export { unbox } from "./unbox/unbox.svelte.js";
|
||||
export * from "./types.js";
|
||||
export { composeHandlers } from "./utils/compose-handlers.js";
|
||||
export { cssToStyleObj } from "./utils/css-to-style-obj.js";
|
||||
export { executeCallbacks } from "./utils/execute-callbacks.js";
|
||||
export { addEventListener, type EventCallback } from "./utils/events.js";
|
||||
export { mergeProps } from "./utils/merge-props.js";
|
||||
export { styleToString } from "./utils/style.js";
|
||||
export { srOnlyStyles, srOnlyStylesString } from "./utils/sr-only-styles.js";
|
||||
export { useRefById } from "./utils/use-ref-by-id.svelte.js";
|
||||
export { pascalCase, camelCase, kebabCase } from "./utils/strings.js";
|
||||
export { onDestroyEffect } from "./utils/on-destroy-effect.svelte.js";
|
||||
export { onMountEffect } from "./utils/on-mount-effect.svelte.js";
|
||||
export { afterSleep } from "./utils/after-sleep.js";
|
||||
export { afterTick } from "./utils/after-tick.js";
|
||||
export { useOnChange } from "./utils/use-on-change.svelte.js";
|
||||
export { styleToCSS } from "./utils/style-to-css.js";
|
||||
export { isHTMLElement, isDocument, isWindow, getNodeName, isNode, isShadowRoot, contains, getDocument, getDocumentElement, getWindow, getActiveElement, getParentNode } from "./utils/dom.js";
|
||||
export { DOMContext } from "./utils/dom-context.svelte.js";
|
||||
export { attachRef } from "./utils/attach-ref.js";
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
export { box } from "./box/box.svelte.js";
|
||||
export { boxWith, isBox, isWritableBox, boxFrom, boxFlatten, toReadonlyBox, simpleBox } from "./box/box-extras.svelte.js";
|
||||
export { unbox } from "./unbox/unbox.svelte.js";
|
||||
export * from "./types.js";
|
||||
export { composeHandlers } from "./utils/compose-handlers.js";
|
||||
export { cssToStyleObj } from "./utils/css-to-style-obj.js";
|
||||
export { executeCallbacks } from "./utils/execute-callbacks.js";
|
||||
export { addEventListener } from "./utils/events.js";
|
||||
export { mergeProps } from "./utils/merge-props.js";
|
||||
export { styleToString } from "./utils/style.js";
|
||||
export { srOnlyStyles, srOnlyStylesString } from "./utils/sr-only-styles.js";
|
||||
export { useRefById } from "./utils/use-ref-by-id.svelte.js";
|
||||
export { pascalCase, camelCase, kebabCase } from "./utils/strings.js";
|
||||
export { onDestroyEffect } from "./utils/on-destroy-effect.svelte.js";
|
||||
export { onMountEffect } from "./utils/on-mount-effect.svelte.js";
|
||||
export { afterSleep } from "./utils/after-sleep.js";
|
||||
export { afterTick } from "./utils/after-tick.js";
|
||||
export { useOnChange } from "./utils/use-on-change.svelte.js";
|
||||
export { styleToCSS } from "./utils/style-to-css.js";
|
||||
export { isHTMLElement, isDocument, isWindow, getNodeName, isNode, isShadowRoot, contains, getDocument, getDocumentElement, getWindow, getActiveElement, getParentNode } from "./utils/dom.js";
|
||||
export { DOMContext } from "./utils/dom-context.svelte.js";
|
||||
export { attachRef } from "./utils/attach-ref.js";
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import type { Snippet } from "svelte";
|
||||
import type * as CSS from "csstype";
|
||||
import type { ReadableBox, WritableBox } from "./box/box-extras.svelte.js";
|
||||
export type FunctionArgs<Args extends any[] = any[], Return = void> = (...args: Args) => Return;
|
||||
export type Getter<T> = () => T;
|
||||
export type MaybeGetter<T> = T | Getter<T>;
|
||||
export type MaybeBoxOrGetter<T> = T | Getter<T> | ReadableBox<T>;
|
||||
export type BoxOrGetter<T> = Getter<T> | ReadableBox<T>;
|
||||
export type Box<T> = ReadableBox<T> | WritableBox<T>;
|
||||
export type Expand<T> = T extends infer U ? {
|
||||
[K in keyof U]: U[K];
|
||||
} : never;
|
||||
/**
|
||||
* Given a Record type T, returns a type that represents the same type, but with
|
||||
* all values wrapped in a `ReadableBox`.
|
||||
*/
|
||||
export type ReadableBoxedValues<T> = {
|
||||
[K in keyof T]: ReadableBox<T[K]>;
|
||||
};
|
||||
/**
|
||||
* Given a Record type T, returns a type that represents the same type, but with
|
||||
* all values wrapped in a `WritableBox`.
|
||||
*/
|
||||
export type WritableBoxedValues<T> = {
|
||||
[K in keyof T]: WritableBox<T[K]>;
|
||||
};
|
||||
export type WithChild<
|
||||
/**
|
||||
* The props that the component accepts.
|
||||
*/
|
||||
Props extends Record<PropertyKey, unknown> = {},
|
||||
/**
|
||||
* The props that are passed to the `child` and `children` snippets. The `ElementProps` are
|
||||
* merged with these props for the `child` snippet.
|
||||
*/
|
||||
SnippetProps extends Record<PropertyKey, unknown> = {
|
||||
_default: never;
|
||||
},
|
||||
/**
|
||||
* The underlying DOM element being rendered. You can bind to this prop to
|
||||
* programatically interact with the element.
|
||||
*/
|
||||
Ref = HTMLElement> = Omit<Props, "child" | "children"> & {
|
||||
child?: SnippetProps extends {
|
||||
_default: never;
|
||||
} ? Snippet<[{
|
||||
props: Record<string, unknown>;
|
||||
}]> : Snippet<[SnippetProps & {
|
||||
props: Record<string, unknown>;
|
||||
}]>;
|
||||
children?: SnippetProps extends {
|
||||
_default: never;
|
||||
} ? Snippet : Snippet<[SnippetProps]>;
|
||||
style?: string | null | undefined;
|
||||
ref?: Ref | null | undefined;
|
||||
};
|
||||
export type WithChildren<Props = {}> = Props & {
|
||||
children?: Snippet | undefined;
|
||||
};
|
||||
/**
|
||||
* Constructs a new type by omitting properties from type
|
||||
* 'T' that exist in type 'U'.
|
||||
*
|
||||
* @template T - The base object type from which properties will be omitted.
|
||||
* @template U - The object type whose properties will be omitted from 'T'.
|
||||
* @example
|
||||
* type Result = Without<{ a: number; b: string; }, { b: string; }>;
|
||||
* // Result type will be { a: number; }
|
||||
*/
|
||||
export type Without<T extends object, U extends object> = Omit<T, keyof U>;
|
||||
export type WithRefProps<T = {}> = T & ReadableBoxedValues<{
|
||||
id: string;
|
||||
}> & WritableBoxedValues<{
|
||||
ref: HTMLElement | null;
|
||||
}>;
|
||||
export type WithoutChild<T> = T extends {
|
||||
child?: any;
|
||||
} ? Omit<T, "child"> : T;
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithoutChildren<T> = T extends {
|
||||
children?: any;
|
||||
} ? Omit<T, "children"> : T;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
||||
ref?: U | null;
|
||||
};
|
||||
export type StyleProperties = CSS.Properties & {
|
||||
[str: `--${string}`]: any;
|
||||
};
|
||||
export type AnyFn = (...args: any[]) => any;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { MaybeBoxOrGetter } from "../types.js";
|
||||
export declare function unbox<T>(value: MaybeBoxOrGetter<T>): T;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { isBox } from "../box/box-extras.svelte.js";
|
||||
import { isFunction } from "../utils/is.js";
|
||||
export function unbox(value) {
|
||||
if (isBox(value))
|
||||
return value.current;
|
||||
if (isFunction(value)) {
|
||||
const getter = value;
|
||||
return getter();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* A utility function that executes a callback after a specified number of milliseconds.
|
||||
*/
|
||||
export declare function afterSleep(ms: number, cb: () => void): NodeJS.Timeout;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* A utility function that executes a callback after a specified number of milliseconds.
|
||||
*/
|
||||
export function afterSleep(ms, cb) {
|
||||
return setTimeout(cb, ms);
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { AnyFn } from "../types.js";
|
||||
export declare function afterTick(fn: AnyFn): void;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { tick } from "svelte";
|
||||
export function afterTick(fn) {
|
||||
tick().then(fn);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { type WritableBox } from "../box/box-extras.svelte.js";
|
||||
type RefSetter<T> = (v: T) => void;
|
||||
/**
|
||||
* Creates a Svelte Attachment that attaches a DOM element to a ref.
|
||||
* The ref can be either a WritableBox or a callback function.
|
||||
*
|
||||
* @param ref - Either a WritableBox to store the element in, or a callback function that receives the element
|
||||
* @param onChange - Optional callback that fires when the ref changes
|
||||
* @returns An object with a spreadable attachment key that should be spread onto the element
|
||||
*
|
||||
* @example
|
||||
* // Using with WritableBox
|
||||
* const ref = box<HTMLDivElement | null>(null);
|
||||
* <div {...attachRef(ref)}>Content</div>
|
||||
*
|
||||
* @example
|
||||
* // Using with callback
|
||||
* <div {...attachRef((node) => myNode = node)}>Content</div>
|
||||
*
|
||||
* @example
|
||||
* // Using with onChange
|
||||
* <div {...attachRef(ref, (node) => console.log(node))}>Content</div>
|
||||
*/
|
||||
export declare function attachRef<T extends EventTarget = Element>(ref: WritableBox<T | null> | RefSetter<T | null>, onChange?: (v: T | null) => void): {
|
||||
[x: symbol]: (node: T) => () => void;
|
||||
};
|
||||
export {};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { untrack } from "svelte";
|
||||
import { isBox } from "../box/box-extras.svelte.js";
|
||||
import { createAttachmentKey } from "svelte/attachments";
|
||||
/**
|
||||
* Creates a Svelte Attachment that attaches a DOM element to a ref.
|
||||
* The ref can be either a WritableBox or a callback function.
|
||||
*
|
||||
* @param ref - Either a WritableBox to store the element in, or a callback function that receives the element
|
||||
* @param onChange - Optional callback that fires when the ref changes
|
||||
* @returns An object with a spreadable attachment key that should be spread onto the element
|
||||
*
|
||||
* @example
|
||||
* // Using with WritableBox
|
||||
* const ref = box<HTMLDivElement | null>(null);
|
||||
* <div {...attachRef(ref)}>Content</div>
|
||||
*
|
||||
* @example
|
||||
* // Using with callback
|
||||
* <div {...attachRef((node) => myNode = node)}>Content</div>
|
||||
*
|
||||
* @example
|
||||
* // Using with onChange
|
||||
* <div {...attachRef(ref, (node) => console.log(node))}>Content</div>
|
||||
*/
|
||||
export function attachRef(ref, onChange) {
|
||||
return {
|
||||
[createAttachmentKey()]: (node) => {
|
||||
if (isBox(ref)) {
|
||||
ref.current = node;
|
||||
untrack(() => onChange?.(node));
|
||||
return () => {
|
||||
// we don't want to detach the node if it's still connected
|
||||
if ("isConnected" in node && node.isConnected)
|
||||
return;
|
||||
ref.current = null;
|
||||
onChange?.(null);
|
||||
};
|
||||
}
|
||||
ref(node);
|
||||
untrack(() => onChange?.(node));
|
||||
return () => {
|
||||
// we don't want to detach the node if it's still connected
|
||||
if ("isConnected" in node && node.isConnected)
|
||||
return;
|
||||
ref(null);
|
||||
onChange?.(null);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import type { EventCallback } from "./events.js";
|
||||
import type { ReadableBox } from "../box/box-extras.svelte.js";
|
||||
/**
|
||||
* Composes event handlers into a single function that can be called with an event.
|
||||
* If the previous handler cancels the event using `event.preventDefault()`, the handlers
|
||||
* that follow will not be called.
|
||||
*/
|
||||
export declare function composeHandlers<E extends Event = Event, T extends Element = Element>(...handlers: Array<EventCallback<E> | ReadableBox<EventCallback<E>> | undefined>): (e: E) => void;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Composes event handlers into a single function that can be called with an event.
|
||||
* If the previous handler cancels the event using `event.preventDefault()`, the handlers
|
||||
* that follow will not be called.
|
||||
*/
|
||||
export function composeHandlers(...handlers) {
|
||||
return function (e) {
|
||||
for (const handler of handlers) {
|
||||
if (!handler)
|
||||
continue;
|
||||
if (e.defaultPrevented)
|
||||
return;
|
||||
if (typeof handler === "function") {
|
||||
handler.call(this, e);
|
||||
}
|
||||
else {
|
||||
handler.current?.call(this, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { StyleProperties } from "../types.js";
|
||||
export declare function cssToStyleObj(css: string | null | undefined): StyleProperties;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import parse from "style-to-object";
|
||||
import { camelCase, pascalCase } from "./strings.js";
|
||||
export function cssToStyleObj(css) {
|
||||
if (!css)
|
||||
return {};
|
||||
const styleObj = {};
|
||||
function iterator(name, value) {
|
||||
if (name.startsWith("-moz-") ||
|
||||
name.startsWith("-webkit-") ||
|
||||
name.startsWith("-ms-") ||
|
||||
name.startsWith("-o-")) {
|
||||
styleObj[pascalCase(name)] = value;
|
||||
return;
|
||||
}
|
||||
if (name.startsWith("--")) {
|
||||
styleObj[name] = value;
|
||||
return;
|
||||
}
|
||||
styleObj[camelCase(name)] = value;
|
||||
}
|
||||
parse(css, iterator);
|
||||
return styleObj;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type { Box } from "../types.js";
|
||||
type ElementGetter = () => HTMLElement | null;
|
||||
export declare class DOMContext {
|
||||
readonly element: Box<HTMLElement | null>;
|
||||
readonly root: Document | ShadowRoot;
|
||||
constructor(element: Box<HTMLElement | null> | ElementGetter);
|
||||
getDocument: () => Document;
|
||||
getWindow: () => Window & typeof globalThis;
|
||||
getActiveElement: () => HTMLElement | null;
|
||||
isActiveElement: (node: HTMLElement | null) => boolean;
|
||||
getElementById<T extends Element = HTMLElement>(id: string): T | null;
|
||||
querySelector: (selector: string) => Element | null;
|
||||
querySelectorAll: (selector: string) => NodeListOf<Element>;
|
||||
setTimeout: (callback: () => void, delay: number) => number;
|
||||
clearTimeout: (timeoutId: number) => void;
|
||||
}
|
||||
export {};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { boxWith } from "../box/box-extras.svelte.js";
|
||||
import { getActiveElement, getDocument } from "./dom.js";
|
||||
export class DOMContext {
|
||||
element;
|
||||
root = $derived.by(() => {
|
||||
if (!this.element.current)
|
||||
return document;
|
||||
const rootNode = this.element.current.getRootNode() ?? document;
|
||||
return rootNode;
|
||||
});
|
||||
constructor(element) {
|
||||
if (typeof element === "function") {
|
||||
this.element = boxWith(element);
|
||||
}
|
||||
else {
|
||||
this.element = element;
|
||||
}
|
||||
}
|
||||
getDocument = () => {
|
||||
return getDocument(this.root);
|
||||
};
|
||||
getWindow = () => {
|
||||
return this.getDocument().defaultView ?? window;
|
||||
};
|
||||
getActiveElement = () => {
|
||||
return getActiveElement(this.root);
|
||||
};
|
||||
isActiveElement = (node) => {
|
||||
return node === this.getActiveElement();
|
||||
};
|
||||
getElementById(id) {
|
||||
return this.root.getElementById(id);
|
||||
}
|
||||
querySelector = (selector) => {
|
||||
if (!this.root)
|
||||
return null;
|
||||
return this.root.querySelector(selector);
|
||||
};
|
||||
querySelectorAll = (selector) => {
|
||||
if (!this.root)
|
||||
return [];
|
||||
return this.root.querySelectorAll(selector);
|
||||
};
|
||||
setTimeout = (callback, delay) => {
|
||||
return this.getWindow().setTimeout(callback, delay);
|
||||
};
|
||||
clearTimeout = (timeoutId) => {
|
||||
return this.getWindow().clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export declare function isHTMLElement(node: unknown): node is HTMLElement;
|
||||
export declare function isDocument(node: unknown): node is Document;
|
||||
export declare function isWindow(node: unknown): node is Window;
|
||||
export declare function getNodeName(node: Node | Window): string;
|
||||
export declare function isNode(node: unknown): node is Node;
|
||||
export declare function isShadowRoot(node: unknown): node is ShadowRoot;
|
||||
type Target = HTMLElement | EventTarget | null | undefined;
|
||||
export declare function contains(parent: Target, child: Target): boolean;
|
||||
export declare function getDocument(node: Element | Window | Node | Document | null | undefined): Document;
|
||||
export declare function getDocumentElement(node: Element | Node | Window | Document | null | undefined): HTMLElement;
|
||||
export declare function getWindow(node: Node | ShadowRoot | Document | null | undefined): Window & typeof globalThis;
|
||||
export declare function getActiveElement(rootNode: Document | ShadowRoot): HTMLElement | null;
|
||||
export declare function getParentNode(node: Node): Node;
|
||||
export {};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { isObject } from "./is.js";
|
||||
const ELEMENT_NODE = 1;
|
||||
const DOCUMENT_NODE = 9;
|
||||
const DOCUMENT_FRAGMENT_NODE = 11;
|
||||
export function isHTMLElement(node) {
|
||||
return isObject(node) && node.nodeType === ELEMENT_NODE && typeof node.nodeName === "string";
|
||||
}
|
||||
export function isDocument(node) {
|
||||
return isObject(node) && node.nodeType === DOCUMENT_NODE;
|
||||
}
|
||||
export function isWindow(node) {
|
||||
return isObject(node) && node.constructor?.name === "VisualViewport";
|
||||
}
|
||||
export function getNodeName(node) {
|
||||
if (isHTMLElement(node))
|
||||
return node.localName ?? "";
|
||||
return "#document";
|
||||
}
|
||||
export function isNode(node) {
|
||||
return isObject(node) && node.nodeType !== undefined;
|
||||
}
|
||||
export function isShadowRoot(node) {
|
||||
return isNode(node) && node.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in node;
|
||||
}
|
||||
export function contains(parent, child) {
|
||||
if (!parent || !child)
|
||||
return false;
|
||||
if (!isHTMLElement(parent) || !isHTMLElement(child))
|
||||
return false;
|
||||
const rootNode = child.getRootNode?.();
|
||||
if (parent === child)
|
||||
return true;
|
||||
if (parent.contains(child))
|
||||
return true;
|
||||
if (rootNode && isShadowRoot(rootNode)) {
|
||||
let next = child;
|
||||
while (next) {
|
||||
if (parent === next)
|
||||
return true;
|
||||
// @ts-expect-error - host is not typed
|
||||
next = next.parentNode || next.host;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function getDocument(node) {
|
||||
if (isDocument(node))
|
||||
return node;
|
||||
if (isWindow(node))
|
||||
return node.document;
|
||||
return node?.ownerDocument ?? document;
|
||||
}
|
||||
export function getDocumentElement(node) {
|
||||
return getDocument(node).documentElement;
|
||||
}
|
||||
export function getWindow(node) {
|
||||
if (isShadowRoot(node))
|
||||
return getWindow(node.host);
|
||||
if (isDocument(node))
|
||||
return node.defaultView ?? window;
|
||||
if (isHTMLElement(node))
|
||||
return node.ownerDocument?.defaultView ?? window;
|
||||
return window;
|
||||
}
|
||||
export function getActiveElement(rootNode) {
|
||||
let activeElement = rootNode.activeElement;
|
||||
while (activeElement?.shadowRoot) {
|
||||
const el = activeElement.shadowRoot.activeElement;
|
||||
if (el === activeElement)
|
||||
break;
|
||||
else
|
||||
activeElement = el;
|
||||
}
|
||||
return activeElement;
|
||||
}
|
||||
export function getParentNode(node) {
|
||||
if (getNodeName(node) === "html")
|
||||
return node;
|
||||
const result =
|
||||
// @ts-expect-error - assignedSlot is not typed
|
||||
node.assignedSlot ||
|
||||
node.parentNode ||
|
||||
(isShadowRoot(node) && node.host) ||
|
||||
getDocumentElement(node);
|
||||
return isShadowRoot(result) ? result.host : result;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const EVENT_LIST_SET: Set<string>;
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
const EVENT_LIST = [
|
||||
"onabort",
|
||||
"onanimationcancel",
|
||||
"onanimationend",
|
||||
"onanimationiteration",
|
||||
"onanimationstart",
|
||||
"onauxclick",
|
||||
"onbeforeinput",
|
||||
"onbeforetoggle",
|
||||
"onblur",
|
||||
"oncancel",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"onclose",
|
||||
"oncompositionend",
|
||||
"oncompositionstart",
|
||||
"oncompositionupdate",
|
||||
"oncontextlost",
|
||||
"oncontextmenu",
|
||||
"oncontextrestored",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onfocusin",
|
||||
"onfocusout",
|
||||
"onformdata",
|
||||
"ongotpointercapture",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onlostpointercapture",
|
||||
"onmousedown",
|
||||
"onmouseenter",
|
||||
"onmouseleave",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onpointercancel",
|
||||
"onpointerdown",
|
||||
"onpointerenter",
|
||||
"onpointerleave",
|
||||
"onpointermove",
|
||||
"onpointerout",
|
||||
"onpointerover",
|
||||
"onpointerup",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onscrollend",
|
||||
"onsecuritypolicyviolation",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onselectionchange",
|
||||
"onselectstart",
|
||||
"onslotchange",
|
||||
"onstalled",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"ontouchcancel",
|
||||
"ontouchend",
|
||||
"ontouchmove",
|
||||
"ontouchstart",
|
||||
"ontransitioncancel",
|
||||
"ontransitionend",
|
||||
"ontransitionrun",
|
||||
"ontransitionstart",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwebkitanimationend",
|
||||
"onwebkitanimationiteration",
|
||||
"onwebkitanimationstart",
|
||||
"onwebkittransitionend",
|
||||
"onwheel"
|
||||
];
|
||||
export const EVENT_LIST_SET = new Set(EVENT_LIST);
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
type Arrayable<T> = T | T[];
|
||||
export type EventCallback<E extends Event = Event> = (event: E) => void;
|
||||
type GeneralEventListener<E = Event> = (evt: E) => unknown;
|
||||
export declare function addEventListener<E extends keyof WindowEventMap>(target: Window, event: Arrayable<E>, handler: (this: Window, ev: WindowEventMap[E]) => unknown, options?: boolean | AddEventListenerOptions): VoidFunction;
|
||||
export declare function addEventListener<E extends keyof DocumentEventMap>(target: Document, event: Arrayable<E>, handler: (this: Document, ev: DocumentEventMap[E]) => unknown, options?: boolean | AddEventListenerOptions): VoidFunction;
|
||||
export declare function addEventListener<E extends keyof HTMLElementEventMap>(target: EventTarget, event: Arrayable<E>, handler: GeneralEventListener<HTMLElementEventMap[E]>, options?: boolean | AddEventListenerOptions): VoidFunction;
|
||||
export {};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Adds an event listener to the specified target element(s) for the given event(s), and returns a function to remove it.
|
||||
* @param target The target element(s) to add the event listener to.
|
||||
* @param event The event(s) to listen for.
|
||||
* @param handler The function to be called when the event is triggered.
|
||||
* @param options An optional object that specifies characteristics about the event listener.
|
||||
* @returns A function that removes the event listener from the target element(s).
|
||||
*/
|
||||
export function addEventListener(target, event, handler, options) {
|
||||
const events = Array.isArray(event) ? event : [event];
|
||||
// Add the event listener to each specified event for the target element(s).
|
||||
events.forEach((_event) => target.addEventListener(_event, handler, options));
|
||||
// Return a function that removes the event listener from the target element(s).
|
||||
return () => {
|
||||
events.forEach((_event) => target.removeEventListener(_event, handler, options));
|
||||
};
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Executes an array of callback functions with the same arguments.
|
||||
* @template T The types of the arguments that the callback functions take.
|
||||
* @param callbacks array of callback functions to execute.
|
||||
* @returns A new function that executes all of the original callback functions with the same arguments.
|
||||
*/
|
||||
export declare function executeCallbacks<T extends unknown[]>(...callbacks: T): (...args: unknown[]) => void;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Executes an array of callback functions with the same arguments.
|
||||
* @template T The types of the arguments that the callback functions take.
|
||||
* @param callbacks array of callback functions to execute.
|
||||
* @returns A new function that executes all of the original callback functions with the same arguments.
|
||||
*/
|
||||
export function executeCallbacks(...callbacks) {
|
||||
return (...args) => {
|
||||
for (const callback of callbacks) {
|
||||
if (typeof callback === "function") {
|
||||
callback(...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import type { ClassValue } from "clsx";
|
||||
export declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
|
||||
export declare function isObject(value: unknown): value is Record<PropertyKey, unknown>;
|
||||
export declare function isClassValue(value: unknown): value is ClassValue;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
export function isFunction(value) {
|
||||
return typeof value === "function";
|
||||
}
|
||||
export function isObject(value) {
|
||||
return value !== null && typeof value === "object";
|
||||
}
|
||||
const CLASS_VALUE_PRIMITIVE_TYPES = ["string", "number", "bigint", "boolean"];
|
||||
export function isClassValue(value) {
|
||||
// handle primitive types
|
||||
if (value === null || value === undefined)
|
||||
return true;
|
||||
if (CLASS_VALUE_PRIMITIVE_TYPES.includes(typeof value))
|
||||
return true;
|
||||
// handle arrays (ClassArray)
|
||||
if (Array.isArray(value))
|
||||
return value.every((item) => isClassValue(item));
|
||||
// handle objects (ClassDictionary)
|
||||
if (typeof value === "object") {
|
||||
// ensure it's a plain object and not some other object type
|
||||
if (Object.getPrototypeOf(value) !== Object.prototype)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
type Props = Record<string, unknown>;
|
||||
type PropsArg = Props | null | undefined;
|
||||
type TupleTypes<T> = {
|
||||
[P in keyof T]: T[P];
|
||||
} extends {
|
||||
[key: number]: infer V;
|
||||
} ? NullToObject<V> : never;
|
||||
type NullToObject<T> = T extends null | undefined ? {} : T;
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
||||
/**
|
||||
* Given a list of prop objects, merges them into a single object.
|
||||
* - Automatically composes event handlers (e.g. `onclick`, `oninput`, etc.)
|
||||
* - Chains regular functions with the same name so they are called in order
|
||||
* - Merges class strings with `clsx`
|
||||
* - Merges style objects and converts them to strings
|
||||
* - Handles a bug with Svelte where setting the `hidden` attribute to `false` doesn't remove it
|
||||
* - Overrides other values with the last one
|
||||
*/
|
||||
export declare function mergeProps<T extends PropsArg[]>(...args: T): UnionToIntersection<TupleTypes<T>> & {
|
||||
style?: string;
|
||||
};
|
||||
export {};
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Modified from https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/utils/src/mergeProps.ts (see NOTICE.txt for source)
|
||||
*/
|
||||
import { clsx } from "clsx";
|
||||
import { composeHandlers } from "./compose-handlers.js";
|
||||
import { cssToStyleObj } from "./css-to-style-obj.js";
|
||||
import { isClassValue } from "./is.js";
|
||||
import { executeCallbacks } from "./execute-callbacks.js";
|
||||
import { styleToString } from "./style.js";
|
||||
import { EVENT_LIST_SET } from "./event-list.js";
|
||||
function isEventHandler(key) {
|
||||
return EVENT_LIST_SET.has(key);
|
||||
}
|
||||
/**
|
||||
* Given a list of prop objects, merges them into a single object.
|
||||
* - Automatically composes event handlers (e.g. `onclick`, `oninput`, etc.)
|
||||
* - Chains regular functions with the same name so they are called in order
|
||||
* - Merges class strings with `clsx`
|
||||
* - Merges style objects and converts them to strings
|
||||
* - Handles a bug with Svelte where setting the `hidden` attribute to `false` doesn't remove it
|
||||
* - Overrides other values with the last one
|
||||
*/
|
||||
export function mergeProps(...args) {
|
||||
const result = { ...args[0] };
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const props = args[i];
|
||||
if (!props)
|
||||
continue;
|
||||
// Handle string keys
|
||||
for (const key of Object.keys(props)) {
|
||||
const a = result[key];
|
||||
const b = props[key];
|
||||
const aIsFunction = typeof a === "function";
|
||||
const bIsFunction = typeof b === "function";
|
||||
// compose event handlers
|
||||
if (aIsFunction && typeof bIsFunction && isEventHandler(key)) {
|
||||
// handle merging of event handlers
|
||||
const aHandler = a;
|
||||
const bHandler = b;
|
||||
result[key] = composeHandlers(aHandler, bHandler);
|
||||
}
|
||||
else if (aIsFunction && bIsFunction) {
|
||||
// chain non-event handler functions
|
||||
result[key] = executeCallbacks(a, b);
|
||||
}
|
||||
else if (key === "class") {
|
||||
// handle merging acceptable class values from clsx
|
||||
const aIsClassValue = isClassValue(a);
|
||||
const bIsClassValue = isClassValue(b);
|
||||
if (aIsClassValue && bIsClassValue) {
|
||||
result[key] = clsx(a, b);
|
||||
}
|
||||
else if (aIsClassValue) {
|
||||
result[key] = clsx(a);
|
||||
}
|
||||
else if (bIsClassValue) {
|
||||
result[key] = clsx(b);
|
||||
}
|
||||
}
|
||||
else if (key === "style") {
|
||||
const aIsObject = typeof a === "object";
|
||||
const bIsObject = typeof b === "object";
|
||||
const aIsString = typeof a === "string";
|
||||
const bIsString = typeof b === "string";
|
||||
if (aIsObject && bIsObject) {
|
||||
// both are style objects, merge them
|
||||
result[key] = { ...a, ...b };
|
||||
}
|
||||
else if (aIsObject && bIsString) {
|
||||
// a is style object, b is string, convert b to style object and merge
|
||||
const parsedStyle = cssToStyleObj(b);
|
||||
result[key] = { ...a, ...parsedStyle };
|
||||
}
|
||||
else if (aIsString && bIsObject) {
|
||||
// a is string, b is style object, convert a to style object and merge
|
||||
const parsedStyle = cssToStyleObj(a);
|
||||
result[key] = { ...parsedStyle, ...b };
|
||||
}
|
||||
else if (aIsString && bIsString) {
|
||||
// both are strings, convert both to objects and merge
|
||||
const parsedStyleA = cssToStyleObj(a);
|
||||
const parsedStyleB = cssToStyleObj(b);
|
||||
result[key] = { ...parsedStyleA, ...parsedStyleB };
|
||||
}
|
||||
else if (aIsObject) {
|
||||
result[key] = a;
|
||||
}
|
||||
else if (bIsObject) {
|
||||
result[key] = b;
|
||||
}
|
||||
else if (aIsString) {
|
||||
result[key] = a;
|
||||
}
|
||||
else if (bIsString) {
|
||||
result[key] = b;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// override other values
|
||||
result[key] = b !== undefined ? b : a;
|
||||
}
|
||||
}
|
||||
// handle symbol keys (mostly for `Attachments`)
|
||||
for (const key of Object.getOwnPropertySymbols(props)) {
|
||||
const a = result[key];
|
||||
const b = props[key];
|
||||
// for matching symbols, we just override
|
||||
result[key] = b !== undefined ? b : a;
|
||||
}
|
||||
}
|
||||
// convert style object to string
|
||||
if (typeof result.style === "object") {
|
||||
result.style = styleToString(result.style).replaceAll("\n", " ");
|
||||
}
|
||||
// handle weird svelte bug where `hidden` is not removed when set to `false`
|
||||
if (result.hidden === false) {
|
||||
result.hidden = undefined;
|
||||
delete result.hidden;
|
||||
}
|
||||
// handle weird svelte bug where `disabled` is not removed when set to `false`
|
||||
if (result.disabled === false) {
|
||||
result.disabled = undefined;
|
||||
delete result.disabled;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function onDestroyEffect(fn: () => void): void;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export function onDestroyEffect(fn) {
|
||||
$effect(() => {
|
||||
return () => {
|
||||
fn();
|
||||
};
|
||||
});
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function onMountEffect(fn: () => void): void;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { untrack } from "svelte";
|
||||
export function onMountEffect(fn) {
|
||||
$effect(() => {
|
||||
const cleanup = untrack(() => fn());
|
||||
return cleanup;
|
||||
});
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import type { StyleProperties } from "../types.js";
|
||||
export declare const srOnlyStyles: StyleProperties;
|
||||
export declare const srOnlyStylesString: string;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { styleToString } from "./style.js";
|
||||
export const srOnlyStyles = {
|
||||
position: "absolute",
|
||||
width: "1px",
|
||||
height: "1px",
|
||||
padding: "0",
|
||||
margin: "-1px",
|
||||
overflow: "hidden",
|
||||
clip: "rect(0, 0, 0, 0)",
|
||||
whiteSpace: "nowrap",
|
||||
borderWidth: "0",
|
||||
transform: "translateX(-100%)"
|
||||
};
|
||||
export const srOnlyStylesString = styleToString(srOnlyStyles);
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare function pascalCase(str?: string): string;
|
||||
export declare function camelCase(str?: string): string;
|
||||
export declare function kebabCase(str?: string): string;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
const NUMBER_CHAR_RE = /\d/;
|
||||
const STR_SPLITTERS = ["-", "_", "/", "."];
|
||||
function isUppercase(char = "") {
|
||||
if (NUMBER_CHAR_RE.test(char))
|
||||
return undefined;
|
||||
return char !== char.toLowerCase();
|
||||
}
|
||||
function splitByCase(str) {
|
||||
const parts = [];
|
||||
let buff = "";
|
||||
let previousUpper;
|
||||
let previousSplitter;
|
||||
for (const char of str) {
|
||||
// Splitter
|
||||
const isSplitter = STR_SPLITTERS.includes(char);
|
||||
if (isSplitter === true) {
|
||||
parts.push(buff);
|
||||
buff = "";
|
||||
previousUpper = undefined;
|
||||
continue;
|
||||
}
|
||||
const isUpper = isUppercase(char);
|
||||
if (previousSplitter === false) {
|
||||
// Case rising edge
|
||||
if (previousUpper === false && isUpper === true) {
|
||||
parts.push(buff);
|
||||
buff = char;
|
||||
previousUpper = isUpper;
|
||||
continue;
|
||||
}
|
||||
// Case falling edge
|
||||
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
||||
const lastChar = buff.at(-1);
|
||||
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
||||
buff = lastChar + char;
|
||||
previousUpper = isUpper;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Normal char
|
||||
buff += char;
|
||||
previousUpper = isUpper;
|
||||
previousSplitter = isSplitter;
|
||||
}
|
||||
parts.push(buff);
|
||||
return parts;
|
||||
}
|
||||
export function pascalCase(str) {
|
||||
if (!str)
|
||||
return "";
|
||||
return splitByCase(str)
|
||||
.map((p) => upperFirst(p))
|
||||
.join("");
|
||||
}
|
||||
export function camelCase(str) {
|
||||
return lowerFirst(pascalCase(str || ""));
|
||||
}
|
||||
export function kebabCase(str) {
|
||||
return str
|
||||
? splitByCase(str)
|
||||
.map((p) => p.toLowerCase())
|
||||
.join("-")
|
||||
: "";
|
||||
}
|
||||
function upperFirst(str) {
|
||||
return str ? str[0].toUpperCase() + str.slice(1) : "";
|
||||
}
|
||||
function lowerFirst(str) {
|
||||
return str ? str[0].toLowerCase() + str.slice(1) : "";
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function styleToCSS(styleObj: object): string;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
function createParser(matcher, replacer) {
|
||||
const regex = RegExp(matcher, "g");
|
||||
return (str) => {
|
||||
// throw an error if not a string
|
||||
if (typeof str !== "string") {
|
||||
throw new TypeError(`expected an argument of type string, but got ${typeof str}`);
|
||||
}
|
||||
// if no match between string and matcher
|
||||
if (!str.match(regex))
|
||||
return str;
|
||||
// executes the replacer function for each match
|
||||
return str.replace(regex, replacer);
|
||||
};
|
||||
}
|
||||
const camelToKebab = createParser(/[A-Z]/, (match) => `-${match.toLowerCase()}`);
|
||||
export function styleToCSS(styleObj) {
|
||||
if (!styleObj || typeof styleObj !== "object" || Array.isArray(styleObj)) {
|
||||
throw new TypeError(`expected an argument of type object, but got ${typeof styleObj}`);
|
||||
}
|
||||
return Object.keys(styleObj)
|
||||
.map((property) => `${camelToKebab(property)}: ${styleObj[property]};`)
|
||||
.join("\n");
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { StyleProperties } from "../types.js";
|
||||
export declare function styleToString(style?: StyleProperties): string;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { styleToCSS } from "./style-to-css.js";
|
||||
export function styleToString(style = {}) {
|
||||
return styleToCSS(style).replace("\n", " ");
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import type { Getter } from "../types.js";
|
||||
/**
|
||||
* Simple helper function to sync a read-only dependency with writable state. This only syncs
|
||||
* in one direction, from the dependency to the state. If you need to sync both directions, you
|
||||
* should use the `box.with(() => dep, (v) => (dep = v))` pattern.
|
||||
*
|
||||
* @param getDep - A getter that returns the value that may change and whose new value will be
|
||||
* passed to the `onChange` function passed as the second argument.
|
||||
* @param onChange - A function that accepts a new value to react to or keep other state in sync
|
||||
* with the dependency.
|
||||
*/
|
||||
export declare function useOnChange<T>(getDep: Getter<T>, onChange: (value: T) => void): void;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { watch } from "runed";
|
||||
/**
|
||||
* Simple helper function to sync a read-only dependency with writable state. This only syncs
|
||||
* in one direction, from the dependency to the state. If you need to sync both directions, you
|
||||
* should use the `box.with(() => dep, (v) => (dep = v))` pattern.
|
||||
*
|
||||
* @param getDep - A getter that returns the value that may change and whose new value will be
|
||||
* passed to the `onChange` function passed as the second argument.
|
||||
* @param onChange - A function that accepts a new value to react to or keep other state in sync
|
||||
* with the dependency.
|
||||
*/
|
||||
export function useOnChange(getDep, onChange) {
|
||||
watch(getDep, onChange);
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import type { WritableBox } from "../box/box-extras.svelte.js";
|
||||
import type { Box, Getter } from "../types.js";
|
||||
type UseRefByIdProps = {
|
||||
/**
|
||||
* The ID of the node to find.
|
||||
*/
|
||||
id: Box<string>;
|
||||
/**
|
||||
* The ref to set the node to.
|
||||
*/
|
||||
ref: WritableBox<HTMLElement | null>;
|
||||
/**
|
||||
* A reactive condition that will cause the node to be set.
|
||||
*/
|
||||
deps?: Getter<unknown>;
|
||||
/**
|
||||
* A callback fired when the ref changes.
|
||||
*/
|
||||
onRefChange?: (node: HTMLElement | null) => void;
|
||||
/**
|
||||
* A function that returns the root node to search for the element by ID.
|
||||
* Defaults to `() => document`.
|
||||
*/
|
||||
getRootNode?: Getter<Document | ShadowRoot | undefined>;
|
||||
};
|
||||
/**
|
||||
* Finds the node with that ID and sets it to the boxed node.
|
||||
* Reactive using `$effect` to ensure when the ID or deps change,
|
||||
* an update is triggered and new node is found.
|
||||
*/
|
||||
export declare function useRefById({ id, ref, deps, onRefChange, getRootNode }: UseRefByIdProps): void;
|
||||
export {};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { watch } from "runed";
|
||||
import { onDestroyEffect } from "./on-destroy-effect.svelte.js";
|
||||
/**
|
||||
* Finds the node with that ID and sets it to the boxed node.
|
||||
* Reactive using `$effect` to ensure when the ID or deps change,
|
||||
* an update is triggered and new node is found.
|
||||
*/
|
||||
export function useRefById({ id, ref, deps = () => true, onRefChange, getRootNode }) {
|
||||
watch([() => id.current, deps], ([_id]) => {
|
||||
const rootNode = getRootNode?.() ?? document;
|
||||
const node = rootNode?.getElementById(_id);
|
||||
if (node)
|
||||
ref.current = node;
|
||||
else
|
||||
ref.current = null;
|
||||
onRefChange?.(ref.current);
|
||||
});
|
||||
onDestroyEffect(() => {
|
||||
ref.current = null;
|
||||
onRefChange?.(null);
|
||||
});
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import type { Box } from "../types.js";
|
||||
type WatcherCallback<T> = (curr: T, prev: T) => void | Promise<void> | (() => void) | (() => Promise<void>);
|
||||
type WatchOptions = {
|
||||
/**
|
||||
* Whether to eagerly run the watcher before the state is updated.
|
||||
*/
|
||||
immediate?: boolean;
|
||||
/**
|
||||
* Whether to run the watcher only once.
|
||||
*/
|
||||
once?: boolean;
|
||||
};
|
||||
export declare function watch<T>(box: Box<T>, callback: WatcherCallback<T>, options?: WatchOptions): () => void;
|
||||
export {};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { untrack } from "svelte";
|
||||
export function watch(box, callback, options = {}) {
|
||||
let prev = $state(box.current);
|
||||
let ranOnce = false;
|
||||
const watchEffect = $effect.root(() => {
|
||||
$effect.pre(() => {
|
||||
if (prev === box.current || !options.immediate)
|
||||
return;
|
||||
if (options.once && ranOnce)
|
||||
return;
|
||||
callback(box.current, untrack(() => prev));
|
||||
untrack(() => (prev = box.current));
|
||||
ranOnce = true;
|
||||
});
|
||||
$effect(() => {
|
||||
if (prev === box.current || options.immediate)
|
||||
return;
|
||||
if (options.once && ranOnce)
|
||||
return;
|
||||
callback(box.current, untrack(() => prev));
|
||||
untrack(() => (prev = box.current));
|
||||
ranOnce = true;
|
||||
});
|
||||
});
|
||||
return watchEffect;
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "svelte-toolbelt",
|
||||
"version": "0.10.6",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/huntabyte"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"svelte": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.test.*",
|
||||
"!dist/**/*.spec.*"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.30.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@eslint/js": "^9.12.0",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.21.1",
|
||||
"@sveltejs/package": "^2.3.11",
|
||||
"@sveltejs/vite-plugin-svelte": "4.0.1",
|
||||
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
|
||||
"@types/node": "^20.12.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
||||
"@typescript-eslint/scope-manager": "^8.10.0",
|
||||
"@typescript-eslint/utils": "^8.10.0",
|
||||
"csstype": "^3.1.3",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"globals": "^15.11.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"publint": "^0.1.9",
|
||||
"svelte": "^5.30.2",
|
||||
"svelte-check": "^4.2.1",
|
||||
"svelte-eslint-parser": "^0.43.0",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"vite": "^5.4.8",
|
||||
"vitest": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"runed": "^0.35.1",
|
||||
"style-to-object": "^1.0.8"
|
||||
},
|
||||
"svelte": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"pnpm": ">=8.7.0",
|
||||
"node": ">=18"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.8",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"dev": "pnpm --reporter append-only --color \"/dev:/\"",
|
||||
"dev:svelte": "vite dev",
|
||||
"dev:pkg": "svelte-package --watch",
|
||||
"build": "vite build && npm run package",
|
||||
"preview": "vite preview",
|
||||
"package": "svelte-kit sync && svelte-package && publint",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"ci:publish": "pnpm package && changeset publish"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user