@adea-ai/ui 0.130.1 → 0.130.2
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/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +7 -6
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -1
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +130 -144
- package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -1
- package/dist/components/layout/split-layout/resize-controller-core-retry.d.ts +1 -1
- package/dist/components/layout/split-layout/resize-controller-core-retry.d.ts.map +1 -1
- package/dist/components/layout/split-layout/resize-controller-core-retry.js +91 -55
- package/dist/components/layout/split-layout/resize-controller-core-retry.js.map +1 -1
- package/dist/components/layout/split-layout/resize-controller-core.d.ts +1 -1
- package/dist/components/layout/split-layout/resize-controller-core.d.ts.map +1 -1
- package/dist/components/layout/split-layout/resize-controller-core.js +91 -55
- package/dist/components/layout/split-layout/resize-controller-core.js.map +1 -1
- package/dist/components/layout/split-layout/split-layout.d.ts +1 -1
- package/dist/components/layout/split-layout/split-layout.d.ts.map +1 -1
- package/dist/components/layout/split-layout/split-layout.js +2 -2
- package/dist/components/layout/split-layout/split-layout.js.map +1 -1
- package/dist/r/pixel-resize-handle.json +3 -2
- package/dist/r/registry.json +3 -3
- package/dist/r/split-layout.json +0 -1
- package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +60 -61
- package/dist/r/src/components/layout/split-layout/resize-controller-core-retry.tsx +72 -31
- package/dist/r/src/components/layout/split-layout/resize-controller-core.tsx +72 -31
- package/dist/r/src/components/layout/split-layout/split-layout.tsx +2 -2
- package/package.json +1 -1
- package/registry.json +3 -3
- package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +60 -61
- package/src/components/layout/split-layout/resize-controller-core-retry.tsx +72 -31
- package/src/components/layout/split-layout/resize-controller-core.tsx +72 -31
- package/src/components/layout/split-layout/split-layout.tsx +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GripVertical } from 'lucide-solid'
|
|
2
2
|
import { createSignal, onCleanup } from 'solid-js'
|
|
3
3
|
import { cn } from '../../../lib/utils'
|
|
4
4
|
|
|
@@ -27,21 +27,21 @@ export type PixelResizeHandleProps = {
|
|
|
27
27
|
*
|
|
28
28
|
* A keyboard- and pointer-operable edge ruler for a host-owned pane. Keep the
|
|
29
29
|
* pane's actual width and any persistence in its layout owner; this component
|
|
30
|
-
* reports a bounded pixel value and
|
|
31
|
-
*
|
|
30
|
+
* reports a bounded pixel value and draws the grip at the matching left or
|
|
31
|
+
* right edge.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* that
|
|
36
|
-
*
|
|
33
|
+
* The ruler is a single separator positioned inside a box as wide as the
|
|
34
|
+
* maximum, so it needs no panel-group geometry (and none of the split-layout
|
|
35
|
+
* code that comes with one): arrow keys move it by `step` along its visual
|
|
36
|
+
* edge, Home and End jump to the bounds, and a pointer drag is captured on the
|
|
37
|
+
* handle so cancellation and lost capture end the gesture.
|
|
37
38
|
*/
|
|
38
39
|
export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
39
40
|
const maximum = () => Math.max(1, props.maximum)
|
|
40
41
|
const minimum = () => Math.min(maximum(), Math.max(0, props.minimum))
|
|
41
42
|
const value = () => Math.min(maximum(), Math.max(minimum(), Math.round(props.value)))
|
|
42
|
-
const ratio = () => value() / maximum()
|
|
43
43
|
const isRight = () => props.side === 'right'
|
|
44
|
-
const
|
|
44
|
+
const bounded = (next: number) => Math.min(maximum(), Math.max(minimum(), Math.round(next)))
|
|
45
45
|
const commit = () => props.onCommit?.(value())
|
|
46
46
|
let interacting = false
|
|
47
47
|
const [drag, setDrag] = createSignal<{
|
|
@@ -67,51 +67,48 @@ export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
|
67
67
|
endKeyboard()
|
|
68
68
|
endPointer(false)
|
|
69
69
|
})
|
|
70
|
+
const move = (next: number) => {
|
|
71
|
+
if (next !== value()) props.onChange(next)
|
|
72
|
+
}
|
|
73
|
+
// Arrow keys describe physical movement of the handle: on a right-edge pane
|
|
74
|
+
// moving the handle left widens the pane.
|
|
75
|
+
const KEY_DIRECTION: Record<string, number> = { ArrowLeft: -1, ArrowRight: 1 }
|
|
70
76
|
|
|
71
77
|
return (
|
|
72
|
-
<
|
|
73
|
-
orientation="horizontal"
|
|
74
|
-
dir="ltr"
|
|
75
|
-
sizes={rulerSizes()}
|
|
76
|
-
keyboardDelta={`${props.step ?? 16}px`}
|
|
77
|
-
style={{ width: `${maximum()}px` }}
|
|
78
|
+
<div
|
|
78
79
|
class={cn(
|
|
79
|
-
'pointer-events-none absolute inset-y-0
|
|
80
|
+
'pointer-events-none absolute inset-y-0',
|
|
80
81
|
{
|
|
81
82
|
'left-0': props.side === 'left',
|
|
82
83
|
'right-0': props.side === 'right',
|
|
83
84
|
},
|
|
84
85
|
props.class
|
|
85
86
|
)}
|
|
86
|
-
|
|
87
|
-
// Panel registration and teardown also emit sizes. Only user resizing
|
|
88
|
-
// may update the host preference; collapsing must preserve its width.
|
|
89
|
-
if (!interacting || sizes.length !== 2) return
|
|
90
|
-
const next = Math.round((sizes[isRight() ? 1 : 0] ?? ratio()) * maximum())
|
|
91
|
-
const bounded = Math.min(maximum(), Math.max(minimum(), next))
|
|
92
|
-
if (bounded !== value()) props.onChange(bounded)
|
|
93
|
-
}}
|
|
87
|
+
style={{ width: `${maximum()}px` }}
|
|
94
88
|
>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
aria-
|
|
99
|
-
|
|
100
|
-
<ResizableHandle
|
|
101
|
-
withHandle={props.grip === 'chip' ? true : 'rung'}
|
|
102
|
-
label={props.label ?? `Resize ${props.side} pane`}
|
|
89
|
+
<div
|
|
90
|
+
role="separator"
|
|
91
|
+
tabIndex={0}
|
|
92
|
+
aria-orientation="vertical"
|
|
93
|
+
aria-label={props.label ?? `Resize ${props.side} pane`}
|
|
103
94
|
aria-controls={props.controls}
|
|
104
95
|
aria-valuemin={minimum()}
|
|
105
96
|
aria-valuemax={maximum()}
|
|
106
97
|
aria-valuenow={value()}
|
|
107
98
|
aria-valuetext={`${value()} pixels`}
|
|
108
99
|
data-dragging={drag() ? '' : undefined}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
style={{ left: `${isRight() ? maximum() - value() : value()}px` }}
|
|
101
|
+
class={cn(
|
|
102
|
+
'group/resize bg-border pointer-events-auto absolute inset-y-0 flex w-px items-center justify-center',
|
|
103
|
+
/* Widen the hit target without moving the visual line: 24px on the
|
|
104
|
+
drag axis meets WCAG 2.5.8's minimum target. */
|
|
105
|
+
'after:absolute after:inset-y-0 after:-inset-x-3 after:w-6',
|
|
106
|
+
'transition-colors ease-out focus-visible:bg-primary focus-visible:outline-none',
|
|
107
|
+
props.grip === 'chip' ? 'hover:bg-primary' : 'hover:bg-primary/45'
|
|
108
|
+
)}
|
|
109
|
+
onPointerDown={(event) => {
|
|
112
110
|
if (event.button !== 0 || !event.isPrimary) return
|
|
113
111
|
const handle = event.currentTarget
|
|
114
|
-
if (!(handle instanceof HTMLElement)) return
|
|
115
112
|
endPointer(false)
|
|
116
113
|
interacting = false
|
|
117
114
|
handle.focus()
|
|
@@ -123,7 +120,7 @@ export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
|
123
120
|
handle,
|
|
124
121
|
})
|
|
125
122
|
}}
|
|
126
|
-
onPointerMove={(event
|
|
123
|
+
onPointerMove={(event) => {
|
|
127
124
|
const current = drag()
|
|
128
125
|
if (!current || event.pointerId !== current.pointerId) return
|
|
129
126
|
if (!current.handle.hasPointerCapture(current.pointerId)) {
|
|
@@ -131,19 +128,15 @@ export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
|
131
128
|
return
|
|
132
129
|
}
|
|
133
130
|
const delta = (event.clientX - current.startX) * (isRight() ? -1 : 1)
|
|
134
|
-
|
|
135
|
-
maximum(),
|
|
136
|
-
Math.max(minimum(), Math.round(current.startValue + delta))
|
|
137
|
-
)
|
|
138
|
-
if (next !== value()) props.onChange(next)
|
|
131
|
+
move(bounded(current.startValue + delta))
|
|
139
132
|
}}
|
|
140
|
-
onPointerUp={(event
|
|
133
|
+
onPointerUp={(event) => {
|
|
141
134
|
if (event.pointerId === drag()?.pointerId) endPointer(true)
|
|
142
135
|
}}
|
|
143
|
-
onPointerCancel={(event
|
|
136
|
+
onPointerCancel={(event) => {
|
|
144
137
|
if (event.pointerId === drag()?.pointerId) endPointer(false)
|
|
145
138
|
}}
|
|
146
|
-
onLostPointerCapture={(event
|
|
139
|
+
onLostPointerCapture={(event) => {
|
|
147
140
|
if (event.pointerId === drag()?.pointerId) endPointer(false)
|
|
148
141
|
}}
|
|
149
142
|
onBlur={() => {
|
|
@@ -157,26 +150,32 @@ export function PixelResizeHandle(props: PixelResizeHandleProps) {
|
|
|
157
150
|
event.preventDefault()
|
|
158
151
|
return
|
|
159
152
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// the controlled pane width, including a pane after the separator.
|
|
163
|
-
if (event.key === 'Home' || event.key === 'End') {
|
|
153
|
+
const direction = KEY_DIRECTION[event.key]
|
|
154
|
+
if (direction !== undefined) {
|
|
164
155
|
event.preventDefault()
|
|
165
|
-
|
|
156
|
+
interacting = true
|
|
157
|
+
move(bounded(value() + direction * (isRight() ? -1 : 1) * (props.step ?? 16)))
|
|
158
|
+
} else if (event.key === 'Home' || event.key === 'End') {
|
|
159
|
+
event.preventDefault()
|
|
160
|
+
interacting = true
|
|
161
|
+
move(event.key === 'Home' ? minimum() : maximum())
|
|
166
162
|
}
|
|
167
163
|
}}
|
|
168
164
|
onKeyUp={(event) => {
|
|
169
|
-
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
|
|
170
|
-
endKeyboard()
|
|
171
|
-
}
|
|
165
|
+
if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) endKeyboard()
|
|
172
166
|
}}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
167
|
+
>
|
|
168
|
+
{props.grip === 'chip' ? (
|
|
169
|
+
<div class="bg-border z-(--z-docked) flex h-4 w-2.5 items-center justify-center rounded-sm border border-border">
|
|
170
|
+
<GripVertical class="size-2.5 text-muted-foreground" />
|
|
171
|
+
</div>
|
|
172
|
+
) : (
|
|
173
|
+
<div
|
|
174
|
+
data-slot="resizable-grip"
|
|
175
|
+
class="z-(--z-docked) h-11 w-1.5 flex-none rounded-full bg-border/55"
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
181
180
|
)
|
|
182
181
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type Accessor, type JSX } from 'solid-js'
|
|
1
|
+
import { createSignal, type Accessor, type JSX } from 'solid-js'
|
|
3
2
|
import {
|
|
4
3
|
listLeaves,
|
|
5
4
|
MIN_SPLIT_RATIO,
|
|
@@ -17,50 +16,92 @@ export type SplitLayoutResizeControllerProps<L extends SplitLayoutLeaf> = {
|
|
|
17
16
|
onResize: (splitId: string, ratio: number) => void
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
const KEYBOARD_DELTA = 0.05
|
|
20
|
+
|
|
21
|
+
/** The divider stays behind the first real split while the pane owners remain mounted. */
|
|
21
22
|
export default function SplitLayoutResizeController<L extends SplitLayoutLeaf>(
|
|
22
23
|
props: SplitLayoutResizeControllerProps<L>
|
|
23
24
|
) {
|
|
24
|
-
const
|
|
25
|
+
const [container, setContainer] = createSignal<HTMLDivElement>()
|
|
26
|
+
let activePointer: number | undefined
|
|
27
|
+
const horizontal = () => props.branch().direction === 'row'
|
|
28
|
+
|
|
29
|
+
const resizeTo = (ratio: number) => {
|
|
30
|
+
// A hidden or detached host reports no usable geometry; that is not
|
|
31
|
+
// user resize intent.
|
|
32
|
+
const root = props.root()
|
|
33
|
+
if (!root?.isConnected || root.getClientRects().length === 0) return
|
|
34
|
+
const next = Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, ratio))
|
|
35
|
+
if (Number.isFinite(next) && Math.abs(next - props.branch().ratio) > 0.000001)
|
|
36
|
+
props.onResize(props.branch().id, next)
|
|
37
|
+
}
|
|
38
|
+
const stopDrag = (handle: HTMLElement, event: PointerEvent) => {
|
|
39
|
+
if (activePointer !== event.pointerId) return
|
|
40
|
+
activePointer = undefined
|
|
41
|
+
handle.removeAttribute('data-dragging')
|
|
42
|
+
if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId)
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
sizes={[props.branch().ratio, 1 - props.branch().ratio]}
|
|
29
|
-
keyboardDelta={0.05}
|
|
30
|
-
onSizesChange={(sizes) => {
|
|
31
|
-
// Corvu also reports panel registration/unregistration while
|
|
32
|
-
// mounting or disposing a controller. Those incomplete or
|
|
33
|
-
// hidden-host sizes are not user resize intent.
|
|
34
|
-
const root = props.root()
|
|
35
|
-
if (!root?.isConnected || root.getClientRects().length === 0) return
|
|
36
|
-
if (
|
|
37
|
-
sizes.length !== 2 ||
|
|
38
|
-
sizes.some((size) => !Number.isFinite(size)) ||
|
|
39
|
-
Math.abs(sizes[0]! + sizes[1]! - 1) > 0.000001
|
|
40
|
-
)
|
|
41
|
-
return
|
|
42
|
-
if (sizes[0] !== undefined && Math.abs(sizes[0] - props.branch().ratio) > 0.000001)
|
|
43
|
-
props.onResize(id(), sizes[0])
|
|
44
|
-
}}
|
|
46
|
+
<div
|
|
47
|
+
ref={setContainer}
|
|
45
48
|
style={props.rect()}
|
|
46
|
-
class="pointer-events-none absolute
|
|
49
|
+
class="pointer-events-none absolute min-h-0 min-w-0"
|
|
47
50
|
>
|
|
48
|
-
<
|
|
49
|
-
|
|
51
|
+
<div
|
|
52
|
+
role="separator"
|
|
53
|
+
tabIndex={0}
|
|
54
|
+
data-split-layout-handle=""
|
|
55
|
+
data-orientation={horizontal() ? 'horizontal' : 'vertical'}
|
|
50
56
|
aria-label={
|
|
51
57
|
props.labelForSeparator?.(props.branch()) ??
|
|
52
|
-
(
|
|
58
|
+
(horizontal() ? 'Resize pane columns' : 'Resize pane rows')
|
|
53
59
|
}
|
|
54
|
-
aria-orientation={
|
|
60
|
+
aria-orientation={horizontal() ? 'vertical' : 'horizontal'}
|
|
55
61
|
aria-controls={listLeaves(props.branch())
|
|
56
62
|
.map((leaf) => props.domId(leaf.id))
|
|
57
63
|
.join(' ')}
|
|
58
64
|
aria-valuemin={10}
|
|
59
65
|
aria-valuemax={90}
|
|
60
66
|
aria-valuenow={Math.round(props.branch().ratio * 100)}
|
|
61
|
-
|
|
67
|
+
style={
|
|
68
|
+
horizontal()
|
|
69
|
+
? { left: `${props.branch().ratio * 100}%`, top: 0, bottom: 0, width: '1px' }
|
|
70
|
+
: { top: `${props.branch().ratio * 100}%`, left: 0, right: 0, height: '1px' }
|
|
71
|
+
}
|
|
72
|
+
onKeyDown={(event) => {
|
|
73
|
+
const forward = horizontal() ? 'ArrowRight' : 'ArrowDown'
|
|
74
|
+
const backward = horizontal() ? 'ArrowLeft' : 'ArrowUp'
|
|
75
|
+
const current = props.branch().ratio
|
|
76
|
+
let next: number
|
|
77
|
+
if (event.key === forward) next = current + KEYBOARD_DELTA
|
|
78
|
+
else if (event.key === backward) next = current - KEYBOARD_DELTA
|
|
79
|
+
else if (event.key === 'Home') next = MIN_SPLIT_RATIO
|
|
80
|
+
else if (event.key === 'End') next = MAX_SPLIT_RATIO
|
|
81
|
+
else return
|
|
82
|
+
event.preventDefault()
|
|
83
|
+
resizeTo(next)
|
|
84
|
+
}}
|
|
85
|
+
onPointerDown={(event) => {
|
|
86
|
+
if (event.button !== 0 || activePointer !== undefined) return
|
|
87
|
+
activePointer = event.pointerId
|
|
88
|
+
event.currentTarget.setPointerCapture(event.pointerId)
|
|
89
|
+
event.currentTarget.setAttribute('data-dragging', '')
|
|
90
|
+
event.preventDefault()
|
|
91
|
+
}}
|
|
92
|
+
onPointerMove={(event) => {
|
|
93
|
+
const element = container()
|
|
94
|
+
if (activePointer !== event.pointerId || !element) return
|
|
95
|
+
const box = element.getBoundingClientRect()
|
|
96
|
+
const size = horizontal() ? box.width : box.height
|
|
97
|
+
if (size <= 0) return
|
|
98
|
+
resizeTo((horizontal() ? event.clientX - box.left : event.clientY - box.top) / size)
|
|
99
|
+
}}
|
|
100
|
+
onPointerUp={(event) => stopDrag(event.currentTarget, event)}
|
|
101
|
+
onPointerCancel={(event) => stopDrag(event.currentTarget, event)}
|
|
102
|
+
onLostPointerCapture={(event) => stopDrag(event.currentTarget, event)}
|
|
103
|
+
class="pointer-events-auto absolute shrink-0 bg-border after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 focus-visible:bg-primary focus-visible:outline-none hover:bg-primary data-[orientation=vertical]:after:inset-x-0 data-[orientation=vertical]:after:-inset-y-1 data-[orientation=vertical]:after:h-3 data-[orientation=vertical]:after:w-full"
|
|
62
104
|
/>
|
|
63
|
-
|
|
64
|
-
</Resizable>
|
|
105
|
+
</div>
|
|
65
106
|
)
|
|
66
107
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type Accessor, type JSX } from 'solid-js'
|
|
1
|
+
import { createSignal, type Accessor, type JSX } from 'solid-js'
|
|
3
2
|
import {
|
|
4
3
|
listLeaves,
|
|
5
4
|
MIN_SPLIT_RATIO,
|
|
@@ -17,50 +16,92 @@ export type SplitLayoutResizeControllerProps<L extends SplitLayoutLeaf> = {
|
|
|
17
16
|
onResize: (splitId: string, ratio: number) => void
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
const KEYBOARD_DELTA = 0.05
|
|
20
|
+
|
|
21
|
+
/** The divider stays behind the first real split while the pane owners remain mounted. */
|
|
21
22
|
export default function SplitLayoutResizeController<L extends SplitLayoutLeaf>(
|
|
22
23
|
props: SplitLayoutResizeControllerProps<L>
|
|
23
24
|
) {
|
|
24
|
-
const
|
|
25
|
+
const [container, setContainer] = createSignal<HTMLDivElement>()
|
|
26
|
+
let activePointer: number | undefined
|
|
27
|
+
const horizontal = () => props.branch().direction === 'row'
|
|
28
|
+
|
|
29
|
+
const resizeTo = (ratio: number) => {
|
|
30
|
+
// A hidden or detached host reports no usable geometry; that is not
|
|
31
|
+
// user resize intent.
|
|
32
|
+
const root = props.root()
|
|
33
|
+
if (!root?.isConnected || root.getClientRects().length === 0) return
|
|
34
|
+
const next = Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, ratio))
|
|
35
|
+
if (Number.isFinite(next) && Math.abs(next - props.branch().ratio) > 0.000001)
|
|
36
|
+
props.onResize(props.branch().id, next)
|
|
37
|
+
}
|
|
38
|
+
const stopDrag = (handle: HTMLElement, event: PointerEvent) => {
|
|
39
|
+
if (activePointer !== event.pointerId) return
|
|
40
|
+
activePointer = undefined
|
|
41
|
+
handle.removeAttribute('data-dragging')
|
|
42
|
+
if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId)
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
sizes={[props.branch().ratio, 1 - props.branch().ratio]}
|
|
29
|
-
keyboardDelta={0.05}
|
|
30
|
-
onSizesChange={(sizes) => {
|
|
31
|
-
// Corvu also reports panel registration/unregistration while
|
|
32
|
-
// mounting or disposing a controller. Those incomplete or
|
|
33
|
-
// hidden-host sizes are not user resize intent.
|
|
34
|
-
const root = props.root()
|
|
35
|
-
if (!root?.isConnected || root.getClientRects().length === 0) return
|
|
36
|
-
if (
|
|
37
|
-
sizes.length !== 2 ||
|
|
38
|
-
sizes.some((size) => !Number.isFinite(size)) ||
|
|
39
|
-
Math.abs(sizes[0]! + sizes[1]! - 1) > 0.000001
|
|
40
|
-
)
|
|
41
|
-
return
|
|
42
|
-
if (sizes[0] !== undefined && Math.abs(sizes[0] - props.branch().ratio) > 0.000001)
|
|
43
|
-
props.onResize(id(), sizes[0])
|
|
44
|
-
}}
|
|
46
|
+
<div
|
|
47
|
+
ref={setContainer}
|
|
45
48
|
style={props.rect()}
|
|
46
|
-
class="pointer-events-none absolute
|
|
49
|
+
class="pointer-events-none absolute min-h-0 min-w-0"
|
|
47
50
|
>
|
|
48
|
-
<
|
|
49
|
-
|
|
51
|
+
<div
|
|
52
|
+
role="separator"
|
|
53
|
+
tabIndex={0}
|
|
54
|
+
data-split-layout-handle=""
|
|
55
|
+
data-orientation={horizontal() ? 'horizontal' : 'vertical'}
|
|
50
56
|
aria-label={
|
|
51
57
|
props.labelForSeparator?.(props.branch()) ??
|
|
52
|
-
(
|
|
58
|
+
(horizontal() ? 'Resize pane columns' : 'Resize pane rows')
|
|
53
59
|
}
|
|
54
|
-
aria-orientation={
|
|
60
|
+
aria-orientation={horizontal() ? 'vertical' : 'horizontal'}
|
|
55
61
|
aria-controls={listLeaves(props.branch())
|
|
56
62
|
.map((leaf) => props.domId(leaf.id))
|
|
57
63
|
.join(' ')}
|
|
58
64
|
aria-valuemin={10}
|
|
59
65
|
aria-valuemax={90}
|
|
60
66
|
aria-valuenow={Math.round(props.branch().ratio * 100)}
|
|
61
|
-
|
|
67
|
+
style={
|
|
68
|
+
horizontal()
|
|
69
|
+
? { left: `${props.branch().ratio * 100}%`, top: 0, bottom: 0, width: '1px' }
|
|
70
|
+
: { top: `${props.branch().ratio * 100}%`, left: 0, right: 0, height: '1px' }
|
|
71
|
+
}
|
|
72
|
+
onKeyDown={(event) => {
|
|
73
|
+
const forward = horizontal() ? 'ArrowRight' : 'ArrowDown'
|
|
74
|
+
const backward = horizontal() ? 'ArrowLeft' : 'ArrowUp'
|
|
75
|
+
const current = props.branch().ratio
|
|
76
|
+
let next: number
|
|
77
|
+
if (event.key === forward) next = current + KEYBOARD_DELTA
|
|
78
|
+
else if (event.key === backward) next = current - KEYBOARD_DELTA
|
|
79
|
+
else if (event.key === 'Home') next = MIN_SPLIT_RATIO
|
|
80
|
+
else if (event.key === 'End') next = MAX_SPLIT_RATIO
|
|
81
|
+
else return
|
|
82
|
+
event.preventDefault()
|
|
83
|
+
resizeTo(next)
|
|
84
|
+
}}
|
|
85
|
+
onPointerDown={(event) => {
|
|
86
|
+
if (event.button !== 0 || activePointer !== undefined) return
|
|
87
|
+
activePointer = event.pointerId
|
|
88
|
+
event.currentTarget.setPointerCapture(event.pointerId)
|
|
89
|
+
event.currentTarget.setAttribute('data-dragging', '')
|
|
90
|
+
event.preventDefault()
|
|
91
|
+
}}
|
|
92
|
+
onPointerMove={(event) => {
|
|
93
|
+
const element = container()
|
|
94
|
+
if (activePointer !== event.pointerId || !element) return
|
|
95
|
+
const box = element.getBoundingClientRect()
|
|
96
|
+
const size = horizontal() ? box.width : box.height
|
|
97
|
+
if (size <= 0) return
|
|
98
|
+
resizeTo((horizontal() ? event.clientX - box.left : event.clientY - box.top) / size)
|
|
99
|
+
}}
|
|
100
|
+
onPointerUp={(event) => stopDrag(event.currentTarget, event)}
|
|
101
|
+
onPointerCancel={(event) => stopDrag(event.currentTarget, event)}
|
|
102
|
+
onLostPointerCapture={(event) => stopDrag(event.currentTarget, event)}
|
|
103
|
+
class="pointer-events-auto absolute shrink-0 bg-border after:absolute after:inset-y-0 after:-inset-x-1 after:w-3 focus-visible:bg-primary focus-visible:outline-none hover:bg-primary data-[orientation=vertical]:after:inset-x-0 data-[orientation=vertical]:after:-inset-y-1 data-[orientation=vertical]:after:h-3 data-[orientation=vertical]:after:w-full"
|
|
62
104
|
/>
|
|
63
|
-
|
|
64
|
-
</Resizable>
|
|
105
|
+
</div>
|
|
65
106
|
)
|
|
66
107
|
}
|
|
@@ -185,7 +185,7 @@ function cloneDragPreviewNode(source: Node): Node | undefined {
|
|
|
185
185
|
}
|
|
186
186
|
return clone
|
|
187
187
|
}
|
|
188
|
-
/** Muxy frame geometry keeps leaf owners stable;
|
|
188
|
+
/** Muxy frame geometry keeps leaf owners stable; the lazy resize controller owns constrained separator interactions. */
|
|
189
189
|
export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L>) {
|
|
190
190
|
let root: HTMLDivElement | undefined
|
|
191
191
|
let pendingFrame: number | undefined
|
|
@@ -308,7 +308,7 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
308
308
|
const currentFocus = root?.ownerDocument.activeElement
|
|
309
309
|
if (currentFocus && currentFocus !== root?.ownerDocument.body && currentFocus !== root)
|
|
310
310
|
return
|
|
311
|
-
root?.querySelector<HTMLElement>('[data-
|
|
311
|
+
root?.querySelector<HTMLElement>('[data-split-layout-handle]')?.focus({
|
|
312
312
|
preventScroll: true,
|
|
313
313
|
})
|
|
314
314
|
})
|
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1722,9 +1722,10 @@
|
|
|
1722
1722
|
"type": "registry:ui",
|
|
1723
1723
|
"title": "Pixel Resize Handle",
|
|
1724
1724
|
"description": "",
|
|
1725
|
-
"dependencies": [
|
|
1725
|
+
"dependencies": [
|
|
1726
|
+
"lucide-solid@^1.51.0"
|
|
1727
|
+
],
|
|
1726
1728
|
"registryDependencies": [
|
|
1727
|
-
"@adea-ai/ui/resizable",
|
|
1728
1729
|
"lib"
|
|
1729
1730
|
],
|
|
1730
1731
|
"files": [
|
|
@@ -1806,7 +1807,6 @@
|
|
|
1806
1807
|
"title": "Split Layout",
|
|
1807
1808
|
"description": "Issue-backed binary layout with stable opaque leaf owners; scoped persistence belongs to the host.",
|
|
1808
1809
|
"dependencies": [
|
|
1809
|
-
"@corvu/resizable@^0.2.5",
|
|
1810
1810
|
"lucide-solid@^1.51.0"
|
|
1811
1811
|
],
|
|
1812
1812
|
"registryDependencies": [
|