@mantine/hooks 9.3.1 → 9.4.0

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.
Files changed (122) hide show
  1. package/cjs/use-click-outside/use-click-outside.cjs.map +1 -1
  2. package/cjs/use-clipboard/use-clipboard.cjs.map +1 -1
  3. package/cjs/use-collapse/use-collapse.cjs.map +1 -1
  4. package/cjs/use-collapse/use-horizontal-collapse.cjs.map +1 -1
  5. package/cjs/use-counter/use-counter.cjs.map +1 -1
  6. package/cjs/use-debounced-callback/use-debounced-callback.cjs.map +1 -1
  7. package/cjs/use-debounced-state/use-debounced-state.cjs.map +1 -1
  8. package/cjs/use-debounced-value/use-debounced-value.cjs.map +1 -1
  9. package/cjs/use-did-update/use-did-update.cjs.map +1 -1
  10. package/cjs/use-disclosure/use-disclosure.cjs.map +1 -1
  11. package/cjs/use-document-title/use-document-title.cjs.map +1 -1
  12. package/cjs/use-document-visibility/use-document-visibility.cjs.map +1 -1
  13. package/cjs/use-drag/use-drag.cjs.map +1 -1
  14. package/cjs/use-event-listener/use-event-listener.cjs.map +1 -1
  15. package/cjs/use-eye-dropper/use-eye-dropper.cjs.map +1 -1
  16. package/cjs/use-favicon/use-favicon.cjs.map +1 -1
  17. package/cjs/use-fetch/use-fetch.cjs.map +1 -1
  18. package/cjs/use-file-dialog/use-file-dialog.cjs.map +1 -1
  19. package/cjs/use-floating-window/use-floating-window.cjs.map +1 -1
  20. package/cjs/use-focus-return/use-focus-return.cjs.map +1 -1
  21. package/cjs/use-focus-trap/scope-tab.cjs.map +1 -1
  22. package/cjs/use-focus-trap/tabbable.cjs.map +1 -1
  23. package/cjs/use-focus-trap/use-focus-trap.cjs.map +1 -1
  24. package/cjs/use-focus-within/use-focus-within.cjs.map +1 -1
  25. package/cjs/use-force-update/use-force-update.cjs.map +1 -1
  26. package/cjs/use-fullscreen/use-fullscreen.cjs.map +1 -1
  27. package/cjs/use-hash/use-hash.cjs.map +1 -1
  28. package/cjs/use-headroom/use-headroom.cjs.map +1 -1
  29. package/cjs/use-hotkeys/parse-hotkey.cjs.map +1 -1
  30. package/cjs/use-hotkeys/use-hotkeys.cjs.map +1 -1
  31. package/cjs/use-hover/use-hover.cjs.map +1 -1
  32. package/cjs/use-id/use-id.cjs.map +1 -1
  33. package/cjs/use-idle/use-idle.cjs.map +1 -1
  34. package/cjs/use-in-viewport/use-in-viewport.cjs.map +1 -1
  35. package/cjs/use-input-state/use-input-state.cjs.map +1 -1
  36. package/cjs/use-intersection/use-intersection.cjs.map +1 -1
  37. package/cjs/use-interval/use-interval.cjs.map +1 -1
  38. package/cjs/use-is-first-render/use-is-first-render.cjs.map +1 -1
  39. package/cjs/use-list-state/use-list-state.cjs.map +1 -1
  40. package/cjs/use-local-storage/create-storage.cjs.map +1 -1
  41. package/cjs/use-local-storage/use-local-storage.cjs.map +1 -1
  42. package/cjs/use-logger/use-logger.cjs.map +1 -1
  43. package/cjs/use-long-press/use-long-press.cjs.map +1 -1
  44. package/cjs/use-map/use-map.cjs.map +1 -1
  45. package/cjs/use-mask/use-mask.cjs.map +1 -1
  46. package/cjs/use-media-query/use-media-query.cjs.map +1 -1
  47. package/cjs/use-merged-ref/use-merged-ref.cjs.map +1 -1
  48. package/cjs/use-mounted/use-mounted.cjs.map +1 -1
  49. package/cjs/use-mouse/use-mouse.cjs.map +1 -1
  50. package/cjs/use-move/use-move.cjs.map +1 -1
  51. package/cjs/use-mutation-observer/use-mutation-observer.cjs.map +1 -1
  52. package/cjs/use-network/use-network.cjs.map +1 -1
  53. package/cjs/use-orientation/use-orientation.cjs.map +1 -1
  54. package/cjs/use-os/use-os.cjs.map +1 -1
  55. package/cjs/use-page-leave/use-page-leave.cjs.map +1 -1
  56. package/cjs/use-pagination/use-pagination.cjs.map +1 -1
  57. package/cjs/use-previous/use-previous.cjs.map +1 -1
  58. package/cjs/use-queue/use-queue.cjs.map +1 -1
  59. package/cjs/use-radial-move/use-radial-move.cjs.map +1 -1
  60. package/cjs/use-resize-observer/use-resize-observer.cjs.map +1 -1
  61. package/cjs/use-roving-index/use-roving-index.cjs.map +1 -1
  62. package/cjs/use-scroll-direction/use-scroll-direction.cjs.map +1 -1
  63. package/cjs/use-scroll-into-view/use-scroll-into-view.cjs.map +1 -1
  64. package/cjs/use-scroll-spy/use-scroll-spy.cjs.map +1 -1
  65. package/cjs/use-scroller/use-scroller.cjs.map +1 -1
  66. package/cjs/use-selection/use-selection.cjs.map +1 -1
  67. package/cjs/use-session-storage/use-session-storage.cjs.map +1 -1
  68. package/cjs/use-set/use-set.cjs.map +1 -1
  69. package/cjs/use-set-state/use-set-state.cjs.map +1 -1
  70. package/cjs/use-shallow-effect/use-shallow-effect.cjs.map +1 -1
  71. package/cjs/use-splitter/use-splitter.cjs +250 -70
  72. package/cjs/use-splitter/use-splitter.cjs.map +1 -1
  73. package/cjs/use-state-history/use-state-history.cjs.map +1 -1
  74. package/cjs/use-text-selection/use-text-selection.cjs.map +1 -1
  75. package/cjs/use-throttled-callback/use-throttled-callback.cjs.map +1 -1
  76. package/cjs/use-throttled-state/use-throttled-state.cjs.map +1 -1
  77. package/cjs/use-throttled-value/use-throttled-value.cjs.map +1 -1
  78. package/cjs/use-timeout/use-timeout.cjs.map +1 -1
  79. package/cjs/use-toggle/use-toggle.cjs.map +1 -1
  80. package/cjs/use-uncontrolled/use-uncontrolled.cjs.map +1 -1
  81. package/cjs/use-validated-state/use-validated-state.cjs.map +1 -1
  82. package/cjs/use-viewport-size/use-viewport-size.cjs.map +1 -1
  83. package/cjs/use-window-event/use-window-event.cjs.map +1 -1
  84. package/cjs/use-window-scroll/use-window-scroll.cjs.map +1 -1
  85. package/cjs/utils/lower-first/lower-first.cjs.map +1 -1
  86. package/cjs/utils/random-id/random-id.cjs.map +1 -1
  87. package/cjs/utils/shallow-equal/shallow-equal.cjs.map +1 -1
  88. package/cjs/utils/upper-first/upper-first.cjs.map +1 -1
  89. package/cjs/utils/use-callback-ref/use-callback-ref.cjs.map +1 -1
  90. package/esm/use-clipboard/use-clipboard.mjs.map +1 -1
  91. package/esm/use-document-title/use-document-title.mjs.map +1 -1
  92. package/esm/use-eye-dropper/use-eye-dropper.mjs.map +1 -1
  93. package/esm/use-favicon/use-favicon.mjs.map +1 -1
  94. package/esm/use-fetch/use-fetch.mjs.map +1 -1
  95. package/esm/use-floating-window/use-floating-window.mjs.map +1 -1
  96. package/esm/use-focus-trap/scope-tab.mjs.map +1 -1
  97. package/esm/use-focus-trap/tabbable.mjs.map +1 -1
  98. package/esm/use-hotkeys/parse-hotkey.mjs.map +1 -1
  99. package/esm/use-hotkeys/use-hotkeys.mjs.map +1 -1
  100. package/esm/use-id/use-id.mjs.map +1 -1
  101. package/esm/use-local-storage/create-storage.mjs.map +1 -1
  102. package/esm/use-local-storage/use-local-storage.mjs.map +1 -1
  103. package/esm/use-mask/use-mask.mjs.map +1 -1
  104. package/esm/use-media-query/use-media-query.mjs.map +1 -1
  105. package/esm/use-move/use-move.mjs.map +1 -1
  106. package/esm/use-radial-move/use-radial-move.mjs.map +1 -1
  107. package/esm/use-scroll-into-view/use-scroll-into-view.mjs.map +1 -1
  108. package/esm/use-scroll-spy/use-scroll-spy.mjs.map +1 -1
  109. package/esm/use-scroller/use-scroller.mjs.map +1 -1
  110. package/esm/use-session-storage/use-session-storage.mjs.map +1 -1
  111. package/esm/use-set/use-set.mjs.map +1 -1
  112. package/esm/use-splitter/use-splitter.mjs +250 -70
  113. package/esm/use-splitter/use-splitter.mjs.map +1 -1
  114. package/esm/use-toggle/use-toggle.mjs.map +1 -1
  115. package/esm/utils/lower-first/lower-first.mjs.map +1 -1
  116. package/esm/utils/random-id/random-id.mjs.map +1 -1
  117. package/esm/utils/shallow-equal/shallow-equal.mjs.map +1 -1
  118. package/esm/utils/upper-first/upper-first.mjs.map +1 -1
  119. package/lib/index.d.mts +1 -1
  120. package/lib/index.d.ts +1 -1
  121. package/lib/use-splitter/use-splitter.d.ts +54 -22
  122. package/package.json +1 -1
@@ -2,6 +2,102 @@
2
2
  const require_use_uncontrolled = require("../use-uncontrolled/use-uncontrolled.cjs");
3
3
  let react = require("react");
4
4
  //#region packages/@mantine/hooks/src/use-splitter/use-splitter.ts
5
+ const PX_RE = /^(-?[\d.]+)px$/;
6
+ const REM_RE = /^(-?[\d.]+)rem$/;
7
+ const PERCENT_RE = /^(-?[\d.]+)%$/;
8
+ function isFixedSize(size) {
9
+ return typeof size === "string" && (PX_RE.test(size) || REM_RE.test(size));
10
+ }
11
+ function sizeMagnitude(size) {
12
+ return typeof size === "number" ? size : parseFloat(size);
13
+ }
14
+ function detectPixelMode(options) {
15
+ return options.panels.some((panel) => isFixedSize(panel.defaultSize) || isFixedSize(panel.min) || isFixedSize(panel.max) || isFixedSize(panel.collapseThreshold)) || isFixedSize(options.step) || isFixedSize(options.shiftStep) || (options.sizes?.some(isFixedSize) ?? false);
16
+ }
17
+ function getRootFontSize() {
18
+ if (typeof window === "undefined") return 16;
19
+ const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
20
+ return Number.isFinite(fontSize) && fontSize > 0 ? fontSize : 16;
21
+ }
22
+ function resolveSize(size, pixelMode, containerPx, rootFontSize) {
23
+ if (!pixelMode) return sizeMagnitude(size);
24
+ if (typeof size === "number") return size / 100 * containerPx;
25
+ const percent = PERCENT_RE.exec(size);
26
+ if (percent) return parseFloat(percent[1]) / 100 * containerPx;
27
+ const rem = REM_RE.exec(size);
28
+ if (rem) return parseFloat(rem[1]) * rootFontSize;
29
+ const px = PX_RE.exec(size);
30
+ if (px) return parseFloat(px[1]);
31
+ return 0;
32
+ }
33
+ /** Down-scaling factor applied to fixed panes when their combined pixel size overflows the
34
+ * container, so they shrink to fit (matching `resolveWorkingSizes`). Returns `1` when nothing
35
+ * overflows or the layout is not in pixel mode. Encoders divide by this to invert the scaling and
36
+ * persist the original absolute sizes instead of the shrunk-to-fit ones. */
37
+ function getFixedScale(sizes, pixelMode, containerPx, rootFontSize) {
38
+ if (!pixelMode) return 1;
39
+ let fixedTotal = 0;
40
+ sizes.forEach((size) => {
41
+ if (isFixedSize(size)) fixedTotal += resolveSize(size, true, containerPx, rootFontSize);
42
+ });
43
+ return fixedTotal > containerPx && fixedTotal > 0 ? containerPx / fixedTotal : 1;
44
+ }
45
+ /** Resolves all sizes to pixels at once. Fixed panes get their absolute pixel size, flexible panes
46
+ * share the leftover space by their weight ratio – matching how the layout is rendered with
47
+ * `flex-grow`, so drag math operates on the same pixel sizes the user sees. */
48
+ function resolveWorkingSizes(sizes, pixelMode, containerPx, rootFontSize) {
49
+ if (!pixelMode) return sizes.map((size) => sizeMagnitude(size));
50
+ let fixedTotal = 0;
51
+ let flexibleWeight = 0;
52
+ sizes.forEach((size) => {
53
+ if (isFixedSize(size)) fixedTotal += resolveSize(size, true, containerPx, rootFontSize);
54
+ else flexibleWeight += sizeMagnitude(size);
55
+ });
56
+ const leftover = Math.max(0, containerPx - fixedTotal);
57
+ const fixedScale = getFixedScale(sizes, pixelMode, containerPx, rootFontSize);
58
+ return sizes.map((size) => {
59
+ if (isFixedSize(size)) return resolveSize(size, true, containerPx, rootFontSize) * fixedScale;
60
+ return flexibleWeight > 0 ? sizeMagnitude(size) / flexibleWeight * leftover : 0;
61
+ });
62
+ }
63
+ function encodeSize(value, original, pixelMode, containerPx, rootFontSize, fixedScale = 1) {
64
+ if (!pixelMode) return typeof original === "string" && PERCENT_RE.test(original) ? `${value}%` : value;
65
+ if (typeof original === "number") return containerPx > 0 ? value / containerPx * 100 : original;
66
+ if (PERCENT_RE.test(original)) return `${containerPx > 0 ? value / containerPx * 100 : parseFloat(original)}%`;
67
+ const absolute = fixedScale > 0 ? value / fixedScale : value;
68
+ if (REM_RE.test(original)) return `${rootFontSize > 0 ? absolute / rootFontSize : 0}rem`;
69
+ return `${absolute}px`;
70
+ }
71
+ /** Encodes working pixel sizes back to raw sizes after a resize, keeping the unit each pane was
72
+ * declared in. Panes whose working size did not change keep their original raw value. When fixed
73
+ * panes overflow the container they render down-scaled, so their working sizes are scaled back up to
74
+ * absolute sizes (preserving their declared sizes). If the resize instead hands space to a flexible
75
+ * pane the overflow clears and the layout leaves the down-scaled regime: every pane is then encoded
76
+ * from its current working size – including untouched fixed panes (so they do not jump back to their
77
+ * over-sized value) and untouched flexible panes (so a pane that was squeezed to `0` does not keep a
78
+ * stale weight and steal the freed space on the next render). */
79
+ function encodeWorkingSizes(nextWorking, baseWorking, baseRaw, pixelMode, containerPx, rootFontSize) {
80
+ const fixedScale = getFixedScale(baseRaw, pixelMode, containerPx, rootFontSize);
81
+ let fixedWorkingSum = 0;
82
+ nextWorking.forEach((value, i) => {
83
+ if (isFixedSize(baseRaw[i])) fixedWorkingSum += value;
84
+ });
85
+ const overflowCleared = fixedScale < 1 && fixedWorkingSum < containerPx - 1e-6;
86
+ const encodeScale = overflowCleared ? 1 : fixedScale;
87
+ return nextWorking.map((value, i) => overflowCleared || Math.abs(value - baseWorking[i]) > 1e-6 ? encodeSize(value, baseRaw[i], pixelMode, containerPx, rootFontSize, encodeScale) : baseRaw[i]);
88
+ }
89
+ function resolvePanel(panel, pixelMode, containerPx, rootFontSize) {
90
+ return {
91
+ defaultSize: resolveSize(panel.defaultSize, pixelMode, containerPx, rootFontSize),
92
+ min: panel.min != null ? resolveSize(panel.min, pixelMode, containerPx, rootFontSize) : 0,
93
+ max: panel.max != null ? resolveSize(panel.max, pixelMode, containerPx, rootFontSize) : pixelMode ? containerPx : 100,
94
+ collapseThreshold: panel.collapseThreshold != null ? resolveSize(panel.collapseThreshold, pixelMode, containerPx, rootFontSize) : void 0,
95
+ collapsible: panel.collapsible
96
+ };
97
+ }
98
+ function resolveStep(step, pixelMode, containerPx, rootFontSize) {
99
+ return resolveSize(step, pixelMode, containerPx, rootFontSize);
100
+ }
5
101
  function clamp(value, min, max) {
6
102
  return Math.min(Math.max(value, min), max);
7
103
  }
@@ -9,7 +105,7 @@ function getMin(panel) {
9
105
  return panel.min ?? 0;
10
106
  }
11
107
  function getMax(panel) {
12
- return panel.max ?? 100;
108
+ return panel.max ?? Infinity;
13
109
  }
14
110
  function getCollapseThreshold(panel) {
15
111
  return panel.collapseThreshold ?? getMin(panel);
@@ -20,7 +116,10 @@ function createInitialInternalState() {
20
116
  handleIndex: -1,
21
117
  startPointer: 0,
22
118
  containerSize: 0,
119
+ rootFontSize: 16,
120
+ pixelMode: false,
23
121
  startSizes: [],
122
+ startRaw: [],
24
123
  preCollapseSizes: []
25
124
  };
26
125
  }
@@ -164,8 +263,9 @@ function applyConstraints(sizes, panels, handleIndex, delta, redistribute) {
164
263
  return applyAdjacentOnly(sizes, panels, handleIndex, delta);
165
264
  }
166
265
  function useSplitter(options) {
167
- const { panels, orientation = "horizontal", sizes: controlledSizes, onSizeChange, onCollapseChange, redistribute, step = 1, shiftStep = 10, dir = "ltr", enabled = true } = options;
168
- const defaultSizes = panels.map((p) => p.defaultSize);
266
+ const { panels, orientation = "horizontal", sizes: controlledSizes, onSizeChange, onCollapseChange, redistribute, step = 1, shiftStep = 10, dir = "ltr", resetOnDoubleClick = true, enabled = true } = options;
267
+ const pixelMode = detectPixelMode(options);
268
+ const defaultSizes = panels.map((panel) => panel.defaultSize);
169
269
  const [currentSizes, setCurrentSizes] = require_use_uncontrolled.useUncontrolled({
170
270
  value: controlledSizes,
171
271
  defaultValue: defaultSizes,
@@ -173,63 +273,137 @@ function useSplitter(options) {
173
273
  onChange: onSizeChange
174
274
  });
175
275
  const [activeHandle, setActiveHandle] = (0, react.useState)(-1);
276
+ const [containerSize, setContainerSize] = (0, react.useState)(0);
176
277
  const optionsRef = (0, react.useRef)(options);
177
278
  optionsRef.current = options;
178
279
  const internalStateRef = (0, react.useRef)(createInitialInternalState());
179
280
  const containerRef = (0, react.useRef)(null);
281
+ const containerSizeRef = (0, react.useRef)(0);
282
+ const rootFontSizeRef = (0, react.useRef)(16);
180
283
  const documentControllerRef = (0, react.useRef)(null);
181
284
  const frameRef = (0, react.useRef)(0);
182
285
  const currentSizesRef = (0, react.useRef)(currentSizes);
183
286
  currentSizesRef.current = currentSizes;
184
287
  const preCollapseSizesRef = (0, react.useRef)(defaultSizes);
185
- const collapsed = currentSizes.map((size) => size === 0);
288
+ const collapsed = currentSizes.map((size) => sizeMagnitude(size) === 0);
289
+ const measureContainer = (0, react.useCallback)(() => {
290
+ const node = containerRef.current;
291
+ if (!node) return 0;
292
+ const rect = node.getBoundingClientRect();
293
+ return (optionsRef.current.orientation ?? "horizontal") === "horizontal" ? rect.width : rect.height;
294
+ }, []);
186
295
  const updateSizes = (0, react.useCallback)((newSizes) => {
187
296
  currentSizesRef.current = newSizes;
188
297
  setCurrentSizes(newSizes);
189
298
  }, [setCurrentSizes]);
190
299
  const collapsePanel = (0, react.useCallback)((panelIndex) => {
191
300
  if (!panels[panelIndex]?.collapsible) return;
192
- const sizes = currentSizesRef.current;
193
- if (sizes[panelIndex] === 0) return;
194
- preCollapseSizesRef.current = [...sizes];
195
- const newSizes = [...sizes];
196
- const freedSize = newSizes[panelIndex];
197
- newSizes[panelIndex] = 0;
301
+ const raw = currentSizesRef.current;
302
+ if (sizeMagnitude(raw[panelIndex]) === 0) return;
303
+ const container = pixelMode ? containerSizeRef.current || measureContainer() : 0;
304
+ const rootFontSize = rootFontSizeRef.current;
305
+ const working = resolveWorkingSizes(raw, pixelMode, container, rootFontSize);
306
+ preCollapseSizesRef.current = [...raw];
307
+ const freedSize = working[panelIndex];
308
+ working[panelIndex] = 0;
198
309
  const neighbor = panelIndex === 0 ? 1 : panelIndex - 1;
199
- newSizes[neighbor] += freedSize;
200
- updateSizes(newSizes);
310
+ working[neighbor] += freedSize;
311
+ updateSizes(working.map((value, i) => encodeSize(value, raw[i], pixelMode, container, rootFontSize)));
201
312
  onCollapseChange?.(panelIndex, true);
202
313
  }, [
203
314
  panels,
315
+ pixelMode,
316
+ measureContainer,
204
317
  updateSizes,
205
318
  onCollapseChange
206
319
  ]);
207
320
  const expandPanel = (0, react.useCallback)((panelIndex) => {
208
321
  if (!panels[panelIndex]?.collapsible) return;
209
- const sizes = currentSizesRef.current;
210
- if (sizes[panelIndex] !== 0) return;
211
- const restoreSize = preCollapseSizesRef.current[panelIndex] || panels[panelIndex].defaultSize;
212
- const newSizes = [...sizes];
322
+ const raw = currentSizesRef.current;
323
+ if (sizeMagnitude(raw[panelIndex]) !== 0) return;
324
+ const container = pixelMode ? containerSizeRef.current || measureContainer() : 0;
325
+ const rootFontSize = rootFontSizeRef.current;
326
+ const working = resolveWorkingSizes(raw, pixelMode, container, rootFontSize);
327
+ const preCollapse = preCollapseSizesRef.current;
328
+ const restoreSize = resolveSize(preCollapse[panelIndex] != null && sizeMagnitude(preCollapse[panelIndex]) !== 0 ? preCollapse[panelIndex] : panels[panelIndex].defaultSize, pixelMode, container, rootFontSize);
213
329
  const neighbor = panelIndex === 0 ? 1 : panelIndex - 1;
214
- const available = Math.max(0, newSizes[neighbor] - getMin(panels[neighbor]));
330
+ const neighborMin = panels[neighbor].min != null ? resolveSize(panels[neighbor].min, pixelMode, container, rootFontSize) : 0;
331
+ const available = Math.max(0, working[neighbor] - neighborMin);
215
332
  const actualRestore = Math.min(restoreSize, available);
216
333
  if (actualRestore <= 0) return;
217
- newSizes[panelIndex] = actualRestore;
218
- newSizes[neighbor] -= actualRestore;
219
- updateSizes(newSizes);
334
+ working[panelIndex] = actualRestore;
335
+ working[neighbor] -= actualRestore;
336
+ updateSizes(working.map((value, i) => encodeSize(value, raw[i], pixelMode, container, rootFontSize)));
220
337
  onCollapseChange?.(panelIndex, false);
221
338
  }, [
222
339
  panels,
340
+ pixelMode,
341
+ measureContainer,
223
342
  updateSizes,
224
343
  onCollapseChange
225
344
  ]);
226
345
  const toggleCollapsePanel = (0, react.useCallback)((panelIndex) => {
227
- if (currentSizesRef.current[panelIndex] === 0) expandPanel(panelIndex);
346
+ if (sizeMagnitude(currentSizesRef.current[panelIndex]) === 0) expandPanel(panelIndex);
228
347
  else collapsePanel(panelIndex);
229
348
  }, [collapsePanel, expandPanel]);
349
+ const emitCollapseTransitions = (0, react.useCallback)((prev, next, indices, preCollapseSnapshot) => {
350
+ const onChange = optionsRef.current.onCollapseChange;
351
+ for (const idx of indices) {
352
+ const wasCollapsed = sizeMagnitude(prev[idx]) === 0;
353
+ const nowCollapsed = next[idx] === 0;
354
+ if (!wasCollapsed && nowCollapsed) {
355
+ preCollapseSizesRef.current = [...preCollapseSnapshot];
356
+ onChange?.(idx, true);
357
+ } else if (wasCollapsed && !nowCollapsed) onChange?.(idx, false);
358
+ }
359
+ }, []);
360
+ const reset = (0, react.useCallback)((handleIndex) => {
361
+ const raw = currentSizesRef.current;
362
+ const beforeIdx = handleIndex;
363
+ const afterIdx = handleIndex + 1;
364
+ if (beforeIdx < 0 || afterIdx >= raw.length) return;
365
+ const container = pixelMode ? containerSizeRef.current || measureContainer() : 0;
366
+ const rootFontSize = rootFontSizeRef.current;
367
+ const working = resolveWorkingSizes(raw, pixelMode, container, rootFontSize);
368
+ const resolvedPanels = optionsRef.current.panels.map((panel) => resolvePanel(panel, pixelMode, container, rootFontSize));
369
+ const total = working[beforeIdx] + working[afterIdx];
370
+ const defBefore = resolvedPanels[beforeIdx].defaultSize;
371
+ const defTotal = defBefore + resolvedPanels[afterIdx].defaultSize;
372
+ const next = applyAdjacentOnly(working, resolvedPanels, beforeIdx, (defTotal === 0 ? total / 2 : total * (defBefore / defTotal)) - working[beforeIdx]);
373
+ emitCollapseTransitions(raw, next, [beforeIdx, afterIdx], raw);
374
+ updateSizes(encodeWorkingSizes(next, working, raw, pixelMode, container, rootFontSize));
375
+ }, [
376
+ emitCollapseTransitions,
377
+ updateSizes,
378
+ pixelMode,
379
+ measureContainer
380
+ ]);
230
381
  const containerRefCallback = (0, react.useCallback)((node) => {
231
382
  containerRef.current = node;
232
383
  }, []);
384
+ (0, react.useEffect)(() => {
385
+ if (!pixelMode || typeof ResizeObserver === "undefined") return;
386
+ const node = containerRef.current;
387
+ if (!node) return;
388
+ let frame = 0;
389
+ const update = () => {
390
+ const rect = node.getBoundingClientRect();
391
+ const size = (optionsRef.current.orientation ?? "horizontal") === "horizontal" ? rect.width : rect.height;
392
+ rootFontSizeRef.current = getRootFontSize();
393
+ containerSizeRef.current = size;
394
+ setContainerSize((prev) => prev !== size ? size : prev);
395
+ };
396
+ const observer = new ResizeObserver(() => {
397
+ cancelAnimationFrame(frame);
398
+ frame = requestAnimationFrame(update);
399
+ });
400
+ observer.observe(node);
401
+ update();
402
+ return () => {
403
+ cancelAnimationFrame(frame);
404
+ observer.disconnect();
405
+ };
406
+ }, [pixelMode, orientation]);
233
407
  const handleRefCallbacks = (0, react.useRef)(/* @__PURE__ */ new Map());
234
408
  const handleElementControllers = (0, react.useRef)(/* @__PURE__ */ new Map());
235
409
  const getHandleRefCallback = (0, react.useCallback)((handleIndex) => {
@@ -248,22 +422,28 @@ function useSplitter(options) {
248
422
  if (event.button !== 0) return;
249
423
  const container = containerRef.current;
250
424
  if (!container) return;
425
+ const opts = optionsRef.current;
426
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
251
427
  const rect = container.getBoundingClientRect();
252
- const isHorizontal = (optionsRef.current.orientation ?? "horizontal") === "horizontal";
253
- const containerSize = isHorizontal ? rect.width : rect.height;
428
+ const containerSizePx = isHorizontal ? rect.width : rect.height;
254
429
  const pointerPos = isHorizontal ? event.clientX : event.clientY;
430
+ const isPixelMode = detectPixelMode(opts);
431
+ const rootFontSize = getRootFontSize();
255
432
  const s = internalStateRef.current;
256
433
  s.isDragging = true;
257
434
  s.handleIndex = handleIndex;
258
435
  s.startPointer = pointerPos;
259
- s.containerSize = containerSize;
260
- s.startSizes = [...currentSizesRef.current];
436
+ s.containerSize = containerSizePx;
437
+ s.rootFontSize = rootFontSize;
438
+ s.pixelMode = isPixelMode;
439
+ s.startRaw = [...currentSizesRef.current];
440
+ s.startSizes = resolveWorkingSizes(s.startRaw, isPixelMode, containerSizePx, rootFontSize);
261
441
  s.preCollapseSizes = [...preCollapseSizesRef.current];
262
442
  setActiveHandle(handleIndex);
263
443
  document.body.style.userSelect = "none";
264
444
  document.body.style.webkitUserSelect = "none";
265
445
  document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
266
- optionsRef.current.onResizeStart?.(handleIndex);
446
+ opts.onResizeStart?.(handleIndex);
267
447
  documentControllerRef.current?.abort();
268
448
  documentControllerRef.current = new AbortController();
269
449
  const sig = documentControllerRef.current.signal;
@@ -274,27 +454,19 @@ function useSplitter(options) {
274
454
  const flushResize = (pointerEvent) => {
275
455
  const s = internalStateRef.current;
276
456
  if (!s.containerSize) return;
277
- const isHorizontal = (optionsRef.current.orientation ?? "horizontal") === "horizontal";
278
- const isRtl = isHorizontal && optionsRef.current.dir === "rtl";
279
- const pixelDelta = (isHorizontal ? pointerEvent.clientX : pointerEvent.clientY) - s.startPointer;
280
- const percentDelta = (isRtl ? -pixelDelta : pixelDelta) / s.containerSize * 100;
281
457
  const opts = optionsRef.current;
282
- const newSizes = applyConstraints(s.startSizes, opts.panels, s.handleIndex, percentDelta, opts.redistribute);
458
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
459
+ const isRtl = isHorizontal && opts.dir === "rtl";
460
+ const pointerPos = isHorizontal ? pointerEvent.clientX : pointerEvent.clientY;
461
+ const pixelDelta = (isRtl ? -1 : 1) * (pointerPos - s.startPointer);
462
+ const delta = s.pixelMode ? pixelDelta : pixelDelta / s.containerSize * 100;
463
+ const resolvedPanels = opts.panels.map((panel) => resolvePanel(panel, s.pixelMode, s.containerSize, s.rootFontSize));
464
+ const newSizes = applyConstraints(s.startSizes, resolvedPanels, s.handleIndex, delta, opts.redistribute);
283
465
  const prevSizes = currentSizesRef.current;
284
- const beforeWasCollapsed = prevSizes[s.handleIndex] === 0;
285
- const afterWasCollapsed = prevSizes[s.handleIndex + 1] === 0;
286
- const beforeNowCollapsed = newSizes[s.handleIndex] === 0;
287
- const afterNowCollapsed = newSizes[s.handleIndex + 1] === 0;
288
- if (!beforeWasCollapsed && beforeNowCollapsed) {
289
- preCollapseSizesRef.current = [...s.startSizes];
290
- opts.onCollapseChange?.(s.handleIndex, true);
291
- } else if (beforeWasCollapsed && !beforeNowCollapsed) opts.onCollapseChange?.(s.handleIndex, false);
292
- if (!afterWasCollapsed && afterNowCollapsed) {
293
- preCollapseSizesRef.current = [...s.startSizes];
294
- opts.onCollapseChange?.(s.handleIndex + 1, true);
295
- } else if (afterWasCollapsed && !afterNowCollapsed) opts.onCollapseChange?.(s.handleIndex + 1, false);
296
- currentSizesRef.current = newSizes;
297
- setCurrentSizes(newSizes);
466
+ emitCollapseTransitions(prevSizes, newSizes, [s.handleIndex, s.handleIndex + 1], s.startRaw);
467
+ const encoded = encodeWorkingSizes(newSizes, s.startSizes, s.startRaw, s.pixelMode, s.containerSize, s.rootFontSize);
468
+ currentSizesRef.current = encoded;
469
+ setCurrentSizes(encoded);
298
470
  };
299
471
  const onPointerMove = (event) => {
300
472
  if (!internalStateRef.current.isDragging) return;
@@ -327,9 +499,11 @@ function useSplitter(options) {
327
499
  const getHandleProps = (0, react.useCallback)((input) => {
328
500
  const { index } = input;
329
501
  const orient = orientation;
330
- const beforeSize = currentSizes[index] ?? 0;
331
- const beforePanel = panels[index];
332
- const afterPanel = panels[index + 1];
502
+ const rootFontSize = rootFontSizeRef.current;
503
+ const working = resolveWorkingSizes(currentSizes, pixelMode, containerSize, rootFontSize);
504
+ const resolvedPanels = panels.map((panel) => resolvePanel(panel, pixelMode, containerSize, rootFontSize));
505
+ const beforeSize = working[index] ?? 0;
506
+ const beforePanel = resolvedPanels[index];
333
507
  return {
334
508
  ref: getHandleRefCallback(index),
335
509
  role: "separator",
@@ -342,8 +516,14 @@ function useSplitter(options) {
342
516
  if (!enabled) return;
343
517
  const isHorizontal = orient === "horizontal";
344
518
  const isRtl = dir === "rtl";
519
+ const container = pixelMode ? containerSizeRef.current || measureContainer() : 0;
520
+ const liveRootFontSize = rootFontSizeRef.current;
521
+ const liveWorking = resolveWorkingSizes(currentSizes, pixelMode, container, liveRootFontSize);
522
+ const livePanels = panels.map((panel) => resolvePanel(panel, pixelMode, container, liveRootFontSize));
523
+ const liveBeforePanel = livePanels[index];
524
+ const liveAfterPanel = livePanels[index + 1];
345
525
  let delta = 0;
346
- const currentStep = event.shiftKey ? shiftStep : step;
526
+ const currentStep = resolveStep(event.shiftKey ? shiftStep : step, pixelMode, container, liveRootFontSize);
347
527
  switch (event.key) {
348
528
  case "ArrowLeft":
349
529
  if (!isHorizontal) return;
@@ -362,15 +542,15 @@ function useSplitter(options) {
362
542
  delta = currentStep;
363
543
  break;
364
544
  case "Home":
365
- delta = -(currentSizes[index] - getMin(beforePanel));
545
+ delta = -(liveWorking[index] - getMin(liveBeforePanel));
366
546
  break;
367
547
  case "End":
368
- delta = getMax(beforePanel) - currentSizes[index];
548
+ delta = getMax(liveBeforePanel) - liveWorking[index];
369
549
  break;
370
550
  case "Enter": {
371
- const beforeCollapsible = beforePanel?.collapsible;
372
- const afterCollapsible = afterPanel?.collapsible;
373
- if (beforeCollapsible && currentSizes[index] <= currentSizes[index + 1]) {
551
+ const beforeCollapsible = liveBeforePanel?.collapsible;
552
+ const afterCollapsible = liveAfterPanel?.collapsible;
553
+ if (beforeCollapsible && liveWorking[index] <= liveWorking[index + 1]) {
374
554
  toggleCollapsePanel(index);
375
555
  event.preventDefault();
376
556
  return;
@@ -391,22 +571,15 @@ function useSplitter(options) {
391
571
  }
392
572
  event.preventDefault();
393
573
  if (delta !== 0) {
394
- const newSizes = applyConstraints(currentSizes, panels, index, delta, redistribute);
395
- const beforeWas = currentSizes[index] === 0;
396
- const afterWas = currentSizes[index + 1] === 0;
397
- const beforeNow = newSizes[index] === 0;
398
- const afterNow = newSizes[index + 1] === 0;
399
- if (!beforeWas && beforeNow) {
400
- preCollapseSizesRef.current = [...currentSizes];
401
- onCollapseChange?.(index, true);
402
- } else if (beforeWas && !beforeNow) onCollapseChange?.(index, false);
403
- if (!afterWas && afterNow) {
404
- preCollapseSizesRef.current = [...currentSizes];
405
- onCollapseChange?.(index + 1, true);
406
- } else if (afterWas && !afterNow) onCollapseChange?.(index + 1, false);
407
- updateSizes(newSizes);
574
+ const newSizes = applyConstraints(liveWorking, livePanels, index, delta, redistribute);
575
+ emitCollapseTransitions(currentSizes, newSizes, [index, index + 1], currentSizes);
576
+ updateSizes(encodeWorkingSizes(newSizes, liveWorking, currentSizes, pixelMode, container, liveRootFontSize));
408
577
  }
409
578
  },
579
+ onDoubleClick: () => {
580
+ if (!enabled || !resetOnDoubleClick) return;
581
+ reset(index);
582
+ },
410
583
  "data-active": activeHandle === index || void 0,
411
584
  "data-orientation": orient
412
585
  };
@@ -414,16 +587,21 @@ function useSplitter(options) {
414
587
  orientation,
415
588
  currentSizes,
416
589
  panels,
590
+ pixelMode,
591
+ containerSize,
417
592
  enabled,
418
593
  dir,
419
594
  step,
420
595
  shiftStep,
596
+ resetOnDoubleClick,
421
597
  activeHandle,
422
598
  redistribute,
599
+ measureContainer,
423
600
  getHandleRefCallback,
424
601
  toggleCollapsePanel,
425
602
  updateSizes,
426
- onCollapseChange
603
+ emitCollapseTransitions,
604
+ reset
427
605
  ]);
428
606
  (0, react.useEffect)(() => () => {
429
607
  documentControllerRef.current?.abort();
@@ -441,13 +619,15 @@ function useSplitter(options) {
441
619
  return {
442
620
  ref: containerRefCallback,
443
621
  sizes: currentSizes,
622
+ pixelMode,
444
623
  collapsed,
445
624
  activeHandle,
446
625
  getHandleProps,
447
626
  setSizes: updateSizes,
448
627
  collapse: collapsePanel,
449
628
  expand: expandPanel,
450
- toggleCollapse: toggleCollapsePanel
629
+ toggleCollapse: toggleCollapsePanel,
630
+ reset
451
631
  };
452
632
  }
453
633
  //#endregion