@artooi/ag-ui-web-component 0.47.0 → 0.48.0
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/CHANGELOG.md +135 -1
- package/README.md +171 -15
- package/dist/ag-ui-web-component.bundle.js +591 -176
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +27 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +36 -3
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/core/events/bar_height_detail.d.ts +6 -0
- package/dist/core/events/bar_height_detail.d.ts.map +1 -0
- package/dist/dom/highlight_overlay.d.ts +2 -1
- package/dist/dom/highlight_overlay.d.ts.map +1 -1
- package/dist/dom/ring_color.d.ts +8 -0
- package/dist/dom/ring_color.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +926 -52
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +8 -1
- package/dist/tools/chat_surface_tools.d.ts.map +1 -1
- package/dist/ui/history/conversation_history.d.ts +16 -3
- package/dist/ui/history/conversation_history.d.ts.map +1 -1
- package/dist/ui/placement/collapse_styles.d.ts.map +1 -1
- package/dist/ui/placement/dock_styles.d.ts +2 -0
- package/dist/ui/placement/dock_styles.d.ts.map +1 -0
- package/dist/ui/placement/panel_placement.d.ts +11 -0
- package/dist/ui/placement/panel_placement.d.ts.map +1 -1
- package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
- package/dist/ui/placement/surface_for.d.ts +21 -17
- package/dist/ui/placement/surface_for.d.ts.map +1 -1
- package/dist/ui/progress/tool_group_styles.d.ts.map +1 -1
- package/dist/ui/progress/tool_step_styles.d.ts.map +1 -1
- package/dist/ui/shell/build_launcher.d.ts +15 -4
- package/dist/ui/shell/build_launcher.d.ts.map +1 -1
- package/dist/ui/shell/token_styles.d.ts.map +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/themes/scheme_styles.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +30 -2
- package/src/core/ag_ui_chat.ts +326 -26
- package/src/core/events/bar_height_detail.ts +5 -0
- package/src/dom/highlight_overlay.ts +2 -1
- package/src/dom/ring_color.ts +141 -4
- package/src/index.ts +4 -0
- package/src/tools/chat_surface_tools.ts +18 -8
- package/src/ui/history/conversation_history.ts +51 -7
- package/src/ui/placement/collapse_styles.ts +248 -2
- package/src/ui/placement/dock_styles.ts +91 -0
- package/src/ui/placement/panel_placement.ts +71 -3
- package/src/ui/placement/small_viewport_styles.ts +9 -0
- package/src/ui/placement/surface_for.ts +29 -18
- package/src/ui/progress/tool_group_styles.ts +49 -0
- package/src/ui/progress/tool_step_styles.ts +5 -3
- package/src/ui/shell/build_launcher.ts +32 -5
- package/src/ui/shell/token_styles.ts +28 -1
- package/src/ui/styles.ts +4 -0
- package/src/ui/themes/scheme_styles.ts +10 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Where a docked panel stands: above the bottom bar under collapse-to="bar",
|
|
2
|
+
// above the host's anchor under collapse-to="none". Token restatements only;
|
|
3
|
+
// the bar's own shape is in the collapse module, after the launcher it
|
|
4
|
+
// restyles.
|
|
5
|
+
export const DOCK_STYLES: string = `
|
|
6
|
+
/* ── Dock ────────────────────────────────────────────────────────────────────
|
|
7
|
+
A docked panel rises from the bar along the bottom edge, or from the host's
|
|
8
|
+
own control, instead of resting in a corner of its own. The element stamps
|
|
9
|
+
what it collapses to as data-collapsed-to, and writes where the dock is as
|
|
10
|
+
two private properties: --_dock-bottom, the bar's height or the distance
|
|
11
|
+
from the layout viewport's bottom to the anchor's top, and --_dock-right,
|
|
12
|
+
the anchor's distance from the right edge. Both are read with a 0px
|
|
13
|
+
fallback, so a panel measured before the bar has laid out stands where a
|
|
14
|
+
bar of no height would put it.
|
|
15
|
+
|
|
16
|
+
The cap follows the token block's rule: what the host left free less the
|
|
17
|
+
one gutter the anchored edge spends, so a panel grown to the cap runs from
|
|
18
|
+
just above the bar to the far edge. A host therefore needs no inset to keep
|
|
19
|
+
the panel off its bar; --ag-ui-inset still wins when set.
|
|
20
|
+
|
|
21
|
+
These blocks restate tokens and nothing else, and sit ahead of the phone
|
|
22
|
+
block at no more weight than it has, so on a phone the panel is the screen
|
|
23
|
+
as every other placement is there. The bar measures 0 while it is not drawn
|
|
24
|
+
(a phone with the panel open), which is what lets the side and full shapes
|
|
25
|
+
below fall back to the whole height without a phone rule of their own.
|
|
26
|
+
|
|
27
|
+
Each placement keeps its own side and shape; only the bottom edge moves. */
|
|
28
|
+
:host([data-collapsed-to="bar"]) {
|
|
29
|
+
--_inset: var(
|
|
30
|
+
--ag-ui-inset,
|
|
31
|
+
auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
|
|
32
|
+
calc(
|
|
33
|
+
var(--_edge-gutter) + var(--_viewport-inset-bottom) +
|
|
34
|
+
max(var(--_dock-bottom, 0px), var(--_keyboard-inset))
|
|
35
|
+
)
|
|
36
|
+
auto
|
|
37
|
+
);
|
|
38
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - var(--_edge-gutter)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:host([data-collapsed-to="bar"][placement="bottom-left"]) {
|
|
42
|
+
--_inset: var(
|
|
43
|
+
--ag-ui-inset,
|
|
44
|
+
auto auto
|
|
45
|
+
calc(
|
|
46
|
+
var(--_edge-gutter) + var(--_viewport-inset-bottom) +
|
|
47
|
+
max(var(--_dock-bottom, 0px), var(--_keyboard-inset))
|
|
48
|
+
)
|
|
49
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-left))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* The column and the full-screen panel stop at the bar rather than covering
|
|
54
|
+
it, so the bar stays where the user minimises from. */
|
|
55
|
+
:host([data-collapsed-to="bar"][placement="side"]) {
|
|
56
|
+
--_inset: var(
|
|
57
|
+
--ag-ui-inset,
|
|
58
|
+
calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right)
|
|
59
|
+
calc(var(--_viewport-inset-bottom) + var(--_dock-bottom, 0px)) auto
|
|
60
|
+
);
|
|
61
|
+
--_height: var(--ag-ui-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
62
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
:host([data-collapsed-to="bar"][placement="full"]) {
|
|
66
|
+
--_inset: var(
|
|
67
|
+
--ag-ui-inset,
|
|
68
|
+
calc(var(--_viewport-inset-top) + var(--_keyboard-inset-top)) var(--_viewport-inset-right)
|
|
69
|
+
calc(var(--_viewport-inset-bottom) + var(--_dock-bottom, 0px)) var(--_viewport-inset-left)
|
|
70
|
+
);
|
|
71
|
+
--_height: var(--ag-ui-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
72
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_dock-bottom, 0px)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The host's own bar: the panel stands the anchor gap (8px unless the host
|
|
76
|
+
says otherwise) above the anchor and lines up with its right edge. Only
|
|
77
|
+
once the element found the anchor; until then, and when it never does, the
|
|
78
|
+
placement's own corner stands.
|
|
79
|
+
|
|
80
|
+
The cap spends the larger of the gap and the gutter. The gutter alone is
|
|
81
|
+
what this always spent, so the default is unchanged; but a host that opens
|
|
82
|
+
the gap past the gutter to clear a tall toolbar would otherwise push a
|
|
83
|
+
panel grown to the cap off the top of the window by the difference. */
|
|
84
|
+
:host([data-collapsed-to="none"][data-dock-anchored]) {
|
|
85
|
+
--_inset: var(--ag-ui-inset, auto var(--_dock-right, 0px) calc(var(--_dock-bottom, 0px) + var(--_anchor-gap)) auto);
|
|
86
|
+
--_max-height: var(
|
|
87
|
+
--ag-ui-max-height,
|
|
88
|
+
calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - max(var(--_edge-gutter), var(--_anchor-gap)))
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type ResizeGrip,
|
|
23
23
|
type ResizeSize,
|
|
24
24
|
} from "./resize_handle.js";
|
|
25
|
-
import { surfaceFor } from "./surface_for.js";
|
|
25
|
+
import { type Surface, surfaceFor } from "./surface_for.js";
|
|
26
26
|
|
|
27
27
|
/** Per-tab persistence key for a dragged panel size. */
|
|
28
28
|
const SIZE_KEY = "ag-ui-chat:size";
|
|
@@ -37,6 +37,15 @@ const SIZE_KEY = "ag-ui-chat:size";
|
|
|
37
37
|
*/
|
|
38
38
|
const DRAG_WIDTH = "--_drag-width";
|
|
39
39
|
const DRAG_HEIGHT = "--_drag-height";
|
|
40
|
+
/**
|
|
41
|
+
* How far above the bottom of the layout viewport a docked panel stands, and
|
|
42
|
+
* how far in from its right edge: the bar's height, or the host anchor's top
|
|
43
|
+
* and right. Private and written inline, like the dragged size, and read only
|
|
44
|
+
* by the dock rules; never an inline --_inset, which would outrank the phone
|
|
45
|
+
* block and keep a docked panel off full-bleed on a phone.
|
|
46
|
+
*/
|
|
47
|
+
const DOCK_BOTTOM = "--_dock-bottom";
|
|
48
|
+
const DOCK_RIGHT = "--_dock-right";
|
|
40
49
|
|
|
41
50
|
/** Per-tab persistence key for a dragged launcher position. */
|
|
42
51
|
const LAUNCHER_KEY = "ag-ui-chat:launcher";
|
|
@@ -309,6 +318,7 @@ export class PanelPlacement {
|
|
|
309
318
|
const fullBleed = coversViewport(box, viewport);
|
|
310
319
|
return {
|
|
311
320
|
placement: this.#host.element.getAttribute("placement"),
|
|
321
|
+
collapseTo: this.#surface().collapseTo,
|
|
312
322
|
collapsed: this.#host.collapsed(),
|
|
313
323
|
collapsible: this.#host.collapsible(),
|
|
314
324
|
movable: this.#launcherDraggable() && !fullBleed,
|
|
@@ -425,7 +435,7 @@ export class PanelPlacement {
|
|
|
425
435
|
* per interaction, because `placement` is a live attribute.
|
|
426
436
|
*/
|
|
427
437
|
#resizeAxis(): ResizeAxis {
|
|
428
|
-
return
|
|
438
|
+
return this.#surface().resizeAxis;
|
|
429
439
|
}
|
|
430
440
|
|
|
431
441
|
/**
|
|
@@ -567,6 +577,49 @@ export class PanelPlacement {
|
|
|
567
577
|
}
|
|
568
578
|
}
|
|
569
579
|
|
|
580
|
+
/** The surface the live placement and collapse-to attributes describe. */
|
|
581
|
+
#surface(): Surface {
|
|
582
|
+
const element = this.#host.element;
|
|
583
|
+
return surfaceFor(element.getAttribute("placement"), element.getAttribute("collapse-to"));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** Whether the panel rises from the bottom bar or the host's anchor. */
|
|
587
|
+
#docked(): boolean {
|
|
588
|
+
const target = this.#surface().collapseTo;
|
|
589
|
+
return target === "bar" || target === "none";
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Write where a docked panel stands, and return the bar's height.
|
|
594
|
+
*
|
|
595
|
+
* Under the bar it is the strip's border box, which is 0 whenever the strip
|
|
596
|
+
* is not drawn (a phone with the panel open, where the panel is the screen).
|
|
597
|
+
* Under a host anchor it is the anchor's top and right, measured against the
|
|
598
|
+
* layout viewport a fixed element is placed in; an anchor of null leaves the
|
|
599
|
+
* panel in its placement's own corner. Every other surface carries neither
|
|
600
|
+
* property, so nothing stale is left behind by a live switch.
|
|
601
|
+
*/
|
|
602
|
+
syncDock(bar: HTMLElement, anchor: Element | null): number {
|
|
603
|
+
const element = this.#host.element;
|
|
604
|
+
const target = this.#surface().collapseTo;
|
|
605
|
+
const height = target === "bar" ? bar.offsetHeight : 0;
|
|
606
|
+
if (target === "bar") {
|
|
607
|
+
element.style.setProperty(DOCK_BOTTOM, `${height}px`);
|
|
608
|
+
} else if (target === "none" && anchor !== null) {
|
|
609
|
+
const rect = anchor.getBoundingClientRect();
|
|
610
|
+
const screen = this.#screen();
|
|
611
|
+
element.style.setProperty(DOCK_BOTTOM, `${Math.max(0, screen.height - rect.top)}px`);
|
|
612
|
+
element.style.setProperty(DOCK_RIGHT, `${Math.max(0, screen.width - rect.right)}px`);
|
|
613
|
+
} else {
|
|
614
|
+
element.style.removeProperty(DOCK_BOTTOM);
|
|
615
|
+
}
|
|
616
|
+
if (target !== "none" || anchor === null) {
|
|
617
|
+
element.style.removeProperty(DOCK_RIGHT);
|
|
618
|
+
}
|
|
619
|
+
element.toggleAttribute("data-dock-anchored", target === "none" && anchor !== null);
|
|
620
|
+
return height;
|
|
621
|
+
}
|
|
622
|
+
|
|
570
623
|
/**
|
|
571
624
|
* Whether this placement lets the launcher be dragged, read per interaction
|
|
572
625
|
* because `placement` is a live attribute. `data-launcher-drag="false"` opts
|
|
@@ -575,7 +628,7 @@ export class PanelPlacement {
|
|
|
575
628
|
#launcherDraggable(): boolean {
|
|
576
629
|
return (
|
|
577
630
|
this.#host.element.getAttribute("data-launcher-drag") !== "false" &&
|
|
578
|
-
|
|
631
|
+
this.#surface().launcherDraggable
|
|
579
632
|
);
|
|
580
633
|
}
|
|
581
634
|
|
|
@@ -1057,6 +1110,21 @@ export class PanelPlacement {
|
|
|
1057
1110
|
*/
|
|
1058
1111
|
#applyResize(grip: ResizeGrip, box: PanelRect): PanelRect {
|
|
1059
1112
|
box = this.#withinViewport(box);
|
|
1113
|
+
if (this.#docked()) {
|
|
1114
|
+
// A docked panel keeps its resize but never its position: the edges it
|
|
1115
|
+
// stands on are the bar's or the anchor's, and a grip on one of them
|
|
1116
|
+
// would write the position below and take the panel off its dock. So
|
|
1117
|
+
// the pinned edges stay where they are and only the free ones follow.
|
|
1118
|
+
const now = this.#host.element.getBoundingClientRect();
|
|
1119
|
+
box = {
|
|
1120
|
+
left: grip.x === this.#anchor.x ? now.left : box.left,
|
|
1121
|
+
right: grip.x === this.#anchor.x ? now.right : box.right,
|
|
1122
|
+
top: grip.y === this.#anchor.y ? now.top : box.top,
|
|
1123
|
+
bottom: grip.y === this.#anchor.y ? now.bottom : box.bottom,
|
|
1124
|
+
};
|
|
1125
|
+
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
1126
|
+
return box;
|
|
1127
|
+
}
|
|
1060
1128
|
this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
|
|
1061
1129
|
if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
|
|
1062
1130
|
return box;
|
|
@@ -57,5 +57,14 @@ export const SMALL_VIEWPORT_STYLES: string = `
|
|
|
57
57
|
:host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
|
|
58
58
|
display: none;
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
/* The bottom bar shows only while collapsed. With the panel open the panel
|
|
62
|
+
is the screen, its header's minimise control is the way back, and a
|
|
63
|
+
second strip fixed to the bottom edge would sit under the keyboard while
|
|
64
|
+
the composer has focus. Hidden by display, so the bar measures 0 and the
|
|
65
|
+
host's padding goes with it. */
|
|
66
|
+
:host([data-collapsed-to="bar"]:not([collapsed]):not([data-small-viewport="off"])) .dock-bar {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
60
69
|
}
|
|
61
70
|
`;
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { ResizeAxis } from "./resize_handle.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* What a collapsed panel shrinks to
|
|
5
|
-
*
|
|
6
|
-
* Only `null` versus not-`null` is read today: every caller asks whether the
|
|
7
|
-
* surface can collapse at all, never which target it collapses to. The three
|
|
8
|
-
* names below therefore describe the surface rather than drive it, and nothing
|
|
9
|
-
* relies on them (changing one fails only the test of this record). The
|
|
10
|
-
* bottom-bar collapse is expected to read or replace them, and a reader that
|
|
11
|
-
* does should come with a test outside this record's own.
|
|
4
|
+
* What a collapsed panel shrinks to, stamped on the element as
|
|
5
|
+
* data-collapsed-to so the stylesheet draws it.
|
|
12
6
|
*
|
|
13
7
|
* - `launcher` -- the round button in a corner.
|
|
14
8
|
* - `rail` -- the slim full-height edge the sidebar docks against; the same
|
|
15
9
|
* launcher element, shaped by the stylesheet.
|
|
16
10
|
* - `header` -- the in-flow collapse: the body hides and the header bar stays
|
|
17
11
|
* in the host's layout, so there is no launcher at all.
|
|
12
|
+
* - `bar` -- `collapse-to="bar"` over a placement that would otherwise
|
|
13
|
+
* collapse to `launcher`: a strip along the bottom edge, the panel rising
|
|
14
|
+
* from its right end.
|
|
15
|
+
* - `none` -- `collapse-to="none"` over any collapsible placement: collapsed
|
|
16
|
+
* is hidden, and the host's own control brings the panel back.
|
|
18
17
|
* - `null` -- no collapsed state. `page` is a dedicated route rather than a
|
|
19
18
|
* panel on someone else's page, so there is no "away" for it to go to:
|
|
20
19
|
* collapsing it left a strip of application chrome fixed over a route with
|
|
@@ -25,7 +24,7 @@ import type { ResizeAxis } from "./resize_handle.js";
|
|
|
25
24
|
* those paths asks this record; the stylesheet covers the one path that
|
|
26
25
|
* never does, an attribute written straight onto the element.
|
|
27
26
|
*/
|
|
28
|
-
export type CollapseTarget = "launcher" | "rail" | "header" | null;
|
|
27
|
+
export type CollapseTarget = "launcher" | "rail" | "header" | "bar" | "none" | null;
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* Everything the script needs to know about one display surface, in one
|
|
@@ -36,11 +35,7 @@ export type CollapseTarget = "launcher" | "rail" | "header" | null;
|
|
|
36
35
|
* under this directory); this is the part of a surface that code decides.
|
|
37
36
|
*/
|
|
38
37
|
export interface Surface {
|
|
39
|
-
/**
|
|
40
|
-
* What collapsing shrinks the panel to; `null` when it cannot collapse. Only
|
|
41
|
-
* the `null` test is authoritative: the non-null names are descriptive (see
|
|
42
|
-
* {@link CollapseTarget}), and nothing branches on which one a surface has.
|
|
43
|
-
*/
|
|
38
|
+
/** What collapsing shrinks the panel to; `null` when it cannot collapse. */
|
|
44
39
|
readonly collapseTo: CollapseTarget;
|
|
45
40
|
/**
|
|
46
41
|
* Whether a gesture may move the launcher. The rest have nowhere to put it:
|
|
@@ -127,9 +122,25 @@ const UNKNOWN: Surface = {
|
|
|
127
122
|
};
|
|
128
123
|
|
|
129
124
|
/**
|
|
130
|
-
* The surface `placement`
|
|
131
|
-
* because
|
|
125
|
+
* The surface `placement` and `collapse-to` describe together. Read per
|
|
126
|
+
* interaction by every caller, because both are live attributes.
|
|
127
|
+
*
|
|
128
|
+
* `collapse-to` only ever replaces the round launcher or the whole collapsed
|
|
129
|
+
* state, never a placement's own shape: `bar` applies where the placement
|
|
130
|
+
* collapses to `launcher`, so the sidebar keeps its rail and an embedded chat
|
|
131
|
+
* its header bar, and `none` applies to every collapsible placement. Absent,
|
|
132
|
+
* `auto` and any other value leave the placement as it was. A docked surface
|
|
133
|
+
* cannot be moved -- the panel rises from the bar or the host's anchor, and a
|
|
134
|
+
* position the user dragged would take it away from either -- so both turn
|
|
135
|
+
* the launcher drag off, and with it the header drag and `moveTo`.
|
|
132
136
|
*/
|
|
133
|
-
export function surfaceFor(placement: string | null): Surface {
|
|
134
|
-
|
|
137
|
+
export function surfaceFor(placement: string | null, collapseTo: string | null = null): Surface {
|
|
138
|
+
const surface = SURFACES.get(placement) ?? UNKNOWN;
|
|
139
|
+
if (collapseTo === "bar" && surface.collapseTo === "launcher") {
|
|
140
|
+
return { ...surface, collapseTo: "bar", launcherDraggable: false };
|
|
141
|
+
}
|
|
142
|
+
if (collapseTo === "none" && surface.collapseTo !== null) {
|
|
143
|
+
return { ...surface, collapseTo: "none", launcherDraggable: false };
|
|
144
|
+
}
|
|
145
|
+
return surface;
|
|
135
146
|
}
|
|
@@ -21,6 +21,18 @@ const GROUPED = `:host(:where(:not(${CARD_MODES})))`;
|
|
|
21
21
|
/** The host in one of the four card modes, where a group drawn earlier is flattened. */
|
|
22
22
|
const CARDS = `:host(:is(${CARD_MODES}))`;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A card inside an opened group, with the host in grouped mode and the given
|
|
26
|
+
* data-tool-group-display. The inner attribute sits inside the same :host()
|
|
27
|
+
* as the grouped test, since the host is featureless outside it, and being
|
|
28
|
+
* grouped-only is what lets an outer card mode flatten the group into its own
|
|
29
|
+
* cards: none of these rules match there. Full is no rule at all, so an absent
|
|
30
|
+
* or unknown inner value draws the full card a group always drew.
|
|
31
|
+
*/
|
|
32
|
+
function inner(mode: string): string {
|
|
33
|
+
return `:host(:where(:not(${CARD_MODES}))[data-tool-group-display="${mode}"]) .tool-group-list .tool-call`;
|
|
34
|
+
}
|
|
35
|
+
|
|
24
36
|
export const TOOL_GROUP_STYLES: string = `
|
|
25
37
|
/* ── The folded tool-step group ──────────────────────────────────
|
|
26
38
|
One quiet row per run of consecutive calls, the cards one click down as
|
|
@@ -205,6 +217,43 @@ ${GROUPED} .tool-group .tool-call-subagent {
|
|
|
205
217
|
flex-basis: 100%;
|
|
206
218
|
}
|
|
207
219
|
|
|
220
|
+
/* The inner display mode: how an opened group draws its calls. The same
|
|
221
|
+
visibility rules the card modes apply to a flat card, so a call inside the
|
|
222
|
+
group shows the regions the flat card of that mode shows. The flat inline
|
|
223
|
+
mode's chrome rule is left out on purpose: a card in the group has already
|
|
224
|
+
lost its box and is laid out as a row of the list, and that rule's padding
|
|
225
|
+
and gap would undo the row. Selected from the host attribute, so changing it
|
|
226
|
+
restyles groups already on screen. */
|
|
227
|
+
${inner("compact")}[data-expanded="false"] .tool-call-section {
|
|
228
|
+
display: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
${inner("inline")} .tool-call-section--args {
|
|
232
|
+
display: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
${inner("minimal")} .tool-call-toggle,
|
|
236
|
+
${inner("minimal")} .tool-call-body {
|
|
237
|
+
display: none;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* A call waiting on an approval shows its arguments in every inner mode, as it
|
|
241
|
+
does in every card mode, and for the same reason: the arguments are what
|
|
242
|
+
tell three gated calls of one tool apart. These come after the hides above,
|
|
243
|
+
which they tie with on weight. The arguments region only, never every
|
|
244
|
+
section, since the result region carries the hidden attribute until a
|
|
245
|
+
result exists and a display value here would override it. The arguments
|
|
246
|
+
region carries it too when the call has none, so it is excluded while
|
|
247
|
+
hidden rather than framed empty. */
|
|
248
|
+
${inner("minimal")}[data-status="deferred"] .tool-call-body {
|
|
249
|
+
display: flex;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
${inner("compact")}[data-status="deferred"] .tool-call-section--args:not([hidden]),
|
|
253
|
+
${inner("inline")}[data-status="deferred"] .tool-call-section--args:not([hidden]) {
|
|
254
|
+
display: flex;
|
|
255
|
+
}
|
|
256
|
+
|
|
208
257
|
/* Grouping is decided as each card is built, so a card mode set afterwards
|
|
209
258
|
finds groups already drawn. It flattens them: the group and its list stop
|
|
210
259
|
generating boxes, the row and the repeat ordinals go, and every card lays
|
|
@@ -277,9 +277,11 @@ export const TOOL_STEP_STYLES: string = `
|
|
|
277
277
|
|
|
278
278
|
/* The arguments region only, never every section: the result region carries the
|
|
279
279
|
hidden attribute until a result exists, and a display value here overrides it,
|
|
280
|
-
framing an empty RESULT heading under the question.
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
framing an empty RESULT heading under the question. The arguments region is
|
|
281
|
+
hidden too when the call has none, and is left alone then, or it would frame
|
|
282
|
+
an empty ARGUMENTS heading. */
|
|
283
|
+
:host([data-tool-display="compact"]) .tool-call[data-status="deferred"] .tool-call-section--args:not([hidden]),
|
|
284
|
+
:host([data-tool-display="inline"]) .tool-call[data-status="deferred"] .tool-call-section--args:not([hidden]) {
|
|
283
285
|
display: flex;
|
|
284
286
|
}
|
|
285
287
|
|
|
@@ -7,21 +7,32 @@ export interface LauncherOptions {
|
|
|
7
7
|
readonly launcher: HTMLButtonElement;
|
|
8
8
|
readonly badge: HTMLElement;
|
|
9
9
|
readonly railLabel: HTMLElement;
|
|
10
|
+
/** The bottom bar's wrapper, which holds the launcher and the history button. */
|
|
11
|
+
readonly bar: HTMLElement;
|
|
12
|
+
/** The bottom bar's history button, a sibling of the launcher in the bar. */
|
|
13
|
+
readonly barHistory: HTMLButtonElement;
|
|
10
14
|
readonly titleText: string;
|
|
11
15
|
readonly iconUrl: string | null;
|
|
12
16
|
readonly strings: UiStrings;
|
|
13
17
|
readonly signal: AbortSignal;
|
|
14
18
|
readonly onOpen: () => void;
|
|
19
|
+
readonly onHistory: () => void;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
|
-
* Build what a collapsed widget shrinks to: a round floating button,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* the panel
|
|
23
|
+
* Build what a collapsed widget shrinks to: a round floating button, the slim
|
|
24
|
+
* edge rail under `placement="sidebar"`, or the strip along the bottom edge
|
|
25
|
+
* under `collapse-to="bar"` -- one element, shaped by the stylesheet. The
|
|
26
|
+
* element mounts the bar wrapper as a sibling of the panel, so it survives the
|
|
27
|
+
* panel being hidden.
|
|
28
|
+
*
|
|
29
|
+
* The wrapper is there for the bar's history button: a button cannot nest in
|
|
30
|
+
* a button, so the strip is the launcher and the history button side by side.
|
|
31
|
+
* Outside the bar the wrapper draws no box and the history button is not
|
|
32
|
+
* drawn, so the launcher lays out against the host exactly as it did alone.
|
|
22
33
|
*/
|
|
23
34
|
export function buildLauncher(options: LauncherOptions): void {
|
|
24
|
-
const { launcher, badge, railLabel } = options;
|
|
35
|
+
const { launcher, badge, railLabel, bar, barHistory } = options;
|
|
25
36
|
launcher.className = "launcher";
|
|
26
37
|
launcher.type = "button";
|
|
27
38
|
launcher.setAttribute("part", "launcher");
|
|
@@ -47,4 +58,20 @@ export function buildLauncher(options: LauncherOptions): void {
|
|
|
47
58
|
badge,
|
|
48
59
|
);
|
|
49
60
|
launcher.addEventListener("click", options.onOpen, { signal: options.signal });
|
|
61
|
+
bar.className = "dock-bar";
|
|
62
|
+
bar.setAttribute("part", "bar");
|
|
63
|
+
barHistory.className = "bar-history";
|
|
64
|
+
barHistory.type = "button";
|
|
65
|
+
barHistory.setAttribute("part", "bar-history");
|
|
66
|
+
barHistory.title = options.strings.chatHistory;
|
|
67
|
+
barHistory.setAttribute("aria-label", options.strings.chatHistory);
|
|
68
|
+
// A glyph rather than the header's icon slot: a slot name may appear once
|
|
69
|
+
// per shadow root, and the header's history button already holds it.
|
|
70
|
+
const glyph = document.createElement("span");
|
|
71
|
+
glyph.className = "bar-history-glyph";
|
|
72
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
73
|
+
glyph.textContent = "\u2630";
|
|
74
|
+
barHistory.replaceChildren(glyph);
|
|
75
|
+
barHistory.addEventListener("click", options.onHistory, { signal: options.signal });
|
|
76
|
+
bar.replaceChildren(launcher, barHistory);
|
|
50
77
|
}
|
|
@@ -37,7 +37,15 @@ export const TOKEN_STYLES: string = `
|
|
|
37
37
|
--_line-strong: var(--ag-ui-line-strong, color-mix(in oklch, var(--_fg) 20%, var(--_bg)));
|
|
38
38
|
--_wash: var(--ag-ui-wash, color-mix(in oklch, var(--_fg) 4.5%, var(--_bg)));
|
|
39
39
|
--_accent-ink: var(--ag-ui-accent-ink, color-mix(in oklch, var(--_accent) 82%, var(--_fg)));
|
|
40
|
-
|
|
40
|
+
/* The focus ring is drawn on the panel, so it has to hold 3:1 against
|
|
41
|
+
--_bg whatever accent a host brings (WCAG 2.4.13). Two parts accent to
|
|
42
|
+
three parts ink does that at the worst case, an accent at the far end
|
|
43
|
+
from the ink (white on a light panel, black on a dark one): the ink
|
|
44
|
+
alone sits far above 3:1, and three fifths of it keeps the mix there
|
|
45
|
+
while the hue still reads as the accent. Half and half fell to 2.3:1
|
|
46
|
+
for black on the code theme. The accent over transparent, as before,
|
|
47
|
+
measured only the accent, and a light host accent fell to 1.6:1. */
|
|
48
|
+
--_ring: var(--ag-ui-ring, color-mix(in oklch, var(--_accent) 40%, var(--_fg)));
|
|
41
49
|
/* The user's turn is the accent at a whisper. With Quiet's graphite accent
|
|
42
50
|
that is a neutral wash; once a host sets a brand accent it becomes a tint
|
|
43
51
|
of the brand, which is the one thing Soft does that Quiet keeps. */
|
|
@@ -300,6 +308,25 @@ export const TOKEN_STYLES: string = `
|
|
|
300
308
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
301
309
|
--_position: var(--ag-ui-position, fixed);
|
|
302
310
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
311
|
+
/* A size the user dragged, written inline on the host by the placement
|
|
312
|
+
code, and read ahead of the host's tokens so the drag wins. There is no
|
|
313
|
+
public token behind it; it is declared initial here only so a chat that
|
|
314
|
+
was never dragged does not inherit one, from a dragged chat it is
|
|
315
|
+
slotted into or from any page rule naming it. The inline value still
|
|
316
|
+
beats this default. */
|
|
317
|
+
--_drag-width: initial;
|
|
318
|
+
--_drag-height: initial;
|
|
319
|
+
/* Where a docked panel stands, written inline the same way by the placement
|
|
320
|
+
code: the bottom bar's height, or the host anchor's distance from the
|
|
321
|
+
bottom and right edges. Read only by the dock rules, each with a 0px
|
|
322
|
+
fallback; declared initial so it never inherits either. */
|
|
323
|
+
--_dock-bottom: initial;
|
|
324
|
+
--_dock-right: initial;
|
|
325
|
+
/* The gap between a panel and the host's own button it rises from under
|
|
326
|
+
collapse-to="none". The button usually sits inside a taller toolbar, so a
|
|
327
|
+
host sets this to the button's distance from the toolbar's top plus the
|
|
328
|
+
clearance it wants, and the panel stands clear of the toolbar. */
|
|
329
|
+
--_anchor-gap: var(--ag-ui-anchor-gap, 8px);
|
|
303
330
|
--_width: var(--_drag-width, var(--ag-ui-width, 380px));
|
|
304
331
|
--_height: var(--_drag-height, var(--ag-ui-height, 560px));
|
|
305
332
|
/* The gutter a resting floating panel keeps between itself and the edge of
|
package/src/ui/styles.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { APPROVAL_STYLES } from "./interrupts/approval_styles.js";
|
|
|
23
23
|
import { CONFIRMATION_STYLES } from "./interrupts/confirmation_styles.js";
|
|
24
24
|
import { QUESTION_STYLES } from "./interrupts/question_styles.js";
|
|
25
25
|
import { COLLAPSE_STYLES } from "./placement/collapse_styles.js";
|
|
26
|
+
import { DOCK_STYLES } from "./placement/dock_styles.js";
|
|
26
27
|
import { EMBEDDED_STYLES } from "./placement/embedded_styles.js";
|
|
27
28
|
import { LAUNCHER_STYLES } from "./placement/launcher_styles.js";
|
|
28
29
|
import { PAGE_STYLES } from "./placement/page_styles.js";
|
|
@@ -65,6 +66,9 @@ export const STYLES: string = [
|
|
|
65
66
|
THEME_STYLES,
|
|
66
67
|
PRESET_STYLES,
|
|
67
68
|
PAGE_STYLES,
|
|
69
|
+
// Ahead of the phone block and no heavier than it: a docked panel is still
|
|
70
|
+
// full-bleed on a phone.
|
|
71
|
+
DOCK_STYLES,
|
|
68
72
|
SMALL_VIEWPORT_STYLES,
|
|
69
73
|
SIDEBAR_STYLES,
|
|
70
74
|
LAUNCHER_STYLES,
|
|
@@ -6,9 +6,18 @@ export const SCHEME_STYLES: string = `
|
|
|
6
6
|
dark) and beside a theme name (soft dark). auto follows the operating
|
|
7
7
|
system through color-scheme itself, with no media query to keep in step.
|
|
8
8
|
code is a dark-only theme and has no light half. */
|
|
9
|
+
/* Important, so a scheme word holds against a host rule that sets the
|
|
10
|
+
element's color-scheme (a page that follows its own dark mode). A rule
|
|
11
|
+
from outside the shadow tree beats every :host rule at normal weight, and
|
|
12
|
+
the theme toggle writes the word; without this its press changed nothing.
|
|
13
|
+
code stays last, so code light is still dark. */
|
|
14
|
+
:host([theme~="light"]) {
|
|
15
|
+
color-scheme: light !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
:host([theme~="dark"]),
|
|
10
19
|
:host([theme~="code"]) {
|
|
11
|
-
color-scheme: dark;
|
|
20
|
+
color-scheme: dark !important;
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
:host([theme~="auto"]) {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION: string = "0.
|
|
1
|
+
export const VERSION: string = "0.48.0";
|