drag & drop next steps
See original GitHub issueVisual editor
This RFC tries to specify a paradigm for our visual editor. For internal tooling we want something that allows for flexible layout, yet is constrained enough to require minimal manual tweaking.
Regular components
- All components conform to the width given to them by the layout algorithm. For some components this means they will have to centre themselves in a box (e.g. a button my take width from its content, which may be more narrow than the available width) some components will have an option to expand to the full width of their box (e.g.
fullWidth
property onTextField
). Other components will horizontally adapt to any width imposed on them (e.g. image, datagrid) - All components define the height they want to be rendered at. For some components this will be always the same, e.g. textinputs have a fixed height. And for some it will be configurable. e.g. data grid or image
Layout components
In terms of layout we identify two types of components, columns and rows.
column
a column works vertically. It acts as a component on the horizontal axis (adapt to the width imposed to it by the layout algorithm). It serves as the container for rows. Its only possible children are rows and its only possible parent is a row. A only needs to be manually created when the user wants to build something like a sidebar.
- A column only has rows as its children. Dropping a component on a column where there is no row, implicitly creates a row. Removing the last child from a row, removes that row from the page.
rows
Rows are never created explicitly, they are implicitly created depending on the drop position of certain elements
- A row has the same width as the column it’s under.
- A row is a 12 column grid layout into which its children take their place.
- A child of a row takes up a certain amount of columns (span). The user can adjust the span of an element with drag handles on the left and right side of the element.
page
On the top level, a page acts as a column
Drop algorithm
We must avoid layout shifting as much as possible, therefor we will not live preview elements as they are dragged on the page. Dragging a new element only highlights cursors as available drop targets. Dragging an existing element, to move it around, stays in place until the drop is performed.
The biggest challenge of drag and drop visual editor is to calculate a predictable insertion position for the drop target. I propose the following algorithm:
- around top level rows there is a 5px (?) band which always takes precedence as a drop target to create a new row that contains the element being dragged. We may shift the page rows apart by the same gap during dragging if this can be done with minimal layout shift.
For any other [X,Y] mouse position during the drag
- find the most deeply nested component under the mouse
- divide the component into 4 quadrants by connecting its corners diagonally, determine which quadrant the mouse is over to determine the side of the box that will be the drop target.
- is it left or right: insert a new component to the left of the right within the same parent row as the one of the element that’s hovered (TODO: what if there’s no more space left in the row, a.k.a. all of its columns are occupied? Disable the drop at this location?)
- is it top or bottom:
- If the element is the only child of its row, add another row above or below that row with the dropped element as its only child
- If the element has siblings in its row, replace it with a new column containing two rows, one with the original hovered element and one with the dropped element
- TODO: we need to special case moving around an element within its own row.
This algorithm takes care of the most common situations, top level inserts and inserting at the deepest level. In very deeply nested layouts it may be necessary to insert an element in between two nested layout components. I expect this to be an edge case for internal applications, but it can be solved by designating a keyboard modifier (shift or Cmd) that opens a context menu with all the container components in the same axis which can then be hovered to specify the exact insert position.
Resizing components
Should changing the width of a component or moving it around in its row be constrained by the available columns. Or should rows be allowed to overflow. Will overflow behavior be necessary to allow responsive design? (e.g. width = 3 columns when lg, 6 columns when sm,…)
Benchmark
This RFC borrows some ideas from makeswift.com. Stronlgy encouraged to familiarize yourself with its editor canvas implementation.
considerations
- make column count configurable? count, gap,…
- on the component level, we could mandate a minimum span?
- For elements that have configurable height we can add a drag handle at the bottom, but that doesn’t have to be part of this RFC
- For top level rows we may want to add an option to constrain the width, to create a fluid responsive container
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (11 by maintainers)
I guess https://github.com/mui/mui-toolpad/issues/359 will be a part of this enhancement?
Closing this issue as basic functionality for everything mentioned here seems done to me, even though there’s lots of room to improve these features of course.