@iloveagents/foundry-web-ui 0.33.1 → 0.33.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.
- package/dist/components/sidebar.d.ts +33 -1
- package/dist/components/sidebar.js +94 -44
- package/dist/lib/nav-dnd.d.ts +109 -1
- package/dist/lib/nav-dnd.js +102 -2
- package/dist/lib/theme-runtime.js +18 -4
- package/dist/workbench/conversation-history.d.ts +1 -1
- package/dist/workbench/conversation-history.js +12 -7
- package/dist/workbench/selection-bridge.js +51 -19
- package/package.json +3 -3
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { NavGroup } from "../lib/nav-config.js";
|
|
2
|
+
import type { NavGroup, NavItemDnd } from "../lib/nav-config.js";
|
|
3
3
|
declare function scrollNavRowIntoView(nav: HTMLElement, row: HTMLElement): void;
|
|
4
4
|
export declare const _scrollNavRowIntoViewForTest: typeof scrollNavRowIntoView;
|
|
5
|
+
/** Drop zone rendered inside expanded containers so users can move items back to the container root.
|
|
6
|
+
*
|
|
7
|
+
* The strip stays IN FLOW and reserves its full height for the whole drag:
|
|
8
|
+
* 4px at rest, 28px from the moment a drag begins until it ends, with the
|
|
9
|
+
* band filling exactly what was reserved. Growing on hover instead — the
|
|
10
|
+
* obvious design — fails in both of its available shapes:
|
|
11
|
+
*
|
|
12
|
+
* - in flow, every row beneath jumps 24px at the exact moment the pointer
|
|
13
|
+
* crosses the strip, which is the moment the user is aiming at one of
|
|
14
|
+
* those rows. Dragging a file from a nested folder down to a sibling
|
|
15
|
+
* crosses one strip per open container, so the destination walks away
|
|
16
|
+
* from the cursor mid-drag (measured: 28px per crossing) and the drop
|
|
17
|
+
* lands on whatever slid underneath. From the user's seat the gesture
|
|
18
|
+
* simply does nothing, with no request made and nothing to read.
|
|
19
|
+
* - out of flow, nothing moves, but the expanded band overlaps ~12px of
|
|
20
|
+
* the rows on either side and, being positioned, takes their drops — a
|
|
21
|
+
* moving target traded for a wrong one.
|
|
22
|
+
*
|
|
23
|
+
* Reserving for the whole drag settles the layout once, before the user has
|
|
24
|
+
* aimed at anything. The opening is deliberately NOT animated: a 150ms
|
|
25
|
+
* height transition is a moving target for 150ms, which is inside the time
|
|
26
|
+
* a short drag to a nearby row takes. Only the collapse animates, and by
|
|
27
|
+
* then nothing is being aimed at.
|
|
28
|
+
*
|
|
29
|
+
* Exported for its test only — it is not in the package barrel, and the
|
|
30
|
+
* invariants (in flow, reserved by the drag rather than the pointer, band
|
|
31
|
+
* == strip) are ones jsdom can check from the class names and nothing can
|
|
32
|
+
* check from the outside.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ContainerDropZone({ dnd }: {
|
|
35
|
+
dnd: NavItemDnd;
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
5
37
|
interface SidebarViewProps {
|
|
6
38
|
/** Display-only navigation override; the navigation registry remains intact. */
|
|
7
39
|
groups?: NavGroup[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
3
3
|
import { NavLink, useLocation, useNavigate } from "react-router";
|
|
4
4
|
import { Ellipsis, Layers, PanelLeft, PanelLeftOpen, SquarePen, ChevronDown, ChevronRight, GripVertical, Plus, Upload, ChevronLeft, } from "lucide-react";
|
|
@@ -8,7 +8,7 @@ import { useToolPanelStore } from "../lib/tool-panel-store.js";
|
|
|
8
8
|
import { useNewConversation } from "../lib/use-new-conversation.js";
|
|
9
9
|
import { useNavStore } from "../lib/nav-store.js";
|
|
10
10
|
import { useAppStore } from "../lib/app-store.js";
|
|
11
|
-
import {
|
|
11
|
+
import { beginNavDrag, subscribeNavDrag, getNavDragVersion, navDragAccepts, hasAnyDropTarget, hasExternalFilesData, } from "../lib/nav-dnd.js";
|
|
12
12
|
import { TooltipIconButton } from "./tooltip-icon-button.js";
|
|
13
13
|
import { Button } from "@iloveagents/foundry-web-primitives";
|
|
14
14
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu.js";
|
|
@@ -262,6 +262,12 @@ function useNavItemDnd(dnd) {
|
|
|
262
262
|
return;
|
|
263
263
|
e.dataTransfer.setData(dnd.type, data);
|
|
264
264
|
e.dataTransfer.effectAllowed = "move";
|
|
265
|
+
// Container drop zones reserve their space for the whole drag rather
|
|
266
|
+
// than growing under the pointer — see `beginNavDrag`. The MIME type
|
|
267
|
+
// goes with it so only zones that would take THIS drag open, and the
|
|
268
|
+
// source element so its `dragend` is still heard if a nav refresh
|
|
269
|
+
// detaches this row mid-drag.
|
|
270
|
+
beginNavDrag(dnd.type, e.currentTarget);
|
|
265
271
|
}, [dnd]);
|
|
266
272
|
const onDragEnter = useCallback((e) => {
|
|
267
273
|
if (!hasMatchingDragData(e))
|
|
@@ -359,57 +365,101 @@ function useNavItemDnd(dnd) {
|
|
|
359
365
|
: {},
|
|
360
366
|
};
|
|
361
367
|
}
|
|
362
|
-
/** Drop zone rendered inside expanded containers so users can move items back to the container root.
|
|
363
|
-
|
|
368
|
+
/** Drop zone rendered inside expanded containers so users can move items back to the container root.
|
|
369
|
+
*
|
|
370
|
+
* The strip stays IN FLOW and reserves its full height for the whole drag:
|
|
371
|
+
* 4px at rest, 28px from the moment a drag begins until it ends, with the
|
|
372
|
+
* band filling exactly what was reserved. Growing on hover instead — the
|
|
373
|
+
* obvious design — fails in both of its available shapes:
|
|
374
|
+
*
|
|
375
|
+
* - in flow, every row beneath jumps 24px at the exact moment the pointer
|
|
376
|
+
* crosses the strip, which is the moment the user is aiming at one of
|
|
377
|
+
* those rows. Dragging a file from a nested folder down to a sibling
|
|
378
|
+
* crosses one strip per open container, so the destination walks away
|
|
379
|
+
* from the cursor mid-drag (measured: 28px per crossing) and the drop
|
|
380
|
+
* lands on whatever slid underneath. From the user's seat the gesture
|
|
381
|
+
* simply does nothing, with no request made and nothing to read.
|
|
382
|
+
* - out of flow, nothing moves, but the expanded band overlaps ~12px of
|
|
383
|
+
* the rows on either side and, being positioned, takes their drops — a
|
|
384
|
+
* moving target traded for a wrong one.
|
|
385
|
+
*
|
|
386
|
+
* Reserving for the whole drag settles the layout once, before the user has
|
|
387
|
+
* aimed at anything. The opening is deliberately NOT animated: a 150ms
|
|
388
|
+
* height transition is a moving target for 150ms, which is inside the time
|
|
389
|
+
* a short drag to a nearby row takes. Only the collapse animates, and by
|
|
390
|
+
* then nothing is being aimed at.
|
|
391
|
+
*
|
|
392
|
+
* Exported for its test only — it is not in the package barrel, and the
|
|
393
|
+
* invariants (in flow, reserved by the drag rather than the pointer, band
|
|
394
|
+
* == strip) are ones jsdom can check from the class names and nothing can
|
|
395
|
+
* check from the outside.
|
|
396
|
+
*/
|
|
397
|
+
export function ContainerDropZone({ dnd }) {
|
|
364
398
|
const [isDragOver, setIsDragOver] = useState(false);
|
|
365
|
-
const [isFileDragOver, setIsFileDragOver] = useState(false);
|
|
366
399
|
const dragCounter = useRef(0);
|
|
367
400
|
const acceptsFiles = dnd.canDropFiles?.() ?? false;
|
|
368
401
|
const acceptsEntities = dnd.canDrop();
|
|
402
|
+
// Whether this zone is open is the DRAG's business, not the pointer's:
|
|
403
|
+
// the band is reserved before the pointer reaches it, so hover can no
|
|
404
|
+
// longer say anything about what is being dragged. And reserving space
|
|
405
|
+
// and painting a label is a promise that a drop will land here — so a
|
|
406
|
+
// zone opens only for a drag it would accept, which the drag's own MIME
|
|
407
|
+
// types decide.
|
|
408
|
+
//
|
|
409
|
+
// The subscribed snapshot is a VERSION, not the types themselves: a MIME
|
|
410
|
+
// token may legally contain whatever character one joins them with, and a
|
|
411
|
+
// split on it turns one type into two. The predicate reads the set.
|
|
412
|
+
useSyncExternalStore(subscribeNavDrag, getNavDragVersion, () => 0);
|
|
413
|
+
const dragActive = navDragAccepts({ type: dnd.type, acceptsEntities });
|
|
369
414
|
const hasExternalFiles = (e) => hasExternalFilesData(e.dataTransfer);
|
|
370
415
|
const hasMatchingDragData = (e) => Boolean((acceptsEntities && e.dataTransfer.types.includes(dnd.type)) ||
|
|
371
416
|
(acceptsFiles && hasExternalFiles(e)));
|
|
372
|
-
return (_jsx("div", {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
417
|
+
return (_jsx("div", { className: cn("relative mx-2",
|
|
418
|
+
// Reserved for the whole drag, so the rows below settle once —
|
|
419
|
+
// before the user has aimed — and never while they are aiming.
|
|
420
|
+
// The transition rides on the RESTING class, so the reservation
|
|
421
|
+
// opens instantly and only the collapse animates: an animated
|
|
422
|
+
// opening is a moving target for as long as it runs.
|
|
423
|
+
dragActive ? "mt-0 h-7" : "-mt-1 h-1 transition-[height,margin] duration-150"), children: _jsx("div", { onDragEnter: (e) => {
|
|
424
|
+
if (!hasMatchingDragData(e))
|
|
425
|
+
return;
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
// Hover state only — the band's size is the drag's business, not
|
|
428
|
+
// the pointer's. Opening it from here is the original bug: the row
|
|
429
|
+
// the user is aiming at slides away as they reach it.
|
|
430
|
+
dragCounter.current++;
|
|
431
|
+
setIsDragOver(true);
|
|
432
|
+
}, onDragLeave: () => {
|
|
433
|
+
dragCounter.current--;
|
|
434
|
+
if (dragCounter.current <= 0) {
|
|
435
|
+
dragCounter.current = 0;
|
|
436
|
+
setIsDragOver(false);
|
|
437
|
+
}
|
|
438
|
+
}, onDragOver: (e) => {
|
|
439
|
+
if (!hasMatchingDragData(e))
|
|
440
|
+
return;
|
|
441
|
+
e.preventDefault();
|
|
442
|
+
e.dataTransfer.dropEffect = hasExternalFiles(e) ? "copy" : "move";
|
|
443
|
+
}, onDrop: async (e) => {
|
|
444
|
+
const files = Array.from(e.dataTransfer.files ?? []);
|
|
445
|
+
const draggedData = acceptsEntities ? e.dataTransfer.getData(dnd.type) : "";
|
|
446
|
+
const hasFiles = acceptsFiles && files.length > 0 && dnd.onFileDrop;
|
|
447
|
+
const hasEntityDrop = Boolean(acceptsEntities && draggedData);
|
|
448
|
+
if (!hasFiles && !hasEntityDrop)
|
|
449
|
+
return;
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
e.stopPropagation();
|
|
382
452
|
dragCounter.current = 0;
|
|
383
|
-
setIsFileDragOver(false);
|
|
384
453
|
setIsDragOver(false);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const hasFiles = acceptsFiles && files.length > 0 && dnd.onFileDrop;
|
|
395
|
-
const hasEntityDrop = Boolean(acceptsEntities && draggedData);
|
|
396
|
-
if (!hasFiles && !hasEntityDrop)
|
|
397
|
-
return;
|
|
398
|
-
e.preventDefault();
|
|
399
|
-
e.stopPropagation();
|
|
400
|
-
dragCounter.current = 0;
|
|
401
|
-
setIsFileDragOver(false);
|
|
402
|
-
setIsDragOver(false);
|
|
403
|
-
if (hasFiles) {
|
|
404
|
-
await dnd.onFileDrop(files);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
await dnd.onDrop(draggedData);
|
|
408
|
-
}, className: cn("relative mx-2 rounded-md overflow-hidden", isDragOver
|
|
409
|
-
? cn("h-7 flex items-center justify-center gap-1", isFileDragOver
|
|
410
|
-
? "bg-primary/10 border border-dashed border-primary"
|
|
411
|
-
: "bg-primary/10 border border-dashed border-primary/30")
|
|
412
|
-
: "-mt-1 h-1"), children: isDragOver && (_jsxs(_Fragment, { children: [isFileDragOver && _jsx(Upload, { className: "size-3 text-primary" }), _jsx("span", { className: "text-[10px] text-primary/70", children: getContainerDropZoneLabel(isFileDragOver) })] })) }));
|
|
454
|
+
if (hasFiles) {
|
|
455
|
+
await dnd.onFileDrop(files);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
await dnd.onDrop(draggedData);
|
|
459
|
+
}, className: cn(
|
|
460
|
+
// Fills the reserved strip exactly: what is painted is what
|
|
461
|
+
// accepts a drop, and nothing extends over a neighbouring row.
|
|
462
|
+
"h-full w-full rounded-md overflow-hidden", dragActive && "flex items-center justify-center", isDragOver && "bg-primary/10 border border-dashed border-primary/30"), children: dragActive && (_jsx("span", { className: cn("text-[10px] transition-colors", isDragOver ? "text-primary/70" : "text-sidebar-foreground/35"), children: "Move to root" })) }) }));
|
|
413
463
|
}
|
|
414
464
|
/**
|
|
415
465
|
* Leading disclosure cell on every nav row — the Confluence page-tree
|
package/dist/lib/nav-dnd.d.ts
CHANGED
|
@@ -5,4 +5,112 @@ export declare function hasExternalFilesData(dataTransfer: {
|
|
|
5
5
|
} | null;
|
|
6
6
|
}): boolean;
|
|
7
7
|
export declare function hasAnyDropTarget(isDropTarget: boolean, isFileDropTarget: boolean): boolean;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Is a drag that the nav can accept in flight, anywhere — and what is it?
|
|
10
|
+
*
|
|
11
|
+
* Container drop zones are a 4px strip at rest and a labelled band while a
|
|
12
|
+
* drag is in flight, and WHEN they grow decides whether the sidebar is
|
|
13
|
+
* usable. Growing on hover is the trap, in both available shapes:
|
|
14
|
+
*
|
|
15
|
+
* - in flow, every row below the strip jumps 24px at the moment the pointer
|
|
16
|
+
* crosses it — which is the moment the user is aiming at one of them, so
|
|
17
|
+
* the destination walks away from the cursor;
|
|
18
|
+
* - out of flow, nothing moves, but the expanded band then covers ~12px of
|
|
19
|
+
* the rows on either side and takes their drops.
|
|
20
|
+
*
|
|
21
|
+
* So the space is reserved for the whole drag instead. The layout settles
|
|
22
|
+
* once, before the user has aimed at anything, and from then on every zone's
|
|
23
|
+
* hit area is exactly the band that is painted — no moving target, and no
|
|
24
|
+
* neighbour quietly answering for another.
|
|
25
|
+
*
|
|
26
|
+
* "Before the user has aimed" is the whole value, and it is also the whole
|
|
27
|
+
* PRECONDITION. Only a drag that starts in this document has a moment before
|
|
28
|
+
* the aim: `dragstart` fires before any movement, so the layout can settle
|
|
29
|
+
* while the pointer is still on the row being picked up.
|
|
30
|
+
*
|
|
31
|
+
* A drag arriving from OUTSIDE — the desktop, or another window — has no such
|
|
32
|
+
* moment. The browser hit-tests whatever is under the pointer before any
|
|
33
|
+
* script runs, so the first `dragenter` this document sees is already aimed
|
|
34
|
+
* at something; opening the zones from it moves that something out from under
|
|
35
|
+
* a stationary pointer. That is the very failure this whole change exists to
|
|
36
|
+
* remove, arriving by a different road.
|
|
37
|
+
*
|
|
38
|
+
* So outside drags get no reservation: the strips stay 4px and nothing moves.
|
|
39
|
+
* They are not left without a target — container rows accept and highlight
|
|
40
|
+
* file drops themselves, which is the path a file drag was always going to
|
|
41
|
+
* take. The root-drop affordance is a convenience with a redundant route; the
|
|
42
|
+
* mis-aim was a correctness failure with none.
|
|
43
|
+
*
|
|
44
|
+
* WHAT is tracked is the gesture's set of MIME types, not just "a drag is
|
|
45
|
+
* happening". Reserving space and painting a label is a promise that a drop
|
|
46
|
+
* will land, so only a zone that would take this particular drag may make it.
|
|
47
|
+
* A set rather than one type because `dragstart` BUBBLES: a draggable row
|
|
48
|
+
* sits inside a draggable container, both handlers run, and tracking one
|
|
49
|
+
* would track whichever ran last — the ancestor's — leaving zones that match
|
|
50
|
+
* the child's payload shut.
|
|
51
|
+
*
|
|
52
|
+
* The set is read directly and never serialised. It was briefly published as
|
|
53
|
+
* a joined string, for `useSyncExternalStore`'s identity comparison — but a
|
|
54
|
+
* MIME token may legally contain the separator (`|` is not one of RFC 2045's
|
|
55
|
+
* tspecials), and then one type splits into two. The snapshot is a version
|
|
56
|
+
* counter instead, so there is nothing to encode.
|
|
57
|
+
*/
|
|
58
|
+
type NavDragListener = () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Ends whatever drag is in flight.
|
|
61
|
+
*
|
|
62
|
+
* Bound to the window for `dragend` (a drop or a cancel) and `drop` (a
|
|
63
|
+
* source outside this document, which has no `dragend` here at all), and to
|
|
64
|
+
* the source element itself, whose own `dragend` is the only one left once a
|
|
65
|
+
* nav refresh has detached it.
|
|
66
|
+
*
|
|
67
|
+
* There is deliberately no `dragleave` ending. A relatedTarget-less
|
|
68
|
+
* `dragleave` is the only signal a drag leaving the window gives, and it is
|
|
69
|
+
* the same signal whether the drag is over or merely outside — so it ended
|
|
70
|
+
* live in-page drags, collapsing every zone for the rest of the gesture. It
|
|
71
|
+
* existed for drags with no `dragend` in this document, and none of those
|
|
72
|
+
* opens a reservation now.
|
|
73
|
+
*/
|
|
74
|
+
export declare function endNavDrag(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Called from `dragstart` on a nav row, with the MIME type that row wrote its
|
|
77
|
+
* payload under and the element it started from.
|
|
78
|
+
*
|
|
79
|
+
* The type ADDS rather than replaces: `dragstart` bubbles through every
|
|
80
|
+
* draggable ancestor, and each one's type belongs to the same gesture.
|
|
81
|
+
*
|
|
82
|
+
* The source is held so its `dragend` can be heard even after a nav refresh
|
|
83
|
+
* has removed the row mid-drag. A detached node still receives its own
|
|
84
|
+
* `dragend`, but the event reaches no window listener from there — so
|
|
85
|
+
* without this the reservation would stay open, every zone expanded, until
|
|
86
|
+
* some later drag happened to finish elsewhere.
|
|
87
|
+
*/
|
|
88
|
+
export declare function beginNavDrag(type: string, source?: Element | null): void;
|
|
89
|
+
export declare function subscribeNavDrag(listener: NavDragListener): () => void;
|
|
90
|
+
/**
|
|
91
|
+
* The snapshot `useSyncExternalStore` compares: a version, bumped on every
|
|
92
|
+
* change. A primitive, so identity comparison is meaningful, and carrying no
|
|
93
|
+
* encoding of its own — see the note about `|` above.
|
|
94
|
+
*/
|
|
95
|
+
export declare function getNavDragVersion(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Would a zone with this `dnd.type` accept the live drag?
|
|
98
|
+
*
|
|
99
|
+
* Reserving space and painting a label is a promise that a drop will land,
|
|
100
|
+
* so only a zone that would actually take this drag may do either. "A drag
|
|
101
|
+
* is happening" was not enough: two modules with different `dnd.type`s
|
|
102
|
+
* opened each other's zones.
|
|
103
|
+
*
|
|
104
|
+
* Files need no case here. Only a nav row's `dragstart` opens a reservation
|
|
105
|
+
* and those carry an entity, so no zone ever opens for a file drag whatever
|
|
106
|
+
* it is configured to accept. It still HANDLES one — the resting strip takes
|
|
107
|
+
* a file drop like any other target — and the container's own row advertises
|
|
108
|
+
* that same drop at full size.
|
|
109
|
+
*/
|
|
110
|
+
export declare function navDragAccepts(zone: {
|
|
111
|
+
type: string;
|
|
112
|
+
acceptsEntities: boolean;
|
|
113
|
+
}): boolean;
|
|
114
|
+
/** Test seam — module-level state outlives a test file otherwise. */
|
|
115
|
+
export declare function __resetNavDragForTest(): void;
|
|
116
|
+
export {};
|
package/dist/lib/nav-dnd.js
CHANGED
|
@@ -4,6 +4,106 @@ export function hasExternalFilesData(dataTransfer) {
|
|
|
4
4
|
export function hasAnyDropTarget(isDropTarget, isFileDropTarget) {
|
|
5
5
|
return isDropTarget || isFileDropTarget;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const navDragListeners = new Set();
|
|
8
|
+
/** The MIME types this gesture carries. Empty means no drag is in flight. */
|
|
9
|
+
const navDragTypes = new Set();
|
|
10
|
+
/** Bumped on every change — the `useSyncExternalStore` snapshot. */
|
|
11
|
+
let navDragVersion = 0;
|
|
12
|
+
/** The element the drag started from, when we were given one — so its
|
|
13
|
+
* `dragend` is heard even after a nav refresh detaches it mid-drag. */
|
|
14
|
+
let navDragSource = null;
|
|
15
|
+
let navDragWatchBound = false;
|
|
16
|
+
function publish() {
|
|
17
|
+
navDragVersion += 1;
|
|
18
|
+
for (const listener of navDragListeners)
|
|
19
|
+
listener();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Ends whatever drag is in flight.
|
|
23
|
+
*
|
|
24
|
+
* Bound to the window for `dragend` (a drop or a cancel) and `drop` (a
|
|
25
|
+
* source outside this document, which has no `dragend` here at all), and to
|
|
26
|
+
* the source element itself, whose own `dragend` is the only one left once a
|
|
27
|
+
* nav refresh has detached it.
|
|
28
|
+
*
|
|
29
|
+
* There is deliberately no `dragleave` ending. A relatedTarget-less
|
|
30
|
+
* `dragleave` is the only signal a drag leaving the window gives, and it is
|
|
31
|
+
* the same signal whether the drag is over or merely outside — so it ended
|
|
32
|
+
* live in-page drags, collapsing every zone for the rest of the gesture. It
|
|
33
|
+
* existed for drags with no `dragend` in this document, and none of those
|
|
34
|
+
* opens a reservation now.
|
|
35
|
+
*/
|
|
36
|
+
export function endNavDrag() {
|
|
37
|
+
navDragSource = null;
|
|
38
|
+
if (navDragTypes.size === 0)
|
|
39
|
+
return;
|
|
40
|
+
navDragTypes.clear();
|
|
41
|
+
publish();
|
|
42
|
+
}
|
|
43
|
+
function bindNavDragWatch() {
|
|
44
|
+
if (navDragWatchBound || typeof window === "undefined")
|
|
45
|
+
return;
|
|
46
|
+
navDragWatchBound = true;
|
|
47
|
+
window.addEventListener("dragend", endNavDrag, true);
|
|
48
|
+
window.addEventListener("drop", endNavDrag, true);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Called from `dragstart` on a nav row, with the MIME type that row wrote its
|
|
52
|
+
* payload under and the element it started from.
|
|
53
|
+
*
|
|
54
|
+
* The type ADDS rather than replaces: `dragstart` bubbles through every
|
|
55
|
+
* draggable ancestor, and each one's type belongs to the same gesture.
|
|
56
|
+
*
|
|
57
|
+
* The source is held so its `dragend` can be heard even after a nav refresh
|
|
58
|
+
* has removed the row mid-drag. A detached node still receives its own
|
|
59
|
+
* `dragend`, but the event reaches no window listener from there — so
|
|
60
|
+
* without this the reservation would stay open, every zone expanded, until
|
|
61
|
+
* some later drag happened to finish elsewhere.
|
|
62
|
+
*/
|
|
63
|
+
export function beginNavDrag(type, source) {
|
|
64
|
+
bindNavDragWatch();
|
|
65
|
+
if (source && source !== navDragSource) {
|
|
66
|
+
navDragSource = source;
|
|
67
|
+
source.addEventListener("dragend", endNavDrag, { once: true });
|
|
68
|
+
}
|
|
69
|
+
if (navDragTypes.has(type))
|
|
70
|
+
return;
|
|
71
|
+
navDragTypes.add(type);
|
|
72
|
+
publish();
|
|
73
|
+
}
|
|
74
|
+
export function subscribeNavDrag(listener) {
|
|
75
|
+
bindNavDragWatch();
|
|
76
|
+
navDragListeners.add(listener);
|
|
77
|
+
return () => navDragListeners.delete(listener);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The snapshot `useSyncExternalStore` compares: a version, bumped on every
|
|
81
|
+
* change. A primitive, so identity comparison is meaningful, and carrying no
|
|
82
|
+
* encoding of its own — see the note about `|` above.
|
|
83
|
+
*/
|
|
84
|
+
export function getNavDragVersion() {
|
|
85
|
+
return navDragVersion;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Would a zone with this `dnd.type` accept the live drag?
|
|
89
|
+
*
|
|
90
|
+
* Reserving space and painting a label is a promise that a drop will land,
|
|
91
|
+
* so only a zone that would actually take this drag may do either. "A drag
|
|
92
|
+
* is happening" was not enough: two modules with different `dnd.type`s
|
|
93
|
+
* opened each other's zones.
|
|
94
|
+
*
|
|
95
|
+
* Files need no case here. Only a nav row's `dragstart` opens a reservation
|
|
96
|
+
* and those carry an entity, so no zone ever opens for a file drag whatever
|
|
97
|
+
* it is configured to accept. It still HANDLES one — the resting strip takes
|
|
98
|
+
* a file drop like any other target — and the container's own row advertises
|
|
99
|
+
* that same drop at full size.
|
|
100
|
+
*/
|
|
101
|
+
export function navDragAccepts(zone) {
|
|
102
|
+
return zone.acceptsEntities && navDragTypes.has(zone.type);
|
|
103
|
+
}
|
|
104
|
+
/** Test seam — module-level state outlives a test file otherwise. */
|
|
105
|
+
export function __resetNavDragForTest() {
|
|
106
|
+
navDragTypes.clear();
|
|
107
|
+
navDragSource = null;
|
|
108
|
+
navDragVersion = 0;
|
|
9
109
|
}
|
|
@@ -480,6 +480,13 @@ export const FOUNDRY_VIOLET_THEME = {
|
|
|
480
480
|
border: "oklch(92% 0.008 290)",
|
|
481
481
|
input: "oklch(92% 0.008 290)",
|
|
482
482
|
ring: "oklch(43% 0.24 286)",
|
|
483
|
+
heading: "var(--primary)",
|
|
484
|
+
contentHeading1: "var(--primary)",
|
|
485
|
+
contentHeading2: "var(--foreground)",
|
|
486
|
+
contentHeading3: "var(--foreground)",
|
|
487
|
+
contentHeading4: "var(--foreground)",
|
|
488
|
+
contentHeading5: "var(--foreground)",
|
|
489
|
+
contentHeading6: "var(--foreground)",
|
|
483
490
|
},
|
|
484
491
|
sidebar: {
|
|
485
492
|
light: {
|
|
@@ -489,8 +496,8 @@ export const FOUNDRY_VIOLET_THEME = {
|
|
|
489
496
|
sidebarPrimaryForeground: "oklch(100% 0 0)",
|
|
490
497
|
sidebarAccent: "color-mix(in srgb, var(--primary) 4%, var(--background) 96%)",
|
|
491
498
|
sidebarAccentForeground: "color-mix(in srgb, var(--foreground) 92%, var(--primary) 8%)",
|
|
492
|
-
sidebarSelected: "var(--
|
|
493
|
-
sidebarSelectedForeground: "var(--
|
|
499
|
+
sidebarSelected: "var(--sidebar)",
|
|
500
|
+
sidebarSelectedForeground: "var(--sidebar-primary)",
|
|
494
501
|
sidebarIcon: "color-mix(in srgb, var(--sidebar-foreground) 60%, transparent)",
|
|
495
502
|
sidebarBorder: "color-mix(in srgb, var(--border) 96%, var(--foreground) 4%)",
|
|
496
503
|
sidebarRing: "oklch(43% 0.24 286)",
|
|
@@ -502,8 +509,8 @@ export const FOUNDRY_VIOLET_THEME = {
|
|
|
502
509
|
sidebarPrimaryForeground: "oklch(0.98 0 0)",
|
|
503
510
|
sidebarAccent: "color-mix(in srgb, var(--primary) 8%, var(--background) 92%)",
|
|
504
511
|
sidebarAccentForeground: "color-mix(in srgb, var(--foreground) 90%, white 10%)",
|
|
505
|
-
sidebarSelected: "
|
|
506
|
-
sidebarSelectedForeground: "var(--
|
|
512
|
+
sidebarSelected: "var(--sidebar)",
|
|
513
|
+
sidebarSelectedForeground: "var(--sidebar-primary)",
|
|
507
514
|
sidebarIcon: "color-mix(in srgb, var(--sidebar-foreground) 60%, transparent)",
|
|
508
515
|
sidebarBorder: "color-mix(in srgb, var(--border) 90%, white 10%)",
|
|
509
516
|
sidebarRing: "oklch(0.72 0.18 286)",
|
|
@@ -529,6 +536,13 @@ export const FOUNDRY_VIOLET_THEME = {
|
|
|
529
536
|
border: "oklch(1 0.01 286 / 12%)",
|
|
530
537
|
input: "oklch(1 0.01 286 / 15%)",
|
|
531
538
|
ring: "oklch(0.72 0.18 286)",
|
|
539
|
+
heading: "var(--primary)",
|
|
540
|
+
contentHeading1: "var(--primary)",
|
|
541
|
+
contentHeading2: "var(--foreground)",
|
|
542
|
+
contentHeading3: "var(--foreground)",
|
|
543
|
+
contentHeading4: "var(--foreground)",
|
|
544
|
+
contentHeading5: "var(--foreground)",
|
|
545
|
+
contentHeading6: "var(--foreground)",
|
|
532
546
|
},
|
|
533
547
|
typography: {
|
|
534
548
|
bodyFont: '"Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
@@ -7,7 +7,7 @@ export declare function ConversationHistory({ groups, workspaceId, onClose, show
|
|
|
7
7
|
showBack?: boolean;
|
|
8
8
|
welcome?: boolean;
|
|
9
9
|
compact?: boolean;
|
|
10
|
-
/** The global start page shows
|
|
10
|
+
/** The global start page shows recent and pinned chats below the composer. */
|
|
11
11
|
home?: boolean;
|
|
12
12
|
onViewAll?: () => void;
|
|
13
13
|
/** Current content entity. Compact homes show only an exact match unless this is the workspace root. */
|
|
@@ -5,6 +5,7 @@ import { Fragment, useEffect, useState } from "react";
|
|
|
5
5
|
import { Link } from "react-router";
|
|
6
6
|
import { ArrowLeft, MessageSquare, MoreHorizontal, Pin, Loader2, ChevronDown, Check, MessagesSquare, PauseCircle, Copy, } from "lucide-react";
|
|
7
7
|
import { useChatRuns, isChatRunActive } from "../lib/chat-runs-store.js";
|
|
8
|
+
const HOME_SECTION_LIMIT = 5;
|
|
8
9
|
/** History is a view of existing navigation targets, never a second chat runtime. */
|
|
9
10
|
export function ConversationHistory({ groups, workspaceId, onClose, showBack = true, welcome = false, compact = false, home = false, onViewAll, entityId, rootContext = false, }) {
|
|
10
11
|
const [loadingTo, setLoadingTo] = useState(null);
|
|
@@ -76,8 +77,12 @@ export function ConversationHistory({ groups, workspaceId, onClose, showBack = t
|
|
|
76
77
|
}
|
|
77
78
|
return (active(b) - active(a) || Number(Boolean(b.meta?.pinned)) - Number(Boolean(a.meta?.pinned)));
|
|
78
79
|
});
|
|
79
|
-
const homeRecentItems = home
|
|
80
|
-
|
|
80
|
+
const homeRecentItems = home
|
|
81
|
+
? items.filter((item) => !item.meta?.pinned).slice(0, HOME_SECTION_LIMIT)
|
|
82
|
+
: [];
|
|
83
|
+
const homePinnedItems = home
|
|
84
|
+
? items.filter((item) => item.meta?.pinned).slice(0, HOME_SECTION_LIMIT)
|
|
85
|
+
: [];
|
|
81
86
|
const homeHasItems = homeRecentItems.length > 0 || homePinnedItems.length > 0;
|
|
82
87
|
const visibleItems = compact ? items.slice(0, 3) : items;
|
|
83
88
|
const activeItems = visibleItems.filter((item) => {
|
|
@@ -105,9 +110,9 @@ export function ConversationHistory({ groups, workspaceId, onClose, showBack = t
|
|
|
105
110
|
? "w-full"
|
|
106
111
|
: compact
|
|
107
112
|
? "w-full"
|
|
108
|
-
: "mx-auto flex min-h-0 w-full max-w-3xl flex-1 flex-col px-4 pb-4", children: [welcome && (_jsxs("div", { className: "pb-6 pt-8", children: [_jsx("h1", { className: "text-xl font-semibold", children: "How can I help?" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Start something new or continue a recent chat." })] })), showBack && (_jsxs("button", { onClick: onClose, className: "mb-4 flex items-center gap-2 py-2 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { size: 14 }), " Back to chat"] })), (!compact || items.length > 0) && (
|
|
109
|
-
? "mb-2 mt-5 flex items-center justify-
|
|
110
|
-
: "mb-2 mt-2 flex items-center justify-between", children:
|
|
113
|
+
: "mx-auto flex min-h-0 w-full max-w-3xl flex-1 flex-col px-4 pb-4", children: [welcome && (_jsxs("div", { className: "pb-6 pt-8", children: [_jsx("h1", { className: "text-xl font-semibold", children: "How can I help?" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Start something new or continue a recent chat." })] })), showBack && (_jsxs("button", { onClick: onClose, className: "mb-4 flex items-center gap-2 py-2 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { size: 14 }), " Back to chat"] })), (!compact || items.length > 0) && (_jsx("div", { className: home
|
|
114
|
+
? "mb-2 mt-5 flex items-center justify-center px-1 text-center"
|
|
115
|
+
: "mb-2 mt-2 flex items-center justify-between", children: _jsx("h2", { className: compact ? "text-xs font-medium text-muted-foreground" : "text-base font-semibold", children: home ? "Your chats" : compact ? "Recent chats" : "Chat history" }) })), Boolean(workspaceId) && !compact && (_jsx("div", { className: "mb-3", children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", "aria-label": "History filter", className: "w-full justify-between font-normal shadow-none", children: [_jsx("span", { className: "truncate", children: all ? "All chats" : "Last used in this workspace" }), _jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" })] }) }), _jsx(DropdownMenuContent, { align: "start", className: "w-[var(--radix-dropdown-menu-trigger-width)]", children: [
|
|
111
116
|
{ value: false, label: "Last used in this workspace" },
|
|
112
117
|
{ value: true, label: "All chats" },
|
|
113
118
|
].map((option) => (_jsxs(DropdownMenuItem, { role: "menuitemradio", "aria-checked": all === option.value, onSelect: () => setAll(option.value), children: [_jsx("span", { className: "flex-1", children: option.label }), all === option.value && _jsx(Check, { className: "size-4", "aria-hidden": "true" })] }, option.label))) })] }) })), !compact && (_jsx(Input, { "aria-label": "Search chats", placeholder: "Search chats", value: search, onChange: (event) => setSearch(event.target.value), className: "mb-3" })), _jsx("span", { className: "sr-only", role: "status", children: copiedId ? "Chat ID copied" : "" }), loadError && (_jsx("p", { role: "alert", className: "mb-2 text-xs text-destructive", children: loadError })), _jsxs("div", { className: home
|
|
@@ -154,7 +159,7 @@ export function ConversationHistory({ groups, workspaceId, onClose, showBack = t
|
|
|
154
159
|
onClose();
|
|
155
160
|
}, "data-conversation-row": true, className: home
|
|
156
161
|
? "flex min-w-0 flex-1 items-center gap-3 rounded-lg px-2 py-2 text-left text-sm focus-visible:outline-2 focus-visible:outline-primary"
|
|
157
|
-
: "flex min-w-0 flex-1 items-center gap-2 rounded-lg px-2 py-1.5 text-sm hover:bg-muted focus-visible:outline-2 focus-visible:outline-primary", children: [loadingTo === item.to ? (_jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground", "aria-label": "Opening chat" })) : runFor(item)?.status === "running" ? (_jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-primary motion-reduce:animate-none", "aria-hidden": true })) : runFor(item)?.status === "waiting" ? (_jsx(PauseCircle, { className: "size-4 shrink-0 text-primary", "aria-hidden": true })) : item.meta?.pinned ? (_jsx(Pin, { className: "size-4 shrink-0 text-muted-foreground", "aria-label": "Pinned chat" })) : (_jsx(MessageSquare, { className: "size-4 shrink-0 text-muted-foreground" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate", children: item.label }), " ", runFor(item) && isChatRunActive(runFor(item)) ? (_jsx("span", { className: "block text-xs text-primary", children: runFor(item)?.status === "waiting" ? "Waiting for you" : "Running" })) : ((!compact || rootContext) && (_jsx("span", { className: "block truncate text-xs text-muted-foreground", children: !all &&
|
|
162
|
+
: "flex min-w-0 flex-1 items-center gap-2 rounded-lg px-2 py-1.5 text-sm hover:bg-muted focus-visible:outline-2 focus-visible:outline-primary", children: [loadingTo === item.to ? (_jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground", "aria-label": "Opening chat" })) : runFor(item)?.status === "running" ? (_jsx(Loader2, { className: "size-4 shrink-0 animate-spin text-primary motion-reduce:animate-none", "aria-hidden": true })) : runFor(item)?.status === "waiting" ? (_jsx(PauseCircle, { className: "size-4 shrink-0 text-primary", "aria-hidden": true })) : item.meta?.pinned ? (_jsx(Pin, { className: "size-4 shrink-0 text-muted-foreground", "aria-label": "Pinned chat" })) : (_jsx(MessageSquare, { className: "size-4 shrink-0 text-muted-foreground" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate", children: item.label }), " ", runFor(item) && isChatRunActive(runFor(item)) ? (_jsx("span", { className: "block text-xs text-primary", children: runFor(item)?.status === "waiting" ? "Waiting for you" : "Running" })) : ((home || !compact || rootContext) && (_jsx("span", { className: "block truncate text-xs text-muted-foreground", children: !all &&
|
|
158
163
|
typeof item.meta?.previewEntityName === "string" &&
|
|
159
164
|
item.meta.previewEntityName
|
|
160
165
|
? `Last at ${item.meta.previewEntityName}`
|
|
@@ -181,5 +186,5 @@ export function ConversationHistory({ groups, workspaceId, onClose, showBack = t
|
|
|
181
186
|
.catch(() => setLoadError(`Could not copy. Chat ID: ${decodeURIComponent(id)}`));
|
|
182
187
|
}, children: [_jsx(Copy, { className: "size-4" }), "Copy chat ID"] }), Boolean(item.actions?.length) && _jsx(DropdownMenuSeparator, {}), item.actions?.map((action) => (_jsxs(Fragment, { children: [action.separator && _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onSelect: () => {
|
|
183
188
|
void action.handler();
|
|
184
|
-
}, className: action.destructive ? "text-destructive" : undefined, children: [_jsx(action.icon, { className: "mr-2 size-4" }), action.label] })] }, action.label)))] })] })] }, item.to)))] }, section.label))), items.length === 0 && !compact && (_jsx("p", { className: "py-3 text-sm text-muted-foreground", children: search ? "No matching chats" : "No chats yet" }))] }), compact && onViewAll && !home && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: onViewAll, className: "mt-1 h-9 w-fit gap-2 px-2 text-sm font-normal text-muted-foreground [@media(pointer:coarse)]:min-h-11", children: [_jsx(MessagesSquare, { className: "size-4", "aria-hidden": true }), "Chat history"] }))] }));
|
|
189
|
+
}, className: action.destructive ? "text-destructive" : undefined, children: [_jsx(action.icon, { className: "mr-2 size-4" }), action.label] })] }, action.label)))] })] })] }, item.to)))] }, section.label))), items.length === 0 && !compact && (_jsx("p", { className: "py-3 text-sm text-muted-foreground", children: search ? "No matching chats" : "No chats yet" }))] }), home && onViewAll && (_jsx("div", { className: "mt-2 flex justify-center", children: _jsxs(Button, { variant: "ghost", size: "sm", onClick: onViewAll, className: "h-9 w-fit gap-2 px-2 text-sm font-normal text-muted-foreground [@media(pointer:coarse)]:min-h-11", children: [_jsx(MessagesSquare, { className: "size-4", "aria-hidden": true }), "View all conversations"] }) })), compact && onViewAll && !home && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: onViewAll, className: "mt-1 h-9 w-fit gap-2 px-2 text-sm font-normal text-muted-foreground [@media(pointer:coarse)]:min-h-11", children: [_jsx(MessagesSquare, { className: "size-4", "aria-hidden": true }), "Chat history"] }))] }));
|
|
185
190
|
}
|
|
@@ -25,10 +25,10 @@ export function SelectionBridge({ children }) {
|
|
|
25
25
|
top: rect.top - bounds.top,
|
|
26
26
|
height: rect.height,
|
|
27
27
|
left: rect.left - bounds.left + 0.5,
|
|
28
|
-
rowRight: rect.right - bounds.left - 1,
|
|
29
28
|
width: bounds.width,
|
|
30
29
|
navHeight: bounds.height,
|
|
31
30
|
surfaceWidth: node.nextElementSibling?.getBoundingClientRect().width ?? 0,
|
|
31
|
+
radius: Math.min(16, rect.height / 2, Number.parseFloat(getComputedStyle(row).borderTopLeftRadius) || 0),
|
|
32
32
|
}
|
|
33
33
|
: null);
|
|
34
34
|
});
|
|
@@ -46,6 +46,10 @@ export function SelectionBridge({ children }) {
|
|
|
46
46
|
attributes: true,
|
|
47
47
|
attributeFilter: ["class", "style", "data-active-nav-row"],
|
|
48
48
|
});
|
|
49
|
+
mutation?.observe(document.documentElement, {
|
|
50
|
+
attributes: true,
|
|
51
|
+
attributeFilter: ["class", "style"],
|
|
52
|
+
});
|
|
49
53
|
node.addEventListener("scroll", measure, true);
|
|
50
54
|
measure();
|
|
51
55
|
return () => {
|
|
@@ -55,27 +59,55 @@ export function SelectionBridge({ children }) {
|
|
|
55
59
|
node.removeEventListener("scroll", measure, true);
|
|
56
60
|
};
|
|
57
61
|
}, []);
|
|
58
|
-
return (_jsxs("div", { ref: root, "data-connected-navigation": anchor ? "true" : undefined, className: "relative flex shrink-0",
|
|
62
|
+
return (_jsxs("div", { ref: root, "data-connected-navigation": anchor ? "true" : undefined, className: "relative flex shrink-0", style: {
|
|
63
|
+
"--connected-row-radius": anchor ? `${anchor.radius}px` : undefined,
|
|
64
|
+
}, children: [_jsx("style", { children: `
|
|
59
65
|
[data-connected-navigation="true"] + [data-workbench-surface]::after { display: none; }
|
|
60
66
|
.workbench-preview [data-sidebar-surface="desktop"] nav { padding-right: 16px; }
|
|
61
67
|
.workbench-preview [data-sidebar-surface="desktop"] { border-right-color: transparent; }
|
|
62
68
|
[data-connected-navigation="true"] [data-sidebar-surface="desktop"] [data-active-nav-row="true"] {
|
|
63
|
-
|
|
69
|
+
background: var(--background);
|
|
70
|
+
border-color: transparent;
|
|
71
|
+
border-top-right-radius: 0;
|
|
72
|
+
border-bottom-right-radius: 0;
|
|
73
|
+
}
|
|
74
|
+
[data-connected-navigation="true"] [data-sidebar-surface="desktop"] [data-active-nav-row="true"]::after {
|
|
75
|
+
content: "";
|
|
76
|
+
position: absolute;
|
|
77
|
+
z-index: 0;
|
|
78
|
+
inset-block: 0;
|
|
79
|
+
left: 100%;
|
|
80
|
+
width: 16px;
|
|
81
|
+
background: var(--background);
|
|
82
|
+
}
|
|
83
|
+
[data-connected-navigation="true"] [data-sidebar-surface="desktop"] [data-active-nav-row="true"]::before {
|
|
84
|
+
content: "";
|
|
85
|
+
position: absolute;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
inset-block: 5px;
|
|
88
|
+
left: 5px;
|
|
89
|
+
right: -10px;
|
|
90
|
+
border-radius: max(0px, calc(var(--connected-row-radius) - 5px));
|
|
91
|
+
background: var(--sidebar-selected);
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
[data-connected-navigation="true"] [data-sidebar-surface="desktop"] [data-active-nav-row="true"] > * {
|
|
95
|
+
z-index: 2;
|
|
64
96
|
}
|
|
65
|
-
` }), children, anchor && (_jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute z-20", style: { inset: 0, width: anchor.width + anchor.surfaceWidth, height: anchor.navHeight }, viewBox: `0 0 ${anchor.width + anchor.surfaceWidth} ${anchor.navHeight}`, children: _jsx("path", { fill: "none", stroke: "var(--sidebar-ring)", strokeOpacity: 0.6, strokeWidth: 1, d: `M ${anchor.width +
|
|
66
|
-
H ${anchor.width + anchor.surfaceWidth -
|
|
67
|
-
Q ${anchor.width + anchor.surfaceWidth - 0.5},0.5 ${anchor.width + anchor.surfaceWidth - 0.5}
|
|
68
|
-
V ${anchor.navHeight -
|
|
69
|
-
Q ${anchor.width + anchor.surfaceWidth - 0.5},${anchor.navHeight - 0.5} ${anchor.width + anchor.surfaceWidth -
|
|
70
|
-
H ${anchor.width +
|
|
71
|
-
Q ${anchor.width - 0.5},${anchor.navHeight - 0.5} ${anchor.width - 0.5},${anchor.navHeight -
|
|
72
|
-
V ${anchor.top + anchor.height +
|
|
73
|
-
Q ${anchor.width - 0.5},${anchor.top + anchor.height - 0.5} ${anchor.width -
|
|
74
|
-
H ${anchor.left +
|
|
75
|
-
Q ${anchor.left},${anchor.top + anchor.height - 0.5} ${anchor.left},${anchor.top + anchor.height -
|
|
76
|
-
V ${anchor.top +
|
|
77
|
-
Q ${anchor.left},${anchor.top + 0.5} ${anchor.left +
|
|
78
|
-
H ${anchor.width -
|
|
79
|
-
Q ${anchor.width - 0.5},${anchor.top + 0.5} ${anchor.width - 0.5},${anchor.top -
|
|
80
|
-
V
|
|
97
|
+
` }), children, anchor && (_jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute z-20", style: { inset: 0, width: anchor.width + anchor.surfaceWidth, height: anchor.navHeight }, viewBox: `0 0 ${anchor.width + anchor.surfaceWidth} ${anchor.navHeight}`, children: _jsx("path", { fill: "none", stroke: "var(--sidebar-ring)", strokeOpacity: 0.6, strokeWidth: 1, d: `M ${anchor.width + anchor.radius},0.5
|
|
98
|
+
H ${anchor.width + anchor.surfaceWidth - anchor.radius}
|
|
99
|
+
Q ${anchor.width + anchor.surfaceWidth - 0.5},0.5 ${anchor.width + anchor.surfaceWidth - 0.5},${anchor.radius}
|
|
100
|
+
V ${anchor.navHeight - anchor.radius}
|
|
101
|
+
Q ${anchor.width + anchor.surfaceWidth - 0.5},${anchor.navHeight - 0.5} ${anchor.width + anchor.surfaceWidth - anchor.radius},${anchor.navHeight - 0.5}
|
|
102
|
+
H ${anchor.width + anchor.radius}
|
|
103
|
+
Q ${anchor.width - 0.5},${anchor.navHeight - 0.5} ${anchor.width - 0.5},${anchor.navHeight - anchor.radius}
|
|
104
|
+
V ${anchor.top + anchor.height + anchor.radius}
|
|
105
|
+
Q ${anchor.width - 0.5},${anchor.top + anchor.height - 0.5} ${anchor.width - anchor.radius},${anchor.top + anchor.height - 0.5}
|
|
106
|
+
H ${anchor.left + anchor.radius}
|
|
107
|
+
Q ${anchor.left},${anchor.top + anchor.height - 0.5} ${anchor.left},${anchor.top + anchor.height - anchor.radius}
|
|
108
|
+
V ${anchor.top + anchor.radius}
|
|
109
|
+
Q ${anchor.left},${anchor.top + 0.5} ${anchor.left + anchor.radius},${anchor.top + 0.5}
|
|
110
|
+
H ${anchor.width - anchor.radius}
|
|
111
|
+
Q ${anchor.width - 0.5},${anchor.top + 0.5} ${anchor.width - 0.5},${anchor.top - anchor.radius}
|
|
112
|
+
V ${anchor.radius} Q ${anchor.width - 0.5},0.5 ${anchor.width + anchor.radius},0.5 Z` }) }))] }));
|
|
81
113
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iloveagents/foundry-web-ui",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React agent UI core for Foundry UI — chat, composer, AG-UI adapter for assistant-ui, tool cards, panels, sidebar, theme runtime, and UI stores.",
|
|
6
6
|
"keywords": [
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"recharts": "^3.10.1",
|
|
81
81
|
"remark-gfm": "^4.0.0",
|
|
82
82
|
"tailwind-merge": "^3.5.0",
|
|
83
|
-
"@iloveagents/foundry-
|
|
84
|
-
"@iloveagents/foundry-
|
|
83
|
+
"@iloveagents/foundry-agent": "^0.33.3",
|
|
84
|
+
"@iloveagents/foundry-web-primitives": "^0.33.3"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@ag-ui/client": "^0.0.52",
|