/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/import Agent from 'react-devtools-shared/src/backend/agent';
import resolveBoxStyle from './resolveBoxStyle';
import isArray from 'react-devtools-shared/src/isArray';
import type {BackendBridge} from 'react-devtools-shared/src/bridge';
import type {RendererID} from '../types';
import type {StyleAndLayout} from './types';
export type ResolveNativeStyle = (stylesheetID: any) => ?Object;
export type SetupNativeStyleEditor = typeof setupNativeStyleEditor;
export default function setupNativeStyleEditor(
bridge: BackendBridge,agent: Agent,resolveNativeStyle: ResolveNativeStyle,validAttributes?: $ReadOnlyArray<string> | null,) {bridge.addListener(
'NativeStyleEditor_measure',
({id, rendererID}: {id: number, rendererID: RendererID}) => {
measureStyle(agent, bridge, resolveNativeStyle, id, rendererID);
},);bridge.addListener(
'NativeStyleEditor_renameAttribute',
({id,
rendererID,
oldName,
newName,
value,
}: {id: number,
rendererID: RendererID,
oldName: string,
newName: string,
value: string,
}) => {
renameStyle(agent, id, rendererID, oldName, newName, value);
setTimeout(() =>
measureStyle(agent, bridge, resolveNativeStyle, id, rendererID),
);},);bridge.addListener(
'NativeStyleEditor_setValue',
({id,
rendererID,
name,
value,
}: {id: number,
rendererID: number,
name: string,
value: string,
}) => {
setStyle(agent, id, rendererID, name, value);
setTimeout(() =>
measureStyle(agent, bridge, resolveNativeStyle, id, rendererID),
);},);bridge.send('isNativeStyleEditorSupported', {
isSupported: true,
validAttributes,
});}const EMPTY_BOX_STYLE = {
top: 0,
left: 0,
right: 0,
bottom: 0,
};const componentIDToStyleOverrides: Map<number, Object> = new Map();
function measureStyle(
agent: Agent,bridge: BackendBridge,resolveNativeStyle: ResolveNativeStyle,id: number,rendererID: RendererID,) {const data = agent.getInstanceAndStyle({id, rendererID});
if (!data || !data.style) {
bridge.send(
'NativeStyleEditor_styleAndLayout',
({id,
layout: null,
style: null,
}: StyleAndLayout),
);return;
}const {instance, style} = data;
let resolvedStyle = resolveNativeStyle(style);
// If it's a host component we edited before, amend styles.
const styleOverrides = componentIDToStyleOverrides.get(id);
if (styleOverrides != null) {
resolvedStyle = Object.assign({}, resolvedStyle, styleOverrides);
}if (!instance || typeof instance.measure !== 'function') {
bridge.send(
'NativeStyleEditor_styleAndLayout',
({id,
layout: null,
style: resolvedStyle || null,
}: StyleAndLayout),
);return;
}instance.measure((x, y, width, height, left, top) => {
// RN Android sometimes returns undefined here. Don't send measurements in this case.
// https://github.com/jhen0409/react-native-debugger/issues/84#issuecomment-304611817
if (typeof x !== 'number') {
bridge.send(
'NativeStyleEditor_styleAndLayout',
({id,
layout: null,
style: resolvedStyle || null,
}: StyleAndLayout),
);return;
}const margin =
(resolvedStyle != null && resolveBoxStyle('margin', resolvedStyle)) ||
EMPTY_BOX_STYLE;
const padding =
(resolvedStyle != null && resolveBoxStyle('padding', resolvedStyle)) ||
EMPTY_BOX_STYLE;
bridge.send(
'NativeStyleEditor_styleAndLayout',
({id,
layout: {x,
y,
width,
height,
left,
top,
margin,
padding,
},style: resolvedStyle || null,
}: StyleAndLayout),
);});}function shallowClone(object: Object): Object {
const cloned: {[string]: $FlowFixMe} = {};
for (const n in object) {
cloned[n] = object[n];
}return cloned;
}function renameStyle(
agent: Agent,id: number,rendererID: RendererID,oldName: string,newName: string,value: string,): void {const data = agent.getInstanceAndStyle({id, rendererID});
if (!data || !data.style) {
return;
}const {instance, style} = data;
const newStyle = newName
? {[oldName]: undefined, [newName]: value}
: {[oldName]: undefined};
let customStyle;
// TODO It would be nice if the renderer interface abstracted this away somehow.
if (instance !== null && typeof instance.setNativeProps === 'function') {
// In the case of a host component, we need to use setNativeProps().
// Remember to "correct" resolved styles when we read them next time.
const styleOverrides = componentIDToStyleOverrides.get(id);
if (!styleOverrides) {
componentIDToStyleOverrides.set(id, newStyle);
} else {
Object.assign(styleOverrides, newStyle);
}// TODO Fabric does not support setNativeProps; chat with Sebastian or Eli
instance.setNativeProps({style: newStyle});
} else if (isArray(style)) {
const lastIndex = style.length - 1;
if (typeof style[lastIndex] === 'object' && !isArray(style[lastIndex])) {
customStyle = shallowClone(style[lastIndex]);
delete customStyle[oldName];
if (newName) {
customStyle[newName] = value;
} else {
customStyle[oldName] = undefined;
}agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style', lastIndex],
value: customStyle,
});} else {
agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style'],
value: style.concat([newStyle]),
});}} else if (typeof style === 'object') {
customStyle = shallowClone(style);
delete customStyle[oldName];
if (newName) {
customStyle[newName] = value;
} else {
customStyle[oldName] = undefined;
}agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style'],
value: customStyle,
});} else {
agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style'],
value: [style, newStyle],
});}agent.emit('hideNativeHighlight');
}function setStyle(
agent: Agent,id: number,rendererID: RendererID,name: string,value: string,) {const data = agent.getInstanceAndStyle({id, rendererID});
if (!data || !data.style) {
return;
}const {instance, style} = data;
const newStyle = {[name]: value};
// TODO It would be nice if the renderer interface abstracted this away somehow.
if (instance !== null && typeof instance.setNativeProps === 'function') {
// In the case of a host component, we need to use setNativeProps().
// Remember to "correct" resolved styles when we read them next time.
const styleOverrides = componentIDToStyleOverrides.get(id);
if (!styleOverrides) {
componentIDToStyleOverrides.set(id, newStyle);
} else {
Object.assign(styleOverrides, newStyle);
}// TODO Fabric does not support setNativeProps; chat with Sebastian or Eli
instance.setNativeProps({style: newStyle});
} else if (isArray(style)) {
const lastLength = style.length - 1;
if (typeof style[lastLength] === 'object' && !isArray(style[lastLength])) {
agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style', lastLength, name],
value,
});} else {
agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style'],
value: style.concat([newStyle]),
});}} else {
agent.overrideValueAtPath({
type: 'props',
id,
rendererID,
path: ['style'],
value: [style, newStyle],
});}agent.emit('hideNativeHighlight');
}