@adea-ai/ui 0.103.1 → 0.104.1
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/README.md +10 -0
- package/dist/components/composites/about-dialog/about-dialog.js +1 -1
- package/dist/components/composites/about-dialog/about-dialog.js.map +1 -1
- package/dist/components/composites/catalog-browser/catalog-detail.d.ts +4 -0
- package/dist/components/composites/catalog-browser/catalog-detail.d.ts.map +1 -1
- package/dist/components/composites/catalog-browser/catalog-detail.js +36 -7
- package/dist/components/composites/catalog-browser/catalog-detail.js.map +1 -1
- package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
- package/dist/components/composites/update-dialog/update-dialog.js +34 -30
- package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
- package/dist/components/layout/split-layout/split-layout.d.ts.map +1 -1
- package/dist/components/layout/split-layout/split-layout.js +131 -3
- package/dist/components/layout/split-layout/split-layout.js.map +1 -1
- package/dist/r/catalog-browser.json +1 -0
- package/dist/r/registry.json +1 -0
- package/dist/r/src/components/composites/about-dialog/about-dialog.tsx +1 -1
- package/dist/r/src/components/composites/catalog-browser/catalog-detail.tsx +36 -6
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +7 -3
- package/dist/r/src/components/layout/split-layout/split-layout.tsx +149 -9
- package/package.json +1 -1
- package/registry.json +1 -0
- package/src/components/composites/about-dialog/about-dialog.tsx +1 -1
- package/src/components/composites/catalog-browser/catalog-detail.tsx +36 -6
- package/src/components/composites/update-dialog/update-dialog.tsx +7 -3
- package/src/components/layout/split-layout/split-layout.tsx +149 -9
|
@@ -17,6 +17,8 @@ import { computeLayoutFrames, type LayoutRect } from './geometry'
|
|
|
17
17
|
import { paneDropIntent, type PaneDropIntent } from './drop'
|
|
18
18
|
export type { PaneDropIntent } from './drop'
|
|
19
19
|
const PANE_DRAG_TYPE = 'application/x-adea-pane-move'
|
|
20
|
+
const DRAG_PREVIEW_MAX_WIDTH = 384
|
|
21
|
+
const DRAG_PREVIEW_MAX_HEIGHT = 256
|
|
20
22
|
import {
|
|
21
23
|
listLeaves,
|
|
22
24
|
MIN_SPLIT_RATIO,
|
|
@@ -57,6 +59,114 @@ function rectStyle(rect: LayoutRect): JSX.CSSProperties {
|
|
|
57
59
|
height: `${rect.height * 100}%`,
|
|
58
60
|
}
|
|
59
61
|
}
|
|
62
|
+
function dragPreviewPlaceholder(source: HTMLElement, message: string) {
|
|
63
|
+
const placeholder = document.createElement('div')
|
|
64
|
+
placeholder.className = cn(
|
|
65
|
+
source.getAttribute('class'),
|
|
66
|
+
'flex min-h-8 min-w-0 items-center justify-center bg-muted px-2 text-center text-xs text-muted-foreground'
|
|
67
|
+
)
|
|
68
|
+
placeholder.setAttribute('data-split-drag-placeholder', source.localName)
|
|
69
|
+
placeholder.textContent = message
|
|
70
|
+
return placeholder
|
|
71
|
+
}
|
|
72
|
+
function boundedSnapshotSize(width: number, height: number) {
|
|
73
|
+
const sourceWidth = Math.max(1, width)
|
|
74
|
+
const sourceHeight = Math.max(1, height)
|
|
75
|
+
const scale = Math.min(
|
|
76
|
+
1,
|
|
77
|
+
DRAG_PREVIEW_MAX_WIDTH / sourceWidth,
|
|
78
|
+
DRAG_PREVIEW_MAX_HEIGHT / sourceHeight
|
|
79
|
+
)
|
|
80
|
+
return {
|
|
81
|
+
width: Math.max(1, Math.round(sourceWidth * scale)),
|
|
82
|
+
height: Math.max(1, Math.round(sourceHeight * scale)),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function cloneDragPreviewNode(source: Node): Node | undefined {
|
|
86
|
+
if (source.nodeType === Node.TEXT_NODE) return source.cloneNode(false)
|
|
87
|
+
if (!(source instanceof HTMLElement || source instanceof SVGElement))
|
|
88
|
+
return source.cloneNode(false)
|
|
89
|
+
|
|
90
|
+
if (['script', 'style', 'link', 'source', 'track', 'meta'].includes(source.localName))
|
|
91
|
+
return undefined
|
|
92
|
+
if (['iframe', 'video', 'audio', 'object', 'embed'].includes(source.localName))
|
|
93
|
+
return dragPreviewPlaceholder(
|
|
94
|
+
source as HTMLElement,
|
|
95
|
+
'Embedded content unavailable while moving'
|
|
96
|
+
)
|
|
97
|
+
if (source instanceof HTMLImageElement) {
|
|
98
|
+
const snapshot = document.createElement('canvas')
|
|
99
|
+
const size = boundedSnapshotSize(
|
|
100
|
+
source.naturalWidth || source.width,
|
|
101
|
+
source.naturalHeight || source.height
|
|
102
|
+
)
|
|
103
|
+
snapshot.width = size.width
|
|
104
|
+
snapshot.height = size.height
|
|
105
|
+
snapshot.className = source.getAttribute('class') ?? ''
|
|
106
|
+
snapshot.setAttribute('data-split-drag-image', '')
|
|
107
|
+
try {
|
|
108
|
+
if (!source.complete || source.naturalWidth === 0)
|
|
109
|
+
throw new Error('image has no decoded pixels')
|
|
110
|
+
const context = snapshot.getContext('2d')
|
|
111
|
+
if (!context) throw new Error('canvas snapshot context is unavailable')
|
|
112
|
+
context.drawImage(source, 0, 0, snapshot.width, snapshot.height)
|
|
113
|
+
return snapshot
|
|
114
|
+
} catch {
|
|
115
|
+
return dragPreviewPlaceholder(source, 'Image preview unavailable')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Copying a defined custom element can run its constructor; inserting it can
|
|
120
|
+
// also reconnect a stream or terminal session. Flatten its display tree.
|
|
121
|
+
if (source instanceof HTMLElement && source.localName.includes('-')) {
|
|
122
|
+
const children = document.createDocumentFragment()
|
|
123
|
+
for (const child of source.childNodes) {
|
|
124
|
+
const clone = cloneDragPreviewNode(child)
|
|
125
|
+
if (clone) children.append(clone)
|
|
126
|
+
}
|
|
127
|
+
return children.childNodes.length > 0
|
|
128
|
+
? children
|
|
129
|
+
: dragPreviewPlaceholder(source, 'Live component preview unavailable while moving')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const clone = source.cloneNode(false) as HTMLElement | SVGElement
|
|
133
|
+
for (const attribute of [
|
|
134
|
+
'id',
|
|
135
|
+
'style',
|
|
136
|
+
'draggable',
|
|
137
|
+
'data-pane-drag-handle',
|
|
138
|
+
'data-pane-id',
|
|
139
|
+
'src',
|
|
140
|
+
'srcset',
|
|
141
|
+
'href',
|
|
142
|
+
'xlink:href',
|
|
143
|
+
'data',
|
|
144
|
+
'poster',
|
|
145
|
+
])
|
|
146
|
+
clone.removeAttribute(attribute)
|
|
147
|
+
for (const attribute of Array.from(clone.attributes))
|
|
148
|
+
if (/^on/i.test(attribute.name)) clone.removeAttribute(attribute.name)
|
|
149
|
+
|
|
150
|
+
if (source instanceof HTMLCanvasElement && clone instanceof HTMLCanvasElement) {
|
|
151
|
+
const size = boundedSnapshotSize(source.width, source.height)
|
|
152
|
+
clone.width = size.width
|
|
153
|
+
clone.height = size.height
|
|
154
|
+
try {
|
|
155
|
+
const context = clone.getContext('2d')
|
|
156
|
+
if (!context) throw new Error('canvas snapshot context is unavailable')
|
|
157
|
+
context.drawImage(source, 0, 0, clone.width, clone.height)
|
|
158
|
+
} catch {
|
|
159
|
+
return dragPreviewPlaceholder(source, 'Canvas preview unavailable while moving')
|
|
160
|
+
}
|
|
161
|
+
return clone
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const child of source.childNodes) {
|
|
165
|
+
const childClone = cloneDragPreviewNode(child)
|
|
166
|
+
if (childClone) clone.append(childClone)
|
|
167
|
+
}
|
|
168
|
+
return clone
|
|
169
|
+
}
|
|
60
170
|
/** Muxy frame geometry keeps leaf owners stable; Corvu owns constrained separator interactions. */
|
|
61
171
|
export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L>) {
|
|
62
172
|
let root: HTMLDivElement | undefined
|
|
@@ -66,11 +176,35 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
66
176
|
const prefix = createUniqueId()
|
|
67
177
|
let nextDomId = 0
|
|
68
178
|
let nextDragId = 0
|
|
179
|
+
let dragPreview: HTMLElement | undefined
|
|
69
180
|
const [drag, setDrag] = createSignal<{ id: string; token: string }>()
|
|
70
181
|
const [drop, setDrop] = createSignal<{ id: string; intent: PaneDropIntent }>()
|
|
71
182
|
const clearDrag = () => {
|
|
72
183
|
setDrag(undefined)
|
|
73
184
|
setDrop(undefined)
|
|
185
|
+
dragPreview?.remove()
|
|
186
|
+
dragPreview = undefined
|
|
187
|
+
}
|
|
188
|
+
const createDragPreview = (source: HTMLElement) => {
|
|
189
|
+
dragPreview?.remove()
|
|
190
|
+
const preview = cloneDragPreviewNode(source) as HTMLElement
|
|
191
|
+
preview.className =
|
|
192
|
+
'pointer-events-none fixed -left-full top-0 z-(--z-dialog) flex h-64 max-h-screen w-96 max-w-screen min-h-0 min-w-0 flex-col overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-xl'
|
|
193
|
+
preview.removeAttribute('id')
|
|
194
|
+
preview.removeAttribute('style')
|
|
195
|
+
preview.removeAttribute('role')
|
|
196
|
+
preview.removeAttribute('aria-label')
|
|
197
|
+
preview.removeAttribute('data-pane-id')
|
|
198
|
+
preview.removeAttribute('data-focused')
|
|
199
|
+
preview.removeAttribute('data-drag-source')
|
|
200
|
+
preview.removeAttribute('data-drop-direction')
|
|
201
|
+
preview.removeAttribute('data-drop-placement')
|
|
202
|
+
preview.setAttribute('data-split-drag-preview', '')
|
|
203
|
+
preview.setAttribute('aria-hidden', 'true')
|
|
204
|
+
preview.inert = true
|
|
205
|
+
document.body.append(preview)
|
|
206
|
+
dragPreview = preview
|
|
207
|
+
return preview
|
|
74
208
|
}
|
|
75
209
|
const domId = (id: string) => {
|
|
76
210
|
const existing = domIds.get(id)
|
|
@@ -236,7 +370,7 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
236
370
|
props.onMove?.(current.id, id, next)
|
|
237
371
|
}}
|
|
238
372
|
style={rectStyle(frames().get(id)?.rect ?? { x: 0, y: 0, width: 0, height: 0 })}
|
|
239
|
-
class="absolute flex min-h-0 min-w-0 flex-col overflow-hidden border border-border transition-opacity data-[drag-source]:opacity-40 data-[focused]:border-primary bg-background text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
|
|
373
|
+
class="absolute flex min-h-0 min-w-0 flex-col overflow-hidden border border-border transition-opacity motion-reduce:transition-none data-[drag-source]:opacity-40 data-[focused]:border-primary bg-background text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring"
|
|
240
374
|
onFocusIn={() => props.onFocus?.(id)}
|
|
241
375
|
>
|
|
242
376
|
<header class="flex min-w-0 shrink-0 items-center gap-2 bg-surface px-2">
|
|
@@ -245,6 +379,7 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
245
379
|
draggable={Boolean(props.onMove)}
|
|
246
380
|
class={cn('flex min-w-0 flex-1 items-center gap-2 truncate text-sm', {
|
|
247
381
|
'cursor-grab': Boolean(props.onMove),
|
|
382
|
+
'cursor-grabbing': drag()?.id === id,
|
|
248
383
|
})}
|
|
249
384
|
title={props.onMove ? `Drag ${props.labelForLeaf(leaf())} to move` : undefined}
|
|
250
385
|
onDragStart={(event) => {
|
|
@@ -256,18 +391,23 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
256
391
|
setDrag({ id, token })
|
|
257
392
|
event.dataTransfer.setData(PANE_DRAG_TYPE, token)
|
|
258
393
|
event.dataTransfer.effectAllowed = 'move'
|
|
259
|
-
// The
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
// its source visibly recedes.
|
|
394
|
+
// The native drag image carries this pane's actual content
|
|
395
|
+
// in a bounded floating-window frame; the source recedes
|
|
396
|
+
// independently as the browser follows the image pointer.
|
|
263
397
|
const source = refs.get(id)
|
|
264
398
|
const frame = source?.getBoundingClientRect()
|
|
265
|
-
if (source && frame && typeof event.dataTransfer.setDragImage === 'function')
|
|
399
|
+
if (source && frame && typeof event.dataTransfer.setDragImage === 'function') {
|
|
400
|
+
const preview = createDragPreview(source)
|
|
401
|
+
const previewFrame = preview.getBoundingClientRect()
|
|
402
|
+
const scaleX = previewFrame.width / frame.width
|
|
403
|
+
const scaleY = previewFrame.height / frame.height
|
|
266
404
|
event.dataTransfer.setDragImage(
|
|
267
|
-
|
|
268
|
-
Math.max(0, Math.round(event.clientX - frame.left)),
|
|
269
|
-
Math.max(0, Math.round(event.clientY - frame.top))
|
|
405
|
+
preview,
|
|
406
|
+
Math.max(0, Math.round((event.clientX - frame.left) * scaleX)),
|
|
407
|
+
Math.max(0, Math.round((event.clientY - frame.top) * scaleY))
|
|
270
408
|
)
|
|
409
|
+
preview.setAttribute('data-native-drag-image', '')
|
|
410
|
+
}
|
|
271
411
|
}}
|
|
272
412
|
onDragEnd={clearDrag}
|
|
273
413
|
>
|