From 8f982034c23162c69034d8a15eca9a12c503fefd Mon Sep 17 00:00:00 2001 From: dangreen Date: Thu, 20 Aug 2026 20:04:16 +0400 Subject: [PATCH] feat(nanoviews): add `as_` to hand a row through a transform A row that needs `record` has to be a block: a `const` for the transformed row, a blank line, a `return`, and the braces around them. `as_(record, $player => li()($player.$name))` makes it an expression again. It is not about `record` - the transform is any function of the row, and the row types are left unconstrained so one signature serves the readable, writable and static forms. The transformed row stays the row: a write through it still reaches the items array. --- packages/nanoviews/.size-limit.json | 2 +- packages/nanoviews/src/flow/for.spec.ts | 66 +++++++++++++++++++++++++ packages/nanoviews/src/flow/for.ts | 14 ++++++ todo.txt | 9 ---- 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/packages/nanoviews/.size-limit.json b/packages/nanoviews/.size-limit.json index 2275901e..1f5e310c 100644 --- a/packages/nanoviews/.size-limit.json +++ b/packages/nanoviews/.size-limit.json @@ -10,7 +10,7 @@ "name": "All publics (Brotli)", "path": "dist/index.js", "import": "*", - "limit": "6.6 kB" + "limit": "6.65 kB" }, { "name": "Average usage (Gzip)", diff --git a/packages/nanoviews/src/flow/for.spec.ts b/packages/nanoviews/src/flow/for.spec.ts index bb1a9d6e..ee63fb5d 100644 --- a/packages/nanoviews/src/flow/for.spec.ts +++ b/packages/nanoviews/src/flow/for.spec.ts @@ -25,6 +25,7 @@ import { import { fragment } from '../elements/fragment.js' import { trackById, + as_, for_ } from './for.js' import * as Stories from './for.stories.js' @@ -1032,6 +1033,71 @@ describe('nanoviews', () => { expect(container.innerHTML).toBe('
') }) + it('should hand the row through a transform with `as_`', () => { + const items = signal([ + { + id: 1, + name: 'Yatoro' + }, + { + id: 2, + name: 'Larl' + } + ]) + const { container } = render(() => ul()( + for_(items, trackById)( + as_(record, ($player, $index) => li()($player.$name, ':', $index)) + ) + )) + + expect(container.innerHTML).toBe('
') + + // the transformed row is still the row: a write reaches the array + untracked(items)[0].name = 'x' + items([ + { + id: 1, + name: 'Collapse' + }, + { + id: 2, + name: 'Larl' + } + ]) + + expect(container.innerHTML).toBe('
') + }) + + it('should keep a row written through `as_` writable', () => { + const items = signal([ + { + id: 1, + name: ' Larl ' + } + ]) + + render(() => ul()( + for_(items, trackById)( + as_(record, ($player) => { + const name = untracked($player.$name) + + if (name !== name.trim()) { + $player.$name(name.trim()) + } + + return li()($player.$name) + }) + ) + )) + + expect(untracked(items)).toEqual([ + { + id: 1, + name: 'Larl' + } + ]) + }) + describe('fuzz', () => { // A named test pins a shape someone thought of; these walk sequences // nobody did. Both invariants are checked after every step: the rows diff --git a/packages/nanoviews/src/flow/for.ts b/packages/nanoviews/src/flow/for.ts index 168301fb..8474f2d6 100644 --- a/packages/nanoviews/src/flow/for.ts +++ b/packages/nanoviews/src/flow/for.ts @@ -30,6 +30,20 @@ export function trackById(item: { id: unknown }) { return item.id } +/** + * Hand the row through a transform before rendering it + * @param as - Function that transforms the row, eg `record` + * @param each_ - Function that renders the transformed row + * @returns Function to pass to `for_` + */ +/* @__NO_SIDE_EFFECTS__ */ +export function as_( + as: (item: Item) => Value, + each_: (value: Value, index: Index) => Child +) { + return (item: Item, index: Index) => each_(as(item), index) +} + type AnyEach = ( item: Accessor, index: ReadableSignal diff --git a/todo.txt b/todo.txt index 31e9e3cc..b3b46b8f 100644 --- a/todo.txt +++ b/todo.txt @@ -47,17 +47,8 @@ - for_ duplicate track keys: unsupported by design, must keep failing loudly - the crash lands a step away from the cause, so it needs a clear throw at the point of detection - rework return slot$ to look like element/component? fn.prop is faster than {f,p} - util to transform static props to signal props? -- additional arg for record in for$? as$ - static index for untracked loop? -for_($items)( - as_(record, ($item) => li()($item.$name)) -) - -for_($items)( - asRecord(($item) => li()($item.$name)) -) - ## Perf - !== undefined is faster