Skip to content

Commit c51ceb7

Browse files
committed
Add some callbacks when resizing a sidebar
And use a ResizeObserver in order to avoid to have to compare the width with min/max which can be in an other unit than px.
1 parent 615965f commit c51ceb7

2 files changed

Lines changed: 128 additions & 63 deletions

File tree

web/sidebar.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,9 @@
7979
:not(.sidebarResizer) {
8080
pointer-events: none;
8181
}
82+
83+
.sidebarResizer {
84+
background-color: var(--resizer-hover-bg-color);
85+
}
8286
}
8387
}

web/sidebar.js

Lines changed: 124 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,30 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib";
16+
import { noContextMenu, stopEvent } from "pdfjs-lib";
17+
18+
// Timeout before ending resize operation.
19+
const RESIZE_TIMEOUT = 400; // ms
1720

1821
/**
1922
* Viewer control to display a sidebar with resizer functionality.
2023
*/
2124
class Sidebar {
22-
#minWidth = 0;
23-
24-
#maxWidth = 0;
25-
2625
#initialWidth = 0;
2726

2827
#width = 0;
2928

3029
#coefficient;
3130

32-
#visible = false;
31+
#resizeTimeout = null;
32+
33+
#resizer;
34+
35+
#isResizerOnTheLeft;
36+
37+
#isKeyboardResizing = false;
38+
39+
#resizeObserver = null;
3340

3441
/**
3542
* @typedef {Object} SidebarElements
@@ -48,46 +55,67 @@ class Sidebar {
4855
constructor({ sidebar, resizer, toggleButton }, ltr, isResizerOnTheLeft) {
4956
this._sidebar = sidebar;
5057
this.#coefficient = ltr === isResizerOnTheLeft ? -1 : 1;
58+
this.#resizer = resizer;
59+
this.#isResizerOnTheLeft = isResizerOnTheLeft;
5160

5261
const style = window.getComputedStyle(sidebar);
53-
this.#minWidth = parseFloat(style.getPropertyValue("--sidebar-min-width"));
54-
this.#maxWidth = parseFloat(style.getPropertyValue("--sidebar-max-width"));
5562
this.#initialWidth = this.#width = parseFloat(
5663
style.getPropertyValue("--sidebar-width")
5764
);
65+
resizer.ariaValueMin = parseFloat(
66+
style.getPropertyValue("--sidebar-min-width")
67+
);
68+
resizer.ariaValueMax = parseFloat(
69+
style.getPropertyValue("--sidebar-max-width")
70+
);
71+
resizer.ariaValueNow = this.#width;
5872

59-
this.#makeSidebarResizable(resizer, isResizerOnTheLeft);
73+
this.#makeSidebarResizable();
6074
toggleButton.addEventListener("click", this.toggle.bind(this));
75+
this._isOpen = false;
6176
sidebar.hidden = true;
6277
}
6378

64-
#makeSidebarResizable(resizer, isResizerOnTheLeft) {
65-
resizer.ariaValueMin = this.#minWidth;
66-
resizer.ariaValueMax = this.#maxWidth;
67-
resizer.ariaValueNow = this.#width;
68-
79+
#makeSidebarResizable() {
80+
const sidebarStyle = this._sidebar.style;
6981
let pointerMoveAC;
7082
const cancelResize = () => {
71-
this.#width = MathClamp(this.#width, this.#minWidth, this.#maxWidth);
83+
this.#resizeTimeout = null;
7284
this._sidebar.classList.remove("resizing");
7385
pointerMoveAC?.abort();
7486
pointerMoveAC = null;
87+
this.#resizeObserver?.disconnect();
88+
this.#resizeObserver = null;
89+
this.#isKeyboardResizing = false;
90+
this.onStopResizing();
7591
};
76-
resizer.addEventListener("pointerdown", e => {
92+
this.#resizer.addEventListener("pointerdown", e => {
7793
if (pointerMoveAC) {
7894
cancelResize();
7995
return;
8096
}
97+
this.onStartResizing();
8198
const { clientX } = e;
8299
stopEvent(e);
83100
let prevX = clientX;
84101
pointerMoveAC = new AbortController();
85102
const { signal } = pointerMoveAC;
86103
const sidebar = this._sidebar;
87-
const sidebarStyle = sidebar.style;
88104
sidebar.classList.add("resizing");
89105
const parentStyle = sidebar.parentElement.style;
90106
parentStyle.minWidth = 0;
107+
this.#resizeObserver?.disconnect();
108+
this.#resizeObserver = new ResizeObserver(
109+
([
110+
{
111+
borderBoxSize: [{ inlineSize }],
112+
},
113+
]) => {
114+
prevX += this.#width - inlineSize;
115+
this.#setWidth(inlineSize);
116+
}
117+
);
118+
this.#resizeObserver.observe(sidebar);
91119
window.addEventListener("contextmenu", noContextMenu, { signal });
92120
window.addEventListener(
93121
"pointermove",
@@ -96,16 +124,7 @@ class Sidebar {
96124
return;
97125
}
98126
stopEvent(ev);
99-
const { clientX: x } = ev;
100-
this.#setNewWidth(
101-
x - prevX,
102-
parentStyle,
103-
resizer,
104-
sidebarStyle,
105-
isResizerOnTheLeft,
106-
/* isFromKeyboard */ false
107-
);
108-
prevX = x;
127+
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * (ev.clientX - prevX))}px`;
109128
},
110129
{ signal, capture: true }
111130
);
@@ -121,59 +140,101 @@ class Sidebar {
121140
{ signal }
122141
);
123142
});
124-
resizer.addEventListener("keydown", e => {
143+
this.#resizer.addEventListener("keydown", e => {
125144
const { key } = e;
126145
const isArrowLeft = key === "ArrowLeft";
127146
if (isArrowLeft || key === "ArrowRight") {
147+
if (!this.#isKeyboardResizing) {
148+
this._sidebar.classList.add("resizing");
149+
this.#isKeyboardResizing = true;
150+
this.#resizeObserver?.disconnect();
151+
this.#resizeObserver = new ResizeObserver(
152+
([
153+
{
154+
borderBoxSize: [{ inlineSize }],
155+
},
156+
]) => {
157+
this.#setWidth(inlineSize);
158+
}
159+
);
160+
this.#resizeObserver.observe(this._sidebar);
161+
this.onStartResizing();
162+
}
163+
128164
const base = e.ctrlKey || e.metaKey ? 10 : 1;
129165
const dx = base * (isArrowLeft ? -1 : 1);
130-
this.#setNewWidth(
131-
dx,
132-
this._sidebar.parentElement.style,
133-
resizer,
134-
this._sidebar.style,
135-
isResizerOnTheLeft,
136-
/* isFromKeyboard */ true
137-
);
166+
clearTimeout(this.#resizeTimeout);
167+
this.#resizeTimeout = setTimeout(cancelResize, RESIZE_TIMEOUT);
168+
sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * dx)}px`;
138169
stopEvent(e);
139170
}
140171
});
141172
}
142173

143-
#setNewWidth(
144-
dx,
145-
parentStyle,
146-
resizer,
147-
sidebarStyle,
148-
isResizerOnTheLeft,
149-
isFromKeyboard
150-
) {
151-
let newWidth = this.#width + this.#coefficient * dx;
152-
if (!isFromKeyboard) {
153-
this.#width = newWidth;
154-
}
155-
if (
156-
(newWidth > this.#maxWidth || newWidth < this.#minWidth) &&
157-
(this.#width === this.#maxWidth || this.#width === this.#minWidth)
158-
) {
159-
return;
160-
}
161-
newWidth = MathClamp(newWidth, this.#minWidth, this.#maxWidth);
162-
if (isFromKeyboard) {
163-
this.#width = newWidth;
174+
#setWidth(newWidth) {
175+
this.#width = newWidth;
176+
this.#resizer.ariaValueNow = Math.round(newWidth);
177+
if (this.#isResizerOnTheLeft) {
178+
this._sidebar.parentElement.style.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
164179
}
165-
resizer.ariaValueNow = Math.round(newWidth);
166-
sidebarStyle.width = `${newWidth.toFixed(3)}px`;
167-
if (isResizerOnTheLeft) {
168-
parentStyle.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
180+
this.onResizing(newWidth);
181+
}
182+
183+
/**
184+
* Get the current width of the sidebar in pixels.
185+
* @returns {number}
186+
*/
187+
get width() {
188+
return this.#width;
189+
}
190+
191+
/**
192+
* Set the width of the sidebar in pixels.
193+
* @param {number} newWidth
194+
*/
195+
set width(newWidth) {
196+
if (!this.#resizeObserver) {
197+
this.#resizeObserver = new ResizeObserver(
198+
([
199+
{
200+
borderBoxSize: [{ inlineSize }],
201+
},
202+
]) => {
203+
this.#setWidth(inlineSize);
204+
}
205+
);
206+
this.#resizeObserver.observe(this._sidebar);
169207
}
208+
this._sidebar.style.width = `${newWidth}px`;
209+
clearTimeout(this.#resizeTimeout);
210+
this.#resizeTimeout = setTimeout(() => {
211+
this.#resizeObserver.disconnect();
212+
this.#resizeObserver = null;
213+
}, RESIZE_TIMEOUT);
170214
}
171215

216+
/**
217+
* Callback to be executed when the user starts resizing the sidebar.
218+
*/
219+
onStartResizing() {}
220+
221+
/**
222+
* Callback to be executed when the user stops resizing the sidebar.
223+
*/
224+
onStopResizing() {}
225+
226+
/**
227+
* Callback to be executed when the sidebar is being resized.
228+
* @param {number} newWidth - The new width of the sidebar in pixels.
229+
*/
230+
onResizing(_newWidth) {}
231+
172232
/**
173233
* Toggle the sidebar's visibility.
234+
* @param {boolean} [visibility] - The visibility state to set.
174235
*/
175-
toggle() {
176-
this._sidebar.hidden = !(this.#visible = !this.#visible);
236+
toggle(visibility = !this._isOpen) {
237+
this._sidebar.hidden = !(this._isOpen = visibility);
177238
}
178239
}
179240

0 commit comments

Comments
 (0)