@mantine/hooks 9.3.2 → 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 +226 -55
  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 +226 -55
  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 +48 -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
  }
@@ -165,7 +264,8 @@ function applyConstraints(sizes, panels, handleIndex, delta, redistribute) {
165
264
  }
166
265
  function useSplitter(options) {
167
266
  const { panels, orientation = "horizontal", sizes: controlledSizes, onSizeChange, onCollapseChange, redistribute, step = 1, shiftStep = 10, dir = "ltr", resetOnDoubleClick = true, enabled = true } = options;
168
- const defaultSizes = panels.map((p) => p.defaultSize);
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,64 +273,83 @@ 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]);
230
349
  const emitCollapseTransitions = (0, react.useCallback)((prev, next, indices, preCollapseSnapshot) => {
231
350
  const onChange = optionsRef.current.onCollapseChange;
232
351
  for (const idx of indices) {
233
- const wasCollapsed = prev[idx] === 0;
352
+ const wasCollapsed = sizeMagnitude(prev[idx]) === 0;
234
353
  const nowCollapsed = next[idx] === 0;
235
354
  if (!wasCollapsed && nowCollapsed) {
236
355
  preCollapseSizesRef.current = [...preCollapseSnapshot];
@@ -239,21 +358,52 @@ function useSplitter(options) {
239
358
  }
240
359
  }, []);
241
360
  const reset = (0, react.useCallback)((handleIndex) => {
242
- const prev = currentSizesRef.current;
243
- const currentPanels = optionsRef.current.panels;
361
+ const raw = currentSizesRef.current;
244
362
  const beforeIdx = handleIndex;
245
363
  const afterIdx = handleIndex + 1;
246
- if (beforeIdx < 0 || afterIdx >= prev.length) return;
247
- const total = prev[beforeIdx] + prev[afterIdx];
248
- const defBefore = currentPanels[beforeIdx].defaultSize;
249
- const defTotal = defBefore + currentPanels[afterIdx].defaultSize;
250
- const next = applyAdjacentOnly(prev, currentPanels, beforeIdx, (defTotal === 0 ? total / 2 : total * (defBefore / defTotal)) - prev[beforeIdx]);
251
- emitCollapseTransitions(prev, next, [beforeIdx, afterIdx], prev);
252
- updateSizes(next);
253
- }, [emitCollapseTransitions, updateSizes]);
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
+ ]);
254
381
  const containerRefCallback = (0, react.useCallback)((node) => {
255
382
  containerRef.current = node;
256
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]);
257
407
  const handleRefCallbacks = (0, react.useRef)(/* @__PURE__ */ new Map());
258
408
  const handleElementControllers = (0, react.useRef)(/* @__PURE__ */ new Map());
259
409
  const getHandleRefCallback = (0, react.useCallback)((handleIndex) => {
@@ -272,22 +422,28 @@ function useSplitter(options) {
272
422
  if (event.button !== 0) return;
273
423
  const container = containerRef.current;
274
424
  if (!container) return;
425
+ const opts = optionsRef.current;
426
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
275
427
  const rect = container.getBoundingClientRect();
276
- const isHorizontal = (optionsRef.current.orientation ?? "horizontal") === "horizontal";
277
- const containerSize = isHorizontal ? rect.width : rect.height;
428
+ const containerSizePx = isHorizontal ? rect.width : rect.height;
278
429
  const pointerPos = isHorizontal ? event.clientX : event.clientY;
430
+ const isPixelMode = detectPixelMode(opts);
431
+ const rootFontSize = getRootFontSize();
279
432
  const s = internalStateRef.current;
280
433
  s.isDragging = true;
281
434
  s.handleIndex = handleIndex;
282
435
  s.startPointer = pointerPos;
283
- s.containerSize = containerSize;
284
- 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);
285
441
  s.preCollapseSizes = [...preCollapseSizesRef.current];
286
442
  setActiveHandle(handleIndex);
287
443
  document.body.style.userSelect = "none";
288
444
  document.body.style.webkitUserSelect = "none";
289
445
  document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
290
- optionsRef.current.onResizeStart?.(handleIndex);
446
+ opts.onResizeStart?.(handleIndex);
291
447
  documentControllerRef.current?.abort();
292
448
  documentControllerRef.current = new AbortController();
293
449
  const sig = documentControllerRef.current.signal;
@@ -298,16 +454,19 @@ function useSplitter(options) {
298
454
  const flushResize = (pointerEvent) => {
299
455
  const s = internalStateRef.current;
300
456
  if (!s.containerSize) return;
301
- const isHorizontal = (optionsRef.current.orientation ?? "horizontal") === "horizontal";
302
- const isRtl = isHorizontal && optionsRef.current.dir === "rtl";
303
- const pixelDelta = (isHorizontal ? pointerEvent.clientX : pointerEvent.clientY) - s.startPointer;
304
- const percentDelta = (isRtl ? -pixelDelta : pixelDelta) / s.containerSize * 100;
305
457
  const opts = optionsRef.current;
306
- 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);
307
465
  const prevSizes = currentSizesRef.current;
308
- emitCollapseTransitions(prevSizes, newSizes, [s.handleIndex, s.handleIndex + 1], s.startSizes);
309
- currentSizesRef.current = newSizes;
310
- 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);
311
470
  };
312
471
  const onPointerMove = (event) => {
313
472
  if (!internalStateRef.current.isDragging) return;
@@ -340,9 +499,11 @@ function useSplitter(options) {
340
499
  const getHandleProps = (0, react.useCallback)((input) => {
341
500
  const { index } = input;
342
501
  const orient = orientation;
343
- const beforeSize = currentSizes[index] ?? 0;
344
- const beforePanel = panels[index];
345
- 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];
346
507
  return {
347
508
  ref: getHandleRefCallback(index),
348
509
  role: "separator",
@@ -355,8 +516,14 @@ function useSplitter(options) {
355
516
  if (!enabled) return;
356
517
  const isHorizontal = orient === "horizontal";
357
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];
358
525
  let delta = 0;
359
- const currentStep = event.shiftKey ? shiftStep : step;
526
+ const currentStep = resolveStep(event.shiftKey ? shiftStep : step, pixelMode, container, liveRootFontSize);
360
527
  switch (event.key) {
361
528
  case "ArrowLeft":
362
529
  if (!isHorizontal) return;
@@ -375,15 +542,15 @@ function useSplitter(options) {
375
542
  delta = currentStep;
376
543
  break;
377
544
  case "Home":
378
- delta = -(currentSizes[index] - getMin(beforePanel));
545
+ delta = -(liveWorking[index] - getMin(liveBeforePanel));
379
546
  break;
380
547
  case "End":
381
- delta = getMax(beforePanel) - currentSizes[index];
548
+ delta = getMax(liveBeforePanel) - liveWorking[index];
382
549
  break;
383
550
  case "Enter": {
384
- const beforeCollapsible = beforePanel?.collapsible;
385
- const afterCollapsible = afterPanel?.collapsible;
386
- 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]) {
387
554
  toggleCollapsePanel(index);
388
555
  event.preventDefault();
389
556
  return;
@@ -404,9 +571,9 @@ function useSplitter(options) {
404
571
  }
405
572
  event.preventDefault();
406
573
  if (delta !== 0) {
407
- const newSizes = applyConstraints(currentSizes, panels, index, delta, redistribute);
574
+ const newSizes = applyConstraints(liveWorking, livePanels, index, delta, redistribute);
408
575
  emitCollapseTransitions(currentSizes, newSizes, [index, index + 1], currentSizes);
409
- updateSizes(newSizes);
576
+ updateSizes(encodeWorkingSizes(newSizes, liveWorking, currentSizes, pixelMode, container, liveRootFontSize));
410
577
  }
411
578
  },
412
579
  onDoubleClick: () => {
@@ -420,6 +587,8 @@ function useSplitter(options) {
420
587
  orientation,
421
588
  currentSizes,
422
589
  panels,
590
+ pixelMode,
591
+ containerSize,
423
592
  enabled,
424
593
  dir,
425
594
  step,
@@ -427,6 +596,7 @@ function useSplitter(options) {
427
596
  resetOnDoubleClick,
428
597
  activeHandle,
429
598
  redistribute,
599
+ measureContainer,
430
600
  getHandleRefCallback,
431
601
  toggleCollapsePanel,
432
602
  updateSizes,
@@ -449,6 +619,7 @@ function useSplitter(options) {
449
619
  return {
450
620
  ref: containerRefCallback,
451
621
  sizes: currentSizes,
622
+ pixelMode,
452
623
  collapsed,
453
624
  activeHandle,
454
625
  getHandleProps,