@cck-ui/hooks 0.9.0 → 0.11.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.
@@ -0,0 +1,707 @@
1
+ "use client";
2
+ let vue = require("vue");
3
+ //#region packages/@cck-ui/hooks/src/use-splitter/use-splitter.ts
4
+ const PX_RE = /^(-?[\d.]+)px$/;
5
+ const REM_RE = /^(-?[\d.]+)rem$/;
6
+ const PERCENT_RE = /^(-?[\d.]+)%$/;
7
+ function isFixedSize(size) {
8
+ return typeof size === "string" && (PX_RE.test(size) || REM_RE.test(size));
9
+ }
10
+ function sizeMagnitude(size) {
11
+ return typeof size === "number" ? size : parseFloat(size);
12
+ }
13
+ function detectPixelMode(options) {
14
+ 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);
15
+ }
16
+ function getRootFontSize() {
17
+ if (typeof window === "undefined") return 16;
18
+ const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
19
+ return Number.isFinite(fontSize) && fontSize > 0 ? fontSize : 16;
20
+ }
21
+ function resolveSize(size, pixelMode, containerPx, rootFontSize) {
22
+ if (!pixelMode) return sizeMagnitude(size);
23
+ if (typeof size === "number") return size / 100 * containerPx;
24
+ const percent = PERCENT_RE.exec(size);
25
+ if (percent) return parseFloat(percent[1]) / 100 * containerPx;
26
+ const rem = REM_RE.exec(size);
27
+ if (rem) return parseFloat(rem[1]) * rootFontSize;
28
+ const px = PX_RE.exec(size);
29
+ if (px) return parseFloat(px[1]);
30
+ return 0;
31
+ }
32
+ /**
33
+ * @description Down-scaling factor applied to fixed panes when their combined pixel size overflows the container, so they shrink to fit (matching `resolveWorkingSizes`). Returns `1` when nothing overflows or the layout is not in pixel mode. Encoders divide by this to invert the scaling and persist the original absolute sizes instead of the shrunk-to-fit ones.
34
+ */
35
+ function getFixedScale(sizes, pixelMode, containerPx, rootFontSize) {
36
+ if (!pixelMode) return 1;
37
+ let fixedTotal = 0;
38
+ sizes.forEach((size) => {
39
+ if (isFixedSize(size)) fixedTotal += resolveSize(size, true, containerPx, rootFontSize);
40
+ });
41
+ return fixedTotal > containerPx && fixedTotal > 0 ? containerPx / fixedTotal : 1;
42
+ }
43
+ /**
44
+ * @description Resolves all sizes to pixels at once. Fixed panes get their absolute pixel size, flexible panes share the leftover space by their weight ratio - matching how the layout is rendered with `flex-grow`, so drag math operates on the same pixel sizes the user sees.
45
+ */
46
+ function resolveWorkingSizes(sizes, pixelMode, containerPx, rootFontSize) {
47
+ if (!pixelMode) return sizes.map((size) => sizeMagnitude(size));
48
+ let fixedTotal = 0;
49
+ let flexibleWeight = 0;
50
+ sizes.forEach((size) => {
51
+ if (isFixedSize(size)) fixedTotal += resolveSize(size, true, containerPx, rootFontSize);
52
+ else flexibleWeight += sizeMagnitude(size);
53
+ });
54
+ const leftover = Math.max(0, containerPx - fixedTotal);
55
+ const fixedScale = getFixedScale(sizes, pixelMode, containerPx, rootFontSize);
56
+ return sizes.map((size) => {
57
+ if (isFixedSize(size)) return resolveSize(size, true, containerPx, rootFontSize) * fixedScale;
58
+ return flexibleWeight > 0 ? sizeMagnitude(size) / flexibleWeight * leftover : 0;
59
+ });
60
+ }
61
+ function encodeSize(value, original, pixelMode, containerPx, rootFontSize, fixedScale = 1) {
62
+ if (!pixelMode) return typeof original === "string" && PERCENT_RE.test(original) ? `${value}%` : value;
63
+ if (typeof original === "number") return containerPx > 0 ? value / containerPx * 100 : original;
64
+ if (PERCENT_RE.test(original)) return `${containerPx > 0 ? value / containerPx * 100 : parseFloat(original)}%`;
65
+ const absolute = fixedScale > 0 ? value / fixedScale : value;
66
+ if (REM_RE.test(original)) return `${rootFontSize > 0 ? absolute / rootFontSize : 0}rem`;
67
+ return `${absolute}px`;
68
+ }
69
+ /**
70
+ * @description Encodes working pixel sizes back to raw sizes after a resize, keeping the unit each pane was declared in. Panes whose working size did not change keep their original raw value. When fixed panes overflow the container they render down-scaled, so their working sizes are scaled back up to absolute sizes (preserving their declared sizes). If the resize instead hands space to a flexible pane the overflow clears and the layout leaves the down-scaled regime: every pane is then encoded from its current working size - including untouched fixed panes (so they do not jump back to their over-sized value) and untouched flexible panes (so a pane that was squeezed to `0` does not keep a stale weight and steal the freed space on the next render)
71
+ */
72
+ function encodeWorkingSizes(nextWorking, baseWorking, baseRaw, pixelMode, containerPx, rootFontSize) {
73
+ const fixedScale = getFixedScale(baseRaw, pixelMode, containerPx, rootFontSize);
74
+ let fixedWorkingSum = 0;
75
+ nextWorking.forEach((value, i) => {
76
+ if (isFixedSize(baseRaw[i])) fixedWorkingSum += value;
77
+ });
78
+ const overflowCleared = fixedScale < 1 && fixedWorkingSum < containerPx - 1e-6;
79
+ const encodeScale = overflowCleared ? 1 : fixedScale;
80
+ return nextWorking.map((value, i) => overflowCleared || Math.abs(value - baseWorking[i]) > 1e-6 ? encodeSize(value, baseRaw[i], pixelMode, containerPx, rootFontSize, encodeScale) : baseRaw[i]);
81
+ }
82
+ function resolvePanel(panel, pixelMode, containerPx, rootFontSize) {
83
+ return {
84
+ defaultSize: resolveSize(panel.defaultSize, pixelMode, containerPx, rootFontSize),
85
+ min: panel.min != null ? resolveSize(panel.min, pixelMode, containerPx, rootFontSize) : 0,
86
+ max: panel.max != null ? resolveSize(panel.max, pixelMode, containerPx, rootFontSize) : pixelMode ? containerPx : 100,
87
+ collapseThreshold: panel.collapseThreshold != null ? resolveSize(panel.collapseThreshold, pixelMode, containerPx, rootFontSize) : void 0,
88
+ collapsible: panel.collapsible
89
+ };
90
+ }
91
+ function resolveStep(step, pixelMode, containerPx, rootFontSize) {
92
+ return resolveSize(step, pixelMode, containerPx, rootFontSize);
93
+ }
94
+ function clamp(value, min, max) {
95
+ return Math.min(Math.max(value, min), max);
96
+ }
97
+ function getMin(panel) {
98
+ return panel.min ?? 0;
99
+ }
100
+ function getMax(panel) {
101
+ return panel.max ?? Infinity;
102
+ }
103
+ function getCollapseThreshold(panel) {
104
+ return panel.collapseThreshold ?? getMin(panel);
105
+ }
106
+ function checkCollapse(sizes, panels, handleIndex, delta) {
107
+ const beforeIdx = handleIndex;
108
+ const afterIdx = handleIndex + 1;
109
+ const beforePanel = panels[beforeIdx];
110
+ const afterPanel = panels[afterIdx];
111
+ const rawBefore = sizes[beforeIdx] + delta;
112
+ const rawAfter = sizes[afterIdx] - delta;
113
+ if (beforePanel.collapsible && rawBefore < getCollapseThreshold(beforePanel) && rawBefore < sizes[beforeIdx]) {
114
+ const result = [...sizes];
115
+ result[afterIdx] += result[beforeIdx];
116
+ result[beforeIdx] = 0;
117
+ return result;
118
+ }
119
+ if (afterPanel.collapsible && rawAfter < getCollapseThreshold(afterPanel) && rawAfter < sizes[afterIdx]) {
120
+ const result = [...sizes];
121
+ result[beforeIdx] += result[afterIdx];
122
+ result[afterIdx] = 0;
123
+ return result;
124
+ }
125
+ return null;
126
+ }
127
+ function applyAdjacentOnly(sizes, panels, handleIndex, delta) {
128
+ const result = [...sizes];
129
+ const beforeIdx = handleIndex;
130
+ const afterIdx = handleIndex + 1;
131
+ const total = result[beforeIdx] + result[afterIdx];
132
+ const effectiveBeforeMax = Math.min(getMax(panels[beforeIdx]), total - getMin(panels[afterIdx]));
133
+ const effectiveBeforeMin = Math.max(getMin(panels[beforeIdx]), total - getMax(panels[afterIdx]));
134
+ const newBefore = clamp(result[beforeIdx] + delta, effectiveBeforeMin, effectiveBeforeMax);
135
+ result[beforeIdx] = newBefore;
136
+ result[afterIdx] = total - newBefore;
137
+ return result;
138
+ }
139
+ function redistributeNearest(sizes, panels, handleIndex, delta) {
140
+ const result = [...sizes];
141
+ if (delta > 0) {
142
+ const growIdx = handleIndex;
143
+ const maxGrow = getMax(panels[growIdx]) - result[growIdx];
144
+ const wantedGrow = Math.min(delta, maxGrow);
145
+ let taken = 0;
146
+ for (let i = handleIndex + 1; i < result.length && taken < wantedGrow; i += 1) {
147
+ const canGive = result[i] - getMin(panels[i]);
148
+ const take = Math.min(canGive, wantedGrow - taken);
149
+ result[i] -= take;
150
+ taken += take;
151
+ }
152
+ result[growIdx] += taken;
153
+ } else if (delta < 0) {
154
+ const growIdx = handleIndex + 1;
155
+ const maxGrow = getMax(panels[growIdx]) - result[growIdx];
156
+ const wantedGrow = Math.min(Math.abs(delta), maxGrow);
157
+ let taken = 0;
158
+ for (let i = handleIndex; i >= 0 && taken < wantedGrow; i -= 1) {
159
+ const canGive = result[i] - getMin(panels[i]);
160
+ const take = Math.min(canGive, wantedGrow - taken);
161
+ result[i] -= take;
162
+ taken += take;
163
+ }
164
+ result[growIdx] += taken;
165
+ }
166
+ return result;
167
+ }
168
+ function redistributeEqual(sizes, panels, handleIndex, delta) {
169
+ const result = [...sizes];
170
+ if (delta > 0) {
171
+ const growIdx = handleIndex;
172
+ const maxGrow = getMax(panels[growIdx]) - result[growIdx];
173
+ const wantedGrow = Math.min(delta, maxGrow);
174
+ const donors = [];
175
+ for (let i = handleIndex + 1; i < result.length; i += 1) if (result[i] > getMin(panels[i])) donors.push(i);
176
+ let remaining = wantedGrow;
177
+ while (remaining > .001 && donors.length > 0) {
178
+ const perDonor = remaining / donors.length;
179
+ const exhausted = [];
180
+ for (let d = 0; d < donors.length; d += 1) {
181
+ const idx = donors[d];
182
+ const canGive = result[idx] - getMin(panels[idx]);
183
+ const take = Math.min(canGive, perDonor);
184
+ result[idx] -= take;
185
+ remaining -= take;
186
+ if (canGive <= perDonor + .001) exhausted.push(d);
187
+ }
188
+ for (let i = exhausted.length - 1; i >= 0; i -= 1) donors.splice(exhausted[i], 1);
189
+ if (exhausted.length === 0) break;
190
+ }
191
+ result[growIdx] += wantedGrow - remaining;
192
+ } else if (delta < 0) {
193
+ const growIdx = handleIndex + 1;
194
+ const maxGrow = getMax(panels[growIdx]) - result[growIdx];
195
+ const wantedGrow = Math.min(Math.abs(delta), maxGrow);
196
+ const donors = [];
197
+ for (let i = handleIndex; i >= 0; i -= 1) if (result[i] > getMin(panels[i])) donors.push(i);
198
+ let remaining = wantedGrow;
199
+ while (remaining > .001 && donors.length > 0) {
200
+ const perDonor = remaining / donors.length;
201
+ const exhausted = [];
202
+ for (let d = 0; d < donors.length; d += 1) {
203
+ const idx = donors[d];
204
+ const canGive = result[idx] - getMin(panels[idx]);
205
+ const take = Math.min(canGive, perDonor);
206
+ result[idx] -= take;
207
+ remaining -= take;
208
+ if (canGive <= perDonor + .001) exhausted.push(d);
209
+ }
210
+ for (let i = exhausted.length - 1; i >= 0; i -= 1) donors.splice(exhausted[i], 1);
211
+ if (exhausted.length === 0) break;
212
+ }
213
+ result[growIdx] += wantedGrow - remaining;
214
+ }
215
+ return result;
216
+ }
217
+ function applyConstraints(sizes, panels, handleIndex, delta, redistribute) {
218
+ if (typeof redistribute === "function") return redistribute({
219
+ sizes: [...sizes],
220
+ panels,
221
+ handleIndex,
222
+ delta
223
+ });
224
+ if (redistribute === "nearest" || redistribute === "equal") {
225
+ const result = (redistribute === "nearest" ? redistributeNearest : redistributeEqual)(sizes, panels, handleIndex, delta);
226
+ const beforeIdx = handleIndex;
227
+ const afterIdx = handleIndex + 1;
228
+ const beforePanel = panels[beforeIdx];
229
+ const afterPanel = panels[afterIdx];
230
+ if (beforePanel.collapsible && result[beforeIdx] < getCollapseThreshold(beforePanel) && result[beforeIdx] < sizes[beforeIdx]) {
231
+ const freed = result[beforeIdx];
232
+ result[afterIdx] += freed;
233
+ result[beforeIdx] = 0;
234
+ } else if (afterPanel.collapsible && result[afterIdx] < getCollapseThreshold(afterPanel) && result[afterIdx] < sizes[afterIdx]) {
235
+ const freed = result[afterIdx];
236
+ result[beforeIdx] += freed;
237
+ result[afterIdx] = 0;
238
+ }
239
+ return result;
240
+ }
241
+ const collapsed = checkCollapse(sizes, panels, handleIndex, delta);
242
+ if (collapsed) return collapsed;
243
+ return applyAdjacentOnly(sizes, panels, handleIndex, delta);
244
+ }
245
+ function useSplitter(options) {
246
+ const { panels, orientation = "horizontal", sizes: controlledSizes, onSizeChange, onCollapseChange, redistribute, step = 1, shiftStep = 10, dir = "ltr", resetOnDoubleClick = true, enabled = true } = options;
247
+ const pixelMode = detectPixelMode(options);
248
+ const currentSizes = (0, vue.ref)([]);
249
+ const activeHandle = (0, vue.ref)(-1);
250
+ const containerSize = (0, vue.ref)(0);
251
+ const containerEl = (0, vue.shallowRef)(null);
252
+ const rootFontSizeRef = (0, vue.ref)(16);
253
+ const preCollapseSizes = (0, vue.ref)([]);
254
+ const handleElementControllers = /* @__PURE__ */ new Map();
255
+ const documentController = (0, vue.shallowRef)(null);
256
+ const frameId = (0, vue.ref)(0);
257
+ const isDragging = (0, vue.ref)(false);
258
+ const startData = (0, vue.ref)(null);
259
+ const optionsRef = (0, vue.shallowRef)(options);
260
+ optionsRef.value = options;
261
+ const defaultSizes = panels.map((p) => p.defaultSize);
262
+ const initSizes = () => {
263
+ if (controlledSizes) currentSizes.value = [...controlledSizes];
264
+ else currentSizes.value = [...defaultSizes];
265
+ };
266
+ initSizes();
267
+ const collapsed = (0, vue.computed)(() => currentSizes.value.map((s) => sizeMagnitude(s) === 0));
268
+ const updateSizes = (newSizes) => {
269
+ currentSizes.value = newSizes;
270
+ onSizeChange?.(newSizes);
271
+ };
272
+ const measureContainer = () => {
273
+ const node = containerEl.value;
274
+ if (!node) return 0;
275
+ const rect = node.getBoundingClientRect();
276
+ return orientation === "horizontal" ? rect.width : rect.height;
277
+ };
278
+ const collapsePanel = (panelIndex) => {
279
+ if (!panels[panelIndex]?.collapsible) return;
280
+ if (sizeMagnitude(currentSizes.value[panelIndex]) === 0) return;
281
+ const container = pixelMode ? containerSize.value || measureContainer() : 0;
282
+ const rootFontSize = rootFontSizeRef.value;
283
+ const working = resolveWorkingSizes(currentSizes.value, pixelMode, container, rootFontSize);
284
+ preCollapseSizes.value = [...currentSizes.value];
285
+ const freedSize = working[panelIndex];
286
+ working[panelIndex] = 0;
287
+ const neighbor = panelIndex === 0 ? 1 : panelIndex - 1;
288
+ working[neighbor] += freedSize;
289
+ updateSizes(working.map((value, i) => encodeSize(value, currentSizes.value[i], pixelMode, container, rootFontSize)));
290
+ onCollapseChange?.(panelIndex, true);
291
+ };
292
+ const expandPanel = (panelIndex) => {
293
+ if (!panels[panelIndex]?.collapsible) return;
294
+ if (sizeMagnitude(currentSizes.value[panelIndex]) !== 0) return;
295
+ const container = pixelMode ? containerSize.value || measureContainer() : 0;
296
+ const rootFontSize = rootFontSizeRef.value;
297
+ const working = resolveWorkingSizes(currentSizes.value, pixelMode, container, rootFontSize);
298
+ const preCollapse = preCollapseSizes.value;
299
+ const restoreSize = resolveSize(preCollapse[panelIndex] != null && sizeMagnitude(preCollapse[panelIndex]) !== 0 ? preCollapse[panelIndex] : panels[panelIndex].defaultSize, pixelMode, container, rootFontSize);
300
+ const neighbor = panelIndex === 0 ? 1 : panelIndex - 1;
301
+ const neighborMin = panels[neighbor].min != null ? resolveSize(panels[neighbor].min, pixelMode, container, rootFontSize) : 0;
302
+ const available = Math.max(0, working[neighbor] - neighborMin);
303
+ const actualRestore = Math.min(restoreSize, available);
304
+ if (actualRestore <= 0) return;
305
+ working[panelIndex] = actualRestore;
306
+ working[neighbor] -= actualRestore;
307
+ updateSizes(working.map((value, i) => encodeSize(value, currentSizes.value[i], pixelMode, container, rootFontSize)));
308
+ onCollapseChange?.(panelIndex, false);
309
+ };
310
+ const toggleCollapsePanel = (panelIndex) => {
311
+ if (sizeMagnitude(currentSizes.value[panelIndex]) === 0) expandPanel(panelIndex);
312
+ else collapsePanel(panelIndex);
313
+ };
314
+ const reset = (handleIndex) => {
315
+ const raw = currentSizes.value;
316
+ const beforeIdx = handleIndex;
317
+ const afterIdx = handleIndex + 1;
318
+ if (beforeIdx < 0 || afterIdx >= raw.length) return;
319
+ const container = pixelMode ? containerSize.value || measureContainer() : 0;
320
+ const rootFontSize = rootFontSizeRef.value;
321
+ const working = resolveWorkingSizes(raw, pixelMode, container, rootFontSize);
322
+ const resolvedPanels = panels.map((p) => resolvePanel(p, pixelMode, container, rootFontSize));
323
+ const total = working[beforeIdx] + working[afterIdx];
324
+ const defBefore = resolvedPanels[beforeIdx].defaultSize;
325
+ const defTotal = defBefore + resolvedPanels[afterIdx].defaultSize;
326
+ const next = applyAdjacentOnly(working, resolvedPanels, beforeIdx, (defTotal === 0 ? total / 2 : total * (defBefore / defTotal)) - working[beforeIdx]);
327
+ updateSizes(encodeWorkingSizes(next, working, raw, pixelMode, container, rootFontSize));
328
+ };
329
+ const containerRef = (el) => {
330
+ containerEl.value = el;
331
+ };
332
+ (0, vue.onMounted)(() => {
333
+ const node = containerEl.value;
334
+ if (!node) return;
335
+ const update = () => {
336
+ const rect = node.getBoundingClientRect();
337
+ const size = orientation === "horizontal" ? rect.width : rect.height;
338
+ rootFontSizeRef.value = getRootFontSize();
339
+ containerSize.value = size;
340
+ };
341
+ if (typeof ResizeObserver !== "undefined") {
342
+ const observer = new ResizeObserver(() => {
343
+ cancelAnimationFrame(frameId.value);
344
+ frameId.value = requestAnimationFrame(update);
345
+ });
346
+ observer.observe(node);
347
+ update();
348
+ (0, vue.onUnmounted)(() => {
349
+ cancelAnimationFrame(frameId.value);
350
+ observer.disconnect();
351
+ });
352
+ } else update();
353
+ });
354
+ (0, vue.onUnmounted)(() => {
355
+ documentController.value?.abort();
356
+ handleElementControllers.forEach((c) => c.abort());
357
+ handleElementControllers.clear();
358
+ cancelAnimationFrame(frameId.value);
359
+ });
360
+ const getHandleProps = (input) => {
361
+ const { index } = input;
362
+ const orient = orientation;
363
+ const rootFontSize = rootFontSizeRef.value;
364
+ const working = resolveWorkingSizes(currentSizes.value, pixelMode, containerSize.value, rootFontSize);
365
+ const resolvedPanels = panels.map((p) => resolvePanel(p, pixelMode, containerSize.value, rootFontSize));
366
+ const beforeSize = working[index] ?? 0;
367
+ const beforePanel = resolvedPanels[index];
368
+ const onKeyDown = (event) => {
369
+ if (!enabled) return;
370
+ const isHorizontal = orient === "horizontal";
371
+ const isRtl = dir === "rtl";
372
+ const container = pixelMode ? containerSize.value || measureContainer() : 0;
373
+ const liveRootFontSize = rootFontSizeRef.value;
374
+ const liveWorking = resolveWorkingSizes(currentSizes.value, pixelMode, container, liveRootFontSize);
375
+ const livePanels = panels.map((p) => resolvePanel(p, pixelMode, container, liveRootFontSize));
376
+ const liveBeforePanel = livePanels[index];
377
+ const liveAfterPanel = livePanels[index + 1];
378
+ let delta = 0;
379
+ const currentStep = resolveStep(event.shiftKey ? shiftStep : step, pixelMode, container, liveRootFontSize);
380
+ switch (event.key) {
381
+ case "ArrowLeft":
382
+ if (!isHorizontal) return;
383
+ delta = isRtl ? currentStep : -currentStep;
384
+ break;
385
+ case "ArrowRight":
386
+ if (!isHorizontal) return;
387
+ delta = isRtl ? -currentStep : currentStep;
388
+ break;
389
+ case "ArrowUp":
390
+ if (isHorizontal) return;
391
+ delta = -currentStep;
392
+ break;
393
+ case "ArrowDown":
394
+ if (isHorizontal) return;
395
+ delta = currentStep;
396
+ break;
397
+ case "Home":
398
+ delta = -(liveWorking[index] - getMin(liveBeforePanel));
399
+ break;
400
+ case "End":
401
+ delta = getMax(liveBeforePanel) - liveWorking[index];
402
+ break;
403
+ case "Enter":
404
+ const beforeCollapsible = liveBeforePanel?.collapsible;
405
+ const afterCollapsible = liveAfterPanel?.collapsible;
406
+ if (beforeCollapsible && liveWorking[index] <= liveWorking[index + 1]) {
407
+ toggleCollapsePanel(index);
408
+ event.preventDefault();
409
+ return;
410
+ }
411
+ if (afterCollapsible) {
412
+ toggleCollapsePanel(index + 1);
413
+ event.preventDefault();
414
+ return;
415
+ }
416
+ if (beforeCollapsible) {
417
+ toggleCollapsePanel(index);
418
+ event.preventDefault();
419
+ return;
420
+ }
421
+ return;
422
+ default: return;
423
+ }
424
+ event.preventDefault();
425
+ if (delta !== 0) {
426
+ const newSizes = applyConstraints(liveWorking, livePanels, index, delta, redistribute);
427
+ updateSizes(encodeWorkingSizes(newSizes, liveWorking, currentSizes.value, pixelMode, container, liveRootFontSize));
428
+ }
429
+ };
430
+ const onDoubleClick = () => {
431
+ if (!enabled || !resetOnDoubleClick) return;
432
+ reset(index);
433
+ };
434
+ return {
435
+ ref: getHandleRefCallback(index),
436
+ role: "separator",
437
+ "aria-orientation": orient,
438
+ "aria-valuenow": Math.round(beforeSize),
439
+ "aria-valuemin": Math.round(getMin(beforePanel)),
440
+ "aria-valuemax": Math.round(getMax(beforePanel)),
441
+ tabIndex: 0,
442
+ onKeyDown,
443
+ onDoubleClick,
444
+ "data-active": activeHandle.value === index || void 0,
445
+ "data-orientation": orient
446
+ };
447
+ };
448
+ const createPointerDownHandler = (handleIndex) => (event) => {
449
+ if (optionsRef.value.enabled === false) return;
450
+ if (event.button !== 0) return;
451
+ const container = containerEl.value;
452
+ if (!container) return;
453
+ const opts = optionsRef.value;
454
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
455
+ const rect = container.getBoundingClientRect();
456
+ const containerSizePx = isHorizontal ? rect.width : rect.height;
457
+ const pointerPos = isHorizontal ? event.clientX : event.clientY;
458
+ const isPixelMode = detectPixelMode(opts);
459
+ const rootFontSize = getRootFontSize();
460
+ const raw = currentSizes.value;
461
+ const startSizes = resolveWorkingSizes(raw, isPixelMode, containerSizePx, rootFontSize);
462
+ preCollapseSizes.value = [...raw];
463
+ isDragging.value = true;
464
+ activeHandle.value = handleIndex;
465
+ startData.value = {
466
+ handleIndex,
467
+ startPointer: pointerPos,
468
+ containerSize: containerSizePx,
469
+ rootFontSize,
470
+ pixelMode: isPixelMode,
471
+ startSizes,
472
+ startRaw: [...raw]
473
+ };
474
+ document.body.style.userSelect = "none";
475
+ document.body.style.webkitUserSelect = "none";
476
+ document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
477
+ opts.onResizeStart?.(handleIndex);
478
+ documentController.value?.abort();
479
+ documentController.value = new AbortController();
480
+ const sig = documentController.value.signal;
481
+ const flushResize = (pointerEvent) => {
482
+ const data = startData.value;
483
+ if (!data) return;
484
+ const opts = optionsRef.value;
485
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
486
+ const isRtl = isHorizontal && opts.dir === "rtl";
487
+ const pointerPos = isHorizontal ? pointerEvent.clientX : pointerEvent.clientY;
488
+ const pixelDelta = (isRtl ? -1 : 1) * (pointerPos - data.startPointer);
489
+ const delta = data.pixelMode ? pixelDelta : pixelDelta / data.containerSize * 100;
490
+ const resolvedPanels = panels.map((p) => resolvePanel(p, data.pixelMode, data.containerSize, data.rootFontSize));
491
+ const encoded = encodeWorkingSizes(applyConstraints(data.startSizes, resolvedPanels, data.handleIndex, delta, opts.redistribute), data.startSizes, data.startRaw, data.pixelMode, data.containerSize, data.rootFontSize);
492
+ updateSizes(encoded);
493
+ };
494
+ const onPointerMove = (event) => {
495
+ if (!isDragging.value) return;
496
+ cancelAnimationFrame(frameId.value);
497
+ frameId.value = requestAnimationFrame(() => flushResize(event));
498
+ };
499
+ const onPointerUp = (event) => {
500
+ if (!isDragging.value) return;
501
+ cancelAnimationFrame(frameId.value);
502
+ flushResize(event);
503
+ isDragging.value = false;
504
+ const finishHandle = activeHandle.value;
505
+ activeHandle.value = -1;
506
+ document.body.style.userSelect = "";
507
+ document.body.style.webkitUserSelect = "";
508
+ document.body.style.cursor = "";
509
+ documentController.value?.abort();
510
+ documentController.value = null;
511
+ optionsRef.value.onResizeEnd?.(finishHandle, [...currentSizes.value]);
512
+ startData.value = null;
513
+ };
514
+ document.addEventListener("pointermove", onPointerMove, { signal: sig });
515
+ document.addEventListener("pointerup", onPointerUp, { signal: sig });
516
+ document.addEventListener("pointercancel", onPointerUp, { signal: sig });
517
+ };
518
+ const createTouchStartHandler = (handleIndex) => (event) => {
519
+ if (optionsRef.value.enabled === false) return;
520
+ if (event.touches.length !== 1) return;
521
+ event.preventDefault();
522
+ const container = containerEl.value;
523
+ if (!container) return;
524
+ const opts = optionsRef.value;
525
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
526
+ const rect = container.getBoundingClientRect();
527
+ const containerSizePx = isHorizontal ? rect.width : rect.height;
528
+ const touch = event.touches[0];
529
+ const pointerPos = isHorizontal ? touch.clientX : touch.clientY;
530
+ const isPixelMode = detectPixelMode(opts);
531
+ const rootFontSize = getRootFontSize();
532
+ const raw = currentSizes.value;
533
+ const startSizes = resolveWorkingSizes(raw, isPixelMode, containerSizePx, rootFontSize);
534
+ preCollapseSizes.value = [...raw];
535
+ isDragging.value = true;
536
+ activeHandle.value = handleIndex;
537
+ startData.value = {
538
+ handleIndex,
539
+ startPointer: pointerPos,
540
+ containerSize: containerSizePx,
541
+ rootFontSize,
542
+ pixelMode: isPixelMode,
543
+ startSizes,
544
+ startRaw: [...raw]
545
+ };
546
+ document.body.style.userSelect = "none";
547
+ document.body.style.webkitUserSelect = "none";
548
+ document.body.style.cursor = isHorizontal ? "col-resize" : "row-resize";
549
+ opts.onResizeStart?.(handleIndex);
550
+ documentController.value?.abort();
551
+ documentController.value = new AbortController();
552
+ const sig = documentController.value.signal;
553
+ const flushResize = (touchEvent) => {
554
+ if (touchEvent.touches.length !== 1) return;
555
+ const data = startData.value;
556
+ if (!data) return;
557
+ const opts = optionsRef.value;
558
+ const isHorizontal = (opts.orientation ?? "horizontal") === "horizontal";
559
+ const isRtl = isHorizontal && opts.dir === "rtl";
560
+ const touch = touchEvent.touches[0];
561
+ const pointerPos = isHorizontal ? touch.clientX : touch.clientY;
562
+ const pixelDelta = (isRtl ? -1 : 1) * (pointerPos - data.startPointer);
563
+ const delta = data.pixelMode ? pixelDelta : pixelDelta / data.containerSize * 100;
564
+ const resolvedPanels = panels.map((p) => resolvePanel(p, data.pixelMode, data.containerSize, data.rootFontSize));
565
+ const encoded = encodeWorkingSizes(applyConstraints(data.startSizes, resolvedPanels, data.handleIndex, delta, opts.redistribute), data.startSizes, data.startRaw, data.pixelMode, data.containerSize, data.rootFontSize);
566
+ updateSizes(encoded);
567
+ };
568
+ const onTouchMove = (event) => {
569
+ if (event.touches.length !== 1) return;
570
+ if (!isDragging.value) return;
571
+ event.preventDefault();
572
+ cancelAnimationFrame(frameId.value);
573
+ frameId.value = requestAnimationFrame(() => flushResize(event));
574
+ };
575
+ const onTouchEnd = (event) => {
576
+ if (!isDragging.value) return;
577
+ cancelAnimationFrame(frameId.value);
578
+ flushResize(event);
579
+ isDragging.value = false;
580
+ const finishHandle = activeHandle.value;
581
+ activeHandle.value = -1;
582
+ document.body.style.userSelect = "";
583
+ document.body.style.webkitUserSelect = "";
584
+ document.body.style.cursor = "";
585
+ documentController.value?.abort();
586
+ documentController.value = null;
587
+ optionsRef.value.onResizeEnd?.(finishHandle, [...currentSizes.value]);
588
+ startData.value = null;
589
+ };
590
+ document.addEventListener("touchmove", onTouchMove, { signal: sig });
591
+ document.addEventListener("touchend", onTouchEnd, { signal: sig });
592
+ document.addEventListener("touchcancel", onTouchEnd, { signal: sig });
593
+ };
594
+ const createKeyDownHandler = (handleIndex) => (event) => {
595
+ if (!enabled) return;
596
+ const isHorizontal = orientation === "horizontal";
597
+ const isRtl = dir === "rtl";
598
+ const container = pixelMode ? containerSize.value || measureContainer() : 0;
599
+ const liveRootFontSize = rootFontSizeRef.value;
600
+ const liveWorking = resolveWorkingSizes(currentSizes.value, pixelMode, container, liveRootFontSize);
601
+ const livePanels = panels.map((p) => resolvePanel(p, pixelMode, container, liveRootFontSize));
602
+ const liveBeforePanel = livePanels[handleIndex];
603
+ const liveAfterPanel = livePanels[handleIndex + 1];
604
+ let delta = 0;
605
+ const currentStep = resolveStep(event.shiftKey ? shiftStep : step, pixelMode, container, liveRootFontSize);
606
+ switch (event.key) {
607
+ case "ArrowLeft":
608
+ if (!isHorizontal) return;
609
+ delta = isRtl ? currentStep : -currentStep;
610
+ break;
611
+ case "ArrowRight":
612
+ if (!isHorizontal) return;
613
+ delta = isRtl ? -currentStep : currentStep;
614
+ break;
615
+ case "ArrowUp":
616
+ if (isHorizontal) return;
617
+ delta = -currentStep;
618
+ break;
619
+ case "ArrowDown":
620
+ if (isHorizontal) return;
621
+ delta = currentStep;
622
+ break;
623
+ case "Home":
624
+ delta = -(liveWorking[handleIndex] - getMin(liveBeforePanel));
625
+ break;
626
+ case "End":
627
+ delta = getMax(liveBeforePanel) - liveWorking[handleIndex];
628
+ break;
629
+ case "Enter": {
630
+ const beforeCollapsible = liveBeforePanel?.collapsible;
631
+ const afterCollapsible = liveAfterPanel?.collapsible;
632
+ if (beforeCollapsible && liveWorking[handleIndex] <= liveWorking[handleIndex + 1]) {
633
+ toggleCollapsePanel(handleIndex);
634
+ event.preventDefault();
635
+ return;
636
+ }
637
+ if (afterCollapsible) {
638
+ toggleCollapsePanel(handleIndex + 1);
639
+ event.preventDefault();
640
+ return;
641
+ }
642
+ if (beforeCollapsible) {
643
+ toggleCollapsePanel(handleIndex);
644
+ event.preventDefault();
645
+ return;
646
+ }
647
+ return;
648
+ }
649
+ default: return;
650
+ }
651
+ event.preventDefault();
652
+ if (delta !== 0) {
653
+ const newSizes = applyConstraints(liveWorking, livePanels, handleIndex, delta, redistribute);
654
+ updateSizes(encodeWorkingSizes(newSizes, liveWorking, currentSizes.value, pixelMode, container, liveRootFontSize));
655
+ }
656
+ };
657
+ const createDoubleClickHandler = (handleIndex) => () => {
658
+ const opts = optionsRef.value;
659
+ if (opts.enabled === false || opts.resetOnDoubleClick === false) return;
660
+ reset(handleIndex);
661
+ };
662
+ const getHandleRefCallback = (handleIndex) => {
663
+ let controller = handleElementControllers.get(handleIndex);
664
+ const callback = (node) => {
665
+ if (controller) {
666
+ controller.abort();
667
+ handleElementControllers.delete(handleIndex);
668
+ controller = void 0;
669
+ }
670
+ if (!node) return;
671
+ controller = new AbortController();
672
+ handleElementControllers.set(handleIndex, controller);
673
+ const onPointerDown = createPointerDownHandler(handleIndex);
674
+ const onTouchStart = createTouchStartHandler(handleIndex);
675
+ node.addEventListener("pointerdown", onPointerDown, { signal: controller.signal });
676
+ node.addEventListener("touchstart", onTouchStart, { signal: controller.signal });
677
+ };
678
+ return callback;
679
+ };
680
+ const getHandleEventHandlers = (input) => {
681
+ const { index } = input;
682
+ return {
683
+ onPointerDown: createPointerDownHandler(index),
684
+ onTouchStart: createTouchStartHandler(index),
685
+ onKeyDown: createKeyDownHandler(index),
686
+ onDoubleClick: createDoubleClickHandler(index)
687
+ };
688
+ };
689
+ return {
690
+ sizes: (0, vue.computed)(() => currentSizes.value),
691
+ pixelMode,
692
+ collapsed: collapsed.value,
693
+ activeHandle: activeHandle.value,
694
+ containerRef,
695
+ getHandleProps,
696
+ getHandleEventHandlers,
697
+ setSizes: updateSizes,
698
+ collapse: collapsePanel,
699
+ expand: expandPanel,
700
+ toggleCollapse: toggleCollapsePanel,
701
+ reset
702
+ };
703
+ }
704
+ //#endregion
705
+ exports.useSplitter = useSplitter;
706
+
707
+ //# sourceMappingURL=use-splitter.cjs.map