From 85a6fbf801e080aa153165024fcf7ebe2007fc1a Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 Jan 2025 10:05:40 -0600 Subject: [PATCH 01/13] add entries to dev --- packages/keyed/dev/entries.tsx | 104 +++++++++++++++++++++++++++++++++ packages/keyed/dev/index.tsx | 10 +++- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 packages/keyed/dev/entries.tsx diff --git a/packages/keyed/dev/entries.tsx b/packages/keyed/dev/entries.tsx new file mode 100644 index 000000000..f15bf0943 --- /dev/null +++ b/packages/keyed/dev/entries.tsx @@ -0,0 +1,104 @@ +import { createStore, produce } from "solid-js/store"; +import { createEffect } from "solid-js"; +import { Entries } from "../src/index.js"; +import { TransitionGroup } from "solid-transition-group"; + +const foods = [ + "oatmeal", + "plantains", + "cranberries", + "chickpeas", + "tofu", + "Parmesan cheese", + "amaretto", + "sunflower seeds", + "grapes", + "vegemite", + "pasta", + "cider", + "chicken", + "pinto beans", + "bok choy", + "sweet peppers", + "Cappuccino Latte", + "corn", + "broccoli", + "brussels sprouts", + "bread", + "milk", + "honey", + "chips", + "cookie", +]; +const randomIndex = (list: readonly any[]): number => Math.floor(Math.random() * list.length); +const getRandomFood = () => foods[randomIndex(foods)]!; +const randomKey = (record: Record): string => { + const keys = Object.keys(record); + return keys[randomIndex(keys)]!; +}; + +export default function App() { + const [store, setStore] = createStore<{ entries: Record }>({ + entries: { + [Math.random()]: "bread", + [Math.random()]: "milk", + [Math.random()]: "honey", + [Math.random()]: "chips", + [Math.random()]: "cookie", + }, + }); + + const addRandom = () => { + setStore("entries", Math.random().toString(), getRandomFood()); + }; + const removeRandom = () => + setStore( + "entries", + produce(p => { + delete p[randomKey(p)]; + }), + ); + const clone = () => setStore("entries", p => JSON.parse(JSON.stringify(p))); + const changeRandomValue = () => setStore("entries", p => ({ [randomKey(p)]: getRandomFood() })); + + return ( + <> +
+ + + + +
+
+ + No items.

} + > + {(key, value, index) => { + createEffect(() => { + console.log("Effect:", key, value()); + }); + return ( +
+ {index()}. {value()} +
+ ID: {key} +
+
+ ); + }} +
+
+
+ + ); +} diff --git a/packages/keyed/dev/index.tsx b/packages/keyed/dev/index.tsx index 4c9b40c71..3caa86219 100644 --- a/packages/keyed/dev/index.tsx +++ b/packages/keyed/dev/index.tsx @@ -1,11 +1,19 @@ import { Component } from "solid-js"; import Key from "./key.js"; +import Entries from "./entries.js"; const App: Component = () => { return (
- +
+

Key

+ +
+
+

Entries

+ +
); }; From 1500975bc2750f85ea73796aeb536b3af406282f Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 Jan 2025 13:47:19 -0600 Subject: [PATCH 02/13] add --- packages/keyed/README.md | 32 +++++++++ packages/keyed/dev/index.tsx | 5 ++ packages/keyed/dev/mapEntries.tsx | 109 ++++++++++++++++++++++++++++++ packages/keyed/src/index.ts | 36 ++++++++++ 4 files changed, 182 insertions(+) create mode 100644 packages/keyed/dev/mapEntries.tsx diff --git a/packages/keyed/README.md b/packages/keyed/README.md index a6ed35ff7..f2f0df095 100644 --- a/packages/keyed/README.md +++ b/packages/keyed/README.md @@ -152,6 +152,38 @@ Third argument of the map function is an index signal. ``` +## `` + +Creates a list of elements by mapping Map entries. Similar to Solid's `` and ``, but here, render function takes three arguments, and both value and index arguments are signals. + +### How to use it + +```tsx +import { MapEntries } from "@solid-primitives/keyed"; + +No items}> + {(key, value) => ( +
+ {key}: {value()} +
+ )} +
; +``` + +### Index argument + +Third argument of the map function is an index signal. + +```tsx +No items}> + {(key, value, index) => ( +
+ {key}: {value()} +
+ )} +
+``` + ## `` Causes the children to rerender when the `on` key changes. Equivalent of `v-key` in vue, and `{#key}` in svelte. diff --git a/packages/keyed/dev/index.tsx b/packages/keyed/dev/index.tsx index 3caa86219..1a176d5ba 100644 --- a/packages/keyed/dev/index.tsx +++ b/packages/keyed/dev/index.tsx @@ -2,6 +2,7 @@ import { Component } from "solid-js"; import Key from "./key.js"; import Entries from "./entries.js"; +import MapEntries from "./mapEntries.js"; const App: Component = () => { return ( @@ -14,6 +15,10 @@ const App: Component = () => {

Entries

+
+

MapEntries

+ +
); }; diff --git a/packages/keyed/dev/mapEntries.tsx b/packages/keyed/dev/mapEntries.tsx new file mode 100644 index 000000000..0e9939ceb --- /dev/null +++ b/packages/keyed/dev/mapEntries.tsx @@ -0,0 +1,109 @@ +import { createStore } from "solid-js/store"; +import { createEffect } from "solid-js"; +import { MapEntries } from "../src/index.js"; +import { TransitionGroup } from "solid-transition-group"; + +const foods = [ + "oatmeal", + "plantains", + "cranberries", + "chickpeas", + "tofu", + "Parmesan cheese", + "amaretto", + "sunflower seeds", + "grapes", + "vegemite", + "pasta", + "cider", + "chicken", + "pinto beans", + "bok choy", + "sweet peppers", + "Cappuccino Latte", + "corn", + "broccoli", + "brussels sprouts", + "bread", + "milk", + "honey", + "chips", + "cookie", +]; +const randomIndex = (list: readonly any[]): number => Math.floor(Math.random() * list.length); +const getRandomFood = () => foods[randomIndex(foods)]!; +const randomKey = (map: Map): string => { + const keys = Array.from(map.keys()); + return keys[randomIndex(keys)]!; +}; + +export default function App() { + const [store, setStore] = createStore<{ entries: Map }>({ + entries: new Map([ + [Math.random().toString(), "bread"], + [Math.random().toString(), "milk"], + [Math.random().toString(), "honey"], + [Math.random().toString(), "chips"], + [Math.random().toString(), "cookie"], + ]), + }); + + const addRandom = () => { + setStore("entries", p => { + p.set(Math.random().toString(), getRandomFood()); + return new Map(p); + }); + }; + const removeRandom = () => + setStore("entries", p => { + p.delete(randomKey(p)); + return new Map(p); + }); + const clone = () => setStore("entries", p => new Map(p)); + const changeRandomValue = () => + setStore("entries", p => { + p.set(randomKey(p), getRandomFood()); + return new Map(p); + }); + + return ( + <> +
+ + + + +
+
+ + No items.

} + > + {(key, value, index) => { + createEffect(() => { + console.log("Effect:", key, value()); + }); + return ( +
+ {index()}. {value()} +
+ ID: {key} +
+
+ ); + }} +
+
+
+ + ); +} diff --git a/packages/keyed/src/index.ts b/packages/keyed/src/index.ts index 342a86795..2a5b7f246 100644 --- a/packages/keyed/src/index.ts +++ b/packages/keyed/src/index.ts @@ -201,6 +201,42 @@ export function Entries(props: { ) as unknown as JSX.Element; } +/** + * creates a list of elements from the entries of provided Map + * + * @param props + * @param props.of map to iterate entries of (`myMap.entries()`) + * @param props.children + * a map render function that receives an Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: + * ```tsx + * No items}> + * {(key, value, index) =>
{key}: {value()}
} + *
+ * ``` + * + * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#MapEntries + */ +export function MapEntries(props: { + of: Map | undefined | null | false; + fallback?: JSX.Element; + children: (key: K, v: Accessor, i: Accessor) => JSX.Element; +}): JSX.Element { + const mapFn = props.children; + return createMemo( + mapArray( + () => props.of && Array.from(props.of.keys()), + mapFn.length < 3 + ? key => + (mapFn as (key: K, v: Accessor) => JSX.Element)( + key, + () => (props.of as Map).get(key)!, + ) + : (key, i) => mapFn(key, () => (props.of as Map).get(key)!, i), + "fallback" in props ? { fallback: () => props.fallback } : undefined, + ), + ) as unknown as JSX.Element; +} + export type RerunChildren = ((input: T, prevInput: T | undefined) => JSX.Element) | JSX.Element; /** From ab7b9ddd0323c79158ff9924056bd69a29fad0f6 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 Jan 2025 14:45:35 -0600 Subject: [PATCH 03/13] demo that ReactiveMap works as expected --- packages/keyed/dev/index.tsx | 5 ++ packages/keyed/dev/reactiveMapEntries.tsx | 99 +++++++++++++++++++++++ packages/keyed/package.json | 1 + pnpm-lock.yaml | 3 + 4 files changed, 108 insertions(+) create mode 100644 packages/keyed/dev/reactiveMapEntries.tsx diff --git a/packages/keyed/dev/index.tsx b/packages/keyed/dev/index.tsx index 1a176d5ba..bc1b44c69 100644 --- a/packages/keyed/dev/index.tsx +++ b/packages/keyed/dev/index.tsx @@ -3,6 +3,7 @@ import { Component } from "solid-js"; import Key from "./key.js"; import Entries from "./entries.js"; import MapEntries from "./mapEntries.js"; +import ReactiveMapEntries from "./reactiveMapEntries.js"; const App: Component = () => { return ( @@ -19,6 +20,10 @@ const App: Component = () => {

MapEntries

+
+

MapEntries with ReactiveMap

+ +
); }; diff --git a/packages/keyed/dev/reactiveMapEntries.tsx b/packages/keyed/dev/reactiveMapEntries.tsx new file mode 100644 index 000000000..486c62527 --- /dev/null +++ b/packages/keyed/dev/reactiveMapEntries.tsx @@ -0,0 +1,99 @@ +import { createStore } from "solid-js/store"; +import { createEffect } from "solid-js"; +import { MapEntries } from "../src/index.js"; +import { TransitionGroup } from "solid-transition-group"; +import { ReactiveMap } from "@solid-primitives/map"; + +const foods = [ + "oatmeal", + "plantains", + "cranberries", + "chickpeas", + "tofu", + "Parmesan cheese", + "amaretto", + "sunflower seeds", + "grapes", + "vegemite", + "pasta", + "cider", + "chicken", + "pinto beans", + "bok choy", + "sweet peppers", + "Cappuccino Latte", + "corn", + "broccoli", + "brussels sprouts", + "bread", + "milk", + "honey", + "chips", + "cookie", +]; +const randomIndex = (list: readonly any[]): number => Math.floor(Math.random() * list.length); +const getRandomFood = () => foods[randomIndex(foods)]!; +const randomKey = (map: ReactiveMap): string => { + const keys = Array.from(map.keys()); + return keys[randomIndex(keys)]!; +}; + +export default function App() { + const [store, setStore] = createStore<{ entries: ReactiveMap }>({ + entries: new ReactiveMap([ + [Math.random().toString(), "bread"], + [Math.random().toString(), "milk"], + [Math.random().toString(), "honey"], + [Math.random().toString(), "chips"], + [Math.random().toString(), "cookie"], + ]), + }); + + const addRandom = () => { + store.entries.set(Math.random().toString(), getRandomFood()); + }; + const removeRandom = () => store.entries.delete(randomKey(store.entries)); + const clone = () => setStore("entries", p => new ReactiveMap(p)); + const changeRandomValue = () => store.entries.set(randomKey(store.entries), getRandomFood()); + + return ( + <> +
+ + + + +
+
+ + No items.

} + > + {(key, value, index) => { + createEffect(() => { + console.log("Effect:", key, value()); + }); + return ( +
+ {index()}. {value()} +
+ ID: {key} +
+
+ ); + }} +
+
+
+ + ); +} diff --git a/packages/keyed/package.json b/packages/keyed/package.json index a6c5f1969..fbfd4ff96 100644 --- a/packages/keyed/package.json +++ b/packages/keyed/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@solid-primitives/refs": "workspace:^", + "@solid-primitives/map": "workspace:^", "@solid-primitives/utils": "workspace:^", "solid-js": "^1.8.7", "solid-transition-group": "^0.2.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index addbfa8d1..9af2a13f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -492,6 +492,9 @@ importers: packages/keyed: devDependencies: + '@solid-primitives/map': + specifier: workspace:^ + version: link:../map '@solid-primitives/refs': specifier: workspace:^ version: link:../refs From cf11535f0d47342ac45c3609f0373ff8c8ff2088 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 Jan 2025 15:04:15 -0600 Subject: [PATCH 04/13] add changeset --- .changeset/sharp-beers-approve.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-beers-approve.md diff --git a/.changeset/sharp-beers-approve.md b/.changeset/sharp-beers-approve.md new file mode 100644 index 000000000..d7d21b1ab --- /dev/null +++ b/.changeset/sharp-beers-approve.md @@ -0,0 +1,5 @@ +--- +"@solid-primitives/keyed": minor +--- + +Add `MapEntries` control flow component. From 37c3d351ab85a391aa8530b16733b2b853ed4522 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 1 Jan 2025 16:11:49 -0600 Subject: [PATCH 05/13] too much pasta --- packages/keyed/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/keyed/src/index.ts b/packages/keyed/src/index.ts index 2a5b7f246..9a8791eb2 100644 --- a/packages/keyed/src/index.ts +++ b/packages/keyed/src/index.ts @@ -207,7 +207,7 @@ export function Entries(props: { * @param props * @param props.of map to iterate entries of (`myMap.entries()`) * @param props.children - * a map render function that receives an Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: + * a map render function that receives a Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: * ```tsx * No items}> * {(key, value, index) =>
{key}: {value()}
} From 9529fd747b6b0908856fd0268b31ece99f008219 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 2 Jan 2025 13:51:44 -0600 Subject: [PATCH 06/13] mv and small tweaks --- .changeset/sharp-beers-approve.md | 2 +- packages/keyed/README.md | 32 --------------- packages/keyed/dev/entries.tsx | 2 + packages/keyed/dev/index.tsx | 10 ----- packages/keyed/dev/key.tsx | 2 + packages/keyed/package.json | 1 - packages/keyed/src/index.ts | 37 +----------------- packages/map/README.md | 37 +++++++++++++++++- packages/map/dev/index.tsx | 11 ++++++ packages/{keyed => map}/dev/mapEntries.tsx | 2 + .../{keyed => map}/dev/reactiveMapEntries.tsx | 2 + packages/map/package.json | 3 +- packages/map/src/index.ts | 39 ++++++++++++++++++- pnpm-lock.yaml | 6 +-- 14 files changed, 99 insertions(+), 87 deletions(-) rename packages/{keyed => map}/dev/mapEntries.tsx (96%) rename packages/{keyed => map}/dev/reactiveMapEntries.tsx (96%) diff --git a/.changeset/sharp-beers-approve.md b/.changeset/sharp-beers-approve.md index d7d21b1ab..a9a483b60 100644 --- a/.changeset/sharp-beers-approve.md +++ b/.changeset/sharp-beers-approve.md @@ -1,5 +1,5 @@ --- -"@solid-primitives/keyed": minor +"@solid-primitives/map": minor --- Add `MapEntries` control flow component. diff --git a/packages/keyed/README.md b/packages/keyed/README.md index f2f0df095..a6ed35ff7 100644 --- a/packages/keyed/README.md +++ b/packages/keyed/README.md @@ -152,38 +152,6 @@ Third argument of the map function is an index signal.
``` -## `` - -Creates a list of elements by mapping Map entries. Similar to Solid's `` and ``, but here, render function takes three arguments, and both value and index arguments are signals. - -### How to use it - -```tsx -import { MapEntries } from "@solid-primitives/keyed"; - -No items}> - {(key, value) => ( -
- {key}: {value()} -
- )} -
; -``` - -### Index argument - -Third argument of the map function is an index signal. - -```tsx -No items}> - {(key, value, index) => ( -
- {key}: {value()} -
- )} -
-``` - ## `` Causes the children to rerender when the `on` key changes. Equivalent of `v-key` in vue, and `{#key}` in svelte. diff --git a/packages/keyed/dev/entries.tsx b/packages/keyed/dev/entries.tsx index f15bf0943..5cccaaeaf 100644 --- a/packages/keyed/dev/entries.tsx +++ b/packages/keyed/dev/entries.tsx @@ -1,3 +1,5 @@ +// changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 + import { createStore, produce } from "solid-js/store"; import { createEffect } from "solid-js"; import { Entries } from "../src/index.js"; diff --git a/packages/keyed/dev/index.tsx b/packages/keyed/dev/index.tsx index bc1b44c69..3caa86219 100644 --- a/packages/keyed/dev/index.tsx +++ b/packages/keyed/dev/index.tsx @@ -2,8 +2,6 @@ import { Component } from "solid-js"; import Key from "./key.js"; import Entries from "./entries.js"; -import MapEntries from "./mapEntries.js"; -import ReactiveMapEntries from "./reactiveMapEntries.js"; const App: Component = () => { return ( @@ -16,14 +14,6 @@ const App: Component = () => {

Entries

-
-

MapEntries

- -
-
-

MapEntries with ReactiveMap

- -
); }; diff --git a/packages/keyed/dev/key.tsx b/packages/keyed/dev/key.tsx index 593a4c7eb..b14e93318 100644 --- a/packages/keyed/dev/key.tsx +++ b/packages/keyed/dev/key.tsx @@ -1,3 +1,5 @@ +// changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 + import { splice, update } from "@solid-primitives/utils/immutable"; import { createEffect, createSignal } from "solid-js"; import { Key } from "../src/index.js"; diff --git a/packages/keyed/package.json b/packages/keyed/package.json index fbfd4ff96..a6c5f1969 100644 --- a/packages/keyed/package.json +++ b/packages/keyed/package.json @@ -54,7 +54,6 @@ }, "devDependencies": { "@solid-primitives/refs": "workspace:^", - "@solid-primitives/map": "workspace:^", "@solid-primitives/utils": "workspace:^", "solid-js": "^1.8.7", "solid-transition-group": "^0.2.3" diff --git a/packages/keyed/src/index.ts b/packages/keyed/src/index.ts index 9a8791eb2..7d8f9a8e8 100644 --- a/packages/keyed/src/index.ts +++ b/packages/keyed/src/index.ts @@ -185,6 +185,7 @@ export function Entries(props: { i: Accessor, ) => JSX.Element; }): JSX.Element { + // changes to this function may be applicable to similar functions - grep 4A29BECD-767A-4CC0-AEBB-3543D7B444C6 const mapFn = props.children; return createMemo( mapArray( @@ -201,42 +202,6 @@ export function Entries(props: { ) as unknown as JSX.Element; } -/** - * creates a list of elements from the entries of provided Map - * - * @param props - * @param props.of map to iterate entries of (`myMap.entries()`) - * @param props.children - * a map render function that receives a Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: - * ```tsx - * No items}> - * {(key, value, index) =>
{key}: {value()}
} - *
- * ``` - * - * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#MapEntries - */ -export function MapEntries(props: { - of: Map | undefined | null | false; - fallback?: JSX.Element; - children: (key: K, v: Accessor, i: Accessor) => JSX.Element; -}): JSX.Element { - const mapFn = props.children; - return createMemo( - mapArray( - () => props.of && Array.from(props.of.keys()), - mapFn.length < 3 - ? key => - (mapFn as (key: K, v: Accessor) => JSX.Element)( - key, - () => (props.of as Map).get(key)!, - ) - : (key, i) => mapFn(key, () => (props.of as Map).get(key)!, i), - "fallback" in props ? { fallback: () => props.fallback } : undefined, - ), - ) as unknown as JSX.Element; -} - export type RerunChildren = ((input: T, prevInput: T | undefined) => JSX.Element) | JSX.Element; /** diff --git a/packages/map/README.md b/packages/map/README.md index 0f36a3557..85e2255f5 100644 --- a/packages/map/README.md +++ b/packages/map/README.md @@ -7,12 +7,13 @@ [![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/) [![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/map?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/map) [![version](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/map) -[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fasmaps%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives#contribution-process) +[![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) -The reactive versions of `Map` & `WeakMap` built-in data structures. +The reactive versions of `Map` & `WeakMap` built-in data structures and control flow component. - **[`ReactiveMap`](#reactivemap-and-reactiveweakmap)** - A reactive `Map`. - **[`ReactiveWeakMap`](#reactivemap-and-reactiveweakmap)** - A reactive `WeakMap`. +- **[`MapEntries`](#mapentries)** - Creates a list of elements by mapping Map entries. ## Installation @@ -83,6 +84,38 @@ map.get("John").age = 35; map.set("John", { age: 35 }); ``` +## `` + +Creates a list of elements by mapping Map entries. Similar to Solid's `` and ``, but here, render function takes three arguments, and both value and index arguments are signals. + +### How to use it + +```tsx +import { MapEntries } from "@solid-primitives/map"; + +No items}> + {(key, value) => ( +
+ {key}: {value()} +
+ )} +
; +``` + +### Index argument + +Third argument of the map function is an index signal. + +```tsx +No items}> + {(key, value, index) => ( +
+ {key}: {value()} +
+ )} +
+``` + ## Changelog See [CHANGELOG.md](./CHANGELOG.md) diff --git a/packages/map/dev/index.tsx b/packages/map/dev/index.tsx index 6db4cba48..cb34d2ca8 100644 --- a/packages/map/dev/index.tsx +++ b/packages/map/dev/index.tsx @@ -5,6 +5,9 @@ import { ReactiveMap } from "../src/index.js"; import { onCleanup } from "solid-js"; import { createEffect } from "solid-js"; +import MapEntries from "./mapEntries.js"; +import ReactiveMapEntries from "./reactiveMapEntries.js"; + const App: Component = () => { const map = new ReactiveMap(); @@ -43,6 +46,14 @@ const App: Component = () => {

size: {map.size}

+
+

MapEntries

+ +
+
+

MapEntries with ReactiveMap

+ +
); }; diff --git a/packages/keyed/dev/mapEntries.tsx b/packages/map/dev/mapEntries.tsx similarity index 96% rename from packages/keyed/dev/mapEntries.tsx rename to packages/map/dev/mapEntries.tsx index 0e9939ceb..7ec5b25ef 100644 --- a/packages/keyed/dev/mapEntries.tsx +++ b/packages/map/dev/mapEntries.tsx @@ -1,3 +1,5 @@ +// changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 + import { createStore } from "solid-js/store"; import { createEffect } from "solid-js"; import { MapEntries } from "../src/index.js"; diff --git a/packages/keyed/dev/reactiveMapEntries.tsx b/packages/map/dev/reactiveMapEntries.tsx similarity index 96% rename from packages/keyed/dev/reactiveMapEntries.tsx rename to packages/map/dev/reactiveMapEntries.tsx index 486c62527..dd2b15c29 100644 --- a/packages/keyed/dev/reactiveMapEntries.tsx +++ b/packages/map/dev/reactiveMapEntries.tsx @@ -1,3 +1,5 @@ +// changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 + import { createStore } from "solid-js/store"; import { createEffect } from "solid-js"; import { MapEntries } from "../src/index.js"; diff --git a/packages/map/package.json b/packages/map/package.json index db2379168..a2f51c3fc 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -62,6 +62,7 @@ }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.8.7" + "solid-js": "^1.8.7", + "solid-transition-group": "^0.2.3" } } diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index d60889400..0f5e6a0f8 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -1,4 +1,4 @@ -import { Accessor, batch } from "solid-js"; +import { type JSX, Accessor, batch, createMemo, mapArray } from "solid-js"; import { TriggerCache } from "@solid-primitives/trigger"; const $KEYS = Symbol("track-keys"); @@ -187,3 +187,40 @@ export function createMap(initial?: [K, V][]): SignalMap { export function createWeakMap(initial?: [K, V][]): ReactiveWeakMap { return new ReactiveWeakMap(initial); } + +/** + * Creates a list of elements from the entries of provided Map + * + * @param props + * @param props.of map to iterate entries of (`myMap.entries()`) + * @param props.children + * a map render function that receives a Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: + * ```tsx + * No items}> + * {(key, value, index) =>
{key}: {value()}
} + *
+ * ``` + * + * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#mapentries + */ +export function MapEntries(props: { + of: Map | undefined | null | false; + fallback?: JSX.Element; + children: (key: K, v: Accessor, i: Accessor) => JSX.Element; +}): JSX.Element { + // changes to this function may be applicable to similar functions - grep 4A29BECD-767A-4CC0-AEBB-3543D7B444C6 + const mapFn = props.children; + return createMemo( + mapArray( + () => props.of && Array.from(props.of.keys()), + mapFn.length < 3 + ? key => + (mapFn as (key: K, v: Accessor) => JSX.Element)( + key, + () => (props.of as Map).get(key)!, + ) + : (key, i) => mapFn(key, () => (props.of as Map).get(key)!, i), + "fallback" in props ? { fallback: () => props.fallback } : undefined, + ), + ) as unknown as JSX.Element; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9af2a13f0..a77bb1526 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -492,9 +492,6 @@ importers: packages/keyed: devDependencies: - '@solid-primitives/map': - specifier: workspace:^ - version: link:../map '@solid-primitives/refs': specifier: workspace:^ version: link:../refs @@ -529,6 +526,9 @@ importers: solid-js: specifier: ^1.8.7 version: 1.8.20 + solid-transition-group: + specifier: ^0.2.3 + version: 0.2.3(solid-js@1.8.20) packages/marker: devDependencies: From 5f04a47d3ea49b3caa805026e2dc62b77baeb018 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 2 Jan 2025 15:32:02 -0600 Subject: [PATCH 07/13] add to main readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 311855f70..6fc5b36bf 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ The goal of Solid Primitives is to wrap client and server side functionality to |[destructure](https://github.com/solidjs-community/solid-primitives/tree/main/packages/destructure#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[destructure](https://github.com/solidjs-community/solid-primitives/tree/main/packages/destructure#destructure)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/destructure?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/destructure)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/destructure?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/destructure)| |[immutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/immutable#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-1.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createImmutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/immutable#createimmutable)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/immutable?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/immutable)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/immutable?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/immutable)| |[lifecycle](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createIsMounted](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#createismounted)
[isHydrated](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#ishydrated)
[onConnect](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#onconnect)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/lifecycle?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/lifecycle)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/lifecycle?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/lifecycle)| -|[map](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[ReactiveMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactivemap)
[ReactiveWeakMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactiveweakmap)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/map?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/map)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/map)| +|[map](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[ReactiveMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactivemap)
[ReactiveWeakMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactiveweakmap)
[MapEntries](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#mapentries)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/map?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/map)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/map)| |[memo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createLatest](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlatest)
[createLatestMany](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlatestmany)
[createWritableMemo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createwritablememo)
[createLazyMemo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlazymemo)
[createPureReaction](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createpurereaction)
[createMemoCache](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#creatememocache)
[createReducer](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createreducer)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/memo?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/memo)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/memo?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/memo)| |[mutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createMutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#createmutable)
[modifyMutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#modifymutable)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/mutable?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/mutable)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/mutable?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/mutable)| |[resource](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createAggregated](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createaggregated)
[createDeepSignal](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createdeepsignal)
[makeAbortable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makeabortable)
[createAbortable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createabortable)
[makeCache](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makecache)
[makeRetrying](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makeretrying)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/resource?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/resource)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/resource?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/resource)| From d27c49884bb9de2eee9175feabbb2fece543e847 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 2 Jan 2025 16:59:05 -0600 Subject: [PATCH 08/13] mv back --- .changeset/sharp-beers-approve.md | 2 +- README.md | 4 +- packages/keyed/README.md | 33 +++++++ packages/keyed/dev/index.tsx | 5 + packages/{map => keyed}/dev/mapEntries.tsx | 0 packages/keyed/src/index.ts | 37 ++++++++ packages/map/README.md | 35 +------ packages/map/dev/index.tsx | 11 --- packages/map/dev/reactiveMapEntries.tsx | 101 --------------------- packages/map/package.json | 3 +- packages/map/src/index.ts | 39 +------- pnpm-lock.yaml | 3 - 12 files changed, 81 insertions(+), 192 deletions(-) rename packages/{map => keyed}/dev/mapEntries.tsx (100%) delete mode 100644 packages/map/dev/reactiveMapEntries.tsx diff --git a/.changeset/sharp-beers-approve.md b/.changeset/sharp-beers-approve.md index a9a483b60..d7d21b1ab 100644 --- a/.changeset/sharp-beers-approve.md +++ b/.changeset/sharp-beers-approve.md @@ -1,5 +1,5 @@ --- -"@solid-primitives/map": minor +"@solid-primitives/keyed": minor --- Add `MapEntries` control flow component. diff --git a/README.md b/README.md index 6fc5b36bf..4cb006a29 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The goal of Solid Primitives is to wrap client and server side functionality to |

*Control Flow*

| |[context](https://github.com/solidjs-community/solid-primitives/tree/main/packages/context#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createContextProvider](https://github.com/solidjs-community/solid-primitives/tree/main/packages/context#createcontextprovider)
[MultiProvider](https://github.com/solidjs-community/solid-primitives/tree/main/packages/context#multiprovider)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/context?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/context)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/context?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/context)| |[jsx-tokenizer](https://github.com/solidjs-community/solid-primitives/tree/main/packages/jsx-tokenizer#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createTokenizer](https://github.com/solidjs-community/solid-primitives/tree/main/packages/jsx-tokenizer#createtokenizer)
[createToken](https://github.com/solidjs-community/solid-primitives/tree/main/packages/jsx-tokenizer#createtoken)
[resolveTokens](https://github.com/solidjs-community/solid-primitives/tree/main/packages/jsx-tokenizer#resolvetokens)
[isToken](https://github.com/solidjs-community/solid-primitives/tree/main/packages/jsx-tokenizer#istoken)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/jsx-tokenizer?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/jsx-tokenizer)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/jsx-tokenizer?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/jsx-tokenizer)| -|[keyed](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[keyArray](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#keyarray)
[Key](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#key)
[Entries](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#entries)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/keyed?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/keyed)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/keyed?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/keyed)| +|[keyed](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[keyArray](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#keyarray)
[Key](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#key)
[Entries](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#entries)
[MapEntries](https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#mapentries)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/keyed?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/keyed)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/keyed?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/keyed)| |[list](https://github.com/solidjs-community/solid-primitives/tree/main/packages/list#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[listArray](https://github.com/solidjs-community/solid-primitives/tree/main/packages/list#listarray)
[List](https://github.com/solidjs-community/solid-primitives/tree/main/packages/list#list)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/list?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/list)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/list?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/list)| |[range](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-1.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[repeat](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#repeat)
[mapRange](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#maprange)
[indexRange](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#indexrange)
[Repeat](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#repeat)
[Range](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#range)
[IndexRange](https://github.com/solidjs-community/solid-primitives/tree/main/packages/range#indexrange)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/range?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/range)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/range?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/range)| |[refs](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[mergeRefs](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#mergerefs)
[resolveElements](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#resolveelements)
[resolveFirst](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#resolvefirst)
[Ref](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#ref)
[Refs](https://github.com/solidjs-community/solid-primitives/tree/main/packages/refs#refs)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/refs?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/refs)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/refs?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/refs)| @@ -89,7 +89,7 @@ The goal of Solid Primitives is to wrap client and server side functionality to |[destructure](https://github.com/solidjs-community/solid-primitives/tree/main/packages/destructure#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[destructure](https://github.com/solidjs-community/solid-primitives/tree/main/packages/destructure#destructure)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/destructure?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/destructure)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/destructure?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/destructure)| |[immutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/immutable#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-1.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createImmutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/immutable#createimmutable)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/immutable?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/immutable)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/immutable?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/immutable)| |[lifecycle](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createIsMounted](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#createismounted)
[isHydrated](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#ishydrated)
[onConnect](https://github.com/solidjs-community/solid-primitives/tree/main/packages/lifecycle#onconnect)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/lifecycle?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/lifecycle)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/lifecycle?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/lifecycle)| -|[map](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[ReactiveMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactivemap)
[ReactiveWeakMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactiveweakmap)
[MapEntries](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#mapentries)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/map?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/map)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/map)| +|[map](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[ReactiveMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactivemap)
[ReactiveWeakMap](https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#reactiveweakmap)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/map?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/map)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/map)| |[memo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createLatest](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlatest)
[createLatestMany](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlatestmany)
[createWritableMemo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createwritablememo)
[createLazyMemo](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createlazymemo)
[createPureReaction](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createpurereaction)
[createMemoCache](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#creatememocache)
[createReducer](https://github.com/solidjs-community/solid-primitives/tree/main/packages/memo#createreducer)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/memo?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/memo)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/memo?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/memo)| |[mutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-3.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createMutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#createmutable)
[modifyMutable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/mutable#modifymutable)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/mutable?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/mutable)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/mutable?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/mutable)| |[resource](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#readme)|[![STAGE](https://img.shields.io/endpoint?style=for-the-badge&label=&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-2.json)](https://github.com/solidjs-community/solid-primitives/blob/main/CONTRIBUTING.md#contribution-process)|[createAggregated](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createaggregated)
[createDeepSignal](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createdeepsignal)
[makeAbortable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makeabortable)
[createAbortable](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#createabortable)
[makeCache](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makecache)
[makeRetrying](https://github.com/solidjs-community/solid-primitives/tree/main/packages/resource#makeretrying)|[![SIZE](https://img.shields.io/bundlephobia/minzip/@solid-primitives/resource?style=for-the-badge&label=)](https://bundlephobia.com/package/@solid-primitives/resource)|[![VERSION](https://img.shields.io/npm/v/@solid-primitives/resource?style=for-the-badge&label=)](https://www.npmjs.com/package/@solid-primitives/resource)| diff --git a/packages/keyed/README.md b/packages/keyed/README.md index a6ed35ff7..293135969 100644 --- a/packages/keyed/README.md +++ b/packages/keyed/README.md @@ -14,6 +14,7 @@ Control Flow primitives and components that require specifying explicit keys to - [`keyArray`](#keyarray) - Reactively maps an array by specified key with a callback function - underlying helper for the `` control flow. - [`Key`](#key) - Creates a list of elements by mapping items by provided key. - [`Entries`](#entries) - Creates a list of elements by mapping object entries. +- [`MapEntries`](#mapentries) - Creates a list of elements by mapping Map entries. - [`Rerun`](#rerun) - Causes the children to rerender when the `on` changes. ## Installation @@ -152,6 +153,38 @@ Third argument of the map function is an index signal.
``` +## `` + +Creates a list of elements by mapping Map entries. Similar to Solid's `` and ``, but here, render function takes three arguments, and both value and index arguments are signals. + +### How to use it + +```tsx +import { MapEntries } from "@solid-primitives/keyed"; + +No items}> + {(key, value) => ( +
+ {key}: {value()} +
+ )} +
; +``` + +### Index argument + +Third argument of the map function is an index signal. + +```tsx +No items}> + {(key, value, index) => ( +
+ {key}: {value()} +
+ )} +
+``` + ## `` Causes the children to rerender when the `on` key changes. Equivalent of `v-key` in vue, and `{#key}` in svelte. diff --git a/packages/keyed/dev/index.tsx b/packages/keyed/dev/index.tsx index 3caa86219..1a176d5ba 100644 --- a/packages/keyed/dev/index.tsx +++ b/packages/keyed/dev/index.tsx @@ -2,6 +2,7 @@ import { Component } from "solid-js"; import Key from "./key.js"; import Entries from "./entries.js"; +import MapEntries from "./mapEntries.js"; const App: Component = () => { return ( @@ -14,6 +15,10 @@ const App: Component = () => {

Entries

+
+

MapEntries

+ +
); }; diff --git a/packages/map/dev/mapEntries.tsx b/packages/keyed/dev/mapEntries.tsx similarity index 100% rename from packages/map/dev/mapEntries.tsx rename to packages/keyed/dev/mapEntries.tsx diff --git a/packages/keyed/src/index.ts b/packages/keyed/src/index.ts index 7d8f9a8e8..8be5e01a7 100644 --- a/packages/keyed/src/index.ts +++ b/packages/keyed/src/index.ts @@ -202,6 +202,43 @@ export function Entries(props: { ) as unknown as JSX.Element; } +/** + * Creates a list of elements from the entries of provided Map + * + * @param props + * @param props.of map to iterate entries of (`myMap.entries()`) + * @param props.children + * a map render function that receives a Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: + * ```tsx + * No items}> + * {(key, value, index) =>
{key}: {value()}
} + *
+ * ``` + * + * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#mapentries + */ +export function MapEntries(props: { + of: Map | undefined | null | false; + fallback?: JSX.Element; + children: (key: K, v: Accessor, i: Accessor) => JSX.Element; +}): JSX.Element { + // changes to this function may be applicable to similar functions - grep 4A29BECD-767A-4CC0-AEBB-3543D7B444C6 + const mapFn = props.children; + return createMemo( + mapArray( + () => props.of && Array.from(props.of.keys()), + mapFn.length < 3 + ? key => + (mapFn as (key: K, v: Accessor) => JSX.Element)( + key, + () => (props.of as Map).get(key)!, + ) + : (key, i) => mapFn(key, () => (props.of as Map).get(key)!, i), + "fallback" in props ? { fallback: () => props.fallback } : undefined, + ), + ) as unknown as JSX.Element; +} + export type RerunChildren = ((input: T, prevInput: T | undefined) => JSX.Element) | JSX.Element; /** diff --git a/packages/map/README.md b/packages/map/README.md index 85e2255f5..42e2cdf3c 100644 --- a/packages/map/README.md +++ b/packages/map/README.md @@ -9,11 +9,10 @@ [![version](https://img.shields.io/npm/v/@solid-primitives/map?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/map) [![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) -The reactive versions of `Map` & `WeakMap` built-in data structures and control flow component. +The reactive versions of `Map` & `WeakMap` built-in data structures. - **[`ReactiveMap`](#reactivemap-and-reactiveweakmap)** - A reactive `Map`. - **[`ReactiveWeakMap`](#reactivemap-and-reactiveweakmap)** - A reactive `WeakMap`. -- **[`MapEntries`](#mapentries)** - Creates a list of elements by mapping Map entries. ## Installation @@ -84,38 +83,6 @@ map.get("John").age = 35; map.set("John", { age: 35 }); ``` -## `` - -Creates a list of elements by mapping Map entries. Similar to Solid's `` and ``, but here, render function takes three arguments, and both value and index arguments are signals. - -### How to use it - -```tsx -import { MapEntries } from "@solid-primitives/map"; - -No items}> - {(key, value) => ( -
- {key}: {value()} -
- )} -
; -``` - -### Index argument - -Third argument of the map function is an index signal. - -```tsx -No items}> - {(key, value, index) => ( -
- {key}: {value()} -
- )} -
-``` - ## Changelog See [CHANGELOG.md](./CHANGELOG.md) diff --git a/packages/map/dev/index.tsx b/packages/map/dev/index.tsx index cb34d2ca8..6db4cba48 100644 --- a/packages/map/dev/index.tsx +++ b/packages/map/dev/index.tsx @@ -5,9 +5,6 @@ import { ReactiveMap } from "../src/index.js"; import { onCleanup } from "solid-js"; import { createEffect } from "solid-js"; -import MapEntries from "./mapEntries.js"; -import ReactiveMapEntries from "./reactiveMapEntries.js"; - const App: Component = () => { const map = new ReactiveMap(); @@ -46,14 +43,6 @@ const App: Component = () => {

size: {map.size}

-
-

MapEntries

- -
-
-

MapEntries with ReactiveMap

- -
); }; diff --git a/packages/map/dev/reactiveMapEntries.tsx b/packages/map/dev/reactiveMapEntries.tsx deleted file mode 100644 index dd2b15c29..000000000 --- a/packages/map/dev/reactiveMapEntries.tsx +++ /dev/null @@ -1,101 +0,0 @@ -// changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 - -import { createStore } from "solid-js/store"; -import { createEffect } from "solid-js"; -import { MapEntries } from "../src/index.js"; -import { TransitionGroup } from "solid-transition-group"; -import { ReactiveMap } from "@solid-primitives/map"; - -const foods = [ - "oatmeal", - "plantains", - "cranberries", - "chickpeas", - "tofu", - "Parmesan cheese", - "amaretto", - "sunflower seeds", - "grapes", - "vegemite", - "pasta", - "cider", - "chicken", - "pinto beans", - "bok choy", - "sweet peppers", - "Cappuccino Latte", - "corn", - "broccoli", - "brussels sprouts", - "bread", - "milk", - "honey", - "chips", - "cookie", -]; -const randomIndex = (list: readonly any[]): number => Math.floor(Math.random() * list.length); -const getRandomFood = () => foods[randomIndex(foods)]!; -const randomKey = (map: ReactiveMap): string => { - const keys = Array.from(map.keys()); - return keys[randomIndex(keys)]!; -}; - -export default function App() { - const [store, setStore] = createStore<{ entries: ReactiveMap }>({ - entries: new ReactiveMap([ - [Math.random().toString(), "bread"], - [Math.random().toString(), "milk"], - [Math.random().toString(), "honey"], - [Math.random().toString(), "chips"], - [Math.random().toString(), "cookie"], - ]), - }); - - const addRandom = () => { - store.entries.set(Math.random().toString(), getRandomFood()); - }; - const removeRandom = () => store.entries.delete(randomKey(store.entries)); - const clone = () => setStore("entries", p => new ReactiveMap(p)); - const changeRandomValue = () => store.entries.set(randomKey(store.entries), getRandomFood()); - - return ( - <> -
- - - - -
-
- - No items.

} - > - {(key, value, index) => { - createEffect(() => { - console.log("Effect:", key, value()); - }); - return ( -
- {index()}. {value()} -
- ID: {key} -
-
- ); - }} -
-
-
- - ); -} diff --git a/packages/map/package.json b/packages/map/package.json index a2f51c3fc..db2379168 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -62,7 +62,6 @@ }, "typesVersions": {}, "devDependencies": { - "solid-js": "^1.8.7", - "solid-transition-group": "^0.2.3" + "solid-js": "^1.8.7" } } diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 0f5e6a0f8..d60889400 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -1,4 +1,4 @@ -import { type JSX, Accessor, batch, createMemo, mapArray } from "solid-js"; +import { Accessor, batch } from "solid-js"; import { TriggerCache } from "@solid-primitives/trigger"; const $KEYS = Symbol("track-keys"); @@ -187,40 +187,3 @@ export function createMap(initial?: [K, V][]): SignalMap { export function createWeakMap(initial?: [K, V][]): ReactiveWeakMap { return new ReactiveWeakMap(initial); } - -/** - * Creates a list of elements from the entries of provided Map - * - * @param props - * @param props.of map to iterate entries of (`myMap.entries()`) - * @param props.children - * a map render function that receives a Map key, **value signal** and **index signal** and returns a JSX-Element; if the list is empty, an optional fallback is returned: - * ```tsx - * No items}> - * {(key, value, index) =>
{key}: {value()}
} - *
- * ``` - * - * @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/map#mapentries - */ -export function MapEntries(props: { - of: Map | undefined | null | false; - fallback?: JSX.Element; - children: (key: K, v: Accessor, i: Accessor) => JSX.Element; -}): JSX.Element { - // changes to this function may be applicable to similar functions - grep 4A29BECD-767A-4CC0-AEBB-3543D7B444C6 - const mapFn = props.children; - return createMemo( - mapArray( - () => props.of && Array.from(props.of.keys()), - mapFn.length < 3 - ? key => - (mapFn as (key: K, v: Accessor) => JSX.Element)( - key, - () => (props.of as Map).get(key)!, - ) - : (key, i) => mapFn(key, () => (props.of as Map).get(key)!, i), - "fallback" in props ? { fallback: () => props.fallback } : undefined, - ), - ) as unknown as JSX.Element; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a77bb1526..addbfa8d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -526,9 +526,6 @@ importers: solid-js: specifier: ^1.8.7 version: 1.8.20 - solid-transition-group: - specifier: ^0.2.3 - version: 0.2.3(solid-js@1.8.20) packages/marker: devDependencies: From 7712d1915c6737bdb5df19ecc26523905b35011e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 2 Jan 2025 17:23:01 -0600 Subject: [PATCH 09/13] test --- packages/keyed/test/index.test.ts | 209 -------------- packages/keyed/test/index.test.tsx | 443 +++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+), 209 deletions(-) delete mode 100644 packages/keyed/test/index.test.ts create mode 100644 packages/keyed/test/index.test.tsx diff --git a/packages/keyed/test/index.test.ts b/packages/keyed/test/index.test.ts deleted file mode 100644 index 435d770bd..000000000 --- a/packages/keyed/test/index.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { createComputed, createMemo, createRoot, createSignal } from "solid-js"; -import { createStore } from "solid-js/store"; -import { update } from "@solid-primitives/utils/immutable"; -import { describe, expect, test } from "vitest"; -import { keyArray } from "../src/index.js"; - -const el1 = { id: 1, value: "bread" }; -const el2 = { id: 2, value: "milk" }; -const el3 = { id: 3, value: "honey" }; -const el4 = { id: 4, value: "chips" }; - -describe("keyArray", () => { - test("maps and returns all initial items", () => - createRoot(dispose => { - const mapped = keyArray( - () => [el1, el2, el3], - v => v.id, - v => ({ ...v(), key: v().id }), - ); - expect(mapped().length).toBe(3); - expect(mapped()[0].key).toBe(1); - expect(mapped()[1].key).toBe(2); - expect(mapped()[2].key).toBe(3); - - dispose(); - })); - - test("cloning list should have no effect", () => - createRoot(dispose => { - const [list, setList] = createSignal([el1, el2, el3]); - let changes = 0; - const mapped = keyArray( - list, - v => v.id, - v => v().id, - ); - createComputed(() => mapped(), changes++); - expect(mapped()).toEqual([1, 2, 3]); - expect(changes).toBe(1); - - setList(p => p.slice()); - expect(mapped()).toEqual([1, 2, 3]); - expect(changes).toBe(1); - - dispose(); - })); - - test("mapFn is reactive", () => - createRoot(dispose => { - const [list, setList] = createSignal([el1, el2, el3]); - let changes = 0; - const mapped = keyArray( - list, - v => v.id, - v => { - const item = { value: v().value }; - createComputed(() => (item.value = v().value)); - return item; - }, - ); - createComputed(() => mapped(), changes++); - - expect(mapped()).toEqual([{ value: "bread" }, { value: "milk" }, { value: "honey" }]); - expect(changes).toBe(1); - - setList(p => update(p, 0, "value", "bananas")); - expect(mapped()).toEqual([{ value: "bananas" }, { value: "milk" }, { value: "honey" }]); - expect(changes).toBe(1); - - dispose(); - - setList(p => update(p, 1, "value", "orange juice")); - expect(mapped()).toEqual([{ value: "bananas" }, { value: "milk" }, { value: "honey" }]); - expect(changes).toBe(1); - expect(changes).toBe(1); - })); - - test("index is reactive", () => - createRoot(dispose => { - const [list, setList] = createSignal([el1, el2, el3]); - let changes = 0; - let maprun = 0; - const mapped = keyArray( - list, - v => v.id, - (v, i) => { - maprun++; - const item = { i: i(), v: v().value }; - createComputed(() => (item.i = i()), (item.v = v().value)); - return item; - }, - ); - createComputed(() => { - mapped(); - changes++; - }); - - expect(mapped()).toEqual([ - { i: 0, v: "bread" }, - { i: 1, v: "milk" }, - { i: 2, v: "honey" }, - ]); - expect(changes).toBe(1); - expect(maprun).toBe(3); - - setList([el1, el3, el2]); - expect(mapped()).toEqual([ - { i: 0, v: "bread" }, - { i: 1, v: "honey" }, - { i: 2, v: "milk" }, - ]); - expect(changes).toBe(2); - expect(maprun).toBe(3); - - setList([el1, el4, el3, el2]); - expect(mapped()).toEqual([ - { i: 0, v: "bread" }, - { i: 1, v: "chips" }, - { i: 2, v: "honey" }, - { i: 3, v: "milk" }, - ]); - expect(changes).toBe(3); - expect(maprun).toBe(4); - - dispose(); - })); - - test("supports top-level store arrays", () => - createRoot(dispose => { - const [list, setList] = createStore([ - { i: 0, v: "foo" }, - { i: 1, v: "bar" }, - { i: 2, v: "baz" }, - ]); - - const mapped = keyArray( - () => list, - e => e.i, - (item, index) => [item, index] as const, - ); - - const getUnwrapped = (): [number, string, number][] => - mapped().map(([e, index]) => { - const { i, v } = e(); - return [i, v, index()]; - }); - - expect(mapped().length).toBe(3); - expect(getUnwrapped()).toEqual([ - [0, "foo", 0], - [1, "bar", 1], - [2, "baz", 2], - ]); - - const [a0, a1, a2] = mapped(); - - setList([ - { i: 2, v: "foo" }, - { i: 0, v: "bar" }, - { i: 1, v: "baz" }, - ]); - - expect(mapped().length).toBe(3); - expect(getUnwrapped()).toEqual([ - [2, "foo", 0], - [0, "bar", 1], - [1, "baz", 2], - ]); - - const [b0, b1, b2] = mapped(); - expect(a0).toBe(b1); - expect(a1).toBe(b2); - expect(a2).toBe(b0); - - dispose(); - })); - - test("key entries by prop name", () => - createRoot(dispose => { - const entriesFrom: [string, {}][] = [ - ["0", 0], - ["1", 1], - ["2", 2], - ["3", 3], - ]; - const entriesTo: [string, {}][] = [ - ["0", 0], - ["1", 1], - ["2", 2], - ]; - - const [list, setList] = createSignal<[string, {}][]>(entriesFrom); - const mapped = createMemo( - keyArray( - list, - v => v[0], - v => v()[1], - ), - ); - expect(mapped().length).toBe(4); - expect(mapped()).toEqual([0, 1, 2, 3]); - - setList(entriesTo); - expect(mapped().length).toBe(3); - expect(mapped()).toEqual([0, 1, 2]); - - dispose(); - })); -}); diff --git a/packages/keyed/test/index.test.tsx b/packages/keyed/test/index.test.tsx new file mode 100644 index 000000000..012ac2456 --- /dev/null +++ b/packages/keyed/test/index.test.tsx @@ -0,0 +1,443 @@ +import { createComputed, createMemo, createRoot, createSignal } from "solid-js"; +import { createStore } from "solid-js/store"; +import { update } from "@solid-primitives/utils/immutable"; +import { describe, expect, test } from "vitest"; +import { keyArray, MapEntries } from "../src/index.js"; +import { render } from "solid-js/web"; + +const el1 = { id: 1, value: "bread" }; +const el2 = { id: 2, value: "milk" }; +const el3 = { id: 3, value: "honey" }; +const el4 = { id: 4, value: "chips" }; + +describe("keyArray", () => { + test("maps and returns all initial items", () => + createRoot(dispose => { + const mapped = keyArray( + () => [el1, el2, el3], + v => v.id, + v => ({ ...v(), key: v().id }), + ); + expect(mapped().length).toBe(3); + expect(mapped()[0]!.key).toBe(1); + expect(mapped()[1]!.key).toBe(2); + expect(mapped()[2]!.key).toBe(3); + + dispose(); + })); + + test("cloning list should have no effect", () => + createRoot(dispose => { + const [list, setList] = createSignal([el1, el2, el3]); + let changes = 0; + const mapped = keyArray( + list, + v => v.id, + v => v().id, + ); + createComputed(() => mapped(), changes++); + expect(mapped()).toEqual([1, 2, 3]); + expect(changes).toBe(1); + + setList(p => p.slice()); + expect(mapped()).toEqual([1, 2, 3]); + expect(changes).toBe(1); + + dispose(); + })); + + test("mapFn is reactive", () => + createRoot(dispose => { + const [list, setList] = createSignal([el1, el2, el3]); + let changes = 0; + const mapped = keyArray( + list, + v => v.id, + v => { + const item = { value: v().value }; + createComputed(() => (item.value = v().value)); + return item; + }, + ); + createComputed(() => mapped(), changes++); + + expect(mapped()).toEqual([{ value: "bread" }, { value: "milk" }, { value: "honey" }]); + expect(changes).toBe(1); + + setList(p => update(p, 0, "value", "bananas")); + expect(mapped()).toEqual([{ value: "bananas" }, { value: "milk" }, { value: "honey" }]); + expect(changes).toBe(1); + + dispose(); + + setList(p => update(p, 1, "value", "orange juice")); + expect(mapped()).toEqual([{ value: "bananas" }, { value: "milk" }, { value: "honey" }]); + expect(changes).toBe(1); + expect(changes).toBe(1); + })); + + test("index is reactive", () => + createRoot(dispose => { + const [list, setList] = createSignal([el1, el2, el3]); + let changes = 0; + let maprun = 0; + const mapped = keyArray( + list, + v => v.id, + (v, i) => { + maprun++; + const item = { i: i(), v: v().value }; + createComputed(() => (item.i = i()), (item.v = v().value)); + return item; + }, + ); + createComputed(() => { + mapped(); + changes++; + }); + + expect(mapped()).toEqual([ + { i: 0, v: "bread" }, + { i: 1, v: "milk" }, + { i: 2, v: "honey" }, + ]); + expect(changes).toBe(1); + expect(maprun).toBe(3); + + setList([el1, el3, el2]); + expect(mapped()).toEqual([ + { i: 0, v: "bread" }, + { i: 1, v: "honey" }, + { i: 2, v: "milk" }, + ]); + expect(changes).toBe(2); + expect(maprun).toBe(3); + + setList([el1, el4, el3, el2]); + expect(mapped()).toEqual([ + { i: 0, v: "bread" }, + { i: 1, v: "chips" }, + { i: 2, v: "honey" }, + { i: 3, v: "milk" }, + ]); + expect(changes).toBe(3); + expect(maprun).toBe(4); + + dispose(); + })); + + test("supports top-level store arrays", () => + createRoot(dispose => { + const [list, setList] = createStore([ + { i: 0, v: "foo" }, + { i: 1, v: "bar" }, + { i: 2, v: "baz" }, + ]); + + const mapped = keyArray( + () => list, + e => e.i, + (item, index) => [item, index] as const, + ); + + const getUnwrapped = (): [number, string, number][] => + mapped().map(([e, index]) => { + const { i, v } = e(); + return [i, v, index()]; + }); + + expect(mapped().length).toBe(3); + expect(getUnwrapped()).toEqual([ + [0, "foo", 0], + [1, "bar", 1], + [2, "baz", 2], + ]); + + const [a0, a1, a2] = mapped(); + + setList([ + { i: 2, v: "foo" }, + { i: 0, v: "bar" }, + { i: 1, v: "baz" }, + ]); + + expect(mapped().length).toBe(3); + expect(getUnwrapped()).toEqual([ + [2, "foo", 0], + [0, "bar", 1], + [1, "baz", 2], + ]); + + const [b0, b1, b2] = mapped(); + expect(a0).toBe(b1); + expect(a1).toBe(b2); + expect(a2).toBe(b0); + + dispose(); + })); + + test("key entries by prop name", () => + createRoot(dispose => { + const entriesFrom: [string, {}][] = [ + ["0", 0], + ["1", 1], + ["2", 2], + ["3", 3], + ]; + const entriesTo: [string, {}][] = [ + ["0", 0], + ["1", 1], + ["2", 2], + ]; + + const [list, setList] = createSignal<[string, {}][]>(entriesFrom); + const mapped = createMemo( + keyArray( + list, + v => v[0], + v => v()[1], + ), + ); + expect(mapped().length).toBe(4); + expect(mapped()).toEqual([0, 1, 2, 3]); + + setList(entriesTo); + expect(mapped().length).toBe(3); + expect(mapped()).toEqual([0, 1, 2]); + + dispose(); + })); +}); + +describe("MapEntries", () => { + test("simple", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const startingMap = new Map([ + [1, "1"], + [2, "2"], + [3, "3"], + ]); + const [s] = createSignal(startingMap); + const unmount = render( + () => ( + + {(k, v, i) => ( +
+ {i()}. {k}: {v()} +
+ )} +
+ ), + container, + ); + + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + }); + + unmount(); + document.body.removeChild(container); + }); + + test("doesn't change for same values", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const startingMap = new Map([ + [1, "1"], + [2, "2"], + [3, "3"], + ]); + const [s, set] = createSignal(startingMap); + const unmount = render( + () => ( + + {(k, v, i) => ( +
+ {i()}. {k}: {v()} +
+ )} +
+ ), + container, + ); + + const oldMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + oldMapped[k] = v; + }); + + set(new Map(startingMap)); + + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + expect(oldMapped[k]).toBe(v); + }); + + unmount(); + document.body.removeChild(container); + }); + + test("changes value of elements", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const startingMap = new Map([ + [1, "1"], + [2, "2"], + [3, "3"], + ]); + const [s, set] = createSignal(startingMap); + const unmount = render( + () => ( + + {(k, v, i) => ( +
+ {i()}. {k}: {v()} +
+ )} +
+ ), + container, + ); + + const oldMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + oldMapped[k] = v; + }); + + const nextMap = new Map([ + [1, "1"], + [2, "2?!"], + [3, "3"], + ]); + set(nextMap); + + container.childNodes.forEach((v, i) => { + const k = Array.from(nextMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${nextMap.get(k)}`); + expect(oldMapped[k]).toBe(v); + }); + + unmount(); + document.body.removeChild(container); + }); + + test("creates new elements", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const startingMap = new Map([ + [1, "1"], + [2, "2"], + [3, "3"], + ]); + const [s, set] = createSignal(startingMap); + const unmount = render( + () => ( + + {(k, v, i) => ( +
+ {i()}. {k}: {v()} +
+ )} +
+ ), + container, + ); + + const oldMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + oldMapped[k] = v; + }); + + const nextMap = new Map([ + [1, "1"], + [2, "2"], + [3, "3"], + [4, "4"], + [5, "5"], + ]); + set(nextMap); + + const newMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(nextMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${nextMap.get(k)}`); + newMapped[k] = v; + }); + + expect(oldMapped[0]).toBe(newMapped[0]); + expect(oldMapped[1]).toBe(newMapped[1]); + expect(oldMapped[2]).toBe(newMapped[2]); + expect(oldMapped[3]).toBe(newMapped[3]); + expect(oldMapped.includes(newMapped[4]!)).toEqual(false); + expect(oldMapped.includes(newMapped[5]!)).toEqual(false); + + unmount(); + document.body.removeChild(container); + }); + + test("deletes unused elements", () => { + const container = document.createElement("div"); + document.body.appendChild(container); + + const startingMap = new Map([ + [0, "0"], + [1, "1"], + [2, "2"], + [3, "3"], + ]); + const [s, set] = createSignal(startingMap); + const unmount = render( + () => ( + + {(k, v, i) => ( +
+ {i()}. {k}: {v()} +
+ )} +
+ ), + container, + ); + + const oldMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(startingMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${startingMap.get(k)}`); + oldMapped[k] = v; + }); + + const nextMap = new Map([ + [0, "0"], + [3, "3"], + ]); + set(nextMap); + + const newMapped: ChildNode[] = new Array(container.childNodes.length); + container.childNodes.forEach((v, i) => { + const k = Array.from(nextMap.keys())[i]!; + expect(v.textContent).toEqual(`${i}. ${k}: ${nextMap.get(k)}`); + newMapped[k] = v; + }); + + expect(oldMapped[0]).toBe(newMapped[0]); + expect(oldMapped[3]).toBe(newMapped[3]); + expect(newMapped.includes(oldMapped[1]!)).toEqual(false); + expect(newMapped.includes(oldMapped[2]!)).toEqual(false); + + unmount(); + document.body.removeChild(container); + }); +}); From 1d13567227716cd09bea0710b6aff24f50cbbae8 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 2 Jan 2025 18:34:28 -0600 Subject: [PATCH 10/13] store => signal --- packages/keyed/dev/mapEntries.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/keyed/dev/mapEntries.tsx b/packages/keyed/dev/mapEntries.tsx index 7ec5b25ef..341488b7b 100644 --- a/packages/keyed/dev/mapEntries.tsx +++ b/packages/keyed/dev/mapEntries.tsx @@ -1,7 +1,6 @@ // changes to this file might be applicable to similar files - grep 95DB7339-BB2A-4F06-A34A-25DDF8BF7AF7 -import { createStore } from "solid-js/store"; -import { createEffect } from "solid-js"; +import { createEffect, createSignal } from "solid-js"; import { MapEntries } from "../src/index.js"; import { TransitionGroup } from "solid-transition-group"; @@ -40,30 +39,30 @@ const randomKey = (map: Map): string => { }; export default function App() { - const [store, setStore] = createStore<{ entries: Map }>({ - entries: new Map([ + const [map, setMap] = createSignal( + new Map([ [Math.random().toString(), "bread"], [Math.random().toString(), "milk"], [Math.random().toString(), "honey"], [Math.random().toString(), "chips"], [Math.random().toString(), "cookie"], ]), - }); + ); const addRandom = () => { - setStore("entries", p => { + setMap(p => { p.set(Math.random().toString(), getRandomFood()); return new Map(p); }); }; const removeRandom = () => - setStore("entries", p => { + setMap(p => { p.delete(randomKey(p)); return new Map(p); }); - const clone = () => setStore("entries", p => new Map(p)); + const clone = () => setMap(p => new Map(p)); const changeRandomValue = () => - setStore("entries", p => { + setMap(p => { p.set(randomKey(p), getRandomFood()); return new Map(p); }); @@ -87,7 +86,7 @@ export default function App() {
No items.

} > {(key, value, index) => { From 3f1cf042152a4eb402549ab2fc1f7f6c65ea4efd Mon Sep 17 00:00:00 2001 From: Alex Errant <109672176+AlexErrant@users.noreply.github.com> Date: Sat, 4 Jan 2025 09:22:01 -0600 Subject: [PATCH 11/13] Apply suggestions from code review better docs Co-authored-by: Damian Tarnawski --- packages/keyed/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/keyed/README.md b/packages/keyed/README.md index 293135969..a08cd82ac 100644 --- a/packages/keyed/README.md +++ b/packages/keyed/README.md @@ -162,6 +162,8 @@ Creates a list of elements by mapping Map entries. Similar to Solid's `` an ```tsx import { MapEntries } from "@solid-primitives/keyed"; +const [map, setMap] = createSignal(new Map()); + No items
}> {(key, value) => (
@@ -175,6 +177,8 @@ import { MapEntries } from "@solid-primitives/keyed"; Third argument of the map function is an index signal. +`MapEntries` is using [`Map#key()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) so the index and resulting JSX will follow the insertion order. + ```tsx No items
}> {(key, value, index) => ( From 973ee01047aca4cf8d8c4fe47233c326397d876e Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sat, 4 Jan 2025 17:09:01 +0100 Subject: [PATCH 12/13] Add MapEntries to package.json --- packages/keyed/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/keyed/package.json b/packages/keyed/package.json index a6c5f1969..abff5807a 100644 --- a/packages/keyed/package.json +++ b/packages/keyed/package.json @@ -15,7 +15,8 @@ "list": [ "keyArray", "Key", - "Entries" + "Entries", + "MapEntries" ], "category": "Control Flow" }, From 533c8218390684194d8e0b1ce5df63e4575c0669 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jan 2025 11:13:02 -0600 Subject: [PATCH 13/13] simplify --- packages/keyed/dev/entries.tsx | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/packages/keyed/dev/entries.tsx b/packages/keyed/dev/entries.tsx index 5cccaaeaf..48b1c7e9f 100644 --- a/packages/keyed/dev/entries.tsx +++ b/packages/keyed/dev/entries.tsx @@ -40,28 +40,20 @@ const randomKey = (record: Record): string => { }; export default function App() { - const [store, setStore] = createStore<{ entries: Record }>({ - entries: { - [Math.random()]: "bread", - [Math.random()]: "milk", - [Math.random()]: "honey", - [Math.random()]: "chips", - [Math.random()]: "cookie", - }, + const [store, setStore] = createStore>({ + [Math.random()]: "bread", + [Math.random()]: "milk", + [Math.random()]: "honey", + [Math.random()]: "chips", + [Math.random()]: "cookie", }); const addRandom = () => { - setStore("entries", Math.random().toString(), getRandomFood()); + setStore(Math.random().toString(), getRandomFood()); }; - const removeRandom = () => - setStore( - "entries", - produce(p => { - delete p[randomKey(p)]; - }), - ); + const removeRandom = () => setStore(p => ({ [randomKey(p)]: undefined })); const clone = () => setStore("entries", p => JSON.parse(JSON.stringify(p))); - const changeRandomValue = () => setStore("entries", p => ({ [randomKey(p)]: getRandomFood() })); + const changeRandomValue = () => setStore(p => ({ [randomKey(p)]: getRandomFood() })); return ( <> @@ -81,10 +73,7 @@ export default function App() {
- No items.

} - > + No items.

}> {(key, value, index) => { createEffect(() => { console.log("Effect:", key, value());