Skip to content

Support minEmptyWidth for DropZone to keep empty horizontal slots visible #1649

@shusann01116

Description

@shusann01116

Description

DropZone exposes minEmptyHeight so an empty slot keeps a visible height when the parent container is laid out as a column. There is no equivalent for width, so an empty DropZone placed inside a row-oriented parent collapses to width: 0 and becomes invisible and unclickable — even when its height is preserved by minEmptyHeight.

Users who use DropZone as the flex container of a layout component (for example a generic Box block whose flex-direction is configurable) hit this the moment the author chooses row: the first drop target has no area to hit, and any empty children already placed disappear from view. The expected outcome is that DropZone keeps an empty slot visible on both axes, regardless of flex-direction / collisionAxis.

Minimal repro

A custom block that makes DropZone the flex container with a configurable direction:

const BoxConfig: ComponentConfig<{ direction: 'row' | 'column'; children: Slot }> = {
  fields: {
    direction: {
      type: 'radio',
      options: [
        { label: 'row', value: 'row' },
        { label: 'column', value: 'column' },
      ],
    },
    children: { type: 'slot' },
  },
  defaultProps: { direction: 'row', children: [] },
  render: ({ children: Content, direction }) => (
    <Content
      as="div"
      collisionAxis={direction === 'row' ? 'x' : 'y'}
      minEmptyHeight={64}
      style={{ display: 'flex', flexDirection: direction, gap: 8 }}
    />
  ),
};
  1. Drop a Box with direction: 'row'.
  2. Drop empty Boxes inside it.
  3. The nested zones render at 64px × 0px--min-empty-height works, but nothing keeps the width, so they are invisible and have no clickable area.

Considerations

  • Closely related to Support CSS units for minEmptyHeight (px, %, vh, …) #1226 (closed) and the merged PR Support css units for minEmptyHeight #1298, which added CSS-unit support to minEmptyHeight. A width counterpart would benefit from the same treatment so that percent / viewport units work from day one.
  • --min-empty-height is already emitted by the DropZone render and consumed by the built-in DropZone styles. A width counterpart can follow the same wiring.
  • Relevant source areas (inferred):
    • the DropZone component that applies --min-empty-height inline style
    • the DropZone stylesheet that applies min-height: var(--min-empty-height)
  • Browser-side workarounds exist (CSS :has()), but they require Chrome 105+ / Safari 15.4+ / Firefox 121+ and must reach into Puck internals (data-puck-dropzone / data-puck-component attributes), so a prop-level fix is strictly better.

Proposals

Proposal 1 — add a minEmptyWidth prop that mirrors minEmptyHeight

Add minEmptyWidth?: number | string to DropZoneProps, emit --min-empty-width from the DropZone render, and apply min-width: var(--min-empty-width, 0) in the built-in DropZone styles.

<Content
  as="div"
  collisionAxis="x"
  minEmptyHeight={64}
  minEmptyWidth={64}   // NEW
  style={{ display: 'flex', flexDirection: 'row' }}
/>

Pros:

  • Symmetric with minEmptyHeight; easy to discover.
  • No new concept — existing users already know how to pair it with collisionAxis.
  • Re-uses the CSS-unit parsing added in Support css units for minEmptyHeight #1298.

Cons:

  • Two related props (minEmptyHeight / minEmptyWidth) instead of one. See Proposal 2 for an alternative.

Proposal 2 — expose a single minEmpty that covers both axes

Collapse the two into one prop:

minEmpty={64}                  // both axes
minEmpty={{ x: 64, y: '8vh' }} // axis-specific

Pros: smaller surface. Cons: deprecates minEmptyHeight, larger change, a bit more indirection to learn.

Workaround (current)

For reference, our consumer emits a custom CSS variable --box-min-empty-width from render and uses a :has()-gated rule to apply min-width only when the DropZone has no child [data-puck-component] children. It works but depends on Puck's internal attributes and the :has() selector.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions