@syntrologie/adapt-chatbot 2.27.0 → 2.28.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 (99) hide show
  1. package/dist/AdaptiveChatBar.d.ts +76 -0
  2. package/dist/AdaptiveChatBar.d.ts.map +1 -0
  3. package/dist/AdaptiveChatBar.js +10 -0
  4. package/dist/AdaptiveChatBar.js.map +7 -0
  5. package/dist/AdaptiveChatBarMountable.d.ts +35 -0
  6. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -0
  7. package/dist/AdaptiveChatTrail.d.ts +77 -0
  8. package/dist/AdaptiveChatTrail.d.ts.map +1 -0
  9. package/dist/AdaptiveChatTrail.js +9 -0
  10. package/dist/AdaptiveChatTrail.js.map +7 -0
  11. package/dist/AdaptiveChipsStrip.d.ts +1150 -0
  12. package/dist/AdaptiveChipsStrip.d.ts.map +1 -0
  13. package/dist/AdaptiveChipsStrip.js +11 -0
  14. package/dist/AdaptiveChipsStrip.js.map +7 -0
  15. package/dist/AdaptiveChipsStripMountable.d.ts +24 -0
  16. package/dist/AdaptiveChipsStripMountable.d.ts.map +1 -0
  17. package/dist/ChatAssistantLit.d.ts +22 -50
  18. package/dist/ChatAssistantLit.d.ts.map +1 -1
  19. package/dist/ChatAssistantLit.js +5 -3
  20. package/dist/ChatSession.d.ts +178 -0
  21. package/dist/ChatSession.d.ts.map +1 -0
  22. package/dist/ChatTransport.d.ts +283 -0
  23. package/dist/ChatTransport.d.ts.map +1 -0
  24. package/dist/NavLinkMountable.d.ts +25 -0
  25. package/dist/NavLinkMountable.d.ts.map +1 -0
  26. package/dist/NavLinkMountable.test.d.ts +2 -0
  27. package/dist/NavLinkMountable.test.d.ts.map +1 -0
  28. package/dist/TextAnswerMountable.d.ts +17 -0
  29. package/dist/TextAnswerMountable.d.ts.map +1 -0
  30. package/dist/Turnstile.d.ts +83 -0
  31. package/dist/Turnstile.d.ts.map +1 -0
  32. package/dist/chunk-435KJD27.js +192 -0
  33. package/dist/chunk-435KJD27.js.map +7 -0
  34. package/dist/chunk-AUER7ZCK.js +634 -0
  35. package/dist/chunk-AUER7ZCK.js.map +7 -0
  36. package/dist/chunk-DOMEUJR7.js +382 -0
  37. package/dist/chunk-DOMEUJR7.js.map +7 -0
  38. package/dist/{chunk-W457NMGD.js → chunk-KUO67E2W.js} +1537 -4130
  39. package/dist/chunk-KUO67E2W.js.map +7 -0
  40. package/dist/chunk-QELVKBQV.js +214 -0
  41. package/dist/chunk-QELVKBQV.js.map +7 -0
  42. package/dist/chunk-UC4XU6GH.js +3306 -0
  43. package/dist/chunk-UC4XU6GH.js.map +7 -0
  44. package/dist/elements/ActionHandler.d.ts +34 -0
  45. package/dist/elements/ActionHandler.d.ts.map +1 -0
  46. package/dist/elements/ElementInstanceStore.d.ts +155 -0
  47. package/dist/elements/ElementInstanceStore.d.ts.map +1 -0
  48. package/dist/elements/ElementInstanceStore.test.d.ts +2 -0
  49. package/dist/elements/ElementInstanceStore.test.d.ts.map +1 -0
  50. package/dist/elements/ElementTypeHandler.d.ts +77 -0
  51. package/dist/elements/ElementTypeHandler.d.ts.map +1 -0
  52. package/dist/elements/ItemHandler.d.ts +60 -0
  53. package/dist/elements/ItemHandler.d.ts.map +1 -0
  54. package/dist/elements/ItemHandler.test.d.ts +2 -0
  55. package/dist/elements/ItemHandler.test.d.ts.map +1 -0
  56. package/dist/elements/TileHandler.d.ts +52 -0
  57. package/dist/elements/TileHandler.d.ts.map +1 -0
  58. package/dist/elements/blockRenderer.d.ts +46 -0
  59. package/dist/elements/blockRenderer.d.ts.map +1 -0
  60. package/dist/elements/blockRenderer.test.d.ts +13 -0
  61. package/dist/elements/blockRenderer.test.d.ts.map +1 -0
  62. package/dist/elements/blocks.d.ts +58 -0
  63. package/dist/elements/blocks.d.ts.map +1 -0
  64. package/dist/elements/envelope.d.ts +24 -0
  65. package/dist/elements/envelope.d.ts.map +1 -0
  66. package/dist/elements/fetcher.d.ts +40 -0
  67. package/dist/elements/fetcher.d.ts.map +1 -0
  68. package/dist/elements/index.d.ts +32 -0
  69. package/dist/elements/index.d.ts.map +1 -0
  70. package/dist/elements/types.d.ts +106 -0
  71. package/dist/elements/types.d.ts.map +1 -0
  72. package/dist/observer/__tests__/allowlist.test.d.ts +9 -0
  73. package/dist/observer/__tests__/allowlist.test.d.ts.map +1 -0
  74. package/dist/observer/__tests__/observer-isolation.test.d.ts +13 -0
  75. package/dist/observer/__tests__/observer-isolation.test.d.ts.map +1 -0
  76. package/dist/observer/__tests__/queue.test.d.ts +2 -0
  77. package/dist/observer/__tests__/queue.test.d.ts.map +1 -0
  78. package/dist/observer/__tests__/transport.test.d.ts +2 -0
  79. package/dist/observer/__tests__/transport.test.d.ts.map +1 -0
  80. package/dist/observer/allowlist.d.ts +32 -0
  81. package/dist/observer/allowlist.d.ts.map +1 -0
  82. package/dist/observer/index.d.ts +35 -0
  83. package/dist/observer/index.d.ts.map +1 -0
  84. package/dist/observer/queue.d.ts +57 -0
  85. package/dist/observer/queue.d.ts.map +1 -0
  86. package/dist/observer/transport.d.ts +26 -0
  87. package/dist/observer/transport.d.ts.map +1 -0
  88. package/dist/runtime.d.ts +7 -0
  89. package/dist/runtime.d.ts.map +1 -1
  90. package/dist/runtime.js +1617 -2
  91. package/dist/runtime.js.map +4 -4
  92. package/dist/schema.d.ts +3120 -7
  93. package/dist/schema.d.ts.map +1 -1
  94. package/dist/schema.js +40 -0
  95. package/dist/schema.js.map +2 -2
  96. package/dist/types.d.ts +30 -2
  97. package/dist/types.d.ts.map +1 -1
  98. package/package.json +13 -1
  99. package/dist/chunk-W457NMGD.js.map +0 -7
@@ -0,0 +1,634 @@
1
+ import {
2
+ AuthoringFieldsZ,
3
+ NotifyZ,
4
+ TriggerWhenZ
5
+ } from "./chunk-435KJD27.js";
6
+
7
+ // src/AdaptiveChipsStrip.ts
8
+ import { html, LitElement, nothing } from "lit";
9
+ import { styleMap } from "lit/directives/style-map.js";
10
+ import { z } from "zod";
11
+ var ChipPayloadZ = z.object({
12
+ widget: z.string().min(1).describe(
13
+ "Widget id from the runtime registry \u2014 same vocabulary as a tile's `widget` field. Click \u2192 strip mounts this widget into the chip drawer. Built-ins shipped by adaptive-chatbot: `adaptive-chatbot:text-answer`. Other adaptives register their own (e.g. `adaptive-faq:question-card`, `adaptive-product:mini`)."
14
+ ),
15
+ props: z.record(z.unknown()).optional().describe("Props passed to the mounted widget. The widget owns its own props schema.")
16
+ }).strict().describe(
17
+ "The content that mounts in the chip's drawer when it opens. Same { widget, props } shape used by slots.lid and tile configs \u2014 chips are tiles by another name."
18
+ );
19
+ var SuggestionChipSchema = z.object({
20
+ ...AuthoringFieldsZ,
21
+ kind: z.literal("suggestions:chip").describe(
22
+ "Compositional action type for a single chip in the AdaptiveChipsStrip. Rendered by the strip widget; not executed by the runtime action engine."
23
+ ),
24
+ config: z.object({
25
+ id: z.string().min(1).describe("Stable identity for keyed rendering and event payloads."),
26
+ title: z.string().min(1).describe("Chip label \u2014 single line, ~32ch typical, ellipsis-truncates beyond."),
27
+ payload: ChipPayloadZ
28
+ }).describe(
29
+ "Per-chip configuration: identity, label, and the payload that opens in the drawer."
30
+ ),
31
+ triggerWhen: TriggerWhenZ.describe(
32
+ "Conditional visibility strategy. When null or omitted, the chip is always shown. Use a rules strategy to show/hide based on page URL, event counts, or session metrics \u2014 same evaluator that powers tile activation."
33
+ ),
34
+ notify: NotifyZ.describe(
35
+ "Toast notification shown when triggerWhen transitions false \u2192 true. Required when triggerWhen is set \u2014 pass null to opt out of the toast."
36
+ )
37
+ }).refine((data) => !data.triggerWhen || data.notify !== void 0, {
38
+ message: "notify is required when triggerWhen is present (use null to opt out of notifications)"
39
+ }).describe(
40
+ "A single suggestion chip in the chips strip. Same authoring shape as faq:question \u2014 discriminator + config body + optional conditional gating + optional proactive notification."
41
+ );
42
+ var AdaptiveChipsStrip = class extends LitElement {
43
+ constructor() {
44
+ super(...arguments);
45
+ this.chips = [];
46
+ /**
47
+ * Runtime reference — provides `widgets.has(id)` and
48
+ * `widgets.mount(id, container, props)` so the strip can render
49
+ * chip payloads through the same registry that mounts tile widgets.
50
+ * Null until the host wires it (the AdaptiveChipsStripMountable
51
+ * passes it in from the runtime).
52
+ */
53
+ this.runtimeRef = null;
54
+ /**
55
+ * Strip-level chrome toggle — applies uniformly to all chips +
56
+ * the payload drawer. Resolved upstream from `tile.chromeless`
57
+ * (which itself defaults from `slot.theme.chromeless`). Per-chip
58
+ * inconsistency would break the mini-canvas visual rhythm so
59
+ * there's no per-chip flag — one strip, one style.
60
+ */
61
+ this.chromeless = false;
62
+ this._openId = null;
63
+ /**
64
+ * Horizontal-scroll affordance state. The strip is a single row that
65
+ * overflows horizontally when there are more chips than the panel
66
+ * width can hold; the left/right chevron buttons are visible only
67
+ * when the user can actually scroll in that direction. ResizeObserver
68
+ * + scroll listener keep these in sync.
69
+ */
70
+ this._canScrollLeft = false;
71
+ this._canScrollRight = false;
72
+ this._stripResizeObserver = null;
73
+ /** Unsubscribe handle for the compositional-append/patch/remove event
74
+ * subscription on `runtime.events`. Null when the strip hasn't yet
75
+ * resolved its tile id (no parent container yet) or the runtime
76
+ * doesn't expose an event bus. */
77
+ this._compositionalUnsub = null;
78
+ /** Tile id this strip occupies. Resolved on first `updated()` pass by
79
+ * walking the DOM ancestor chain for `data-tile-id`. Cached because
80
+ * the lookup is stable for the lifetime of the mount. Null while
81
+ * unresolved (or when not running inside a SyntroTileCard, e.g.
82
+ * in standalone test fixtures). */
83
+ this._tileId = null;
84
+ /** Instance ids of chips appended by LLM mounts (vs. authored chip
85
+ * configs from `props.chips`). Tracked so `removeItem(instance_id)`
86
+ * can find and prune by id. */
87
+ this._llmAppendedIds = /* @__PURE__ */ new Set();
88
+ /**
89
+ * Tracks the currently-mounted payload widget so we can swap on chip
90
+ * change + unmount on close. Same lifecycle pattern as
91
+ * `SyntroCanvasOverlay._lidMountHandle`.
92
+ */
93
+ this._payloadMount = null;
94
+ this._onStripScroll = () => {
95
+ this._updateScrollAffordances();
96
+ };
97
+ this._onPayloadClose = () => {
98
+ this._openId = null;
99
+ };
100
+ }
101
+ createRenderRoot() {
102
+ return this;
103
+ }
104
+ disconnectedCallback() {
105
+ super.disconnectedCallback();
106
+ this._unmountPayload();
107
+ this._stripResizeObserver?.disconnect();
108
+ this._stripResizeObserver = null;
109
+ this._compositionalUnsub?.();
110
+ this._compositionalUnsub = null;
111
+ }
112
+ updated(changed) {
113
+ super.updated(changed);
114
+ if (changed.has("_openId") || changed.has("chips") || changed.has("runtimeRef")) {
115
+ this._syncPayloadMount();
116
+ }
117
+ this._syncStripObserver();
118
+ this._updateScrollAffordances();
119
+ if (changed.has("runtimeRef")) {
120
+ this._compositionalUnsub?.();
121
+ this._compositionalUnsub = null;
122
+ this._tileId = this._resolveTileId();
123
+ }
124
+ if (!this._compositionalUnsub && this._tileId == null) {
125
+ this._tileId = this._resolveTileId();
126
+ }
127
+ if (!this._compositionalUnsub) {
128
+ this._subscribeCompositional();
129
+ }
130
+ }
131
+ _resolveTileId() {
132
+ const anchor = this.closest("[data-tile-id]");
133
+ return anchor?.getAttribute("data-tile-id") ?? null;
134
+ }
135
+ /**
136
+ * Subscribe to the runtime's event bus for compositional-item events
137
+ * targeting this strip's tile id. The subscription survives until
138
+ * disconnect; when an event arrives with a non-matching tile_id, it's
139
+ * a no-op (the same subscription handles all strips on the page —
140
+ * filter is just per-event check).
141
+ */
142
+ _subscribeCompositional() {
143
+ const bus = this.runtimeRef?.events;
144
+ if (!bus?.subscribe || !this._tileId) return;
145
+ const tileId = this._tileId;
146
+ this._compositionalUnsub = bus.subscribe((event) => {
147
+ const props = event.props ?? {};
148
+ if (props.tile_id !== tileId) return;
149
+ if (event.name === "element.compositional_append") {
150
+ this.insertItem(
151
+ String(props.instance_id ?? ""),
152
+ props.item,
153
+ props.position ?? "append"
154
+ );
155
+ } else if (event.name === "element.compositional_patch") {
156
+ this.patchItem(String(props.instance_id ?? ""), props.item);
157
+ } else if (event.name === "element.compositional_remove") {
158
+ this.removeItem(String(props.instance_id ?? ""));
159
+ }
160
+ });
161
+ bus.publish?.("element.compositional_replay_request", { tile_id: tileId });
162
+ }
163
+ // ─────────────────────────────────────────────────────────────────────────
164
+ // CompositionalContainer contract — `ItemHandler` translates wire
165
+ // mutations into events that call these methods. The wire `item`
166
+ // shape is fully formed (`{kind, config: {id, title, payload, …}}`);
167
+ // the strip just appends, patches, or removes by `instance_id`.
168
+ // `instance_id` is the canonical id; chip.config.id is set to it by
169
+ // the backend, so they're the same value. We store the id on
170
+ // `chip.config.id` and use that as the lookup key.
171
+ // ─────────────────────────────────────────────────────────────────────────
172
+ /** Append (or prepend) a new chip authored by the LLM. */
173
+ insertItem(instanceId, item, position) {
174
+ if (!instanceId) return;
175
+ if (this._llmAppendedIds.has(instanceId)) return;
176
+ const chip = item;
177
+ if (!chip?.config?.id) return;
178
+ this._llmAppendedIds.add(instanceId);
179
+ this.chips = position === "prepend" ? [chip, ...this.chips] : [...this.chips, chip];
180
+ }
181
+ /** Replace the existing chip's content. v1: full replacement (no
182
+ * in-place merge); the chip widget already re-renders on chip data
183
+ * changes. */
184
+ patchItem(instanceId, item) {
185
+ if (!instanceId) return;
186
+ const idx = this.chips.findIndex((c) => c.config.id === instanceId);
187
+ if (idx < 0) return;
188
+ const next = [...this.chips];
189
+ next[idx] = item;
190
+ this.chips = next;
191
+ }
192
+ /** Remove a chip by instance id. Closes the payload drawer if the
193
+ * removed chip was the open one. */
194
+ removeItem(instanceId) {
195
+ if (!instanceId) return;
196
+ const next = this.chips.filter((c) => c.config.id !== instanceId);
197
+ if (next.length === this.chips.length) return;
198
+ this.chips = next;
199
+ this._llmAppendedIds.delete(instanceId);
200
+ if (this._openId === instanceId) this._openId = null;
201
+ }
202
+ /**
203
+ * Attach a ResizeObserver to the scroll container so we recompute
204
+ * left/right arrow visibility when chips are added/removed or the
205
+ * panel width changes. The scroll listener on the strip element
206
+ * handles user-driven scrolls.
207
+ */
208
+ _syncStripObserver() {
209
+ const strip = this.querySelector("[data-chips-strip]");
210
+ if (!strip) {
211
+ this._stripResizeObserver?.disconnect();
212
+ this._stripResizeObserver = null;
213
+ return;
214
+ }
215
+ if (typeof ResizeObserver === "undefined") return;
216
+ if (!this._stripResizeObserver) {
217
+ this._stripResizeObserver = new ResizeObserver(() => this._updateScrollAffordances());
218
+ }
219
+ this._stripResizeObserver.disconnect();
220
+ this._stripResizeObserver.observe(strip);
221
+ }
222
+ /**
223
+ * Recompute whether the left/right scroll arrows should be visible.
224
+ * Left arrow ⇐ scrollLeft > 0. Right arrow ⇐ there are more chips
225
+ * to the right (scrollLeft + clientWidth < scrollWidth). Threshold
226
+ * of 1px tolerance handles sub-pixel rounding on scaled displays.
227
+ */
228
+ _updateScrollAffordances() {
229
+ const strip = this.querySelector("[data-chips-strip]");
230
+ if (!strip) return;
231
+ const canLeft = strip.scrollLeft > 1;
232
+ const canRight = strip.scrollLeft + strip.clientWidth < strip.scrollWidth - 1;
233
+ if (canLeft !== this._canScrollLeft) this._canScrollLeft = canLeft;
234
+ if (canRight !== this._canScrollRight) this._canScrollRight = canRight;
235
+ }
236
+ /**
237
+ * Scroll the strip by ~80% of its visible width, so each arrow click
238
+ * advances the user past most-but-not-all of the current chips
239
+ * (a small overlap helps the user keep their place in the row).
240
+ */
241
+ _scrollBy(direction) {
242
+ const strip = this.querySelector("[data-chips-strip]");
243
+ if (!strip) return;
244
+ const delta = strip.clientWidth * 0.8 * (direction === "left" ? -1 : 1);
245
+ strip.scrollBy({ left: delta, behavior: "smooth" });
246
+ }
247
+ _unmountPayload() {
248
+ if (this._payloadMount) {
249
+ try {
250
+ this._payloadMount.handle.unmount();
251
+ } catch (err) {
252
+ console.warn("[adaptive-chips-strip] payload widget unmount threw:", err);
253
+ }
254
+ this._payloadMount = null;
255
+ }
256
+ }
257
+ /**
258
+ * Reconcile the payload mount with the current `_openId`:
259
+ * - drawer closed / no runtime / widget id unknown → tear down
260
+ * - same chip + same widget id → no-op (or props update)
261
+ * - different chip → unmount old, mount new
262
+ */
263
+ _syncPayloadMount() {
264
+ const chip = this.chips.find((c) => c.config.id === this._openId) ?? null;
265
+ const widgets = this.runtimeRef?.widgets;
266
+ if (!chip || !widgets || !widgets.has(chip.config.payload.widget)) {
267
+ this._unmountPayload();
268
+ return;
269
+ }
270
+ const nextWidget = chip.config.payload.widget;
271
+ const nextProps = chip.config.payload.props ?? {};
272
+ if (this._payloadMount?.chipId === chip.config.id && this._payloadMount.widgetId === nextWidget) {
273
+ this._payloadMount.handle.update?.(nextProps);
274
+ return;
275
+ }
276
+ this._unmountPayload();
277
+ const container = this.querySelector("[data-chip-payload-mount]");
278
+ if (!container) {
279
+ requestAnimationFrame(() => {
280
+ if (this._openId !== chip.config.id) return;
281
+ const retry = this.querySelector("[data-chip-payload-mount]");
282
+ if (!retry) return;
283
+ const handle2 = widgets.mount(nextWidget, retry, nextProps);
284
+ this._payloadMount = { chipId: chip.config.id, widgetId: nextWidget, handle: handle2 };
285
+ });
286
+ return;
287
+ }
288
+ const handle = widgets.mount(nextWidget, container, nextProps);
289
+ this._payloadMount = { chipId: chip.config.id, widgetId: nextWidget, handle };
290
+ }
291
+ _onChipClick(chip) {
292
+ if (this._openId === chip.config.id) {
293
+ this._openId = null;
294
+ return;
295
+ }
296
+ this._openId = chip.config.id;
297
+ this.dispatchEvent(
298
+ new CustomEvent("chip-revealed", {
299
+ detail: { id: chip.config.id, payload: chip.config.payload },
300
+ bubbles: true,
301
+ composed: true
302
+ })
303
+ );
304
+ }
305
+ _onChipDismiss(chip, e) {
306
+ e.stopPropagation();
307
+ if (this._openId === chip.config.id) this._openId = null;
308
+ this.dispatchEvent(
309
+ new CustomEvent("chip-dismissed", {
310
+ detail: { id: chip.config.id },
311
+ bubbles: true,
312
+ composed: true
313
+ })
314
+ );
315
+ }
316
+ render() {
317
+ const open = this.chips.find((c) => c.config.id === this._openId) ?? null;
318
+ return html`
319
+ <div data-syntro-chips-tile style=${styleMap(rootStyles())}>
320
+ <div data-chips-strip-frame style=${styleMap(stripFrameStyles())}>
321
+ ${this._canScrollLeft ? html`<button
322
+ type="button"
323
+ data-chips-scroll="left"
324
+ aria-label="Scroll suggestions left"
325
+ @click=${() => this._scrollBy("left")}
326
+ style=${styleMap(scrollButtonStyles("left"))}
327
+ >‹</button>` : nothing}
328
+ <div
329
+ data-chips-strip
330
+ style=${styleMap(stripStyles())}
331
+ @scroll=${this._onStripScroll}
332
+ >
333
+ ${this.chips.map(
334
+ (chip) => html`<button
335
+ type="button"
336
+ data-chip-id=${chip.config.id}
337
+ data-syntro-element-id=${chip.config.id}
338
+ data-state=${this._openId === chip.config.id ? "open" : "closed"}
339
+ data-chromeless=${this.chromeless ? "true" : "false"}
340
+ style=${styleMap(chipStyles(this._openId === chip.config.id, this.chromeless))}
341
+ @click=${() => this._onChipClick(chip)}
342
+ >
343
+ <span style=${styleMap(chipTitleStyles())}>${chip.config.title}</span>
344
+ <span style=${styleMap(chipGlyphStyles())}>
345
+ ${this._openId === chip.config.id ? "\u25BC" : "\u203A"}
346
+ </span>
347
+ ${// Per-chip dismiss × is suppressed when the strip is
348
+ // chromeless — LLM-curated suggestions in a mini-
349
+ // canvas surface shouldn't be user-dismissible (the
350
+ // model picks them; the user can't "remove" them
351
+ // any more than they can remove a paragraph of
352
+ // prose). Also suppressed on the open chip in any
353
+ // mode (the chip's button-toggle handles close).
354
+ this.chromeless || this._openId === chip.config.id ? nothing : html`<span
355
+ role="button"
356
+ tabindex="0"
357
+ data-chip-dismiss=${chip.config.id}
358
+ aria-label="Dismiss suggestion"
359
+ title="Dismiss"
360
+ style=${styleMap(chipDismissStyles())}
361
+ @click=${(e) => this._onChipDismiss(chip, e)}
362
+ @keydown=${(e) => {
363
+ if (e.key === "Enter" || e.key === " ") {
364
+ e.preventDefault();
365
+ this._onChipDismiss(chip, e);
366
+ }
367
+ }}
368
+ >×</span>`}
369
+ </button>`
370
+ )}
371
+ </div>
372
+ ${this._canScrollRight ? html`<button
373
+ type="button"
374
+ data-chips-scroll="right"
375
+ aria-label="Scroll suggestions right"
376
+ @click=${() => this._scrollBy("right")}
377
+ style=${styleMap(scrollButtonStyles("right"))}
378
+ >›</button>` : nothing}
379
+ </div>
380
+ ${open ? html`<div
381
+ data-chip-payload
382
+ data-for-chip=${open.config.id}
383
+ data-chromeless=${this.chromeless ? "true" : "false"}
384
+ style=${styleMap(payloadStyles(this.chromeless))}
385
+ >
386
+ <!--
387
+ Drawer header — title (the Q in Q&A) ALWAYS renders
388
+ because it's the actual content of the answer
389
+ pairing, not chrome. Chromeless mode drops the
390
+ surrounding chrome (border, background, ⌃ close)
391
+ but the title stays so the chip → answer link is
392
+ visible above the body. User closes by re-clicking
393
+ the chip.
394
+ -->
395
+ <div
396
+ data-chip-payload-header
397
+ style=${styleMap(payloadHeaderStyles(this.chromeless))}
398
+ >
399
+ <span>${open.config.title}</span>
400
+ ${this.chromeless ? nothing : html`<button
401
+ type="button"
402
+ data-chip-payload-close
403
+ aria-label="Collapse"
404
+ @click=${this._onPayloadClose}
405
+ style=${styleMap(payloadCloseStyles())}
406
+ >⌃</button>`}
407
+ </div>
408
+ <div style=${styleMap(payloadBodyStyles(this.chromeless))}>
409
+ <!--
410
+ Payload widget mounts here imperatively in updated().
411
+ The mount container is a stable element across renders so
412
+ the runtime registry can track + update + unmount
413
+ properly. If the widget id is unknown to the registry,
414
+ we leave a labeled placeholder so the gap is visible
415
+ in development rather than silently empty.
416
+ -->
417
+ ${this.runtimeRef?.widgets?.has(open.config.payload.widget) ? html`<div data-chip-payload-mount></div>` : html`<p style=${styleMap(unresolvedStyles())}>
418
+ Widget <code>${open.config.payload.widget}</code> not registered.
419
+ </p>`}
420
+ </div>
421
+ </div>` : nothing}
422
+ </div>
423
+ `;
424
+ }
425
+ };
426
+ AdaptiveChipsStrip.properties = {
427
+ chips: { attribute: false },
428
+ runtimeRef: { attribute: false },
429
+ chromeless: { type: Boolean, reflect: true },
430
+ _openId: { state: true },
431
+ _canScrollLeft: { state: true },
432
+ _canScrollRight: { state: true }
433
+ };
434
+ function rootStyles() {
435
+ return {
436
+ display: "flex",
437
+ flexDirection: "column",
438
+ gap: "10px",
439
+ width: "100%"
440
+ };
441
+ }
442
+ function stripFrameStyles() {
443
+ return {
444
+ position: "relative",
445
+ display: "flex",
446
+ alignItems: "center",
447
+ width: "100%"
448
+ };
449
+ }
450
+ function stripStyles() {
451
+ return {
452
+ display: "flex",
453
+ flexDirection: "row",
454
+ // Single row — chips never wrap. Horizontal overflow becomes
455
+ // a scrollable region with left/right arrow affordances on either
456
+ // side of the frame. `flex-wrap: wrap` (the old behavior) packed
457
+ // chips into multiple rows, which broke the mini-canvas surface's
458
+ // visual rhythm — chat-bar above, ONE row of chips, payload below.
459
+ flexWrap: "nowrap",
460
+ gap: "8px",
461
+ width: "100%",
462
+ overflowX: "auto",
463
+ overflowY: "hidden",
464
+ // Hide the native scrollbar — the chevron buttons are the
465
+ // affordance. Firefox: `scrollbarWidth: 'none'`. WebKit: the
466
+ // global `::-webkit-scrollbar { display: none }` rule in
467
+ // chatTrailGlobalStyles.ts (which is shared chat-surface CSS) is
468
+ // what hides it across all our scroll surfaces.
469
+ scrollbarWidth: "none",
470
+ scrollSnapType: "x proximity",
471
+ scrollBehavior: "smooth",
472
+ // Don't let touch scroll bubble up to the page when the strip
473
+ // can scroll horizontally — keeps wheel scroll on the canvas
474
+ // intact while letting the strip move under the cursor.
475
+ overscrollBehavior: "contain"
476
+ };
477
+ }
478
+ function scrollButtonStyles(side) {
479
+ return {
480
+ position: "absolute",
481
+ [side]: "0",
482
+ top: "50%",
483
+ transform: "translateY(-50%)",
484
+ zIndex: "2",
485
+ width: "24px",
486
+ height: "24px",
487
+ display: "inline-flex",
488
+ alignItems: "center",
489
+ justifyContent: "center",
490
+ padding: "0",
491
+ borderRadius: "9999px",
492
+ border: "1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)",
493
+ background: "hsl(var(--sc-accent-color) / 0.18)",
494
+ backdropFilter: "blur(6px)",
495
+ WebkitBackdropFilter: "blur(6px)",
496
+ color: "var(--sc-tile-title-color, currentColor)",
497
+ fontSize: "14px",
498
+ lineHeight: "1",
499
+ cursor: "pointer",
500
+ opacity: "0.85",
501
+ transition: "opacity 150ms ease, background 150ms ease"
502
+ };
503
+ }
504
+ function chipStyles(isOpen, _chromeless) {
505
+ return {
506
+ display: "inline-flex",
507
+ alignItems: "center",
508
+ gap: "6px",
509
+ // Flex to text width up to a sane cap. The title span inside owns
510
+ // the ellipsis safety net for genuinely-long titles.
511
+ maxWidth: "32ch",
512
+ color: isOpen ? "var(--sc-tile-title-color, currentColor)" : "var(--sc-tile-text-color, currentColor)",
513
+ fontSize: "12px",
514
+ fontWeight: "600",
515
+ cursor: "pointer",
516
+ transition: "background 180ms ease, border-color 180ms ease, color 180ms ease",
517
+ padding: "6px 12px",
518
+ borderRadius: "9999px",
519
+ border: isOpen ? "1px solid hsl(var(--sc-accent-color) / 0.45)" : "1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)",
520
+ background: isOpen ? "hsl(var(--sc-accent-color) / 0.10)" : "transparent"
521
+ };
522
+ }
523
+ function chipTitleStyles() {
524
+ return {
525
+ whiteSpace: "nowrap",
526
+ overflow: "hidden",
527
+ textOverflow: "ellipsis",
528
+ minWidth: "0"
529
+ };
530
+ }
531
+ function chipDismissStyles() {
532
+ return {
533
+ display: "inline-flex",
534
+ alignItems: "center",
535
+ justifyContent: "center",
536
+ width: "16px",
537
+ height: "16px",
538
+ marginLeft: "2px",
539
+ borderRadius: "9999px",
540
+ color: "inherit",
541
+ fontSize: "12px",
542
+ lineHeight: "1",
543
+ cursor: "pointer",
544
+ opacity: "0.45",
545
+ flexShrink: "0",
546
+ transition: "opacity 150ms ease, background 150ms ease"
547
+ };
548
+ }
549
+ function chipGlyphStyles() {
550
+ return {
551
+ fontSize: "10px",
552
+ opacity: "0.7",
553
+ flexShrink: "0"
554
+ };
555
+ }
556
+ function payloadStyles(chromeless) {
557
+ const base = {
558
+ display: "flex",
559
+ flexDirection: "column",
560
+ gap: "6px",
561
+ color: "var(--sc-tile-title-color, currentColor)"
562
+ };
563
+ if (chromeless) {
564
+ return {
565
+ ...base,
566
+ padding: "4px 0 0",
567
+ borderRadius: "0",
568
+ border: "none",
569
+ background: "transparent"
570
+ };
571
+ }
572
+ return {
573
+ ...base,
574
+ padding: "10px 12px",
575
+ borderRadius: "var(--sc-tile-border-radius, 12px)",
576
+ border: "1px solid hsl(var(--sc-accent-color) / 0.18)",
577
+ background: "hsl(var(--sc-accent-color) / 0.04)"
578
+ };
579
+ }
580
+ function payloadHeaderStyles(chromeless) {
581
+ return {
582
+ display: "flex",
583
+ alignItems: "center",
584
+ justifyContent: "space-between",
585
+ gap: "8px",
586
+ fontSize: "12px",
587
+ fontWeight: "600",
588
+ // In chromeless mode the header is the only visible heading for
589
+ // the answer below (no surrounding box/border). Use the accent
590
+ // color so the Q reads distinctly as the prompt for the A.
591
+ ...chromeless ? { color: "hsl(var(--sc-accent-color) / 0.95)" } : {}
592
+ };
593
+ }
594
+ function payloadCloseStyles() {
595
+ return {
596
+ width: "22px",
597
+ height: "22px",
598
+ display: "inline-flex",
599
+ alignItems: "center",
600
+ justifyContent: "center",
601
+ borderRadius: "9999px",
602
+ background: "transparent",
603
+ border: "1px solid hsl(var(--sc-tile-border-color, 0 0% 100%) / 0.18)",
604
+ color: "inherit",
605
+ fontSize: "11px",
606
+ cursor: "pointer",
607
+ opacity: "0.65"
608
+ };
609
+ }
610
+ function payloadBodyStyles(chromeless) {
611
+ return {
612
+ fontSize: "12px",
613
+ lineHeight: "1.55",
614
+ color: "var(--sc-tile-text-color, currentColor)",
615
+ // No top divider needed when the header is suppressed.
616
+ ...chromeless ? {} : {}
617
+ };
618
+ }
619
+ function unresolvedStyles() {
620
+ return {
621
+ margin: "0",
622
+ fontStyle: "italic",
623
+ opacity: "0.7"
624
+ };
625
+ }
626
+ if (!customElements.get("adaptive-chips-strip")) {
627
+ customElements.define("adaptive-chips-strip", AdaptiveChipsStrip);
628
+ }
629
+
630
+ export {
631
+ SuggestionChipSchema,
632
+ AdaptiveChipsStrip
633
+ };
634
+ //# sourceMappingURL=chunk-AUER7ZCK.js.map