INIT
This commit is contained in:
+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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user