From 21024fe414849c4c8187fd9d5efc1c19155fcaa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bertalan=20K=C3=B6rmendy?= Date: Wed, 20 Nov 2024 15:26:20 +0100 Subject: [PATCH] Style plugin-aware `useInspectorInfo` (#6655) ## Problem `useInspectorInfo`, a widely used helper in the inspector, cannot read element styles set by Tailwind ## Fix The fix is made up from multiple pieces: - I extended `StylePlugin` with a new function, `readStyleFromElementProps`, which reads a given style info key from the `JSXAttributes` passed to it. I updated both `InlineStylePlugin` and `TailwindStylePlugin` to support this function, and updated all affected tests. This change was originally prototyped on the [Megaspike](https://github.com/concrete-utopia/utopia/pull/6574) - I updated `useGetMultiselectedProps` to use `StylePlugin.readStyleFromElementProps` when a style prop is read. - I updated `CSSStylePropertyNotParsable` and `ParsedCSSStyleProperty` to preserve the original value read from `projectContents`, in addition to the parsed representation. This way, `CSSStyleProperties` can be used by code that expect to work with this lower-level representation (such as the internals of `useInspectorInfo`) - I updated the `SET_PROP` and `UNSET_PROP` actions to use the `setProperty` and `deleteProperty` commands under the hood. This way, any editor code using these actions will be able to use the style plugins to write element style. This change was originally prototyped on the [Megaspike](https://github.com/concrete-utopia/utopia/pull/6574) ### Out of scope This change only touches `useGetMultiselectedProps` from the internals of `useInspectorInfo`. `useGetSpiedProps` is left unchanged, since the values element style props set through Tailwind don't show up in `allElementProps` (which is the data source for spied props) ## Manual Tests: I hereby swear that: - [x] I opened a hydrogen project and it loaded - [x] I could navigate to various routes in Play mode --- editor/src/components/canvas/canvas-types.ts | 16 +++- .../canvas/commands/utils/property-utils.ts | 2 +- .../plugins/inline-style-plugin.spec.ts | 29 +++++-- .../canvas/plugins/inline-style-plugin.ts | 17 ++-- .../canvas/plugins/style-plugins.ts | 8 +- .../canvas/plugins/tailwind-style-plugin.ts | 82 +++++++++++------- .../src/components/editor/actions/actions.tsx | 84 ++++++++----------- .../inspector/common/property-path-hooks.ts | 45 ++++++++-- 8 files changed, 182 insertions(+), 101 deletions(-) diff --git a/editor/src/components/canvas/canvas-types.ts b/editor/src/components/canvas/canvas-types.ts index 010fb804613f..218f168e7dec 100644 --- a/editor/src/components/canvas/canvas-types.ts +++ b/editor/src/components/canvas/canvas-types.ts @@ -1,4 +1,5 @@ import type { ReactElement } from 'react' +import type { JSExpression, PartOfJSXAttributeValue } from '../../core/shared/element-template' import { ElementInstanceMetadataMap } from '../../core/shared/element-template' import type { PropertyPath, ElementPath } from '../../core/shared/project-file-types' import type { KeysPressed } from '../../utils/keyboard' @@ -543,11 +544,13 @@ interface CSSStylePropertyNotFound { interface CSSStylePropertyNotParsable { type: 'not-parsable' + originalValue: JSExpression | PartOfJSXAttributeValue } interface ParsedCSSStyleProperty { type: 'property' tags: PropertyTag[] + propertyValue: JSExpression | PartOfJSXAttributeValue value: T } @@ -560,12 +563,17 @@ export function cssStylePropertyNotFound(): CSSStylePropertyNotFound { return { type: 'not-found' } } -export function cssStylePropertyNotParsable(): CSSStylePropertyNotParsable { - return { type: 'not-parsable' } +export function cssStylePropertyNotParsable( + originalValue: JSExpression | PartOfJSXAttributeValue, +): CSSStylePropertyNotParsable { + return { type: 'not-parsable', originalValue: originalValue } } -export function cssStyleProperty(value: T): ParsedCSSStyleProperty { - return { type: 'property', tags: [], value: value } +export function cssStyleProperty( + value: T, + propertyValue: JSExpression | PartOfJSXAttributeValue, +): ParsedCSSStyleProperty { + return { type: 'property', tags: [], value: value, propertyValue: propertyValue } } export function maybePropertyValue(property: CSSStyleProperty): T | null { diff --git a/editor/src/components/canvas/commands/utils/property-utils.ts b/editor/src/components/canvas/commands/utils/property-utils.ts index 1a28302d8c7d..123bba748aae 100644 --- a/editor/src/components/canvas/commands/utils/property-utils.ts +++ b/editor/src/components/canvas/commands/utils/property-utils.ts @@ -7,7 +7,7 @@ import { modifyUnderlyingElementForOpenFile } from '../../../editor/store/editor import { patchParseSuccessAtElementPath } from '../patch-utils' import type { CSSNumber } from '../../../inspector/common/css-utils' import { isCSSNumber } from '../../../inspector/common/css-utils' -import { type StyleInfo, isStyleInfoKey } from '../../canvas-types' +import type { StyleInfo } from '../../canvas-types' export interface EditorStateWithPatch { editorStateWithChanges: EditorState diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts index 7be9e9103a53..8ba0e9733b8c 100644 --- a/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts +++ b/editor/src/components/canvas/plugins/inline-style-plugin.spec.ts @@ -1,10 +1,6 @@ import * as EP from '../../../core/shared/element-path' import { cssNumber } from '../../inspector/common/css-utils' -import { - cssStyleProperty, - cssStylePropertyNotFound, - cssStylePropertyNotParsable, -} from '../canvas-types' +import { cssStylePropertyNotFound } from '../canvas-types' import type { EditorRenderResult } from '../ui-jsx.test-utils' import { renderTestEditorWithCode } from '../ui-jsx.test-utils' import { InlineStylePlugin } from './inline-style-plugin' @@ -45,8 +41,18 @@ export var storyboard = ( expect(styleInfo).not.toBeNull() const { flexDirection, gap } = styleInfo! - expect(flexDirection).toEqual(cssStyleProperty('column')) - expect(gap).toEqual(cssStyleProperty(cssNumber(2, 'rem'))) + expect(flexDirection).toMatchObject({ + type: 'property', + tags: [], + value: 'column', + propertyValue: { value: 'column' }, + }) + expect(gap).toMatchObject({ + type: 'property', + tags: [], + value: cssNumber(2, 'rem'), + propertyValue: { value: '2rem' }, + }) }) it('can parse style info with missing/unparsable props', async () => { @@ -88,7 +94,14 @@ export var storyboard = ( expect(styleInfo).not.toBeNull() const { flexDirection, gap } = styleInfo! expect(flexDirection).toEqual(cssStylePropertyNotFound()) - expect(gap).toEqual(cssStylePropertyNotParsable()) + expect(gap).toMatchObject({ + type: 'not-parsable', + originalValue: { + type: 'JS_PROPERTY_ACCESS', + onValue: { type: 'JS_IDENTIFIER', name: 'gap' }, + property: 'small', + }, + }) }) }) diff --git a/editor/src/components/canvas/plugins/inline-style-plugin.ts b/editor/src/components/canvas/plugins/inline-style-plugin.ts index 0a7d9f1f95ae..226ff8a45401 100644 --- a/editor/src/components/canvas/plugins/inline-style-plugin.ts +++ b/editor/src/components/canvas/plugins/inline-style-plugin.ts @@ -1,5 +1,4 @@ import type { JSXAttributes, PropertyPath } from 'utopia-shared/src/types' -import type { StyleLayoutProp } from '../../../core/layout/layout-helpers-new' import * as Either from '../../../core/shared/either' import { getJSXAttributesAtPath, @@ -9,7 +8,7 @@ import type { ModifiableAttribute } from '../../../core/shared/jsx-attributes' import { getJSXElementFromProjectContents } from '../../editor/store/editor-state' import { cssParsers, type ParsedCSSProperties } from '../../inspector/common/css-utils' import { stylePropPathMappingFn } from '../../inspector/common/property-path-hooks' -import type { CSSStyleProperty } from '../canvas-types' +import type { CSSStyleProperty, StyleInfo } from '../canvas-types' import { cssStyleProperty, cssStylePropertyNotParsable, @@ -29,7 +28,7 @@ function getPropValue(attributes: JSXAttributes, path: PropertyPath): Modifiable return result.attribute } -function getPropertyFromInstance

( +function getPropertyFromInstance

( prop: P, attributes: JSXAttributes, ): CSSStyleProperty> | null { @@ -39,18 +38,24 @@ function getPropertyFromInstance

Either.Either const parsed = parser(simpleValue.value) if (Either.isLeft(parsed) || parsed.value == null) { - return cssStylePropertyNotParsable() + return cssStylePropertyNotParsable(attribute) } - return cssStyleProperty(parsed.value) + return cssStyleProperty(parsed.value, attribute) } export const InlineStylePlugin: StylePlugin = { name: 'Inline Style', + readStyleFromElementProps: ( + attributes: JSXAttributes, + prop: T, + ): CSSStyleProperty> | null => { + return getPropertyFromInstance(prop, attributes) + }, styleInfoFactory: ({ projectContents }) => (elementPath) => { diff --git a/editor/src/components/canvas/plugins/style-plugins.ts b/editor/src/components/canvas/plugins/style-plugins.ts index 5fa2e0aed628..c23215963f48 100644 --- a/editor/src/components/canvas/plugins/style-plugins.ts +++ b/editor/src/components/canvas/plugins/style-plugins.ts @@ -1,4 +1,4 @@ -import type { ElementPath } from 'utopia-shared/src/types' +import type { ElementPath, JSXAttributes } from 'utopia-shared/src/types' import type { EditorState, EditorStatePatch } from '../../editor/store/editor-state' import type { InteractionLifecycle, @@ -18,7 +18,9 @@ import type { EditorStateWithPatch } from '../commands/utils/property-utils' import { applyValuesAtPath } from '../commands/utils/property-utils' import * as PP from '../../../core/shared/property-path' import { emptyComments, jsExpressionValue } from '../../../core/shared/element-template' +import type { CSSStyleProperty } from '../canvas-types' import { isStyleInfoKey, type StyleInfo } from '../canvas-types' +import type { ParsedCSSProperties } from '../../inspector/common/css-utils' export interface UpdateCSSProp { type: 'set' @@ -51,6 +53,10 @@ export type StyleUpdate = UpdateCSSProp | DeleteCSSProp export interface StylePlugin { name: string styleInfoFactory: StyleInfoFactory + readStyleFromElementProps: ( + attributes: JSXAttributes, + prop: T, + ) => CSSStyleProperty> | null updateStyles: ( editorState: EditorState, elementPath: ElementPath, diff --git a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts index 21d5508638a0..768f66dd6207 100644 --- a/editor/src/components/canvas/plugins/tailwind-style-plugin.ts +++ b/editor/src/components/canvas/plugins/tailwind-style-plugin.ts @@ -1,25 +1,33 @@ import * as TailwindClassParser from '@xengine/tailwindcss-class-parser' -import { isLeft } from '../../../core/shared/either' +import { defaultEither, flatMapEither, isLeft } from '../../../core/shared/either' import { getClassNameAttribute } from '../../../core/tailwind/tailwind-options' import { getElementFromProjectContents } from '../../editor/store/editor-state' -import type { Parser } from '../../inspector/common/css-utils' +import type { ParsedCSSProperties, Parser } from '../../inspector/common/css-utils' import { cssParsers } from '../../inspector/common/css-utils' import { mapDropNulls } from '../../../core/shared/array-utils' import type { StylePlugin } from './style-plugins' import type { Config } from 'tailwindcss/types/config' -import { cssStyleProperty, type CSSStyleProperty } from '../canvas-types' +import type { StyleInfo } from '../canvas-types' +import { cssStyleProperty, isStyleInfoKey, type CSSStyleProperty } from '../canvas-types' import * as UCL from './tailwind-style-plugin-utils/update-class-list' import { assertNever } from '../../../core/shared/utils' +import { + jsxSimpleAttributeToValue, + getModifiableJSXAttributeAtPath, +} from '../../../core/shared/jsx-attribute-utils' +import { emptyComments, type JSXAttributes } from 'utopia-shared/src/types' +import * as PP from '../../../core/shared/property-path' +import { jsExpressionValue } from '../../../core/shared/element-template' -function parseTailwindProperty( - value: unknown, - parse: Parser, -): CSSStyleProperty> | null { - const parsed = parse(value, null) +function parseTailwindProperty( + value: string | number | undefined, + prop: T, +): CSSStyleProperty> | null { + const parsed = cssParsers[prop](value, null) if (isLeft(parsed) || parsed.value == null) { return null } - return cssStyleProperty(parsed.value) + return cssStyleProperty(parsed.value, jsExpressionValue(value, emptyComments)) } const TailwindPropertyMapping: Record = { @@ -81,6 +89,25 @@ const underscoresToSpaces = (s: string | undefined) => s?.replace(/[-_]/g, ' ') export const TailwindPlugin = (config: Config | null): StylePlugin => ({ name: 'Tailwind', + readStyleFromElementProps:

( + attributes: JSXAttributes, + prop: P, + ): CSSStyleProperty> | null => { + const classNameAttribute = defaultEither( + null, + flatMapEither( + (attr) => jsxSimpleAttributeToValue(attr), + getModifiableJSXAttributeAtPath(attributes, PP.create('className')), + ), + ) + + if (typeof classNameAttribute !== 'string') { + return null + } + + const mapping = getTailwindClassMapping(classNameAttribute.split(' '), config) + return parseTailwindProperty(mapping[TailwindPropertyMapping[prop]], prop) + }, styleInfoFactory: ({ projectContents }) => (elementPath) => { @@ -95,48 +122,45 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({ const mapping = getTailwindClassMapping(classList.split(' '), config) return { - gap: parseTailwindProperty(mapping[TailwindPropertyMapping.gap], cssParsers.gap), + gap: parseTailwindProperty(mapping[TailwindPropertyMapping.gap], 'gap'), flexDirection: parseTailwindProperty( mapping[TailwindPropertyMapping.flexDirection], - cssParsers.flexDirection, - ), - left: parseTailwindProperty(mapping[TailwindPropertyMapping.left], cssParsers.left), - right: parseTailwindProperty(mapping[TailwindPropertyMapping.right], cssParsers.right), - top: parseTailwindProperty(mapping[TailwindPropertyMapping.top], cssParsers.top), - bottom: parseTailwindProperty(mapping[TailwindPropertyMapping.bottom], cssParsers.bottom), - width: parseTailwindProperty(mapping[TailwindPropertyMapping.width], cssParsers.width), - height: parseTailwindProperty(mapping[TailwindPropertyMapping.height], cssParsers.height), - flexBasis: parseTailwindProperty( - mapping[TailwindPropertyMapping.flexBasis], - cssParsers.flexBasis, + 'flexDirection', ), + left: parseTailwindProperty(mapping[TailwindPropertyMapping.left], 'left'), + right: parseTailwindProperty(mapping[TailwindPropertyMapping.right], 'right'), + top: parseTailwindProperty(mapping[TailwindPropertyMapping.top], 'top'), + bottom: parseTailwindProperty(mapping[TailwindPropertyMapping.bottom], 'bottom'), + width: parseTailwindProperty(mapping[TailwindPropertyMapping.width], 'width'), + height: parseTailwindProperty(mapping[TailwindPropertyMapping.height], 'height'), + flexBasis: parseTailwindProperty(mapping[TailwindPropertyMapping.flexBasis], 'flexBasis'), padding: parseTailwindProperty( underscoresToSpaces(mapping[TailwindPropertyMapping.padding]), - cssParsers.padding, + 'padding', ), paddingTop: parseTailwindProperty( mapping[TailwindPropertyMapping.paddingTop], - cssParsers.paddingTop, + 'paddingTop', ), paddingRight: parseTailwindProperty( mapping[TailwindPropertyMapping.paddingRight], - cssParsers.paddingRight, + 'paddingRight', ), paddingBottom: parseTailwindProperty( mapping[TailwindPropertyMapping.paddingBottom], - cssParsers.paddingBottom, + 'paddingBottom', ), paddingLeft: parseTailwindProperty( mapping[TailwindPropertyMapping.paddingLeft], - cssParsers.paddingLeft, + 'paddingLeft', ), - zIndex: parseTailwindProperty(mapping[TailwindPropertyMapping.zIndex], cssParsers.zIndex), + zIndex: parseTailwindProperty(mapping[TailwindPropertyMapping.zIndex], 'zIndex'), } }, updateStyles: (editorState, elementPath, updates) => { const propsToDelete = mapDropNulls( (update) => - update.type !== 'delete' || TailwindPropertyMapping[update.property] == null + update.type !== 'delete' || TailwindPropertyMapping[update.property] == null // TODO: make this type-safe ? null : UCL.remove(TailwindPropertyMapping[update.property]), updates, @@ -144,7 +168,7 @@ export const TailwindPlugin = (config: Config | null): StylePlugin => ({ const propsToSet = mapDropNulls( (update) => - update.type !== 'set' || TailwindPropertyMapping[update.property] == null + update.type !== 'set' || TailwindPropertyMapping[update.property] == null // TODO: make this type-safe ? null : UCL.add({ property: TailwindPropertyMapping[update.property], diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx index 2d158b0407b4..9e156336f069 100644 --- a/editor/src/components/editor/actions/actions.tsx +++ b/editor/src/components/editor/actions/actions.tsx @@ -630,10 +630,15 @@ import { getParseCacheOptions } from '../../../core/shared/parse-cache-utils' import { styleP } from '../../inspector/inspector-common' import { getUpdateOperationResult } from '../../../core/shared/import/import-operation-service' import { updateRequirements } from '../../../core/shared/import/project-health-check/utopia-requirements-service' -import { applyValuesAtPath, deleteValuesAtPath } from '../../canvas/commands/utils/property-utils' +import { + applyValuesAtPath, + deleteValuesAtPath, + maybeCssPropertyFromInlineStyle, +} from '../../canvas/commands/utils/property-utils' import type { HuggingElementContentsStatus } from '../../../components/canvas/hugging-utils' import { getHuggingElementContentsStatus } from '../../../components/canvas/hugging-utils' import { createStoryboardFileIfNecessary } from '../../../core/shared/import/project-health-check/requirements/requirement-storyboard' +import { setProperty } from '../../canvas/commands/set-property-command' export const MIN_CODE_PANE_REOPEN_WIDTH = 100 @@ -1727,50 +1732,41 @@ export const UPDATE_FNS = { }, UNSET_PROPERTY: (action: UnsetProperty, editor: EditorModel): EditorModel => { // TODO also queue group true up, just like for SET_PROP - let unsetPropFailedMessage: string | null = null - const updatedEditor = modifyUnderlyingElementForOpenFile( - action.element, - editor, - (element) => { - const updatedProps = unsetJSXValueAtPath(element.props, action.property) - return foldEither( - (failureMessage) => { - unsetPropFailedMessage = failureMessage - return element - }, - (updatedAttributes) => ({ - ...element, - props: updatedAttributes, - }), - updatedProps, - ) - }, - (success) => success, - ) - if (unsetPropFailedMessage != null) { - const toastAction = showToast(notice(unsetPropFailedMessage, 'ERROR')) - return UPDATE_FNS.ADD_TOAST(toastAction, editor) - } else { - return updatedEditor - } + // TODO this used to fire a toast if the prop couldn't be removed + return foldAndApplyCommandsSimple(editor, [ + deleteProperties('always', action.element, [action.property]), + ]) }, SET_PROP: (action: SetProp, editor: EditorModel): EditorModel => { let setPropFailedMessage: string | null = null let newSelectedViews: Array = editor.selectedViews + const prop = maybeCssPropertyFromInlineStyle(action.propertyPath) + const valueForStyleProp = + action.value.type === 'ATTRIBUTE_VALUE' && + (typeof action.value.value === 'number' || typeof action.value.value === 'string') + ? action.value.value + : null + + const editorWithPropSet = + prop == null || valueForStyleProp == null + ? applyValuesAtPath(editor, action.target, [ + { path: action.propertyPath, value: action.value }, + ]).editorStateWithChanges + : foldAndApplyCommandsSimple(editor, [ + setProperty('always', action.target, PP.create('style', prop), valueForStyleProp), + ]) + + if (isJSXElement(action.value)) { + newSelectedViews = [EP.appendToPath(action.target, action.value.uid)] + } let updatedEditor = modifyUnderlyingTargetElement( action.target, - editor, + editorWithPropSet, (element) => { if (!isJSXElement(element)) { return element } - const updatedProps = setJSXValueAtPath(element.props, action.propertyPath, action.value) - // when this is a render prop we should select it - if (isJSXElement(action.value)) { - newSelectedViews = [EP.appendToPath(action.target, action.value.uid)] - } if ( - isRight(updatedProps) && PP.contains( [ PP.create('style', 'top'), @@ -1783,8 +1779,9 @@ export const UPDATE_FNS = { action.propertyPath, ) ) { + // TODO: refactor this to read from the plugins const maybeInvalidGroupState = groupStateFromJSXElement( - { ...element, props: updatedProps.value }, + element, action.target, editor.jsxMetadata, editor.elementPathTree, @@ -1803,18 +1800,11 @@ export const UPDATE_FNS = { return element } } - return foldEither( - (failureMessage) => { - setPropFailedMessage = failureMessage - return element - }, - (updatedAttributes) => ({ - ...element, - // we round style.left/top/right/bottom/width/height pins for the modified element - props: roundAttributeLayoutValues(styleStringInArray, updatedAttributes), - }), - updatedProps, - ) + return { + ...element, + // TODO: refactor this to use commands + props: roundAttributeLayoutValues(styleStringInArray, element.props), + } }, (success, _, underlyingFilePath) => { const updatedImports = mergeImports( diff --git a/editor/src/components/inspector/common/property-path-hooks.ts b/editor/src/components/inspector/common/property-path-hooks.ts index cd7d469b963f..4bfb0b2b1f50 100644 --- a/editor/src/components/inspector/common/property-path-hooks.ts +++ b/editor/src/components/inspector/common/property-path-hooks.ts @@ -48,7 +48,7 @@ import type { StyleLayoutProp } from '../../../core/layout/layout-helpers-new' import { isHTMLComponent, isUtopiaAPIComponent } from '../../../core/model/project-file-utils' import { stripNulls } from '../../../core/shared/array-utils' import type { Either } from '../../../core/shared/either' -import { eitherToMaybe, flatMapEither, isRight, left } from '../../../core/shared/either' +import { eitherToMaybe, flatMapEither, isRight, left, right } from '../../../core/shared/either' import type { JSXAttributes, ComputedStyle, @@ -92,6 +92,10 @@ import type { EditorAction } from '../../editor/action-types' import { useDispatch } from '../../editor/store/dispatch-context' import { eitherRight, fromTypeGuard } from '../../../core/shared/optics/optic-creators' import { modify } from '../../../core/shared/optics/optic-utilities' +import { getActivePlugin } from '../../canvas/plugins/style-plugins' +import { isStyleInfoKey, type StyleInfo } from '../../canvas/canvas-types' +import { assertNever } from '../../../core/shared/utils' +import { maybeCssPropertyFromInlineStyle } from '../../canvas/commands/utils/property-utils' export interface InspectorPropsContextData { selectedViews: Array @@ -744,10 +748,38 @@ const getModifiableAttributeResultToExpressionOptic = eitherRight< ModifiableAttribute >().compose(fromTypeGuard(isRegularJSXAttribute)) +function maybeStyleInfoKeyFromPropertyPath(propertyPath: PropertyPath): keyof StyleInfo | null { + const maybeCSSProp = maybeCssPropertyFromInlineStyle(propertyPath) + if (maybeCSSProp == null || !isStyleInfoKey(maybeCSSProp)) { + return null + } + return maybeCSSProp +} + export function useGetMultiselectedProps

( pathMappingFn: PathMappingFn

, propKeys: P[], ): MultiselectAtProps

{ + const styleInfoReaderRef = useRefEditorState( + (store) => + (props: JSXAttributes, prop: keyof StyleInfo): GetModifiableAttributeResult => { + const elementStyle = getActivePlugin(store.editor).readStyleFromElementProps(props, prop) + if (elementStyle == null) { + return left('not found') + } + switch (elementStyle.type) { + case 'not-found': + return right({ type: 'ATTRIBUTE_NOT_FOUND' }) + case 'not-parsable': + return right(elementStyle.originalValue) + case 'property': + return right(elementStyle.propertyValue) + default: + assertNever(elementStyle) + } + }, + ) + return useKeepReferenceEqualityIfPossible( useContextSelector( InspectorPropsContext, @@ -755,10 +787,13 @@ export function useGetMultiselectedProps

( const keyFn = (propKey: P) => propKey const mapFn = (propKey: P) => { return contextData.editedMultiSelectedProps.map((props) => { - const result = getModifiableJSXAttributeAtPath( - props, - pathMappingFn(propKey, contextData.targetPath), - ) + const targetPath = pathMappingFn(propKey, contextData.targetPath) + const maybeStyleInfoKey = maybeStyleInfoKeyFromPropertyPath(targetPath) + const result = + maybeStyleInfoKey != null + ? styleInfoReaderRef.current(props, maybeStyleInfoKey) + : getModifiableJSXAttributeAtPath(props, targetPath) + // This wipes the uid from any `JSExpression` values we may have retrieved, // as that can cause the deep equality check to fail for different elements // with the same value for a given property.