@adea-ai/ui 0.130.1 → 0.130.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts +7 -6
  2. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.d.ts.map +1 -1
  3. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js +130 -144
  4. package/dist/components/layout/pixel-resize-handle/pixel-resize-handle.js.map +1 -1
  5. package/dist/components/layout/split-layout/resize-controller-core-retry.d.ts +1 -1
  6. package/dist/components/layout/split-layout/resize-controller-core-retry.d.ts.map +1 -1
  7. package/dist/components/layout/split-layout/resize-controller-core-retry.js +91 -55
  8. package/dist/components/layout/split-layout/resize-controller-core-retry.js.map +1 -1
  9. package/dist/components/layout/split-layout/resize-controller-core.d.ts +1 -1
  10. package/dist/components/layout/split-layout/resize-controller-core.d.ts.map +1 -1
  11. package/dist/components/layout/split-layout/resize-controller-core.js +91 -55
  12. package/dist/components/layout/split-layout/resize-controller-core.js.map +1 -1
  13. package/dist/components/layout/split-layout/split-layout.d.ts +1 -1
  14. package/dist/components/layout/split-layout/split-layout.d.ts.map +1 -1
  15. package/dist/components/layout/split-layout/split-layout.js +2 -2
  16. package/dist/components/layout/split-layout/split-layout.js.map +1 -1
  17. package/dist/r/pixel-resize-handle.json +3 -2
  18. package/dist/r/registry.json +3 -3
  19. package/dist/r/split-layout.json +0 -1
  20. package/dist/r/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +60 -61
  21. package/dist/r/src/components/layout/split-layout/resize-controller-core-retry.tsx +72 -31
  22. package/dist/r/src/components/layout/split-layout/resize-controller-core.tsx +72 -31
  23. package/dist/r/src/components/layout/split-layout/split-layout.tsx +2 -2
  24. package/package.json +1 -1
  25. package/registry.json +3 -3
  26. package/src/components/layout/pixel-resize-handle/pixel-resize-handle.tsx +60 -61
  27. package/src/components/layout/split-layout/resize-controller-core-retry.tsx +72 -31
  28. package/src/components/layout/split-layout/resize-controller-core.tsx +72 -31
  29. package/src/components/layout/split-layout/split-layout.tsx +2 -2
@@ -1,4 +1,4 @@
1
- import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../ui/resizable'
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 places the shared resizable grip at the
31
- * matching left or right edge.
30
+ * reports a bounded pixel value and draws the grip at the matching left or
31
+ * right edge.
32
32
  *
33
- * Corvu 0.2.5 supplies geometry, keyboard resizing, ARIA and the shared grip,
34
- * but its global drag handler does not end on pointer cancellation. Prevent
35
- * that handler through its public start hook and use local pointer capture
36
- * for bounded pixel gestures, including cancellation and lost capture.
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 rulerSizes = () => (isRight() ? [1 - ratio(), ratio()] : [ratio(), 1 - ratio()])
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
- <ResizablePanelGroup
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 flex',
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
- onSizesChange={(sizes) => {
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
- <ResizablePanel
96
- minSize={isRight() ? 0 : minimum() / maximum()}
97
- maxSize={1}
98
- aria-hidden="true"
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
- onHandleDragStart={(event) => {
110
- // Corvu checks defaultPrevented before starting its global handler.
111
- event.preventDefault()
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 cursor-pointer 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: PointerEvent) => {
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
- const next = Math.min(
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: PointerEvent) => {
133
+ onPointerUp={(event) => {
141
134
  if (event.pointerId === drag()?.pointerId) endPointer(true)
142
135
  }}
143
- onPointerCancel={(event: PointerEvent) => {
136
+ onPointerCancel={(event) => {
144
137
  if (event.pointerId === drag()?.pointerId) endPointer(false)
145
138
  }}
146
- onLostPointerCapture={(event: PointerEvent) => {
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
- if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) interacting = true
161
- // Corvu operates on physical panel order. Home/End here describe
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
- props.onChange(event.key === 'Home' ? minimum() : maximum())
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
- class="pointer-events-auto"
174
- />
175
- <ResizablePanel
176
- minSize={isRight() ? minimum() / maximum() : 0}
177
- maxSize={1}
178
- aria-hidden="true"
179
- />
180
- </ResizablePanelGroup>
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 Resizable from '@corvu/resizable'
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
- /** Corvu stays behind the first real split while the pane owners remain mounted. */
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 id = () => props.branch().id
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
- <Resizable
27
- orientation={props.branch().direction === 'row' ? 'horizontal' : 'vertical'}
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 flex min-h-0 min-w-0 data-[orientation=vertical]:flex-col"
49
+ class="pointer-events-none absolute min-h-0 min-w-0"
47
50
  >
48
- <Resizable.Panel minSize={MIN_SPLIT_RATIO} maxSize={MAX_SPLIT_RATIO} aria-hidden="true" />
49
- <Resizable.Handle
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
- (props.branch().direction === 'row' ? 'Resize pane columns' : 'Resize pane rows')
58
+ (horizontal() ? 'Resize pane columns' : 'Resize pane rows')
53
59
  }
54
- aria-orientation={props.branch().direction === 'row' ? 'vertical' : 'horizontal'}
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
- class="pointer-events-auto relative w-px 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]:h-px data-[orientation=vertical]:w-full 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"
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 cursor-pointer 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
- <Resizable.Panel minSize={MIN_SPLIT_RATIO} maxSize={MAX_SPLIT_RATIO} aria-hidden="true" />
64
- </Resizable>
105
+ </div>
65
106
  )
66
107
  }
@@ -1,5 +1,4 @@
1
- import Resizable from '@corvu/resizable'
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
- /** Corvu stays behind the first real split while the pane owners remain mounted. */
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 id = () => props.branch().id
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
- <Resizable
27
- orientation={props.branch().direction === 'row' ? 'horizontal' : 'vertical'}
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 flex min-h-0 min-w-0 data-[orientation=vertical]:flex-col"
49
+ class="pointer-events-none absolute min-h-0 min-w-0"
47
50
  >
48
- <Resizable.Panel minSize={MIN_SPLIT_RATIO} maxSize={MAX_SPLIT_RATIO} aria-hidden="true" />
49
- <Resizable.Handle
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
- (props.branch().direction === 'row' ? 'Resize pane columns' : 'Resize pane rows')
58
+ (horizontal() ? 'Resize pane columns' : 'Resize pane rows')
53
59
  }
54
- aria-orientation={props.branch().direction === 'row' ? 'vertical' : 'horizontal'}
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
- class="pointer-events-auto relative w-px 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]:h-px data-[orientation=vertical]:w-full 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"
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 cursor-pointer 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
- <Resizable.Panel minSize={MIN_SPLIT_RATIO} maxSize={MAX_SPLIT_RATIO} aria-hidden="true" />
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; Corvu owns constrained separator interactions. */
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-corvu-resizable-handle]')?.focus({
311
+ root?.querySelector<HTMLElement>('[data-split-layout-handle]')?.focus({
312
312
  preventScroll: true,
313
313
  })
314
314
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.130.1",
3
+ "version": "0.130.3",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
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": [