@artooi/ag-ui-web-component 0.46.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +221 -1
  2. package/README.md +298 -31
  3. package/dist/ag-ui-web-component.bundle.js +597 -177
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +37 -2
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +73 -2
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/core/chat_registry.d.ts +23 -0
  10. package/dist/core/chat_registry.d.ts.map +1 -0
  11. package/dist/core/events/bar_height_detail.d.ts +6 -0
  12. package/dist/core/events/bar_height_detail.d.ts.map +1 -0
  13. package/dist/core/events/run_finished_detail.d.ts +6 -0
  14. package/dist/core/events/run_finished_detail.d.ts.map +1 -1
  15. package/dist/core/events/thread_detail.d.ts +27 -0
  16. package/dist/core/events/thread_detail.d.ts.map +1 -0
  17. package/dist/core/run_handlers.d.ts +6 -1
  18. package/dist/core/run_handlers.d.ts.map +1 -1
  19. package/dist/dom/animations.d.ts +21 -6
  20. package/dist/dom/animations.d.ts.map +1 -1
  21. package/dist/dom/highlight_overlay.d.ts +8 -1
  22. package/dist/dom/highlight_overlay.d.ts.map +1 -1
  23. package/dist/dom/ring_color.d.ts +22 -0
  24. package/dist/dom/ring_color.d.ts.map +1 -0
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1164 -95
  28. package/dist/index.js.map +4 -4
  29. package/dist/tools/chat_surface_tools.d.ts +8 -1
  30. package/dist/tools/chat_surface_tools.d.ts.map +1 -1
  31. package/dist/ui/history/conversation_history.d.ts +24 -2
  32. package/dist/ui/history/conversation_history.d.ts.map +1 -1
  33. package/dist/ui/placement/collapse_styles.d.ts.map +1 -1
  34. package/dist/ui/placement/dock_styles.d.ts +2 -0
  35. package/dist/ui/placement/dock_styles.d.ts.map +1 -0
  36. package/dist/ui/placement/panel_placement.d.ts +17 -0
  37. package/dist/ui/placement/panel_placement.d.ts.map +1 -1
  38. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -1
  39. package/dist/ui/placement/surface_for.d.ts +21 -17
  40. package/dist/ui/placement/surface_for.d.ts.map +1 -1
  41. package/dist/ui/progress/tool_call_card.d.ts.map +1 -1
  42. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -1
  43. package/dist/ui/progress/tool_step_group.d.ts.map +1 -1
  44. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -1
  45. package/dist/ui/shell/build_launcher.d.ts +15 -4
  46. package/dist/ui/shell/build_launcher.d.ts.map +1 -1
  47. package/dist/ui/shell/token_styles.d.ts.map +1 -1
  48. package/dist/ui/styles.d.ts.map +1 -1
  49. package/dist/ui/themes/scheme_styles.d.ts.map +1 -1
  50. package/package.json +1 -1
  51. package/src/constants.ts +41 -2
  52. package/src/core/ag_ui_chat.ts +430 -27
  53. package/src/core/chat_registry.ts +53 -0
  54. package/src/core/events/bar_height_detail.ts +5 -0
  55. package/src/core/events/run_finished_detail.ts +6 -0
  56. package/src/core/events/thread_detail.ts +26 -0
  57. package/src/core/run_handlers.ts +9 -4
  58. package/src/dom/animations.ts +30 -31
  59. package/src/dom/highlight_overlay.ts +10 -5
  60. package/src/dom/ring_color.ts +222 -0
  61. package/src/index.ts +7 -0
  62. package/src/tools/chat_surface_tools.ts +18 -8
  63. package/src/ui/history/conversation_history.ts +75 -7
  64. package/src/ui/placement/collapse_styles.ts +248 -2
  65. package/src/ui/placement/dock_styles.ts +91 -0
  66. package/src/ui/placement/embedded_styles.ts +2 -2
  67. package/src/ui/placement/panel_placement.ts +105 -12
  68. package/src/ui/placement/preset_styles.ts +1 -1
  69. package/src/ui/placement/sidebar_styles.ts +1 -1
  70. package/src/ui/placement/small_viewport_styles.ts +14 -0
  71. package/src/ui/placement/surface_for.ts +29 -18
  72. package/src/ui/progress/tool_call_card.ts +18 -8
  73. package/src/ui/progress/tool_group_styles.ts +49 -0
  74. package/src/ui/progress/tool_step_group.ts +3 -0
  75. package/src/ui/progress/tool_step_styles.ts +5 -3
  76. package/src/ui/shell/build_launcher.ts +32 -5
  77. package/src/ui/shell/token_styles.ts +30 -3
  78. package/src/ui/styles.ts +4 -0
  79. package/src/ui/themes/scheme_styles.ts +10 -1
  80. package/src/version.ts +1 -1
@@ -9,6 +9,7 @@ import type {
9
9
  ClientConversationStore,
10
10
  NavigationCheckpoint,
11
11
  } from "../../core/conversation_store.js";
12
+ import type { ThreadDetail } from "../../core/events/thread_detail.js";
12
13
  import type { MessageRole } from "../../core/message_role.js";
13
14
  import { RunIndex } from "../../core/run_index.js";
14
15
  import { toolStatusFromOutcome } from "../../core/tool_outcome.js";
@@ -119,6 +120,11 @@ export interface ConversationHistoryHost {
119
120
  readonly resetState: () => void;
120
121
  /** Swap the composer between Send and Stop, which the composer owns. */
121
122
  readonly setRunning: (running: boolean) => void;
123
+ /**
124
+ * Say the conversation on screen changed, once its transcript is replaced.
125
+ * The element fills in the thread it replaced, which it last reported.
126
+ */
127
+ readonly threadChanged: (threadId: string, reason: ThreadDetail["reason"]) => void;
122
128
  }
123
129
 
124
130
  /**
@@ -210,6 +216,8 @@ export class ConversationHistory {
210
216
  * Also what tells a pick refused for a load from one refused for a run.
211
217
  */
212
218
  #loading: (() => void) | null = null;
219
+ /** The thread switch under way and the promise that settles it, or `null`. */
220
+ #switching: { readonly threadId: string; readonly done: Promise<void> } | null = null;
213
221
 
214
222
  constructor(host: ConversationHistoryHost) {
215
223
  this.#host = host;
@@ -325,17 +333,56 @@ export class ConversationHistory {
325
333
  this.#host.checkpoints.open();
326
334
  }
327
335
 
328
- /** Switch the active conversation to an existing thread and replay it. */
329
- async switchThread(threadId: string): Promise<void> {
336
+ /**
337
+ * Switch the active conversation to an existing thread and replay it.
338
+ *
339
+ * A call for the thread a switch is already loading is handed that switch's
340
+ * own promise, so it settles when the thread is drawn rather than at once,
341
+ * before the replay.
342
+ *
343
+ * A store that rejects the load leaves the element on the thread it was on:
344
+ * the switch has already cleared the transcript, so it points back at the
345
+ * old thread and draws it again, saying nothing, and then rejects with the
346
+ * store's error. Left as it was, the element named a thread it was not
347
+ * showing and never reported it.
348
+ */
349
+ switchThread(threadId: string): Promise<void> {
330
350
  if (threadId === this.#threadId) {
331
- return;
351
+ return this.#switching?.threadId === threadId ? this.#switching.done : Promise.resolve();
332
352
  }
353
+ const previous = this.#threadId;
354
+ const done = this.#switchTo(threadId, previous).finally(() => {
355
+ if (this.#switching === entry) {
356
+ this.#switching = null;
357
+ }
358
+ });
359
+ const entry = { threadId, done };
360
+ this.#switching = entry;
361
+ return done;
362
+ }
363
+
364
+ async #switchTo(threadId: string, previous: string): Promise<void> {
333
365
  this.#host.cancelRun();
334
366
  this.#host.resetState();
335
367
  this.#host.conversationStore().setActiveThread(threadId);
336
368
  this.#threadId = threadId;
337
369
  this.#host.setRunning(false);
338
- await this.rehydrate();
370
+ try {
371
+ await this.rehydrate("switch");
372
+ } catch (error) {
373
+ // Only while this switch is still the one on screen: a newer switch, a
374
+ // new chat or a delete has moved the thread on, and undoing that would
375
+ // put the old one back over it.
376
+ if (this.#threadId === threadId) {
377
+ this.#threadId = previous;
378
+ this.#host.conversationStore().setActiveThread(previous);
379
+ // Drawn again without a word: the thread on screen is the one the last
380
+ // event reported, so there is nothing to tell a host. A store that
381
+ // cannot answer for it either leaves the screen as it is.
382
+ await this.rehydrate(null).catch(() => undefined);
383
+ }
384
+ throw error;
385
+ }
339
386
  }
340
387
 
341
388
  /** Rename a thread, then reload the list that shows its title. */
@@ -355,6 +402,8 @@ export class ConversationHistory {
355
402
  this.#host.resetState();
356
403
  this.adoptActiveThread();
357
404
  this.#host.setRunning(false);
405
+ // No replay to wait for: the thread that took its place is a fresh one.
406
+ this.#host.threadChanged(this.#threadId, "delete");
358
407
  }
359
408
  void this.refreshDrawer();
360
409
  }
@@ -628,8 +677,12 @@ export class ConversationHistory {
628
677
  * for the run it would have resumed -- unless what replaced it is a restore
629
678
  * of the same conversation, still current, which answers for that run
630
679
  * instead; see `#forgetStopped`.
680
+ *
681
+ * `reason` is what the thread event this restore ends with reports as why
682
+ * the conversation changed. A restore that is stopped reports nothing, and
683
+ * neither does one given `null`, which draws the thread already reported.
631
684
  */
632
- async rehydrate(): Promise<void> {
685
+ async rehydrate(reason: ThreadDetail["reason"] | null): Promise<void> {
633
686
  // Claimed before awaiting, and checked by `#restoring` wherever the restore
634
687
  // has waited or run host code. With a slow remote store, picking thread B
635
688
  // then C interleaved both replays into one transcript, and New chat drew
@@ -727,11 +780,26 @@ export class ConversationHistory {
727
780
  }
728
781
  }
729
782
  }
783
+ if (checkpoint === null) {
784
+ this.#noticeIfRunUnfinished(messages);
785
+ }
786
+ // Said once the transcript is the restored one, and only by a restore
787
+ // still current: one that was stopped returned above, so two quick
788
+ // switches report the second alone. Before a resume, because the resume
789
+ // is a run, and waiting for it reported the change a whole answer late.
790
+ if (reason !== null) {
791
+ this.#host.threadChanged(threadId, reason);
792
+ }
730
793
  if (checkpoint !== null) {
794
+ // A listener is host code, and can stop the restore as a replayed
795
+ // renderer can. Resuming then answered the call into the conversation
796
+ // that replaced it, and left the checkpoint for a later visit to resume.
797
+ if (!this.#restoring(generation)) {
798
+ this.#forgetStopped(store, threadId, checkpoint);
799
+ return;
800
+ }
731
801
  await this.#resumeFrom(checkpoint, generation);
732
- return;
733
802
  }
734
- this.#noticeIfRunUnfinished(messages);
735
803
  } finally {
736
804
  // The resumed run starting has let go of the hold already, as a send's
737
805
  // does, and a request that then fails still started one: the client
@@ -1,5 +1,6 @@
1
- // Collapsing and expanding: the animation toward the launcher, and the
2
- // in-flow and page placements that collapse differently or not at all.
1
+ // Collapsing and expanding: the animation toward the launcher, the in-flow
2
+ // and page placements that collapse differently or not at all, and the two
3
+ // collapse-to targets: the bottom bar, and the host's own (hidden here).
3
4
  export const COLLAPSE_STYLES: string = `
4
5
  /* ── Collapse ───────────────────────────────────────────────────────────────
5
6
  Collapsing shrinks the widget to the round floating launcher: the panel
@@ -101,12 +102,18 @@ export const COLLAPSE_STYLES: string = `
101
102
  :host([collapsed][placement="embedded"]) .messages-wrap,
102
103
  :host([collapsed][placement="embedded"]) .messages,
103
104
  :host([collapsed][placement="embedded"]) .input-row,
105
+ :host([collapsed][placement="embedded"]) slot[name="above-composer"],
104
106
  :host([collapsed][placement="embedded"]) .skill-chips,
105
107
  :host([collapsed][placement="embedded"]) .skill-palette,
106
108
  :host([collapsed][placement="embedded"]) .skill-hint {
107
109
  display: none;
108
110
  }
109
111
 
112
+ /* The above-composer slot is in the list above because it is the host's content
113
+ against the composer: a bare slot has no box of its own, so hiding the input
114
+ row alone left the slotted row showing under the header bar. Its sibling slot
115
+ below the composer is a footer and is left to the host. */
116
+
110
117
  /* The page placement has no collapsed state, so it offers no control for one.
111
118
  A dedicated route has no "away" to go to: shrinking it left a strip of
112
119
  application chrome fixed over a route that no longer had an owner, and this
@@ -134,4 +141,243 @@ export const COLLAPSE_STYLES: string = `
134
141
  transform: none;
135
142
  visibility: visible;
136
143
  }
144
+
145
+ /* ── Bottom bar (collapse-to="bar") ──────────────────────────────────────────
146
+ The round launcher drawn as a strip along the bottom edge: the title, the
147
+ unread badge and a running mark, with a history button beside it. The
148
+ wrapper draws no box anywhere else, so the launcher lays out against the
149
+ host as it always has; under the bar it is fixed to the bottom of the
150
+ viewport, outside the host's box, which sits where the panel opens.
151
+
152
+ Here rather than with the launcher because it restyles the launcher's
153
+ collapsed and pressed states, which this module follows in the cascade.
154
+ The strip stays drawn while the panel is open on a wide viewport, as the
155
+ way to minimise it; the phone block hides it then. */
156
+ .dock-bar {
157
+ display: contents;
158
+ }
159
+
160
+ .bar-history {
161
+ display: none;
162
+ }
163
+
164
+ :host([data-collapsed-to="bar"]) .dock-bar {
165
+ position: fixed;
166
+ inset: auto var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left);
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: flex-end;
170
+ gap: 4px;
171
+ box-sizing: border-box;
172
+ min-height: 40px;
173
+ padding: 4px 8px;
174
+ border-top: 1px solid var(--_border);
175
+ background: var(--_bg);
176
+ color: var(--_fg);
177
+ pointer-events: auto;
178
+ }
179
+
180
+ /* A left-hand chat keeps its side: its controls and its panel at the left end. */
181
+ :host([data-collapsed-to="bar"][placement="bottom-left"]) .dock-bar {
182
+ justify-content: flex-start;
183
+ }
184
+
185
+ /* The launcher as the strip's title button. Its resting state hides it behind
186
+ an open panel; the bar keeps it on screen, so every part of that state is
187
+ restated, and the hover and press scales are cancelled at the weight of the
188
+ rules that set them. The title truncates rather than taking a fixed width,
189
+ leaving room beside it for whatever a later release adds to the row. */
190
+ :host([data-collapsed-to="bar"]) .launcher {
191
+ position: static;
192
+ inset: auto;
193
+ flex: 0 1 auto;
194
+ justify-content: flex-start;
195
+ gap: 8px;
196
+ width: auto;
197
+ min-width: 0;
198
+ max-width: 100%;
199
+ height: auto;
200
+ min-height: 32px;
201
+ padding: 4px 10px;
202
+ border-radius: 8px;
203
+ background: transparent;
204
+ color: var(--_fg);
205
+ box-shadow: none;
206
+ touch-action: manipulation;
207
+ opacity: 1;
208
+ transform: none;
209
+ visibility: visible;
210
+ }
211
+
212
+ :host([data-collapsed-to="bar"]) .launcher:hover {
213
+ background: var(--_wash);
214
+ }
215
+
216
+ /* Bare at rest, like the history button beside it. The themes frame the
217
+ round launcher with a border or a shadow at the weight of the rule above
218
+ and later in the cascade, so the child combinator on the bar is there for
219
+ weight, not scope: the launcher is always the bar's direct child. */
220
+ :host([data-collapsed-to="bar"]) .dock-bar > .launcher {
221
+ border: 0;
222
+ box-shadow: none;
223
+ }
224
+
225
+ :host([data-collapsed-to="bar"][collapsed]) .launcher:hover,
226
+ :host([data-collapsed-to="bar"][collapsed]) .launcher:active {
227
+ transform: none;
228
+ }
229
+
230
+ :host([data-collapsed-to="bar"]) .launcher .icon-holder {
231
+ flex: none;
232
+ width: 18px;
233
+ height: 18px;
234
+ }
235
+
236
+ :host([data-collapsed-to="bar"]) .rail-label {
237
+ display: block;
238
+ min-width: 0;
239
+ overflow: hidden;
240
+ font-size: 0.9em;
241
+ font-weight: 600;
242
+ white-space: nowrap;
243
+ text-overflow: ellipsis;
244
+ }
245
+
246
+ :host([data-collapsed-to="bar"]) .launcher-badge {
247
+ position: static;
248
+ flex: none;
249
+ box-shadow: none;
250
+ }
251
+
252
+ /* The running mark: the element already reflects a run as data-running, so
253
+ the bar shows one without a state of its own. */
254
+ :host([data-collapsed-to="bar"][data-running]) .launcher::after {
255
+ content: "";
256
+ flex: none;
257
+ width: 8px;
258
+ height: 8px;
259
+ border-radius: 50%;
260
+ background: var(--_accent);
261
+ animation: ag-ui-dock-running 1.2s ease-in-out infinite;
262
+ }
263
+
264
+ @keyframes ag-ui-dock-running {
265
+ 50% {
266
+ opacity: 0.35;
267
+ }
268
+ }
269
+
270
+ @media (prefers-reduced-motion: reduce) {
271
+ :host([data-collapsed-to="bar"][data-running]) .launcher::after {
272
+ animation: none;
273
+ }
274
+ }
275
+
276
+ :host([data-collapsed-to="bar"]) .bar-history {
277
+ display: inline-flex;
278
+ flex: none;
279
+ align-items: center;
280
+ justify-content: center;
281
+ width: 32px;
282
+ height: 32px;
283
+ padding: 0;
284
+ border: 0;
285
+ border-radius: 8px;
286
+ background: transparent;
287
+ color: var(--_muted);
288
+ font: inherit;
289
+ cursor: pointer;
290
+ }
291
+
292
+ :host([data-collapsed-to="bar"]) .bar-history:hover {
293
+ background: var(--_wash);
294
+ color: var(--_fg);
295
+ }
296
+
297
+ /* Both declare a display of their own, so each needs its own hidden rule. */
298
+ :host([data-collapsed-to]) .dock-bar[hidden],
299
+ :host([data-collapsed-to]) .bar-history[hidden] {
300
+ display: none;
301
+ }
302
+
303
+ /* History from the bar: the panel's own drawer, moved beside the bar while
304
+ the panel is away, drawn as a popover standing on the bar's end. There is
305
+ no backdrop; a press outside the bar closes it, as Escape does.
306
+
307
+ Its outer edge keeps the panel's gutter, so the popover and the panel line
308
+ up on the side they share. The bar is already inset by the host's reserved
309
+ edges, so the gutter alone is the whole offset from it, and the width cap
310
+ leaves the gutter free on both sides. A phone keeps a narrow margin
311
+ instead, in the block below: the panel there is the whole screen, so there
312
+ is no edge to line up with. */
313
+ :host([data-collapsed-to="bar"]) .dock-bar > .drawer {
314
+ inset: auto var(--_edge-gutter) calc(100% + 8px) auto;
315
+ width: min(320px, calc(var(--_viewport-width) - 2 * var(--_edge-gutter)));
316
+ height: min(440px, calc(var(--_viewport-height) - var(--_dock-bottom, 0px) - 16px));
317
+ border-radius: var(--_panel-radius);
318
+ box-shadow: var(--_shadow);
319
+ overflow: hidden;
320
+ }
321
+
322
+ :host([data-collapsed-to="bar"][placement="bottom-left"]) .dock-bar > .drawer {
323
+ inset: auto auto calc(100% + 8px) var(--_edge-gutter);
324
+ }
325
+
326
+ @media (max-width: 600px) {
327
+ :host([data-collapsed-to="bar"]:not([data-small-viewport="off"])) .dock-bar > .drawer {
328
+ inset: auto 8px calc(100% + 8px) auto;
329
+ width: min(320px, calc(100vw - 16px));
330
+ }
331
+
332
+ :host([data-collapsed-to="bar"][placement="bottom-left"]:not([data-small-viewport="off"])) .dock-bar > .drawer {
333
+ inset: auto auto calc(100% + 8px) 8px;
334
+ }
335
+ }
336
+
337
+ :host([data-collapsed-to="bar"]) .dock-bar > .drawer .drawer-backdrop {
338
+ display: none;
339
+ }
340
+
341
+ /* The popover closes in one step. The in-panel drawer slides its panel out
342
+ and holds the overlay's visibility until the slide ends; here the overlay
343
+ is the popover's own box, rounded and clipped, so the slide moved the list
344
+ inside a box that stayed put (read as a shrink) and the box went only once
345
+ the slide had finished. This outweighs the drawer's own hidden rule, so the
346
+ box hides at once, and whatever the list inside does is out of sight. The
347
+ way in needs no rule: the drawer is moved into the bar as it opens, and a
348
+ moved element starts no transition. Nothing is left for reduced motion. */
349
+ :host([data-collapsed-to="bar"]) .dock-bar > .drawer {
350
+ transition: none;
351
+ }
352
+
353
+ /* Border-box, so the list's own border sits inside the popover's clipped box
354
+ rather than 2px past its bottom edge. */
355
+ :host([data-collapsed-to="bar"]) .dock-bar > .drawer .drawer-panel {
356
+ box-sizing: border-box;
357
+ width: 100%;
358
+ border: 1px solid var(--_border);
359
+ border-radius: inherit;
360
+ box-shadow: none;
361
+ }
362
+
363
+ /* ── The host's own bar (collapse-to="none") ─────────────────────────────────
364
+ Collapsed is hidden: no launcher, no rail, no header bar. The host's control
365
+ calls the public API, and the panel rises from its anchor (the dock module)
366
+ or from the placement's corner. Each placement's own collapsed rule is
367
+ overridden at its weight or above, later in the cascade. */
368
+ :host([data-collapsed-to="none"][collapsed]) {
369
+ pointer-events: none;
370
+ }
371
+
372
+ :host([data-collapsed-to="none"][collapsed]) .launcher {
373
+ display: none;
374
+ }
375
+
376
+ :host([data-collapsed-to="none"][collapsed][placement="sidebar"]) {
377
+ width: 0;
378
+ }
379
+
380
+ :host([data-collapsed-to="none"][collapsed][placement="embedded"]) {
381
+ display: none;
382
+ }
137
383
  `;
@@ -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
+ `;
@@ -4,8 +4,8 @@ export const EMBEDDED_STYLES: string = `
4
4
  the widget lives in the host's own layout (fixes overlay/z-index clashes). */
5
5
  :host([placement="embedded"]) {
6
6
  --_position: var(--ag-ui-position, static);
7
- --_width: var(--ag-ui-width, 100%);
8
- --_height: var(--ag-ui-height, 100%);
7
+ --_width: var(--_drag-width, var(--ag-ui-width, 100%));
8
+ --_height: var(--_drag-height, var(--ag-ui-height, 100%));
9
9
  --_max-width: var(--ag-ui-max-width, 100%);
10
10
  --_max-height: var(--ag-ui-max-height, 100%);
11
11
  --_shadow: var(--ag-ui-shadow, none);