WEBUI-2040: Ability to adjust the width of the Browse pane, Search pane, and Information pane [LTS-2023]#3186
Open
naveen-konda wants to merge 12 commits into
Conversation
…ne, and Information pane
Contributor
There was a problem hiding this comment.
Pull request overview
Implements user-adjustable pane widths for the left navigation drawer and the document “Information” side pane, including persistence of preferred widths across sessions and basic keyboard/drag interactions.
Changes:
- Adds a draggable + keyboard-accessible resize handle to the app drawer, with width clamping and
localStoragepersistence. - Adds a draggable + keyboard-accessible resize handle to the document page side pane, with clamping, persistence, and coordination to “push back” the drawer when needed.
- Introduces a themeable CSS custom property for resize-handle styling and new i18n strings for resize instructions.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| themes/light/theme.html | Adds --nuxeo-resize-handle-color for light theme. |
| themes/kawaii/theme.html | Adds --nuxeo-resize-handle-color for kawaii theme. |
| themes/default/theme.html | Adds --nuxeo-resize-handle-color for default theme. |
| themes/dark/theme.html | Adds --nuxeo-resize-handle-color for dark theme (adjusted color for contrast). |
| i18n/messages.json | Adds i18n strings describing keyboard resize interaction for drawer and info pane. |
| elements/nuxeo-app.js | Implements drawer resize handle, width clamping, persistence, and layout notifications. |
| elements/document/nuxeo-document-page.js | Implements info-pane resize handle, width clamping, persistence, and drawer “shrink” coordination. |
Comments suppressed due to low confidence (3)
elements/nuxeo-app.js:1546
- Drawer width persistence/clamping and the new keyboard resizing paths (
_computeOpenDrawerWidth,_onDrawerResizeKey,_resetDrawerWidth,_onShrinkDrawerRequest) are not covered by unit tests, despitetest/nuxeo-app.test.jsexisting for this element. Adding a few focused tests would help prevent regressions (e.g., clamping behavior, storage load/save/reset, Arrow/Home/End behavior).
/** Open drawer width (px): stored preference or natural default, clamped. */
_computeOpenDrawerWidth() {
const fallback = DRAWER_NATURAL_CONTENT_PX + this._sidebarPx();
const stored = this._drawerOpenWidth != null ? this._drawerOpenWidth : this._loadStoredDrawerWidth();
if (stored == null) {
return fallback;
}
this._drawerOpenWidth = stored;
return this._clampDrawerWidth(stored);
},
_clampDrawerWidth(px) {
return Math.min(this._maxDrawerWidth(), Math.max(this._minDrawerWidth(), px));
},
_loadStoredDrawerWidth() {
try {
const raw = window.localStorage && window.localStorage.getItem(DRAWER_STORAGE_KEY);
const n = raw != null ? Number.parseInt(raw, 10) : NaN;
return Number.isFinite(n) ? n : null;
} catch (_e) {
return null;
}
},
_persistDrawerWidth(px) {
try {
if (window.localStorage) {
window.localStorage.setItem(DRAWER_STORAGE_KEY, String(px));
}
} catch (_e) {
// ignore storage errors (e.g. private mode quota)
}
},
_onDrawerResizeStart(e) {
if (!this.drawerOpened || this.isNarrow) {
return;
}
e.preventDefault();
e.stopPropagation();
const point = e.touches && e.touches[0] ? e.touches[0] : e;
const startX = point.clientX;
const startWidth = this._computeOpenDrawerWidth();
this.setAttribute('drawer-resizing', '');
const rtl = this._isRTL;
const onMove = (ev) => {
const p = ev.touches && ev.touches[0] ? ev.touches[0] : ev;
const delta = (p.clientX - startX) * (rtl ? -1 : 1);
const next = this._clampDrawerWidth(startWidth + delta);
this._drawerOpenWidth = next;
this.drawerWidth = `${next}px`;
this._scheduleDrawerDragLayoutNotify();
};
const onEnd = () => {
this._cancelDrawerDragLayoutNotify();
this.removeAttribute('drawer-resizing');
window.removeEventListener('mousemove', onMove);
window.removeEventListener('mouseup', onEnd);
window.removeEventListener('touchmove', onMove);
window.removeEventListener('touchend', onEnd);
if (this._drawerOpenWidth != null) {
this._persistDrawerWidth(this._drawerOpenWidth);
}
this._notifyLayoutChanged();
};
window.addEventListener('mousemove', onMove);
window.addEventListener('mouseup', onEnd);
window.addEventListener('touchmove', onMove, { passive: false });
window.addEventListener('touchend', onEnd);
},
_onDrawerResizeKey(e) {
if (!this.drawerOpened || this.isNarrow) {
return;
}
const step = e.shiftKey ? DRAWER_KEY_STEP_SHIFT_PX : DRAWER_KEY_STEP_PX;
const current = this._computeOpenDrawerWidth();
const rtl = this._isRTL;
let next;
switch (e.key) {
case 'ArrowLeft':
next = current + (rtl ? step : -step);
break;
case 'ArrowRight':
next = current + (rtl ? -step : step);
break;
case 'Home':
next = this._minDrawerWidth();
break;
case 'End':
next = this._maxDrawerWidth();
break;
case 'Enter':
case ' ':
this._resetDrawerWidth();
e.preventDefault();
return;
default:
return;
}
e.preventDefault();
next = this._clampDrawerWidth(next);
this._drawerOpenWidth = next;
this.drawerWidth = `${next}px`;
this._persistDrawerWidth(next);
this._notifyLayoutChanged();
},
elements/document/nuxeo-document-page.js:463
sideWidthalready hasreflectToAttribute: true, but_sideWidthChangedalso manually sets/removes theside-widthattribute. This is redundant and can lead to confusing state (attribute may be mutated from two different mechanisms). It should be enough to only update the CSS custom property and let Polymer handle attribute reflection.
_sideWidthChanged(value) {
if (value == null || Number.isNaN(Number(value))) {
this.style.removeProperty('--nuxeo-side-pane-width');
this.removeAttribute('side-width');
} else {
this.style.setProperty('--nuxeo-side-pane-width', `${value}px`);
this.setAttribute('side-width', String(value));
}
elements/document/nuxeo-document-page.js:419
- The new side-pane width persistence and resizing logic (
sideWidth, localStorage load/persist, clamping, keyboard/drag handlers, andnuxeo-shrink-draweremission) is not covered by unit tests, even thoughtest/nuxeo-document-page.test.jsexists. Adding tests around clamping bounds and storage load/reset would reduce regression risk.
/** Info pane width (px); null uses default flex layout. */
sideWidth: {
type: Number,
value: null,
notify: true,
reflectToAttribute: true,
observer: '_sideWidthChanged',
},
},
ready() {
if (!this.hasAttribute('dir')) {
const direction = document.documentElement.getAttribute('dir');
this.setAttribute('dir', direction);
}
this._pendingStoredSideWidth = this._loadStoredSideWidth();
},
attached() {
// Re-clamp on viewport/drawer changes (numeric only — no DOM flicker).
this._onWindowResize = () => {
this._reclampSideWidth();
};
window.addEventListener('resize', this._onWindowResize);
animationFrame.run(() => {
if (this._pendingStoredSideWidth != null) {
if (this._isNarrowViewport()) {
// Keep stored width unclamped; narrow CSS ignores [side-width].
this.sideWidth = this._pendingStoredSideWidth;
} else {
this.sideWidth = this._clampSideWidth(this._pendingStoredSideWidth);
}
this._pendingStoredSideWidth = null;
}
});
},
…e-width-of-the-sidenav-pane-and-information-pane
|
madhurkulshrestha-hyland
requested changes
May 26, 2026
madhurkulshrestha-hyland
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



ELEMENTS-1844 / WEBUI-2040 — Feature Summary
Shipping ticket: WEBUI-2040 — Ability to adjust the width of the Browse pane, Search pane, and Information pane (PR #3186).
Note: Browse and Search are not separate layouts — both use the same left drawer in
nuxeo-app(different drawer tabs). Only the Information pane on document pages is a second resizable panel.Dual-repo shipping: UI work spans nuxeo-web-ui (WEBUI-2040) and nuxeo-elements (ELEMENTS-1844 — shared
nuxeo-resize-handlewidget, i18n keys, tooltip clone styling). Both must land (elements RC published before or with web-ui) for CI to resolve imports.At a glance (PM / QA)
localStorage).nuxeo-shrink-drawer).1. What this feature does
Users can now drag the edge of two side panels to make them wider or narrower:
The chosen width is remembered across page refreshes (per browser).
That's it. No new buttons, no new menus — just a draggable edge.
2. How it looks
Wide desktop (document page) — the info pane lives inside the main column, not beside the whole app:
On search / tasks / admin routes, only the drawer handle applies (no info pane).
Each handle is a
**nuxeo-resize-handle** strip (6 px). It is invisible until hover/focus (--nuxeo-resize-handle-colorper theme). Double-click or Enter / Space (when focused) resets that pane to its default width.3. What was added (high level)
**nuxeo-resize-handle** in nuxeo-elements — 6 px strip, pointer/keyboard/RTL, tooltip, ARIA; hosts listen forresize-*events.**NuxeoAppDrawerResizeBehavior** (elements/behaviors/nuxeo-app-drawer-resize-behavior.js); shell wiring innuxeo-app.js.elements/document/nuxeo-document-page.js; same widget in the template.localStorage(nuxeo.drawerWidth,nuxeo.documentPage.sidePaneWidth).nuxeo-shrink-drawerso the drawer shrinks and the main column stays safe.**_notifyLayoutChanged()**→notifyResize()+ guardedwindow.resizefor all main-content routes; drawer drag uses iron-resize only (no global resize per mousemove). Info pane fires**nuxeo-layout-updated** on settle so zoom reflows even when the drawer did not move.--nuxeo-resize-handle-colorin default, light, dark, and kawaii themes (hover/focus on the widget). Tooltip label styles live in nuxeo-elements (nuxeo-tooltipclone), notthemes/base.js.**app.drawer.resize**,**documentPage.resize.side**in nuxeo-elementsui/i18n/messages.json(Crowdin sync as usual).test/nuxeo-app-drawer-resize-behavior.test.js,test/nuxeo-document-page.test.js,test/nuxeo-app.test.js(RTL integration); nuxeo-elementsui/test/nuxeo-resize-handle-extras.test.js,nuxeo-tooltip.test.js.No existing screens, layouts, or workflows were changed when the user has not resized anything.
3a. Charts — layout, resize, and who gets updated
Where each handle lives (accurate nesting)
flowchart TB subgraph app ["nuxeo-app"] SB["Sidebar\n52 px"] DR["Drawer\n+ nuxeo-resize-handle\nedge=end ⇨"] subgraph main ["#mainContent — all routes"] ROUTE["search · tasks · admin · …"] subgraph doconly ["document route only"] DV[".main — document view"] INFO[".side — info pane\n+ nuxeo-resize-handle\nedge=start ⇦"] end end end SB --- DR --- main DV --- INFOThree ideas to keep straight
flowchart TB subgraph perPane ["Per pane"] PREF["User preference\nlocalStorage"] PAINT["What you see now\ninline width / CSS var"] LEGAL["Legal range right now\nmin … max for this screen"] end PREF --> LEGAL LEGAL --> PAINTOn zoom or window resize, preference in storage stays; min/max change; code re-clamps painted width back toward preference (same pattern for drawer and info pane).
When widths are saved
localStorageiron-resizeonly (tables reflow, no globalwindow.resizestorm)notifyResize+window.resize)resizeon document-page onlynuxeo-layout-updated→ app full settlenuxeo-layout-updated→ app full settleHow main content learns the width changed
flowchart LR subgraph during ["While dragging"] D1["Drawer drag"] D2["Info drag"] D1 --> IR1["iron-resize only"] D2 --> R2["resize event\non document-page"] end subgraph settle ["On settle / zoom"] S1["Drawer: release,\nkeys, reclamp"] S2["Info: release,\nkeys, reclamp"] S2 --> E["nuxeo-layout-updated"] S1 --> N["_notifyLayoutChanged"] E --> N N --> IR2["iron-resize"] N --> WR["window.resize\n(guarded)"] end IR1 --> W["Tables · viewers\nin main column"] IR2 --> W WR --> WnotifyResize()→iron-resizewindow.resizenuxeo-layout-updatednuxeo-applistenerNot page-by-page: the shell does not special-case routes; widgets must listen via
IronResizableBehaviororwindow.resize.Contract: implementation-strategy §3.4a.
When both panes compete for space (push-back)
sequenceDiagram participant U as User participant I as Info pane participant A as nuxeo-app participant D as Drawer U->>I: Drag info pane past its max I->>A: nuxeo-shrink-drawer { amount } A->>D: Shrink drawer (min floor applies) Note over I,D: Main document column stays ≥ reserved widthIf the drawer is already at its minimum, the info pane stops growing — no error, no layout break.
Zoom in / out (drawer vs info pane)
Responsive breakpoints (when handles appear)
Browser zoom changes effective CSS pixels the same way as resizing the window.
Release train (dual repo)
What ships in the PRs
nuxeo-web-ui (WEBUI-2040):
elements/nuxeo-app.js— shell, layout notify, shrink-drawer listener,nuxeo-layout-updatedconsumerelements/behaviors/nuxeo-app-drawer-resize-behavior.js— drawer width math, persistence, resize handlerselements/document/nuxeo-document-page.js— info-pane math, persistence, push-back,nuxeo-layout-updatedproducerthemes/*/theme.html—--nuxeo-resize-handle-colortest/nuxeo-app-drawer-resize-behavior.test.js,test/nuxeo-document-page.test.js,test/nuxeo-app.test.jsdocs/ELEMENTS-1844-*.mdnuxeo-elements (coordinated release —
nuxeo-resize-handle, tooltip clone fix, i18n keys):ui/widgets/nuxeo-resize-handle.js,ui/widgets/nuxeo-tooltip.jsui/nuxeo-ui-elements.js,ui/i18n/messages.jsonui/test/nuxeo-resize-handle-extras.test.js,ui/test/nuxeo-tooltip.test.jsCI installs the latest
@nuxeo/nuxeo-ui-elementsRC from the registry — the elements PR must publish an RC that includesnuxeo-resize-handlebefore web-ui CI passes on the widget refactor.4. How the resize works in practice
What happens while you drag
**nuxeo-resize-handle** edge (mouse, touch, or Tab).window.resizeeach frame (performance).localStorageand the app runs a full layout settle.localStorage(clamped to the current screen).What stops the user from making things unusable
Each pane has a minimum width (so labels and icons never get cut off) and a maximum width (so the rest of the page is never squashed).
If the user tries to drag past these limits, the pane simply stops moving — there is no jump, no error, no jarring snap.
Resetting back to the default
EnterorSpace.5. How the main document content stays readable
This is the most important behaviour to understand.
The document view (the middle column) is the focus of the user's work. The two side panels exist to support it. So the implementation reserves space for the main column before the side panels are allowed to grow.
The reservation rule is: the main column is guaranteed at least half of the document-page container's width, capped at a comfortable 640 px on very large screens and floored at 240 px on very small ones. That gives readers enough room for data tables and document content even when both panes are maxed out.
In plain numbers — both panes dragged to their maximum at that viewport (drawer max is dynamic, grows when you zoom out):
If the user drags the info pane bigger and the drawer is already wide, the drawer automatically shrinks to make room — the main column never drops below its reserved minimum.
The reservation is proportional, so it adapts to smaller screens and to browser zoom. Below the 1024 px breakpoint the document page switches to vertical stacking (main on top, info below) and resize edges are hidden — matching the stock responsive behaviour.
6. Different screen sizes and browser zoom
The feature follows the same responsive breakpoints the rest of Nuxeo Web UI already uses:
Browser zoom is handled the same way as window resizing:
drawerPanel.notifyResize()(iron-resize) and a guardedwindow.resize. This applies to whatever page is open in main content, not only the document view.localStorageafter zoom (so it matches the drawer: refresh is not required to restore a widened info pane).7. Accessibility & keyboard support
The draggable edges are full keyboard widgets, not just mouse targets. They show up in the keyboard tab order and announce themselves to screen readers.
←/→arrowShift+ arrowHomeEndEnterorSpacearia-label).The mouse cursor changes to a horizontal-resize arrow when hovering. Keyboard
**:focus-visible** uses an outline in--nuxeo-resize-handle-color(same token as the hover fill).Labels:
**label-key** onnuxeo-resize-handle→ translatedaria-labelplus**nuxeo-tooltip**on hover (styles scoped on the tooltip clone, not global theme CSS).app.drawer.resize(drawer),documentPage.resize.side(info pane).Example SR text: "Resize the side navigation panel. Use Left/Right arrow keys to adjust width, Home or End for minimum or maximum, Enter or Space to reset."
RTL (Arabic, Hebrew): see § 7a — mirrored arrows, handle placement, and QA.
7a. LTR vs RTL (Arabic, Hebrew)
The UI follows the app / page
**dir** attribute. Resize behaviour is mirrored so users always widen a pane by dragging away from the main content and narrow it by dragging toward the main content. Keyboard Left/Right are remapped the same way (labels still say “Left/Right arrow keys” in English).Handle placement (inner edge toward main)
The widget uses
edge+ explicitdir(not CSS:host-contextthroughapp-drawershadow DOM).edgedirfrom hostendnuxeo-app_resizeHandleDir(_isRTL)startnuxeo-document-page_resizeHandleDir(pagedir)Tooltip hover position flips with direction (
_drawerResizeTooltipPosition/_sideResizeTooltipPosition).Which arrow widens the pane?
Logic lives in
**nuxeo-resize-handle** (resizeDeltaForKey). “Widen” means increase pane width in pixels.end→Right←Leftend←Left→Rightstart←Left→Rightstart→Right←LeftHome/Endstill mean minimum / maximum width in both directions.Shift+ arrow keeps the same widen/narrow mapping with a 64 px step.Pointer drag (mouse / touch)
Drag uses
resizeDeltaFromPointer: the sign of movement is flipped in RTL so the same physical gesture (pull the inner edge away from the document) widens the pane in both LTR and RTL. QA should verify drag direction feels natural in an RTL locale, not only arrow keys.flowchart LR subgraph widget ["nuxeo-resize-handle"] D["dir=ltr | rtl"] E["edge=start | end"] K["resizeDeltaForKey\nresizeDeltaFromPointer"] end subgraph hosts ["Hosts apply delta"] APP["nuxeo-app\n_drawerResizeStep…"] DOC["nuxeo-document-page\n_onSideResizeStep…"] end D --> K E --> K K --> APP K --> DOCWhat QA should check in RTL
app-drawernuxeo-shrink-drawer; drawer shrinkslocalStoragekeys unchanged; width restores after refreshAutomated coverage:
test/nuxeo-app.test.js(drawer handle),test/nuxeo-document-page.test.jsandtest/nuxeo-app-drawer-resize-behavior.test.js(RTL suites), nuxeo-elementsui/test/nuxeo-resize-handle.test.js(delta helpers).8. Persistence
The chosen widths are saved in the browser's
localStorage:nuxeo.drawerWidthnuxeo.documentPage.sidePaneWidthready; info pane onattachedviaanimationFrame).localStorageis unavailable (private mode, quota, etc.), the panes use defaults and errors are ignored.9. Things product / QA should be aware of
nuxeo-apprunsnotifyResizeon every notify path; syntheticwindow.resizeonly on settle (not each drawer drag frame). Info pane usesnuxeo-layout-updatedto trigger the same settle path. See § 3a.**--nuxeo-resize-handle-color**, not the primary brand color.Minimum QA pass (before merge)
localStoragekey cleareddir=rtl10. Files to look at
ui/widgets/nuxeo-resize-handle.jsui/widgets/nuxeo-tooltip.jsthemes/base.js).elements/behaviors/nuxeo-app-drawer-resize-behavior.jselements/nuxeo-app.js**_notifyLayoutChanged/_runLayoutNotify**,nuxeo-layout-updatedlistener.elements/document/nuxeo-document-page.js**nuxeo-layout-updated**on settle/zoom.ui/i18n/messages.jsonapp.drawer.resize,documentPage.resize.side.themes/*/theme.html--nuxeo-resize-handle-color.test/nuxeo-app-drawer-resize-behavior.test.jstest/nuxeo-document-page.test.jsnuxeo-layout-updated).test/nuxeo-app.test.js#drawerResizeHandle.TL;DR
Users resize the Browse/Search drawer (all routes) and Information pane (document pages) via
**nuxeo-resize-handle. Widths persist in**nuxeo.drawerWidth**and**nuxeo.documentPage.sidePaneWidth**. Main content keeps ≥ half of the doc-page width (240–640 px band). RTL: inner-edge handles + mirrored arrows/drag (§ 7a). While dragging: light reflow (iron-resize/ localresize). On settle: app runsnotifyResize+ guardedwindow.resize; info pane signals**nuxeo-layout-updated. Info pane can**nuxeo-shrink-drawer**. Ship elements RC then web-ui. WEBUI-2040 / ELEMENTS-1844. Diagrams: § 3a · QA: § 9.