@adea-ai/ui 0.109.1 → 0.110.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 +14 -0
- package/dist/components/composites/account-menu/account-menu.js +1 -1
- package/dist/components/composites/action-button/action-button.js +1 -1
- package/dist/components/layout/split-layout/index.js +2 -2
- package/dist/components/layout/split-layout/model.d.ts +13 -0
- package/dist/components/layout/split-layout/model.d.ts.map +1 -1
- package/dist/components/layout/split-layout/model.js +55 -18
- package/dist/components/layout/split-layout/model.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 +5 -2
- package/dist/components/layout/split-layout/split-layout.js.map +1 -1
- package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog/dialog.js +15 -9
- package/dist/components/ui/dialog/dialog.js.map +1 -1
- package/dist/components/ui/tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip/tooltip.js +2 -2
- package/dist/components/ui/tooltip/tooltip.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/r/dialog.json +1 -0
- package/dist/r/registry.json +2 -1
- package/dist/r/split-layout.json +1 -1
- package/dist/r/src/components/layout/split-layout/model.ts +62 -10
- package/dist/r/src/components/layout/split-layout/split-layout.tsx +4 -3
- package/dist/r/src/components/ui/dialog/dialog.tsx +17 -10
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +6 -1
- package/package.json +1 -1
- package/registry.json +2 -1
- package/src/components/layout/split-layout/model.ts +62 -10
- package/src/components/layout/split-layout/split-layout.tsx +4 -3
- package/src/components/ui/dialog/dialog.tsx +17 -10
- package/src/components/ui/tooltip/tooltip.tsx +6 -1
|
@@ -41,6 +41,10 @@ export type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readon
|
|
|
41
41
|
splitId: string
|
|
42
42
|
}>
|
|
43
43
|
|
|
44
|
+
export type BalancedSplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<
|
|
45
|
+
Pick<SplitPaneInput<L>, 'placement' | 'leaf' | 'splitId'>
|
|
46
|
+
>
|
|
47
|
+
|
|
44
48
|
function replaceLeaf<L extends SplitLayoutLeaf>(
|
|
45
49
|
node: SplitLayoutNode<L>,
|
|
46
50
|
leafId: string,
|
|
@@ -86,14 +90,19 @@ function assertUniqueInput<L extends SplitLayoutLeaf>(
|
|
|
86
90
|
if (typeof splitId !== 'string' || splitId.length === 0)
|
|
87
91
|
throw new Error('invalid_state: split identity must be nonempty')
|
|
88
92
|
const ids = new Set<string>()
|
|
93
|
+
const splitIds: string[] = []
|
|
89
94
|
const visit = (node: SplitLayoutNode<L>) => {
|
|
90
95
|
if (ids.has(node.id)) throw new Error('invalid_state: duplicate pane id')
|
|
91
96
|
ids.add(node.id)
|
|
92
|
-
if (node.kind === 'split')
|
|
97
|
+
if (node.kind === 'split') {
|
|
98
|
+
splitIds.push(node.id)
|
|
99
|
+
node.children.forEach(visit)
|
|
100
|
+
}
|
|
93
101
|
}
|
|
94
102
|
visit(state.center)
|
|
95
103
|
if (ids.has(leaf.id) || ids.has(splitId) || leaf.id === splitId)
|
|
96
104
|
throw new Error('invalid_state: duplicate pane id')
|
|
105
|
+
return splitIds
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
/** Visual-tree validation only; applications still decode and scope persisted preferences. */
|
|
@@ -163,6 +172,51 @@ export function splitPane<L extends SplitLayoutLeaf>(
|
|
|
163
172
|
return { ...state, center, focusedLeafId: input.leaf.id, closed: [] }
|
|
164
173
|
}
|
|
165
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Add a pane beside the selected leaf and reflow the visual tree into a
|
|
177
|
+
* balanced row-major grid. One or two leaves share one row; from three leaves
|
|
178
|
+
* onward, leaves fill a first row and then a second row, with no more than four
|
|
179
|
+
* columns in either. Each row gives its leaves equal width and the two rows
|
|
180
|
+
* equal height. Existing opaque leaf objects are preserved. Split IDs are
|
|
181
|
+
* recycled across rebuilt branches, whose axis and descendants can change;
|
|
182
|
+
* resize actions must resolve against the current tree. Ratios are recomputed.
|
|
183
|
+
* This is opt-in: callers
|
|
184
|
+
* that need an explicit axis and ratio should keep using `splitPane`.
|
|
185
|
+
*/
|
|
186
|
+
export function splitPaneBalanced<L extends SplitLayoutLeaf>(
|
|
187
|
+
state: SplitLayoutState<L>,
|
|
188
|
+
targetLeafId: string,
|
|
189
|
+
input: BalancedSplitPaneInput<L>
|
|
190
|
+
): SplitLayoutState<L> {
|
|
191
|
+
const leaves = [...listLeaves(state.center)]
|
|
192
|
+
const targetIndex = leaves.findIndex((leaf) => leaf.id === targetLeafId)
|
|
193
|
+
if (targetIndex < 0) return state
|
|
194
|
+
if (leaves.length >= MAX_LAYOUT_LEAVES)
|
|
195
|
+
throw new Error('limit_exceeded: center layout has eight leaves')
|
|
196
|
+
const splitIds = assertUniqueInput(state, input.leaf, input.splitId)
|
|
197
|
+
|
|
198
|
+
const insertionIndex = targetIndex + (input.placement === 'before' ? 0 : 1)
|
|
199
|
+
leaves.splice(insertionIndex, 0, input.leaf)
|
|
200
|
+
|
|
201
|
+
const build = (start: number, end: number, vertical = false): SplitLayoutNode<L> => {
|
|
202
|
+
if (end - start === 1) return leaves[start]!
|
|
203
|
+
const middle = start + Math.ceil((end - start) / 2)
|
|
204
|
+
// The new identity belongs to the lowest branch separating the selected
|
|
205
|
+
// pane from its new neighbour. Other branches reuse the existing ID pool.
|
|
206
|
+
const id = middle === targetIndex + 1 ? input.splitId : splitIds.shift()!
|
|
207
|
+
return {
|
|
208
|
+
kind: 'split',
|
|
209
|
+
id,
|
|
210
|
+
direction: vertical ? 'column' : 'row',
|
|
211
|
+
ratio: vertical ? 0.5 : (middle - start) / (end - start),
|
|
212
|
+
children: [build(start, middle), build(middle, end)],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const center = build(0, leaves.length, leaves.length > 2)
|
|
216
|
+
assertLayoutTree(center)
|
|
217
|
+
return { ...state, center, focusedLeafId: input.leaf.id, closed: [] }
|
|
218
|
+
}
|
|
219
|
+
|
|
166
220
|
export function closePane<L extends SplitLayoutLeaf>(
|
|
167
221
|
state: SplitLayoutState<L>,
|
|
168
222
|
leafId: string,
|
|
@@ -287,21 +341,19 @@ export function resizeSplit<L extends SplitLayoutLeaf>(
|
|
|
287
341
|
export function normalizeLayout<L extends SplitLayoutLeaf>(
|
|
288
342
|
state: SplitLayoutState<L>
|
|
289
343
|
): SplitLayoutState<L> {
|
|
290
|
-
const visit = (node: SplitLayoutNode<L>):
|
|
291
|
-
if (node.kind === 'leaf') return
|
|
344
|
+
const visit = (node: SplitLayoutNode<L>): SplitLayoutNode<L> => {
|
|
345
|
+
if (node.kind === 'leaf') return node
|
|
292
346
|
const first = visit(node.children[0])
|
|
293
347
|
const second = visit(node.children[1])
|
|
294
348
|
const ratio = Number.isFinite(node.ratio)
|
|
295
349
|
? Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, node.ratio))
|
|
296
350
|
: (MIN_SPLIT_RATIO + MAX_SPLIT_RATIO) / 2
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
changed,
|
|
301
|
-
}
|
|
351
|
+
return first === node.children[0] && second === node.children[1] && ratio === node.ratio
|
|
352
|
+
? node
|
|
353
|
+
: { ...node, ratio, children: [first, second] }
|
|
302
354
|
}
|
|
303
|
-
const
|
|
304
|
-
return
|
|
355
|
+
const center = visit(state.center)
|
|
356
|
+
return center === state.center ? state : { ...state, center }
|
|
305
357
|
}
|
|
306
358
|
|
|
307
359
|
/** The leaf immediately after (`1`) or before (`-1`) the given leaf in reading order. */
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type JSX,
|
|
12
12
|
} from 'solid-js'
|
|
13
13
|
import { X, GripVertical } from 'lucide-solid'
|
|
14
|
-
import {
|
|
14
|
+
import { ActionButton } from '../../composites/action-button'
|
|
15
15
|
import { cn } from '#lib/utils'
|
|
16
16
|
import { computeLayoutFrames, type LayoutRect } from './geometry'
|
|
17
17
|
import { paneDropIntent, type PaneDropIntent } from './drop'
|
|
@@ -420,14 +420,15 @@ export function SplitLayout<L extends SplitLayoutLeaf>(props: SplitLayoutProps<L
|
|
|
420
420
|
</span>
|
|
421
421
|
{actions}
|
|
422
422
|
{props.onClose ? (
|
|
423
|
-
<
|
|
423
|
+
<ActionButton
|
|
424
424
|
variant="ghost"
|
|
425
425
|
size="icon-xs"
|
|
426
426
|
aria-label={`Close ${props.labelForLeaf(leaf())}`}
|
|
427
|
+
tooltip={`Close ${props.labelForLeaf(leaf())}.`}
|
|
427
428
|
onClick={() => close(id)}
|
|
428
429
|
>
|
|
429
430
|
<X />
|
|
430
|
-
</
|
|
431
|
+
</ActionButton>
|
|
431
432
|
) : null}
|
|
432
433
|
</header>
|
|
433
434
|
<div class="min-h-0 min-w-0 flex-1 overflow-auto">{content}</div>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
overlayTitle,
|
|
16
16
|
} from '#lib/overlay'
|
|
17
17
|
import { cn } from '#lib/utils'
|
|
18
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
|
|
18
19
|
|
|
19
20
|
type FocusCycle = {
|
|
20
21
|
content: HTMLElement
|
|
@@ -290,16 +291,22 @@ export function DialogContent(props: DialogContentProps) {
|
|
|
290
291
|
>
|
|
291
292
|
{local.children}
|
|
292
293
|
<Show when={local.closeButton !== false}>
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
294
|
+
{/* The corner close is icon-only, so it carries the shared tooltip
|
|
295
|
+
treatment while its accessible name stays "Close". */}
|
|
296
|
+
<Tooltip>
|
|
297
|
+
<TooltipTrigger
|
|
298
|
+
as={KobalteDialog.CloseButton}
|
|
299
|
+
aria-label="Close"
|
|
300
|
+
class={cn(
|
|
301
|
+
'absolute top-3.5 end-3.5 rounded-md p-1 text-muted-foreground',
|
|
302
|
+
'transition-colors ease-out hover:bg-surface-hover hover:text-foreground',
|
|
303
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle outline-none'
|
|
304
|
+
)}
|
|
305
|
+
>
|
|
306
|
+
{local.closeButton ?? <X class="size-4" />}
|
|
307
|
+
</TooltipTrigger>
|
|
308
|
+
<TooltipContent>Close dialog.</TooltipContent>
|
|
309
|
+
</Tooltip>
|
|
303
310
|
</Show>
|
|
304
311
|
</KobalteDialogContent>
|
|
305
312
|
</div>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
useContext,
|
|
11
11
|
} from 'solid-js'
|
|
12
12
|
import { isServer } from 'solid-js/web'
|
|
13
|
-
import { tooltipTip } from '#lib/overlay'
|
|
13
|
+
import { tooltipTip, topLayerProps } from '#lib/overlay'
|
|
14
14
|
import { cn } from '#lib/utils'
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -397,6 +397,11 @@ export function TooltipContent(props: TooltipContentProps) {
|
|
|
397
397
|
invokeEventHandler(local.onPointerDownOutside, event)
|
|
398
398
|
if (!event.defaultPrevented) interaction?.markCloseIntent()
|
|
399
399
|
}}
|
|
400
|
+
// A tooltip opened from inside a modal dialog is part of that dialog's
|
|
401
|
+
// surface, so it carries the same top-layer exemption as a menu or a
|
|
402
|
+
// popover — without it the dialog's hide-outside observer aria-hides
|
|
403
|
+
// the tip while it is on screen.
|
|
404
|
+
{...topLayerProps}
|
|
400
405
|
{...rest}
|
|
401
406
|
>
|
|
402
407
|
{local.children}
|