@zag-js/splitter 1.4.1 → 1.5.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.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createAnatomy } from '@zag-js/anatomy';
2
- import { getRelativePoint, raf, trackPointerMove, dataAttr, getEventStep, getEventKey } from '@zag-js/dom-query';
3
- import { setRafTimeout, createSplitProps, ensure } from '@zag-js/utils';
2
+ import { trackPointerMove, queryAll, dataAttr, getEventKey, getEventPoint } from '@zag-js/dom-query';
3
+ import { prev, next, ensure, isEqual, setRafTimeout, ensureProps, createSplitProps } from '@zag-js/utils';
4
4
  import { createMachine } from '@zag-js/core';
5
5
  import { createProps } from '@zag-js/types';
6
6
 
@@ -19,17 +19,17 @@ var getCursor = (state, x) => {
19
19
  if (state.isAtMax) cursor = x ? "w-resize" : "n-resize";
20
20
  return cursor;
21
21
  };
22
- var getPanelStyle = (panels, id) => {
23
- const flexGrow = panels.find((panel) => panel.id === id)?.size ?? "0";
24
- return { flexBasis: 0, flexGrow, flexShrink: 1, overflow: "hidden" };
22
+ var getResizeTriggerEls = (ctx) => {
23
+ return queryAll(getRootEl(ctx), `[role=separator][data-ownedby='${CSS.escape(getRootId(ctx))}']`);
25
24
  };
26
- var setupGlobalCursor = (ctx, state, x) => {
25
+ var setupGlobalCursor = (ctx, state, x, nonce) => {
27
26
  const styleEl = ctx.getById(getGlobalCursorId(ctx));
28
27
  const textContent = `* { cursor: ${getCursor(state, x)} !important; }`;
29
28
  if (styleEl) {
30
29
  styleEl.textContent = textContent;
31
30
  } else {
32
31
  const style = ctx.getDoc().createElement("style");
32
+ if (nonce) style.nonce = nonce;
33
33
  style.id = getGlobalCursorId(ctx);
34
34
  style.textContent = textContent;
35
35
  ctx.getDoc().head.appendChild(style);
@@ -39,170 +39,255 @@ var removeGlobalCursor = (ctx) => {
39
39
  const styleEl = ctx.getById(getGlobalCursorId(ctx));
40
40
  styleEl?.remove();
41
41
  };
42
-
43
- // src/splitter.utils.ts
44
- function validateSize(key, size) {
45
- if (Math.floor(size) > 100) {
46
- throw new Error(`Total ${key} of panels cannot be greater than 100`);
47
- }
48
- }
49
- function getNormalizedPanels(sizes) {
50
- let numOfPanelsWithoutSize = 0;
51
- let totalSize = 0;
42
+ function calculateAriaValues({
43
+ size,
44
+ panels,
45
+ pivotIndices
46
+ }) {
47
+ let currentMinSize = 0;
48
+ let currentMaxSize = 100;
52
49
  let totalMinSize = 0;
53
- const panels = sizes.map((panel) => {
54
- const minSize = panel.minSize ?? 0;
55
- const maxSize = panel.maxSize ?? 100;
56
- totalMinSize += minSize;
57
- if (panel.size == null) {
58
- numOfPanelsWithoutSize++;
50
+ let totalMaxSize = 0;
51
+ const firstIndex = pivotIndices[0];
52
+ ensure(firstIndex, "No pivot index found");
53
+ panels.forEach((panel, index) => {
54
+ const { maxSize = 100, minSize = 0 } = panel;
55
+ if (index === firstIndex) {
56
+ currentMinSize = minSize;
57
+ currentMaxSize = maxSize;
59
58
  } else {
60
- totalSize += panel.size;
59
+ totalMinSize += minSize;
60
+ totalMaxSize += maxSize;
61
61
  }
62
- return {
63
- ...panel,
64
- minSize,
65
- maxSize
66
- };
67
62
  });
68
- validateSize("minSize", totalMinSize);
69
- validateSize("size", totalSize);
70
- let end = 0;
71
- let remainingSize = 0;
72
- const result = panels.map((panel) => {
73
- let start = end;
74
- if (panel.size != null) {
75
- end += panel.size;
76
- remainingSize = panel.size - panel.minSize;
77
- return {
78
- ...panel,
79
- start,
80
- end,
81
- remainingSize
82
- };
83
- }
84
- const size = (100 - totalSize) / numOfPanelsWithoutSize;
85
- end += size;
86
- remainingSize = size - panel.minSize;
87
- return { ...panel, size, start, end, remainingSize };
88
- });
89
- return result;
63
+ const valueMax = Math.min(currentMaxSize, 100 - totalMinSize);
64
+ const valueMin = Math.max(currentMinSize, 100 - totalMaxSize);
65
+ const valueNow = size[firstIndex];
66
+ return {
67
+ valueMax,
68
+ valueMin,
69
+ valueNow
70
+ };
90
71
  }
91
- function getHandlePanels(panels, id) {
92
- const [beforeId, afterId] = id?.split(":") ?? [];
93
- if (!beforeId || !afterId) return;
72
+ function getAriaValue(size, panels, handleId) {
73
+ const [beforeId, afterId] = handleId.split(":");
94
74
  const beforeIndex = panels.findIndex((panel) => panel.id === beforeId);
95
75
  const afterIndex = panels.findIndex((panel) => panel.id === afterId);
96
- if (beforeIndex === -1 || afterIndex === -1) return;
97
- const before = panels[beforeIndex];
98
- const after = panels[afterIndex];
76
+ const { valueMax, valueMin, valueNow } = calculateAriaValues({
77
+ size,
78
+ panels,
79
+ pivotIndices: [beforeIndex, afterIndex]
80
+ });
99
81
  return {
100
- before: {
101
- ...before,
102
- index: beforeIndex
103
- },
104
- after: {
105
- ...after,
106
- index: afterIndex
107
- }
82
+ beforeId,
83
+ afterId,
84
+ valueMax: Math.round(valueMax),
85
+ valueMin: Math.round(valueMin),
86
+ valueNow: valueNow != null ? Math.round(valueNow) : void 0
108
87
  };
109
88
  }
110
- function getHandleBounds(panels, id) {
111
- const handlePanels = getHandlePanels(panels, id);
112
- if (!handlePanels) return;
113
- const { before, after } = handlePanels;
114
- return {
115
- min: Math.max(before.start + before.minSize, after.end - after.maxSize),
116
- max: Math.min(after.end - after.minSize, before.maxSize + before.start)
117
- };
89
+
90
+ // src/utils/fuzzy.ts
91
+ var PRECISION = 10;
92
+ function fuzzyCompareNumbers(actual, expected, fractionDigits = PRECISION) {
93
+ if (actual.toFixed(fractionDigits) === expected.toFixed(fractionDigits)) {
94
+ return 0;
95
+ } else {
96
+ return actual > expected ? 1 : -1;
97
+ }
118
98
  }
119
- function getPanelBounds(panels, id) {
120
- const bounds = getHandleBounds(panels, id);
121
- const handlePanels = getHandlePanels(panels, id);
122
- if (!bounds || !handlePanels) return;
123
- const { before, after } = handlePanels;
124
- const beforeMin = Math.abs(before.start - bounds.min);
125
- const afterMin = after.size + (before.size - beforeMin);
126
- const beforeMax = Math.abs(before.start - bounds.max);
127
- const afterMax = after.size - (beforeMax - before.size);
128
- return {
129
- before: {
130
- index: before.index,
131
- min: beforeMin,
132
- max: beforeMax,
133
- isAtMin: beforeMin === before.size,
134
- isAtMax: beforeMax === before.size,
135
- up(step) {
136
- return Math.min(before.size + step, beforeMax);
137
- },
138
- down(step) {
139
- return Math.max(before.size - step, beforeMin);
140
- }
141
- },
142
- after: {
143
- index: after.index,
144
- min: afterMin,
145
- max: afterMax,
146
- isAtMin: afterMin === after.size,
147
- isAtMax: afterMax === after.size,
148
- up(step) {
149
- return Math.min(after.size + step, afterMin);
150
- },
151
- down(step) {
152
- return Math.max(after.size - step, afterMax);
153
- }
99
+ function fuzzyNumbersEqual(actual, expected, fractionDigits = PRECISION) {
100
+ if (actual == null || expected == null) return false;
101
+ return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
102
+ }
103
+ function fuzzySizeEqual(actual, expected, fractionDigits) {
104
+ if (actual.length !== expected.length) {
105
+ return false;
106
+ }
107
+ for (let index = 0; index < actual.length; index++) {
108
+ const actualSize = actual[index];
109
+ const expectedSize = expected[index];
110
+ if (!fuzzyNumbersEqual(actualSize, expectedSize, fractionDigits)) {
111
+ return false;
154
112
  }
113
+ }
114
+ return true;
115
+ }
116
+ function getPanelById(panels, id) {
117
+ const panel = panels.find((panel2) => panel2.id === id);
118
+ ensure(panel, `Panel data not found for id "${id}"`);
119
+ return panel;
120
+ }
121
+ function findPanelDataIndex(panels, panel) {
122
+ return panels.findIndex((prevPanel) => prevPanel === panel || prevPanel.id === panel.id);
123
+ }
124
+ function findPanelIndex(panels, id) {
125
+ return panels.findIndex((panel) => panel.id === id);
126
+ }
127
+ function panelDataHelper(panels, panel, sizes) {
128
+ const index = findPanelIndex(panels, panel.id);
129
+ const pivotIndices = index === panels.length - 1 ? [index - 1, index] : [index, index + 1];
130
+ const panelSize = sizes[index];
131
+ return { ...panel, panelSize, pivotIndices };
132
+ }
133
+ function sortPanels(panels) {
134
+ return panels.sort((panelA, panelB) => {
135
+ const orderA = panelA.order;
136
+ const orderB = panelB.order;
137
+ if (orderA == null && orderB == null) {
138
+ return 0;
139
+ } else if (orderA == null) {
140
+ return -1;
141
+ } else if (orderB == null) {
142
+ return 1;
143
+ } else {
144
+ return orderA - orderB;
145
+ }
146
+ });
147
+ }
148
+ function getPanelLayout(panels) {
149
+ return panels.map((panel) => panel.id).sort().join(":");
150
+ }
151
+ function serializePanels(panels) {
152
+ const keys = panels.map((panel) => panel.id);
153
+ const sortedKeys = keys.sort();
154
+ const serialized = sortedKeys.map((key) => {
155
+ const panel = panels.find((panel2) => panel2.id === key);
156
+ return JSON.stringify(panel);
157
+ });
158
+ return serialized.join(",");
159
+ }
160
+ function getPanelFlexBoxStyle({
161
+ defaultSize,
162
+ dragState,
163
+ sizes,
164
+ panels,
165
+ panelIndex,
166
+ precision = 3
167
+ }) {
168
+ const size = sizes[panelIndex];
169
+ let flexGrow;
170
+ if (size == null) {
171
+ flexGrow = defaultSize != void 0 ? defaultSize.toPrecision(precision) : "1";
172
+ } else if (panels.length === 1) {
173
+ flexGrow = "1";
174
+ } else {
175
+ flexGrow = size.toPrecision(precision);
176
+ }
177
+ return {
178
+ flexBasis: 0,
179
+ flexGrow,
180
+ flexShrink: 1,
181
+ // Without this, Panel sizes may be unintentionally overridden by their content
182
+ overflow: "hidden",
183
+ // Disable pointer events inside of a panel during resize
184
+ // This avoid edge cases like nested iframes
185
+ pointerEvents: dragState !== null ? "none" : void 0
155
186
  };
156
187
  }
157
- function clamp(value, min, max) {
158
- return Math.min(Math.max(value, min), max);
188
+ function getUnsafeDefaultSize({ panels, size: sizes }) {
189
+ const finalSizes = Array(panels.length);
190
+ let numPanelsWithSizes = 0;
191
+ let remainingSize = 100;
192
+ for (let index = 0; index < panels.length; index++) {
193
+ const panel = panels[index];
194
+ ensure(panel, `Panel data not found for index ${index}`);
195
+ const defaultSize = sizes[index];
196
+ if (defaultSize != null) {
197
+ numPanelsWithSizes++;
198
+ finalSizes[index] = defaultSize;
199
+ remainingSize -= defaultSize;
200
+ }
201
+ }
202
+ for (let index = 0; index < panels.length; index++) {
203
+ const panel = panels[index];
204
+ ensure(panel, `Panel data not found for index ${index}`);
205
+ const defaultSize = sizes[index];
206
+ if (defaultSize != null) {
207
+ continue;
208
+ }
209
+ const numRemainingPanels = panels.length - numPanelsWithSizes;
210
+ const size = remainingSize / numRemainingPanels;
211
+ numPanelsWithSizes++;
212
+ finalSizes[index] = size;
213
+ remainingSize -= size;
214
+ }
215
+ return finalSizes;
159
216
  }
160
217
 
161
218
  // src/splitter.connect.ts
162
219
  function connect(service, normalize) {
163
220
  const { state, send, prop, computed, context, scope } = service;
164
- const horizontal = computed("isHorizontal");
165
- const focused = state.hasTag("focus");
221
+ const horizontal = computed("horizontal");
166
222
  const dragging = state.matches("dragging");
167
- const panels = computed("panels");
168
- const activeResizeId = context.get("activeResizeId");
169
- function getResizeTriggerState(props2) {
170
- const { id, disabled } = props2;
171
- const ids = id.split(":");
172
- const panelIds = ids.map((id2) => getPanelId(scope, id2));
173
- const panels2 = getHandleBounds(computed("panels"), id);
174
- return {
175
- disabled: !!disabled,
176
- focused: activeResizeId === id && focused,
177
- panelIds,
178
- min: panels2?.min,
179
- max: panels2?.max,
180
- value: 0
181
- };
182
- }
223
+ const getPanelStyle = (id) => {
224
+ const panels = prop("panels");
225
+ const panelIndex = panels.findIndex((panel) => panel.id === id);
226
+ const defaultSize = context.initial("size")[panelIndex];
227
+ const dragState = context.get("dragState");
228
+ return getPanelFlexBoxStyle({
229
+ defaultSize,
230
+ dragState,
231
+ sizes: context.get("size"),
232
+ panels,
233
+ panelIndex
234
+ });
235
+ };
183
236
  return {
184
- focused,
185
237
  dragging,
186
- getResizeTriggerState,
187
- bounds: getHandleBounds(computed("panels"), activeResizeId),
188
- setToMinSize(id) {
189
- const panel = panels.find((panel2) => panel2.id === id);
190
- send({ type: "SIZE.SET", id, size: panel?.minSize, src: "setToMinSize" });
238
+ getItems() {
239
+ return prop("panels").flatMap((panel, index, arr) => {
240
+ const nextPanel = arr[index + 1];
241
+ if (panel && nextPanel) {
242
+ return [
243
+ { type: "panel", id: panel.id },
244
+ { type: "handle", id: `${panel.id}:${nextPanel.id}` }
245
+ ];
246
+ }
247
+ return [{ type: "panel", id: panel.id }];
248
+ });
249
+ },
250
+ getSizes() {
251
+ return context.get("size");
191
252
  },
192
- setToMaxSize(id) {
193
- const panel = panels.find((panel2) => panel2.id === id);
194
- send({ type: "SIZE.SET", id, size: panel?.maxSize, src: "setToMaxSize" });
253
+ setSizes(size) {
254
+ send({ type: "SIZE.SET", size });
195
255
  },
196
- setSize(id, size) {
197
- send({ type: "SIZE.SET", id, size });
256
+ collapsePanel(id) {
257
+ send({ type: "PANEL.COLLAPSE", id });
198
258
  },
199
- setSizes(sizes) {
200
- send({ type: "SIZES.SET", sizes });
259
+ expandPanel(id, minSize) {
260
+ send({ type: "PANEL.EXPAND", id, minSize });
201
261
  },
202
- getSize(id) {
203
- const panel = panels.find((panel2) => panel2.id === id);
204
- ensure(panel, `Panel with id ${id} not found`);
205
- return panel.size;
262
+ resizePanel(id, unsafePanelSize) {
263
+ send({ type: "PANEL.RESIZE", id, size: unsafePanelSize });
264
+ },
265
+ getPanelSize(id) {
266
+ const panels = prop("panels");
267
+ const size = context.get("size");
268
+ const panelData = getPanelById(panels, id);
269
+ const { panelSize } = panelDataHelper(panels, panelData, size);
270
+ ensure(panelSize, `Panel size not found for panel "${panelData.id}"`);
271
+ return panelSize;
272
+ },
273
+ isPanelCollapsed(id) {
274
+ const panels = prop("panels");
275
+ const size = context.get("size");
276
+ const panelData = getPanelById(panels, id);
277
+ const { collapsedSize = 0, collapsible, panelSize } = panelDataHelper(panels, panelData, size);
278
+ ensure(panelSize, `Panel size not found for panel "${panelData.id}"`);
279
+ return collapsible === true && fuzzyNumbersEqual(panelSize, collapsedSize);
280
+ },
281
+ isPanelExpanded(id) {
282
+ const panels = prop("panels");
283
+ const size = context.get("size");
284
+ const panelData = getPanelById(panels, id);
285
+ const { collapsedSize = 0, collapsible, panelSize } = panelDataHelper(panels, panelData, size);
286
+ ensure(panelSize, `Panel size not found for panel "${panelData.id}"`);
287
+ return !collapsible || fuzzyCompareNumbers(panelSize, collapsedSize) > 0;
288
+ },
289
+ getLayout() {
290
+ return getPanelLayout(prop("panels"));
206
291
  },
207
292
  getRootProps() {
208
293
  return normalize.element({
@@ -225,44 +310,51 @@ function connect(service, normalize) {
225
310
  ...parts.panel.attrs,
226
311
  "data-orientation": prop("orientation"),
227
312
  dir: prop("dir"),
313
+ "data-id": id,
314
+ "data-index": findPanelIndex(prop("panels"), id),
228
315
  id: getPanelId(scope, id),
229
316
  "data-ownedby": getRootId(scope),
230
- style: getPanelStyle(panels, id)
317
+ style: getPanelStyle(id)
231
318
  });
232
319
  },
233
320
  getResizeTriggerProps(props2) {
234
- const { id, disabled, step = 1 } = props2;
235
- const triggerState = getResizeTriggerState(props2);
321
+ const { id, disabled } = props2;
322
+ const aria = getAriaValue(context.get("size"), prop("panels"), id);
323
+ const dragging2 = context.get("dragState")?.resizeTriggerId === id;
324
+ const focused = dragging2 || context.get("keyboardState")?.resizeTriggerId === id;
236
325
  return normalize.element({
237
326
  ...parts.resizeTrigger.attrs,
238
327
  dir: prop("dir"),
239
328
  id: getResizeTriggerId(scope, id),
240
329
  role: "separator",
330
+ "data-id": id,
241
331
  "data-ownedby": getRootId(scope),
242
332
  tabIndex: disabled ? void 0 : 0,
243
- "aria-valuenow": triggerState.value,
244
- "aria-valuemin": triggerState.min,
245
- "aria-valuemax": triggerState.max,
333
+ "aria-valuenow": aria.valueNow,
334
+ "aria-valuemin": aria.valueMin,
335
+ "aria-valuemax": aria.valueMax,
246
336
  "data-orientation": prop("orientation"),
247
337
  "aria-orientation": prop("orientation"),
248
- "aria-controls": triggerState.panelIds.join(" "),
249
- "data-focus": dataAttr(triggerState.focused),
338
+ "aria-controls": `${getPanelId(scope, aria.beforeId)} ${getPanelId(scope, aria.afterId)}`,
339
+ "data-focus": dataAttr(focused),
250
340
  "data-disabled": dataAttr(disabled),
251
341
  style: {
252
342
  touchAction: "none",
253
343
  userSelect: "none",
254
344
  WebkitUserSelect: "none",
255
345
  flex: "0 0 auto",
256
- pointerEvents: dragging && !triggerState.focused ? "none" : void 0,
346
+ pointerEvents: dragging2 && !focused ? "none" : void 0,
257
347
  cursor: horizontal ? "col-resize" : "row-resize",
258
348
  [horizontal ? "minHeight" : "minWidth"]: "0"
259
349
  },
260
350
  onPointerDown(event) {
351
+ if (event.button !== 0) return;
261
352
  if (disabled) {
262
353
  event.preventDefault();
263
354
  return;
264
355
  }
265
- send({ type: "POINTER_DOWN", id });
356
+ const point = getEventPoint(event);
357
+ send({ type: "POINTER_DOWN", id, point });
266
358
  event.currentTarget.setPointerCapture(event.pointerId);
267
359
  event.preventDefault();
268
360
  event.stopPropagation();
@@ -282,40 +374,49 @@ function connect(service, normalize) {
282
374
  send({ type: "POINTER_LEAVE", id });
283
375
  },
284
376
  onBlur() {
377
+ if (disabled) return;
285
378
  send({ type: "BLUR" });
286
379
  },
287
380
  onFocus() {
288
- send({ type: "FOCUS", id });
289
- },
290
- onDoubleClick() {
291
381
  if (disabled) return;
292
- send({ type: "DOUBLE_CLICK", id });
382
+ send({ type: "FOCUS", id });
293
383
  },
294
384
  onKeyDown(event) {
295
385
  if (event.defaultPrevented) return;
296
386
  if (disabled) return;
297
- const moveStep = getEventStep(event) * step;
387
+ const keyboardResizeBy = prop("keyboardResizeBy");
388
+ let delta = 0;
389
+ if (event.shiftKey) {
390
+ delta = 10;
391
+ } else if (keyboardResizeBy != null) {
392
+ delta = keyboardResizeBy;
393
+ } else {
394
+ delta = 1;
395
+ }
298
396
  const keyMap = {
299
397
  Enter() {
300
- send({ type: "ENTER" });
398
+ send({ type: "ENTER", id });
301
399
  },
302
400
  ArrowUp() {
303
- send({ type: "ARROW_UP", step: moveStep });
401
+ send({ type: "KEYBOARD_MOVE", id, delta: horizontal ? 0 : delta });
304
402
  },
305
403
  ArrowDown() {
306
- send({ type: "ARROW_DOWN", step: moveStep });
404
+ send({ type: "KEYBOARD_MOVE", id, delta: horizontal ? 0 : delta });
307
405
  },
308
406
  ArrowLeft() {
309
- send({ type: "ARROW_LEFT", step: moveStep });
407
+ send({ type: "KEYBOARD_MOVE", id, delta: horizontal ? -delta : 0 });
310
408
  },
311
409
  ArrowRight() {
312
- send({ type: "ARROW_RIGHT", step: moveStep });
410
+ send({ type: "KEYBOARD_MOVE", id, delta: horizontal ? delta : 0 });
313
411
  },
314
412
  Home() {
315
- send({ type: "HOME" });
413
+ send({ type: "KEYBOARD_MOVE", id, delta: -100 });
316
414
  },
317
415
  End() {
318
- send({ type: "END" });
416
+ send({ type: "KEYBOARD_MOVE", id, delta: 100 });
417
+ },
418
+ F6() {
419
+ send({ type: "FOCUS.CYCLE", id, shiftKey: event.shiftKey });
319
420
  }
320
421
  };
321
422
  const key = getEventKey(event, {
@@ -332,81 +433,295 @@ function connect(service, normalize) {
332
433
  }
333
434
  };
334
435
  }
436
+ function resizePanel({ panels, index, size }) {
437
+ const panel = panels[index];
438
+ ensure(panel, `Panel data not found for index ${index}`);
439
+ let { collapsedSize = 0, collapsible, maxSize = 100, minSize = 0 } = panel;
440
+ if (fuzzyCompareNumbers(size, minSize) < 0) {
441
+ if (collapsible) {
442
+ const halfwayPoint = (collapsedSize + minSize) / 2;
443
+ if (fuzzyCompareNumbers(size, halfwayPoint) < 0) {
444
+ size = collapsedSize;
445
+ } else {
446
+ size = minSize;
447
+ }
448
+ } else {
449
+ size = minSize;
450
+ }
451
+ }
452
+ size = Math.min(maxSize, size);
453
+ size = parseFloat(size.toFixed(PRECISION));
454
+ return size;
455
+ }
456
+
457
+ // src/utils/resize-by-delta.ts
458
+ function resizeByDelta(props2) {
459
+ let { delta, initialSize, panels, pivotIndices, prevSize, trigger } = props2;
460
+ if (fuzzyNumbersEqual(delta, 0)) {
461
+ return initialSize;
462
+ }
463
+ const nextSize = [...initialSize];
464
+ const [firstPivotIndex, secondPivotIndex] = pivotIndices;
465
+ ensure(firstPivotIndex, "Invalid first pivot index");
466
+ ensure(secondPivotIndex, "Invalid second pivot index");
467
+ let deltaApplied = 0;
468
+ {
469
+ if (trigger === "keyboard") {
470
+ {
471
+ const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
472
+ const panel = panels[index];
473
+ ensure(panel, `Panel data not found for index ${index}`);
474
+ const { collapsedSize = 0, collapsible, minSize = 0 } = panel;
475
+ if (collapsible) {
476
+ const prevSize2 = initialSize[index];
477
+ ensure(prevSize2, `Previous size not found for panel index ${index}`);
478
+ if (fuzzyNumbersEqual(prevSize2, collapsedSize)) {
479
+ const localDelta = minSize - prevSize2;
480
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
481
+ delta = delta < 0 ? 0 - localDelta : localDelta;
482
+ }
483
+ }
484
+ }
485
+ }
486
+ {
487
+ const index = delta < 0 ? firstPivotIndex : secondPivotIndex;
488
+ const panel = panels[index];
489
+ ensure(panel, `No panel data found for index ${index}`);
490
+ const { collapsedSize = 0, collapsible, minSize = 0 } = panel;
491
+ if (collapsible) {
492
+ const prevSize2 = initialSize[index];
493
+ ensure(prevSize2, `Previous size not found for panel index ${index}`);
494
+ if (fuzzyNumbersEqual(prevSize2, minSize)) {
495
+ const localDelta = prevSize2 - collapsedSize;
496
+ if (fuzzyCompareNumbers(localDelta, Math.abs(delta)) > 0) {
497
+ delta = delta < 0 ? 0 - localDelta : localDelta;
498
+ }
499
+ }
500
+ }
501
+ }
502
+ }
503
+ }
504
+ {
505
+ const increment = delta < 0 ? 1 : -1;
506
+ let index = delta < 0 ? secondPivotIndex : firstPivotIndex;
507
+ let maxAvailableDelta = 0;
508
+ while (true) {
509
+ const prevSize2 = initialSize[index];
510
+ ensure(prevSize2, `Previous size not found for panel index ${index}`);
511
+ const maxSafeSize = resizePanel({
512
+ panels,
513
+ index,
514
+ size: 100
515
+ });
516
+ const delta2 = maxSafeSize - prevSize2;
517
+ maxAvailableDelta += delta2;
518
+ index += increment;
519
+ if (index < 0 || index >= panels.length) {
520
+ break;
521
+ }
522
+ }
523
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
524
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
525
+ }
526
+ {
527
+ const pivotIndex = delta < 0 ? firstPivotIndex : secondPivotIndex;
528
+ let index = pivotIndex;
529
+ while (index >= 0 && index < panels.length) {
530
+ const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
531
+ const prevSize2 = initialSize[index];
532
+ ensure(prevSize2, `Previous size not found for panel index ${index}`);
533
+ const unsafeSize = prevSize2 - deltaRemaining;
534
+ const safeSize = resizePanel({ panels, index, size: unsafeSize });
535
+ if (!fuzzyNumbersEqual(prevSize2, safeSize)) {
536
+ deltaApplied += prevSize2 - safeSize;
537
+ nextSize[index] = safeSize;
538
+ if (deltaApplied.toPrecision(3).localeCompare(Math.abs(delta).toPrecision(3), void 0, {
539
+ numeric: true
540
+ }) >= 0) {
541
+ break;
542
+ }
543
+ }
544
+ if (delta < 0) {
545
+ index--;
546
+ } else {
547
+ index++;
548
+ }
549
+ }
550
+ }
551
+ if (fuzzySizeEqual(prevSize, nextSize)) {
552
+ return prevSize;
553
+ }
554
+ {
555
+ const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
556
+ const prevSize2 = initialSize[pivotIndex];
557
+ ensure(prevSize2, `Previous size not found for panel index ${pivotIndex}`);
558
+ const unsafeSize = prevSize2 + deltaApplied;
559
+ const safeSize = resizePanel({ panels, index: pivotIndex, size: unsafeSize });
560
+ nextSize[pivotIndex] = safeSize;
561
+ if (!fuzzyNumbersEqual(safeSize, unsafeSize)) {
562
+ let deltaRemaining = unsafeSize - safeSize;
563
+ const pivotIndex2 = delta < 0 ? secondPivotIndex : firstPivotIndex;
564
+ let index = pivotIndex2;
565
+ while (index >= 0 && index < panels.length) {
566
+ const prevSize3 = nextSize[index];
567
+ ensure(prevSize3, `Previous size not found for panel index ${index}`);
568
+ const unsafeSize2 = prevSize3 + deltaRemaining;
569
+ const safeSize2 = resizePanel({ panels, index, size: unsafeSize2 });
570
+ if (!fuzzyNumbersEqual(prevSize3, safeSize2)) {
571
+ deltaRemaining -= safeSize2 - prevSize3;
572
+ nextSize[index] = safeSize2;
573
+ }
574
+ if (fuzzyNumbersEqual(deltaRemaining, 0)) {
575
+ break;
576
+ }
577
+ if (delta > 0) {
578
+ index--;
579
+ } else {
580
+ index++;
581
+ }
582
+ }
583
+ }
584
+ }
585
+ const totalSize = nextSize.reduce((total, size) => size + total, 0);
586
+ if (!fuzzyNumbersEqual(totalSize, 100)) {
587
+ return prevSize;
588
+ }
589
+ return nextSize;
590
+ }
591
+ function validateSizes({ size: prevSize, panels }) {
592
+ const nextSize = [...prevSize];
593
+ const nextSizeTotalSize = nextSize.reduce((accumulated, current) => accumulated + current, 0);
594
+ if (nextSize.length !== panels.length) {
595
+ throw Error(`Invalid ${panels.length} panel size: ${nextSize.map((size) => `${size}%`).join(", ")}`);
596
+ } else if (!fuzzyNumbersEqual(nextSizeTotalSize, 100) && nextSize.length > 0) {
597
+ for (let index = 0; index < panels.length; index++) {
598
+ const unsafeSize = nextSize[index];
599
+ ensure(unsafeSize, `No size data found for index ${index}`);
600
+ const safeSize = 100 / nextSizeTotalSize * unsafeSize;
601
+ nextSize[index] = safeSize;
602
+ }
603
+ }
604
+ let remainingSize = 0;
605
+ for (let index = 0; index < panels.length; index++) {
606
+ const unsafeSize = nextSize[index];
607
+ ensure(unsafeSize, `No size data found for index ${index}`);
608
+ const safeSize = resizePanel({ panels, index, size: unsafeSize });
609
+ if (unsafeSize != safeSize) {
610
+ remainingSize += unsafeSize - safeSize;
611
+ nextSize[index] = safeSize;
612
+ }
613
+ }
614
+ if (!fuzzyNumbersEqual(remainingSize, 0)) {
615
+ for (let index = 0; index < panels.length; index++) {
616
+ const prevSize2 = nextSize[index];
617
+ ensure(prevSize2, `No size data found for index ${index}`);
618
+ const unsafeSize = prevSize2 + remainingSize;
619
+ const safeSize = resizePanel({ panels, index, size: unsafeSize });
620
+ if (prevSize2 !== safeSize) {
621
+ remainingSize -= safeSize - prevSize2;
622
+ nextSize[index] = safeSize;
623
+ if (fuzzyNumbersEqual(remainingSize, 0)) {
624
+ break;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ return nextSize;
630
+ }
631
+
632
+ // src/splitter.machine.ts
335
633
  var machine = createMachine({
336
634
  props({ props: props2 }) {
635
+ ensureProps(props2, ["panels"]);
337
636
  return {
338
637
  orientation: "horizontal",
339
638
  defaultSize: [],
340
- ...props2
639
+ dir: "ltr",
640
+ ...props2,
641
+ panels: sortPanels(props2.panels)
341
642
  };
342
643
  },
343
644
  initialState() {
344
645
  return "idle";
345
646
  },
346
- context({ prop, bindable, getContext }) {
647
+ context({ prop, bindable, getContext, getRefs }) {
347
648
  return {
348
- activeResizeId: bindable(() => ({
349
- defaultValue: null
350
- })),
351
649
  size: bindable(() => ({
352
- defaultValue: prop("defaultSize"),
353
650
  value: prop("size"),
354
- hash(a) {
355
- return a.map((panel) => `${panel.id}:${panel.size}`).join(",");
651
+ defaultValue: prop("defaultSize"),
652
+ isEqual(a, b) {
653
+ return b != null && fuzzySizeEqual(a, b);
356
654
  },
357
655
  onChange(value) {
358
- const context = getContext();
359
- prop("onSizeChange")?.({
656
+ const ctx = getContext();
657
+ const refs = getRefs();
658
+ const sizesBeforeCollapse = refs.get("panelSizeBeforeCollapse");
659
+ const expandToSizes = Object.fromEntries(sizesBeforeCollapse.entries());
660
+ const resizeTriggerId = ctx.get("dragState")?.resizeTriggerId ?? null;
661
+ const layout = getPanelLayout(prop("panels"));
662
+ prop("onResize")?.({
360
663
  size: value,
361
- activeHandleId: context.get("activeResizeId")
664
+ layout,
665
+ resizeTriggerId,
666
+ expandToSizes
362
667
  });
363
668
  }
364
669
  })),
365
- activeResizeState: bindable(() => ({
366
- defaultValue: { isAtMin: false, isAtMax: false }
670
+ dragState: bindable(() => ({
671
+ defaultValue: null
672
+ })),
673
+ keyboardState: bindable(() => ({
674
+ defaultValue: null
367
675
  }))
368
676
  };
369
677
  },
370
- refs({ context }) {
678
+ watch({ track, action, prop }) {
679
+ track([() => serializePanels(prop("panels"))], () => {
680
+ action(["syncSize"]);
681
+ });
682
+ },
683
+ refs() {
371
684
  return {
372
- previousPanels: getNormalizedPanels(context.get("size"))
685
+ panelSizeBeforeCollapse: /* @__PURE__ */ new Map(),
686
+ prevDelta: 0,
687
+ panelIdToLastNotifiedSizeMap: /* @__PURE__ */ new Map()
373
688
  };
374
689
  },
375
- watch({ track, action, context }) {
376
- track([() => context.hash("size")], () => {
377
- action(["setActiveResizeState"]);
378
- });
379
- },
380
690
  computed: {
381
- isHorizontal: ({ prop }) => prop("orientation") === "horizontal",
382
- panels: ({ context }) => getNormalizedPanels(context.get("size"))
691
+ horizontal({ prop }) {
692
+ return prop("orientation") === "horizontal";
693
+ }
383
694
  },
384
695
  on: {
385
696
  "SIZE.SET": {
386
- actions: ["setPanelSize"]
697
+ actions: ["setSize"]
698
+ },
699
+ "PANEL.COLLAPSE": {
700
+ actions: ["collapsePanel"]
701
+ },
702
+ "PANEL.EXPAND": {
703
+ actions: ["expandPanel"]
387
704
  },
388
- "SIZES.SET": {
389
- actions: ["setPanelSizes"]
705
+ "PANEL.RESIZE": {
706
+ actions: ["resizePanel"]
390
707
  }
391
708
  },
709
+ entry: ["syncSize"],
392
710
  states: {
393
711
  idle: {
394
- entry: ["clearActiveHandleId"],
712
+ entry: ["clearDraggingState", "clearKeyboardState"],
395
713
  on: {
396
714
  POINTER_OVER: {
397
715
  target: "hover:temp",
398
- actions: ["setActiveHandleId"]
716
+ actions: ["setKeyboardState"]
399
717
  },
400
718
  FOCUS: {
401
719
  target: "focused",
402
- actions: ["setActiveHandleId"]
720
+ actions: ["setKeyboardState"]
403
721
  },
404
722
  POINTER_DOWN: {
405
723
  target: "dragging",
406
- actions: ["setActiveHandleId"]
407
- },
408
- DOUBLE_CLICK: {
409
- actions: ["resetStartPanel", "setPreviousPanels"]
724
+ actions: ["setDraggingState"]
410
725
  }
411
726
  }
412
727
  },
@@ -418,7 +733,7 @@ var machine = createMachine({
418
733
  },
419
734
  POINTER_DOWN: {
420
735
  target: "dragging",
421
- actions: ["setActiveHandleId"]
736
+ actions: ["setDraggingState"]
422
737
  },
423
738
  POINTER_LEAVE: {
424
739
  target: "idle"
@@ -429,7 +744,8 @@ var machine = createMachine({
429
744
  tags: ["focus"],
430
745
  on: {
431
746
  POINTER_DOWN: {
432
- target: "dragging"
747
+ target: "dragging",
748
+ actions: ["setDraggingState"]
433
749
  },
434
750
  POINTER_LEAVE: {
435
751
  target: "idle"
@@ -442,52 +758,32 @@ var machine = createMachine({
442
758
  BLUR: {
443
759
  target: "idle"
444
760
  },
761
+ ENTER: {
762
+ actions: ["collapseOrExpandPanel"]
763
+ },
445
764
  POINTER_DOWN: {
446
765
  target: "dragging",
447
- actions: ["setActiveHandleId"]
448
- },
449
- ARROW_LEFT: {
450
- guard: "isHorizontal",
451
- actions: ["shrinkStartPanel", "setPreviousPanels"]
452
- },
453
- ARROW_RIGHT: {
454
- guard: "isHorizontal",
455
- actions: ["expandStartPanel", "setPreviousPanels"]
766
+ actions: ["setDraggingState"]
456
767
  },
457
- ARROW_UP: {
458
- guard: "isVertical",
459
- actions: ["shrinkStartPanel", "setPreviousPanels"]
768
+ KEYBOARD_MOVE: {
769
+ actions: ["setKeyboardValue"]
460
770
  },
461
- ARROW_DOWN: {
462
- guard: "isVertical",
463
- actions: ["expandStartPanel", "setPreviousPanels"]
464
- },
465
- ENTER: [
466
- {
467
- guard: "isStartPanelAtMax",
468
- actions: ["setStartPanelToMin", "setPreviousPanels"]
469
- },
470
- { actions: ["setStartPanelToMax", "setPreviousPanels"] }
471
- ],
472
- HOME: {
473
- actions: ["setStartPanelToMin", "setPreviousPanels"]
474
- },
475
- END: {
476
- actions: ["setStartPanelToMax", "setPreviousPanels"]
771
+ "FOCUS.CYCLE": {
772
+ actions: ["focusNextResizeTrigger"]
477
773
  }
478
774
  }
479
775
  },
480
776
  dragging: {
481
777
  tags: ["focus"],
482
- entry: ["focusResizeHandle"],
483
778
  effects: ["trackPointerMove"],
779
+ entry: ["invokeOnResizeStart"],
484
780
  on: {
485
781
  POINTER_MOVE: {
486
- actions: ["setPointerValue", "setGlobalCursor", "invokeOnResize"]
782
+ actions: ["setPointerValue", "setGlobalCursor"]
487
783
  },
488
784
  POINTER_UP: {
489
- target: "focused",
490
- actions: ["setPreviousPanels", "clearGlobalCursor", "blurResizeHandle", "invokeOnResizeEnd"]
785
+ target: "idle",
786
+ actions: ["invokeOnResizeEnd", "clearGlobalCursor"]
491
787
  }
492
788
  }
493
789
  }
@@ -511,178 +807,310 @@ var machine = createMachine({
511
807
  });
512
808
  }
513
809
  },
514
- guards: {
515
- isStartPanelAtMin: ({ context }) => context.get("activeResizeState").isAtMin,
516
- isStartPanelAtMax: ({ context }) => context.get("activeResizeState").isAtMax,
517
- isHorizontal: ({ prop }) => prop("orientation") === "horizontal",
518
- isVertical: ({ prop }) => prop("orientation") !== "horizontal"
519
- },
520
810
  actions: {
521
- setGlobalCursor({ context, scope, computed }) {
522
- const activeState = context.get("activeResizeState");
523
- const isHorizontal = computed("isHorizontal");
524
- setupGlobalCursor(scope, activeState, isHorizontal);
525
- },
526
- clearGlobalCursor({ scope }) {
527
- removeGlobalCursor(scope);
811
+ setSize(params) {
812
+ const { context, event, prop } = params;
813
+ const unsafeSize = event.size;
814
+ const prevSize = context.get("size");
815
+ const panels = prop("panels");
816
+ const safeSize = validateSizes({
817
+ size: unsafeSize,
818
+ panels
819
+ });
820
+ if (!isEqual(prevSize, safeSize)) {
821
+ setSize(params, safeSize);
822
+ }
528
823
  },
529
- invokeOnResize({ context, prop }) {
530
- prop("onSizeChange")?.({
531
- size: Array.from(context.get("size")),
532
- activeHandleId: context.get("activeResizeId")
824
+ syncSize({ context, prop }) {
825
+ const panels = prop("panels");
826
+ let prevSize = context.get("size");
827
+ let unsafeSize = null;
828
+ if (prevSize.length === 0) {
829
+ unsafeSize = getUnsafeDefaultSize({
830
+ panels,
831
+ size: context.initial("size")
832
+ });
833
+ }
834
+ const nextSize = validateSizes({
835
+ size: unsafeSize ?? prevSize,
836
+ panels
533
837
  });
838
+ if (!isEqual(prevSize, nextSize)) {
839
+ context.set("size", nextSize);
840
+ }
534
841
  },
535
- invokeOnResizeEnd({ context, prop }) {
536
- prop("onSizeChangeEnd")?.({
537
- size: Array.from(context.get("size")),
538
- activeHandleId: context.get("activeResizeId")
842
+ setDraggingState({ context, event, prop, scope }) {
843
+ const orientation = prop("orientation");
844
+ const size = context.get("size");
845
+ const resizeTriggerId = event.id;
846
+ const panelGroupEl = getRootEl(scope);
847
+ if (!panelGroupEl) return;
848
+ const handleElement = getResizeTriggerEl(scope, resizeTriggerId);
849
+ ensure(handleElement, `Drag handle element not found for id "${resizeTriggerId}"`);
850
+ const initialCursorPosition = orientation === "horizontal" ? event.point.x : event.point.y;
851
+ context.set("dragState", {
852
+ resizeTriggerId: event.id,
853
+ resizeTriggerRect: handleElement.getBoundingClientRect(),
854
+ initialCursorPosition,
855
+ initialSize: size
539
856
  });
540
857
  },
541
- setActiveHandleId({ context, event }) {
542
- context.set("activeResizeId", event.id);
858
+ clearDraggingState({ context }) {
859
+ context.set("dragState", null);
543
860
  },
544
- clearActiveHandleId({ context }) {
545
- context.set("activeResizeId", null);
861
+ setKeyboardState({ context, event }) {
862
+ context.set("keyboardState", {
863
+ resizeTriggerId: event.id
864
+ });
546
865
  },
547
- setPanelSize({ context, event }) {
548
- const { id, size } = event;
549
- context.set(
550
- "size",
551
- (prev) => prev.map((panel) => {
552
- const panelSize = clamp(size, panel.minSize ?? 0, panel.maxSize ?? 100);
553
- return panel.id === id ? { ...panel, size: panelSize } : panel;
554
- })
555
- );
866
+ clearKeyboardState({ context }) {
867
+ context.set("keyboardState", null);
556
868
  },
557
- setPanelSizes({ context, event }) {
558
- context.set("size", event.sizes);
869
+ collapsePanel(params) {
870
+ const { context, prop, event, refs } = params;
871
+ const prevSize = context.get("size");
872
+ const panels = prop("panels");
873
+ const panel = panels.find((panel2) => panel2.id === event.id);
874
+ ensure(panel, `Panel data not found for id "${event.id}"`);
875
+ if (panel.collapsible) {
876
+ const { collapsedSize = 0, panelSize, pivotIndices } = panelDataHelper(panels, panel, prevSize);
877
+ ensure(panelSize, `Panel size not found for panel "${panel.id}"`);
878
+ if (!fuzzyNumbersEqual(panelSize, collapsedSize)) {
879
+ refs.get("panelSizeBeforeCollapse").set(panel.id, panelSize);
880
+ const isLastPanel = findPanelDataIndex(panels, panel) === panels.length - 1;
881
+ const delta = isLastPanel ? panelSize - collapsedSize : collapsedSize - panelSize;
882
+ const nextSize = resizeByDelta({
883
+ delta,
884
+ initialSize: prevSize,
885
+ panels,
886
+ pivotIndices,
887
+ prevSize,
888
+ trigger: "imperative-api"
889
+ });
890
+ if (!isEqual(prevSize, nextSize)) {
891
+ setSize(params, nextSize);
892
+ }
893
+ }
894
+ }
559
895
  },
560
- setStartPanelToMin({ context, computed }) {
561
- const bounds = getPanelBounds(computed("panels"), context.get("activeResizeId"));
562
- if (!bounds) return;
563
- const { before, after } = bounds;
564
- context.set("size", (prev) => {
565
- const next = prev.slice();
566
- next[before.index].size = before.min;
567
- next[after.index].size = after.min;
568
- return next;
569
- });
896
+ expandPanel(params) {
897
+ const { context, prop, event, refs } = params;
898
+ const panels = prop("panels");
899
+ const prevSize = context.get("size");
900
+ const panel = panels.find((panel2) => panel2.id === event.id);
901
+ ensure(panel, `Panel data not found for id "${event.id}"`);
902
+ if (panel.collapsible) {
903
+ const {
904
+ collapsedSize = 0,
905
+ panelSize = 0,
906
+ minSize: minSizeFromProps = 0,
907
+ pivotIndices
908
+ } = panelDataHelper(panels, panel, prevSize);
909
+ const minSize = event.minSize ?? minSizeFromProps;
910
+ if (fuzzyNumbersEqual(panelSize, collapsedSize)) {
911
+ const prevPanelSize = refs.get("panelSizeBeforeCollapse").get(panel.id);
912
+ const baseSize = prevPanelSize != null && prevPanelSize >= minSize ? prevPanelSize : minSize;
913
+ const isLastPanel = findPanelDataIndex(panels, panel) === panels.length - 1;
914
+ const delta = isLastPanel ? panelSize - baseSize : baseSize - panelSize;
915
+ const nextSize = resizeByDelta({
916
+ delta,
917
+ initialSize: prevSize,
918
+ panels,
919
+ pivotIndices,
920
+ prevSize,
921
+ trigger: "imperative-api"
922
+ });
923
+ if (!isEqual(prevSize, nextSize)) {
924
+ setSize(params, nextSize);
925
+ }
926
+ }
927
+ }
570
928
  },
571
- setStartPanelToMax({ context, computed }) {
572
- const bounds = getPanelBounds(computed("panels"), context.get("activeResizeId"));
573
- if (!bounds) return;
574
- const { before, after } = bounds;
575
- context.set("size", (prev) => {
576
- const next = prev.slice();
577
- next[before.index].size = before.max;
578
- next[after.index].size = after.max;
579
- return next;
929
+ resizePanel(params) {
930
+ const { context, prop, event } = params;
931
+ const prevSize = context.get("size");
932
+ const panels = prop("panels");
933
+ const panel = getPanelById(panels, event.id);
934
+ const unsafePanelSize = event.size;
935
+ const { panelSize, pivotIndices } = panelDataHelper(panels, panel, prevSize);
936
+ ensure(panelSize, `Panel size not found for panel "${panel.id}"`);
937
+ const isLastPanel = findPanelDataIndex(panels, panel) === panels.length - 1;
938
+ const delta = isLastPanel ? panelSize - unsafePanelSize : unsafePanelSize - panelSize;
939
+ const nextSize = resizeByDelta({
940
+ delta,
941
+ initialSize: prevSize,
942
+ panels,
943
+ pivotIndices,
944
+ prevSize,
945
+ trigger: "imperative-api"
580
946
  });
947
+ if (!isEqual(prevSize, nextSize)) {
948
+ setSize(params, nextSize);
949
+ }
581
950
  },
582
- expandStartPanel({ context, event, computed }) {
583
- const bounds = getPanelBounds(computed("panels"), context.get("activeResizeId"));
584
- if (!bounds) return;
585
- const { before, after } = bounds;
586
- context.set("size", (prev) => {
587
- const next = prev.slice();
588
- next[before.index].size = before.up(event.step);
589
- next[after.index].size = after.down(event.step);
590
- return next;
951
+ setPointerValue(params) {
952
+ const { context, event, prop, scope } = params;
953
+ const dragState = context.get("dragState");
954
+ if (!dragState) return;
955
+ const { resizeTriggerId, initialSize, initialCursorPosition } = dragState;
956
+ const panels = prop("panels");
957
+ const panelGroupElement = getRootEl(scope);
958
+ ensure(panelGroupElement, `Panel group element not found`);
959
+ const pivotIndices = resizeTriggerId.split(":").map((id) => panels.findIndex((panel) => panel.id === id));
960
+ const horizontal = prop("orientation") === "horizontal";
961
+ const cursorPosition = horizontal ? event.point.x : event.point.y;
962
+ const groupRect = panelGroupElement.getBoundingClientRect();
963
+ const groupSizeInPixels = horizontal ? groupRect.width : groupRect.height;
964
+ const offsetPixels = cursorPosition - initialCursorPosition;
965
+ const offsetPercentage = offsetPixels / groupSizeInPixels * 100;
966
+ const prevSize = context.get("size");
967
+ const nextSize = resizeByDelta({
968
+ delta: offsetPercentage,
969
+ initialSize: initialSize ?? prevSize,
970
+ panels,
971
+ pivotIndices,
972
+ prevSize,
973
+ trigger: "mouse-or-touch"
591
974
  });
975
+ if (!isEqual(prevSize, nextSize)) {
976
+ setSize(params, nextSize);
977
+ }
592
978
  },
593
- shrinkStartPanel({ context, event, computed }) {
594
- const bounds = getPanelBounds(computed("panels"), context.get("activeResizeId"));
595
- if (!bounds) return;
596
- const { before, after } = bounds;
597
- context.set("size", (prev) => {
598
- const next = prev.slice();
599
- next[before.index].size = before.down(event.step);
600
- next[after.index].size = after.up(event.step);
601
- return next;
979
+ setKeyboardValue(params) {
980
+ const { context, event, prop } = params;
981
+ const panelDataArray = prop("panels");
982
+ const resizeTriggerId = event.id;
983
+ const delta = event.delta;
984
+ const pivotIndices = resizeTriggerId.split(":").map((id) => panelDataArray.findIndex((panelData) => panelData.id === id));
985
+ const prevSize = context.get("size");
986
+ const nextSize = resizeByDelta({
987
+ delta,
988
+ initialSize: prevSize,
989
+ panels: panelDataArray,
990
+ pivotIndices,
991
+ prevSize,
992
+ trigger: "keyboard"
602
993
  });
994
+ if (!isEqual(prevSize, nextSize)) {
995
+ setSize(params, nextSize);
996
+ }
603
997
  },
604
- resetStartPanel({ context, computed }) {
605
- const bounds = getPanelBounds(computed("panels"), context.get("activeResizeId"));
606
- if (!bounds) return;
607
- const { before, after } = bounds;
608
- const initialSize = context.initial("size");
609
- context.set("size", (prev) => {
610
- const next = prev.slice();
611
- next[before.index].size = initialSize[before.index].size;
612
- next[after.index].size = initialSize[after.index].size;
613
- return next;
998
+ invokeOnResizeEnd({ context, prop }) {
999
+ queueMicrotask(() => {
1000
+ const dragState = context.get("dragState");
1001
+ prop("onResizeEnd")?.({
1002
+ size: context.get("size"),
1003
+ resizeTriggerId: dragState?.resizeTriggerId ?? null
1004
+ });
614
1005
  });
615
1006
  },
616
- focusResizeHandle({ scope, context }) {
617
- raf(() => {
618
- const activeId = context.get("activeResizeId");
619
- if (!activeId) return;
620
- getResizeTriggerEl(scope, activeId)?.focus({ preventScroll: true });
1007
+ invokeOnResizeStart({ prop }) {
1008
+ queueMicrotask(() => {
1009
+ prop("onResizeStart")?.();
621
1010
  });
622
1011
  },
623
- blurResizeHandle({ scope, context }) {
624
- raf(() => {
625
- const activeId = context.get("activeResizeId");
626
- if (!activeId) return;
627
- getResizeTriggerEl(scope, activeId)?.blur();
628
- });
1012
+ collapseOrExpandPanel(params) {
1013
+ const { context, prop } = params;
1014
+ const panelDataArray = prop("panels");
1015
+ const sizes = context.get("size");
1016
+ const resizeTriggerId = context.get("keyboardState")?.resizeTriggerId;
1017
+ const [idBefore, idAfter] = resizeTriggerId?.split(":") ?? [];
1018
+ const index = panelDataArray.findIndex((panelData2) => panelData2.id === idBefore);
1019
+ if (index === -1) return;
1020
+ const panelData = panelDataArray[index];
1021
+ ensure(panelData, `No panel data found for index ${index}`);
1022
+ const size = sizes[index];
1023
+ const { collapsedSize = 0, collapsible, minSize = 0 } = panelData;
1024
+ if (size != null && collapsible) {
1025
+ const pivotIndices = [idBefore, idAfter].map(
1026
+ (id) => panelDataArray.findIndex((panelData2) => panelData2.id === id)
1027
+ );
1028
+ const nextSize = resizeByDelta({
1029
+ delta: fuzzyNumbersEqual(size, collapsedSize) ? minSize - collapsedSize : collapsedSize - size,
1030
+ initialSize: context.initial("size"),
1031
+ panels: panelDataArray,
1032
+ pivotIndices,
1033
+ prevSize: sizes,
1034
+ trigger: "keyboard"
1035
+ });
1036
+ if (!isEqual(sizes, nextSize)) {
1037
+ setSize(params, nextSize);
1038
+ }
1039
+ }
629
1040
  },
630
- setPreviousPanels({ refs, computed }) {
631
- refs.set("previousPanels", computed("panels").slice());
1041
+ setGlobalCursor({ context, scope, prop }) {
1042
+ const dragState = context.get("dragState");
1043
+ if (!dragState) return;
1044
+ const panels = prop("panels");
1045
+ const horizontal = prop("orientation") === "horizontal";
1046
+ const [idBefore] = dragState.resizeTriggerId.split(":");
1047
+ const indexBefore = panels.findIndex((panel2) => panel2.id === idBefore);
1048
+ const panel = panels[indexBefore];
1049
+ const size = context.get("size");
1050
+ const aria = getAriaValue(size, panels, dragState.resizeTriggerId);
1051
+ const isAtMin = fuzzyNumbersEqual(aria.valueNow, aria.valueMin) || fuzzyNumbersEqual(aria.valueNow, panel.collapsedSize);
1052
+ const isAtMax = fuzzyNumbersEqual(aria.valueNow, aria.valueMax);
1053
+ const cursorState = { isAtMin, isAtMax };
1054
+ setupGlobalCursor(scope, cursorState, horizontal, prop("nonce"));
632
1055
  },
633
- setActiveResizeState({ context, computed }) {
634
- const panels = getPanelBounds(computed("panels"), context.get("activeResizeId"));
635
- if (!panels) return;
636
- const { before } = panels;
637
- context.set("activeResizeState", {
638
- isAtMin: before.isAtMin,
639
- isAtMax: before.isAtMax
640
- });
1056
+ clearGlobalCursor({ scope }) {
1057
+ removeGlobalCursor(scope);
641
1058
  },
642
- setPointerValue({ context, event, prop, scope, computed }) {
643
- const panels = getHandlePanels(computed("panels"), context.get("activeResizeId"));
644
- const bounds = getHandleBounds(computed("panels"), context.get("activeResizeId"));
645
- if (!panels || !bounds) return;
646
- const rootEl = getRootEl(scope);
647
- if (!rootEl) return;
648
- const relativePoint = getRelativePoint(event.point, rootEl);
649
- const percentValue = relativePoint.getPercentValue({
650
- dir: prop("dir"),
651
- orientation: prop("orientation")
652
- });
653
- let pointValue = percentValue * 100;
654
- context.set("activeResizeState", {
655
- isAtMin: pointValue < bounds.min,
656
- isAtMax: pointValue > bounds.max
657
- });
658
- pointValue = clamp(pointValue, bounds.min, bounds.max);
659
- const { before, after } = panels;
660
- const offset = pointValue - before.end;
661
- context.set("size", (prev) => {
662
- const next = prev.slice();
663
- next[before.index].size = before.size + offset;
664
- next[after.index].size = after.size - offset;
665
- return next;
666
- });
1059
+ focusNextResizeTrigger({ event, scope }) {
1060
+ const resizeTriggers = getResizeTriggerEls(scope);
1061
+ const index = resizeTriggers.findIndex((el) => el.dataset.id === event.id);
1062
+ const handleEl = event.shiftKey ? prev(resizeTriggers, index) : next(resizeTriggers, index);
1063
+ handleEl?.focus();
667
1064
  }
668
1065
  }
669
1066
  }
670
1067
  });
1068
+ function setSize(params, sizes) {
1069
+ const { refs, prop, context } = params;
1070
+ const panelsArray = prop("panels");
1071
+ const onCollapse = prop("onCollapse");
1072
+ const onExpand = prop("onExpand");
1073
+ const panelIdToLastNotifiedSizeMap = refs.get("panelIdToLastNotifiedSizeMap");
1074
+ context.set("size", sizes);
1075
+ sizes.forEach((size, index) => {
1076
+ const panelData = panelsArray[index];
1077
+ ensure(panelData, `Panel data not found for index ${index}`);
1078
+ const { collapsedSize = 0, collapsible, id: panelId } = panelData;
1079
+ const lastNotifiedSize = panelIdToLastNotifiedSizeMap.get(panelId);
1080
+ if (lastNotifiedSize == null || size !== lastNotifiedSize) {
1081
+ panelIdToLastNotifiedSizeMap.set(panelId, size);
1082
+ if (collapsible && (onCollapse || onExpand)) {
1083
+ if ((lastNotifiedSize == null || fuzzyNumbersEqual(lastNotifiedSize, collapsedSize)) && !fuzzyNumbersEqual(size, collapsedSize)) {
1084
+ onExpand?.({ panelId, size });
1085
+ }
1086
+ if (onCollapse && (lastNotifiedSize == null || !fuzzyNumbersEqual(lastNotifiedSize, collapsedSize)) && fuzzyNumbersEqual(size, collapsedSize)) {
1087
+ onCollapse?.({ panelId, size });
1088
+ }
1089
+ }
1090
+ }
1091
+ });
1092
+ }
671
1093
  var props = createProps()([
672
1094
  "dir",
673
1095
  "getRootNode",
674
1096
  "id",
675
1097
  "ids",
676
- "onSizeChange",
677
- "onSizeChangeEnd",
1098
+ "onResize",
1099
+ "onResizeStart",
1100
+ "onResizeEnd",
1101
+ "onCollapse",
1102
+ "onExpand",
678
1103
  "orientation",
679
1104
  "size",
680
- "defaultSize"
1105
+ "defaultSize",
1106
+ "panels",
1107
+ "keyboardResizeBy",
1108
+ "nonce"
681
1109
  ]);
682
1110
  var splitProps = createSplitProps(props);
683
- var panelProps = createProps()(["id", "snapSize"]);
1111
+ var panelProps = createProps()(["id"]);
684
1112
  var splitPanelProps = createSplitProps(panelProps);
685
- var resizeTriggerProps = createProps()(["disabled", "id", "step"]);
1113
+ var resizeTriggerProps = createProps()(["disabled", "id"]);
686
1114
  var splitResizeTriggerProps = createSplitProps(resizeTriggerProps);
687
1115
 
688
- export { anatomy, connect, machine, panelProps, props, resizeTriggerProps, splitPanelProps, splitProps, splitResizeTriggerProps };
1116
+ export { anatomy, connect, getPanelLayout as layout, machine, panelProps, props, resizeTriggerProps, splitPanelProps, splitProps, splitResizeTriggerProps };