@delightstack/components 0.1.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/LICENSE +21 -0
- package/README.md +136 -0
- package/SKILL.md +149 -0
- package/bin/agents.js +63 -0
- package/dist/actions/Alert.svelte +202 -0
- package/dist/actions/Alert.svelte.d.ts +36 -0
- package/dist/actions/Alert.svelte.d.ts.map +1 -0
- package/dist/actions/Button.svelte +1450 -0
- package/dist/actions/Button.svelte.d.ts +56 -0
- package/dist/actions/Button.svelte.d.ts.map +1 -0
- package/dist/actions/ButtonGroup.svelte +111 -0
- package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
- package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/actions/CommandPalette.svelte +939 -0
- package/dist/actions/CommandPalette.svelte.d.ts +37 -0
- package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/actions/ContextMenu.svelte +138 -0
- package/dist/actions/ContextMenu.svelte.d.ts +54 -0
- package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/actions/Modal.svelte +474 -0
- package/dist/actions/Modal.svelte.d.ts +28 -0
- package/dist/actions/Modal.svelte.d.ts.map +1 -0
- package/dist/actions/Popover.svelte +1214 -0
- package/dist/actions/Popover.svelte.d.ts +31 -0
- package/dist/actions/Popover.svelte.d.ts.map +1 -0
- package/dist/actions/Portal.svelte +80 -0
- package/dist/actions/Portal.svelte.d.ts +17 -0
- package/dist/actions/Portal.svelte.d.ts.map +1 -0
- package/dist/actions/ThemeToggle.svelte +345 -0
- package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
- package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/actions/index.d.ts +13 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +10 -0
- package/dist/actions/scrollbar.d.ts +48 -0
- package/dist/actions/scrollbar.d.ts.map +1 -0
- package/dist/actions/scrollbar.js +404 -0
- package/dist/display/Accordion.svelte +586 -0
- package/dist/display/Accordion.svelte.d.ts +41 -0
- package/dist/display/Accordion.svelte.d.ts.map +1 -0
- package/dist/display/Avatar.svelte +527 -0
- package/dist/display/Avatar.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte.d.ts.map +1 -0
- package/dist/display/AvatarGroup.svelte +298 -0
- package/dist/display/AvatarGroup.svelte.d.ts +31 -0
- package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
- package/dist/display/Calendar.svelte +1366 -0
- package/dist/display/Calendar.svelte.d.ts +58 -0
- package/dist/display/Calendar.svelte.d.ts.map +1 -0
- package/dist/display/Chart.svelte +1426 -0
- package/dist/display/Chart.svelte.d.ts +35 -0
- package/dist/display/Chart.svelte.d.ts.map +1 -0
- package/dist/display/Code.svelte +780 -0
- package/dist/display/Code.svelte.d.ts +19 -0
- package/dist/display/Code.svelte.d.ts.map +1 -0
- package/dist/display/Comparison.svelte +686 -0
- package/dist/display/Comparison.svelte.d.ts +22 -0
- package/dist/display/Comparison.svelte.d.ts.map +1 -0
- package/dist/display/Counter.svelte +285 -0
- package/dist/display/Counter.svelte.d.ts +21 -0
- package/dist/display/Counter.svelte.d.ts.map +1 -0
- package/dist/display/Expand.svelte +48 -0
- package/dist/display/Expand.svelte.d.ts +9 -0
- package/dist/display/Expand.svelte.d.ts.map +1 -0
- package/dist/display/List.svelte +294 -0
- package/dist/display/List.svelte.d.ts +40 -0
- package/dist/display/List.svelte.d.ts.map +1 -0
- package/dist/display/ListContextReset.svelte +19 -0
- package/dist/display/ListContextReset.svelte.d.ts +7 -0
- package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
- package/dist/display/ListItem.svelte +834 -0
- package/dist/display/ListItem.svelte.d.ts +22 -0
- package/dist/display/ListItem.svelte.d.ts.map +1 -0
- package/dist/display/QR.svelte +1193 -0
- package/dist/display/QR.svelte.d.ts +23 -0
- package/dist/display/QR.svelte.d.ts.map +1 -0
- package/dist/display/SplitPane.svelte +744 -0
- package/dist/display/SplitPane.svelte.d.ts +25 -0
- package/dist/display/SplitPane.svelte.d.ts.map +1 -0
- package/dist/display/Stat.svelte +439 -0
- package/dist/display/Stat.svelte.d.ts +24 -0
- package/dist/display/Stat.svelte.d.ts.map +1 -0
- package/dist/display/Table.svelte +4654 -0
- package/dist/display/Table.svelte.d.ts +249 -0
- package/dist/display/Table.svelte.d.ts.map +1 -0
- package/dist/display/TableCellEditor.svelte +935 -0
- package/dist/display/TableCellEditor.svelte.d.ts +58 -0
- package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
- package/dist/display/Timeline.svelte +1258 -0
- package/dist/display/Timeline.svelte.d.ts +43 -0
- package/dist/display/Timeline.svelte.d.ts.map +1 -0
- package/dist/display/Tree.svelte +1740 -0
- package/dist/display/Tree.svelte.d.ts +74 -0
- package/dist/display/Tree.svelte.d.ts.map +1 -0
- package/dist/display/Typewriter.svelte +338 -0
- package/dist/display/Typewriter.svelte.d.ts +22 -0
- package/dist/display/Typewriter.svelte.d.ts.map +1 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +18 -0
- package/dist/feedback/Callout.svelte +529 -0
- package/dist/feedback/Callout.svelte.d.ts +24 -0
- package/dist/feedback/Callout.svelte.d.ts.map +1 -0
- package/dist/feedback/Confetti.svelte +631 -0
- package/dist/feedback/Confetti.svelte.d.ts +90 -0
- package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
- package/dist/feedback/Progress.svelte +382 -0
- package/dist/feedback/Progress.svelte.d.ts +25 -0
- package/dist/feedback/Progress.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast.svelte +967 -0
- package/dist/feedback/Toast.svelte.d.ts +54 -0
- package/dist/feedback/Toast.svelte.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +7 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/index.js +4 -0
- package/dist/form/Checkbox.svelte +449 -0
- package/dist/form/Checkbox.svelte.d.ts +27 -0
- package/dist/form/Checkbox.svelte.d.ts.map +1 -0
- package/dist/form/Fieldset.svelte +410 -0
- package/dist/form/Fieldset.svelte.d.ts +22 -0
- package/dist/form/Fieldset.svelte.d.ts.map +1 -0
- package/dist/form/FileUpload.svelte +934 -0
- package/dist/form/FileUpload.svelte.d.ts +41 -0
- package/dist/form/FileUpload.svelte.d.ts.map +1 -0
- package/dist/form/Form.svelte +530 -0
- package/dist/form/Form.svelte.d.ts +120 -0
- package/dist/form/Form.svelte.d.ts.map +1 -0
- package/dist/form/Input.svelte +2858 -0
- package/dist/form/Input.svelte.d.ts +66 -0
- package/dist/form/Input.svelte.d.ts.map +1 -0
- package/dist/form/Radio.svelte +507 -0
- package/dist/form/Radio.svelte.d.ts +39 -0
- package/dist/form/Radio.svelte.d.ts.map +1 -0
- package/dist/form/Range.svelte +912 -0
- package/dist/form/Range.svelte.d.ts +33 -0
- package/dist/form/Range.svelte.d.ts.map +1 -0
- package/dist/form/Rating.svelte +429 -0
- package/dist/form/Rating.svelte.d.ts +28 -0
- package/dist/form/Rating.svelte.d.ts.map +1 -0
- package/dist/form/Select.svelte +1933 -0
- package/dist/form/Select.svelte.d.ts +54 -0
- package/dist/form/Select.svelte.d.ts.map +1 -0
- package/dist/form/Toggle.svelte +645 -0
- package/dist/form/Toggle.svelte.d.ts +50 -0
- package/dist/form/Toggle.svelte.d.ts.map +1 -0
- package/dist/form/index.d.ts +15 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/layout/README.md +172 -0
- package/dist/media/Carousel.svelte +2424 -0
- package/dist/media/Carousel.svelte.d.ts +47 -0
- package/dist/media/Carousel.svelte.d.ts.map +1 -0
- package/dist/media/Gallery.svelte +2881 -0
- package/dist/media/Gallery.svelte.d.ts +82 -0
- package/dist/media/Gallery.svelte.d.ts.map +1 -0
- package/dist/media/Image.svelte +389 -0
- package/dist/media/Image.svelte.d.ts +33 -0
- package/dist/media/Image.svelte.d.ts.map +1 -0
- package/dist/media/PDF.svelte +1793 -0
- package/dist/media/PDF.svelte.d.ts +44 -0
- package/dist/media/PDF.svelte.d.ts.map +1 -0
- package/dist/media/Panorama.svelte +1391 -0
- package/dist/media/Panorama.svelte.d.ts +47 -0
- package/dist/media/Panorama.svelte.d.ts.map +1 -0
- package/dist/media/Video.svelte +2501 -0
- package/dist/media/Video.svelte.d.ts +58 -0
- package/dist/media/Video.svelte.d.ts.map +1 -0
- package/dist/media/carousel.d.ts +211 -0
- package/dist/media/carousel.d.ts.map +1 -0
- package/dist/media/carousel.js +408 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/navigation/BottomSheet.svelte +636 -0
- package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
- package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
- package/dist/navigation/Breadcrumbs.svelte +611 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/navigation/Pagination.svelte +641 -0
- package/dist/navigation/Pagination.svelte.d.ts +27 -0
- package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
- package/dist/navigation/Steps.svelte +965 -0
- package/dist/navigation/Steps.svelte.d.ts +43 -0
- package/dist/navigation/Steps.svelte.d.ts.map +1 -0
- package/dist/navigation/Tabs.svelte +698 -0
- package/dist/navigation/Tabs.svelte.d.ts +41 -0
- package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { type Snippet } from 'svelte';
|
|
3
|
+
import { scrollbar } from '../actions/scrollbar';
|
|
4
|
+
|
|
5
|
+
const propId = $props.id();
|
|
6
|
+
let {
|
|
7
|
+
/** Whether the split is vertical (top/bottom) instead of horizontal (left/right) */
|
|
8
|
+
vertical = false,
|
|
9
|
+
|
|
10
|
+
/** First pane size as a percentage (0-100), bindable */
|
|
11
|
+
size = $bindable(50),
|
|
12
|
+
|
|
13
|
+
/** Minimum first pane size as a percentage */
|
|
14
|
+
min_size = 10,
|
|
15
|
+
|
|
16
|
+
/** Maximum first pane size as a percentage */
|
|
17
|
+
max_size = 90,
|
|
18
|
+
|
|
19
|
+
/** Snap points as percentages */
|
|
20
|
+
snap = [] as number[],
|
|
21
|
+
|
|
22
|
+
/** Distance in percentage to trigger snap */
|
|
23
|
+
snap_threshold = 8,
|
|
24
|
+
|
|
25
|
+
/** Whether panes can be collapsed */
|
|
26
|
+
collapsible = false,
|
|
27
|
+
|
|
28
|
+
/** Which pane is currently collapsed, bindable */
|
|
29
|
+
collapsed = $bindable(null) as 'first' | 'second' | null,
|
|
30
|
+
|
|
31
|
+
/** The ID of the element */
|
|
32
|
+
id = propId,
|
|
33
|
+
|
|
34
|
+
/** Specifies a custom class name for the container element */
|
|
35
|
+
class: class_name = '',
|
|
36
|
+
|
|
37
|
+
/** First pane content */
|
|
38
|
+
first = undefined as undefined | Snippet,
|
|
39
|
+
|
|
40
|
+
/** Second pane content */
|
|
41
|
+
second = undefined as undefined | Snippet,
|
|
42
|
+
|
|
43
|
+
/** Called when pane size changes */
|
|
44
|
+
onresize = undefined as ((detail: { size: number }) => void) | undefined,
|
|
45
|
+
|
|
46
|
+
/** Called when a pane is collapsed or expanded */
|
|
47
|
+
oncollapse = undefined as
|
|
48
|
+
| ((detail: { pane: 'first' | 'second' | null }) => void)
|
|
49
|
+
| undefined,
|
|
50
|
+
}: {
|
|
51
|
+
vertical?: boolean;
|
|
52
|
+
size?: number;
|
|
53
|
+
min_size?: number;
|
|
54
|
+
max_size?: number;
|
|
55
|
+
snap?: number[];
|
|
56
|
+
snap_threshold?: number;
|
|
57
|
+
collapsible?: boolean;
|
|
58
|
+
collapsed?: 'first' | 'second' | null;
|
|
59
|
+
id?: string;
|
|
60
|
+
class?: string;
|
|
61
|
+
first?: Snippet;
|
|
62
|
+
second?: Snippet;
|
|
63
|
+
onresize?: (detail: { size: number }) => void;
|
|
64
|
+
oncollapse?: (detail: { pane: 'first' | 'second' | null }) => void;
|
|
65
|
+
} = $props();
|
|
66
|
+
|
|
67
|
+
let container: HTMLElement | undefined = $state(undefined);
|
|
68
|
+
let dragging = $state(false);
|
|
69
|
+
let animating = $state(false);
|
|
70
|
+
let overshoot_px = $state(0);
|
|
71
|
+
let snapping = $state(false);
|
|
72
|
+
let snap_timer: ReturnType<typeof setTimeout> | undefined;
|
|
73
|
+
let animating_timer: ReturnType<typeof setTimeout> | undefined;
|
|
74
|
+
let last_pointer_coord = 0;
|
|
75
|
+
let snapped_to: number | null = null;
|
|
76
|
+
let expanded_during_drag = false;
|
|
77
|
+
|
|
78
|
+
const COLLAPSE_THRESHOLD = 5;
|
|
79
|
+
|
|
80
|
+
/** The size value before a collapse, so we can restore it on expand */
|
|
81
|
+
let size_before_collapse = $state(50);
|
|
82
|
+
|
|
83
|
+
const clamped_size = $derived(Math.min(max_size, Math.max(min_size, size)));
|
|
84
|
+
|
|
85
|
+
/** The flex-basis for the first pane (includes overshoot for smooth snap/rubber band) */
|
|
86
|
+
const first_basis = $derived.by(() => {
|
|
87
|
+
if (collapsed === 'first') return '0%';
|
|
88
|
+
if (collapsed === 'second') return '100%';
|
|
89
|
+
return `calc(${clamped_size}% + ${overshoot_px}px)`;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/** The flex-basis for the second pane (includes overshoot for smooth snap/rubber band) */
|
|
93
|
+
const second_basis = $derived.by(() => {
|
|
94
|
+
if (collapsed === 'first') return '100%';
|
|
95
|
+
if (collapsed === 'second') return '0%';
|
|
96
|
+
return `calc(${100 - clamped_size}% - ${overshoot_px}px)`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/** Convert a pointer position to a percentage of the container */
|
|
100
|
+
function pointerToPercent(clientX: number, clientY: number): number {
|
|
101
|
+
if (!container) return size;
|
|
102
|
+
const rect = container.getBoundingClientRect();
|
|
103
|
+
let percent: number;
|
|
104
|
+
if (vertical) {
|
|
105
|
+
percent = ((clientY - rect.top) / rect.height) * 100;
|
|
106
|
+
} else {
|
|
107
|
+
percent = ((clientX - rect.left) / rect.width) * 100;
|
|
108
|
+
}
|
|
109
|
+
return percent;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Apply snap points and clamping to a raw percentage */
|
|
113
|
+
function applyConstraints(percent: number): number {
|
|
114
|
+
// Clamp to min/max
|
|
115
|
+
let result = Math.min(max_size, Math.max(min_size, percent));
|
|
116
|
+
|
|
117
|
+
// Apply snap points with hysteresis: once snapped, require a much larger
|
|
118
|
+
// movement to escape (2.2x threshold), making snaps feel strongly sticky
|
|
119
|
+
if (snap.length > 0) {
|
|
120
|
+
const threshold = snapped_to !== null ? snap_threshold * 2.2 : snap_threshold;
|
|
121
|
+
let best_snap = -1;
|
|
122
|
+
let min_dist = Infinity;
|
|
123
|
+
|
|
124
|
+
for (const point of snap) {
|
|
125
|
+
const dist = Math.abs(result - point);
|
|
126
|
+
if (dist < min_dist && dist <= threshold) {
|
|
127
|
+
min_dist = dist;
|
|
128
|
+
best_snap = point;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (best_snap >= 0) {
|
|
133
|
+
result = best_snap;
|
|
134
|
+
snapped_to = best_snap;
|
|
135
|
+
} else {
|
|
136
|
+
snapped_to = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Round to 2 decimal places for cleanliness
|
|
141
|
+
return Math.round(result * 100) / 100;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Update the size and fire the onresize event */
|
|
145
|
+
function updateSize(new_size: number) {
|
|
146
|
+
const constrained = applyConstraints(new_size);
|
|
147
|
+
if (constrained !== size) {
|
|
148
|
+
size = constrained;
|
|
149
|
+
onresize?.({ size });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Compute visual overshoot for magnetic snap gravity and edge rubber band */
|
|
154
|
+
function updateOvershoot() {
|
|
155
|
+
if (!container) return;
|
|
156
|
+
const rect = container.getBoundingClientRect();
|
|
157
|
+
const dimension = vertical ? rect.height : rect.width;
|
|
158
|
+
const raw_pct = vertical
|
|
159
|
+
? (last_pointer_coord - rect.top) / rect.height
|
|
160
|
+
: (last_pointer_coord - rect.left) / rect.width;
|
|
161
|
+
const raw_percent = raw_pct * 100;
|
|
162
|
+
|
|
163
|
+
if (raw_percent < min_size) {
|
|
164
|
+
// Edge rubber band past min (tanh bounded)
|
|
165
|
+
const overflow_px = ((raw_percent - min_size) / 100) * dimension;
|
|
166
|
+
const max_shift = 24;
|
|
167
|
+
overshoot_px = max_shift * Math.tanh(overflow_px / 80);
|
|
168
|
+
} else if (raw_percent > max_size) {
|
|
169
|
+
// Edge rubber band past max (tanh bounded)
|
|
170
|
+
const overflow_px = ((raw_percent - max_size) / 100) * dimension;
|
|
171
|
+
const max_shift = 24;
|
|
172
|
+
overshoot_px = max_shift * Math.tanh(overflow_px / 80);
|
|
173
|
+
} else if (snapped_to !== null) {
|
|
174
|
+
// Magnetic snap gravity — smooth easing that reaches full-follow
|
|
175
|
+
// at the snap zone boundary, guaranteeing visual continuity.
|
|
176
|
+
// Uses the wider escape threshold to match hysteresis zone.
|
|
177
|
+
const snapped_pct = snapped_to / 100;
|
|
178
|
+
const pull_px = (raw_pct - snapped_pct) * dimension;
|
|
179
|
+
const escape_radius_px = ((snap_threshold * 2.2) / 100) * dimension;
|
|
180
|
+
|
|
181
|
+
if (escape_radius_px < 1) {
|
|
182
|
+
overshoot_px = 0;
|
|
183
|
+
} else {
|
|
184
|
+
const t = Math.min(1, Math.abs(pull_px) / escape_radius_px);
|
|
185
|
+
const gravity = 0.16;
|
|
186
|
+
const eased = gravity * t + (1 - gravity) * t * t;
|
|
187
|
+
overshoot_px = Math.sign(pull_px) * eased * escape_radius_px;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
overshoot_px = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Clean up drag state and listeners */
|
|
195
|
+
function stopDrag(trigger_snap_back = true) {
|
|
196
|
+
dragging = false;
|
|
197
|
+
snapped_to = null;
|
|
198
|
+
if (trigger_snap_back && Math.abs(overshoot_px) > 0.5) {
|
|
199
|
+
snapping = true;
|
|
200
|
+
clearTimeout(snap_timer);
|
|
201
|
+
snap_timer = setTimeout(() => {
|
|
202
|
+
snapping = false;
|
|
203
|
+
}, 400);
|
|
204
|
+
}
|
|
205
|
+
overshoot_px = 0;
|
|
206
|
+
document.removeEventListener('mousemove', handlePointerMove);
|
|
207
|
+
document.removeEventListener('mouseup', handlePointerUp);
|
|
208
|
+
document.removeEventListener('touchmove', handleTouchMove);
|
|
209
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Collapse or expand a pane */
|
|
213
|
+
function setCollapsed(pane: 'first' | 'second' | null) {
|
|
214
|
+
if (!collapsible) return;
|
|
215
|
+
|
|
216
|
+
if (pane !== null && collapsed === null) {
|
|
217
|
+
// Collapsing: save current size
|
|
218
|
+
size_before_collapse = size;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
animating = true;
|
|
222
|
+
collapsed = pane;
|
|
223
|
+
oncollapse?.({ pane });
|
|
224
|
+
|
|
225
|
+
if (pane === null) {
|
|
226
|
+
// Restoring: set size back
|
|
227
|
+
size = size_before_collapse;
|
|
228
|
+
onresize?.({ size });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Remove animating flag after transition completes
|
|
232
|
+
clearTimeout(animating_timer);
|
|
233
|
+
animating_timer = setTimeout(() => {
|
|
234
|
+
animating = false;
|
|
235
|
+
}, 200);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Toggle collapse: collapse the smaller pane, or expand if already collapsed */
|
|
239
|
+
function toggleCollapse() {
|
|
240
|
+
if (!collapsible) return;
|
|
241
|
+
if (collapsed !== null) {
|
|
242
|
+
setCollapsed(null);
|
|
243
|
+
} else {
|
|
244
|
+
// Collapse the smaller pane
|
|
245
|
+
setCollapsed(size <= 50 ? 'first' : 'second');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Remove document listeners and pending timers if the component unmounts mid-drag
|
|
250
|
+
$effect(() => {
|
|
251
|
+
return () => {
|
|
252
|
+
stopDrag(false);
|
|
253
|
+
clearTimeout(snap_timer);
|
|
254
|
+
clearTimeout(animating_timer);
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ---- Pointer drag handling ----
|
|
259
|
+
|
|
260
|
+
function handlePointerDown(e: MouseEvent) {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
dragging = true;
|
|
263
|
+
snapping = false;
|
|
264
|
+
expanded_during_drag = false;
|
|
265
|
+
clearTimeout(snap_timer);
|
|
266
|
+
last_pointer_coord = vertical ? e.clientY : e.clientX;
|
|
267
|
+
document.addEventListener('mousemove', handlePointerMove);
|
|
268
|
+
document.addEventListener('mouseup', handlePointerUp);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function handlePointerMove(e: MouseEvent) {
|
|
272
|
+
if (!dragging) return;
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
const raw = pointerToPercent(e.clientX, e.clientY);
|
|
275
|
+
|
|
276
|
+
// Expand collapsed pane by dragging away from edge
|
|
277
|
+
if (collapsed !== null) {
|
|
278
|
+
const should_expand =
|
|
279
|
+
(collapsed === 'first' && raw > COLLAPSE_THRESHOLD) ||
|
|
280
|
+
(collapsed === 'second' && raw < 100 - COLLAPSE_THRESHOLD);
|
|
281
|
+
if (should_expand) {
|
|
282
|
+
collapsed = null;
|
|
283
|
+
expanded_during_drag = true;
|
|
284
|
+
oncollapse?.({ pane: null });
|
|
285
|
+
updateSize(raw);
|
|
286
|
+
last_pointer_coord = vertical ? e.clientY : e.clientX;
|
|
287
|
+
updateOvershoot();
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Don't re-collapse in the same drag that expanded a pane
|
|
293
|
+
if (collapsible && !expanded_during_drag) {
|
|
294
|
+
if (raw < COLLAPSE_THRESHOLD) {
|
|
295
|
+
stopDrag(false);
|
|
296
|
+
setCollapsed('first');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (raw > 100 - COLLAPSE_THRESHOLD) {
|
|
300
|
+
stopDrag(false);
|
|
301
|
+
setCollapsed('second');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
updateSize(raw);
|
|
307
|
+
last_pointer_coord = vertical ? e.clientY : e.clientX;
|
|
308
|
+
updateOvershoot();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function handlePointerUp() {
|
|
312
|
+
if (!dragging) return;
|
|
313
|
+
stopDrag();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Touch handling
|
|
317
|
+
function handleTouchStart(e: TouchEvent) {
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
dragging = true;
|
|
320
|
+
snapping = false;
|
|
321
|
+
expanded_during_drag = false;
|
|
322
|
+
clearTimeout(snap_timer);
|
|
323
|
+
const touch = e.touches[0];
|
|
324
|
+
last_pointer_coord = vertical ? touch.clientY : touch.clientX;
|
|
325
|
+
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
326
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function handleTouchMove(e: TouchEvent) {
|
|
330
|
+
if (!dragging) return;
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
const touch = e.touches[0];
|
|
333
|
+
const raw = pointerToPercent(touch.clientX, touch.clientY);
|
|
334
|
+
|
|
335
|
+
// Expand collapsed pane by dragging away from edge
|
|
336
|
+
if (collapsed !== null) {
|
|
337
|
+
const should_expand =
|
|
338
|
+
(collapsed === 'first' && raw > COLLAPSE_THRESHOLD) ||
|
|
339
|
+
(collapsed === 'second' && raw < 100 - COLLAPSE_THRESHOLD);
|
|
340
|
+
if (should_expand) {
|
|
341
|
+
collapsed = null;
|
|
342
|
+
expanded_during_drag = true;
|
|
343
|
+
oncollapse?.({ pane: null });
|
|
344
|
+
updateSize(raw);
|
|
345
|
+
last_pointer_coord = vertical ? touch.clientY : touch.clientX;
|
|
346
|
+
updateOvershoot();
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Don't re-collapse in the same drag that expanded a pane
|
|
352
|
+
if (collapsible && !expanded_during_drag) {
|
|
353
|
+
if (raw < COLLAPSE_THRESHOLD) {
|
|
354
|
+
stopDrag(false);
|
|
355
|
+
setCollapsed('first');
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (raw > 100 - COLLAPSE_THRESHOLD) {
|
|
359
|
+
stopDrag(false);
|
|
360
|
+
setCollapsed('second');
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
updateSize(raw);
|
|
366
|
+
last_pointer_coord = vertical ? touch.clientY : touch.clientX;
|
|
367
|
+
updateOvershoot();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function handleTouchEnd() {
|
|
371
|
+
if (!dragging) return;
|
|
372
|
+
stopDrag();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Double-click to collapse
|
|
376
|
+
function handleDblClick() {
|
|
377
|
+
if (!collapsible) return;
|
|
378
|
+
toggleCollapse();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ---- Keyboard handling ----
|
|
382
|
+
|
|
383
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
384
|
+
const step = e.shiftKey ? 5 : 1;
|
|
385
|
+
let new_size = collapsed === null ? size : size_before_collapse;
|
|
386
|
+
|
|
387
|
+
switch (e.key) {
|
|
388
|
+
case 'ArrowLeft':
|
|
389
|
+
case 'ArrowUp':
|
|
390
|
+
e.preventDefault();
|
|
391
|
+
if (collapsed !== null) {
|
|
392
|
+
setCollapsed(null);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
new_size = size - step;
|
|
396
|
+
break;
|
|
397
|
+
case 'ArrowRight':
|
|
398
|
+
case 'ArrowDown':
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
if (collapsed !== null) {
|
|
401
|
+
setCollapsed(null);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
new_size = size + step;
|
|
405
|
+
break;
|
|
406
|
+
case 'Home':
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
if (collapsed !== null) {
|
|
409
|
+
setCollapsed(null);
|
|
410
|
+
}
|
|
411
|
+
new_size = min_size;
|
|
412
|
+
break;
|
|
413
|
+
case 'End':
|
|
414
|
+
e.preventDefault();
|
|
415
|
+
if (collapsed !== null) {
|
|
416
|
+
setCollapsed(null);
|
|
417
|
+
}
|
|
418
|
+
new_size = max_size;
|
|
419
|
+
break;
|
|
420
|
+
case 'Enter':
|
|
421
|
+
e.preventDefault();
|
|
422
|
+
toggleCollapse();
|
|
423
|
+
return;
|
|
424
|
+
default:
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
updateSize(new_size);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ---- Expand button handler ----
|
|
432
|
+
|
|
433
|
+
function handleExpand() {
|
|
434
|
+
setCollapsed(null);
|
|
435
|
+
}
|
|
436
|
+
</script>
|
|
437
|
+
|
|
438
|
+
<div
|
|
439
|
+
class={['split-pane', class_name].filter(Boolean).join(' ')}
|
|
440
|
+
class:vertical
|
|
441
|
+
class:horizontal={!vertical}
|
|
442
|
+
class:dragging
|
|
443
|
+
class:snapping
|
|
444
|
+
class:animating
|
|
445
|
+
class:collapsed-first={collapsed === 'first'}
|
|
446
|
+
class:collapsed-second={collapsed === 'second'}
|
|
447
|
+
{id}
|
|
448
|
+
bind:this={container}>
|
|
449
|
+
<!-- First pane -->
|
|
450
|
+
<div
|
|
451
|
+
class="pane first"
|
|
452
|
+
style:flex-basis={first_basis}
|
|
453
|
+
aria-hidden={collapsed === 'first' || undefined}
|
|
454
|
+
{@attach scrollbar()}>
|
|
455
|
+
{#if first}
|
|
456
|
+
{@render first()}
|
|
457
|
+
{/if}
|
|
458
|
+
{#if collapsible && collapsed === 'first'}
|
|
459
|
+
<button
|
|
460
|
+
class:vertical
|
|
461
|
+
type="button"
|
|
462
|
+
aria-label="Expand first pane"
|
|
463
|
+
onclick={handleExpand}>
|
|
464
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
465
|
+
{#if vertical}
|
|
466
|
+
<path
|
|
467
|
+
d="M3 6l5 5 5-5"
|
|
468
|
+
fill="none"
|
|
469
|
+
stroke="currentColor"
|
|
470
|
+
stroke-width="2"
|
|
471
|
+
stroke-linecap="round"
|
|
472
|
+
stroke-linejoin="round" />
|
|
473
|
+
{:else}
|
|
474
|
+
<path
|
|
475
|
+
d="M6 3l5 5-5 5"
|
|
476
|
+
fill="none"
|
|
477
|
+
stroke="currentColor"
|
|
478
|
+
stroke-width="2"
|
|
479
|
+
stroke-linecap="round"
|
|
480
|
+
stroke-linejoin="round" />
|
|
481
|
+
{/if}
|
|
482
|
+
</svg>
|
|
483
|
+
</button>
|
|
484
|
+
{/if}
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<!-- Divider -->
|
|
488
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
489
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
490
|
+
<div
|
|
491
|
+
class="divider"
|
|
492
|
+
role="separator"
|
|
493
|
+
tabindex="0"
|
|
494
|
+
aria-orientation={vertical ? 'vertical' : 'horizontal'}
|
|
495
|
+
aria-valuenow={Math.round(clamped_size)}
|
|
496
|
+
aria-valuemin={min_size}
|
|
497
|
+
aria-valuemax={max_size}
|
|
498
|
+
aria-label="Resize panes"
|
|
499
|
+
onmousedown={handlePointerDown}
|
|
500
|
+
ontouchstart={handleTouchStart}
|
|
501
|
+
ondblclick={handleDblClick}
|
|
502
|
+
onkeydown={handleKeyDown}>
|
|
503
|
+
<div class="handle"></div>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<!-- Second pane -->
|
|
507
|
+
<div
|
|
508
|
+
class="pane second"
|
|
509
|
+
style:flex-basis={second_basis}
|
|
510
|
+
aria-hidden={collapsed === 'second' || undefined}
|
|
511
|
+
{@attach scrollbar()}>
|
|
512
|
+
{#if collapsible && collapsed === 'second'}
|
|
513
|
+
<button
|
|
514
|
+
class:vertical
|
|
515
|
+
type="button"
|
|
516
|
+
aria-label="Expand second pane"
|
|
517
|
+
onclick={handleExpand}>
|
|
518
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
519
|
+
{#if vertical}
|
|
520
|
+
<path
|
|
521
|
+
d="M3 10l5-5 5 5"
|
|
522
|
+
fill="none"
|
|
523
|
+
stroke="currentColor"
|
|
524
|
+
stroke-width="2"
|
|
525
|
+
stroke-linecap="round"
|
|
526
|
+
stroke-linejoin="round" />
|
|
527
|
+
{:else}
|
|
528
|
+
<path
|
|
529
|
+
d="M10 3l-5 5 5 5"
|
|
530
|
+
fill="none"
|
|
531
|
+
stroke="currentColor"
|
|
532
|
+
stroke-width="2"
|
|
533
|
+
stroke-linecap="round"
|
|
534
|
+
stroke-linejoin="round" />
|
|
535
|
+
{/if}
|
|
536
|
+
</svg>
|
|
537
|
+
</button>
|
|
538
|
+
{/if}
|
|
539
|
+
{#if second}
|
|
540
|
+
{@render second()}
|
|
541
|
+
{/if}
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<style>
|
|
546
|
+
.split-pane {
|
|
547
|
+
display: flex;
|
|
548
|
+
width: 100%;
|
|
549
|
+
height: 100%;
|
|
550
|
+
overflow: hidden;
|
|
551
|
+
|
|
552
|
+
&.horizontal {
|
|
553
|
+
flex-direction: row;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
&.vertical {
|
|
557
|
+
flex-direction: column;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
&.dragging {
|
|
561
|
+
user-select: none;
|
|
562
|
+
-webkit-user-select: none;
|
|
563
|
+
cursor: col-resize;
|
|
564
|
+
|
|
565
|
+
&.vertical {
|
|
566
|
+
cursor: row-resize;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.pane {
|
|
572
|
+
position: relative;
|
|
573
|
+
overflow: auto;
|
|
574
|
+
min-width: 0;
|
|
575
|
+
min-height: 0;
|
|
576
|
+
|
|
577
|
+
/* Explicit cross-axis size so nested components with height/width: 100% resolve correctly */
|
|
578
|
+
.horizontal > & {
|
|
579
|
+
height: 100%;
|
|
580
|
+
}
|
|
581
|
+
.vertical > & {
|
|
582
|
+
width: 100%;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.snapping & {
|
|
586
|
+
transition: flex-basis 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.animating & {
|
|
590
|
+
transition: flex-basis 200ms ease;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.dragging & {
|
|
594
|
+
transition: none;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.first {
|
|
599
|
+
flex-shrink: 0;
|
|
600
|
+
flex-grow: 0;
|
|
601
|
+
|
|
602
|
+
.collapsed-first & {
|
|
603
|
+
overflow: hidden;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.second {
|
|
608
|
+
flex-shrink: 0;
|
|
609
|
+
flex-grow: 0;
|
|
610
|
+
|
|
611
|
+
.collapsed-second & {
|
|
612
|
+
overflow: hidden;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.divider {
|
|
617
|
+
flex-shrink: 0;
|
|
618
|
+
position: relative;
|
|
619
|
+
display: flex;
|
|
620
|
+
align-items: center;
|
|
621
|
+
justify-content: center;
|
|
622
|
+
background: light-dark(var(--color-border, #e0e0e0), var(--color-border, #3a3a3a));
|
|
623
|
+
touch-action: none;
|
|
624
|
+
outline: none;
|
|
625
|
+
z-index: 1;
|
|
626
|
+
|
|
627
|
+
&:hover,
|
|
628
|
+
&:active {
|
|
629
|
+
background: light-dark(var(--color-action, #1976d2), var(--color-action, #5c9ce6));
|
|
630
|
+
transition: none;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
&:focus-visible {
|
|
634
|
+
outline: 2px solid var(--color-action, #1976d2);
|
|
635
|
+
outline-offset: -2px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* Direct child selectors prevent leaking into nested SplitPane instances */
|
|
639
|
+
.horizontal > & {
|
|
640
|
+
width: 4px;
|
|
641
|
+
cursor: col-resize;
|
|
642
|
+
}
|
|
643
|
+
.vertical > & {
|
|
644
|
+
height: 4px;
|
|
645
|
+
cursor: row-resize;
|
|
646
|
+
}
|
|
647
|
+
.dragging > & {
|
|
648
|
+
background: light-dark(var(--color-action, #1976d2), var(--color-action, #5c9ce6));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.handle {
|
|
653
|
+
position: absolute;
|
|
654
|
+
|
|
655
|
+
.horizontal > .divider > & {
|
|
656
|
+
width: 12px;
|
|
657
|
+
height: 100%;
|
|
658
|
+
left: -4px;
|
|
659
|
+
}
|
|
660
|
+
.vertical > .divider > & {
|
|
661
|
+
height: 12px;
|
|
662
|
+
width: 100%;
|
|
663
|
+
top: -4px;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/* The expand buttons are the component's only <button>s */
|
|
668
|
+
button {
|
|
669
|
+
position: absolute;
|
|
670
|
+
z-index: 2;
|
|
671
|
+
display: flex;
|
|
672
|
+
align-items: center;
|
|
673
|
+
justify-content: center;
|
|
674
|
+
border: 1px solid
|
|
675
|
+
light-dark(var(--color-border, #e0e0e0), var(--color-border, #3a3a3a));
|
|
676
|
+
background: light-dark(var(--color-bg, #ffffff), var(--color-bg, #1e1e1e));
|
|
677
|
+
color: light-dark(var(--color-text-muted, #666), var(--color-text-muted, #aaa));
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
padding: 0;
|
|
680
|
+
width: 20px;
|
|
681
|
+
height: 20px;
|
|
682
|
+
border-radius: var(--radius-md, 4px);
|
|
683
|
+
@supports (corner-shape: squircle) {
|
|
684
|
+
corner-shape: squircle;
|
|
685
|
+
border-radius: calc(var(--radius-md, 4px) * var(--squircle-ratio, 2));
|
|
686
|
+
}
|
|
687
|
+
transition:
|
|
688
|
+
color 150ms ease,
|
|
689
|
+
background 150ms ease;
|
|
690
|
+
|
|
691
|
+
&:hover {
|
|
692
|
+
color: light-dark(var(--color-action, #1976d2), var(--color-action, #5c9ce6));
|
|
693
|
+
background: light-dark(
|
|
694
|
+
var(--color-bg-active, #f5f5f5),
|
|
695
|
+
var(--color-bg-active, #2a2a2a)
|
|
696
|
+
);
|
|
697
|
+
transition: none;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
&:focus-visible {
|
|
701
|
+
outline: 2px solid var(--color-action, #1976d2);
|
|
702
|
+
outline-offset: 2px;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
svg {
|
|
706
|
+
width: 12px;
|
|
707
|
+
height: 12px;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* Expand button positioning for collapsed first pane (horizontal) */
|
|
712
|
+
.collapsed-first.horizontal .first button {
|
|
713
|
+
top: 50%;
|
|
714
|
+
right: 0;
|
|
715
|
+
transform: translateY(-50%);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* Expand button positioning for collapsed first pane (vertical) */
|
|
719
|
+
.collapsed-first.vertical .first button {
|
|
720
|
+
left: 50%;
|
|
721
|
+
bottom: 0;
|
|
722
|
+
transform: translateX(-50%);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Expand button positioning for collapsed second pane (horizontal) */
|
|
726
|
+
.collapsed-second.horizontal .second button {
|
|
727
|
+
top: 50%;
|
|
728
|
+
left: 0;
|
|
729
|
+
transform: translateY(-50%);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/* Expand button positioning for collapsed second pane (vertical) */
|
|
733
|
+
.collapsed-second.vertical .second button {
|
|
734
|
+
left: 50%;
|
|
735
|
+
top: 0;
|
|
736
|
+
transform: translateX(-50%);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@media (prefers-reduced-motion: reduce) {
|
|
740
|
+
.pane {
|
|
741
|
+
transition: none;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
</style>
|