From cf38fdb2454d0abfece9d54a81d3f1eeca1cede6 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 9 Aug 2026 07:21:27 -0700 Subject: [PATCH] feat(site-builder): array editors scroll to the item picked in Layers Wires ArrayItemFieldsEditor and FeaturesEditor up to useLayerFocus() so clicking a virtual row in the Layers tree scrolls the matching item's card into view in the right-hand array editor. scrollIntoView is optional-chained on both the queried element and the method itself so a miss or an environment without it degrades silently. Co-Authored-By: Claude Opus 5 (1M context) --- .../panels/right/styles/ArrayItemFields.tsx | 26 +++++++++++++++++-- .../panels/right/styles/FeaturesEditor.tsx | 21 ++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/craft/src/panels/right/styles/ArrayItemFields.tsx b/craft/src/panels/right/styles/ArrayItemFields.tsx index d2d07fd..b2bb96c 100644 --- a/craft/src/panels/right/styles/ArrayItemFields.tsx +++ b/craft/src/panels/right/styles/ArrayItemFields.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { useEditor } from '@craftjs/core'; import { CollapsibleSection, ArrayPropEditor, smallInputStyle } from './shared'; +import { useLayerFocus } from '../../left/LayerFocusContext'; /* ---------- Shared array-item field editor ---------- Extracted from SectionTypePanel and GenericPropsEditor, which both had a @@ -18,7 +19,26 @@ export const ArrayItemFieldsEditor: React.FC<{ selectedId: string; propKey: stri const sampleItem = arrayItems[0] || {}; const itemFields = typeof sampleItem === 'object' && sampleItem !== null ? Object.keys(sampleItem) : []; + // Layers panel -> array editor "scroll to this item" hookup. The outer + //
wraps ArrayPropEditor's rendered cards (the actual + // per-item background box lives in shared.tsx's ArrayPropEditor, which is + // also used by MediaStylePanel/FormStylePanel -- rather than touch that + // shared component for one consumer, the data-array-item tag below goes + // on the content renderItem returns, which is enough for scrollIntoView + // to bring the right card into the viewport. + const { focus } = useLayerFocus(); + const rootRef = useRef(null); + + useEffect(() => { + if (!focus || focus.prop !== propKey) return; + const card = rootRef.current?.querySelector(`[data-array-item="${propKey}:${focus.index}"]`); + card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' }); + // `focus.nonce` is in the dep list so clicking the SAME row twice + // re-scrolls (the request object is otherwise identical). + }, [focus?.nonce, focus?.prop, focus?.index, propKey]); + return ( +
{ actions.setProp(selectedId, (props: any) => { @@ -42,7 +63,7 @@ export const ArrayItemFieldsEditor: React.FC<{ selectedId: string; propKey: stri ); } return ( -
+
{itemFields.map((field) => { const fieldVal = item[field]; if (typeof fieldVal === 'boolean') { @@ -123,5 +144,6 @@ export const ArrayItemFieldsEditor: React.FC<{ selectedId: string; propKey: stri } /> +
); }; diff --git a/craft/src/panels/right/styles/FeaturesEditor.tsx b/craft/src/panels/right/styles/FeaturesEditor.tsx index 42db943..3e127ac 100644 --- a/craft/src/panels/right/styles/FeaturesEditor.tsx +++ b/craft/src/panels/right/styles/FeaturesEditor.tsx @@ -1,7 +1,8 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { useEditor } from '@craftjs/core'; import { labelStyle, inputStyle, sectionGap } from './shared'; import { AssetPicker } from '../../../ui/AssetPicker'; +import { useLayerFocus } from '../../left/LayerFocusContext'; interface Feature { title?: string; description?: string; icon?: string; @@ -27,10 +28,24 @@ export const FeaturesEditor: React.FC<{ selectedId: string; features: unknown }> mutate((arr) => [...arr, { title: 'New Feature', description: 'Describe this feature.', icon: '🔧', image: '', imageAlt: '', buttonText: '', buttonUrl: '' }]); const remove = (i: number) => mutate((arr) => { arr.splice(i, 1); return arr; }); + // Layers panel -> array editor "scroll to this item" hookup. See + // ArrayItemFields.tsx for the generic-editor counterpart; this component + // keeps its own per-feature cards, so it tags/scrolls them directly. + const { focus } = useLayerFocus(); + const rootRef = useRef(null); + + useEffect(() => { + if (!focus || focus.prop !== 'features') return; + const card = rootRef.current?.querySelector(`[data-array-item="features:${focus.index}"]`); + card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' }); + // `focus.nonce` is in the dep list so clicking the SAME row twice + // re-scrolls (the request object is otherwise identical). + }, [focus?.nonce, focus?.prop, focus?.index]); + return ( -
+
{list.map((feat, i) => ( -
+
update(i, 'title', e.target.value)} placeholder="Title" style={{ ...inputStyle, flex: 1 }} />