Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/signal-builders-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@solid-primitives/signal-builders": major
---

Migrate to Solid.js v2.0 (beta.13)

## Breaking Changes

**Peer dependency**: `solid-js@^2.0.0-beta.13` is now required.

- The `on` helper from `solid-js` (used internally by `capitalize`) is removed in Solid 2.0; `capitalize` now uses a plain `createMemo` which is equivalent
- `get` and `merge` now correctly return reactive `Accessor<T>` values via `createMemo` — previously they returned plain (non-reactive) values despite their type signatures claiming otherwise; any code that was working around this bug by calling the result as a plain value will break
107 changes: 51 additions & 56 deletions packages/signal-builders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@
[![version](https://img.shields.io/npm/v/@solid-primitives/signal-builders?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/signal-builders)
[![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)

A collection of chainable and composable reactive signal calculations, _AKA_ **Signal Builders**.
A collection of chainable, composable reactive computations — **Signal Builders** — for common array, object, number, string, and type-conversion operations.

## Installation

```bash
npm install @solid-primitives/signal-builders
# or
yarn add @solid-primitives/signal-builders
pnpm add @solid-primitives/signal-builders
```

## How to use it
Requires `solid-js@^2.0.0-beta.13` as a peer dependency.

Signal builders create computations when used, so they need to be used under a reactive root.
## Usage

Note, since all of the signal builders use [`createMemo`](https://www.solidjs.com/docs/latest/api#creatememo) to wrap the calculation, updates will be caused only when the calculated value changes. Also the calculations should stay 'pure' – try to not cause side effects inside them.
Each builder wraps its computation in `createMemo`, so results only update when the computed value actually changes. Builders must be called inside a reactive owner (a component body or `createRoot`), and computations should be kept pure — avoid side effects inside them.

Because each builder returns an `Accessor<T>`, the output of one can be passed directly as input to another:

### Array

Expand Down Expand Up @@ -56,10 +58,10 @@ modifiedUser(); // { name: { first: "John", last: "Solid" }, age: 21 }
import { add, multiply, clamp, int } from "@solid-primitives/signal-builders";

const [input, setInput] = createSignal("123");
const [ing, setIng] = createSignal(-45);
const [offset, setOffset] = createSignal(-45);
const [max, setMax] = createSignal(1000);

const value = clamp(multiply(int(input), add(ing, 54, 9)), 0, max);
const value = clamp(multiply(int(input), add(offset, 54, 9)), 0, max);
```

### String
Expand All @@ -71,74 +73,67 @@ const [greeting, setGreeting] = createSignal("Hello");
const [target, setTarget] = createSignal("World");

const message = template`${greeting}, ${target}!`;
message(); // => Hello, World!
message(); // => "Hello, World!"

const solidMessage = lowercase(add(substring(message, 0, 7), "Solid"));
solidMessage(); // => hello, solid
solidMessage(); // => "hello, solid"
```

## List of builders
## Builder Reference

### Array

- **`push`** - basically `Array.prototype.push()`
- **`drop`** - drop n items from the array start
- **`dropRight`** - drop n items from the end of the array
- **`filter`** - basically `Array.prototype.filter()`
- **`filterOut`** - filter out passed item from an array
- **`remove`** - removes passed item from an array (first one from the start)
- **`removeItems`** - removes multiple items from an array
- **`splice`** - signal-builder `Array.prototype.splice()`
- **`slice`** - signal-builder `Array.prototype.slice()`
- **`map`** - signal-builder `Array.prototype.map()`
- **`sort`** - signal-builder `Array.prototype.sort()`
- **`concat`** - Append multiple arrays together
- **`flatten`** - Flattens a nested array into a one-level array
- **`filterInstance`** - filter list: only leave items that are instances of specified Classes
- **`filterOutInstance`** - filter list: remove items that are instances of specified Classes

### 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.
- **`push`** — append items to an array
- **`drop`** — remove n items from the start
- **`dropRight`** — remove n items from the end
- **`filter`** — `Array.prototype.filter()`
- **`filterOut`** — remove all occurrences of a specific item
- **`remove`** — remove the first occurrence of a specific item
- **`removeItems`** — remove multiple specific items
- **`splice`** — `Array.prototype.splice()`
- **`slice`** — `Array.prototype.slice()`
- **`map`** — `Array.prototype.map()`
- **`sort`** — `Array.prototype.sort()`
- **`concat`** — concatenate multiple arrays
- **`flatten`** — flatten one level of nesting
- **`filterInstance`** — keep only items that are instances of the specified classes
- **`filterOutInstance`** — remove items that are instances of the specified classes

### Object

- **`omit`** - get an object copy without the provided keys
- **`pick`** - get an object copy with only the provided keys
- **`merge`** - Merges multiple objects into a single one.
- **`omit`** — copy an object without the specified keys
- **`pick`** — copy an object with only the specified keys
- **`get`** — read a value at a key path (up to 6 levels deep)
- **`merge`** — shallow merge of multiple objects
- **`update`** — immutably set a value at a key path; the last argument can be a new value or a setter function `(prev) => next`

### Convert

- **`string`** - turns passed value to a string
- **`float`** - turns passed string to an float number
- **`int`** - turns passed string to an intiger
- **`join`** - join array with a separator to a string
- **`string`** — convert a value to a string
- **`float`** — parse a string as a float (`Number.parseFloat`)
- **`int`** — parse a string as an integer (`Number.parseInt`)
- **`join`** join an array into a string with a separator

### Number

- **`add`** - `a + b + c + ...`
- **`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
- **`round`** - `Math.round()`
- **`ceil`** - `Math.ceil()`
- **`floor`** - `Math.floor()`
- **`add`** `a + b + c + ...`
- **`substract`** `a - b - c - ...`
- **`multiply`** `a * b * c * ...`
- **`divide`** `a / b / c / ...`
- **`power`** `a ** b ** c ** ...`
- **`clamp`** — constrain a value between min and max
- **`round`** `Math.round()`
- **`ceil`** `Math.ceil()`
- **`floor`** `Math.floor()`

### String

- **`add`** - `a + b + c + ...`
- **`lowercase`** - signal builder `String.prototype.toLowerCase()`
- **`uppercase`** - signal builder `String.prototype.toUpperCase()`
- **`capitalize`** - capitalize a string input e.g. `"solidJS"` -> `"Solidjs"`
- **`substring`** - signal builder `String.prototype.substring()`
- **`template`** - Create reactive string templates

## A call for feedback

`signal-builders` package is now a proof of concept of a fresh and experimental idea. Therefore all feedback/ideas/issues are highly welcome! :)
- **`lowercase`** — `String.prototype.toLowerCase()`
- **`uppercase`** — `String.prototype.toUpperCase()`
- **`capitalize`** — capitalize the first character and lowercase the rest
- **`substring`** — `String.prototype.substring()`
- **`add`** — `a + b + c + ...` (string concatenation)
- **`template`** — reactive tagged template literal

## Changelog

Expand Down
22 changes: 17 additions & 5 deletions packages/signal-builders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@solid-primitives/signal-builders",
"version": "0.2.3",
"description": "A collection of chainable and composable reactive signal calculations, aka Signal Builders.",
"author": "Your Name <you@youremail.com>",
"author": "Damian Tarnawski <gthetarnav@gmail.com>",
"license": "MIT",
"homepage": "https://primitives.solidjs.community/package/signal-builders",
"repository": {
Expand All @@ -13,14 +13,26 @@
"name": "signal-builders",
"stage": 2,
"list": [
"List of builders"
"push",
"filter",
"sort",
"map",
"get",
"merge",
"update",
"add",
"clamp",
"template"
],
"category": "Reactivity"
},
"keywords": [
"solid",
"primitives",
"fp"
"signal",
"reactive",
"fp",
"functional"
],
"files": [
"dist"
Expand Down Expand Up @@ -49,10 +61,10 @@
"@solid-primitives/utils": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"solid-js": "^2.0.0-beta.14"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"solid-js": "2.0.0-beta.14"
}
}
48 changes: 3 additions & 45 deletions packages/signal-builders/src/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ import * as _ from "@solid-primitives/utils/immutable";
import { type Accessor, createMemo } from "solid-js";
import type { MappingFn, Predicate, FlattenArray } from "@solid-primitives/utils/immutable";

/**
* signal-builder `Array.prototype.push()`
*/
export const push = <
A extends MaybeAccessor<any[]>,
V extends ItemsOf<MaybeAccessorValue<A>>,
Expand All @@ -24,62 +21,38 @@ export const push = <
): Accessor<(V | MaybeAccessorValue<ItemsOf<T>>)[]> =>
createMemo(() => _.push(access(list), ...accessArray(items)));

/**
* signal-builder that drops n items from the array start.
*/
export const drop = <T extends any[]>(list: MaybeAccessor<T>, n?: number): Accessor<T> =>
createMemo(() => _.drop(access(list), n) as T);

/**
* signal-builder that drops n items from the end of the array.
*/
export const dropRight = <T extends any[]>(list: MaybeAccessor<T>, n?: number): Accessor<T> =>
createMemo(() => _.dropRight(access(list), n) as T);

/**
* signal-builder `Array.prototype.filter()`
*/
export const filter = <A extends MaybeAccessor<any[]>, V extends ItemsOf<MaybeAccessorValue<A>>>(
list: A,
predicate: Predicate<V>,
): Accessor<V[]> => createMemo(() => _.filter(access(list), predicate));

/**
* signal-builder `Array.prototype.filter()` that filters out passed item
*/
export const filterOut = <A extends MaybeAccessor<any[]>, V extends ItemsOf<MaybeAccessorValue<A>>>(
list: A,
item: MaybeAccessor<V>,
): Accessor<V[]> => createMemo(() => _.filterOut(access(list), access(item)));

/**
* signal-builder `Array.prototype.sort()`
*/
export const sort = <A extends MaybeAccessor<any[]>, V extends ItemsOf<MaybeAccessorValue<A>>>(
list: A,
compareFn?: (a: V, b: V) => number,
): Accessor<V[]> => createMemo(() => _.sort(access(list), compareFn));

/**
* signal-builder `Array.prototype.map()`
*/
export const map = <A extends MaybeAccessor<any[]>, T>(
list: A,
mapFn: MappingFn<ItemsOf<MaybeAccessorValue<A>>, T>,
): Accessor<T[]> => createMemo(() => _.map(access(list), mapFn));

/**
* signal-builder `Array.prototype.slice()`
*/
export const slice = <T extends any[]>(
list: MaybeAccessor<T>,
start?: number,
end?: number,
): Accessor<T> => createMemo(() => _.slice(access(list), start, end) as T);

/**
* signal-builder `Array.prototype.splice()`
*/
export const splice = <
A extends MaybeAccessor<any[]>,
V extends ItemsOf<MaybeAccessorValue<A>>,
Expand All @@ -94,49 +67,34 @@ export const splice = <
_.splice(access(list), access(start), access(deleteCount), ...accessArray(items)),
);

/**
* signal-builder removing passed item from an array (first one from the start)
*/
/** Removes the first occurrence of `item`. */
export const remove = <A extends MaybeAccessor<any[]>, V extends ItemsOf<MaybeAccessorValue<A>>>(
list: A,
item: MaybeAccessor<V>,
): Accessor<V[]> => createMemo(() => _.remove(access(list), access(item)));

/**
* signal-builder removing multiple items from an array
*/
export const removeItems = <T extends any[]>(
list: MaybeAccessor<T>,
...items: MaybeAccessor<ItemsOf<T>>[]
): Accessor<T> => createMemo(() => _.removeItems(access(list), ...accessArray(items)) as T);

/**
* signal-builder appending multiple arrays together
*/
export const concat = <A extends MaybeAccessor<any>[], V extends MaybeAccessorValue<ItemsOf<A>>>(
...a: A
): Accessor<Array<V extends any[] ? ItemsOf<V> : V>> =>
createMemo(() => _.concat(...accessArray(a)));

/**
* Signal builder: Flattens a nested array into a one-level array
*/
export const flatten = <T extends any[]>(list: MaybeAccessor<T>): Accessor<FlattenArray<T>> =>
createMemo(() => _.flatten(access(list)) as FlattenArray<T>);

/**
* Signal builder: filter list: only leave items that are instances of specified Classes
*/
/** Keeps only items that are instances of any of the provided classes. */
export const filterInstance = <T, I extends AnyClass[]>(list: MaybeAccessor<T[]>, ...classes: I) =>
(classes.length === 1
? createMemo(() => access(list).filter(item => ofClass(item, classes[0]!)))
: createMemo(() =>
access(list).filter(item => item && classes.some(c => ofClass(item, c))),
)) as Accessor<Extract<T, InstanceType<ItemsOf<I>>>[]>;

/**
* Signal builder: filter list: remove items that are instances of specified Classes
*/
/** Removes items that are instances of any of the provided classes. */
export const filterOutInstance = <T, I extends AnyClass[]>(
list: MaybeAccessor<T[]>,
...classes: I
Expand Down
12 changes: 0 additions & 12 deletions packages/signal-builders/src/convert.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import { access, type MaybeAccessor } from "@solid-primitives/utils";
import { type Accessor, createMemo } from "solid-js";

/**
* signal-builder turning passed value to a string
*/
export const string = (from: any): Accessor<string> => createMemo(() => access(from) + "");

/**
* signal-builder turning passed string to an float number
*/
export const float = (input: MaybeAccessor<string>): Accessor<number> =>
createMemo(() => Number.parseFloat(access(input)));

/**
* signal-builder turning passed string to an intiger
*/
export const int = (input: MaybeAccessor<string>, radix?: number): Accessor<number> =>
createMemo(() => Number.parseInt(access(input), radix));

/**
* signal-builder joining array with a separator to a string
*/
export const join = <T extends any[]>(
list: MaybeAccessor<T>,
separator?: MaybeAccessor<string>,
Expand Down
Loading