fix: 修复关闭SSH终端标签页时会话状态未更新的问题

This commit is contained in:
2026-04-18 02:35:38 +08:00
commit 6e2e2f9387
43467 changed files with 5489040 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Solid Primitives Working Group
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.
+195
View File
@@ -0,0 +1,195 @@
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=Refs" alt="Solid Primitives Refs">
</p>
# @solid-primitives/refs
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/refs?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/refs)
[![version](https://img.shields.io/npm/v/@solid-primitives/refs?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/refs)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
Collection of primitives, components and directives that help managing references to JSX elements, keeping track of mounted/unmounted elements.
##### Primitives:
- [`mergeRefs`](#mergerefs) - Utility for chaining multiple `ref` assignments with `props.ref` forwarding.
- [`resolveElements`](#resolveelements) - Utility for resolving recursively nested JSX children to a single element or an array of elements.
- [`resolveFirst`](#resolvefirst) - Utility for resolving recursively nested JSX children in search of the first element that matches a predicate.
- [`<Refs>`](#refs) - Get up-to-date references of the multiple children elements.
- [`<Ref>`](#ref) - Get up-to-date reference to a single child element.
## Installation
```bash
npm install @solid-primitives/refs
# or
pnpm add @solid-primitives/refs
# or
yarn add @solid-primitives/refs
```
## `mergeRefs`
Utility for chaining multiple `ref` assignments with `props.ref` forwarding.
### How to use it
```tsx
import { mergeRefs, Ref } from "@solid-primitives/refs";
interface ButtonProps {
ref?: Ref<HTMLButtonElement>;
}
function Button(props: ButtonProps) {
let ref: HTMLButtonElement | undefined;
onMount(() => {
// use the local ref
});
return <button ref={mergeRefs(props.ref, el => (ref = el))} />;
}
// in consumer's component:
let ref: HTMLButtonElement | undefined;
<Button ref={ref} />;
```
## `resolveElements`
Utility for resolving recursively nested JSX children to a single element or an array of elements using a predicate.
### How to use it
`resolveElements`'s API is similar to Solid's `children` helper. It accepts a function that returns JSX children and a predicate function that filters the elements.
```tsx
function Button(props: ParentProps) {
const children = resolveElements(() => props.children);
// ^?: Accessor<Element | Element[] | null>
return (
// Similarly to `children` helper, a `toArray` method is available
<For each={children.toArray()}>
{child => (
<div>
{child.localName}: {child}
</div>
)}
</For>
);
}
```
### Using a custom predicate
The default predicate is `el => el instanceof Element`. You can provide a custom predicate to `resolveElements` to filter the elements.
```tsx
const els = resolveElements(
() => props.children,
(el): el is HTMLDivElement => el instanceof HTMLDivElement,
);
els(); // => HTMLDivElement | HTMLDivElement[] | null
```
On the server side the custom predicate will be ignored, but can be overridden by passing it as a third argument.
The default predicate can be imported from `@solid-primitives/refs`:
```tsx
import { defaultElementPredicate } from "@solid-primitives/refs";
```
On the client it uses `instanceof Element` check, on the server it checks for the object with `t` property. (generated by compiling JSX)
## `resolveFirst`
Utility for resolving recursively nested JSX children in search of the first element that matches a predicate.
### How to use it
`resolveFirst` matches the API of [`resolveElements`](#resolveelements) but returns only the first element that matches the predicate.
```tsx
function Button(props: ParentProps) {
const child = resolveFirst(() => props.children);
// ^?: Accessor<Element | null>
return (
<div>
{child()?.localName}: {child()}
</div>
);
}
```
`resolveFirst` also accepts a custom predicate as a second argument. See [`Using a custom predicate`](#using-a-custom-predicate) section for more details.
## `<Ref>`
Get up-to-date reference to a single child element.
### How to use it
`<Ref>` accepts only a `ref` property for getting the current element or `undefined`, and requires `children` to be passed in.
```tsx
import { Ref } from "@solid-primitives/refs";
const [ref, setRef] = createSignal<Element | undefined>();
<Ref ref={setRef}>{props.children}</Ref>;
```
## `<Refs>`
Get up-to-date references of the multiple children elements.
### How to use it
`<Refs>` accepts only a `ref` property for getting the current array of elements, and requires `children` to be passed in.
```tsx
import { Refs } from "@solid-primitives/refs";
const [refs, setRefs] = createSignal<Element[]>([]);
<Refs ref={setRefs}>
<For each={my_list()}>{item => <div>{item}</div>}</For>
<Show when={show()}>
<div>Hello</div>
</Show>
</Refs>;
```
#### Demo
https://stackblitz.com/edit/solid-vite-unocss-bkbgap?file=index.tsx
(run `npm start` in the terminal)
## Types
### `Ref`
Type for the `ref` prop
```ts
export type Ref<T> = T | ((el: T) => void) | undefined;
```
### `RefProps`
Component properties with types for `ref` prop
```ts
interface RefProps<T> {
ref?: Ref<T>;
}
```
## Changelog
See [CHANGELOG.md](./CHANGELOG.md)
+151
View File
@@ -0,0 +1,151 @@
import { type Accessor, type JSX } from "solid-js";
export type { ResolvedChildren, ResolvedJSXElement } from "solid-js/types/reactive/signal.js";
/**
* Type for the `ref` prop
*/
export type Ref<T> = T | ((el: T) => void) | undefined;
/**
* Component properties with types for `ref` prop
* ```ts
* {
* ref?: T | ((el: T) => void);
* }
* ```
*/
export interface RefProps<T> {
ref?: Ref<T>;
}
/**
* Utility for chaining multiple `ref` assignments with `props.ref` forwarding.
* @param refs list of ref setters. Can be a `props.ref` prop for ref forwarding or a setter to a local variable (`el => ref = el`).
* @example
* ```tsx
* interface ButtonProps {
* ref?: Ref<HTMLButtonElement>
* }
* function Button (props: ButtonProps) {
* let ref: HTMLButtonElement | undefined
* onMount(() => {
* // use the local ref
* })
* return <button ref={mergeRefs(props.ref, el => ref = el)} />
* }
*
* // in consumer's component:
* let ref: HTMLButtonElement | undefined
* <Button ref={ref} />
* ```
*/
export declare function mergeRefs<T>(...refs: Ref<T>[]): (el: T) => void;
/**
* Default predicate used in `resolveElements()` and `resolveFirst()` to filter Elements.
*
* On the client it uses `instanceof Element` check, on the server it checks for the object with `t` property. (generated by compiling JSX)
*/
export declare const defaultElementPredicate: (item: JSX.Element | Element) => item is Element;
/**
* Utility for resolving recursively nested JSX children to a single element or an array of elements using a predicate.
*
* It does **not** create a computation - should be wrapped in one to repeat the resolution on changes.
*
* @param value JSX children
* @param predicate predicate to filter elements
* @returns single element or an array of elements or `null` if no elements were found
*/
export declare function getResolvedElements<T extends object>(value: JSX.Element, predicate: (item: JSX.Element | T) => item is T): T | T[] | null;
export type ResolveChildrenReturn<T extends object> = Accessor<T | T[] | null> & {
toArray: () => T[];
};
/**
* Utility for resolving recursively nested JSX children to a single element or an array of elements using a predicate.
*
* @param fn Accessor of JSX children
* @param predicate predicate to filter elements.
* ```ts
* // default predicate
* (item: JSX.Element): item is Element => item instanceof Element
* ```
* @param serverPredicate predicate to filter elements on server. {@link defaultElementPredicate}
* @returns Signal of a single element or an array of elements or `null` if no elements were found
* ```ts
* Accessor<T | T[] | null> & { toArray: () => T[] }
* ```
* @example
* ```tsx
* function Button(props: ParentProps) {
* const children = resolveElements(() => props.children)
* return <For each={children.toArray()}>
* {child => <div>{child.localName}: {child}</div>}
* </For>
* }
* ```
*/
export declare function resolveElements(fn: Accessor<JSX.Element>): ResolveChildrenReturn<Element>;
export declare function resolveElements<T extends object & JSX.Element>(fn: Accessor<JSX.Element>, predicate: (item: JSX.Element) => item is T, serverPredicate?: (item: JSX.Element) => item is T): ResolveChildrenReturn<T>;
export declare function resolveElements<T extends object>(fn: Accessor<JSX.Element>, predicate: (item: JSX.Element | T) => item is T, serverPredicate?: (item: JSX.Element | T) => item is T): ResolveChildrenReturn<T>;
/**
* Utility for resolving recursively nested JSX children in search of the first element that matches a predicate.
*
* It does **not** create a computation - should be wrapped in one to repeat the resolution on changes.
*
* @param value JSX children
* @param predicate predicate to filter elements
* @returns single found element or `null` if no elements were found
*/
export declare function getFirstChild<T extends object>(value: JSX.Element, predicate: (item: JSX.Element | T) => item is T): T | null;
/**
* Utility for resolving recursively nested JSX children in search of the first element that matches a predicate.
* @param fn Accessor of JSX children
* @param predicate predicate to filter elements.
* ```ts
* // default predicate
* (item: JSX.Element): item is Element => item instanceof Element
* ```
* @param serverPredicate predicate to filter elements on server. {@link defaultElementPredicate}
* @returns Signal of a single found element or `null` if no elements were found
* ```ts
* Accessor<T | null>
* ```
* @example
* ```tsx
* function Button(props: ParentProps) {
* const child = resolveFirst(() => props.children)
* return <div>{child()?.localName}: {child()}</div>
* }
* ```
*/
export declare function resolveFirst(fn: Accessor<JSX.Element>): Accessor<Element | null>;
export declare function resolveFirst<T extends object & JSX.Element>(fn: Accessor<JSX.Element>, predicate: (item: JSX.Element) => item is T, serverPredicate?: (item: JSX.Element) => item is T): Accessor<T | null>;
export declare function resolveFirst<T extends object>(fn: Accessor<JSX.Element>, predicate: (item: JSX.Element | T) => item is T, serverPredicate?: (item: JSX.Element | T) => item is T): Accessor<T | null>;
/**
* Get up-to-date references of the multiple children elements.
* @param ref Getter of current array of elements
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#Refs
* @example
* ```tsx
* const [refs, setRefs] = createSignal<Element[]>([]);
* <Refs ref={setRefs}>
* {props.children}
* </Refs>
* ```
*/
export declare function Refs(props: {
ref: Ref<Element[]>;
children: JSX.Element;
}): JSX.Element;
/**
* Get up-to-date reference to a single child element.
* @param ref Getter of current element *(or `undefined` if not mounted)*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#Ref
* @example
* ```tsx
* const [ref, setRef] = createSignal<Element | undefined>();
* <Ref ref={setRef}>
* {props.children}
* </Ref>
* ```
*/
export declare function Ref(props: {
ref: Ref<Element | undefined>;
children: JSX.Element;
}): JSX.Element;
+150
View File
@@ -0,0 +1,150 @@
import { chain, arrayEquals } from "@solid-primitives/utils";
import { children, createComputed, createMemo, onCleanup, untrack, } from "solid-js";
import { isServer } from "solid-js/web";
/**
* Utility for chaining multiple `ref` assignments with `props.ref` forwarding.
* @param refs list of ref setters. Can be a `props.ref` prop for ref forwarding or a setter to a local variable (`el => ref = el`).
* @example
* ```tsx
* interface ButtonProps {
* ref?: Ref<HTMLButtonElement>
* }
* function Button (props: ButtonProps) {
* let ref: HTMLButtonElement | undefined
* onMount(() => {
* // use the local ref
* })
* return <button ref={mergeRefs(props.ref, el => ref = el)} />
* }
*
* // in consumer's component:
* let ref: HTMLButtonElement | undefined
* <Button ref={ref} />
* ```
*/
export function mergeRefs(...refs) {
return chain(refs);
}
/**
* Default predicate used in `resolveElements()` and `resolveFirst()` to filter Elements.
*
* On the client it uses `instanceof Element` check, on the server it checks for the object with `t` property. (generated by compiling JSX)
*/
export const defaultElementPredicate = isServer
? (item) => item != null && typeof item === "object" && "t" in item
: (item) => item instanceof Element;
/**
* Utility for resolving recursively nested JSX children to a single element or an array of elements using a predicate.
*
* It does **not** create a computation - should be wrapped in one to repeat the resolution on changes.
*
* @param value JSX children
* @param predicate predicate to filter elements
* @returns single element or an array of elements or `null` if no elements were found
*/
export function getResolvedElements(value, predicate) {
if (predicate(value))
return value;
if (typeof value === "function" && !value.length)
return getResolvedElements(value(), predicate);
if (Array.isArray(value)) {
const results = [];
for (const item of value) {
const result = getResolvedElements(item, predicate);
if (result)
Array.isArray(result) ? results.push.apply(results, result) : results.push(result);
}
return results.length ? results : null;
}
return null;
}
export function resolveElements(fn, predicate = defaultElementPredicate, serverPredicate = defaultElementPredicate) {
const children = createMemo(fn);
const memo = createMemo(() => getResolvedElements(children(), isServer ? serverPredicate : predicate));
memo.toArray = () => {
const value = memo();
return Array.isArray(value) ? value : value ? [value] : [];
};
return memo;
}
/**
* Utility for resolving recursively nested JSX children in search of the first element that matches a predicate.
*
* It does **not** create a computation - should be wrapped in one to repeat the resolution on changes.
*
* @param value JSX children
* @param predicate predicate to filter elements
* @returns single found element or `null` if no elements were found
*/
export function getFirstChild(value, predicate) {
if (predicate(value))
return value;
if (typeof value === "function" && !value.length)
return getFirstChild(value(), predicate);
if (Array.isArray(value)) {
for (const item of value) {
const result = getFirstChild(item, predicate);
if (result)
return result;
}
}
return null;
}
export function resolveFirst(fn, predicate = defaultElementPredicate, serverPredicate = defaultElementPredicate) {
const children = createMemo(fn);
return createMemo(() => getFirstChild(children(), isServer ? serverPredicate : predicate));
}
/**
* Get up-to-date references of the multiple children elements.
* @param ref Getter of current array of elements
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#Refs
* @example
* ```tsx
* const [refs, setRefs] = createSignal<Element[]>([]);
* <Refs ref={setRefs}>
* {props.children}
* </Refs>
* ```
*/
export function Refs(props) {
if (isServer) {
return props.children;
}
const cb = props.ref, resolved = children(() => props.children);
let prev = [];
createComputed(() => {
const els = resolved.toArray().filter(defaultElementPredicate);
if (!arrayEquals(prev, els))
untrack(() => cb(els));
prev = els;
}, []);
onCleanup(() => prev.length && cb([]));
return resolved;
}
/**
* Get up-to-date reference to a single child element.
* @param ref Getter of current element *(or `undefined` if not mounted)*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#Ref
* @example
* ```tsx
* const [ref, setRef] = createSignal<Element | undefined>();
* <Ref ref={setRef}>
* {props.children}
* </Ref>
* ```
*/
export function Ref(props) {
if (isServer) {
return props.children;
}
const cb = props.ref, resolved = children(() => props.children);
let prev;
createComputed(() => {
const el = resolved.toArray().find(defaultElementPredicate);
if (el !== prev)
untrack(() => cb(el));
prev = el;
});
onCleanup(() => prev && cb(undefined));
return resolved;
}
+64
View File
@@ -0,0 +1,64 @@
{
"name": "@solid-primitives/refs",
"version": "1.1.3",
"description": "Library of primitives, components and directives for SolidJS that help managing references to JSX elements.",
"author": "Damian Tarnawski @thetarnav <gthetarnav@gmail.com>",
"license": "MIT",
"homepage": "https://primitives.solidjs.community/package/refs",
"repository": {
"type": "git",
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
},
"primitive": {
"name": "refs",
"stage": 2,
"list": [
"mergeRefs",
"resolveElements",
"resolveFirst",
"Ref",
"Refs"
],
"category": "Control Flow"
},
"private": false,
"sideEffects": false,
"files": [
"dist"
],
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"browser": {},
"exports": {
"import": {
"@solid-primitives/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"typesVersions": {},
"keywords": [
"elements",
"ref",
"solid",
"primitives"
],
"dependencies": {
"@solid-primitives/utils": "^6.4.0"
},
"devDependencies": {
"solid-js": "^1.9.7",
"solid-transition-group": "^0.2.3"
},
"peerDependencies": {
"solid-js": "^1.6.12"
},
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Solid Primitives Working Group
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.
+199
View File
@@ -0,0 +1,199 @@
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=transition" alt="Solid Primitives transition">
</p>
# @solid-primitives/transition-group
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/transition-group?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/transition-group)
[![version](https://img.shields.io/npm/v/@solid-primitives/transition-group?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/transition-group)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
Provides reactive primitives for implementing transition effects on a group of elements, or your own `<Transition>` and `<TransitionGroup>` components.
- [`createSwitchTransition`](#createswitchtransition) - Create an element transition interface for switching between single elements.
- [`createListTransition`](#createlisttransition) - Create an element list transition interface for changes to the list of elements.
## Installation
```bash
npm install @solid-primitives/transition-group
# or
yarn add @solid-primitives/transition-group
# or
pnpm add @solid-primitives/transition-group
```
## `createSwitchTransition`
Create an element transition interface for switching between single elements.
It can be used to implement own transition effect, or a custom `<Transition>`-like component.
### How to use it
It will observe the source and return a signal with array of elements to be rendered (current one and exiting ones).
`createSwitchTransition` takes two parameters:
- `source` a signal with the current element. Any nullish value will mean there is no element.
Any object can used as the source, but most likely you will want to use a `HTMLElement` or `SVGElement`.
- `options` transition options:
- `onEnter` - a function to be called when a new element is entering. It receives the element and a callback to be called when the transition is done.
- `onExit` - a function to be called when an exiting element is leaving. It receives the element and a callback to be called when the transition is done.
- `mode` - transition mode. Defaults to `"parallel"`. Other options are `"out-in"` and `"in-out"`.
- `appear` - whether to run the transition on the initial element. Defaults to `false`.
If enabled, the initial element will still be included in the initial render (for SSR), but the transition fill happen when the first client-side effect is run. So to avoid the initial element to be visible, you can set the initial element's style to `display: none` and set it to `display: block` in the `onEnter` callback.
Returns a signal with an array of the current element and exiting previous elements.
```ts
import { createSwitchTransition } from "@solid-primitives/transition-group";
const [el, setEl] = createSignal<HTMLDivElement>();
const rendered = createSwitchTransition(el, {
onEnter(el, done) {
// the enter callback is called before the element is inserted into the DOM
// so run the animation in the next animation frame / microtask
queueMicrotask(() => {
/*...*/
});
},
onExit(el, done) {
// the exitting element is kept in the DOM until the done() callback is called
},
});
// change the source to trigger the transition
setEl(refToHtmlElement);
```
### Resolving JSX
Usually the source will be a JSX element, and you will want to resolve it to a DOM element before passing it to `createSwitchTransition`. It leaves the resolving to you, so you can do it in any way you want.
For example, you can `children` helper from `solid-js`, to get the first found HTML element.
```ts
import { children } from "solid-js";
import { createSwitchTransition } from "@solid-primitives/transition-group";
const resolved = children(() => props.children);
const filtered = createMemo(() => resolved.toArray().find(el => el instanceof HTMLElement));
return createSwitchTransition(filtered, {
/*...*/
});
```
Or use a `resolveFirst` helper from `@solid-primitives/refs`
```ts
import { resolveFirst } from "@solid-primitives/refs";
import { createSwitchTransition } from "@solid-primitives/transition-group";
const resolved = resolveFirst(() => props.children);
return createSwitchTransition(resolved, {
/*...*/
});
```
## `createListTransition`
Create an element list transition interface for changes to the list of elements.
It can be used to implement own transition effect, or a custom `<TransitionGroup>`-like component.
### How to use it
It will observe the source and return a signal with array of elements to be rendered (current ones and exiting ones).
`createListTransition` takes two parameters:
- `source` a signal with the current list of elements.
Any object can used as the element, but most likely you will want to use a `HTMLElement` or `SVGElement`.
- `options` transition options:
- `onChange` - a function to be called when the list changes. It receives the list of added elements, removed elements, and moved elements. It also receives a callback to be called when the removed elements are finished animating (they can be removed from the DOM).
- `appear` - whether to run the transition on the initial elements. Defaults to `false`.
If enabled, the initial elements will still be included in the initial render (for SSR), but the transition fill happen when the first client-side effect is run. So to avoid the initial elements to be visible, you can set the initial element's style to `display: none` and set it to `display: block` in the `onChange` callback.
- `exitMethod` - This controls how the elements exit.
- `"remove"` removes the element immediately.
- `"move-to-end"` (default) will move elements which have exited to the end of the array.
- `"keep-index"` will splice them in at their previous index.
Returns a signal with an array of the current elements and exiting previous elements.
```ts
import { createListTransition } from "@solid-primitives/transition-group";
const [els, setEls] = createSignal<HTMLElement[]>([]);
const rendered = createListTransition(els, {
onChange({ list, added, removed, unchanged, finishRemoved }) {
// the callback is called before the added elements are inserted into the DOM
// so run the animation in the next animation frame / microtask
queueMicrotask(() => {
/*...*/
});
// the removed elements are kept in the DOM until the finishRemoved() callback is called
finishRemoved(removed);
},
});
// change the source to trigger the transition
setEls([...refsToHTMLElements]);
```
### Resolving JSX
Usually the source will be a JSX Element, and you will want to resolve it to a list of DOM elements before passing it to `createListTransition`. It leaves the resolving to you, so you can do it in any way you want.
For example, you can `children` helper from `solid-js`, and filter out non-HTML elements:
```ts
import { children } from "solid-js";
import { createListTransition } from "@solid-primitives/transition-group";
const resolved = children(() => props.children);
const filtered = createMemo(() => resolved.toArray().filter(el => el instanceof HTMLElement));
return createListTransition(filtered, {
/*...*/
});
```
Or use a `resolveElements` helper from `@solid-primitives/refs`
```ts
import { resolveElements } from "@solid-primitives/refs";
import { createSwitchTransition } from "@solid-primitives/transition-group";
const resolved = resolveElements(() => props.children);
return createListTransition(resolved.toArray, {
/*...*/
});
```
## Demo
[Deployed example](https://primitives.solidjs.community/playground/transition-group) | [Source code](https://github.com/solidjs-community/solid-primitives/tree/main/packages/transition-group/dev)
## Usage references
Packages that use `@solid-primitives/transition-group`:
- [`solid-transition-group`](https://github.com/solidjs-community/solid-transition-group/tree/main/src)
- [`motionone/solid`](https://github.com/motiondivision/motionone/tree/main/packages/solid/src)
## Changelog
See [CHANGELOG.md](./CHANGELOG.md)
+118
View File
@@ -0,0 +1,118 @@
import { type Accessor } from "solid-js";
export type TransitionMode = "out-in" | "in-out" | "parallel";
export type OnTransition<T> = (el: T, done: () => void) => void;
export type SwitchTransitionOptions<T> = {
/**
* a function to be called when a new element is entering. {@link OnTransition}
*
* It receives the element and a callback to be called when the transition is done.
*/
onEnter?: OnTransition<T>;
/**
* a function to be called when an exiting element is leaving. {@link OnTransition}
*
* It receives the element and a callback to be called when the transition is done.
* The element is kept in the DOM until the done() callback is called.
*/
onExit?: OnTransition<T>;
/**
* transition mode. {@link TransitionMode}
*
* Defaults to `"parallel"`. Other options are `"out-in"` and `"in-out"`.
*/
mode?: TransitionMode;
/** whether to run the transition on the initial elements. Defaults to `false` */
appear?: boolean;
};
/**
* Create an element transition interface for switching between single elements.
* It can be used to implement own transition effect, or a custom `<Transition>`-like component.
*
* It will observe {@link source} and return a signal with array of elements to be rendered (current one and exiting ones).
*
* @param source a signal with the current element. Any nullish value will mean there is no element.
* Any object can used as the source, but most likely you will want to use a `HTMLElement` or `SVGElement`.
* @param options transition options {@link SwitchTransitionOptions}
* @returns a signal with an array of the current element and exiting previous elements.
*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/transition-group#createSwitchTransition
*
* @example
* const [el, setEl] = createSignal<HTMLDivElement>();
*
* const rendered = createSwitchTransition(el, {
* onEnter(el, done) {
* // the enter callback is called before the element is inserted into the DOM
* // so run the animation in the next animation frame / microtask
* queueMicrotask(() => { ... })
* },
* onExit(el, done) {
* // the exitting element is kept in the DOM until the done() callback is called
* },
* })
*
* // change the source to trigger the transition
* setEl(refToHtmlElement);
*/
export declare function createSwitchTransition<T>(source: Accessor<T>, options: SwitchTransitionOptions<NonNullable<T>>): Accessor<NonNullable<T>[]>;
export type OnListChange<T> = (payload: {
/** full list of elements to be rendered */
list: T[];
/** list of elements that were added since the last change */
added: T[];
/** list of elements that were removed since the last change */
removed: T[];
/** list of elements that were already added before, and are not currently exiting */
unchanged: T[];
/** Callback for finishing the transition of exiting elements - removes them from rendered array */
finishRemoved: (els: T[]) => void;
}) => void;
export type ExitMethod = "remove" | "move-to-end" | "keep-index";
export type ListTransitionOptions<T> = {
/**
* A function to be called when the list changes. {@link OnListChange}
*
* It receives the list of current, added, removed, and unchanged elements.
* It also receives a callback to be called when the removed elements are finished animating (they can be removed from the DOM).
*/
onChange: OnListChange<T>;
/** whether to run the transition on the initial elements. Defaults to `false` */
appear?: boolean;
/**
* This controls how the elements exit. {@link ExitMethod}
* - `"remove"` removes the element immediately.
* - `"move-to-end"` (default) will move elements which have exited to the end of the array.
* - `"keep-index"` will splice them in at their previous index.
*/
exitMethod?: ExitMethod;
};
/**
* Create an element list transition interface for changes to the list of elements.
* It can be used to implement own transition effect, or a custom `<TransitionGroup>`-like component.
*
* It will observe {@link source} and return a signal with array of elements to be rendered (current ones and exiting ones).
*
* @param source a signal with the current list of elements.
* Any object can used as the element, but most likely you will want to use a `HTMLElement` or `SVGElement`.
* @param options transition options {@link ListTransitionOptions}
*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/transition-group#createListTransition
*
* @example
* const [els, setEls] = createSignal<HTMLElement[]>([]);
*
* const rendered = createListTransition(els, {
* onChange({ list, added, removed, unchanged, finishRemoved }) {
* // the callback is called before the added elements are inserted into the DOM
* // so run the animation in the next animation frame / microtask
* queueMicrotask(() => { ... })
*
* // the removed elements are kept in the DOM until the finishRemoved() callback is called
* finishRemoved(removed);
* }
* })
*
* // change the source to trigger the transition
* setEls([...refsToHTMLElements]);
*/
export declare function createListTransition<T extends object>(source: Accessor<readonly T[]>, options: ListTransitionOptions<T>): Accessor<T[]>;
+196
View File
@@ -0,0 +1,196 @@
import { batch, createSignal, untrack, $TRACK, createComputed, createMemo, useTransition, } from "solid-js";
import { isServer } from "solid-js/web";
const noop = () => {
/* noop */
};
const noopTransition = (el, done) => done();
/**
* Create an element transition interface for switching between single elements.
* It can be used to implement own transition effect, or a custom `<Transition>`-like component.
*
* It will observe {@link source} and return a signal with array of elements to be rendered (current one and exiting ones).
*
* @param source a signal with the current element. Any nullish value will mean there is no element.
* Any object can used as the source, but most likely you will want to use a `HTMLElement` or `SVGElement`.
* @param options transition options {@link SwitchTransitionOptions}
* @returns a signal with an array of the current element and exiting previous elements.
*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/transition-group#createSwitchTransition
*
* @example
* const [el, setEl] = createSignal<HTMLDivElement>();
*
* const rendered = createSwitchTransition(el, {
* onEnter(el, done) {
* // the enter callback is called before the element is inserted into the DOM
* // so run the animation in the next animation frame / microtask
* queueMicrotask(() => { ... })
* },
* onExit(el, done) {
* // the exitting element is kept in the DOM until the done() callback is called
* },
* })
*
* // change the source to trigger the transition
* setEl(refToHtmlElement);
*/
export function createSwitchTransition(source, options) {
const initSource = untrack(source);
const initReturned = initSource ? [initSource] : [];
if (isServer) {
return () => initReturned;
}
const { onEnter = noopTransition, onExit = noopTransition } = options;
const [returned, setReturned] = createSignal(options.appear ? [] : initReturned);
const [isTransitionPending] = useTransition();
let next;
let isExiting = false;
function exitTransition(el, after) {
if (!el)
return after && after();
isExiting = true;
onExit(el, () => {
batch(() => {
isExiting = false;
setReturned(p => p.filter(e => e !== el));
after && after();
});
});
}
function enterTransition(after) {
const el = next;
if (!el)
return after && after();
next = undefined;
setReturned(p => [el, ...p]);
onEnter(el, after ?? noop);
}
const triggerTransitions = options.mode === "out-in"
? // exit -> enter
// exit -> enter
prev => isExiting || exitTransition(prev, enterTransition)
: options.mode === "in-out"
? // enter -> exit
// enter -> exit
prev => enterTransition(() => exitTransition(prev))
: // exit & enter
// exit & enter
prev => {
exitTransition(prev);
enterTransition();
};
createComputed((prev) => {
const el = source();
if (untrack(isTransitionPending)) {
// wait for pending transition to end before animating
isTransitionPending();
return prev;
}
if (el !== prev) {
next = el;
batch(() => untrack(() => triggerTransitions(prev)));
}
return el;
}, options.appear ? undefined : initSource);
return returned;
}
/**
* Create an element list transition interface for changes to the list of elements.
* It can be used to implement own transition effect, or a custom `<TransitionGroup>`-like component.
*
* It will observe {@link source} and return a signal with array of elements to be rendered (current ones and exiting ones).
*
* @param source a signal with the current list of elements.
* Any object can used as the element, but most likely you will want to use a `HTMLElement` or `SVGElement`.
* @param options transition options {@link ListTransitionOptions}
*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/transition-group#createListTransition
*
* @example
* const [els, setEls] = createSignal<HTMLElement[]>([]);
*
* const rendered = createListTransition(els, {
* onChange({ list, added, removed, unchanged, finishRemoved }) {
* // the callback is called before the added elements are inserted into the DOM
* // so run the animation in the next animation frame / microtask
* queueMicrotask(() => { ... })
*
* // the removed elements are kept in the DOM until the finishRemoved() callback is called
* finishRemoved(removed);
* }
* })
*
* // change the source to trigger the transition
* setEls([...refsToHTMLElements]);
*/
export function createListTransition(source, options) {
const initSource = untrack(source);
if (isServer) {
const copy = initSource.slice();
return () => copy;
}
const { onChange } = options;
// if appear is enabled, the initial transition won't have any previous elements.
// otherwise the elements will match and transition skipped, or transitioned if the source is different from the initial value
let prevSet = new Set(options.appear ? undefined : initSource);
const exiting = new WeakSet();
const [toRemove, setToRemove] = createSignal([], { equals: false });
const [isTransitionPending] = useTransition();
const finishRemoved = options.exitMethod === "remove"
? noop
: els => {
setToRemove(p => (p.push.apply(p, els), p));
for (const el of els)
exiting.delete(el);
};
const handleRemoved = options.exitMethod === "remove"
? noop
: options.exitMethod === "keep-index"
? (els, el, i) => els.splice(i, 0, el)
: (els, el) => els.push(el);
return createMemo(prev => {
const elsToRemove = toRemove();
const sourceList = source();
sourceList[$TRACK]; // top level store tracking
if (untrack(isTransitionPending)) {
// wait for pending transition to end before animating
isTransitionPending();
return prev;
}
if (elsToRemove.length) {
const next = prev.filter(e => !elsToRemove.includes(e));
elsToRemove.length = 0;
onChange({ list: next, added: [], removed: [], unchanged: next, finishRemoved });
return next;
}
return untrack(() => {
const nextSet = new Set(sourceList);
const next = sourceList.slice();
const added = [];
const removed = [];
const unchanged = [];
for (const el of sourceList) {
(prevSet.has(el) ? unchanged : added).push(el);
}
let nothingChanged = !added.length;
for (let i = 0; i < prev.length; i++) {
const el = prev[i];
if (!nextSet.has(el)) {
if (!exiting.has(el)) {
removed.push(el);
exiting.add(el);
}
handleRemoved(next, el, i);
}
if (nothingChanged && el !== next[i])
nothingChanged = false;
}
// skip if nothing changed
if (!removed.length && nothingChanged)
return prev;
onChange({ list: next, added, removed, unchanged, finishRemoved });
prevSet = nextSet;
return next;
});
}, options.appear ? [] : initSource.slice());
}
+64
View File
@@ -0,0 +1,64 @@
{
"name": "@solid-primitives/transition-group",
"version": "1.1.2",
"description": "Reactive primitives for implementing transition effects in SolidJS",
"author": "Damian Tarnawski <gthetarnav@gmail.com>",
"contributors": [],
"license": "MIT",
"homepage": "https://primitives.solidjs.community/package/transition-group",
"repository": {
"type": "git",
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
},
"bugs": {
"url": "https://github.com/solidjs-community/solid-primitives/issues"
},
"primitive": {
"name": "transition-group",
"stage": 2,
"list": [
"createSwitchTransition",
"createListTransition"
],
"category": "Animation"
},
"keywords": [
"solid",
"primitives",
"transition",
"animation",
"transition-group"
],
"private": false,
"sideEffects": false,
"files": [
"dist"
],
"type": "module",
"module": "./dist/index.js",
"browser": {},
"types": "./dist/index.d.ts",
"exports": {
"import": {
"@solid-primitives/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"peerDependencies": {
"solid-js": "^1.6.12"
},
"devDependencies": {
"solid-js": "^1.9.7",
"@solid-primitives/refs": "^1.1.2",
"@solid-primitives/utils": "^6.3.2"
},
"typesVersions": {},
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Solid Primitives Working Group
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.
+120
View File
@@ -0,0 +1,120 @@
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=Utils" alt="Solid Primitives Utils">
</p>
# @solid-primitives/utils
Solid Primitives Utilities is a support and helper package for a number of primitives in our library. Please free to augment or centralize useful utilities and methods in this package for sharing.
## Installation
```bash
npm install @solid-primitives/utils
# or
pnpm add @solid-primitives/utils
# or
yarn add @solid-primitives/utils
```
## Immutable helpers
Functional programming helpers for making non-mutating changes to data. Keeping it immutable. Useful for updating signals.
```ts
import { pick } from "@solid-primitives/utils/immutable";
const original = { foo: 123, bar: "baz" };
const newObj = pick(original, "foo");
original; // { foo: 123, bar: "baz" }
newObj; // { foo: 123 }
```
Use it for changing signals:
```ts
import { push, update } from "@solid-primitives/utils/immutable";
const [list, setList] = createSignal([1, 2, 3]);
setList(p => push(p, 4));
const [user, setUser] = createSignal({
name: "John",
street: { name: "Kingston Cei", number: 24 },
});
setUser(p => update(p, "street", "number", 64));
```
## List of functions:
### Copying
- **`shallowArrayCopy`** - make shallow copy of an array
- **`shallowObjectCopy`** - make shallow copy of an object
- **`shallowCopy`** - make shallow copy of an array/object
- **`withArrayCopy`** - apply mutations to the an array without changing the original
- **`withObjectCopy`** - apply mutations to the an object without changing the original
- **`withCopy`** - apply mutations to the an object/array without changing the original
### Array
- **`push`** - non-mutating `Array.prototype.push()`
- **`drop`** - non-mutating function that drops n items from the array start
- **`dropRight`** - non-mutating function that drops n items from the array end
- **`filterOut`** - standalone `Array.prototype.filter()` that filters out passed item
- **`filter`** - standalone `Array.prototype.filter()`
- **`sort`** - non-mutating `Array.prototype.sort()` as a standalone function
- **`sortBy`** - Sort an array by object key, or multiple keys
- **`map`** - standalone `Array.prototype.map()` function
- **`slice`** - standalone `Array.prototype.slice()` function
- **`splice`** - non-mutating `Array.prototype.splice()` as a standalone function
- **`fill`** - non-mutating `Array.prototype.fill()` as a standalone function
- **`concat`** - Creates a new array concatenating array with any additional arrays and/or values.
- **`remove`** - Remove item from array
- **`removeItems`** - Remove multiple items from an array
- **`flatten`** - Flattens a nested array into a one-level array
- **`filterInstance`** - Flattens a nested array into a one-level array
- **`filterOutInstance`** - Flattens a nested array into a one-level array
### Object
- **`omit`** - Create a new subset object without the provided keys
- **`pick`** - Create a new subset object with only the provided keys
- **`split`** - Split object into multiple subset objects.
- **`merge`** - Merges multiple objects into a single one.
### Object/Array
- **`get`** - Get a single property value of an object by specifying a path to it.
- **`update`** - Change single value in an object by key, or series of recursing keys.
### Number
- **`add`** - `a + b + c + ...` _(works for numbers or strings)_
- **`substract`** - `a - b - c - ...`
- **`multiply`** - `a * b * c * ...`
- **`divide`** - `a / b / c / ...`
- **`power`** - `a ** b ** c ** ...`
- **`clamp`** - clamp a number value between two other values
## String transforms
`(string) => T` transform functions for converting raw string data into typed values. Useful as the `transform` option for SSE, WebSocket, and similar streaming primitives.
```ts
import { json, ndjson, safe } from "@solid-primitives/utils";
const { data } = createSSE<Event>(url, { transform: json });
const { data } = createSSE<Event[]>(url, { transform: ndjson });
const { data } = createSSE<Event>(url, { transform: safe(json) });
```
- **`json`** - Parse a string as a single JSON value
- **`ndjson`** - Parse newline-delimited JSON (NDJSON / JSON Lines) into an array
- **`lines`** - Split a string into a `string[]` by newline, filtering empty lines
- **`number`** - Parse a string as a number via `Number()`
- **`safe(transform, fallback?)`** - Wrap any transform in a `try/catch`; returns `fallback` instead of throwing
- **`pipe(a, b)`** - Compose two transforms into one
## Changelog
See [CHANGELOG.md](./CHANGELOG.md)
+112
View File
@@ -0,0 +1,112 @@
import { type AnyClass, type ItemsOf, type Many } from "../index.js";
import type { FlattenArray, MappingFn, Predicate } from "./types.js";
/**
* non-mutating `Array.prototype.push()`
* @returns changed array copy
*/
export declare const push: <T>(list: readonly T[], ...items: T[]) => T[];
/**
* non-mutating function that drops n items from the array start.
* @returns changed array copy
*
* @example
* ```ts
* const newList = drop([1,2,3])
* newList // => [2,3]
*
* const newList = drop([1,2,3], 2)
* newList // => [3]
* ```
*/
export declare const drop: <T>(list: T[], n?: number) => T[];
/**
* non-mutating function that drops n items from the array end.
* @returns changed array copy
*
* @example
* ```ts
* const newList = dropRight([1,2,3])
* newList // => [1,2]
*
* const newList = dropRight([1,2,3], 2)
* newList // => [1]
* ```
*/
export declare const dropRight: <T>(list: T[], n?: number) => T[];
/**
* standalone `Array.prototype.filter()` that filters out passed item
* @returns changed array copy
*/
export declare const filterOut: <T>(list: readonly T[], item: T) => T[] & {
removed: number;
};
/**
* standalone `Array.prototype.filter()`
* @returns changed array copy
*/
export declare function filter<T>(list: readonly T[], predicate: Predicate<T>): T[] & {
removed: number;
};
/**
* non-mutating `Array.prototype.sort()` as a standalone function
* @returns changed array copy
*/
export declare const sort: <T>(list: T[], compareFn?: (a: T, b: T) => number) => T[];
/**
* standalone `Array.prototype.map()` function
*/
export declare const map: <T, V>(list: readonly T[], mapFn: MappingFn<T, V>) => V[];
/**
* standalone `Array.prototype.slice()` function
*/
export declare const slice: <T>(list: readonly T[], start?: number, end?: number) => T[];
/**
* non-mutating `Array.prototype.splice()` as a standalone function
* @returns changed array copy
*/
export declare const splice: <T>(list: readonly T[], start: number, deleteCount?: number, ...items: T[]) => T[];
/**
* non-mutating `Array.prototype.fill()` as a standalone function
* @returns changed array copy
*/
export declare const fill: <T>(list: readonly T[], value: T, start?: number, end?: number) => T[];
/**
* Creates a new array concatenating array with any additional arrays and/or values.
* @param ...a values or arrays
* @returns new concatenated array
*/
export declare function concat<A extends any[], V extends ItemsOf<A>>(...a: A): Array<V extends any[] ? ItemsOf<V> : V>;
/**
* Remove item from array
* @returns changed array copy
*/
export declare const remove: <T>(list: readonly T[], item: T, ...insertItems: T[]) => T[];
/**
* Remove multiple items from an array
* @returns changed array copy
*/
export declare const removeItems: <T>(list: readonly T[], ...items: T[]) => T[];
/**
* Flattens a nested array into a one-level array
* @returns changed array copy
*/
export declare const flatten: <T extends any[]>(arr: T) => FlattenArray<T>[];
/**
* Sort an array by object key, or multiple keys
* @returns changed array copy
*/
export declare const sortBy: <T>(arr: T[], ...paths: T extends object ? (Many<keyof T> | Many<(item: T) => any>)[] : Many<(item: T) => any>[]) => T[];
/**
* Returns a subset of items that are instances of provided Classes
* @param list list of original items
* @param ...classes list or classes
* @returns changed array copy
*/
export declare const filterInstance: <T, I extends AnyClass[]>(list: readonly T[], ...classes: I) => Extract<T, InstanceType<ItemsOf<I>>>[];
/**
* Returns a subset of items that aren't instances of provided Classes
* @param list list of original items
* @param ...classes list or classes
* @returns changed array copy
*/
export declare const filterOutInstance: <T, I extends AnyClass[]>(list: readonly T[], ...classes: I) => Exclude<T, InstanceType<ItemsOf<I>>>[];
+139
View File
@@ -0,0 +1,139 @@
import { compare, ofClass } from "../index.js";
import { withArrayCopy } from "./copy.js";
import { get } from "./object.js";
/**
* non-mutating `Array.prototype.push()`
* @returns changed array copy
*/
export const push = (list, ...items) => withArrayCopy(list, list => list.push(...items));
/**
* non-mutating function that drops n items from the array start.
* @returns changed array copy
*
* @example
* ```ts
* const newList = drop([1,2,3])
* newList // => [2,3]
*
* const newList = drop([1,2,3], 2)
* newList // => [3]
* ```
*/
export const drop = (list, n = 1) => list.slice(n);
/**
* non-mutating function that drops n items from the array end.
* @returns changed array copy
*
* @example
* ```ts
* const newList = dropRight([1,2,3])
* newList // => [1,2]
*
* const newList = dropRight([1,2,3], 2)
* newList // => [1]
* ```
*/
export const dropRight = (list, n = 1) => list.slice(0, list.length - n);
/**
* standalone `Array.prototype.filter()` that filters out passed item
* @returns changed array copy
*/
export const filterOut = (list, item) => filter(list, i => i !== item);
/**
* standalone `Array.prototype.filter()`
* @returns changed array copy
*/
export function filter(list, predicate) {
const newList = list.filter(predicate);
newList.removed = list.length - newList.length;
return newList;
}
/**
* non-mutating `Array.prototype.sort()` as a standalone function
* @returns changed array copy
*/
export const sort = (list, compareFn) => list.slice().sort(compareFn);
/**
* standalone `Array.prototype.map()` function
*/
export const map = (list, mapFn) => list.map(mapFn);
/**
* standalone `Array.prototype.slice()` function
*/
export const slice = (list, start, end) => list.slice(start, end);
/**
* non-mutating `Array.prototype.splice()` as a standalone function
* @returns changed array copy
*/
export const splice = (list, start, deleteCount = 0, ...items) => withArrayCopy(list, list => list.splice(start, deleteCount, ...items));
/**
* non-mutating `Array.prototype.fill()` as a standalone function
* @returns changed array copy
*/
export const fill = (list, value, start, end) => list.slice().fill(value, start, end);
/**
* Creates a new array concatenating array with any additional arrays and/or values.
* @param ...a values or arrays
* @returns new concatenated array
*/
export function concat(...a) {
const result = [];
for (const i in a) {
Array.isArray(a[i]) ? result.push(...a[i]) : result.push(a[i]);
}
return result;
}
/**
* Remove item from array
* @returns changed array copy
*/
export const remove = (list, item, ...insertItems) => {
const index = list.indexOf(item);
return splice(list, index, 1, ...insertItems);
};
/**
* Remove multiple items from an array
* @returns changed array copy
*/
export const removeItems = (list, ...items) => {
const res = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
const ii = items.indexOf(item);
if (ii !== -1)
items.splice(ii, 1);
else
res.push(item);
}
return res;
};
/**
* Flattens a nested array into a one-level array
* @returns changed array copy
*/
export const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
/**
* Sort an array by object key, or multiple keys
* @returns changed array copy
*/
export const sortBy = (arr, ...paths) => flatten(paths).reduce((source, path) => sort(source, (a, b) => typeof path === "function"
? compare(path(a), path(b))
: compare(get(a, path), get(b, path))), arr);
/**
* Returns a subset of items that are instances of provided Classes
* @param list list of original items
* @param ...classes list or classes
* @returns changed array copy
*/
export const filterInstance = (list, ...classes) => (classes.length === 1
? list.filter(item => ofClass(item, classes[0]))
: list.filter(item => item && classes.some(c => ofClass(item, c))));
/**
* Returns a subset of items that aren't instances of provided Classes
* @param list list of original items
* @param ...classes list or classes
* @returns changed array copy
*/
export const filterOutInstance = (list, ...classes) => (classes.length === 1
? list.filter(item => item && !ofClass(item, classes[0]))
: list.filter(item => item && !classes.some(c => ofClass(item, c))));
+27
View File
@@ -0,0 +1,27 @@
/** make shallow copy of an array */
export declare const shallowArrayCopy: <T>(array: readonly T[]) => T[];
/** make shallow copy of an object */
export declare const shallowObjectCopy: <T extends object>(object: T) => T;
/** make shallow copy of an array/object */
export declare const shallowCopy: <T extends object>(source: T) => T;
/**
* apply mutations to the an array without changing the original
* @param array original array
* @param mutator function applying mutations to the copy of source
* @returns changed array copy
*/
export declare const withArrayCopy: <T>(array: readonly T[], mutator: (copy: T[]) => void) => T[];
/**
* apply mutations to the an object without changing the original
* @param object original object
* @param mutator function applying mutations to the copy of source
* @returns changed object copy
*/
export declare const withObjectCopy: <T extends object>(object: T, mutator: (copy: T) => void) => T;
/**
* apply mutations to the an object/array without changing the original
* @param source original object
* @param mutator function applying mutations to the copy of source
* @returns changed object copy
*/
export declare const withCopy: <T extends object>(source: T, mutator: (copy: T) => void) => T;
+37
View File
@@ -0,0 +1,37 @@
/** make shallow copy of an array */
export const shallowArrayCopy = (array) => array.slice();
/** make shallow copy of an object */
export const shallowObjectCopy = (object) => Object.assign({}, object);
/** make shallow copy of an array/object */
export const shallowCopy = (source) => Array.isArray(source) ? shallowArrayCopy(source) : shallowObjectCopy(source);
/**
* apply mutations to the an array without changing the original
* @param array original array
* @param mutator function applying mutations to the copy of source
* @returns changed array copy
*/
export const withArrayCopy = (array, mutator) => {
const copy = shallowArrayCopy(array);
mutator(copy);
return copy;
};
/**
* apply mutations to the an object without changing the original
* @param object original object
* @param mutator function applying mutations to the copy of source
* @returns changed object copy
*/
export const withObjectCopy = (object, mutator) => {
const copy = shallowObjectCopy(object);
mutator(copy);
return copy;
};
/**
* apply mutations to the an object/array without changing the original
* @param source original object
* @param mutator function applying mutations to the copy of source
* @returns changed object copy
*/
export const withCopy = (source, mutator) => Array.isArray(source)
? withArrayCopy(source, mutator)
: withObjectCopy(source, mutator);
+6
View File
@@ -0,0 +1,6 @@
export * from "./copy.js";
export * from "./number.js";
export * from "./update.js";
export * from "./object.js";
export * from "./array.js";
export * from "./types.js";
+6
View File
@@ -0,0 +1,6 @@
export * from "./copy.js";
export * from "./number.js";
export * from "./update.js";
export * from "./object.js";
export * from "./array.js";
export * from "./types.js";
+13
View File
@@ -0,0 +1,13 @@
/** `a + b + c + ...` */
export declare function add(...a: number[]): number;
export declare function add(...a: string[]): string;
/** `a - b - c - ...` */
export declare const substract: (a: number, ...b: number[]) => number;
/** `a * b * c * ...` */
export declare const multiply: (a: number, ...b: number[]) => number;
/** `a / b / c / ...` */
export declare const divide: (a: number, ...b: number[]) => number;
/** `a ** b ** c ** ...` */
export declare const power: (a: number, ...b: number[]) => number;
/** clamp a number value between two other values */
export declare const clamp: (n: number, min: number, max: number) => number;
+37
View File
@@ -0,0 +1,37 @@
export function add(...a) {
let r = 0;
for (const n of a) {
r += n;
}
return r;
}
/** `a - b - c - ...` */
export const substract = (a, ...b) => {
for (const n of b) {
a -= n;
}
return a;
};
/** `a * b * c * ...` */
export const multiply = (a, ...b) => {
for (const n of b) {
a *= n;
}
return a;
};
/** `a / b / c / ...` */
export const divide = (a, ...b) => {
for (const n of b) {
a /= n;
}
return a;
};
/** `a ** b ** c ** ...` */
export const power = (a, ...b) => {
for (const n of b) {
a = a ** n;
}
return a;
};
/** clamp a number value between two other values */
export const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
+63
View File
@@ -0,0 +1,63 @@
import { type Modify } from "../index.js";
/**
* Create a new subset object without the provided keys
*
* @example
* ```ts
* const newObject = omit({ a:"foo", b:"bar", c: "baz" }, 'a', 'b')
* newObject // => { c: "baz" }
* ```
*/
export declare const omit: <O extends object, K extends keyof O>(object: O, ...keys: K[]) => Omit<O, K>;
/**
* Create a new subset object with only the provided keys
*
* @example
* ```ts
* const newObject = pick({ a:"foo", b:"bar", c: "baz" }, 'a', 'b')
* newObject // => { a:"foo", b:"bar" }
* ```
*/
export declare const pick: <O extends object, K extends keyof O>(object: O, ...keys: K[]) => Pick<O, K>;
/**
* Get a single property value of an object by specifying a path to it.
*/
export declare function get<O extends object, K extends keyof O>(obj: O, key: K): O[K];
export declare function get<O extends object, K1 extends keyof O, K2 extends keyof O[K1]>(obj: O, k1: K1, k2: K2): O[K1][K2];
export declare function get<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>(obj: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3];
export declare function get<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2], K4 extends keyof O[K1][K2][K3]>(obj: O, k1: K1, k2: K2, k3: K3, k4: K4): O[K1][K2][K3][K4];
export declare function get<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2], K4 extends keyof O[K1][K2][K3], K5 extends keyof O[K1][K2][K3][K4]>(obj: O, k1: K1, k2: K2, k3: K3, k4: K4, k5: K5): O[K1][K2][K3][K4][K5];
export declare function get<O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2], K4 extends keyof O[K1][K2][K3], K5 extends keyof O[K1][K2][K3][K4], K6 extends keyof O[K1][K2][K3][K4][K5]>(obj: O, k1: K1, k2: K2, k3: K3, k4: K4, k5: K5, k6: K6): O[K1][K2][K3][K4][K5][K6];
/**
* Split object properties by keys into multiple object copies with a subset of selected properties.
*
* @param object original object
* @param ...keys keys to pick from the source, or multiple arrays of keys *(for splitting into more than 2 objects)*
* ```ts
* (keyof object)[][] | (keyof object)[]
* ```
* @returns array of subset objects
*/
export declare function split<T extends object, K extends keyof T>(object: T, ...keys: K[]): [Pick<T, K>, Omit<T, K>];
export declare function split<T extends object, K1 extends keyof T, K2 extends keyof T>(object: T, ...keys: [K1[], K2[]]): [Pick<T, K1>, Pick<T, K2>, Omit<T, K1 | K2>];
export declare function split<T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T>(object: T, ...keys: [K1[], K2[], K3[]]): [Pick<T, K1>, Pick<T, K2>, Pick<T, K3>, Omit<T, K1 | K2 | K3>];
export declare function split<T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T, K4 extends keyof T>(object: T, ...keys: [K1[], K2[], K3[], K4[]]): [Pick<T, K1>, Pick<T, K2>, Pick<T, K3>, Pick<T, K4>, Omit<T, K1 | K2 | K3 | K4>];
export declare function split<T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T, K4 extends keyof T, K5 extends keyof T>(object: T, ...keys: [K1[], K2[], K3[], K4[], K5[]]): [
Pick<T, K1>,
Pick<T, K2>,
Pick<T, K3>,
Pick<T, K4>,
Pick<T, K5>,
Omit<T, K1 | K2 | K3 | K4 | K5>
];
/**
* Merges multiple objects into a single one. Only the first level of properties is merged. An alternative to `{ ...a, ...b, ...c }`.
* @param ...objects objects to merge
* @example
* const d = merge(a, b, c)
*/
export declare function merge<A extends object, B extends object>(a: A, b: B): Modify<A, B>;
export declare function merge<A extends object, B extends object, C extends object>(a: A, b: B, c: C): Modify<Modify<A, B>, C>;
export declare function merge<A extends object, B extends object, C extends object, D extends object>(a: A, b: B, c: C, d: D): Modify<Modify<Modify<A, B>, C>, D>;
export declare function merge<A extends object, B extends object, C extends object, D extends object, E extends object>(a: A, b: B, c: C, d: D, e: E): Modify<Modify<Modify<Modify<A, B>, C>, D>, E>;
export declare function merge<A extends object, B extends object, C extends object, D extends object, E extends object, F extends object>(a: A, b: B, c: C, d: D, e: E, f: F): Modify<Modify<Modify<Modify<Modify<A, B>, C>, D>, E>, F>;
+54
View File
@@ -0,0 +1,54 @@
import { withObjectCopy, shallowObjectCopy } from "./copy.js";
import {} from "../index.js";
/**
* Create a new subset object without the provided keys
*
* @example
* ```ts
* const newObject = omit({ a:"foo", b:"bar", c: "baz" }, 'a', 'b')
* newObject // => { c: "baz" }
* ```
*/
export const omit = (object, ...keys) => withObjectCopy(object, object => keys.forEach(key => delete object[key]));
/**
* Create a new subset object with only the provided keys
*
* @example
* ```ts
* const newObject = pick({ a:"foo", b:"bar", c: "baz" }, 'a', 'b')
* newObject // => { a:"foo", b:"bar" }
* ```
*/
export const pick = (object, ...keys) => keys.reduce((n, k) => {
if (k in object)
n[k] = object[k];
return n;
}, {});
export function get(obj, ...keys) {
let res = obj;
for (const key of keys) {
res = res[key];
}
return res;
}
export function split(object, ...list) {
const _list = (typeof list[0] === "string" ? [list] : list);
const copy = shallowObjectCopy(object);
const result = [];
for (let i = 0; i < _list.length; i++) {
const keys = _list[i];
result.push({});
for (const key of keys) {
result[i][key] = copy[key];
delete copy[key];
}
}
return [...result, copy];
}
export function merge(...objects) {
const result = {};
for (const obj of objects) {
Object.assign(result, obj);
}
return result;
}
+7
View File
@@ -0,0 +1,7 @@
import { type ItemsOf } from "../index.js";
export type Predicate<T> = (item: T, index: number, array: readonly T[]) => boolean;
export type MappingFn<T, V> = (item: T, index: number, array: readonly T[]) => V;
export type FlattenArray<T> = T extends any[] ? FlattenArray<ItemsOf<T>> : T;
export type ModifyValue<O, K extends keyof O, V> = Omit<O, K> & {
[key in K]: V;
};
+1
View File
@@ -0,0 +1 @@
import {} from "../index.js";
+30
View File
@@ -0,0 +1,30 @@
import { type ModifyValue } from "./types.js";
export type UpdateSetter<O, K extends keyof O, V> = V | ((prev: O[K]) => V);
export type Update = {
<O extends object, K0 extends keyof O, K1 extends keyof O[K0], K2 extends keyof O[K0][K1], K3 extends keyof O[K0][K1][K2], K4 extends keyof O[K0][K1][K2][K3], V>(object: O, k0: K0, k1: K1, k2: K2, k3: K3, k4: K4, setter: UpdateSetter<O[K0][K1][K2][K3], K4, V>): ModifyValue<O, K0, ModifyValue<O[K0], K1, ModifyValue<O[K0][K1], K2, ModifyValue<O[K0][K1][K2], K3, ModifyValue<O[K0][K1][K2][K3], K4, V>>>>>;
<O extends object, K0 extends keyof O, K1 extends keyof O[K0], K2 extends keyof O[K0][K1], K3 extends keyof O[K0][K1][K2], V>(object: O, k0: K0, k1: K1, k2: K2, k3: K3, setter: UpdateSetter<O[K0][K1][K2], K3, V>): ModifyValue<O, K0, ModifyValue<O[K0], K1, ModifyValue<O[K0][K1], K2, ModifyValue<O[K0][K1][K2], K3, V>>>>;
<O extends object, K0 extends keyof O, K1 extends keyof O[K0], K2 extends keyof O[K0][K1], V>(object: O, k0: K0, k1: K1, k2: K2, setter: UpdateSetter<O[K0][K1], K2, V>): ModifyValue<O, K0, ModifyValue<O[K0], K1, ModifyValue<O[K0][K1], K2, V>>>;
<O extends object, K0 extends keyof O, K1 extends keyof O[K0], V>(object: O, k0: K0, k1: K1, setter: UpdateSetter<O[K0], K1, V>): ModifyValue<O, K0, ModifyValue<O[K0], K1, V>>;
<O extends object, K extends keyof O, V>(object: O, key: K, setter: UpdateSetter<O, K, V>): ModifyValue<O, K, V>;
};
/**
* Change single value in an object by key. Allows accessign nested objects by passing multiple keys.
*
* Performs a shallow copy of each accessed object.
*
* @param object original source
* @param ...keys keys of sequential accessed objects
* @param value a value to set in place of a previous one, or a setter function.
* ```ts
* V | ((prev: O[K]) => V)
* ```
* a new value doesn't have to have the same type as the original
* @returns changed copy of the original object
*
* @example
* const original = { foo: { bar: { baz: 123 }}};
* const newObj = update(original, "foo", "bar", "baz", prev => prev + 1)
* original // { foo: { bar: { baz: 123 }}}
* newObj // { foo: { bar: { baz: 124 }}}
*/
export declare const update: Update;
+30
View File
@@ -0,0 +1,30 @@
import { withCopy } from "./copy.js";
import {} from "./types.js";
/**
* Change single value in an object by key. Allows accessign nested objects by passing multiple keys.
*
* Performs a shallow copy of each accessed object.
*
* @param object original source
* @param ...keys keys of sequential accessed objects
* @param value a value to set in place of a previous one, or a setter function.
* ```ts
* V | ((prev: O[K]) => V)
* ```
* a new value doesn't have to have the same type as the original
* @returns changed copy of the original object
*
* @example
* const original = { foo: { bar: { baz: 123 }}};
* const newObj = update(original, "foo", "bar", "baz", prev => prev + 1)
* original // { foo: { bar: { baz: 123 }}}
* newObj // { foo: { bar: { baz: 124 }}}
*/
export const update = (...args) => withCopy(args[0], obj => {
if (args.length > 3)
obj[args[1]] = update(obj[args[1]], ...args.slice(2));
else if (typeof args[2] === "function")
obj[args[1]] = args[2](obj[args[1]]);
else
obj[args[1]] = args[2];
});
+186
View File
@@ -0,0 +1,186 @@
import { onCleanup, createSignal, type Accessor, type AccessorArray, type EffectFunction, type NoInfer, type SignalOptions } from "solid-js";
import { isServer } from "solid-js/web";
import type { AnyClass, MaybeAccessor, MaybeAccessorValue, Noop, AnyObject, AnyFunction } from "./types.js";
export * from "./types.js";
export { isServer };
export declare const isClient: boolean;
export declare const isDev: boolean;
export declare const isProd: boolean;
/** no operation */
export declare const noop: Noop;
export declare const trueFn: () => boolean;
export declare const falseFn: () => boolean;
/** @deprecated use {@link equalFn} from "solid-js" */
export declare const defaultEquals: <T>(a: T, b: T) => boolean;
export declare const EQUALS_FALSE_OPTIONS: {
readonly equals: false;
};
export declare const INTERNAL_OPTIONS: {
readonly internal: true;
};
/**
* Check if the value is an instance of ___
*/
export declare const ofClass: (v: any, c: AnyClass) => boolean;
/** Check if value is typeof "object" or "function" */
export declare function isObject(value: any): value is AnyObject;
export declare const isNonNullable: <T>(i: T) => i is NonNullable<T>;
export declare const filterNonNullable: <T extends readonly unknown[]>(arr: T) => NonNullable<T[number]>[];
export declare const compare: (a: any, b: any) => number;
/**
* Check shallow array equality
*/
export declare const arrayEquals: (a: readonly unknown[], b: readonly unknown[]) => boolean;
/**
* Returns a function that will call all functions in the order they were chained with the same arguments.
*/
export declare function chain<Args extends [] | any[]>(callbacks: {
[Symbol.iterator](): IterableIterator<((...args: Args) => any) | undefined>;
}): (...args: Args) => void;
/**
* Returns a function that will call all functions in the reversed order with the same arguments.
*/
export declare function reverseChain<Args extends [] | any[]>(callbacks: (((...args: Args) => any) | undefined)[]): (...args: Args) => void;
export declare const clamp: (n: number, min: number, max: number) => number;
/**
* Accesses the value of a MaybeAccessor
* @example
* ```ts
* access("foo") // => "foo"
* access(() => "foo") // => "foo"
* ```
*/
export declare const access: <T extends MaybeAccessor<any>>(v: T) => MaybeAccessorValue<T>;
export declare const asArray: <T>(value: T) => (T extends any[] ? T[number] : NonNullable<T>)[];
/**
* Access an array of MaybeAccessors
* @example
* const list = [1, 2, () => 3)] // T: MaybeAccessor<number>[]
* const newList = accessArray(list) // T: number[]
*/
export declare const accessArray: <A extends MaybeAccessor<any>>(list: readonly A[]) => MaybeAccessorValue<A>[];
/**
* Run the function if the accessed value is not `undefined` nor `null`
* @param value
* @param fn
*/
export declare const withAccess: <T, A extends MaybeAccessor<T>, V = MaybeAccessorValue<A>>(value: A, fn: (value: NonNullable<V>) => void) => void;
export declare const asAccessor: <A extends MaybeAccessor<unknown>>(v: A) => Accessor<MaybeAccessorValue<A>>;
/** If value is a function call it with a given arguments otherwise get the value as is */
export declare function accessWith<T>(valueOrFn: T, ...args: T extends AnyFunction ? Parameters<T> : never): T extends AnyFunction ? ReturnType<T> : T;
/**
* Solid's `on` helper, but always defers and returns a provided initial value when if does instead of `undefined`.
*
* @param deps
* @param fn
* @param initialValue
*/
export declare function defer<S, Next extends Prev, Prev = Next>(deps: AccessorArray<S> | Accessor<S>, fn: (input: S, prevInput: S, prev: undefined | NoInfer<Prev>) => Next, initialValue: Next): EffectFunction<undefined | NoInfer<Next>, NoInfer<Next>>;
export declare function defer<S, Next extends Prev, Prev = Next>(deps: AccessorArray<S> | Accessor<S>, fn: (input: S, prevInput: S, prev: undefined | NoInfer<Prev>) => Next, initialValue?: undefined): EffectFunction<undefined | NoInfer<Next>>;
/**
* Get entries of an object
*/
export declare const entries: <T extends object>(obj: T) => [keyof T, T[keyof T]][];
/**
* Get keys of an object
*/
export declare const keys: <T extends object>(object: T) => (keyof T)[];
/**
* Solid's `onCleanup` that doesn't warn in development if used outside of a component.
*/
export declare const tryOnCleanup: typeof onCleanup;
export declare const createCallbackStack: <A0 = void, A1 = void, A2 = void, A3 = void>() => {
push: (...callbacks: ((arg0: A0, arg1: A1, arg2: A2, arg3: A3) => void)[]) => void;
execute: (arg0: A0, arg1: A1, arg2: A2, arg3: A3) => void;
clear: VoidFunction;
};
/**
* Group synchronous function calls.
* @param fn
* @returns `fn`
*/
export declare function createMicrotask<A extends any[] | []>(fn: (...a: A) => void): (...a: A) => void;
/**
* A hydratable version of the {@link createSignal}. It will use the serverValue on the server and the update function on the client. If initialized during hydration it will use serverValue as the initial value and update it once hydration is complete.
*
* @param serverValue initial value of the state on the server
* @param update called once on the client or on hydration to initialize the value
* @param options {@link SignalOptions}
* @returns
* ```ts
* [state: Accessor<T>, setState: Setter<T>]
* ```
* @see {@link createSignal}
*/
export declare function createHydratableSignal<T>(serverValue: T, update: () => T, options?: SignalOptions<T>): ReturnType<typeof createSignal<T>>;
/** @deprecated use {@link createHydratableSignal} instead */
export declare const createHydrateSignal: typeof createHydratableSignal;
/**
* Handle items removed and added to the array by diffing it by refference.
*
* @param current new array instance
* @param prev previous array copy
* @param handleAdded called once for every added item to array
* @param handleRemoved called once for every removed from array
*/
export declare function handleDiffArray<T>(current: readonly T[], prev: readonly T[], handleAdded: (item: T) => void, handleRemoved: (item: T) => void): void;
/**
* Parse a string as a single JSON value.
*
* ```ts
* const { data } = createSSE<{ status: string }>(url, { transform: json });
* ```
*/
export declare const json: <T>(raw: string) => T;
/**
* Parse a string as newline-delimited JSON (NDJSON / JSON Lines).
*
* Each non-empty line is parsed as a separate JSON value and returned as an array.
*
* ```ts
* const { data } = createSSE<TickEvent[]>(url, { transform: ndjson });
* // data() === [{ id: 1, type: "tick" }, { id: 2, type: "tick" }]
* ```
*/
export declare const ndjson: <T>(raw: string) => T[];
/**
* Split a string into individual lines, returning a `string[]`. Empty lines are filtered out.
*
* ```ts
* const { data } = createSSE<string[]>(url, { transform: lines });
* // data() === ["line one", "line two"]
* ```
*/
export declare const lines: (raw: string) => string[];
/**
* Parse a string as a number using `Number()` semantics.
*
* Note: `""` → `0`, non-numeric strings → `NaN`.
*
* ```ts
* const { data } = createSSE<number>(url, { transform: number });
* // data() === 42
* ```
*/
export declare const number: (raw: string) => number;
/**
* Wrap any `(string) => T` transform in a `try/catch`. Returns `fallback`
* (default `undefined`) instead of throwing on malformed input.
*
* ```ts
* const { data } = createSSE<MyEvent>(url, { transform: safe(json) });
* const { data } = createSSE<number>(url, { transform: safe(number, 0) });
* ```
*/
export declare function safe<T>(transform: (raw: string) => T): (raw: string) => T | undefined;
export declare function safe<T>(transform: (raw: string) => T, fallback: T): (raw: string) => T;
/**
* Compose two transforms into one: the output of `a` is passed as the input of `b`.
*
* ```ts
* const { data } = createSSE<RawEvent[]>(url, {
* transform: pipe(ndjson<RawEvent>, rows => rows.filter(r => r.type === "tick")),
* });
* ```
*/
export declare function pipe<A, B>(a: (raw: string) => A, b: (a: A) => B): (raw: string) => B;
+278
View File
@@ -0,0 +1,278 @@
import { getOwner, onCleanup, createSignal, untrack, sharedConfig, onMount, DEV, equalFn, } from "solid-js";
import { isServer } from "solid-js/web";
export * from "./types.js";
//
// GENERAL HELPERS:
//
export { isServer };
export const isClient = !isServer;
export const isDev = isClient && !!DEV;
export const isProd = !isDev;
/** no operation */
export const noop = (() => void 0);
export const trueFn = () => true;
export const falseFn = () => false;
/** @deprecated use {@link equalFn} from "solid-js" */
export const defaultEquals = equalFn;
export const EQUALS_FALSE_OPTIONS = { equals: false };
export const INTERNAL_OPTIONS = { internal: true };
/**
* Check if the value is an instance of ___
*/
export const ofClass = (v, c) => v instanceof c || (v && v.constructor === c);
/** Check if value is typeof "object" or "function" */
export function isObject(value) {
return value !== null && (typeof value === "object" || typeof value === "function");
}
export const isNonNullable = (i) => i != null;
export const filterNonNullable = (arr) => arr.filter(isNonNullable);
export const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
/**
* Check shallow array equality
*/
export const arrayEquals = (a, b) => a === b || (a.length === b.length && a.every((e, i) => e === b[i]));
/**
* Returns a function that will call all functions in the order they were chained with the same arguments.
*/
export function chain(callbacks) {
return (...args) => {
for (const callback of callbacks)
callback && callback(...args);
};
}
/**
* Returns a function that will call all functions in the reversed order with the same arguments.
*/
export function reverseChain(callbacks) {
return (...args) => {
for (let i = callbacks.length - 1; i >= 0; i--) {
const callback = callbacks[i];
callback && callback(...args);
}
};
}
export const clamp = (n, min, max) => Math.min(Math.max(n, min), max);
/**
* Accesses the value of a MaybeAccessor
* @example
* ```ts
* access("foo") // => "foo"
* access(() => "foo") // => "foo"
* ```
*/
export const access = (v) => typeof v === "function" && !v.length ? v() : v;
export const asArray = (value) => Array.isArray(value) ? value : value ? [value] : [];
/**
* Access an array of MaybeAccessors
* @example
* const list = [1, 2, () => 3)] // T: MaybeAccessor<number>[]
* const newList = accessArray(list) // T: number[]
*/
export const accessArray = (list) => list.map(v => access(v));
/**
* Run the function if the accessed value is not `undefined` nor `null`
* @param value
* @param fn
*/
export const withAccess = (value, fn) => {
const _value = access(value);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
typeof _value != null && fn(_value);
};
export const asAccessor = (v) => (typeof v === "function" ? v : () => v);
/** If value is a function call it with a given arguments otherwise get the value as is */
export function accessWith(valueOrFn, ...args) {
return typeof valueOrFn === "function" ? valueOrFn(...args) : valueOrFn;
}
export function defer(deps, fn, initialValue) {
const isArray = Array.isArray(deps);
let prevInput;
let shouldDefer = true;
return prevValue => {
let input;
if (isArray) {
input = Array(deps.length);
for (let i = 0; i < deps.length; i++)
input[i] = deps[i]();
}
else
input = deps();
if (shouldDefer) {
shouldDefer = false;
prevInput = input;
return initialValue;
}
const result = untrack(() => fn(input, prevInput, prevValue));
prevInput = input;
return result;
};
}
/**
* Get entries of an object
*/
export const entries = Object.entries;
/**
* Get keys of an object
*/
export const keys = Object.keys;
/**
* Solid's `onCleanup` that doesn't warn in development if used outside of a component.
*/
export const tryOnCleanup = isDev
? fn => (getOwner() ? onCleanup(fn) : fn)
: onCleanup;
export const createCallbackStack = () => {
let stack = [];
const clear = () => (stack = []);
return {
push: (...callbacks) => stack.push(...callbacks),
execute(arg0, arg1, arg2, arg3) {
stack.forEach(cb => cb(arg0, arg1, arg2, arg3));
clear();
},
clear,
};
};
/**
* Group synchronous function calls.
* @param fn
* @returns `fn`
*/
export function createMicrotask(fn) {
let calls = 0;
let args;
onCleanup(() => (calls = 0));
return (...a) => {
(args = a), calls++;
queueMicrotask(() => --calls === 0 && fn(...args));
};
}
/**
* A hydratable version of the {@link createSignal}. It will use the serverValue on the server and the update function on the client. If initialized during hydration it will use serverValue as the initial value and update it once hydration is complete.
*
* @param serverValue initial value of the state on the server
* @param update called once on the client or on hydration to initialize the value
* @param options {@link SignalOptions}
* @returns
* ```ts
* [state: Accessor<T>, setState: Setter<T>]
* ```
* @see {@link createSignal}
*/
export function createHydratableSignal(serverValue, update, options) {
if (isServer) {
return createSignal(serverValue, options);
}
if (sharedConfig.context) {
const [state, setState] = createSignal(serverValue, options);
onMount(() => setState(() => update()));
return [state, setState];
}
return createSignal(update(), options);
}
/** @deprecated use {@link createHydratableSignal} instead */
export const createHydrateSignal = createHydratableSignal;
/**
* Handle items removed and added to the array by diffing it by refference.
*
* @param current new array instance
* @param prev previous array copy
* @param handleAdded called once for every added item to array
* @param handleRemoved called once for every removed from array
*/
export function handleDiffArray(current, prev, handleAdded, handleRemoved) {
const currLength = current.length;
const prevLength = prev.length;
let i = 0;
if (!prevLength) {
for (; i < currLength; i++)
handleAdded(current[i]);
return;
}
if (!currLength) {
for (; i < prevLength; i++)
handleRemoved(prev[i]);
return;
}
for (; i < prevLength; i++) {
if (prev[i] !== current[i])
break;
}
let prevEl;
let currEl;
prev = prev.slice(i);
current = current.slice(i);
for (prevEl of prev) {
if (!current.includes(prevEl))
handleRemoved(prevEl);
}
for (currEl of current) {
if (!prev.includes(currEl))
handleAdded(currEl);
}
}
// ─── String transforms ────────────────────────────────────────────────────────
/**
* Parse a string as a single JSON value.
*
* ```ts
* const { data } = createSSE<{ status: string }>(url, { transform: json });
* ```
*/
export const json = (raw) => JSON.parse(raw);
/**
* Parse a string as newline-delimited JSON (NDJSON / JSON Lines).
*
* Each non-empty line is parsed as a separate JSON value and returned as an array.
*
* ```ts
* const { data } = createSSE<TickEvent[]>(url, { transform: ndjson });
* // data() === [{ id: 1, type: "tick" }, { id: 2, type: "tick" }]
* ```
*/
export const ndjson = (raw) => raw
.split("\n")
.filter(line => line !== "")
.map(line => JSON.parse(line));
/**
* Split a string into individual lines, returning a `string[]`. Empty lines are filtered out.
*
* ```ts
* const { data } = createSSE<string[]>(url, { transform: lines });
* // data() === ["line one", "line two"]
* ```
*/
export const lines = (raw) => raw.split("\n").filter(line => line !== "");
/**
* Parse a string as a number using `Number()` semantics.
*
* Note: `""` → `0`, non-numeric strings → `NaN`.
*
* ```ts
* const { data } = createSSE<number>(url, { transform: number });
* // data() === 42
* ```
*/
export const number = (raw) => Number(raw);
export function safe(transform, fallback) {
return (raw) => {
try {
return transform(raw);
}
catch {
return fallback;
}
};
}
/**
* Compose two transforms into one: the output of `a` is passed as the input of `b`.
*
* ```ts
* const { data } = createSSE<RawEvent[]>(url, {
* transform: pipe(ndjson<RawEvent>, rows => rows.filter(r => r.type === "tick")),
* });
* ```
*/
export function pipe(a, b) {
return (raw) => b(a(raw));
}
+89
View File
@@ -0,0 +1,89 @@
import type { Accessor, Setter } from "solid-js";
export type { EffectOptions, OnOptions } from "solid-js";
export type { ResolvedJSXElement, ResolvedChildren } from "solid-js/types/reactive/signal.js";
/**
* Can be single or in an array
*/
export type Many<T> = T | T[];
export type Values<O extends Object> = O[keyof O];
export type Noop = (...a: any[]) => void;
export type Directive<P = true> = (el: Element, props: Accessor<P>) => void;
/**
* Infers the type of the array elements
*/
export type ItemsOf<T> = T extends (infer E)[] ? E : never;
export type ItemsOfMany<T> = T extends any[] ? ItemsOf<T> : T;
export type SetterParam<T> = Parameters<Setter<T>>[0];
/**
* T or a reactive/non-reactive function returning T
*/
export type MaybeAccessor<T> = T | Accessor<T>;
/**
* Accessed value of a MaybeAccessor
* @example
* ```ts
* MaybeAccessorValue<MaybeAccessor<string>>
* // => string
* MaybeAccessorValue<MaybeAccessor<() => string>>
* // => string | (() => string)
* MaybeAccessorValue<MaybeAccessor<string> | Function>
* // => string | void
* ```
*/
export type MaybeAccessorValue<T extends MaybeAccessor<any>> = T extends () => any ? ReturnType<T> : T;
export type OnAccessEffectFunction<S, Prev, Next extends Prev = Prev> = (input: AccessReturnTypes<S>, prevInput: AccessReturnTypes<S>, v: Prev) => Next;
export type AccessReturnTypes<S> = S extends MaybeAccessor<any>[] ? {
[I in keyof S]: AccessReturnTypes<S[I]>;
} : MaybeAccessorValue<S>;
/** Allows to make shallow overwrites to an interface */
export type Modify<T, R> = Omit<T, keyof R> & R;
/** Allows to make nested overwrites to an interface */
export type ModifyDeep<A extends AnyObject, B extends DeepPartialAny<A>> = {
[K in keyof A]: B[K] extends never ? A[K] : B[K] extends AnyObject ? ModifyDeep<A[K], B[K]> : B[K];
} & (A extends AnyObject ? Omit<B, keyof A> : A);
/** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
export type DeepPartialAny<T> = {
[P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any;
};
/** Removes the `[...list]` functionality */
export type NonIterable<T> = T & {
[Symbol.iterator]: never;
};
/** Get the required keys of an object */
export type RequiredKeys<T> = keyof {
[K in keyof T as T extends {
[_ in K]: unknown;
} ? K : never]: 0;
};
/** Remove the first item of a tuple [1, 2, 3, 4] => [2, 3, 4] */
export type Tail<T extends any[]> = ((...t: T) => void) extends (x: any, ...u: infer U) => void ? U : never;
/** `A | B => A & B` */
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
export type ExtractIfPossible<T, U> = Extract<T, U> extends never ? U : Extract<T, U>;
export type AnyObject = Record<PropertyKey, any>;
export type AnyStatic = [] | any[] | AnyObject;
export type AnyFunction = (...args: any[]) => any;
export type AnyClass = abstract new (...args: any) => any;
export type PrimitiveValue = PropertyKey | boolean | bigint | null | undefined;
export type FalsyValue = false | 0 | "" | null | undefined;
export type Truthy<T> = Exclude<T, FalsyValue>;
export type Falsy<T> = Extract<T, FalsyValue>;
export type Position = {
x: number;
y: number;
};
export type Size = {
width: number;
height: number;
};
/** Unwraps the type definition of an object, making it more readable */
export type Simplify<T> = T extends object ? {
[K in keyof T]: T[K];
} : T;
/** Unboxes type definition, making it more readable */
export type UnboxLazy<T> = T extends () => infer U ? U : T;
type RawNarrow<T> = (T extends [] ? [] : never) | (T extends string | number | bigint | boolean ? T : never) | {
[K in keyof T]: T[K] extends Function ? T[K] : RawNarrow<T[K]>;
};
export type Narrow<T> = T extends [] ? T : RawNarrow<T>;
export type NoInfer<T> = [T][T extends any ? 0 : never];
+1
View File
@@ -0,0 +1 @@
export {};
+69
View File
@@ -0,0 +1,69 @@
{
"name": "@solid-primitives/utils",
"version": "6.4.0",
"description": "A bunch of reactive utility types and functions, for building primitives with Solid.js",
"author": "Damian Tarnawski @thetarnav <gthetarnav@gmail.com>",
"contributors": [
"Tom Pichaud <dev.tompichaud@icloud.com>"
],
"license": "MIT",
"homepage": "https://github.com/solidjs-community/solid-primitives/tree/main/packages/utils#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
},
"bugs": {
"url": "https://github.com/solidjs-community/solid-primitives/issues"
},
"private": false,
"sideEffects": false,
"files": [
"dist"
],
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"browser": {},
"exports": {
".": {
"import": {
"@solid-primitives/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./immutable": {
"import": {
"@solid-primitives/source": "./src/immutable/index.ts",
"types": "./dist/immutable/index.d.ts",
"default": "./dist/immutable/index.js"
}
}
},
"typesVersions": {
"*": {
"immutable": [
"./dist/immutable/index.d.ts"
]
}
},
"keywords": [
"utilities",
"reactivity",
"solid",
"primitives"
],
"peerDependencies": {
"solid-js": "^1.6.12"
},
"devDependencies": {
"solid-js": "^1.9.7"
},
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
}
}