Replace font-size slider with a numeric input + preset dropdown #159

Open
opened 2026-05-06 11:19:38 +00:00 by AhmedHanafy725 · 3 comments
Member

The text font-size control in the selection toolbar is a <input type="range"> slider via _buildRange(min, max, value, unit, onChange). Sliders are imprecise and capped at a max (currently 72/48/36 depending on object type). Users want to type an exact value or pick from common preset sizes.

Expected

Replace the slider with a compact control similar to typical word processors:

  • A small numeric <input> showing the current size (typeable, with up/down chevrons or stepper buttons).
  • A dropdown with preset sizes (10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288) — user can click one to apply, or type any custom value into the input.
  • The control should look like the rest of the property panel widgets (theme-aware, rounded, ~28px tall) and not exceed the existing slider footprint.

Actual

A wide HTML range slider with a small "px" label. Imprecise, max-capped, no quick presets, no way to type a value.

Notes

The slider helper _buildRange lives in crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js. Font-size call sites:

  • _renderSticky (~line 842) — range 10..72
  • _renderText (~line 873) — range 10..72
  • _renderShape (~line 955) — range 10..48
  • _renderDocument (~line 1044) — range 10..36

Add a new _buildSizePicker(value, onChange, presets) helper (or _buildNumberWithPresets) that renders the desired control, and replace those 4 call sites. Other _buildRange callers (e.g. line thickness, opacity if any) should keep using the slider — only font size moves to the new control.

The preset list should be the standard one shown in the screenshot: [10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288].

The text font-size control in the selection toolbar is a `<input type="range">` slider via `_buildRange(min, max, value, unit, onChange)`. Sliders are imprecise and capped at a max (currently 72/48/36 depending on object type). Users want to type an exact value or pick from common preset sizes. ## Expected Replace the slider with a compact control similar to typical word processors: - A small numeric `<input>` showing the current size (typeable, with up/down chevrons or stepper buttons). - A dropdown with preset sizes (10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288) — user can click one to apply, or type any custom value into the input. - The control should look like the rest of the property panel widgets (theme-aware, rounded, ~28px tall) and not exceed the existing slider footprint. ## Actual A wide HTML range slider with a small "px" label. Imprecise, max-capped, no quick presets, no way to type a value. ## Notes The slider helper `_buildRange` lives in `crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js`. Font-size call sites: - `_renderSticky` (~line 842) — range 10..72 - `_renderText` (~line 873) — range 10..72 - `_renderShape` (~line 955) — range 10..48 - `_renderDocument` (~line 1044) — range 10..36 Add a new `_buildSizePicker(value, onChange, presets)` helper (or `_buildNumberWithPresets`) that renders the desired control, and replace those 4 call sites. Other `_buildRange` callers (e.g. line thickness, opacity if any) should keep using the slider — only font size moves to the new control. The preset list should be the standard one shown in the screenshot: `[10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288]`.
Author
Member

Implementation Spec

Audit

_buildRange(min, max, value, suffix, onInput, tooltip) is used in 7 places — only the 4 font-size sites switch to the new control. The 3 stroke-width sites keep the slider:

Line Caller Range Property Replace?
842 _renderSticky 10..72 px font size YES
873 _renderText 10..72 px font size YES
940 _renderShape 1..8 px stroke width NO
955 _renderShape 10..48 px font size YES
988 _renderDrawing 1..20 px stroke width NO
1044 _renderDocument 10..36 px font size YES
1358 _renderConnector 1..10 px stroke width NO

Reuses existing infra: _openPopover for viewport-anchored popovers (preserves 70820da), wb-pt-popover styling, theme variables.

Helper signature

_buildSizePicker(value, onChange, opts)
//   opts = { presets, min, max, step, suffix, tooltip }
//   defaults: presets=[10,12,14,18,24,36,48,64,80,144,288], min=1, max=999, step=1, suffix='px', tooltip='Font size'

Purpose-built name (mirrors _buildAlignTrigger/_buildFontTrigger/_buildColorTrigger). Call sites collapse to _buildSizePicker(fontSize, onChange).

Files to Modify

  1. crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js — new helper + sweep 4 call sites.
  2. crates/hero_whiteboard_ui/static/web/css/selection_toolbar.css — append new rules.

_buildRange stays — still used by 3 stroke-width callers.

DOM

Trigger:

<span class="wb-pt-size-picker" role="group" aria-label="Font size">
  <input type="number" class="wb-pt-size-input" min="1" max="999" step="1" value="18"
         inputmode="numeric" aria-label="Font size" title="Font size">
  <button type="button" class="wb-pt-size-presets-btn" aria-haspopup="listbox" title="Font size presets">
    <i class="bi bi-chevron-down"></i>
  </button>
</span>

Uses the input's native spinner (no custom up/down chevrons). Total ~78px wide, comparable to the slider.

Popover:

<div class="wb-pt-popover wb-pt-size-popover">
  <div class="wb-pt-size-presets" role="listbox">
    <button class="wb-pt-size-preset" data-value="10">10</button>
    ... 12, 14, 18, 24, 36, 48, 64, 80, 144, 288 ...
  </div>
</div>

The preset matching the current input value gets is-selected.

Behavior

  • Type a value → live onChange on every keystroke (matches slider parity).
  • Native spinner / ArrowUp / ArrowDown step by 1 live.
  • Enter commits + clamps + blurs.
  • Escape with popover open → close popover. Escape on input → revert to last-committed value.
  • Click chevron → open preset list. Click a preset → commit that value, close popover.
  • Outside-click closes popover via existing _openPopover infrastructure.
  • Validation: parseInt; NaN → last valid; clamp to [min, max] (1..999 default) on commit.

CSS additions

.wb-pt-size-picker { display: inline-flex; align-items: center; gap: 2px; }

.wb-pt-size-input {
  width: 52px; height: 28px; padding: 2px 4px;
  background: var(--wb-toolbar-bg); border: 1px solid var(--wb-border);
  border-radius: 6px 0 0 6px; color: var(--wb-text);
  font-size: 12px; text-align: center; outline: none;
  -moz-appearance: textfield;
}
.wb-pt-size-input:focus { border-color: var(--wb-primary); z-index: 1; }

.wb-pt-size-presets-btn {
  width: 22px; height: 28px; padding: 0;
  background: var(--wb-toolbar-bg); border: 1px solid var(--wb-border); border-left-width: 0;
  border-radius: 0 6px 6px 0; color: var(--wb-text-muted); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.wb-pt-size-presets-btn:hover { background: var(--wb-hover); color: var(--wb-text); }
.wb-pt-size-presets-btn .bi { font-size: 12px; line-height: 1; }

.wb-pt-size-presets { display: flex; flex-direction: column; gap: 2px; max-height: 240px; overflow-y: auto; min-width: 80px; }
.wb-pt-size-preset {
  text-align: left; padding: 4px 10px;
  background: transparent; border: 0; border-radius: 4px;
  color: var(--wb-text); cursor: pointer; font-size: 13px;
}
.wb-pt-size-preset:hover { background: var(--wb-hover); }
.wb-pt-size-preset.is-selected { background: var(--wb-primary); color: var(--wb-primary-text); }

Call-site sweep

Replace each font-size _buildRange(...) with _buildSizePicker(fontSize, onChange). The previous min/max caps (72/48/36) are dropped — the user can now type up to 999. Stroke-width sites at lines 940, 988, 1358 stay on _buildRange.

Acceptance Criteria

  • Sticky/text/shape/document font size: number input + chevron button, no slider.
  • Type a number + Enter → applies + syncs.
  • Native spinner / ArrowUp/Down steps by 1 live.
  • Chevron opens popover with exactly: 10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288. Current value highlighted.
  • Click preset → applies, closes popover.
  • Outside-click + Escape close the popover.
  • Empty/NaN/out-of-range input clamped to [1, 999] on commit; no NaN downstream.
  • Stroke width still slider in shape/drawing/connector.
  • Light/dark theme: control consistent in both.
  • Popover viewport-anchored (preserves 70820da).

What NOT to break

  • _buildRange + wb-pt-range CSS — still used by 3 stroke-width callers.
  • _openPopover / _closeTopPopover stack — reused, not modified.
  • Color-picker swatches from 34156d5 — different CSS class space (wb-pt-color-* vs wb-pt-size-*), no collision.
  • _mdFontSize field + WhiteboardObjects.rerender* contract — unchanged, helper hands an integer to the same callbacks.
  • Other input helpers (_buildTextInput, _buildSelect, _buildPopoverTextEditor) — unchanged.
## Implementation Spec ### Audit `_buildRange(min, max, value, suffix, onInput, tooltip)` is used in 7 places — only the 4 font-size sites switch to the new control. The 3 stroke-width sites keep the slider: | Line | Caller | Range | Property | Replace? | |---|---|---|---|---| | 842 | `_renderSticky` | 10..72 px | font size | YES | | 873 | `_renderText` | 10..72 px | font size | YES | | 940 | `_renderShape` | 1..8 px | stroke width | NO | | 955 | `_renderShape` | 10..48 px | font size | YES | | 988 | `_renderDrawing` | 1..20 px | stroke width | NO | | 1044 | `_renderDocument` | 10..36 px | font size | YES | | 1358 | `_renderConnector` | 1..10 px | stroke width | NO | Reuses existing infra: `_openPopover` for viewport-anchored popovers (preserves `70820da`), `wb-pt-popover` styling, theme variables. ### Helper signature ```js _buildSizePicker(value, onChange, opts) // opts = { presets, min, max, step, suffix, tooltip } // defaults: presets=[10,12,14,18,24,36,48,64,80,144,288], min=1, max=999, step=1, suffix='px', tooltip='Font size' ``` Purpose-built name (mirrors `_buildAlignTrigger`/`_buildFontTrigger`/`_buildColorTrigger`). Call sites collapse to `_buildSizePicker(fontSize, onChange)`. ### Files to Modify 1. `crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js` — new helper + sweep 4 call sites. 2. `crates/hero_whiteboard_ui/static/web/css/selection_toolbar.css` — append new rules. `_buildRange` stays — still used by 3 stroke-width callers. ### DOM Trigger: ```html <span class="wb-pt-size-picker" role="group" aria-label="Font size"> <input type="number" class="wb-pt-size-input" min="1" max="999" step="1" value="18" inputmode="numeric" aria-label="Font size" title="Font size"> <button type="button" class="wb-pt-size-presets-btn" aria-haspopup="listbox" title="Font size presets"> <i class="bi bi-chevron-down"></i> </button> </span> ``` Uses the input's **native spinner** (no custom up/down chevrons). Total ~78px wide, comparable to the slider. Popover: ```html <div class="wb-pt-popover wb-pt-size-popover"> <div class="wb-pt-size-presets" role="listbox"> <button class="wb-pt-size-preset" data-value="10">10</button> ... 12, 14, 18, 24, 36, 48, 64, 80, 144, 288 ... </div> </div> ``` The preset matching the current input value gets `is-selected`. ### Behavior - Type a value → live `onChange` on every keystroke (matches slider parity). - Native spinner / ArrowUp / ArrowDown step by 1 live. - Enter commits + clamps + blurs. - Escape with popover open → close popover. Escape on input → revert to last-committed value. - Click chevron → open preset list. Click a preset → commit that value, close popover. - Outside-click closes popover via existing `_openPopover` infrastructure. - Validation: parseInt; NaN → last valid; clamp to `[min, max]` (1..999 default) on commit. ### CSS additions ```css .wb-pt-size-picker { display: inline-flex; align-items: center; gap: 2px; } .wb-pt-size-input { width: 52px; height: 28px; padding: 2px 4px; background: var(--wb-toolbar-bg); border: 1px solid var(--wb-border); border-radius: 6px 0 0 6px; color: var(--wb-text); font-size: 12px; text-align: center; outline: none; -moz-appearance: textfield; } .wb-pt-size-input:focus { border-color: var(--wb-primary); z-index: 1; } .wb-pt-size-presets-btn { width: 22px; height: 28px; padding: 0; background: var(--wb-toolbar-bg); border: 1px solid var(--wb-border); border-left-width: 0; border-radius: 0 6px 6px 0; color: var(--wb-text-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } .wb-pt-size-presets-btn:hover { background: var(--wb-hover); color: var(--wb-text); } .wb-pt-size-presets-btn .bi { font-size: 12px; line-height: 1; } .wb-pt-size-presets { display: flex; flex-direction: column; gap: 2px; max-height: 240px; overflow-y: auto; min-width: 80px; } .wb-pt-size-preset { text-align: left; padding: 4px 10px; background: transparent; border: 0; border-radius: 4px; color: var(--wb-text); cursor: pointer; font-size: 13px; } .wb-pt-size-preset:hover { background: var(--wb-hover); } .wb-pt-size-preset.is-selected { background: var(--wb-primary); color: var(--wb-primary-text); } ``` ### Call-site sweep Replace each font-size `_buildRange(...)` with `_buildSizePicker(fontSize, onChange)`. The previous min/max caps (72/48/36) are dropped — the user can now type up to 999. Stroke-width sites at lines 940, 988, 1358 stay on `_buildRange`. ### Acceptance Criteria - [ ] Sticky/text/shape/document font size: number input + chevron button, no slider. - [ ] Type a number + Enter → applies + syncs. - [ ] Native spinner / ArrowUp/Down steps by 1 live. - [ ] Chevron opens popover with exactly: 10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288. Current value highlighted. - [ ] Click preset → applies, closes popover. - [ ] Outside-click + Escape close the popover. - [ ] Empty/NaN/out-of-range input clamped to [1, 999] on commit; no NaN downstream. - [ ] Stroke width still slider in shape/drawing/connector. - [ ] Light/dark theme: control consistent in both. - [ ] Popover viewport-anchored (preserves `70820da`). ### What NOT to break - `_buildRange` + `wb-pt-range` CSS — still used by 3 stroke-width callers. - `_openPopover` / `_closeTopPopover` stack — reused, not modified. - Color-picker swatches from `34156d5` — different CSS class space (`wb-pt-color-*` vs `wb-pt-size-*`), no collision. - `_mdFontSize` field + `WhiteboardObjects.rerender*` contract — unchanged, helper hands an integer to the same callbacks. - Other input helpers (`_buildTextInput`, `_buildSelect`, `_buildPopoverTextEditor`) — unchanged.
Author
Member

Validation

Check Result
Files changed 2 (selection_toolbar.js +109/-8, selection_toolbar.css +65)
cargo check --workspace pass
cargo test --workspace --lib pass
Helper refs 1 _buildSizePicker def + 4 calls (font size); 1 _buildRange def + 3 calls (stroke width) — sliders preserved for stroke-width sites
## Validation | Check | Result | |---|---| | Files changed | 2 (selection_toolbar.js +109/-8, selection_toolbar.css +65) | | `cargo check --workspace` | pass | | `cargo test --workspace --lib` | pass | | Helper refs | 1 `_buildSizePicker` def + 4 calls (font size); 1 `_buildRange` def + 3 calls (stroke width) — sliders preserved for stroke-width sites |
Author
Member

Implementation summary

Changes

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js

    • Added DEFAULT_FONT_SIZE_PRESETS = [10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288] constant.
    • Added _buildSizePicker(value, onChange, opts) helper next to _buildRange. Renders a <input type="number"> joined to a chevron button that opens a preset listbox via the existing _openPopover / _closeTopPopover machinery. Live commits on input / Enter / blur, reverts on Escape, clamps to [min, max] (defaults 1..999).
    • Replaced the four font-size _buildRange call sites (sticky, text, shape, document) with _buildSizePicker(fontSize, onChange).
    • The three stroke-width _buildRange call sites (shape stroke, drawing stroke, connector stroke) are unchanged — sliders preserved for those.
  • crates/hero_whiteboard_ui/static/web/css/selection_toolbar.css

    • Appended .wb-pt-size-picker, .wb-pt-size-input, .wb-pt-size-presets-btn, .wb-pt-size-presets, .wb-pt-size-preset rules. Theme-aware via --wb-* variables. Reuses existing .wb-pt-popover for the dropdown shell.

Keyboard: ArrowUp/Down on the input → live increment via the native spinner. Enter → commit + clamp + blur. Escape → revert to last-committed value. Escape with the popover open → close popover.

Mouse: spinner / chevron / preset-click all behave as expected. Outside-click closes the popover via the existing infrastructure.

Validation

  • cargo check --workspace: pass
  • cargo test --workspace --lib: pass
  • Diff scope: 2 files, +174/-8

Notes

UI-only change. Verify visually:

  1. Select a sticky / text / shape / document — font-size control is now a number input + chevron, no slider.
  2. Type 128 and press Enter — applies and syncs.
  3. Click the chevron — preset list with 10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288. Current value (if matching) highlighted.
  4. Click a preset — applies, closes popover.
  5. Outside-click + Escape close the popover.
  6. Open a shape and check that stroke-width is still a slider.
  7. Toggle theme — control colors update.
## Implementation summary ### Changes - `crates/hero_whiteboard_ui/static/web/js/whiteboard/selection_toolbar.js` - Added `DEFAULT_FONT_SIZE_PRESETS = [10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288]` constant. - Added `_buildSizePicker(value, onChange, opts)` helper next to `_buildRange`. Renders a `<input type="number">` joined to a chevron button that opens a preset listbox via the existing `_openPopover` / `_closeTopPopover` machinery. Live commits on input / Enter / blur, reverts on Escape, clamps to `[min, max]` (defaults 1..999). - Replaced the four font-size `_buildRange` call sites (sticky, text, shape, document) with `_buildSizePicker(fontSize, onChange)`. - The three stroke-width `_buildRange` call sites (shape stroke, drawing stroke, connector stroke) are unchanged — sliders preserved for those. - `crates/hero_whiteboard_ui/static/web/css/selection_toolbar.css` - Appended `.wb-pt-size-picker`, `.wb-pt-size-input`, `.wb-pt-size-presets-btn`, `.wb-pt-size-presets`, `.wb-pt-size-preset` rules. Theme-aware via `--wb-*` variables. Reuses existing `.wb-pt-popover` for the dropdown shell. Keyboard: ArrowUp/Down on the input → live increment via the native spinner. Enter → commit + clamp + blur. Escape → revert to last-committed value. Escape with the popover open → close popover. Mouse: spinner / chevron / preset-click all behave as expected. Outside-click closes the popover via the existing infrastructure. ### Validation - `cargo check --workspace`: pass - `cargo test --workspace --lib`: pass - Diff scope: 2 files, +174/-8 ### Notes UI-only change. Verify visually: 1. Select a sticky / text / shape / document — font-size control is now a number input + chevron, no slider. 2. Type `128` and press Enter — applies and syncs. 3. Click the chevron — preset list with 10, 12, 14, 18, 24, 36, 48, 64, 80, 144, 288. Current value (if matching) highlighted. 4. Click a preset — applies, closes popover. 5. Outside-click + Escape close the popover. 6. Open a shape and check that stroke-width is still a slider. 7. Toggle theme — control colors update.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#159
No description provided.