diff --git a/editor/src/components/inspector/common/css-utils.ts b/editor/src/components/inspector/common/css-utils.ts index b48f81e170ca..feec3148dc00 100644 --- a/editor/src/components/inspector/common/css-utils.ts +++ b/editor/src/components/inspector/common/css-utils.ts @@ -652,6 +652,21 @@ export type GridCSSMinmax = BaseGridDimension & { max: GridCSSNumber | GridCSSKeyword } +export function parseGridCSSMinmaxOrRepeat(input: string): GridCSSMinmax | GridCSSRepeat | null { + const parsed = csstree.parse(input, { context: 'value' }) + if (parsed.type === 'Value') { + const parsedDimensions = parseGridChildren(parsed.children) + if ( + isRight(parsedDimensions) && + parsedDimensions.value.length === 1 && + (isGridCSSMinmax(parsedDimensions.value[0]) || isGridCSSRepeat(parsedDimensions.value[0])) + ) { + return parsedDimensions.value[0] + } + } + return null +} + export function isGridCSSKeyword(dim: GridDimension): dim is GridCSSKeyword { return dim.type === 'KEYWORD' } @@ -1007,7 +1022,6 @@ const validGridDimensionKeywords = [ 'subgrid', 'auto-fit', 'auto-fill', - 'minmax', // TODO this should be removed once we have proper editing of grid template expressions! ] as const export type ValidGridDimensionKeyword = (typeof validGridDimensionKeywords)[number] diff --git a/editor/src/components/inspector/flex-section.spec.browser2.tsx b/editor/src/components/inspector/flex-section.spec.browser2.tsx index f8ad05c41e96..1cdcc970fb6d 100644 --- a/editor/src/components/inspector/flex-section.spec.browser2.tsx +++ b/editor/src/components/inspector/flex-section.spec.browser2.tsx @@ -53,29 +53,19 @@ describe('flex section', () => { const grid = await renderResult.renderedDOM.findByTestId('grid') expect(grid.style.gridTemplateColumns).toEqual('[area1] auto 1fr 1fr 1fr 1fr') }) - it('uses auto for defaults when empty', async () => { - const renderResult = await renderTestEditorWithCode( - gridProjectWithoutTemplate, - 'await-first-dom-report', - ) - await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) - const control = await screen.findByTestId('grid-dimension-column-0') - await typeIntoField(control, '100px') - const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('100px auto auto') - }) - it('updates a repeated value', async () => { + it('updates a repeat expression', async () => { const renderResult = await renderTestEditorWithCode( gridProjectWithRepeat, 'await-first-dom-report', ) await selectComponentsForTest(renderResult, [EP.fromString('sb/grid')]) - await typeIntoField(await screen.findByTestId('grid-dimension-column-2'), '42') const grid = await renderResult.renderedDOM.findByTestId('grid') - expect(grid.style.gridTemplateColumns).toEqual('[area1] 1fr repeat(2, 10px 42px) 2fr') - await typeIntoField(await screen.findByTestId('grid-dimension-column-1'), '.5fr') + await typeIntoField( + await screen.findByTestId('grid-dimension-column-1'), + 'repeat(2, 0.5fr 42px)', + ) expect(grid.style.gridTemplateColumns).toEqual('[area1] 1fr repeat(2, 0.5fr 42px) 2fr') }) }) diff --git a/editor/src/components/inspector/flex-section.spec.tsx b/editor/src/components/inspector/flex-section.spec.tsx deleted file mode 100644 index a2ffdf898ef5..000000000000 --- a/editor/src/components/inspector/flex-section.spec.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { cssKeyword, cssNumber, gridCSSKeyword, gridCSSNumber } from './common/css-utils' -import { mergeGridTemplateValues } from './flex-section' - -describe('mergeGridTemplateValues', () => { - it('empty values', async () => { - expect( - mergeGridTemplateValues({ - calculated: [], - fromProps: [], - autoValues: [], - }), - ).toEqual([]) - }) - - it('only calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null)], - fromProps: [], - autoValues: [], - }), - ).toEqual([gridCSSKeyword(cssKeyword('auto'), null)]) - }) - - it('competing calculated and from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null)], - fromProps: [gridCSSNumber(cssNumber(2), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null)]) - }) - - it('multiple elements, empty from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - fromProps: [gridCSSNumber(cssNumber(2), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null), gridCSSNumber(cssNumber(2), null)]) - }) - - it('multiple elements, competing', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - fromProps: [gridCSSNumber(cssNumber(2), null), gridCSSKeyword(cssKeyword('auto'), null)], - autoValues: [], - }), - ).toEqual([gridCSSNumber(cssNumber(2), null), gridCSSKeyword(cssKeyword('auto'), null)]) - }) - - it('one auto value, auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [gridCSSKeyword(cssKeyword('auto'), null)], - fromProps: [], - autoValues: [gridCSSNumber(cssNumber(42), null)], - }), - ).toEqual([gridCSSNumber(cssNumber(42), null)]) - }) - - it('one auto value, multiple auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [], - autoValues: [gridCSSNumber(cssNumber(42), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(42), null), - ]) - }) - - it('multiple auto values, multiple auto calculated', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [], - autoValues: [ - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(3), null), - ], - }), - ).toEqual([ - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(3), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(2), null), - ]) - }) - - it('one auto value, multiple auto calculated, but values from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [gridCSSNumber(cssNumber(42), null)], - autoValues: [gridCSSNumber(cssNumber(1), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - gridCSSNumber(cssNumber(1), null), - ]) - }) - - it('multiple auto values, multiple auto calculated, but values from props', async () => { - expect( - mergeGridTemplateValues({ - calculated: [ - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSKeyword(cssKeyword('auto'), null), - ], - fromProps: [ - gridCSSNumber(cssNumber(42), null), - gridCSSKeyword(cssKeyword('auto'), null), - gridCSSNumber(cssNumber(23), null), - ], - autoValues: [gridCSSNumber(cssNumber(1), null), gridCSSNumber(cssNumber(2), null)], - }), - ).toEqual([ - gridCSSNumber(cssNumber(42), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(23), null), - gridCSSNumber(cssNumber(2), null), - gridCSSNumber(cssNumber(1), null), - ]) - }) -}) diff --git a/editor/src/components/inspector/flex-section.tsx b/editor/src/components/inspector/flex-section.tsx index 1d94ebbd1ada..3ff686593e13 100644 --- a/editor/src/components/inspector/flex-section.tsx +++ b/editor/src/components/inspector/flex-section.tsx @@ -4,7 +4,7 @@ import { jsx } from '@emotion/react' import React from 'react' import { createSelector } from 'reselect' -import { when } from '../../utils/react-conditionals' +import { unless, when } from '../../utils/react-conditionals' import { Substores, useEditorState, useRefEditorState } from '../editor/store/store-hook' import { AddRemoveLayoutSystemControl } from './add-remove-layout-system-control' import { FlexDirectionToggle } from './flex-direction-control' @@ -35,8 +35,6 @@ import type { CSSKeyword, CSSNumber, GridAutoFlow, - GridCSSKeyword, - GridDiscreteDimension, UnknownOrEmptyInput, ValidGridDimensionKeyword, } from './common/css-utils' @@ -50,10 +48,7 @@ import { isCSSKeyword, isCSSNumber, isEmptyInputValue, - isGridCSSKeyword, isGridCSSNumber, - isGridCSSRepeat, - isValidGridDimensionKeyword, printArrayGridDimensions, type GridDimension, } from './common/css-utils' @@ -66,22 +61,13 @@ import { setProperty, } from '../canvas/commands/set-property-command' import * as PP from '../../core/shared/property-path' -import type { - GridAutoOrTemplateBase, - GridContainerProperties, - GridPosition, -} from '../../core/shared/element-template' +import type { GridContainerProperties, GridPosition } from '../../core/shared/element-template' import { gridPositionValue, type ElementInstanceMetadata, type GridElementProperties, } from '../../core/shared/element-template' -import { - expandGridDimensions, - removeGridTemplateDimensionAtIndex, - replaceGridTemplateDimensionAtIndex, - setGridPropsCommands, -} from '../canvas/canvas-strategies/strategies/grid-helpers' +import { setGridPropsCommands } from '../canvas/canvas-strategies/strategies/grid-helpers' import { type CanvasCommand } from '../canvas/commands/commands' import type { DropdownMenuItem } from '../../uuiui/radix-components' import { @@ -92,7 +78,6 @@ import { separatorRadixSelectOption, } from '../../uuiui/radix-components' import { useInspectorLayoutInfo, useInspectorStyleInfo } from './common/property-path-hooks' -import { NumberOrKeywordControl } from '../../uuiui/inputs/number-or-keyword-control' import { optionalMap } from '../../core/shared/optional-utils' import { cssNumberEqual } from '../canvas/controls/select-mode/controls-common' import type { EditorAction } from '../editor/action-types' @@ -104,6 +89,7 @@ import { useSetHoveredControlsHandlers, } from '../canvas/controls/select-mode/select-mode-hooks' import type { Axis } from '../canvas/gap-utils' +import { GridExpressionInput } from '../../uuiui/inputs/grid-expression-input' const axisDropdownMenuButton = 'axisDropdownMenuButton' @@ -160,44 +146,6 @@ export const FlexSection = React.memo(() => { 'FlexSection grid', ) - const columns = React.useMemo((): GridDiscreteDimension[] => { - const autoCols: GridDimension[] = - grid?.specialSizeMeasurements.containerGridProperties.gridAutoColumns?.type === 'DIMENSIONS' - ? grid.specialSizeMeasurements.containerGridProperties.gridAutoColumns.dimensions - : [] - - const merged = mergeGridTemplateValues({ - autoValues: autoCols, - ...getGridTemplateAxisValues({ - calculated: - grid?.specialSizeMeasurements.containerGridProperties.gridTemplateColumns ?? null, - fromProps: - grid?.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateColumns ?? - null, - }), - }) - - return merged.filter((v) => v.type !== 'REPEAT') - }, [grid]) - - const rows = React.useMemo((): GridDiscreteDimension[] => { - const autoRows: GridDimension[] = - grid?.specialSizeMeasurements.containerGridProperties.gridAutoRows?.type === 'DIMENSIONS' - ? grid.specialSizeMeasurements.containerGridProperties.gridAutoRows.dimensions - : [] - - const merged = mergeGridTemplateValues({ - autoValues: autoRows, - ...getGridTemplateAxisValues({ - calculated: grid?.specialSizeMeasurements.containerGridProperties.gridTemplateRows ?? null, - fromProps: - grid?.specialSizeMeasurements.containerGridPropertiesFromProps.gridTemplateRows ?? null, - }), - }) - - return merged.filter((v) => v.type !== 'REPEAT') - }, [grid]) - return (
@@ -211,13 +159,8 @@ export const FlexSection = React.memo(() => {
- - + +
) : null} , @@ -247,21 +190,13 @@ export const FlexSection = React.memo(() => { ) }) -const gridDimensionDropdownKeywords = [ - { label: 'Auto', value: cssKeyword('auto') }, - { label: 'Min-Content', value: cssKeyword('min-content') }, - { label: 'Max-Content', value: cssKeyword('max-content') }, -] - const TemplateDimensionControl = React.memo( ({ grid, - values, axis, title, }: { grid: ElementInstanceMetadata - values: GridDiscreteDimension[] axis: 'column' | 'row' title: string }) => { @@ -269,6 +204,25 @@ const TemplateDimensionControl = React.memo( const metadataRef = useRefEditorState((store) => store.editor.jsxMetadata) + const values = React.useMemo((): GridDimension[] => { + // TODO: handle gridAutoRows/Cols too + switch (axis) { + case 'row': { + const { gridTemplateRows } = grid.specialSizeMeasurements.containerGridPropertiesFromProps + + return gridTemplateRows?.type === 'DIMENSIONS' ? gridTemplateRows.dimensions : [] + } + case 'column': { + const { gridTemplateColumns } = + grid.specialSizeMeasurements.containerGridPropertiesFromProps + + return gridTemplateColumns?.type === 'DIMENSIONS' ? gridTemplateColumns.dimensions : [] + } + default: + assertNever(axis) + } + }, [grid, axis]) + const template = React.useMemo(() => { const fromProps = axis === 'column' @@ -280,24 +234,15 @@ const TemplateDimensionControl = React.memo( return fromProps }, [grid, axis, values]) - const expandedTemplate = React.useMemo(() => { - if (template?.type !== 'DIMENSIONS') { - return [] - } - return expandGridDimensions(template.dimensions) - }, [template]) - const onUpdateDimension = React.useCallback( (index: number) => (newValue: GridDimension) => { if (template?.type !== 'DIMENSIONS') { return } - const newDimensions = replaceGridTemplateDimensionAtIndex( - template.dimensions, - expandedTemplate, - index, - newValue, - ) + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newDimensions = [...left, newValue, ...right] dispatch([ applyCommandsAction([ @@ -310,7 +255,7 @@ const TemplateDimensionControl = React.memo( ]), ]) }, - [template, expandedTemplate, dispatch, axis, grid], + [template, dispatch, axis, grid], ) const onUpdateNumberOrKeyword = React.useCallback( @@ -350,11 +295,10 @@ const TemplateDimensionControl = React.memo( return } - const newValues = removeGridTemplateDimensionAtIndex( - template.dimensions, - expandedTemplate, - index, - ) + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newValues = [...left, ...right] let commands: CanvasCommand[] = [ setProperty( @@ -367,7 +311,7 @@ const TemplateDimensionControl = React.memo( // adjust the position of the elements if they need to be moved const adjustedGridTemplate = removeTemplateValueAtIndex( - grid.specialSizeMeasurements.containerGridProperties, + grid.specialSizeMeasurements.containerGridPropertiesFromProps, axis, index, ) @@ -377,7 +321,7 @@ const TemplateDimensionControl = React.memo( const children = MetadataUtils.getChildrenUnordered(metadataRef.current, grid.elementPath) for (const child of children) { let updated: Partial = { - ...child.specialSizeMeasurements.elementGridProperties, + ...child.specialSizeMeasurements.elementGridPropertiesFromProps, } function needsAdjusting(pos: GridPosition | null, bound: number) { @@ -389,7 +333,7 @@ const TemplateDimensionControl = React.memo( : null } - const position = child.specialSizeMeasurements.elementGridProperties + const position = child.specialSizeMeasurements.elementGridPropertiesFromProps if (axis === 'column') { const adjustColumnStart = needsAdjusting(position.gridColumnStart, gridIndex) const adjustColumnEnd = needsAdjusting(position.gridColumnEnd, gridIndex + 1) @@ -415,7 +359,7 @@ const TemplateDimensionControl = React.memo( dispatch([applyCommandsAction(commands)]) }, - [grid, dispatch, axis, metadataRef, template, expandedTemplate], + [grid, dispatch, axis, metadataRef, template], ) const onAppend = React.useCallback(() => { @@ -442,7 +386,7 @@ const TemplateDimensionControl = React.memo( if (template?.type !== 'DIMENSIONS') { return } - const container = grid.specialSizeMeasurements.containerGridProperties + const container = grid.specialSizeMeasurements.containerGridPropertiesFromProps const dimensions = axis === 'column' ? container.gridTemplateColumns : container.gridTemplateRows if (dimensions?.type !== 'DIMENSIONS') { @@ -458,12 +402,14 @@ const TemplateDimensionControl = React.memo( const newAreaName: string | null = rawNewAreaName.length === 0 ? null : sanitizeAreaName(rawNewAreaName) - const newValues = replaceGridTemplateDimensionAtIndex( - template.dimensions, - expandedTemplate, - index, - { ...values[index], areaName: newAreaName }, - ) + const left = template.dimensions.slice(0, index) + const right = template.dimensions.slice(index + 1) + + const newValues = [ + ...left, + { ...values[index], areaName: newAreaName } as GridDimension, + ...right, + ] let commands: CanvasCommand[] = [ setProperty( @@ -488,14 +434,14 @@ const TemplateDimensionControl = React.memo( ...setGridPropsCommands( child.elementPath, adjustedGridTemplate, - child.specialSizeMeasurements.elementGridProperties, + child.specialSizeMeasurements.elementGridPropertiesFromProps, ), ) } dispatch([applyCommandsAction(commands)]) }, - [grid, axis, values, dispatch, metadataRef, template, expandedTemplate], + [grid, axis, values, dispatch, metadataRef, template], ) const dropdownMenuItems = React.useCallback( @@ -531,6 +477,8 @@ const TemplateDimensionControl = React.memo( [], ) + const dimensionsWithGeneratedIndexes = useGeneratedIndexesFromGridDimensions(values) + return (
- {values.map((value, index) => ( + {dimensionsWithGeneratedIndexes.map((value, index) => ( { setIsOpen(isDropdownOpen) }, []) + + const title = React.useMemo(() => { + if (indexFrom === indexTo) { + return value.areaName ?? indexFrom + } + return value.areaName ?? `${indexFrom} → ${indexTo}` + }, [value, indexFrom, indexTo]) + + const gridExpressionInputFocused = useGridExpressionInputFocused() + return (
- {value.areaName ?? index + 1} + {title} -
- - - + {unless( + gridExpressionInputFocused.focused, + + + , + )}
) } @@ -702,20 +667,6 @@ function renameAreaInTemplateAtIndex( } } -function getGridTemplateAxisValues(template: { - calculated: GridAutoOrTemplateBase | null - fromProps: GridAutoOrTemplateBase | null -}): { calculated: GridDimension[]; fromProps: GridDimension[] } { - const { calculated, fromProps } = template - if (fromProps?.type !== 'DIMENSIONS' && calculated?.type !== 'DIMENSIONS') { - return { calculated: [], fromProps: [] } - } - - const calculatedDimensions = calculated?.type === 'DIMENSIONS' ? calculated.dimensions : [] - const fromPropsDimensions = fromProps?.type === 'DIMENSIONS' ? fromProps.dimensions : [] - return { calculated: calculatedDimensions, fromProps: fromPropsDimensions } -} - const reAlphanumericDashUnderscore = /[^0-9a-z\-_]+/gi function sanitizeAreaName(areaName: string): string { @@ -1086,49 +1037,45 @@ const AutoFlowControl = React.memo(() => { }) AutoFlowControl.displayName = 'AutoFlowControl' -export function mergeGridTemplateValues({ - calculated, - fromProps, - autoValues, -}: { - calculated: GridDimension[] - fromProps: GridDimension[] - autoValues: GridDimension[] -}): GridDimension[] { - const expanded = fromProps.flatMap((v) => { - if (!isGridCSSRepeat(v)) { - return v - } - if (isCSSKeyword(v.times)) { - // NOTE: this will be removed soon with https://github.com/concrete-utopia/utopia/pull/6396, so no need to go in detail here - return v - } - return Array(v.times).fill(v.value).flat() - }) - - function getExplicitValue(dimension: GridDimension, index: number): GridDimension { - if (expanded.length === 0) { - return gridCSSKeyword(cssKeyword('auto'), dimension.areaName) - } else if (expanded[index] == null) { - return dimension - } else { - return expanded[index] - } - } - - return calculated.map((c, index) => { - const explicitValue = getExplicitValue(c, index) - - const autoValueIndex = index % autoValues.length // wrap around - const autoValue = autoValues.at(autoValueIndex) +interface GridDimensionWithGeneratedIndexes { + generatedIndexFrom: number + generatedIndexTo: number + dimension: GridDimension +} - if (isGridCSSKeyword(explicitValue) && explicitValue.value.value === 'auto') { - return { - ...(autoValue ?? explicitValue), - areaName: explicitValue.areaName ?? autoValue?.areaName ?? null, - } as GridCSSKeyword - } +function useGeneratedIndexesFromGridDimensions( + dimensions: Array, +): Array { + return React.useMemo(() => { + let nextIndexFrom = 1 + let result: Array = [] + + dimensions.forEach((dim) => { + if (dim.type !== 'REPEAT') { + result.push({ + generatedIndexFrom: nextIndexFrom, + generatedIndexTo: nextIndexFrom, + dimension: dim, + }) + nextIndexFrom += 1 + } else { + // TODO: handle auto-fill and auto-fit in value.times + const shift = !isCSSKeyword(dim.times) ? dim.times * dim.value.length : dim.value.length + result.push({ + generatedIndexFrom: nextIndexFrom, + generatedIndexTo: nextIndexFrom + shift - 1, + dimension: dim, + }) + nextIndexFrom += shift + } + }) + return result + }, [dimensions]) +} - return explicitValue - }) +const useGridExpressionInputFocused = () => { + const [focused, setFocused] = React.useState(false) + const onFocus = React.useCallback(() => setFocused(true), []) + const onBlur = React.useCallback(() => setFocused(false), []) + return { focused, onFocus, onBlur } } diff --git a/editor/src/uuiui/inputs/grid-expression-input.tsx b/editor/src/uuiui/inputs/grid-expression-input.tsx new file mode 100644 index 000000000000..d3c0217663f9 --- /dev/null +++ b/editor/src/uuiui/inputs/grid-expression-input.tsx @@ -0,0 +1,87 @@ +import React from 'react' +import { + cssKeyword, + isGridCSSNumber, + isValidGridDimensionKeyword, + parseCSSNumber, + parseGridCSSMinmaxOrRepeat, + printGridDimension, + type CSSKeyword, + type CSSNumber, + type GridDimension, + type ValidGridDimensionKeyword, +} from '../../components/inspector/common/css-utils' +import { isRight } from '../../core/shared/either' +import { StringInput } from './string-input' + +interface GridExpressionInputProps { + testId: string + value: GridDimension + onUpdateNumberOrKeyword: (v: CSSNumber | CSSKeyword) => void + onUpdateDimension: (v: GridDimension) => void + onFocus: () => void + onBlur: () => void +} + +export const GridExpressionInput = React.memo( + ({ + testId, + value, + onUpdateNumberOrKeyword, + onUpdateDimension, + onFocus, + onBlur, + }: GridExpressionInputProps) => { + const [printValue, setPrintValue] = React.useState(printGridDimension(value)) + React.useEffect(() => setPrintValue(printGridDimension(value)), [value]) + + const onChange = React.useCallback((e: React.ChangeEvent) => { + setPrintValue(e.target.value) + }, []) + + const onKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.key !== 'Enter') { + return + } + if (isValidGridDimensionKeyword(printValue)) { + return onUpdateNumberOrKeyword(cssKeyword(printValue)) + } + + const defaultUnit = isGridCSSNumber(value) ? value.value.unit : 'px' + const maybeNumber = parseCSSNumber(printValue, 'AnyValid', defaultUnit) + if (isRight(maybeNumber)) { + return onUpdateNumberOrKeyword(maybeNumber.value) + } + + const maybeMinmax = parseGridCSSMinmaxOrRepeat(printValue) + if (maybeMinmax != null) { + return onUpdateDimension({ + ...maybeMinmax, + areaName: value.areaName, + } as GridDimension) + } + + if (printValue === '') { + return onUpdateNumberOrKeyword(cssKeyword('auto')) + } + + setPrintValue(printGridDimension(value)) + }, + [printValue, onUpdateNumberOrKeyword, onUpdateDimension, value], + ) + + return ( + + ) + }, +) +GridExpressionInput.displayName = 'GridExpressionInput' diff --git a/editor/src/uuiui/inputs/string-input.tsx b/editor/src/uuiui/inputs/string-input.tsx index e6f0b3b2b23e..c38c162bb491 100644 --- a/editor/src/uuiui/inputs/string-input.tsx +++ b/editor/src/uuiui/inputs/string-input.tsx @@ -183,7 +183,7 @@ export const HeadlessStringInput = React.forwardRef(null) @@ -237,6 +237,7 @@ export const HeadlessStringInput = React.forwardRef