From 8cc7081d626961116a1b7122635671031ef602eb Mon Sep 17 00:00:00 2001 From: Michael Shafir Date: Tue, 25 Feb 2025 11:40:53 -0500 Subject: [PATCH 1/3] fix to tunnel ordering when items are deleted --- .../src/pages/radix-inputs/index.tsx | 55 +++++++++++-------- apps/reactlit-examples/src/styles/globals.css | 2 +- libs/core/src/inputs/layout.view.tsx | 8 ++- libs/core/src/reactlit.tsx | 6 +- libs/core/src/utils/tunnel.tsx | 34 +++++++----- libs/core/src/utils/unique-by.ts | 5 ++ libs/radix/src/inputs/switch.input.tsx | 5 +- 7 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 libs/core/src/utils/unique-by.ts diff --git a/apps/reactlit-examples/src/pages/radix-inputs/index.tsx b/apps/reactlit-examples/src/pages/radix-inputs/index.tsx index 7a37cbc..2778412 100644 --- a/apps/reactlit-examples/src/pages/radix-inputs/index.tsx +++ b/apps/reactlit-examples/src/pages/radix-inputs/index.tsx @@ -1,4 +1,4 @@ -import { useDebug } from '@/components/debug-toggle'; +import { Debug, useDebug } from '@/components/debug-toggle'; import { Badge, ChevronDownIcon, @@ -60,14 +60,16 @@ const ResultsWrapper: Wrapper = ({ children }) => { const [open, setOpen] = useState(true); return (
setOpen(!open)} >

{open ? : } Results

- {children} +
+ {children} +
); }; @@ -84,6 +86,7 @@ export default function RadixInputs() { color: 'red', slider: 0, rangeSlider: [20, 80], + enableLetter: true, }); const debug = useDebug(); return ( @@ -92,6 +95,9 @@ export default function RadixInputs() { {async ({ display, view }) => { display(
Inputs test
); const [results] = view('results', ResultsWrapper, LayoutView(1)); + const displayResult = (label: string, value: React.ReactNode) => { + results.display(Debug, DisplayLabel(label), value); + }; const name = view( 'name', Label('Name'), @@ -99,7 +105,7 @@ export default function RadixInputs() { placeholder: 'Enter your name', }) ); - results.display(DisplayLabel('Name'), name); + displayResult('Name', name); const bio = view( 'bio', Label('Bio'), @@ -107,29 +113,35 @@ export default function RadixInputs() { placeholder: 'Enter your bio', }) ); - results.display(DisplayLabel('Bio'), bio); + displayResult('Bio', bio); const number = view( 'number', Label('Pick any numbers'), Inputs.Check({ one: '1', two: '2', three: '3' }) ); - results.display(DisplayLabel('Numbers'), number); - const letter = view( - 'letter', - Label('Pick one Letter'), - Inputs.Radio(['A', 'B', 'C']) - ); - results.display(DisplayLabel('Letter'), letter); + displayResult('Numbers', number); + if ( + view( + 'enableLetter', + Label('Letter Selection'), +
, + Inputs.Switch() + ) + ) { + const letter = view( + 'letter', + Label('Pick one Letter'), + Inputs.Radio(['A', 'B', 'C']) + ); + displayResult('Letter', letter); + } const color = view( 'color', Label('Pick a color'),
, Inputs.Select(['red', 'blue', 'green'] as const) ); - results.display( - DisplayLabel('Color'), - {color} - ); + displayResult('Color', {color}); const slider = view( 'slider', Label('Slider'), @@ -138,7 +150,7 @@ export default function RadixInputs() { max: 100, }) ); - results.display(DisplayLabel('Slider'), slider); + displayResult('Slider', slider); const rangeSlider = view( 'rangeSlider', Label('Range Slider'), @@ -147,10 +159,7 @@ export default function RadixInputs() { max: 100, }) ); - results.display( - DisplayLabel('Range Slider'), - rangeSlider.join(' - ') - ); + displayResult('Range Slider', rangeSlider.join(' - ')); const countries = await fetchCountries(); display(
Select a country
); const filteredCountries = view( @@ -168,8 +177,8 @@ export default function RadixInputs() { className: 'h-[300px]', }) ); - results.display( - DisplayLabel('Country'), + displayResult( + 'Country', <> {selectedCountry?.code ? ( ( ctx: Pick, 'display' | 'view'>, t: ReturnType ): LayoutSlot { - const TunnelWrapper: Wrapper = ({ stateKey, children }) => { - return {children}; + const TunnelWrapper: Wrapper = ({ stateKey, position, children }) => { + return ( + + {children} + + ); }; return { display(...args) { diff --git a/libs/core/src/reactlit.tsx b/libs/core/src/reactlit.tsx index b09209e..08c0ff1 100644 --- a/libs/core/src/reactlit.tsx +++ b/libs/core/src/reactlit.tsx @@ -14,6 +14,8 @@ import { useReactlitSet } from './builtins/set'; import { ReactlitProps, StateBase } from './builtins/types'; import { useReactlitView } from './builtins/view'; import { useReactlitState } from './hooks/use-reactlit-state'; +import { useIsomorphicLayoutEffect } from './utils/use-isomorphic-layout-effect'; +import { uniqueBy } from './utils/unique-by'; const defaultRenderError = ({ error }) => (
@@ -55,7 +57,7 @@ export function Reactlit({ const renderLock = useRef(false); const renderAfter = useRef(false); - useEffect(() => { + useIsomorphicLayoutEffect(() => { async function runScript() { setRendering(true); if (renderLock.current) { @@ -103,7 +105,7 @@ export function Reactlit({ return ( <> - {renderState.elements.map(([key, node]) => ( + {uniqueBy(renderState.elements, '0').map(([key, node]) => ( {node} ))} {renderLoading?.(rendering) ?? <>} diff --git a/libs/core/src/utils/tunnel.tsx b/libs/core/src/utils/tunnel.tsx index 3be4fda..8c1a603 100644 --- a/libs/core/src/utils/tunnel.tsx +++ b/libs/core/src/utils/tunnel.tsx @@ -1,26 +1,31 @@ import React, { Fragment } from 'react'; import { create, StoreApi } from 'zustand'; import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'; +import { uniqueBy } from './unique-by'; // modified from tunnel-rat -type Props = { childKey: string; children: React.ReactNode }; +type Props = { childKey: string; order: number; children: React.ReactNode }; type State = { - current: Array<{ childKey: string; node: React.ReactNode }>; + current: Array; version: number; set: StoreApi['setState']; }; +function sortByOrder(array: Props[]) { + return array.sort((a, b) => a.order - b.order); +} + export default function tunnel() { const useStore = create((set) => ({ - current: new Array<{ childKey: string; node: React.ReactNode }>(), + current: new Array(), version: 0, set, })); return { - In: ({ childKey, children }: Props) => { + In: ({ childKey, order, children }: Props) => { const set = useStore((state) => state.set); const version = useStore((state) => state.version); @@ -40,22 +45,25 @@ export default function tunnel() { set(({ current }) => { const existing = current.findIndex((c) => c.childKey === childKey); return { - current: + current: sortByOrder( existing !== -1 ? [ ...current.slice(0, existing), - { childKey, node: children }, + { childKey, order, children }, ...current.slice(existing + 1), ] - : [...current, { childKey, node: children }], + : [...current, { childKey, order, children }] + ), }; }); // remove the cleanup logic so that nodes stay in position, the key logic keeps things from getting too messy - // return () => - // set(({ current }) => ({ - // current: current.filter((c) => c.node !== children), - // })); + return () => + set(({ current }) => { + return { + current: current.filter((c) => c.childKey !== childKey), + }; + }); }, [children, version]); return null; @@ -65,8 +73,8 @@ export default function tunnel() { const current = useStore((state) => state.current); return ( <> - {current.map((c) => ( - {c.node} + {uniqueBy(current, 'childKey').map((c) => ( + {c.children} ))} ); diff --git a/libs/core/src/utils/unique-by.ts b/libs/core/src/utils/unique-by.ts new file mode 100644 index 0000000..f3ce6e2 --- /dev/null +++ b/libs/core/src/utils/unique-by.ts @@ -0,0 +1,5 @@ +export function uniqueBy(array: T[], key: keyof T) { + return array.filter( + (item, index, self) => index === self.findIndex((t) => t[key] === item[key]) + ); +} diff --git a/libs/radix/src/inputs/switch.input.tsx b/libs/radix/src/inputs/switch.input.tsx index b692135..e24e0a2 100644 --- a/libs/radix/src/inputs/switch.input.tsx +++ b/libs/radix/src/inputs/switch.input.tsx @@ -5,15 +5,12 @@ import { LabelType } from '../label'; export type SwitchInputProps = Omit< SwitchProps, 'checked' | 'onCheckedChange' | 'value' | 'onValueChange' -> & { - label?: LabelType; -}; +>; export const SwitchInputComponent = ({ value, stateKey, setValue, - label, display, view, ...props From 0989641be250c7e803b9b273bb78c74f0e066276 Mon Sep 17 00:00:00 2001 From: Michael Shafir Date: Tue, 25 Feb 2025 11:41:26 -0500 Subject: [PATCH 2/3] docs(changeset): fix to tunnel ordering --- .changeset/perfect-eagles-add.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/perfect-eagles-add.md diff --git a/.changeset/perfect-eagles-add.md b/.changeset/perfect-eagles-add.md new file mode 100644 index 0000000..76ec7df --- /dev/null +++ b/.changeset/perfect-eagles-add.md @@ -0,0 +1,7 @@ +--- +'@reactlit/radix': patch +'@reactlit/core': patch +'@reactlit/vanilla': patch +--- + +fix to tunnel ordering From ee11f9e3df69b10e6c9cd790a4482b72c96f821c Mon Sep 17 00:00:00 2001 From: Michael Shafir Date: Tue, 25 Feb 2025 12:09:50 -0500 Subject: [PATCH 3/3] fix build errors --- .../src/pages/hello-world/index.tsx | 4 ++-- .../src/pages/todo-list/index.tsx | 14 ++++---------- libs/radix/src/inputs/search.input.tsx | 1 - libs/radix/src/inputs/text.input.tsx | 2 -- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/reactlit-examples/src/pages/hello-world/index.tsx b/apps/reactlit-examples/src/pages/hello-world/index.tsx index d148c46..2fa51d1 100644 --- a/apps/reactlit-examples/src/pages/hello-world/index.tsx +++ b/apps/reactlit-examples/src/pages/hello-world/index.tsx @@ -1,5 +1,5 @@ import { Reactlit, useReactlitState } from '@reactlit/core'; -import { Inputs } from '@reactlit/radix'; +import { Inputs, Label } from '@reactlit/radix'; export default function HelloWorld() { const [appState, setAppState] = useReactlitState({ @@ -11,8 +11,8 @@ export default function HelloWorld() { display(
Hello World
); const name = view( 'name', + Label('Name'), Inputs.Text({ - label: 'Name', placeholder: 'Enter your name', }) ); diff --git a/apps/reactlit-examples/src/pages/todo-list/index.tsx b/apps/reactlit-examples/src/pages/todo-list/index.tsx index 0744108..de8d016 100644 --- a/apps/reactlit-examples/src/pages/todo-list/index.tsx +++ b/apps/reactlit-examples/src/pages/todo-list/index.tsx @@ -1,6 +1,6 @@ import { Button, Callout, Spinner } from '@radix-ui/themes'; import { DataFetchingPlugin, useReactlit } from '@reactlit/core'; -import { DefaultRadixWrapper, Inputs } from '@reactlit/radix'; +import { DefaultRadixWrapper, Inputs, Label } from '@reactlit/radix'; import { InfoIcon } from 'lucide-react'; import { TodoService } from '../../mocks/todos'; @@ -67,17 +67,11 @@ export default function TodoList() { set('task', selectedTodo.task); set('completed', selectedTodo.completed); } - const task = view( - 'task', - Inputs.Text({ - label: 'Task', - }) - ); + const task = view('task', Label('Task'), Inputs.Text()); const completed = view( 'completed', - Inputs.Switch({ - label: 'Completed', - }) + Label('Completed'), + Inputs.Switch() ); view( 'updaing', diff --git a/libs/radix/src/inputs/search.input.tsx b/libs/radix/src/inputs/search.input.tsx index 3dd4d1c..dead533 100644 --- a/libs/radix/src/inputs/search.input.tsx +++ b/libs/radix/src/inputs/search.input.tsx @@ -37,7 +37,6 @@ export function SearchInput( (viewProps) => ( & { - label?: LabelType; children?: | React.ReactNode | ((props: ViewComponentProps) => React.ReactNode); @@ -17,7 +16,6 @@ export const TextInputComponent = ({ stateKey, setValue, onChange, - label, debounceDelay = 200, children, display,