Rich, web-friendly editor UI framework for SolidJS + TypeScript
solid-stage requests section-preferred-width.md
6.4 kB · 117 lines
Markdown
at main

Feature request: sections with their own width in floating panels #

  • Status: RESOLVED (2026-11-02), with a simpler design than proposed below. No new SectionDef fields: a section is designed for its home panel, so its width is that panel's defaultLeftWidth/defaultRightWidth, or the library default. A new floating panel opens at that width. The widest designed width is a hard minimum for the panel, and nothing shrinks a panel by itself, so a resize drag can only narrow one once its widest section has left. The viewer's info sections get their width from its existing defaultRightWidth with no code change. See docs/dnd.md.
  • Component: layout/sectionLayout.ts, layout/StageFloatHost.tsx; docs in docs/dnd.md
  • Pulled by: dsts-viewer (its info sections are designed for 360px, its controls for the default panel width)
  • Severity: medium. Sections end up at widths they weren't designed for, and stay there
  • Date: 2026-11-01

What happens today #

Found by hand in the viewer, with sections of different widths:

  1. Merging: dragging one floating panel onto another makes the arrivals take the target's width, however narrow, which can squeeze them into a layout they were never designed for.
  2. Pulling apart: a section pulled off a floating panel stays at the width of the panel it left, even though it's now free to return to its own size.

Cause #

A section has no width of its own. SectionDef has no width field, so width exists only on the container: a side panel's track, or FloatingPanel.w.

  • onDocDragEnd seeds every new floating panel with the width the section measured at the moment of the drag: layout.floatOut(id, x, y, sectionEl.getBoundingClientRect().width). That avoids a jump when leaving a side panel, but can't tell a section's natural width from one it only had because of the panel it was in.
  • dockPanel moves the sections and prunes the source panel, with no width logic. The target keeps its width and the arrivals fill it.
  • move into a floating dock behaves the same way.

Proposed API #

Additive on SectionDef. Omitting both keeps today's behavior, so existing consumers don't change.

interface SectionDef {
  // ...existing
  /** Width (px) this section is designed for when floating. */
  readonly preferredWidth?: number;
  /** Narrowest width (px) its content works at. Floors any floating panel holding it. */
  readonly minWidth?: number;
}

The names are suggestions.

Proposed behavior #

Derive a floating panel's width rather than copying it around, so that merging and pulling apart need no special handling:

  • FloatingPanel.w means the user resized this panel. Only a resize drag sets it for sections with a preferredWidth.
  • With no w, a panel is as wide as the widest preferredWidth of its sections, falling back to the default CSS width when none has one.
  • With a w, that width stands, even when a section with a larger preferredWidth joins.
  • Either way, the panel never goes below the largest minWidth of its sections. This also floors the resize drag, in place of MIN_FLOAT_WIDTH where it is larger.
Action Result
Pull a section off a side panel Opens at its preferredWidth
Pull a section off a floating panel Opens at its preferredWidth; the old panel's resize doesn't follow it
Merge two unresized panels Takes the widest preferredWidth of the combined sections
Merge into a resized panel Keeps its width, raised if needed to fit each minWidth
A wide section leaves an unresized panel The panel narrows to fit the sections left

A section with no preferredWidth keeps today's seeding when it leaves a side panel, so it doesn't jump in width. See question 1 for one leaving a floating panel.

Design questions #

  1. Where does a section with no preferredWidth go when pulled off a floating panel? Today it keeps the panel's width, which is the second bug above. Recommended: treat its home dock's width as its natural width when that dock can be measured, otherwise the default CSS width. The home dock is already the place redock sends it, so this fits the model. If that's too costly, the default CSS width alone is still better than keeping a leftover width.
  2. Does w stay set for resized panels after every section but one has left? Recommended: yes. The user sized that panel on purpose, and a new floating panel is what starts fresh.

Details to get right #

  • Persistence: w is already persisted, so a stored width from before this change reads as a user resize. That's harmless, and a consumer can bump version to discard it. A derived width shouldn't be stored at all, so a changed preferredWidth in code takes effect on next load.
  • Region limits: a minWidth or preferredWidth wider than the float region must not push the panel off-screen. max-width: 100% already caps it, and that cap should win.
  • No width jump on drag: the drag image is built at the section's docked width, and the panel lands at preferredWidth, so a section whose two widths differ will change width on drop. That's expected, but the grab offset should still keep the panel's top-left where the ghost's was.
  • Reactivity: a derived width should follow membership changes without a remount, as sections arrive or leave.

Acceptance #

  • Merging panels with sections of different preferredWidths gives the widest, and pulling one back off returns each to its own width.
  • A panel resized by hand keeps that width through a merge, but is raised to fit a minWidth.
  • A resize drag can't go below the largest minWidth of the panel's sections.
  • Sections with neither field behave as they do today when leaving a side panel.
  • InfoIcon, field validation and everything else outside floating panels is unaffected.
  • docs/dnd.md documents both fields, and tests cover the table above.

Out of scope #

  • Side panel widths. These stay the consumer's call, and a section docked in a panel narrower than its minWidth isn't widened by this request. In the viewer that's a real case: the digivolution tree needs about 240px and the controls panel is 230px, so docking it there clips it. That's worth a separate look if it matters in practice.