Skip to content

Commit

Permalink
Parse grid spans, use csstree for placement parsing (#6629)
Browse files Browse the repository at this point in the history
**Problem:**

1. Parsing an element placing does not use `csstree`
2. Spans are not parsed nor represented

This is a purely-parsing PR, laying the groundwork to have strategies
and interactions play nicely with spanning items.

**Fix:**

1. Use `csstree` to parse grid placement
2. Parse `span` coming form parsing props

Fixes #6628
  • Loading branch information
ruggi authored Nov 12, 2024
1 parent a668e00 commit 2daa9a7
Show file tree
Hide file tree
Showing 9 changed files with 518 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import * as EP from '../../../../core/shared/element-path'
import type {
ElementInstanceMetadataMap,
GridAutoOrTemplateBase,
GridPositionOrSpan,
GridPositionValue,
GridSpan,
SpecialSizeMeasurements,
} from '../../../../core/shared/element-template'
import {
isGridSpan,
type ElementInstanceMetadata,
type GridContainerProperties,
type GridElementProperties,
type GridPosition,
} from '../../../../core/shared/element-template'
import type { CanvasRectangle } from '../../../../core/shared/math-utils'
import * as PP from '../../../../core/shared/property-path'
Expand Down Expand Up @@ -42,10 +44,25 @@ import {
gridCellCoordinates,
} from './grid-cell-bounds'

export function gridPositionToValue(p: GridPosition | null | undefined): string | number | null {
export function gridPositionToValue(
p: GridPositionOrSpan | null | undefined,
spanOffset: GridPositionOrSpan | null,
): string | number | null {
if (p == null) {
return null
}

const offset = isGridSpan(spanOffset) && spanOffset.type === 'SPAN_NUMERIC' ? spanOffset.value : 0

if (isGridSpan(p)) {
switch (p.type) {
case 'SPAN_AREA':
return null // # TODO fill this in once we support grid areas
case 'SPAN_NUMERIC':
return p.value + offset
}
}

if (isCSSKeyword(p)) {
return p.value
}
Expand All @@ -69,10 +86,10 @@ export function setGridPropsCommands(
PP.create('style', 'gridRowEnd'),
]),
]
const columnStart = gridPositionToValue(gridProps.gridColumnStart)
const columnEnd = gridPositionToValue(gridProps.gridColumnEnd)
const rowStart = gridPositionToValue(gridProps.gridRowStart)
const rowEnd = gridPositionToValue(gridProps.gridRowEnd)
const columnStart = gridPositionToValue(gridProps.gridColumnStart, null)
const columnEnd = gridPositionToValue(gridProps.gridColumnEnd, gridProps.gridColumnStart ?? null)
const rowStart = gridPositionToValue(gridProps.gridRowStart, null)
const rowEnd = gridPositionToValue(gridProps.gridRowEnd, gridProps.gridRowStart ?? null)

const lineColumnStart = asMaybeNamedLineOrValue(gridTemplate, 'column', columnStart)
const lineColumnEnd = asMaybeNamedLineOrValue(gridTemplate, 'column', columnEnd)
Expand Down Expand Up @@ -149,7 +166,7 @@ function getGridChildCellCoordBoundsFromProps(
// get the grid fixtures (start and end for column and row) from the element metadata
function getGridProperty(field: keyof GridElementProperties, innerFallback: number) {
const propValue = element.specialSizeMeasurements.elementGridProperties[field]
if (propValue == null || isCSSKeyword(propValue)) {
if (propValue == null || isCSSKeyword(propValue) || isGridSpan(propValue)) {
return innerFallback
}
return propValue.numericalPosition ?? innerFallback
Expand Down Expand Up @@ -215,8 +232,19 @@ export function sortElementsByGridPosition(gridTemplateColumns: number) {
return index
}

const row = e.gridRowStart.numericalPosition ?? 1
const column = e.gridColumnStart.numericalPosition ?? 1
function maybeNumericalValue(dim: GridSpan | GridPositionValue) {
return isGridSpan(dim)
? dim.type === 'SPAN_NUMERIC'
? dim.value
: null
: dim.numericalPosition
}

const start = maybeNumericalValue(e.gridRowStart)
const end = maybeNumericalValue(e.gridColumnStart)

const row = start ?? 1
const column = end ?? 1

return (row - 1) * gridTemplateColumns + column - 1
}
Expand All @@ -225,8 +253,8 @@ export function sortElementsByGridPosition(gridTemplateColumns: number) {
}
}

function isGridPositionNumericValue(p: GridPosition | null): p is GridPositionValue {
return p != null && !(isCSSKeyword(p) && p.value === 'auto')
function isGridPositionNumericValue(p: GridPositionOrSpan | null): p is GridPositionValue {
return p != null && !(isCSSKeyword(p) && !isGridSpan(p) && p.value === 'auto')
}

export function getGridPositionIndex(props: {
Expand Down
65 changes: 33 additions & 32 deletions editor/src/components/canvas/controls/grid-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import * as EP from '../../../core/shared/element-path'
import type {
ElementInstanceMetadataMap,
GridAutoOrTemplateDimensions,
GridPositionOrSpan,
} from '../../../core/shared/element-template'
import {
isGridAutoOrTemplateDimensions,
isGridSpan,
type GridAutoOrTemplateBase,
} from '../../../core/shared/element-template'
import type { CanvasPoint, CanvasRectangle, LocalRectangle } from '../../../core/shared/math-utils'
Expand All @@ -30,7 +32,6 @@ import {
pointsEqual,
scaleRect,
windowPoint,
zeroCanvasRect,
zeroRectIfNullOrInfinity,
} from '../../../core/shared/math-utils'
import {
Expand All @@ -47,19 +48,9 @@ import { Modifier } from '../../../utils/modifiers'
import { when } from '../../../utils/react-conditionals'
import { useColorTheme, UtopiaStyles } from '../../../uuiui'
import { useDispatch } from '../../editor/store/dispatch-context'
import { Substores, useEditorState, useRefEditorState } from '../../editor/store/store-hook'
import type { GridDimension, GridDiscreteDimension } from '../../inspector/common/css-utils'
import {
Substores,
useEditorState,
useRefEditorState,
useSelectorWithCallback,
} from '../../editor/store/store-hook'
import type {
CSSNumber,
GridDimension,
GridDiscreteDimension,
} from '../../inspector/common/css-utils'
import {
cssNumberToString,
isCSSKeyword,
isDynamicGridRepeat,
isGridCSSRepeat,
Expand Down Expand Up @@ -111,7 +102,7 @@ import { useResizeEdges } from './select-mode/use-resize-edges'
import { getGridHelperStyleMatchingTargetGrid } from './grid-controls-helpers'
import { isFillOrStretchModeAppliedOnSpecificSide } from '../../inspector/inspector-common'
import type { PinOutlineProps } from './position-outline'
import { PinOutline, PositionOutline, usePropsOrJSXAttributes } from './position-outline'
import { PinOutline, usePropsOrJSXAttributes } from './position-outline'
import { getLayoutProperty } from '../../../core/layout/getLayoutProperty'
import { styleStringInArray } from '../../../utils/common-constants'

Expand Down Expand Up @@ -755,22 +746,24 @@ const GridControl = React.memo<GridControlProps>(({ grid, controlsVisible }) =>

const columnFromProps = cell.specialSizeMeasurements.elementGridProperties.gridColumnStart
const rowFromProps = cell.specialSizeMeasurements.elementGridProperties.gridRowStart

function getAxisValue(value: GridPositionOrSpan | null, counted: number): number {
if (
value == null ||
isCSSKeyword(value) ||
isGridSpan(value) ||
value.numericalPosition == null
) {
return counted
}
return value.numericalPosition
}
return {
elementPath: cell.elementPath,
globalFrame: cell.globalFrame,
borderRadius: cell.specialSizeMeasurements.borderRadius,
column:
columnFromProps == null
? countedColumn
: isCSSKeyword(columnFromProps)
? countedColumn
: columnFromProps.numericalPosition ?? countedColumn,
row:
rowFromProps == null
? countedRow
: isCSSKeyword(rowFromProps)
? countedRow
: rowFromProps.numericalPosition ?? countedRow,
column: getAxisValue(columnFromProps, countedColumn),
row: getAxisValue(rowFromProps, countedRow),
index: index,
}
}, children)
Expand Down Expand Up @@ -1933,15 +1926,23 @@ const GridElementContainingBlock = React.memo<GridElementContainingBlockProps>((
const gridComputed = childMetadata.specialSizeMeasurements.elementGridProperties
return {
gridColumnStart:
gridPositionToValue(gridFromProps.gridColumnStart ?? gridComputed.gridColumnStart) ??
undefined,
gridPositionToValue(
gridFromProps.gridColumnStart ?? gridComputed.gridColumnStart,
null,
) ?? undefined,
gridColumnEnd:
gridPositionToValue(gridFromProps.gridColumnEnd ?? gridComputed.gridColumnEnd) ??
undefined,
gridPositionToValue(
gridFromProps.gridColumnEnd ?? gridComputed.gridColumnEnd,
gridFromProps.gridColumnStart ?? gridComputed.gridColumnStart,
) ?? undefined,
gridRowStart:
gridPositionToValue(gridFromProps.gridRowStart ?? gridComputed.gridRowStart) ?? undefined,
gridPositionToValue(gridFromProps.gridRowStart ?? gridComputed.gridRowStart, null) ??
undefined,
gridRowEnd:
gridPositionToValue(gridFromProps.gridRowEnd ?? gridComputed.gridRowEnd) ?? undefined,
gridPositionToValue(
gridFromProps.gridRowEnd ?? gridComputed.gridRowEnd,
gridFromProps.gridRowStart ?? gridComputed.gridRowStart,
) ?? undefined,
position: childMetadata.specialSizeMeasurements.position ?? undefined,
}
},
Expand Down
89 changes: 40 additions & 49 deletions editor/src/components/canvas/dom-walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
DomElementMetadata,
GridAutoOrTemplateBase,
BorderWidths,
GridPositionOrSpan,
} from '../../core/shared/element-template'
import {
specialSizeMeasurements,
Expand All @@ -19,6 +20,7 @@ import {
gridAutoOrTemplateFallback,
domElementMetadata,
gridAutoOrTemplateDimensions,
isGridSpan,
} from '../../core/shared/element-template'
import type { ElementPath } from '../../core/shared/project-file-types'
import type { ElementCanvasRectangleCache } from '../../core/shared/dom-utils'
Expand Down Expand Up @@ -630,62 +632,51 @@ function getGridElementProperties(
container: GridContainerProperties,
elementStyle: CSSStyleDeclaration,
): GridElementProperties {
function getPlacementPin(
value: GridPositionOrSpan | null,
axis: 'row' | 'column',
pin: 'start' | 'end',
style: string,
) {
if (isGridSpan(value) || value != null) {
return value
}
return defaultEither(null, parseGridPosition(container, axis, pin, value ?? null, style))
}

const gridColumn = defaultEither(
null,
parseGridRange(container, 'column', elementStyle.gridColumn),
)

const gridColumnStart =
gridColumn?.start ??
defaultEither(
null,
parseGridPosition(
container,
'column',
'start',
gridColumn?.start ?? null,
elementStyle.gridColumnStart,
),
) ??
null
const gridColumnEnd =
gridColumn?.end ??
defaultEither(
null,
parseGridPosition(
container,
'column',
'end',
gridColumn?.end ?? null,
elementStyle.gridColumnEnd,
),
) ??
null
const gridColumnStart = getPlacementPin(
gridColumn?.start ?? null,
'column',
'start',
elementStyle.gridColumnStart,
)
const gridColumnEnd = getPlacementPin(
gridColumn?.end ?? null,
'column',
'end',
elementStyle.gridColumnEnd,
)
const adjustedColumnEnd =
isCSSKeyword(gridColumnEnd) && gridColumn?.end != null ? gridColumn.end : gridColumnEnd
isGridSpan(gridColumn?.end) || (isCSSKeyword(gridColumnEnd) && gridColumn?.end != null)
? gridColumn.end
: gridColumnEnd

const gridRow = defaultEither(null, parseGridRange(container, 'row', elementStyle.gridRow))
const gridRowStart =
gridRow?.start ??
defaultEither(
null,
parseGridPosition(
container,
'row',
'start',
gridRow?.start ?? null,
elementStyle.gridRowStart,
),
) ??
null
const gridRowEnd =
gridRow?.end ??
defaultEither(
null,
parseGridPosition(container, 'row', 'end', gridRow?.end ?? null, elementStyle.gridRowEnd),
) ??
null
const adjustedRowEnd = isCSSKeyword(gridRowEnd) && gridRow?.end != null ? gridRow.end : gridRowEnd
const gridRowStart = getPlacementPin(
gridRow?.start ?? null,
'row',
'start',
elementStyle.gridRowStart,
)
const gridRowEnd = getPlacementPin(gridRow?.end ?? null, 'row', 'end', elementStyle.gridRowEnd)
const adjustedRowEnd =
isGridSpan(gridRow?.end) || (isCSSKeyword(gridRowEnd) && gridRow?.end != null)
? gridRow.end
: gridRowEnd

const result = gridElementProperties(
gridColumnStart,
Expand Down
Loading

0 comments on commit 2daa9a7

Please sign in to comment.