@syntrologie/adapt-chatbot 2.26.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.
- package/dist/AdaptiveChatBar.d.ts +76 -0
- package/dist/AdaptiveChatBar.d.ts.map +1 -0
- package/dist/AdaptiveChatBar.js +10 -0
- package/dist/AdaptiveChatBar.js.map +7 -0
- package/dist/AdaptiveChatBarMountable.d.ts +35 -0
- package/dist/AdaptiveChatBarMountable.d.ts.map +1 -0
- package/dist/AdaptiveChatTrail.d.ts +77 -0
- package/dist/AdaptiveChatTrail.d.ts.map +1 -0
- package/dist/AdaptiveChatTrail.js +9 -0
- package/dist/AdaptiveChatTrail.js.map +7 -0
- package/dist/AdaptiveChipsStrip.d.ts +1150 -0
- package/dist/AdaptiveChipsStrip.d.ts.map +1 -0
- package/dist/AdaptiveChipsStrip.js +11 -0
- package/dist/AdaptiveChipsStrip.js.map +7 -0
- package/dist/AdaptiveChipsStripMountable.d.ts +24 -0
- package/dist/AdaptiveChipsStripMountable.d.ts.map +1 -0
- package/dist/ChatAssistantLit.d.ts +23 -21
- package/dist/ChatAssistantLit.d.ts.map +1 -1
- package/dist/ChatAssistantLit.js +5 -3
- package/dist/ChatSession.d.ts +178 -0
- package/dist/ChatSession.d.ts.map +1 -0
- package/dist/ChatTransport.d.ts +283 -0
- package/dist/ChatTransport.d.ts.map +1 -0
- package/dist/NavLinkMountable.d.ts +25 -0
- package/dist/NavLinkMountable.d.ts.map +1 -0
- package/dist/NavLinkMountable.test.d.ts +2 -0
- package/dist/NavLinkMountable.test.d.ts.map +1 -0
- package/dist/TextAnswerMountable.d.ts +17 -0
- package/dist/TextAnswerMountable.d.ts.map +1 -0
- package/dist/Turnstile.d.ts +83 -0
- package/dist/Turnstile.d.ts.map +1 -0
- package/dist/chunk-435KJD27.js +192 -0
- package/dist/chunk-435KJD27.js.map +7 -0
- package/dist/chunk-AUER7ZCK.js +634 -0
- package/dist/chunk-AUER7ZCK.js.map +7 -0
- package/dist/chunk-DOMEUJR7.js +382 -0
- package/dist/chunk-DOMEUJR7.js.map +7 -0
- package/dist/{chunk-O7RWNUVU.js → chunk-KUO67E2W.js} +1573 -4079
- package/dist/chunk-KUO67E2W.js.map +7 -0
- package/dist/chunk-QELVKBQV.js +214 -0
- package/dist/chunk-QELVKBQV.js.map +7 -0
- package/dist/chunk-UC4XU6GH.js +3306 -0
- package/dist/chunk-UC4XU6GH.js.map +7 -0
- package/dist/elements/ActionHandler.d.ts +34 -0
- package/dist/elements/ActionHandler.d.ts.map +1 -0
- package/dist/elements/ElementInstanceStore.d.ts +155 -0
- package/dist/elements/ElementInstanceStore.d.ts.map +1 -0
- package/dist/elements/ElementInstanceStore.test.d.ts +2 -0
- package/dist/elements/ElementInstanceStore.test.d.ts.map +1 -0
- package/dist/elements/ElementTypeHandler.d.ts +77 -0
- package/dist/elements/ElementTypeHandler.d.ts.map +1 -0
- package/dist/elements/ItemHandler.d.ts +60 -0
- package/dist/elements/ItemHandler.d.ts.map +1 -0
- package/dist/elements/ItemHandler.test.d.ts +2 -0
- package/dist/elements/ItemHandler.test.d.ts.map +1 -0
- package/dist/elements/TileHandler.d.ts +52 -0
- package/dist/elements/TileHandler.d.ts.map +1 -0
- package/dist/elements/blockRenderer.d.ts +46 -0
- package/dist/elements/blockRenderer.d.ts.map +1 -0
- package/dist/elements/blockRenderer.test.d.ts +13 -0
- package/dist/elements/blockRenderer.test.d.ts.map +1 -0
- package/dist/elements/blocks.d.ts +58 -0
- package/dist/elements/blocks.d.ts.map +1 -0
- package/dist/elements/envelope.d.ts +24 -0
- package/dist/elements/envelope.d.ts.map +1 -0
- package/dist/elements/fetcher.d.ts +40 -0
- package/dist/elements/fetcher.d.ts.map +1 -0
- package/dist/elements/index.d.ts +32 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/types.d.ts +106 -0
- package/dist/elements/types.d.ts.map +1 -0
- package/dist/observer/__tests__/allowlist.test.d.ts +9 -0
- package/dist/observer/__tests__/allowlist.test.d.ts.map +1 -0
- package/dist/observer/__tests__/observer-isolation.test.d.ts +13 -0
- package/dist/observer/__tests__/observer-isolation.test.d.ts.map +1 -0
- package/dist/observer/__tests__/queue.test.d.ts +2 -0
- package/dist/observer/__tests__/queue.test.d.ts.map +1 -0
- package/dist/observer/__tests__/transport.test.d.ts +2 -0
- package/dist/observer/__tests__/transport.test.d.ts.map +1 -0
- package/dist/observer/allowlist.d.ts +32 -0
- package/dist/observer/allowlist.d.ts.map +1 -0
- package/dist/observer/index.d.ts +35 -0
- package/dist/observer/index.d.ts.map +1 -0
- package/dist/observer/queue.d.ts +57 -0
- package/dist/observer/queue.d.ts.map +1 -0
- package/dist/observer/transport.d.ts +26 -0
- package/dist/observer/transport.d.ts.map +1 -0
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1617 -2
- package/dist/runtime.js.map +4 -4
- package/dist/schema.d.ts +3120 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +40 -0
- package/dist/schema.js.map +2 -2
- package/dist/types.d.ts +30 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +13 -1
- package/dist/chunk-O7RWNUVU.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
|