diff --git a/.changeset/readonly-for-inputs.md b/.changeset/readonly-for-inputs.md new file mode 100644 index 000000000..79c452461 --- /dev/null +++ b/.changeset/readonly-for-inputs.md @@ -0,0 +1,6 @@ +--- +"@preact/signals": patch +"@preact/signals-react": patch +--- + +Allow `` to accept readonly arrays and signals containing readonly arrays. diff --git a/packages/preact/utils/src/index.tsx b/packages/preact/utils/src/index.tsx index 48349dfab..27dafe4ff 100644 --- a/packages/preact/utils/src/index.tsx +++ b/packages/preact/utils/src/index.tsx @@ -28,11 +28,13 @@ export function Show( Show.displayName = "Show"; +type ForEach = + | ReadonlyArray + | Signal> + | ReadonlySignal>; + interface ForProps { - each: - | Signal> - | ReadonlySignal> - | (() => Array | Signal> | ReadonlySignal>); + each: ForEach | (() => ForEach); fallback?: ComponentChildren; getKey?: (item: T, index: number) => string | number; children: (value: T, index: number) => ComponentChildren; @@ -41,8 +43,8 @@ interface ForProps { export function For(props: ForProps): ComponentChildren | null { const cache = useMemo(() => new Map(), []); const list = (typeof props.each === "function" ? props.each() : props.each) as - | Signal> - | Array; + | Signal> + | ReadonlyArray; const listValue = list instanceof Signal ? list.value : list; diff --git a/packages/preact/utils/test/browser/index.test.tsx b/packages/preact/utils/test/browser/index.test.tsx index 30e8799ae..f7f5edc13 100644 --- a/packages/preact/utils/test/browser/index.test.tsx +++ b/packages/preact/utils/test/browser/index.test.tsx @@ -164,6 +164,30 @@ describe("@preact/signals-utils", () => { ); }); + it("Should accept readonly list types", () => { + const plain: readonly string[] = ["foo", "bar"]; + const list = signal(["baz", "qux"]); + const computedList = computed((): readonly string[] => list.value); + const Paragraph = (p: any) =>

{p.children}

; + + act(() => { + render( +
+ {item => {item}} + {item => {item}} + computedList}> + {item => {item}} + +
, + scratch + ); + }); + + expect(scratch.innerHTML).to.eq( + "

foo

bar

baz

qux

baz

qux

" + ); + }); + it("Should pass updated indexes to reused items after removal", () => { const alice = { id: "a", label: "Alice" }; const bob = { id: "b", label: "Bob" }; diff --git a/packages/react/utils/src/index.tsx b/packages/react/utils/src/index.tsx index 4dbabee79..eb99ad5d0 100644 --- a/packages/react/utils/src/index.tsx +++ b/packages/react/utils/src/index.tsx @@ -35,11 +35,13 @@ export function Show(props: ShowProps): JSX.Element | null { Show.displayName = "Show"; +type ForEach = + | ReadonlyArray + | Signal> + | ReadonlySignal>; + interface ForProps { - each: - | Signal> - | ReadonlySignal> - | (() => Array | Signal> | ReadonlySignal>); + each: ForEach | (() => ForEach); fallback?: ReactNode; getKey?: (item: T, index: number) => string | number; children: (value: T, index: number) => ReactNode; @@ -49,8 +51,8 @@ export function For(props: ForProps): JSX.Element | null { useSignals(); const cache = useMemo(() => new Map(), []); const list = (typeof props.each === "function" ? props.each() : props.each) as - | Signal> - | Array; + | Signal> + | ReadonlyArray; const listValue = list instanceof Signal ? list.value : list; diff --git a/packages/react/utils/test/browser/index.test.tsx b/packages/react/utils/test/browser/index.test.tsx index 28323e36d..df6a25582 100644 --- a/packages/react/utils/test/browser/index.test.tsx +++ b/packages/react/utils/test/browser/index.test.tsx @@ -197,6 +197,29 @@ describe("@preact/signals-react-utils", () => { ); }); + it("Should accept readonly list types", async () => { + const plain: readonly string[] = ["foo", "bar"]; + const list = signal(["baz", "qux"]); + const computedList = computed((): readonly string[] => list.value); + const Paragraph = (p: any) =>

{p.children}

; + + await act(() => { + render( +
+ {item => {item}} + {item => {item}} + computedList}> + {item => {item}} + +
+ ); + }); + + expect(scratch.innerHTML).to.eq( + "

foo

bar

baz

qux

baz

qux

" + ); + }); + it("Should pass updated indexes to reused items after removal", async () => { const alice = { id: "a", label: "Alice" }; const bob = { id: "b", label: "Bob" };