@@ -53,6 +53,8 @@ interface PropertyPanelProps {
5353 onImportFonts ?: ( files : FileList | File [ ] ) => Promise < ImportedFontAsset [ ] > ;
5454 activeCompositionPath ?: string | null ;
5555 onSelectLayer ?: ( layer : DomEditLayerItem ) => void ;
56+ manualEditsEnabled ?: boolean ;
57+ onSetManualEditsEnabled ?: ( enabled : boolean ) => void ;
5658}
5759
5860/* ------------------------------------------------------------------ */
@@ -115,6 +117,42 @@ function LayerTree({
115117 ) ;
116118}
117119
120+ /* ------------------------------------------------------------------ */
121+ /* ManualPositioningToggle */
122+ /* ------------------------------------------------------------------ */
123+
124+ function ManualPositioningToggle ( {
125+ enabled,
126+ onToggle,
127+ } : {
128+ enabled : boolean ;
129+ onToggle : ( enabled : boolean ) => void ;
130+ } ) {
131+ return (
132+ < div className = "flex-shrink-0 border-t border-neutral-800 px-4 py-3" >
133+ < div className = "flex items-center justify-between" >
134+ < span className = "text-[11px] text-neutral-400" > Manual positioning</ span >
135+ < button
136+ type = "button"
137+ role = "switch"
138+ aria-checked = { enabled }
139+ onClick = { ( ) => onToggle ( ! enabled ) }
140+ title = { enabled ? "Disable drag-to-reposition" : "Enable drag-to-reposition" }
141+ className = { `relative inline-flex h-5 w-9 flex-shrink-0 rounded-full transition-colors focus-visible:outline-none ${
142+ enabled ? "bg-studio-accent" : "bg-neutral-700"
143+ } `}
144+ >
145+ < span
146+ className = { `pointer-events-none mt-0.5 inline-block h-4 w-4 transform rounded-full bg-white shadow-sm transition-transform ${
147+ enabled ? "translate-x-[18px]" : "translate-x-[2px]"
148+ } `}
149+ />
150+ </ button >
151+ </ div >
152+ </ div >
153+ ) ;
154+ }
155+
118156/* ------------------------------------------------------------------ */
119157/* PropertyPanel */
120158/* ------------------------------------------------------------------ */
@@ -141,41 +179,52 @@ export const PropertyPanel = memo(function PropertyPanel({
141179 onImportFonts,
142180 activeCompositionPath = null ,
143181 onSelectLayer,
182+ manualEditsEnabled = true ,
183+ onSetManualEditsEnabled,
144184} : PropertyPanelProps ) {
145185 const styles = element ?. computedStyles ?? EMPTY_STYLES ;
146186
147187 if ( ! element ) {
148188 return (
149- < div className = "flex h-full flex-col items-center justify-center bg-neutral-900 px-6 text-center" >
150- { multiSelectCount > 1 ? (
151- < >
152- < Layers size = { 18 } className = "mb-3 text-neutral-600" />
153- < p className = "text-sm font-medium text-neutral-200" >
154- { multiSelectCount } elements selected
155- </ p >
156- < p className = "mt-2 max-w-[260px] text-xs leading-5 text-neutral-500" >
157- Select a single element to edit its properties. Click an element in the preview or use
158- the timeline layer panel.
159- </ p >
160- </ >
161- ) : (
162- < >
163- < Eye size = { 18 } className = "mb-3 text-neutral-600" />
164- < p className = "text-sm font-medium text-neutral-200" >
165- Select an element in the preview.
166- </ p >
167- < p className = "mt-2 max-w-[260px] text-xs leading-5 text-neutral-500" >
168- The inspector is tuned for element edits with safer geometry controls, color picking,
169- and cleaner grouped layer controls.
170- </ p >
171- </ >
189+ < div className = "flex h-full flex-col bg-neutral-900" >
190+ < div className = "flex flex-1 flex-col items-center justify-center px-6 text-center" >
191+ { multiSelectCount > 1 ? (
192+ < >
193+ < Layers size = { 18 } className = "mb-3 text-neutral-600" />
194+ < p className = "text-sm font-medium text-neutral-200" >
195+ { multiSelectCount } elements selected
196+ </ p >
197+ < p className = "mt-2 max-w-[260px] text-xs leading-5 text-neutral-500" >
198+ Select a single element to edit its properties. Click an element in the preview or
199+ use the timeline layer panel.
200+ </ p >
201+ </ >
202+ ) : (
203+ < >
204+ < Eye size = { 18 } className = "mb-3 text-neutral-600" />
205+ < p className = "text-sm font-medium text-neutral-200" >
206+ Select an element in the preview.
207+ </ p >
208+ < p className = "mt-2 max-w-[260px] text-xs leading-5 text-neutral-500" >
209+ The inspector is tuned for element edits with safer geometry controls, color
210+ picking, and cleaner grouped layer controls.
211+ </ p >
212+ </ >
213+ ) }
214+ </ div >
215+ { onSetManualEditsEnabled && (
216+ < ManualPositioningToggle
217+ enabled = { manualEditsEnabled }
218+ onToggle = { onSetManualEditsEnabled }
219+ />
172220 ) }
173221 </ div >
174222 ) ;
175223 }
176224
177- const manualOffsetEditingDisabled = ! element . capabilities . canApplyManualOffset ;
178- const manualSizeEditingDisabled = ! element . capabilities . canApplyManualSize ;
225+ const manualOffsetEditingDisabled =
226+ ! element . capabilities . canApplyManualOffset || ! manualEditsEnabled ;
227+ const manualSizeEditingDisabled = ! element . capabilities . canApplyManualSize || ! manualEditsEnabled ;
179228 const sourceLabel = element . id ? `#${ element . id } ` : element . selector ;
180229 const showEditableSections = element . capabilities . canEditStyles ;
181230 const manualOffset = readStudioPathOffset ( element . element ) ;
@@ -318,6 +367,7 @@ export const PropertyPanel = memo(function PropertyPanel({
318367 < MetricField
319368 label = "R"
320369 value = { `${ manualRotation . angle } °` }
370+ disabled = { ! manualEditsEnabled }
321371 onCommit = { ( next ) => commitManualRotation ( next . replace ( "°" , "" ) ) }
322372 />
323373 </ div >
@@ -342,6 +392,9 @@ export const PropertyPanel = memo(function PropertyPanel({
342392 />
343393 ) }
344394 </ div >
395+ { onSetManualEditsEnabled && (
396+ < ManualPositioningToggle enabled = { manualEditsEnabled } onToggle = { onSetManualEditsEnabled } />
397+ ) }
345398 </ div >
346399 ) ;
347400} ) ;
0 commit comments