From 1961baed48376da6f8724bcac4cc9949be508eb6 Mon Sep 17 00:00:00 2001 From: Aleksei Gurianov Date: Sat, 3 Oct 2026 15:20:51 +0000 Subject: [PATCH] refactor(reatom): idiomatic withResizeObserver exposing plain Size state Expose { width, height } plain data instead of the live ResizeObserverEntry platform object, so state stays serializable and consumers read a stable shape. Rename the derived atom sizeEntry -> size and simplify the entry lookup (single observed node). Update the demo app-shell and the create-karkas template copy, plus a patch changeset. --- .../reatom-idiomatic-resize-observer.md | 5 ++++ .../demo/src/shared/reatom/resize-observer.ts | 26 +++++++++++++------ .../src/widgets/app-shell/ui/AppShell.tsx | 2 +- .../src/shared/reatom/resize-observer.ts | 26 +++++++++++++------ .../src/widgets/app-shell/ui/AppShell.tsx | 2 +- 5 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 .changeset/reatom-idiomatic-resize-observer.md diff --git a/.changeset/reatom-idiomatic-resize-observer.md b/.changeset/reatom-idiomatic-resize-observer.md new file mode 100644 index 00000000..4e2fcb1d --- /dev/null +++ b/.changeset/reatom-idiomatic-resize-observer.md @@ -0,0 +1,5 @@ +--- +"create-karkas": patch +--- + +Make `withResizeObserver` idiomatic Reatom: expose plain `Size` state (`{ width, height }`) instead of the live `ResizeObserverEntry` platform object, keep state serializable, and rename the derived atom from `sizeEntry` to `size`. diff --git a/apps/demo/src/shared/reatom/resize-observer.ts b/apps/demo/src/shared/reatom/resize-observer.ts index 63fc593b..9603f92c 100644 --- a/apps/demo/src/shared/reatom/resize-observer.ts +++ b/apps/demo/src/shared/reatom/resize-observer.ts @@ -1,17 +1,27 @@ import { computed, memo, reatomObservable, wrap, type Atom } from '@reatom/core' +export type Size = { width: number; height: number } + export const withResizeObserver = () => { return (target: Atom) => { - const sizeEntry = computed(() => { - const stateAtom = memo(() => { + const size = computed(() => { + const sizeAtom = memo(() => { const node = target() if (!node) return null - return reatomObservable({ - initState: undefined, + return reatomObservable({ + initState: null, subscribe: (set) => { const observer = new ResizeObserver( - wrap((entries) => set(entries.find((entry) => entry.target === node))), + wrap((entries) => { + const entry = entries.at(-1) + if (entry) { + set({ + width: entry.contentRect.width, + height: entry.contentRect.height, + }) + } + }), ) observer.observe(node) return () => observer.disconnect() @@ -19,9 +29,9 @@ export const withResizeObserver = () => { }) }) - return stateAtom?.() - }, `${target.name}.sizeEntry`) + return sizeAtom() + }, `${target.name}.size`) - return { sizeEntry } + return { size } } } diff --git a/apps/demo/src/widgets/app-shell/ui/AppShell.tsx b/apps/demo/src/widgets/app-shell/ui/AppShell.tsx index d3b69b01..17ac4510 100644 --- a/apps/demo/src/widgets/app-shell/ui/AppShell.tsx +++ b/apps/demo/src/widgets/app-shell/ui/AppShell.tsx @@ -35,7 +35,7 @@ const desktopSidebarCollapsedAtom = reatomBoolean(false, 'desktopSidebar.collaps const measureRefAtom = atom(null, 'appShell.measureRef').extend( withResizeObserver(), (target) => ({ - height: computed(() => target.sizeEntry()?.contentRect.height ?? 0, 'appShell.headerHeight'), + height: computed(() => target.size()?.height ?? 0, 'appShell.headerHeight'), }), ) diff --git a/packages/create-karkas/template/src/shared/reatom/resize-observer.ts b/packages/create-karkas/template/src/shared/reatom/resize-observer.ts index 63fc593b..9603f92c 100644 --- a/packages/create-karkas/template/src/shared/reatom/resize-observer.ts +++ b/packages/create-karkas/template/src/shared/reatom/resize-observer.ts @@ -1,17 +1,27 @@ import { computed, memo, reatomObservable, wrap, type Atom } from '@reatom/core' +export type Size = { width: number; height: number } + export const withResizeObserver = () => { return (target: Atom) => { - const sizeEntry = computed(() => { - const stateAtom = memo(() => { + const size = computed(() => { + const sizeAtom = memo(() => { const node = target() if (!node) return null - return reatomObservable({ - initState: undefined, + return reatomObservable({ + initState: null, subscribe: (set) => { const observer = new ResizeObserver( - wrap((entries) => set(entries.find((entry) => entry.target === node))), + wrap((entries) => { + const entry = entries.at(-1) + if (entry) { + set({ + width: entry.contentRect.width, + height: entry.contentRect.height, + }) + } + }), ) observer.observe(node) return () => observer.disconnect() @@ -19,9 +29,9 @@ export const withResizeObserver = () => { }) }) - return stateAtom?.() - }, `${target.name}.sizeEntry`) + return sizeAtom() + }, `${target.name}.size`) - return { sizeEntry } + return { size } } } diff --git a/packages/create-karkas/template/src/widgets/app-shell/ui/AppShell.tsx b/packages/create-karkas/template/src/widgets/app-shell/ui/AppShell.tsx index d3b69b01..17ac4510 100644 --- a/packages/create-karkas/template/src/widgets/app-shell/ui/AppShell.tsx +++ b/packages/create-karkas/template/src/widgets/app-shell/ui/AppShell.tsx @@ -35,7 +35,7 @@ const desktopSidebarCollapsedAtom = reatomBoolean(false, 'desktopSidebar.collaps const measureRefAtom = atom(null, 'appShell.measureRef').extend( withResizeObserver(), (target) => ({ - height: computed(() => target.sizeEntry()?.contentRect.height ?? 0, 'appShell.headerHeight'), + height: computed(() => target.size()?.height ?? 0, 'appShell.headerHeight'), }), )