@artooi/ag-ui-web-component 0.34.0 → 0.35.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 +501 -1
- package/README.md +232 -13
- package/dist/ag-ui-web-component.bundle.js +614 -96
- package/dist/ag-ui-web-component.bundle.js.map +4 -4
- package/dist/constants.d.ts +76 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/ag_ui_chat.d.ts +37 -2
- package/dist/core/ag_ui_chat.d.ts.map +1 -1
- package/dist/dom/animations.d.ts +14 -0
- package/dist/dom/animations.d.ts.map +1 -1
- package/dist/dom/highlight_overlay.d.ts +47 -0
- package/dist/dom/highlight_overlay.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1886 -320
- package/dist/index.js.map +4 -4
- package/dist/tools/chat_surface_tools.d.ts +96 -0
- package/dist/tools/chat_surface_tools.d.ts.map +1 -0
- package/dist/tools/page_action_tools.d.ts +2 -0
- package/dist/tools/page_action_tools.d.ts.map +1 -1
- package/dist/ui/clamp_launcher.d.ts +10 -5
- package/dist/ui/clamp_launcher.d.ts.map +1 -1
- package/dist/ui/clamp_panel.d.ts +2 -2
- package/dist/ui/clamp_panel.d.ts.map +1 -1
- package/dist/ui/launcher_drag.d.ts +2 -2
- package/dist/ui/launcher_drag.d.ts.map +1 -1
- package/dist/ui/launcher_placement.d.ts +14 -1
- package/dist/ui/launcher_placement.d.ts.map +1 -1
- package/dist/ui/panel_drag.d.ts.map +1 -1
- package/dist/ui/place_widget.d.ts +9 -1
- package/dist/ui/place_widget.d.ts.map +1 -1
- package/dist/ui/run_notice.d.ts +14 -3
- package/dist/ui/run_notice.d.ts.map +1 -1
- package/dist/ui/styles.d.ts +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/thread_drawer.d.ts +21 -0
- package/dist/ui/thread_drawer.d.ts.map +1 -1
- package/dist/ui/ui_strings.d.ts +16 -0
- package/dist/ui/ui_strings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +82 -3
- package/src/core/ag_ui_chat.ts +965 -55
- package/src/dom/animations.ts +30 -0
- package/src/dom/highlight_overlay.ts +256 -0
- package/src/index.ts +12 -0
- package/src/tools/chat_surface_tools.ts +207 -0
- package/src/tools/page_action_tools.ts +2 -0
- package/src/ui/clamp_launcher.ts +25 -7
- package/src/ui/clamp_panel.ts +10 -4
- package/src/ui/launcher_drag.ts +11 -2
- package/src/ui/launcher_placement.ts +34 -8
- package/src/ui/panel_drag.ts +4 -0
- package/src/ui/place_widget.ts +11 -3
- package/src/ui/run_notice.ts +32 -3
- package/src/ui/styles.ts +563 -45
- package/src/ui/thread_drawer.ts +138 -8
- package/src/ui/ui_strings.ts +24 -0
- package/src/version.ts +1 -1
package/src/ui/styles.ts
CHANGED
|
@@ -48,6 +48,11 @@ export const STYLES = `
|
|
|
48
48
|
/* Message action row: the control box and the mark inside it. The box has a
|
|
49
49
|
floor of 24px so it stays a reliable target at every density. */
|
|
50
50
|
--_action-size: var(--ag-ui-action-size, 28px);
|
|
51
|
+
/* The header's own controls. Sized rather than left to their contents: a row
|
|
52
|
+
of buttons each as wide as the glyph inside it comes out uneven, and uneven
|
|
53
|
+
buttons two pixels apart read as one smudge rather than five controls. */
|
|
54
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 30px);
|
|
55
|
+
--_header-gap: var(--ag-ui-header-gap, 4px);
|
|
51
56
|
--_action-icon-size: var(--ag-ui-action-icon-size, 15px);
|
|
52
57
|
--_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);
|
|
53
58
|
--_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);
|
|
@@ -142,6 +147,68 @@ export const STYLES = `
|
|
|
142
147
|
--_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
|
|
143
148
|
--_msg-radius: var(--ag-ui-msg-radius, 14px);
|
|
144
149
|
|
|
150
|
+
/* Edges of the viewport the host has already spent, and that a fixed
|
|
151
|
+
placement must therefore stay out of: a sticky nav bar, a docked toolbar,
|
|
152
|
+
a device's safe area. Four longhands rather than one shorthand because a
|
|
153
|
+
custom property is a token stream and CSS cannot index one -- the height
|
|
154
|
+
arithmetic below needs the vertical pair on its own.
|
|
155
|
+
|
|
156
|
+
Every fixed placement derives from these, and the heights subtract them in
|
|
157
|
+
one place. A host that reserved its chrome by restating --ag-ui-inset per
|
|
158
|
+
placement had to keep --ag-ui-height in step by hand, and forgetting it
|
|
159
|
+
overflowed the panel past the bottom of the screen with nothing to say so.
|
|
160
|
+
|
|
161
|
+
These take env(safe-area-inset-*) verbatim, which is what a full-bleed
|
|
162
|
+
placement wants on a device with a notch. */
|
|
163
|
+
--_viewport-inset-top: var(--ag-ui-viewport-inset-top, 0px);
|
|
164
|
+
--_viewport-inset-right: var(--ag-ui-viewport-inset-right, 0px);
|
|
165
|
+
--_viewport-inset-bottom: var(--ag-ui-viewport-inset-bottom, 0px);
|
|
166
|
+
--_viewport-inset-left: var(--ag-ui-viewport-inset-left, 0px);
|
|
167
|
+
/* What is left of the viewport once the host's chrome is taken out, on both
|
|
168
|
+
axes, so a placement never composes this itself and no host can subtract
|
|
169
|
+
an edge from the position and forget it in the height.
|
|
170
|
+
|
|
171
|
+
Settable in their own right, and that is not only convention: an on-screen
|
|
172
|
+
keyboard changes no viewport-percentage length -- not vh, not dvh, not svh
|
|
173
|
+
-- so a full-bleed panel on a phone has to be told the height rather than
|
|
174
|
+
deriving it. The value to publish there is the visual viewport's. */
|
|
175
|
+
--_viewport-height: var(--ag-ui-viewport-height, var(--_visual-viewport-height));
|
|
176
|
+
/* The measured height of the part of the screen the user can actually see,
|
|
177
|
+
written by the element from the visual viewport and falling back to the
|
|
178
|
+
layout viewport where nothing has measured yet.
|
|
179
|
+
|
|
180
|
+
This is the on-screen keyboard, and it needs measuring because no CSS
|
|
181
|
+
length describes it: an on-screen keyboard has no effect on any
|
|
182
|
+
viewport-percentage unit, so 100vh, 100dvh and 100svh are all the same
|
|
183
|
+
number with the keyboard up as without it. A full-bleed panel sized from
|
|
184
|
+
any of them puts its composer behind the keyboard the user is typing into.
|
|
185
|
+
|
|
186
|
+
Separate from the token above so a host that states the usable height
|
|
187
|
+
outright still wins: the element writes this one inline, and an inline
|
|
188
|
+
value would otherwise outrank the host's own rule. */
|
|
189
|
+
--_visual-viewport-height: var(
|
|
190
|
+
--ag-ui-visual-viewport-height,
|
|
191
|
+
calc(100vh - var(--_viewport-inset-top) - var(--_viewport-inset-bottom))
|
|
192
|
+
);
|
|
193
|
+
/* How much of the layout viewport is hidden below the visible one, measured
|
|
194
|
+
and written by the element alongside the height above.
|
|
195
|
+
|
|
196
|
+
A shorter panel is not enough on its own for anything anchored to the
|
|
197
|
+
bottom. A floating widget is positioned against the layout viewport, so
|
|
198
|
+
with a keyboard up its bottom edge -- and the launcher that lives at that
|
|
199
|
+
corner -- sits behind the keyboard however tall the panel is. This is what
|
|
200
|
+
lifts it clear. */
|
|
201
|
+
/* Two tokens, for the same reason the height above has two: the element
|
|
202
|
+
writes the measurement inline, and a host that states its own value needs
|
|
203
|
+
a knob that outranks that write rather than one the next write replaces.
|
|
204
|
+
A host wanting no keyboard lift at all sets --ag-ui-keyboard-inset: 0px. */
|
|
205
|
+
--_keyboard-inset: var(--ag-ui-keyboard-inset, var(--_visual-viewport-inset-bottom));
|
|
206
|
+
--_visual-viewport-inset-bottom: var(--ag-ui-visual-viewport-inset-bottom, 0px);
|
|
207
|
+
--_viewport-width: var(
|
|
208
|
+
--ag-ui-viewport-width,
|
|
209
|
+
calc(100vw - var(--_viewport-inset-left) - var(--_viewport-inset-right))
|
|
210
|
+
);
|
|
211
|
+
|
|
145
212
|
/* Layout — override from outside to dock the widget anywhere.
|
|
146
213
|
Set --ag-ui-position: static (and place this element in your own
|
|
147
214
|
grid/flex layout) to embed it in the page flow instead of floating. */
|
|
@@ -149,14 +216,49 @@ export const STYLES = `
|
|
|
149
216
|
--_z-index: var(--ag-ui-z-index, 2147483000);
|
|
150
217
|
--_width: var(--ag-ui-width, 380px);
|
|
151
218
|
--_height: var(--ag-ui-height, 560px);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
219
|
+
/* The gutter a resting floating panel keeps between itself and the edge of
|
|
220
|
+
the box the host left free. One number, because the inset below spends it
|
|
221
|
+
and the cap beneath has to know it was spent. */
|
|
222
|
+
--_edge-gutter: var(--ag-ui-edge-gutter, 24px);
|
|
223
|
+
--_inset: var(
|
|
224
|
+
--ag-ui-inset,
|
|
225
|
+
auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
|
|
226
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset)) auto
|
|
227
|
+
);
|
|
228
|
+
/* The cap is what the host left free, less the *one* gutter the inset above
|
|
229
|
+
spends on the anchored edge -- not two, and not none.
|
|
230
|
+
|
|
231
|
+
Two was the first answer and it was felt on one axis only. The arithmetic
|
|
232
|
+
says why: the default panel is 560 tall against a cap of the viewport
|
|
233
|
+
minus 48, which on an 800px screen with a header reserved is 72px of
|
|
234
|
+
headroom, so the height reaches its cap almost immediately; the default
|
|
235
|
+
width is 380 against a cap near 1230, which is 850px nothing ever reaches.
|
|
236
|
+
Once the size is capped a grip cannot grow the panel, so a pull on the
|
|
237
|
+
anchored edge is written as position instead and the panel travels, and a
|
|
238
|
+
pull on the free edge stops a whole gutter short of an edge a drag can
|
|
239
|
+
reach.
|
|
240
|
+
|
|
241
|
+
None was the correction, and it overshot: the panel is anchored bottom-
|
|
242
|
+
right with a gutter already spent there, so a cap of the full usable
|
|
243
|
+
height puts the far edge exactly one gutter outside it. On an 800px screen
|
|
244
|
+
with nothing reserved that is a top of -24 -- the header, and every
|
|
245
|
+
control in it, off the top of the window. Reserve a 120px header and it
|
|
246
|
+
sits inside that instead, which is the one thing the reservation exists to
|
|
247
|
+
prevent.
|
|
248
|
+
|
|
249
|
+
One is the fixed point of both complaints. A resting panel grown to the
|
|
250
|
+
cap runs from the usable near edge to its gutter on the far one, so the
|
|
251
|
+
near edge is reachable by a resize exactly as it is by a drag, and neither
|
|
252
|
+
can put any part of the panel outside the box. */
|
|
253
|
+
--_max-width: var(--ag-ui-max-width, calc(var(--_viewport-width) - var(--_edge-gutter)));
|
|
254
|
+
--_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_edge-gutter)));
|
|
155
255
|
/* Reading-column width for placement="page" (full-bleed, centred content). */
|
|
156
256
|
--_content-max-width: var(--ag-ui-content-max-width, 820px);
|
|
157
257
|
/* Slim rail the sidebar placement collapses to. Only that placement reads
|
|
158
258
|
it, but it is declared here so every alias has a default in one place. */
|
|
159
259
|
--_rail-width: var(--ag-ui-rail-width, 52px);
|
|
260
|
+
/* Width of the docked conversation list on a full-page chat. */
|
|
261
|
+
--_threads-rail-width: var(--ag-ui-threads-rail-width, 280px);
|
|
160
262
|
|
|
161
263
|
position: var(--_position);
|
|
162
264
|
inset: var(--_inset);
|
|
@@ -227,23 +329,30 @@ export const STYLES = `
|
|
|
227
329
|
|
|
228
330
|
/* ── Placement presets ──────────────────────────────────────────────────── */
|
|
229
331
|
:host([placement="bottom-left"]) {
|
|
230
|
-
--_inset: var(
|
|
332
|
+
--_inset: var(
|
|
333
|
+
--ag-ui-inset,
|
|
334
|
+
auto auto calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
335
|
+
calc(var(--_edge-gutter) + var(--_viewport-inset-left))
|
|
336
|
+
);
|
|
231
337
|
}
|
|
232
338
|
|
|
233
339
|
:host([placement="side"]) {
|
|
234
|
-
--_inset: var(--ag-ui-inset,
|
|
340
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
235
341
|
--_width: var(--ag-ui-width, 420px);
|
|
236
|
-
--_height: var(--ag-ui-height,
|
|
237
|
-
--_max-height: var(--ag-ui-max-height,
|
|
342
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
343
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
238
344
|
--_radius: var(--ag-ui-radius, 0);
|
|
239
345
|
}
|
|
240
346
|
|
|
241
347
|
:host([placement="full"]) {
|
|
242
|
-
--_inset: var(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
--
|
|
348
|
+
--_inset: var(
|
|
349
|
+
--ag-ui-inset,
|
|
350
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
351
|
+
);
|
|
352
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
353
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
354
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
355
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
247
356
|
--_radius: var(--ag-ui-radius, 0);
|
|
248
357
|
}
|
|
249
358
|
|
|
@@ -253,11 +362,14 @@ export const STYLES = `
|
|
|
253
362
|
composer rather than a per-row wrapper, so user pills still right-align and
|
|
254
363
|
the assistant well spans the column. */
|
|
255
364
|
:host([placement="page"]) {
|
|
256
|
-
--_inset: var(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
--
|
|
365
|
+
--_inset: var(
|
|
366
|
+
--ag-ui-inset,
|
|
367
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
|
|
368
|
+
);
|
|
369
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
370
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
371
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
372
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
261
373
|
--_radius: var(--ag-ui-radius, 0);
|
|
262
374
|
}
|
|
263
375
|
|
|
@@ -288,28 +400,100 @@ export const STYLES = `
|
|
|
288
400
|
max-width: 100%;
|
|
289
401
|
}
|
|
290
402
|
|
|
403
|
+
/* Small viewports: one shape, reached from whichever placement the host chose.
|
|
404
|
+
|
|
405
|
+
A phone is not an eighth placement, it is an override that collapses the
|
|
406
|
+
others onto one of them. The host picked a placement for the desktop it was
|
|
407
|
+
designing; a 380x560 panel with a 24px margin is not a smaller version of
|
|
408
|
+
that decision, it is most of the screen with a frame drawn round it.
|
|
409
|
+
|
|
410
|
+
The breakpoint is a literal because a custom property cannot be read in a
|
|
411
|
+
media query -- which is exactly why there is an opt-out. Everything the block
|
|
412
|
+
sets is a token a host can re-state, but the *trigger* is unreachable, so a
|
|
413
|
+
host whose layout wants a different threshold, or none, sets
|
|
414
|
+
data-small-viewport="off" and keeps its desktop shape at every width. 600px is above every common phone in portrait and below every
|
|
415
|
+
tablet in landscape, and it is a width rather than a pointer test on purpose:
|
|
416
|
+
a touch laptop is coarse-pointered and wide, a narrow desktop window is
|
|
417
|
+
fine-pointered and small, and conflating the two gets both wrong. What the
|
|
418
|
+
pointer decides is which controls make sense, further down.
|
|
419
|
+
|
|
420
|
+
Two placements are left alone. "page" is already this shape. "embedded" sits
|
|
421
|
+
in a box the host sized and placed, and taking that over would break the app
|
|
422
|
+
shell it was embedded into -- the host is the only party that knows whether
|
|
423
|
+
its column should become the whole screen. */
|
|
424
|
+
@media (max-width: 600px) {
|
|
425
|
+
:host([placement="floating"]:not([data-small-viewport="off"])),
|
|
426
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])),
|
|
427
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])),
|
|
428
|
+
:host([placement="side"]:not([data-small-viewport="off"])),
|
|
429
|
+
:host(:not([placement]):not([data-small-viewport="off"])),
|
|
430
|
+
:host([placement=""]:not([data-small-viewport="off"])) {
|
|
431
|
+
--_inset: var(
|
|
432
|
+
--ag-ui-inset,
|
|
433
|
+
var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
434
|
+
calc(var(--_viewport-inset-bottom) + var(--_keyboard-inset))
|
|
435
|
+
var(--_viewport-inset-left)
|
|
436
|
+
);
|
|
437
|
+
--_width: var(--ag-ui-width, var(--_viewport-width));
|
|
438
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
439
|
+
--_max-width: var(--ag-ui-max-width, var(--_viewport-width));
|
|
440
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
441
|
+
--_radius: var(--ag-ui-radius, 0);
|
|
442
|
+
--_shadow: var(--ag-ui-shadow, none);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Nothing to resize once the panel is the screen, and the grips would sit
|
|
446
|
+
under the thumbs holding the phone. */
|
|
447
|
+
:host([placement="floating"]:not([data-small-viewport="off"])) .resize-handle,
|
|
448
|
+
:host([placement="bottom-left"]:not([data-small-viewport="off"])) .resize-handle,
|
|
449
|
+
:host([placement="sidebar"]:not([data-small-viewport="off"])) .resize-handle,
|
|
450
|
+
:host([placement="side"]:not([data-small-viewport="off"])) .resize-handle,
|
|
451
|
+
:host(:not([placement]):not([data-small-viewport="off"])) .resize-handle,
|
|
452
|
+
:host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
|
|
453
|
+
display: none;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
291
457
|
/* Sidebar: a full-height docked panel that slides open/closed and
|
|
292
458
|
collapses to a slim icon rail (not the floating launcher). Docked right by
|
|
293
459
|
default; data-side="left" docks it left. Overlay by default — set
|
|
294
460
|
--ag-ui-position: static (and place this element in your own layout) for a
|
|
295
461
|
host-managed push instead. */
|
|
296
462
|
:host([placement="sidebar"]) {
|
|
297
|
-
--_inset: var(--ag-ui-inset,
|
|
463
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
|
|
298
464
|
--_width: var(--ag-ui-width, 420px);
|
|
299
|
-
--_height: var(--ag-ui-height,
|
|
300
|
-
--_max-height: var(--ag-ui-max-height,
|
|
465
|
+
--_height: var(--ag-ui-height, var(--_viewport-height));
|
|
466
|
+
--_max-height: var(--ag-ui-max-height, var(--_viewport-height));
|
|
301
467
|
--_radius: var(--ag-ui-radius, 0);
|
|
302
468
|
transition: width var(--_motion) var(--_ease);
|
|
303
469
|
}
|
|
304
470
|
|
|
305
471
|
:host([placement="sidebar"][data-side="left"]) {
|
|
306
|
-
--_inset: var(--ag-ui-inset,
|
|
472
|
+
--_inset: var(--ag-ui-inset, var(--_viewport-inset-top) auto var(--_viewport-inset-bottom) var(--_viewport-inset-left));
|
|
307
473
|
}
|
|
308
474
|
|
|
309
475
|
/* The docked panel is pinned to the edge it docks against rather than filling
|
|
310
476
|
the host as a flex child. Collapsing shrinks the host to the rail width, and
|
|
311
477
|
a flex child would be squashed to 52px on the way out instead of sliding out
|
|
312
478
|
at full width. */
|
|
479
|
+
/* The panel is taken out of flow so the collapse can slide it out at full
|
|
480
|
+
width; see the note above. That makes the host its containing block, and the
|
|
481
|
+
host is only one by accident: it is position: fixed by default. A host that
|
|
482
|
+
takes the documented route to a pushed layout instead of an overlay sets
|
|
483
|
+
--ag-ui-position: static, and a static element establishes nothing -- so the
|
|
484
|
+
panel resolved against the initial containing block, landed at the document
|
|
485
|
+
origin, and scrolled away with the page. Docked left it pinned to the
|
|
486
|
+
document's left edge rather than the host's.
|
|
487
|
+
|
|
488
|
+
It looked correct wherever it was first tried, because a full-height column
|
|
489
|
+
at the top of an unscrolled document is exactly where those two answers
|
|
490
|
+
coincide. Containment makes the host a containing block whatever its
|
|
491
|
+
position, so the panel stays in the box the host was given. It is a no-op in
|
|
492
|
+
the default case, which is already positioned. */
|
|
493
|
+
:host([placement="sidebar"]) {
|
|
494
|
+
contain: layout;
|
|
495
|
+
}
|
|
496
|
+
|
|
313
497
|
:host([placement="sidebar"]) .chat {
|
|
314
498
|
position: absolute;
|
|
315
499
|
inset: 0 0 0 auto;
|
|
@@ -325,11 +509,15 @@ export const STYLES = `
|
|
|
325
509
|
/* Collapsed sidebar: shrink the host to the rail width and slide the panel out
|
|
326
510
|
through the edge it docks against. Higher specificity than the generic
|
|
327
511
|
collapse rules, so it wins regardless of source order. */
|
|
512
|
+
/* The rail is the full height of what the host left, not of the screen. It
|
|
513
|
+
said 100vh and pinned its own bottom, which put it under any chrome the host
|
|
514
|
+
had reserved -- and the icon lives at the top of the rail, so the one control
|
|
515
|
+
that reopens the panel was the first thing to disappear behind a sticky
|
|
516
|
+
header. */
|
|
328
517
|
:host([placement="sidebar"][collapsed]) {
|
|
329
518
|
width: var(--_rail-width);
|
|
330
|
-
height:
|
|
331
|
-
max-height:
|
|
332
|
-
bottom: 0;
|
|
519
|
+
height: var(--_viewport-height);
|
|
520
|
+
max-height: var(--_viewport-height);
|
|
333
521
|
pointer-events: auto;
|
|
334
522
|
}
|
|
335
523
|
|
|
@@ -445,17 +633,83 @@ export const STYLES = `
|
|
|
445
633
|
|
|
446
634
|
/* The sidebar collapses to an edge rail instead: full height, square, flush
|
|
447
635
|
against the dock. It slides in with the panel rather than popping. */
|
|
636
|
+
/* The edge rail. It reads as the docked edge of a panel rather than a coloured
|
|
637
|
+
stripe: the surface the panel is made of, a border on the side it docks
|
|
638
|
+
against, and the accent kept for the icon -- a full-height slab of accent is
|
|
639
|
+
the loudest thing on the page and says the least about itself.
|
|
640
|
+
|
|
641
|
+
Content sits at the top rather than centred, because a control floating in
|
|
642
|
+
the middle of a screen-high column has nothing to belong to. */
|
|
448
643
|
:host([placement="sidebar"][collapsed]) .launcher {
|
|
449
644
|
inset: 0;
|
|
450
645
|
width: auto;
|
|
451
646
|
height: auto;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
647
|
+
flex-direction: column;
|
|
648
|
+
align-items: center;
|
|
649
|
+
justify-content: flex-start;
|
|
650
|
+
gap: 12px;
|
|
651
|
+
padding-top: 14px;
|
|
652
|
+
border: 0;
|
|
653
|
+
border-inline-start: 1px solid var(--_border);
|
|
455
654
|
border-radius: 0;
|
|
655
|
+
background: var(--_bg);
|
|
656
|
+
color: var(--_fg);
|
|
657
|
+
box-shadow: none;
|
|
658
|
+
transform: none;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .launcher {
|
|
662
|
+
border-inline-start: 0;
|
|
663
|
+
border-inline-end: 1px solid var(--_border);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* The icon keeps the accent, so there is one obvious thing to press.
|
|
667
|
+
|
|
668
|
+
The glyph is sized here rather than left to fill the holder. A glyph in an
|
|
669
|
+
icon holder takes the holder's whole box by default, which is right where the
|
|
670
|
+
holder is only a box and wrong the moment it becomes a filled circle: the
|
|
671
|
+
mark then runs edge to edge and reads as a square crammed into a disc. The
|
|
672
|
+
proportion is the floating launcher's own -- a 26px glyph in a 56px bubble --
|
|
673
|
+
so the two collapsed states look like the same widget. */
|
|
674
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder {
|
|
675
|
+
display: inline-flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
justify-content: center;
|
|
678
|
+
width: 34px;
|
|
679
|
+
height: 34px;
|
|
680
|
+
border-radius: 50%;
|
|
456
681
|
background: var(--_header-bg);
|
|
457
682
|
color: var(--_header-fg);
|
|
458
|
-
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .glyph,
|
|
686
|
+
:host([placement="sidebar"][collapsed]) .launcher .icon-holder .icon-img {
|
|
687
|
+
width: 16px;
|
|
688
|
+
height: 16px;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/* Written down the rail, which is the only direction it fits. Reading upward
|
|
692
|
+
is the convention for a right-hand edge and matches how a docked panel's
|
|
693
|
+
label is set everywhere it appears. */
|
|
694
|
+
.rail-label {
|
|
695
|
+
display: none;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
:host([placement="sidebar"][collapsed]) .rail-label {
|
|
699
|
+
display: block;
|
|
700
|
+
writing-mode: vertical-rl;
|
|
701
|
+
transform: rotate(180deg);
|
|
702
|
+
max-height: calc(100% - 96px);
|
|
703
|
+
overflow: hidden;
|
|
704
|
+
font-size: 0.9em;
|
|
705
|
+
font-weight: 600;
|
|
706
|
+
letter-spacing: 0.02em;
|
|
707
|
+
white-space: nowrap;
|
|
708
|
+
text-overflow: ellipsis;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
:host([placement="sidebar"][data-side="left"][collapsed]) .rail-label {
|
|
712
|
+
writing-mode: vertical-rl;
|
|
459
713
|
transform: none;
|
|
460
714
|
}
|
|
461
715
|
|
|
@@ -569,17 +823,24 @@ export const STYLES = `
|
|
|
569
823
|
|
|
570
824
|
.header-controls {
|
|
571
825
|
display: flex;
|
|
572
|
-
gap:
|
|
826
|
+
gap: var(--_header-gap);
|
|
573
827
|
flex: none;
|
|
574
828
|
}
|
|
575
829
|
|
|
576
830
|
.header-btn {
|
|
831
|
+
display: inline-flex;
|
|
832
|
+
flex: 0 0 auto;
|
|
833
|
+
align-items: center;
|
|
834
|
+
justify-content: center;
|
|
835
|
+
box-sizing: border-box;
|
|
836
|
+
width: var(--_header-btn-size);
|
|
837
|
+
height: var(--_header-btn-size);
|
|
577
838
|
border: none;
|
|
578
839
|
background: transparent;
|
|
579
840
|
color: inherit;
|
|
580
841
|
font: inherit;
|
|
581
842
|
line-height: 1;
|
|
582
|
-
padding:
|
|
843
|
+
padding: 0;
|
|
583
844
|
border-radius: 6px;
|
|
584
845
|
cursor: pointer;
|
|
585
846
|
opacity: 0.85;
|
|
@@ -601,9 +862,9 @@ export const STYLES = `
|
|
|
601
862
|
Nothing paints there once the panel is gone, so the box only has to stop
|
|
602
863
|
swallowing clicks: pointer events go to none and the launcher takes them.
|
|
603
864
|
|
|
604
|
-
Two placements
|
|
605
|
-
|
|
606
|
-
|
|
865
|
+
Two placements do not use it: "sidebar" slides to its rail (below), and
|
|
866
|
+
"embedded" keeps its header bar, having no corner for a floating circle that
|
|
867
|
+
would escape the host's layout. "page" has no collapsed state at all. */
|
|
607
868
|
:host([collapsed]) {
|
|
608
869
|
pointer-events: none;
|
|
609
870
|
/* A collapsed host has to be allowed to shrink, and in the layout hosts
|
|
@@ -665,37 +926,107 @@ export const STYLES = `
|
|
|
665
926
|
transform-origin: bottom right;
|
|
666
927
|
}
|
|
667
928
|
|
|
668
|
-
/* The
|
|
669
|
-
|
|
670
|
-
:host([collapsed]
|
|
929
|
+
/* The in-flow placement keeps the original collapse: hide the body, let the
|
|
930
|
+
host shrink to the header bar. */
|
|
931
|
+
:host([collapsed][placement="embedded"]) {
|
|
671
932
|
height: auto;
|
|
672
933
|
max-height: none;
|
|
673
934
|
pointer-events: auto;
|
|
674
935
|
}
|
|
675
936
|
|
|
676
|
-
:host([collapsed]
|
|
937
|
+
:host([collapsed][placement="embedded"]) .chat {
|
|
677
938
|
opacity: 1;
|
|
678
939
|
transform: none;
|
|
679
940
|
visibility: visible;
|
|
680
941
|
}
|
|
681
942
|
|
|
682
|
-
/*
|
|
943
|
+
/* It keeps the header bar, so the launcher must stay out of the way: an
|
|
683
944
|
embedded host is position: static, which would let an absolutely-positioned
|
|
684
945
|
circle escape the layout and land against whatever the page positions. */
|
|
685
|
-
:host([collapsed]
|
|
946
|
+
:host([collapsed][placement="embedded"]) .launcher {
|
|
686
947
|
visibility: hidden;
|
|
687
948
|
opacity: 0;
|
|
688
949
|
}
|
|
689
950
|
|
|
690
|
-
:host([collapsed]
|
|
691
|
-
:host([collapsed]
|
|
692
|
-
:host([collapsed]
|
|
693
|
-
:host([collapsed]
|
|
694
|
-
:host([collapsed]
|
|
695
|
-
:host([collapsed]
|
|
951
|
+
:host([collapsed][placement="embedded"]) .messages-wrap,
|
|
952
|
+
:host([collapsed][placement="embedded"]) .messages,
|
|
953
|
+
:host([collapsed][placement="embedded"]) .input-row,
|
|
954
|
+
:host([collapsed][placement="embedded"]) .skill-chips,
|
|
955
|
+
:host([collapsed][placement="embedded"]) .skill-palette,
|
|
956
|
+
:host([collapsed][placement="embedded"]) .skill-hint {
|
|
957
|
+
display: none;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/* A docked conversation list: beside the transcript rather than over it.
|
|
961
|
+
|
|
962
|
+
A full-page chat is the one surface with width to spare, and covering the
|
|
963
|
+
conversation to show the list of conversations is the wrong trade there --
|
|
964
|
+
it hides the thing you are trying to get back to. Everywhere else the panel
|
|
965
|
+
is a few hundred pixels wide and a docked list would leave a column of
|
|
966
|
+
transcript narrower than the messages in it, so this is the only placement
|
|
967
|
+
that gets it.
|
|
968
|
+
|
|
969
|
+
The transcript is moved by padding on the shell rather than by making the
|
|
970
|
+
list a flex sibling: the drawer is the last child of the panel, and no
|
|
971
|
+
selector reaches backwards from it to the rows that have to shift. That is
|
|
972
|
+
also why the state is stamped on the host. */
|
|
973
|
+
:host([data-threads-docked]) .drawer {
|
|
974
|
+
/* Not a scrim: the page behind it is still the user's to work in. */
|
|
975
|
+
pointer-events: none;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
:host([data-threads-docked]) .drawer-backdrop {
|
|
696
979
|
display: none;
|
|
697
980
|
}
|
|
698
981
|
|
|
982
|
+
:host([data-threads-docked]) .drawer-panel {
|
|
983
|
+
width: var(--_threads-rail-width);
|
|
984
|
+
pointer-events: auto;
|
|
985
|
+
border-inline-end: 1px solid var(--_border);
|
|
986
|
+
box-shadow: none;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
:host([data-threads-docked]) .messages,
|
|
990
|
+
:host([data-threads-docked]) .input-row,
|
|
991
|
+
:host([data-threads-docked]) .skill-chips,
|
|
992
|
+
:host([data-threads-docked]) .attachment-tray {
|
|
993
|
+
padding-inline-start: calc(
|
|
994
|
+
var(--_threads-rail-width) + max(var(--_pad), (100% - var(--_content-max-width)) / 2)
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
:host([data-threads-docked]) .header {
|
|
999
|
+
padding-inline-start: calc(var(--_threads-rail-width) + var(--_pad));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/* The page placement has no collapsed state, so it offers no control for one.
|
|
1003
|
+
A dedicated route has no "away" to go to: shrinking it left a strip of
|
|
1004
|
+
application chrome fixed over a route that no longer had an owner, and this
|
|
1005
|
+
is the one placement where the launcher that would bring it back is hidden.
|
|
1006
|
+
|
|
1007
|
+
Hiding the control is only half of it. The state has another way in -- the
|
|
1008
|
+
attribute can be written straight onto the element, and a value stored under
|
|
1009
|
+
a different placement is restored on connect -- and the JS guards that catch
|
|
1010
|
+
those cannot see an attribute set directly. So the placement neutralises the
|
|
1011
|
+
state here as well: with the collapse rules above no longer naming "page", an
|
|
1012
|
+
unguarded collapsed attribute would otherwise fall through to the generic
|
|
1013
|
+
rule that scales the panel away and drops pointer events, leaving nothing on
|
|
1014
|
+
screen and no launcher to press. */
|
|
1015
|
+
:host([placement="page"]) .header-btn--collapse {
|
|
1016
|
+
display: none;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
:host([placement="page"][collapsed]) {
|
|
1020
|
+
pointer-events: auto;
|
|
1021
|
+
align-self: auto;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
:host([placement="page"][collapsed]) .chat {
|
|
1025
|
+
opacity: 1;
|
|
1026
|
+
transform: none;
|
|
1027
|
+
visibility: visible;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
699
1030
|
/* Jump-to-latest: shown only once the reader has scrolled away *and* missed
|
|
700
1031
|
something. Anchored to the panel rather than the list so it does not scroll
|
|
701
1032
|
with the content it is offering to scroll to. */
|
|
@@ -783,6 +1114,28 @@ export const STYLES = `
|
|
|
783
1114
|
|
|
784
1115
|
The 1px box with clip-path, rather than width/height 0, is the shape that
|
|
785
1116
|
survives: a zero-sized element is dropped from the tree by some engines. */
|
|
1117
|
+
/* A used-value reader for the four viewport-inset tokens, and the only
|
|
1118
|
+
reliable one. getComputedStyle().getPropertyValue() on an unregistered
|
|
1119
|
+
custom property hands back the substituted token stream, not a length: it
|
|
1120
|
+
returns "4rem" verbatim, and "calc(56px + env(safe-area-inset-top))" as
|
|
1121
|
+
"calc(56px + 0px)" -- which parses as NaN and takes the whole inset with it.
|
|
1122
|
+
Padding is a real property, so the same tokens come back resolved to px.
|
|
1123
|
+
|
|
1124
|
+
visibility rather than display: none, because a box that generates no
|
|
1125
|
+
layout has no used values to read. Zero-sized, absolutely positioned and
|
|
1126
|
+
inert, so it costs nothing but the read. */
|
|
1127
|
+
.viewport-probe {
|
|
1128
|
+
position: absolute;
|
|
1129
|
+
top: 0;
|
|
1130
|
+
left: 0;
|
|
1131
|
+
width: 0;
|
|
1132
|
+
height: 0;
|
|
1133
|
+
visibility: hidden;
|
|
1134
|
+
pointer-events: none;
|
|
1135
|
+
padding: var(--_viewport-inset-top) var(--_viewport-inset-right)
|
|
1136
|
+
var(--_viewport-inset-bottom) var(--_viewport-inset-left);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
786
1139
|
.sr-only {
|
|
787
1140
|
position: absolute;
|
|
788
1141
|
width: 1px;
|
|
@@ -798,6 +1151,8 @@ export const STYLES = `
|
|
|
798
1151
|
.messages {
|
|
799
1152
|
flex: 1;
|
|
800
1153
|
overflow-y: auto;
|
|
1154
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
1155
|
+
overscroll-behavior: contain;
|
|
801
1156
|
/* The browser's own scroll anchoring competes with the scroller for the same
|
|
802
1157
|
job and wins unpredictably -- it can hold the view still exactly when we
|
|
803
1158
|
want to follow. Turned off so following is decided in one place. Safari
|
|
@@ -950,6 +1305,8 @@ export const STYLES = `
|
|
|
950
1305
|
.message--assistant pre {
|
|
951
1306
|
padding: 8px 10px;
|
|
952
1307
|
overflow: auto;
|
|
1308
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
1309
|
+
overscroll-behavior: contain;
|
|
953
1310
|
background: var(--_bg);
|
|
954
1311
|
border: 1px solid var(--_border);
|
|
955
1312
|
border-radius: 6px;
|
|
@@ -1128,6 +1485,8 @@ export const STYLES = `
|
|
|
1128
1485
|
border-left: 2px solid var(--_border);
|
|
1129
1486
|
max-height: 220px;
|
|
1130
1487
|
overflow: auto;
|
|
1488
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
1489
|
+
overscroll-behavior: contain;
|
|
1131
1490
|
white-space: pre-wrap;
|
|
1132
1491
|
word-break: break-word;
|
|
1133
1492
|
font-family: inherit;
|
|
@@ -1318,6 +1677,8 @@ export const STYLES = `
|
|
|
1318
1677
|
padding: 6px 8px;
|
|
1319
1678
|
max-height: 160px;
|
|
1320
1679
|
overflow: auto;
|
|
1680
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
1681
|
+
overscroll-behavior: contain;
|
|
1321
1682
|
background: var(--_bg);
|
|
1322
1683
|
border: 1px solid var(--_border);
|
|
1323
1684
|
border-radius: 6px;
|
|
@@ -1812,6 +2173,8 @@ export const STYLES = `
|
|
|
1812
2173
|
resize: none;
|
|
1813
2174
|
max-height: var(--_composer-max-height);
|
|
1814
2175
|
overflow-y: auto;
|
|
2176
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
2177
|
+
overscroll-behavior: contain;
|
|
1815
2178
|
padding: 6px 4px 2px;
|
|
1816
2179
|
background: transparent;
|
|
1817
2180
|
border: none;
|
|
@@ -2096,6 +2459,30 @@ export const STYLES = `
|
|
|
2096
2459
|
opacity: 0.75;
|
|
2097
2460
|
}
|
|
2098
2461
|
|
|
2462
|
+
/* The one control a notice may carry. Quiet, because it reports something
|
|
2463
|
+
already done rather than asking for a decision. */
|
|
2464
|
+
.run-notice-undo {
|
|
2465
|
+
flex: 0 0 auto;
|
|
2466
|
+
margin-inline-start: auto;
|
|
2467
|
+
padding: 2px 8px;
|
|
2468
|
+
border: 1px solid var(--_border);
|
|
2469
|
+
border-radius: var(--_radius);
|
|
2470
|
+
background: var(--_bg);
|
|
2471
|
+
color: var(--_accent);
|
|
2472
|
+
font: inherit;
|
|
2473
|
+
font-size: 0.9em;
|
|
2474
|
+
cursor: pointer;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
.run-notice-undo:hover:not(:disabled) {
|
|
2478
|
+
background: var(--_hover);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
.run-notice-undo:disabled {
|
|
2482
|
+
opacity: 0.5;
|
|
2483
|
+
cursor: default;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2099
2486
|
.run-notice-text {
|
|
2100
2487
|
min-width: 0;
|
|
2101
2488
|
overflow-wrap: anywhere;
|
|
@@ -2240,6 +2627,8 @@ export const STYLES = `
|
|
|
2240
2627
|
padding: 8px 10px;
|
|
2241
2628
|
max-height: 140px;
|
|
2242
2629
|
overflow: auto;
|
|
2630
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
2631
|
+
overscroll-behavior: contain;
|
|
2243
2632
|
font-size: 12px;
|
|
2244
2633
|
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
2245
2634
|
background: var(--_assistant-bg);
|
|
@@ -2428,6 +2817,43 @@ export const STYLES = `
|
|
|
2428
2817
|
}
|
|
2429
2818
|
}
|
|
2430
2819
|
|
|
2820
|
+
/* Touch. Separate from the width breakpoint above on purpose: width decides
|
|
2821
|
+
the layout, the pointer decides which controls make sense, and a touch
|
|
2822
|
+
laptop is coarse-pointered and wide while a narrow desktop window is
|
|
2823
|
+
fine-pointered and small. */
|
|
2824
|
+
@media (pointer: coarse) {
|
|
2825
|
+
/* A 6px edge strip is not a control, it is a trap that eats a scroll. The
|
|
2826
|
+
corners are 14px, still under the 24px floor a target is meant to clear,
|
|
2827
|
+
and the panel has other ways to be resized on a device that has a pointer
|
|
2828
|
+
precise enough to grab one. */
|
|
2829
|
+
.resize-handle {
|
|
2830
|
+
display: none;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
/* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,
|
|
2834
|
+
which clears the WCAG minimum and misses both. */
|
|
2835
|
+
:host {
|
|
2836
|
+
--_action-size: var(--ag-ui-action-size, 44px);
|
|
2837
|
+
--_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);
|
|
2838
|
+
--_send-size: var(--ag-ui-send-size, 44px);
|
|
2839
|
+
/* Missed the first time these were raised, which left the header -- the
|
|
2840
|
+
widget's primary controls, and the only way to reach history, a new chat
|
|
2841
|
+
or the collapse -- at half the size of everything beside it. */
|
|
2842
|
+
--_header-btn-size: var(--ag-ui-header-btn-size, 44px);
|
|
2843
|
+
--_header-gap: var(--ag-ui-header-gap, 6px);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
/* iOS Safari zooms the page when a control under 16px takes focus, which
|
|
2847
|
+
drags the whole fixed panel with it and leaves the user pinching back out
|
|
2848
|
+
of a chat they only wanted to type into. The composer inherits the widget
|
|
2849
|
+
font, which is 14px by default and 13px at compact density, so it has to
|
|
2850
|
+
say 16 outright -- and only ever upward, so a host that has deliberately
|
|
2851
|
+
set something larger keeps it. */
|
|
2852
|
+
.input {
|
|
2853
|
+
font-size: max(16px, var(--_font-size));
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2431
2857
|
.message-action[aria-pressed="true"] {
|
|
2432
2858
|
opacity: 1;
|
|
2433
2859
|
color: var(--_accent);
|
|
@@ -2616,6 +3042,8 @@ export const STYLES = `
|
|
|
2616
3042
|
flex-direction: column;
|
|
2617
3043
|
max-height: 220px;
|
|
2618
3044
|
overflow: auto;
|
|
3045
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
3046
|
+
overscroll-behavior: contain;
|
|
2619
3047
|
background: var(--_bg);
|
|
2620
3048
|
border: 1px solid var(--_border);
|
|
2621
3049
|
border-radius: 8px;
|
|
@@ -2704,6 +3132,8 @@ export const STYLES = `
|
|
|
2704
3132
|
box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
|
|
2705
3133
|
max-height: 60%;
|
|
2706
3134
|
overflow-y: auto;
|
|
3135
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
3136
|
+
overscroll-behavior: contain;
|
|
2707
3137
|
transform-origin: top center;
|
|
2708
3138
|
transition:
|
|
2709
3139
|
opacity var(--_motion) var(--_ease),
|
|
@@ -2879,10 +3309,20 @@ export const STYLES = `
|
|
|
2879
3309
|
}
|
|
2880
3310
|
|
|
2881
3311
|
.drawer-title {
|
|
3312
|
+
/* Takes the slack so the two controls group at the trailing edge. With
|
|
3313
|
+
space-between and three items the middle one floats, which reads as the
|
|
3314
|
+
title and the close button being a pair with New chat wedged between
|
|
3315
|
+
them. Truncates rather than pushing them off the row. */
|
|
3316
|
+
flex: 1 1 auto;
|
|
3317
|
+
min-width: 0;
|
|
3318
|
+
overflow: hidden;
|
|
3319
|
+
text-overflow: ellipsis;
|
|
3320
|
+
white-space: nowrap;
|
|
2882
3321
|
font-weight: 600;
|
|
2883
3322
|
}
|
|
2884
3323
|
|
|
2885
3324
|
.drawer-new {
|
|
3325
|
+
flex: 0 0 auto;
|
|
2886
3326
|
border: 1px solid var(--_border);
|
|
2887
3327
|
border-radius: var(--_radius);
|
|
2888
3328
|
background: var(--_bg);
|
|
@@ -2893,10 +3333,63 @@ export const STYLES = `
|
|
|
2893
3333
|
cursor: pointer;
|
|
2894
3334
|
}
|
|
2895
3335
|
|
|
3336
|
+
/* Narrows the list. Sits under the header rather than in it: the header's two
|
|
3337
|
+
controls act on the conversation, and a field that filters what is below it
|
|
3338
|
+
belongs with what it filters. */
|
|
3339
|
+
/* What is waiting for the run to finish. Above the composer, beside the
|
|
3340
|
+
attachment tray, because both are things already handed over and not yet
|
|
3341
|
+
sent. Each chip is a button: pressing it takes that message back. */
|
|
3342
|
+
.queued {
|
|
3343
|
+
display: flex;
|
|
3344
|
+
flex-wrap: wrap;
|
|
3345
|
+
gap: var(--_space);
|
|
3346
|
+
padding: 0 var(--_pad) var(--_space);
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
.queued-chip {
|
|
3350
|
+
max-width: 100%;
|
|
3351
|
+
overflow: hidden;
|
|
3352
|
+
padding: 4px 10px;
|
|
3353
|
+
border: 1px dashed var(--_border);
|
|
3354
|
+
border-radius: var(--_msg-radius);
|
|
3355
|
+
background: var(--_bg);
|
|
3356
|
+
color: var(--_muted);
|
|
3357
|
+
font: inherit;
|
|
3358
|
+
font-size: 0.9em;
|
|
3359
|
+
text-overflow: ellipsis;
|
|
3360
|
+
white-space: nowrap;
|
|
3361
|
+
cursor: pointer;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
.queued-chip:hover {
|
|
3365
|
+
border-style: solid;
|
|
3366
|
+
color: var(--_fg);
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
.drawer-filter {
|
|
3370
|
+
flex: 0 0 auto;
|
|
3371
|
+
box-sizing: border-box;
|
|
3372
|
+
width: calc(100% - var(--_pad) * 2);
|
|
3373
|
+
margin: var(--_space) var(--_pad) 0;
|
|
3374
|
+
padding: 6px 10px;
|
|
3375
|
+
border: 1px solid var(--_border);
|
|
3376
|
+
border-radius: var(--_radius);
|
|
3377
|
+
background: var(--_input-bg);
|
|
3378
|
+
color: var(--_fg);
|
|
3379
|
+
font: inherit;
|
|
3380
|
+
font-size: 0.9em;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
.drawer-filter::placeholder {
|
|
3384
|
+
color: var(--_muted);
|
|
3385
|
+
}
|
|
3386
|
+
|
|
2896
3387
|
.drawer-list {
|
|
2897
3388
|
flex: 1;
|
|
2898
3389
|
min-height: 0;
|
|
2899
3390
|
overflow-y: auto;
|
|
3391
|
+
/* Scrolling past the end of this must not scroll the page behind it. */
|
|
3392
|
+
overscroll-behavior: contain;
|
|
2900
3393
|
}
|
|
2901
3394
|
|
|
2902
3395
|
.drawer-empty {
|
|
@@ -3017,6 +3510,31 @@ export const STYLES = `
|
|
|
3017
3510
|
background: none;
|
|
3018
3511
|
}
|
|
3019
3512
|
|
|
3513
|
+
/* The way back out of the list. It sits beside New chat, which is the control
|
|
3514
|
+
it must not be mistaken for: one returns you to the conversation you were
|
|
3515
|
+
reading, the other replaces it. */
|
|
3516
|
+
.drawer-close {
|
|
3517
|
+
flex: 0 0 auto;
|
|
3518
|
+
display: inline-flex;
|
|
3519
|
+
align-items: center;
|
|
3520
|
+
justify-content: center;
|
|
3521
|
+
width: 26px;
|
|
3522
|
+
height: 26px;
|
|
3523
|
+
padding: 0;
|
|
3524
|
+
border: 0;
|
|
3525
|
+
border-radius: 6px;
|
|
3526
|
+
background: none;
|
|
3527
|
+
color: inherit;
|
|
3528
|
+
font: inherit;
|
|
3529
|
+
font-size: 18px;
|
|
3530
|
+
line-height: 1;
|
|
3531
|
+
cursor: pointer;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
.drawer-close:hover {
|
|
3535
|
+
background: var(--_hover);
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3020
3538
|
:host([placement="embedded"]) .drawer-panel {
|
|
3021
3539
|
width: 100%;
|
|
3022
3540
|
border-right: none;
|