@aparte/core 0.16.6 → 0.16.8
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/README.md +5 -4
- package/dist/client/aparte-client.d.ts +14 -0
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +33 -18
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +32 -25
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +2 -2
- package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts +6 -6
- package/dist/components/composer/aparte-composer-send.d.ts +3 -3
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts +9 -2
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts +0 -1
- package/dist/components/context/aparte-context.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +6 -6
- package/dist/components/elicitation/aparte-elicitation.d.ts +20 -18
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts +9 -2
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
- package/dist/components/sidebar/aparte-sidebar.d.ts +16 -2
- package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -1
- package/dist/components/split/aparte-split.d.ts +9 -4
- package/dist/components/split/aparte-split.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +3 -2
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +27 -4
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/icon-provider.d.ts +6 -0
- package/dist/config/icon-provider.d.ts.map +1 -1
- package/dist/config/locale.d.ts +3 -1
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/conversations/conversation-controller.d.ts.map +1 -1
- package/dist/custom-elements.json +10458 -10325
- package/dist/{dialog-triggers-CVCxqKg3.js → dialog-triggers-D8wNICrT.js} +36 -19
- package/dist/dialog-triggers-D8wNICrT.js.map +1 -0
- package/dist/generated/element-attributes.d.ts +13 -3
- package/dist/generated/element-attributes.d.ts.map +1 -1
- package/dist/{glyphs-DQTeOj4w.js → glyphs-C8z2oJUg.js} +36 -27
- package/dist/{glyphs-DQTeOj4w.js.map → glyphs-C8z2oJUg.js.map} +1 -1
- package/dist/icons/extended.d.ts +0 -6
- package/dist/icons/extended.d.ts.map +1 -1
- package/dist/icons/glyphs.d.ts +13 -4
- package/dist/icons/glyphs.d.ts.map +1 -1
- package/dist/icons.js +28 -31
- package/dist/icons.js.map +1 -1
- package/dist/index.css +909 -357
- package/dist/index.js +176 -54
- package/dist/index.js.map +1 -1
- package/dist/index.node.js +1 -1
- package/dist/primitives/icon/aparte-icon.d.ts +1 -1
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts +2 -3
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +4 -4
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +21 -12
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/error.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/package.json +3 -2
- package/dist/dialog-triggers-CVCxqKg3.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { $ as resolveConfig, a4 as subscribeConfigChange, a1 as runWithConfig, o as AparteElicitationAbortError, w as buildApprovalPanel, x as buildElicitationPanel, J as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, K as escapeHtml, a8 as writeStreamedMarkdown, C as copyText, N as getSegmentRenderer, O as installDefaultRenderersOnce, r as AparteMessageRepository, a9 as stampSegmentActivity, aa as stampSegmentOnInsert, ab as stampSegmentOnUpdate, ac as renumberSegments, ad as openSegmentIds, ae as isTerminalStatus, af as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls, e as APARTE_DEFAULT_LOCALE, P as installDialogTriggersOnce } from "./dialog-triggers-
|
|
2
|
-
import { A, a, b, c, d, f, g, h, i, j, k, l, m, n, p, q, s, t, u, v, y, z, B, D, E, G, H, I, L, M, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./dialog-triggers-
|
|
1
|
+
import { $ as resolveConfig, a4 as subscribeConfigChange, a1 as runWithConfig, o as AparteElicitationAbortError, w as buildApprovalPanel, x as buildElicitationPanel, J as escapeAttr, F as cssEscape, a7 as mergeSegmentUpdate, K as escapeHtml, a8 as writeStreamedMarkdown, C as copyText, N as getSegmentRenderer, O as installDefaultRenderersOnce, r as AparteMessageRepository, a9 as stampSegmentActivity, aa as stampSegmentOnInsert, ab as stampSegmentOnUpdate, ac as renumberSegments, ad as openSegmentIds, ae as isTerminalStatus, af as adoptMessageSegments, Q as isAwaitingReply, W as populateBubbleFromMessage, a6 as uuid, a0 as revokeAttachmentUrls, e as APARTE_DEFAULT_LOCALE, P as installDialogTriggersOnce } from "./dialog-triggers-D8wNICrT.js";
|
|
2
|
+
import { A, a, b, c, d, f, g, h, i, j, k, l, m, n, p, q, s, t, u, v, y, z, B, D, E, G, H, I, L, M, R, S, T, U, V, X, Y, Z, _, a2, a3, a5 } from "./dialog-triggers-D8wNICrT.js";
|
|
3
|
+
function presenceOn(value) {
|
|
4
|
+
return value === "" ? true : Boolean(value);
|
|
5
|
+
}
|
|
3
6
|
class AparteOption extends HTMLElement {
|
|
4
7
|
static get observedAttributes() {
|
|
5
8
|
return ["value", "disabled", "selected", "data-status"];
|
|
@@ -34,21 +37,13 @@ class AparteOption extends HTMLElement {
|
|
|
34
37
|
return this.hasAttribute("disabled");
|
|
35
38
|
}
|
|
36
39
|
set disabled(val) {
|
|
37
|
-
|
|
38
|
-
this.setAttribute("disabled", "");
|
|
39
|
-
} else {
|
|
40
|
-
this.removeAttribute("disabled");
|
|
41
|
-
}
|
|
40
|
+
this.toggleAttribute("disabled", presenceOn(val));
|
|
42
41
|
}
|
|
43
42
|
get selected() {
|
|
44
43
|
return this.hasAttribute("selected");
|
|
45
44
|
}
|
|
46
45
|
set selected(val) {
|
|
47
|
-
|
|
48
|
-
this.setAttribute("selected", "");
|
|
49
|
-
} else {
|
|
50
|
-
this.removeAttribute("selected");
|
|
51
|
-
}
|
|
46
|
+
this.toggleAttribute("selected", presenceOn(val));
|
|
52
47
|
}
|
|
53
48
|
_updateAriaSelected() {
|
|
54
49
|
this.setAttribute("aria-selected", this.selected ? "true" : "false");
|
|
@@ -104,18 +99,13 @@ class AparteOptgroup extends HTMLElement {
|
|
|
104
99
|
return this.hasAttribute("collapsed");
|
|
105
100
|
}
|
|
106
101
|
set collapsed(val) {
|
|
107
|
-
|
|
108
|
-
this.setAttribute("collapsed", "");
|
|
109
|
-
} else {
|
|
110
|
-
this.removeAttribute("collapsed");
|
|
111
|
-
}
|
|
102
|
+
this.toggleAttribute("collapsed", presenceOn(val));
|
|
112
103
|
}
|
|
113
104
|
get loading() {
|
|
114
105
|
return this.hasAttribute("loading");
|
|
115
106
|
}
|
|
116
107
|
set loading(val) {
|
|
117
|
-
|
|
118
|
-
else this.removeAttribute("loading");
|
|
108
|
+
this.toggleAttribute("loading", presenceOn(val));
|
|
119
109
|
}
|
|
120
110
|
_render() {
|
|
121
111
|
if (this.label) {
|
|
@@ -152,7 +142,9 @@ class AparteOptgroup extends HTMLElement {
|
|
|
152
142
|
if (!loader) {
|
|
153
143
|
loader = document.createElement("div");
|
|
154
144
|
loader.className = "aparte-optgroup-loader";
|
|
155
|
-
|
|
145
|
+
const spinner = document.createElement("span");
|
|
146
|
+
spinner.className = "aparte-spinner-small";
|
|
147
|
+
loader.replaceChildren(spinner, ` ${resolveConfig(this).t("loading") || "Loading…"}`);
|
|
156
148
|
this.appendChild(loader);
|
|
157
149
|
}
|
|
158
150
|
} else if (loader) {
|
|
@@ -211,12 +203,13 @@ class AparteSelect extends HTMLElement {
|
|
|
211
203
|
_boundHandleDocumentClick = this._handleDocumentClick.bind(this);
|
|
212
204
|
_boundHandleKeydown = this._handleKeydown.bind(this);
|
|
213
205
|
static get observedAttributes() {
|
|
214
|
-
return ["value", "placeholder", "disabled", "
|
|
206
|
+
return ["value", "placeholder", "disabled", "searchable", "open"];
|
|
215
207
|
}
|
|
216
208
|
connectedCallback() {
|
|
217
209
|
this._value = this.getAttribute("value") || "";
|
|
218
210
|
this._isOpen = this.hasAttribute("open");
|
|
219
211
|
this._render();
|
|
212
|
+
this._applyDisabled();
|
|
220
213
|
this._setupEventListeners();
|
|
221
214
|
this._setupMutationObserver();
|
|
222
215
|
}
|
|
@@ -241,6 +234,13 @@ class AparteSelect extends HTMLElement {
|
|
|
241
234
|
this._dropdown?.setAttribute("hidden", "");
|
|
242
235
|
}
|
|
243
236
|
}
|
|
237
|
+
if (name === "placeholder") {
|
|
238
|
+
this._updateTriggerLabel();
|
|
239
|
+
this._updateNames();
|
|
240
|
+
}
|
|
241
|
+
if (name === "disabled") {
|
|
242
|
+
this._applyDisabled();
|
|
243
|
+
}
|
|
244
244
|
}
|
|
245
245
|
// ─────────────────────────────────────────────────────────────────────────
|
|
246
246
|
// Public API
|
|
@@ -260,11 +260,7 @@ class AparteSelect extends HTMLElement {
|
|
|
260
260
|
return this._isOpen;
|
|
261
261
|
}
|
|
262
262
|
set open(val) {
|
|
263
|
-
|
|
264
|
-
this.setAttribute("open", "");
|
|
265
|
-
} else {
|
|
266
|
-
this.removeAttribute("open");
|
|
267
|
-
}
|
|
263
|
+
this.toggleAttribute("open", presenceOn(val));
|
|
268
264
|
}
|
|
269
265
|
// ─────────────────────────────────────────────────────────────────────────
|
|
270
266
|
// Rendering
|
|
@@ -558,6 +554,34 @@ class AparteSelect extends HTMLElement {
|
|
|
558
554
|
this._emitChange(previousValue);
|
|
559
555
|
this._trigger?.focus();
|
|
560
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* The combobox and its listbox are named by the host's `aria-label`, else by the
|
|
559
|
+
* placeholder — the same rule the first render applies, re-applied when the
|
|
560
|
+
* placeholder changes. Written only when different: the mutation observer watches
|
|
561
|
+
* this subtree.
|
|
562
|
+
*/
|
|
563
|
+
_updateNames() {
|
|
564
|
+
const name = this.getAttribute("aria-label") || this.getAttribute("placeholder") || "Select...";
|
|
565
|
+
for (const el2 of [this._trigger, this.querySelector(".aparte-select-options")]) {
|
|
566
|
+
if (el2 && el2.getAttribute("aria-label") !== name) el2.setAttribute("aria-label", name);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* `disabled` takes the trigger out of the tab order and says so — opacity alone
|
|
571
|
+
* left it focusable and announced as operable. An open dropdown closes.
|
|
572
|
+
*/
|
|
573
|
+
_applyDisabled() {
|
|
574
|
+
const trigger = this._trigger;
|
|
575
|
+
if (!trigger) return;
|
|
576
|
+
const disabled = this.hasAttribute("disabled");
|
|
577
|
+
trigger.setAttribute("tabindex", disabled ? "-1" : "0");
|
|
578
|
+
if (disabled) {
|
|
579
|
+
trigger.setAttribute("aria-disabled", "true");
|
|
580
|
+
if (this._isOpen) this._closeDropdown();
|
|
581
|
+
} else {
|
|
582
|
+
trigger.removeAttribute("aria-disabled");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
561
585
|
_updateTriggerLabel() {
|
|
562
586
|
const placeholder = this.getAttribute("placeholder") || "Select...";
|
|
563
587
|
const labelText = this._trigger?.querySelector(".aparte-select-label-text");
|
|
@@ -895,7 +919,7 @@ if (typeof customElements !== "undefined" && !customElements.get("aparte-elicita
|
|
|
895
919
|
}
|
|
896
920
|
class AparteChat extends HTMLElement {
|
|
897
921
|
static get observedAttributes() {
|
|
898
|
-
return ["placeholder", "disabled", "center-empty", "attachments"];
|
|
922
|
+
return ["placeholder", "disabled", "submit-on-enter", "center-empty", "attachments"];
|
|
899
923
|
}
|
|
900
924
|
_observer = null;
|
|
901
925
|
/**
|
|
@@ -908,6 +932,7 @@ class AparteChat extends HTMLElement {
|
|
|
908
932
|
this._render();
|
|
909
933
|
this._forwardAttr("placeholder");
|
|
910
934
|
this._forwardAttr("disabled");
|
|
935
|
+
this._forwardAttr("submit-on-enter");
|
|
911
936
|
this._syncEmptyWatch();
|
|
912
937
|
}
|
|
913
938
|
disconnectedCallback() {
|
|
@@ -1187,6 +1212,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1187
1212
|
appendToken(chunk) {
|
|
1188
1213
|
this._content += chunk;
|
|
1189
1214
|
this._updateContent();
|
|
1215
|
+
this._syncCopyAction();
|
|
1190
1216
|
}
|
|
1191
1217
|
/** Set content directly */
|
|
1192
1218
|
setContent(content) {
|
|
@@ -1194,6 +1220,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1194
1220
|
this.setAttribute("content", content);
|
|
1195
1221
|
this._resetMarkdownStream();
|
|
1196
1222
|
this._updateContent();
|
|
1223
|
+
this._syncCopyAction();
|
|
1197
1224
|
}
|
|
1198
1225
|
/**
|
|
1199
1226
|
* Drop the incremental Markdown parser's state.
|
|
@@ -1229,6 +1256,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1229
1256
|
const updated = mergeSegmentUpdate(this._segments[index], updates);
|
|
1230
1257
|
this._segments[index] = updated;
|
|
1231
1258
|
this._applySegmentUpdate(segmentId, updated, updates);
|
|
1259
|
+
this._syncCopyAction();
|
|
1232
1260
|
}
|
|
1233
1261
|
}
|
|
1234
1262
|
/** Append content to a segment */
|
|
@@ -1237,6 +1265,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1237
1265
|
if (segment && "content" in segment) {
|
|
1238
1266
|
segment.content += content;
|
|
1239
1267
|
this._applySegmentUpdate(segmentId, segment, { content: segment.content });
|
|
1268
|
+
this._syncCopyAction();
|
|
1240
1269
|
}
|
|
1241
1270
|
}
|
|
1242
1271
|
/** Get all segments */
|
|
@@ -1266,6 +1295,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1266
1295
|
const el2 = this._segmentsEl?.querySelector(`:scope > [data-segment-id="${cssEscape(segmentId)}"]`);
|
|
1267
1296
|
el2?.remove();
|
|
1268
1297
|
this._updateWaiting();
|
|
1298
|
+
this._syncCopyAction();
|
|
1269
1299
|
}
|
|
1270
1300
|
/** Set attachments (chips shown above message content, user role only) */
|
|
1271
1301
|
setAttachments(attachments) {
|
|
@@ -1420,7 +1450,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1420
1450
|
</div>
|
|
1421
1451
|
<div class="aparte-footer">
|
|
1422
1452
|
<div class="aparte-branch-picker" hidden>
|
|
1423
|
-
<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}"
|
|
1453
|
+
<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-prev" aria-label="${escapeAttr(this._cfg.getLocale().previousResponse ?? "Previous response")}">${this._cfg.getIcon("prevBranch")}</button>
|
|
1424
1454
|
<span class="aparte-branch-label">1 / 1</span>
|
|
1425
1455
|
<!-- The move has to be ANNOUNCED. Pressing the arrows deliberately does not
|
|
1426
1456
|
take focus, so without a live region a screen-reader user gets the new
|
|
@@ -1429,7 +1459,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1429
1459
|
dots, which reads as nothing. No new locale key — the position is
|
|
1430
1460
|
digits, and the buttons beside it already carry translated labels. -->
|
|
1431
1461
|
<span class="aparte-sr-only aparte-branch-status" aria-live="polite"></span>
|
|
1432
|
-
<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next" aria-label="${escapeAttr(this._cfg.getLocale().nextResponse ?? "Next response")}"
|
|
1462
|
+
<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-branch-next" aria-label="${escapeAttr(this._cfg.getLocale().nextResponse ?? "Next response")}">${this._cfg.getIcon("nextBranch")}</button>
|
|
1433
1463
|
</div>
|
|
1434
1464
|
<div class="aparte-action-bar" role="toolbar" aria-label="${escapeAttr(this._cfg.getLocale().messageActions ?? "Message actions")}"></div>
|
|
1435
1465
|
</div>
|
|
@@ -1589,6 +1619,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1589
1619
|
this._contentEl.style.display = this._segments.length > 0 ? "none" : "";
|
|
1590
1620
|
}
|
|
1591
1621
|
this._reflectError();
|
|
1622
|
+
this._syncCopyAction();
|
|
1592
1623
|
}
|
|
1593
1624
|
/**
|
|
1594
1625
|
* Reflect the error state on the bubble: `data-error` on `.aparte-message` while
|
|
@@ -1644,16 +1675,16 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1644
1675
|
this._attachmentsEl.innerHTML = this._attachments.map((a4) => {
|
|
1645
1676
|
const name = escapeHtml(a4.name);
|
|
1646
1677
|
if (a4.type.startsWith("image/")) {
|
|
1647
|
-
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-
|
|
1678
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumbnail__image" src="${escapeHtml(a4.url)}" alt="${name}" loading="lazy" /><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1648
1679
|
}
|
|
1649
|
-
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-
|
|
1680
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumbnail__label">${escapeHtml(this._fileExt(a4.name))}</span><span class="aparte-thumb__name">${name}</span></div>`;
|
|
1650
1681
|
}).join("");
|
|
1651
1682
|
if (!this._cfg.getHostHandlers().attachmentPreview) return;
|
|
1652
1683
|
this._attachmentsEl.querySelectorAll(".aparte-thumb--image").forEach((tile) => {
|
|
1653
1684
|
tile.setAttribute("role", "button");
|
|
1654
1685
|
tile.setAttribute("tabindex", "0");
|
|
1655
1686
|
const open = () => {
|
|
1656
|
-
const img = tile.querySelector(".aparte-
|
|
1687
|
+
const img = tile.querySelector(".aparte-thumbnail__image");
|
|
1657
1688
|
if (!img) return;
|
|
1658
1689
|
this.dispatchEvent(new CustomEvent("aparte-attachment-preview", {
|
|
1659
1690
|
bubbles: true,
|
|
@@ -1790,6 +1821,27 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1790
1821
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1791
1822
|
// Action Bar
|
|
1792
1823
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1824
|
+
/**
|
|
1825
|
+
* What the copy action copies: the reply, not the reasoning. A `thinking` segment is
|
|
1826
|
+
* the model's scratchpad, and the client already keeps it out of the history it sends
|
|
1827
|
+
* back (`_segmentsToText`) for the same reason — copying it along with the answer
|
|
1828
|
+
* pasted a paragraph of deliberation above every reply, which no assistant on the
|
|
1829
|
+
* market does. Segments with no `content` (a tool call) contribute nothing, so a
|
|
1830
|
+
* turn made only of those copies as `''` — and the action bar reads this to decide
|
|
1831
|
+
* whether to offer the button at all.
|
|
1832
|
+
*/
|
|
1833
|
+
_copyableText() {
|
|
1834
|
+
return (this._content || this._segments.filter((s2) => s2.type !== "thinking").map((s2) => s2.content ?? "").join("\n")).trim();
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* The copy button is for text. `hidden` rather than removed: the bar is rebuilt
|
|
1838
|
+
* rarely and the text changes on every streamed token, so the button is toggled in
|
|
1839
|
+
* place from each path that changes what a copy would paste.
|
|
1840
|
+
*/
|
|
1841
|
+
_syncCopyAction() {
|
|
1842
|
+
const btn = this._actionBarEl?.querySelector(".aparte-action-copy");
|
|
1843
|
+
if (btn) btn.hidden = this._copyableText() === "";
|
|
1844
|
+
}
|
|
1793
1845
|
_updateActionBar() {
|
|
1794
1846
|
if (!this._actionBarEl) return;
|
|
1795
1847
|
if (this._editing) {
|
|
@@ -1821,6 +1873,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1821
1873
|
}
|
|
1822
1874
|
}
|
|
1823
1875
|
this._actionBarEl.innerHTML = buttons.join("");
|
|
1876
|
+
this._syncCopyAction();
|
|
1824
1877
|
this._appendCustomActions(icons);
|
|
1825
1878
|
this._applyTranscriptBusy();
|
|
1826
1879
|
this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
|
|
@@ -1912,7 +1965,8 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1912
1965
|
const roles = a4.bubble?.roles ?? ["user", "assistant"];
|
|
1913
1966
|
if (!roles.includes(this._role)) continue;
|
|
1914
1967
|
const btn = document.createElement("button");
|
|
1915
|
-
btn.
|
|
1968
|
+
btn.type = "button";
|
|
1969
|
+
btn.className = "aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-custom";
|
|
1916
1970
|
btn.dataset["action"] = `custom:${a4.id}`;
|
|
1917
1971
|
btn.setAttribute("aria-label", a4.label);
|
|
1918
1972
|
btn.setAttribute("title", a4.label);
|
|
@@ -1926,28 +1980,28 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1926
1980
|
switch (action) {
|
|
1927
1981
|
case "copy": {
|
|
1928
1982
|
const l2 = locale.copy ?? "Copy";
|
|
1929
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
|
|
1983
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-copy" data-action="copy" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.copy()}</button>`;
|
|
1930
1984
|
}
|
|
1931
1985
|
case "edit": {
|
|
1932
1986
|
const l2 = locale.edit ?? "Edit message";
|
|
1933
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
|
|
1987
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-edit" data-action="edit" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.edit()}</button>`;
|
|
1934
1988
|
}
|
|
1935
1989
|
case "retry": {
|
|
1936
1990
|
const l2 = locale.retry ?? "Retry";
|
|
1937
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
|
|
1991
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-retry" data-action="retry" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.retry()}</button>`;
|
|
1938
1992
|
}
|
|
1939
1993
|
case "thumbUp": {
|
|
1940
1994
|
const l2 = locale.feedbackPositive ?? "Good response";
|
|
1941
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
|
|
1995
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-feedback-pos" data-action="feedback-positive" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbUp()}</button>`;
|
|
1942
1996
|
}
|
|
1943
1997
|
case "thumbDown": {
|
|
1944
1998
|
const l2 = locale.feedbackNegative ?? "Bad response";
|
|
1945
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
|
|
1999
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-feedback-neg" data-action="feedback-negative" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${icons.thumbDown()}</button>`;
|
|
1946
2000
|
}
|
|
1947
2001
|
case "info": {
|
|
1948
2002
|
if (!this._usage) return "";
|
|
1949
2003
|
const l2 = locale.messageInfo ?? "Details";
|
|
1950
|
-
return `<button class="aparte-btn aparte-btn--icon aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${this._cfg.getIcon("info")}</button>`;
|
|
2004
|
+
return `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-action-btn aparte-action-info" data-action="info" aria-label="${escapeAttr(l2)}" title="${escapeAttr(l2)}">${this._cfg.getIcon("info")}</button>`;
|
|
1951
2005
|
}
|
|
1952
2006
|
default:
|
|
1953
2007
|
return "";
|
|
@@ -1959,7 +2013,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1959
2013
|
const locale = this._cfg.getLocale();
|
|
1960
2014
|
const saveLabel = locale.editConfirm ?? "Save";
|
|
1961
2015
|
const cancelLabel = locale.editCancel ?? "Cancel";
|
|
1962
|
-
this._actionBarEl.innerHTML = `<button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
|
|
2016
|
+
this._actionBarEl.innerHTML = `<button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--success aparte-action-btn aparte-action-edit-save" data-action="edit-save" aria-label="${escapeAttr(saveLabel)}" title="${escapeAttr(saveLabel)}">${this._cfg.getIcon("check")}</button><button type="button" class="aparte-btn aparte-btn--icon aparte-btn--sm aparte-btn--danger aparte-action-btn aparte-action-edit-cancel" data-action="edit-cancel" aria-label="${escapeAttr(cancelLabel)}" title="${escapeAttr(cancelLabel)}">${this._cfg.getIcon("close")}</button>`;
|
|
1963
2017
|
this._actionBarEl.querySelectorAll(".aparte-action-btn").forEach((btn) => {
|
|
1964
2018
|
btn.addEventListener("click", (e) => this._handleActionClick(e));
|
|
1965
2019
|
});
|
|
@@ -1988,7 +2042,7 @@ class AparteChatBubble extends HTMLElement {
|
|
|
1988
2042
|
}
|
|
1989
2043
|
switch (action) {
|
|
1990
2044
|
case "copy": {
|
|
1991
|
-
const text = this.
|
|
2045
|
+
const text = this._copyableText();
|
|
1992
2046
|
const icons = this._cfg.getIconProvider();
|
|
1993
2047
|
const locale = this._cfg.getLocale();
|
|
1994
2048
|
copyText(text).then(() => {
|
|
@@ -2352,6 +2406,8 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2352
2406
|
_overlayObserver = null;
|
|
2353
2407
|
/** Last `--aparte-bottom-inset` written, in px — style writes only when it changes. */
|
|
2354
2408
|
_overlayInset = -1;
|
|
2409
|
+
/** Last `--aparte-transcript-inset` written on the host, in px — same write-on-change rule. */
|
|
2410
|
+
_transcriptInset = -1;
|
|
2355
2411
|
_boundResetHandler = null;
|
|
2356
2412
|
/**
|
|
2357
2413
|
* When true, _reRenderActivePath() only dispatches aparte-path-changed without
|
|
@@ -2372,6 +2428,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
2372
2428
|
this._render();
|
|
2373
2429
|
this._setupEventListeners();
|
|
2374
2430
|
this._setupObservers();
|
|
2431
|
+
this._updateTranscriptInset();
|
|
2375
2432
|
this._boundResetHandler = () => this.clearAll();
|
|
2376
2433
|
window.addEventListener("aparte-reset", this._boundResetHandler);
|
|
2377
2434
|
window.addEventListener("aparte-config-change", this._onConfigChange);
|
|
@@ -3275,6 +3332,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3275
3332
|
_setupObservers() {
|
|
3276
3333
|
this._resizeObserver = new ResizeObserver(() => {
|
|
3277
3334
|
this._updateOverlayInset();
|
|
3335
|
+
this._updateTranscriptInset();
|
|
3278
3336
|
if (this._isAutoScrollEnabled) {
|
|
3279
3337
|
this._scrollToBottom();
|
|
3280
3338
|
}
|
|
@@ -3355,6 +3413,32 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3355
3413
|
this._overlayInset = inset;
|
|
3356
3414
|
this.style.setProperty("--aparte-bottom-inset", `${inset}px`);
|
|
3357
3415
|
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Publish, on the chat host, how far from the host's inline edge the rows start —
|
|
3418
|
+
* the transcript's padding plus the scrollbar gutter the scroller reserves on both
|
|
3419
|
+
* edges, at whatever step its container query has taken — as
|
|
3420
|
+
* `--aparte-transcript-inset`. The composer pads by it (composer.css), so its box
|
|
3421
|
+
* lands on the row's box at every width. The two used to be independent stacks: the
|
|
3422
|
+
* composer cannot know the gutter and a container query cannot reach it, so they sat
|
|
3423
|
+
* 10px apart at 768 and the gutter's half apart at 1280, and the reading-column demo
|
|
3424
|
+
* showed three left edges 12px apart. Written only when the value changes, for the
|
|
3425
|
+
* same reason `_updateOverlayInset` is. On the HOST, not on this element: the
|
|
3426
|
+
* composer is a sibling, and a custom property only travels down.
|
|
3427
|
+
*/
|
|
3428
|
+
_updateTranscriptInset() {
|
|
3429
|
+
const host = this.parentElement;
|
|
3430
|
+
if (!host) return;
|
|
3431
|
+
const ref = this.querySelector(".aparte-messages-wrapper") ?? this._container ?? this;
|
|
3432
|
+
const pad = parseFloat(getComputedStyle(ref).paddingInlineStart) || 0;
|
|
3433
|
+
const rtl = getComputedStyle(host).direction === "rtl";
|
|
3434
|
+
const refRect = ref.getBoundingClientRect();
|
|
3435
|
+
const hostRect = host.getBoundingClientRect();
|
|
3436
|
+
const raw = rtl ? hostRect.right - refRect.right : refRect.left - hostRect.left;
|
|
3437
|
+
const inset = Math.max(0, Math.round((raw + pad) * 10) / 10);
|
|
3438
|
+
if (inset === this._transcriptInset) return;
|
|
3439
|
+
this._transcriptInset = inset;
|
|
3440
|
+
host.style.setProperty("--aparte-transcript-inset", `${inset}px`);
|
|
3441
|
+
}
|
|
3358
3442
|
/** Is the scroll surface within `_scrollThreshold` of its bottom, right now? */
|
|
3359
3443
|
_isAtBottom() {
|
|
3360
3444
|
if (!this._container) return true;
|
|
@@ -3617,6 +3701,7 @@ class AparteChatViewport extends HTMLElement {
|
|
|
3617
3701
|
this._overlayObserver = null;
|
|
3618
3702
|
this._overlayRoot = null;
|
|
3619
3703
|
this._overlayInset = -1;
|
|
3704
|
+
this._transcriptInset = -1;
|
|
3620
3705
|
this._resizeObserver = null;
|
|
3621
3706
|
this._mutationObserver = null;
|
|
3622
3707
|
if (this._spacerRafId !== null) {
|
|
@@ -4400,6 +4485,7 @@ class AparteComposerSend extends HTMLElement {
|
|
|
4400
4485
|
const root = this._getRoot();
|
|
4401
4486
|
const disabled = !root || root.disabled || root.value.trim() === "";
|
|
4402
4487
|
this.innerHTML = `<button
|
|
4488
|
+
type="button"
|
|
4403
4489
|
class="aparte-btn aparte-btn--primary aparte-btn--solid aparte-btn--icon aparte-cs-button aparte-send-button"
|
|
4404
4490
|
aria-label="${escapeAttr(label)}"
|
|
4405
4491
|
title="${escapeAttr(label)}"
|
|
@@ -4551,6 +4637,7 @@ class AparteComposerCancel extends HTMLElement {
|
|
|
4551
4637
|
const label = resolveConfig(this).t("stopButton") || "Stop";
|
|
4552
4638
|
const icon = this._getStopIcon();
|
|
4553
4639
|
this.innerHTML = `<button
|
|
4640
|
+
type="button"
|
|
4554
4641
|
class="aparte-btn aparte-btn--icon aparte-cc-button"
|
|
4555
4642
|
aria-label="${escapeAttr(label)}"
|
|
4556
4643
|
title="${escapeAttr(label)}"
|
|
@@ -4633,9 +4720,9 @@ class AparteComposerAttachments extends HTMLElement {
|
|
|
4633
4720
|
if (file.type.startsWith("image/")) {
|
|
4634
4721
|
const url = URL.createObjectURL(file);
|
|
4635
4722
|
this._objectUrls.push(url);
|
|
4636
|
-
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-
|
|
4723
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--image" title="${name}"><img class="aparte-thumbnail__image" src="${escapeAttr(url)}" alt="${name}" /><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4637
4724
|
}
|
|
4638
|
-
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-
|
|
4725
|
+
return `<div class="aparte-thumbnail aparte-thumb aparte-thumb--file" title="${name}"><span class="aparte-thumbnail__label">${escapeHtml(this._ext(file.name))}</span><span class="aparte-thumb__name">${name}</span>${remove}</div>`;
|
|
4639
4726
|
}).join("");
|
|
4640
4727
|
this.querySelectorAll(".aparte-thumb__remove").forEach((btn, i2) => {
|
|
4641
4728
|
btn.addEventListener("click", (e) => {
|
|
@@ -4649,7 +4736,7 @@ class AparteComposerAttachments extends HTMLElement {
|
|
|
4649
4736
|
tile.setAttribute("role", "button");
|
|
4650
4737
|
tile.setAttribute("tabindex", "0");
|
|
4651
4738
|
const open = () => {
|
|
4652
|
-
const img = tile.querySelector(".aparte-
|
|
4739
|
+
const img = tile.querySelector(".aparte-thumbnail__image");
|
|
4653
4740
|
if (!img) return;
|
|
4654
4741
|
this.dispatchEvent(new CustomEvent("aparte-attachment-preview", {
|
|
4655
4742
|
bubbles: true,
|
|
@@ -5914,9 +6001,6 @@ class AparteScrollRail extends HTMLElement {
|
|
|
5914
6001
|
if (!customElements.get("aparte-scroll-rail")) {
|
|
5915
6002
|
customElements.define("aparte-scroll-rail", AparteScrollRail);
|
|
5916
6003
|
}
|
|
5917
|
-
function presenceOn(value) {
|
|
5918
|
-
return value === "" ? true : Boolean(value);
|
|
5919
|
-
}
|
|
5920
6004
|
const DRAWER_QUERY = "(max-width: 48rem)";
|
|
5921
6005
|
class AparteSidebar extends HTMLElement {
|
|
5922
6006
|
static get observedAttributes() {
|
|
@@ -5987,6 +6071,8 @@ class AparteSidebar extends HTMLElement {
|
|
|
5987
6071
|
this._watchBreakpoint();
|
|
5988
6072
|
this._syncHidden();
|
|
5989
6073
|
this._lastCollapsed = this.collapsed;
|
|
6074
|
+
if (!this.id) this.id = `aparte-sidebar-${++sidebarIdSeq}`;
|
|
6075
|
+
this._syncToggles();
|
|
5990
6076
|
this._ready = true;
|
|
5991
6077
|
}
|
|
5992
6078
|
/**
|
|
@@ -6030,6 +6116,7 @@ class AparteSidebar extends HTMLElement {
|
|
|
6030
6116
|
if (!this._auto && !this.drawer) this._closedByHost = collapsed;
|
|
6031
6117
|
this._syncScrim();
|
|
6032
6118
|
this._syncHidden();
|
|
6119
|
+
this._syncToggles();
|
|
6033
6120
|
if (collapsed && this.drawer && this._opener?.isConnected) {
|
|
6034
6121
|
this._opener.focus();
|
|
6035
6122
|
this._opener = null;
|
|
@@ -6074,6 +6161,26 @@ class AparteSidebar extends HTMLElement {
|
|
|
6074
6161
|
}
|
|
6075
6162
|
this._syncScrim();
|
|
6076
6163
|
this._syncHidden();
|
|
6164
|
+
this._syncToggles();
|
|
6165
|
+
}
|
|
6166
|
+
/**
|
|
6167
|
+
* Every `[data-aparte-sidebar-toggle]` that drives THIS sidebar says what it does:
|
|
6168
|
+
* `aria-expanded` follows the state, `aria-controls` names the element. Synced from
|
|
6169
|
+
* the three places the state settles (connect, the attribute, the breakpoint), never
|
|
6170
|
+
* from `open()` alone — a close on Escape would otherwise leave an "expanded" that
|
|
6171
|
+
* lies. The row's own `⋯` button already did this; the sidebar's toggle did not.
|
|
6172
|
+
*/
|
|
6173
|
+
_syncToggles() {
|
|
6174
|
+
for (const control of document.querySelectorAll("[data-aparte-sidebar-toggle]")) {
|
|
6175
|
+
if (!this._drives(control)) continue;
|
|
6176
|
+
control.setAttribute("aria-expanded", String(!this.collapsed));
|
|
6177
|
+
control.setAttribute("aria-controls", this.id);
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
/** Whether a toggle control targets this sidebar: named by id, else the nearest one. */
|
|
6181
|
+
_drives(control) {
|
|
6182
|
+
const named = control.getAttribute("data-aparte-sidebar-toggle");
|
|
6183
|
+
return named ? named === this.id : nearestSidebar(control) === this;
|
|
6077
6184
|
}
|
|
6078
6185
|
/**
|
|
6079
6186
|
* A collapsed sidebar is invisible — folded to nothing in the flow, slid off the
|
|
@@ -6126,21 +6233,35 @@ class AparteSidebar extends HTMLElement {
|
|
|
6126
6233
|
}
|
|
6127
6234
|
}
|
|
6128
6235
|
_onKeydown = (e) => {
|
|
6129
|
-
if (
|
|
6236
|
+
if (!this.drawer || this.collapsed) return;
|
|
6237
|
+
if (e.key === "Escape") {
|
|
6130
6238
|
e.preventDefault();
|
|
6131
6239
|
this.close();
|
|
6240
|
+
return;
|
|
6241
|
+
}
|
|
6242
|
+
if (e.key === "Tab") {
|
|
6243
|
+
const focusable = Array.from(this.querySelectorAll(FOCUSABLE));
|
|
6244
|
+
if (focusable.length === 0) return;
|
|
6245
|
+
const first = focusable[0];
|
|
6246
|
+
const last = focusable[focusable.length - 1];
|
|
6247
|
+
const active = document.activeElement;
|
|
6248
|
+
if (!this.contains(active)) {
|
|
6249
|
+
e.preventDefault();
|
|
6250
|
+
first.focus();
|
|
6251
|
+
} else if (e.shiftKey && active === first) {
|
|
6252
|
+
e.preventDefault();
|
|
6253
|
+
last.focus();
|
|
6254
|
+
} else if (!e.shiftKey && active === last) {
|
|
6255
|
+
e.preventDefault();
|
|
6256
|
+
first.focus();
|
|
6257
|
+
}
|
|
6132
6258
|
}
|
|
6133
6259
|
};
|
|
6134
6260
|
/** A `[data-aparte-sidebar-toggle]` anywhere toggles this sidebar — no script in the host. */
|
|
6135
6261
|
_onDocumentClick = (e) => {
|
|
6136
6262
|
const control = e.target?.closest?.("[data-aparte-sidebar-toggle]");
|
|
6137
6263
|
if (!control) return;
|
|
6138
|
-
|
|
6139
|
-
if (named) {
|
|
6140
|
-
if (named !== this.id) return;
|
|
6141
|
-
} else if (nearestSidebar(control) !== this) {
|
|
6142
|
-
return;
|
|
6143
|
-
}
|
|
6264
|
+
if (!this._drives(control)) return;
|
|
6144
6265
|
this.toggle(control);
|
|
6145
6266
|
};
|
|
6146
6267
|
// ─── Search ───────────────────────────────────────────────────────────
|
|
@@ -6174,6 +6295,7 @@ class AparteSidebar extends HTMLElement {
|
|
|
6174
6295
|
this.dataset["ownLabel"] = "locale";
|
|
6175
6296
|
}
|
|
6176
6297
|
}
|
|
6298
|
+
let sidebarIdSeq = 0;
|
|
6177
6299
|
const FOCUSABLE = [
|
|
6178
6300
|
"a[href]",
|
|
6179
6301
|
"button:not([disabled])",
|