@hyphen/hyphen-components 9.1.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyphen/hyphen-components",
3
- "version": "9.1.0",
3
+ "version": "9.1.1",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "@hyphen"
@@ -57,7 +57,7 @@
57
57
  "react-select": "^5.10.0",
58
58
  "react-select-event": "^5.5.1",
59
59
  "uuid": "^11.1.1",
60
- "@hyphen/hyphen-design-tokens": "^9.1.0"
60
+ "@hyphen/hyphen-design-tokens": "^9.1.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@babel/core": "^7.27.4",
@@ -289,20 +289,43 @@ test('mobile uses its drawer without resize controls', () => {
289
289
  ).not.toBeInTheDocument();
290
290
  });
291
291
 
292
- test('pointer movement clamps at both bounds and lost capture cancels', () => {
292
+ test('pointer movement clamps at both bounds', () => {
293
293
  render(<Example />);
294
294
  const rail = screen.getByRole('button', { name: /Resize or toggle/ });
295
295
  drag(rail, 500, -1000);
296
296
  expect(width()).toBe('960px');
297
297
  drag(rail, 500, 1500);
298
298
  expect(width()).toBe('320px');
299
- fireEvent.pointerDown(rail, { button: 0, clientX: 500 });
300
- fireEvent.pointerMove(rail, { clientX: 400 });
301
- fireEvent.lostPointerCapture(rail);
302
- expect(width()).toBe('320px');
303
- expect(document.body.style.cursor).toBe('');
304
299
  });
305
300
 
301
+ test.each(['left', 'right'] as const)(
302
+ 'keeps the last dragged %s width when capture ends before pointerup',
303
+ (side) => {
304
+ render(<Example side={side} />);
305
+ const rail = screen.getByRole('button', {
306
+ name: `Resize or toggle ${side} sidebar`,
307
+ });
308
+ const endX = side === 'left' ? 700 : 300;
309
+ fireEvent.pointerDown(rail, { pointerId: 1, button: 0, clientX: 500 });
310
+ // Do not pause for a render between the last move and losing capture.
311
+ act(() => {
312
+ fireEvent.pointerMove(rail, { pointerId: 1, clientX: endX });
313
+ fireEvent.lostPointerCapture(rail, { pointerId: 1 });
314
+ });
315
+ expect(width(side)).toBe('584px');
316
+ expect(localStorage.getItem(`width-${side}`)).toBe('584');
317
+ expect(document.body.style.cursor).toBe('');
318
+ expect(document.body.style.userSelect).toBe('');
319
+ fireEvent.pointerUp(rail, { pointerId: 1, clientX: endX });
320
+ fireEvent.click(rail, { detail: 1 });
321
+ expect(width(side)).toBe('584px');
322
+ expect(document.querySelector(`[data-side="${side}"]`)).toHaveAttribute(
323
+ 'data-state',
324
+ 'expanded'
325
+ );
326
+ }
327
+ );
328
+
306
329
  test('resizes without ResizeObserver and removes its window listener on unmount', () => {
307
330
  Object.defineProperty(window, 'ResizeObserver', {
308
331
  configurable: true,
@@ -121,6 +121,12 @@ export function useSidebarResize({
121
121
  // A storage failure must not interrupt the interaction.
122
122
  }
123
123
  };
124
+ const finish = () => {
125
+ if (!gesture.current) return;
126
+ if (gesture.current.moved) persist();
127
+ gesture.current = null;
128
+ setDragging(false);
129
+ };
124
130
  const cancel = () => {
125
131
  if (!gesture.current) return;
126
132
  setPreferredWidth(gesture.current.preferred);
@@ -158,14 +164,16 @@ export function useSidebarResize({
158
164
  update(
159
165
  start.width + (side === 'left' ? 1 : -1) * (event.clientX - start.x)
160
166
  );
161
- persist();
162
167
  }
163
- gesture.current = null;
164
- setDragging(false);
168
+ finish();
165
169
  event.currentTarget.releasePointerCapture(event.pointerId);
166
170
  },
167
171
  onPointerCancel: cancel,
168
- onLostPointerCapture: cancel,
172
+ // Capture can end before pointerup reaches the rail. Keep the last move
173
+ // instead of treating release as a cancelled drag and restoring its start.
174
+ onLostPointerCapture: (event) => {
175
+ if (gesture.current?.id === event.pointerId) finish();
176
+ },
169
177
  onClickCapture: (event) => {
170
178
  if (suppressClick.current && event.detail !== 0) {
171
179
  event.preventDefault();