@artooi/ag-ui-web-component 0.33.1 → 0.34.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/index.js CHANGED
@@ -69,6 +69,7 @@ var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"
69
69
  var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
70
70
  var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
71
71
  var CHART_ACTIVITY_TYPE = "chart";
72
+ var EDGE_MARGIN = 24;
72
73
  var ANNOUNCE_CLEAR_MS = 150;
73
74
 
74
75
  // src/core/ag_ui_chat.ts
@@ -1093,11 +1094,14 @@ function accepts(accept, file) {
1093
1094
 
1094
1095
  // src/ui/chart_block.ts
1095
1096
  var SVG_NS = "http://www.w3.org/2000/svg";
1096
- var WIDTH = 480;
1097
- var HEIGHT = 220;
1098
1097
  var PAD = { top: 20, right: 12, bottom: 30, left: 44 };
1099
- var PLOT_W = WIDTH - PAD.left - PAD.right;
1100
- var PLOT_H = HEIGHT - PAD.top - PAD.bottom;
1098
+ var DEFAULT_WIDTH = 480;
1099
+ var MIN_WIDTH = 220;
1100
+ var HEIGHT_RATIO = 220 / 480;
1101
+ var MIN_HEIGHT = 160;
1102
+ var MAX_HEIGHT = 320;
1103
+ var WIDTH_STEP = 8;
1104
+ var AXIS_CHAR_WIDTH = 5.6;
1101
1105
  var SERIES_COLORS = [
1102
1106
  "var(--ag-ui-chart-1, #4f7cff)",
1103
1107
  "var(--ag-ui-chart-2, #21b573)",
@@ -1109,6 +1113,16 @@ var SERIES_COLORS = [
1109
1113
  function seriesColor(index) {
1110
1114
  return SERIES_COLORS[index % SERIES_COLORS.length];
1111
1115
  }
1116
+ function geometryFor(width) {
1117
+ const w = Math.max(MIN_WIDTH, width);
1118
+ const h = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, Math.round(w * HEIGHT_RATIO)));
1119
+ return {
1120
+ width: w,
1121
+ height: h,
1122
+ plotW: w - PAD.left - PAD.right,
1123
+ plotH: h - PAD.top - PAD.bottom
1124
+ };
1125
+ }
1112
1126
  function el(name, attrs) {
1113
1127
  const node = document.createElementNS(SVG_NS, name);
1114
1128
  for (const [key, value] of Object.entries(attrs)) {
@@ -1143,21 +1157,26 @@ function extent(spec) {
1143
1157
  const min = Math.min(0, ...values);
1144
1158
  return max === min ? { min, max: max + 1 } : { min, max };
1145
1159
  }
1146
- function scaleY(value, min, max) {
1147
- return PAD.top + PLOT_H - (value - min) / (max - min) * PLOT_H;
1160
+ function scaleY(value, min, max, geo) {
1161
+ return PAD.top + geo.plotH - (value - min) / (max - min) * geo.plotH;
1148
1162
  }
1149
- function bandCentre(index, count) {
1150
- const step = PLOT_W / count;
1163
+ function bandCentre(index, count, geo) {
1164
+ const step = geo.plotW / count;
1151
1165
  return PAD.left + step * index + step / 2;
1152
1166
  }
1153
- function drawAxes(svg2, spec, min, max) {
1167
+ function labelStride(labels, geo) {
1168
+ const band = geo.plotW / labels.length;
1169
+ const widest = Math.max(...labels.map((label) => label.length)) * AXIS_CHAR_WIDTH;
1170
+ return Math.max(1, Math.ceil(widest / band));
1171
+ }
1172
+ function drawAxes(svg2, spec, geo, min, max) {
1154
1173
  for (const value of [min, max]) {
1155
- const y2 = scaleY(value, min, max);
1174
+ const y2 = scaleY(value, min, max, geo);
1156
1175
  svg2.appendChild(
1157
1176
  el("line", {
1158
1177
  x1: PAD.left,
1159
1178
  y1: y2,
1160
- x2: WIDTH - PAD.right,
1179
+ x2: geo.width - PAD.right,
1161
1180
  y2,
1162
1181
  stroke: "currentColor",
1163
1182
  "stroke-opacity": value === min ? 0.35 : 0.12
@@ -1167,23 +1186,27 @@ function drawAxes(svg2, spec, min, max) {
1167
1186
  text(String(Math.round(value)), { x: PAD.left - 6, y: y2 + 4, "text-anchor": "end" })
1168
1187
  );
1169
1188
  }
1189
+ const stride = labelStride(spec.labels, geo);
1170
1190
  spec.labels.forEach((label, i) => {
1191
+ if (i % stride !== 0) {
1192
+ return;
1193
+ }
1171
1194
  svg2.appendChild(
1172
1195
  text(label, {
1173
- x: bandCentre(i, spec.labels.length),
1174
- y: HEIGHT - PAD.bottom + 16,
1196
+ x: bandCentre(i, spec.labels.length, geo),
1197
+ y: geo.height - PAD.bottom + 16,
1175
1198
  "text-anchor": "middle"
1176
1199
  })
1177
1200
  );
1178
1201
  });
1179
1202
  }
1180
- function drawBars(svg2, spec, min, max) {
1181
- const step = PLOT_W / spec.labels.length;
1203
+ function drawBars(svg2, spec, geo, min, max) {
1204
+ const step = geo.plotW / spec.labels.length;
1182
1205
  const width = step * 0.7 / spec.series.length;
1183
- const base = scaleY(min, min, max);
1206
+ const base = scaleY(min, min, max, geo);
1184
1207
  spec.series.forEach((series, s) => {
1185
1208
  series.points.forEach((value, i) => {
1186
- const y2 = scaleY(value, min, max);
1209
+ const y2 = scaleY(value, min, max, geo);
1187
1210
  svg2.appendChild(
1188
1211
  el("rect", {
1189
1212
  x: PAD.left + step * i + step * 0.15 + width * s,
@@ -1197,8 +1220,8 @@ function drawBars(svg2, spec, min, max) {
1197
1220
  });
1198
1221
  });
1199
1222
  }
1200
- function drawStacked(svg2, spec, min, max) {
1201
- const step = PLOT_W / spec.labels.length;
1223
+ function drawStacked(svg2, spec, geo, min, max) {
1224
+ const step = geo.plotW / spec.labels.length;
1202
1225
  const width = step * 0.7;
1203
1226
  const running = spec.labels.map(() => 0);
1204
1227
  spec.series.forEach((series, s) => {
@@ -1206,22 +1229,24 @@ function drawStacked(svg2, spec, min, max) {
1206
1229
  const from = running[i] ?? 0;
1207
1230
  const to = from + value;
1208
1231
  running[i] = to;
1209
- const y2 = scaleY(to, min, max);
1232
+ const y2 = scaleY(to, min, max, geo);
1210
1233
  svg2.appendChild(
1211
1234
  el("rect", {
1212
1235
  x: PAD.left + step * i + step * 0.15,
1213
1236
  y: y2,
1214
1237
  width,
1215
- height: Math.max(1, scaleY(from, min, max) - y2),
1238
+ height: Math.max(1, scaleY(from, min, max, geo) - y2),
1216
1239
  fill: seriesColor(s)
1217
1240
  })
1218
1241
  );
1219
1242
  });
1220
1243
  });
1221
1244
  }
1222
- function drawLines(svg2, spec, min, max) {
1245
+ function drawLines(svg2, spec, geo, min, max) {
1223
1246
  spec.series.forEach((series, s) => {
1224
- const points = series.points.map((value, i) => `${bandCentre(i, spec.labels.length)},${scaleY(value, min, max)}`).join(" ");
1247
+ const points = series.points.map(
1248
+ (value, i) => `${bandCentre(i, spec.labels.length, geo)},${scaleY(value, min, max, geo)}`
1249
+ ).join(" ");
1225
1250
  svg2.appendChild(
1226
1251
  el("polyline", {
1227
1252
  points,
@@ -1233,13 +1258,13 @@ function drawLines(svg2, spec, min, max) {
1233
1258
  );
1234
1259
  });
1235
1260
  }
1236
- function drawScatter(svg2, spec, min, max) {
1261
+ function drawScatter(svg2, spec, geo, min, max) {
1237
1262
  spec.series.forEach((series, s) => {
1238
1263
  series.points.forEach((value, i) => {
1239
1264
  svg2.appendChild(
1240
1265
  el("circle", {
1241
- cx: bandCentre(i, spec.labels.length),
1242
- cy: scaleY(value, min, max),
1266
+ cx: bandCentre(i, spec.labels.length, geo),
1267
+ cy: scaleY(value, min, max, geo),
1243
1268
  r: 4,
1244
1269
  fill: seriesColor(s),
1245
1270
  "fill-opacity": 0.85
@@ -1248,11 +1273,11 @@ function drawScatter(svg2, spec, min, max) {
1248
1273
  });
1249
1274
  });
1250
1275
  }
1251
- function drawPie(svg2, points) {
1276
+ function drawPie(svg2, points, geo) {
1252
1277
  const total = points.reduce((sum, value) => sum + value, 0);
1253
- const cx = WIDTH / 2;
1254
- const cy = PAD.top + PLOT_H / 2;
1255
- const r = Math.min(PLOT_W, PLOT_H) / 2;
1278
+ const cx = geo.width / 2;
1279
+ const cy = PAD.top + geo.plotH / 2;
1280
+ const r = Math.min(geo.plotW, geo.plotH) / 2;
1256
1281
  if (total === 0) {
1257
1282
  svg2.appendChild(
1258
1283
  el("circle", { cx, cy, r, fill: "none", stroke: "currentColor", "stroke-opacity": 0.3 })
@@ -1281,6 +1306,35 @@ function drawPie(svg2, points) {
1281
1306
  angle = end;
1282
1307
  });
1283
1308
  }
1309
+ function drawSvg(spec, geo) {
1310
+ const svg2 = el("svg", {
1311
+ viewBox: `0 0 ${geo.width} ${geo.height}`,
1312
+ width: "100%",
1313
+ role: "img"
1314
+ });
1315
+ svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
1316
+ if (spec.kind === "pie") {
1317
+ const first = spec.series[0];
1318
+ drawPie(
1319
+ svg2,
1320
+ first.points.map((value) => Math.max(0, value)),
1321
+ geo
1322
+ );
1323
+ return svg2;
1324
+ }
1325
+ const { min, max } = extent(spec);
1326
+ drawAxes(svg2, spec, geo, min, max);
1327
+ if (spec.kind === "bar") {
1328
+ drawBars(svg2, spec, geo, min, max);
1329
+ } else if (spec.kind === "stacked") {
1330
+ drawStacked(svg2, spec, geo, min, max);
1331
+ } else if (spec.kind === "line") {
1332
+ drawLines(svg2, spec, geo, min, max);
1333
+ } else {
1334
+ drawScatter(svg2, spec, geo, min, max);
1335
+ }
1336
+ return svg2;
1337
+ }
1284
1338
  function buildLegend(entries2) {
1285
1339
  if (entries2.length < 2) {
1286
1340
  return null;
@@ -1299,6 +1353,12 @@ function buildLegend(entries2) {
1299
1353
  });
1300
1354
  return row;
1301
1355
  }
1356
+ function fitToWidth(block, redraw) {
1357
+ const observer = new ResizeObserver(() => {
1358
+ redraw(Math.round(block.clientWidth / WIDTH_STEP) * WIDTH_STEP);
1359
+ });
1360
+ observer.observe(block);
1361
+ }
1302
1362
  function renderChart(spec) {
1303
1363
  if (spec.labels.length === 0 || spec.series.length === 0) {
1304
1364
  return null;
@@ -1313,27 +1373,8 @@ function renderChart(spec) {
1313
1373
  heading.textContent = spec.title;
1314
1374
  block.appendChild(heading);
1315
1375
  }
1316
- const svg2 = el("svg", { viewBox: `0 0 ${WIDTH} ${HEIGHT}`, width: "100%", role: "img" });
1317
- svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
1318
- if (spec.kind === "pie") {
1319
- const first = spec.series[0];
1320
- drawPie(
1321
- svg2,
1322
- first.points.map((value) => Math.max(0, value))
1323
- );
1324
- } else {
1325
- const { min, max } = extent(spec);
1326
- drawAxes(svg2, spec, min, max);
1327
- if (spec.kind === "bar") {
1328
- drawBars(svg2, spec, min, max);
1329
- } else if (spec.kind === "stacked") {
1330
- drawStacked(svg2, spec, min, max);
1331
- } else if (spec.kind === "line") {
1332
- drawLines(svg2, spec, min, max);
1333
- } else {
1334
- drawScatter(svg2, spec, min, max);
1335
- }
1336
- }
1376
+ let width = DEFAULT_WIDTH;
1377
+ let svg2 = drawSvg(spec, geometryFor(width));
1337
1378
  block.appendChild(svg2);
1338
1379
  const legend = buildLegend(
1339
1380
  spec.kind === "pie" ? spec.labels : spec.series.map((series) => series.label)
@@ -1341,6 +1382,15 @@ function renderChart(spec) {
1341
1382
  if (legend !== null) {
1342
1383
  block.appendChild(legend);
1343
1384
  }
1385
+ fitToWidth(block, (measured) => {
1386
+ if (measured === width) {
1387
+ return;
1388
+ }
1389
+ width = measured;
1390
+ const next = drawSvg(spec, geometryFor(width));
1391
+ svg2.replaceWith(next);
1392
+ svg2 = next;
1393
+ });
1344
1394
  return block;
1345
1395
  }
1346
1396
 
@@ -1707,6 +1757,15 @@ function clampLauncher(launcher, viewport) {
1707
1757
  };
1708
1758
  }
1709
1759
 
1760
+ // src/ui/clamp_panel.ts
1761
+ function clampPanel(host, viewport, margin = EDGE_MARGIN) {
1762
+ const width = host.right - host.left;
1763
+ const height = host.bottom - host.top;
1764
+ const left = Math.max(margin, Math.min(host.left, viewport.width - margin - width));
1765
+ const top = Math.max(margin, Math.min(host.top, viewport.height - margin - height));
1766
+ return { left, top, right: left + width, bottom: top + height };
1767
+ }
1768
+
1710
1769
  // src/ui/confirmation_card.ts
1711
1770
  function actionButton2(modifier, label) {
1712
1771
  const button2 = document.createElement("button");
@@ -1963,8 +2022,29 @@ function enableLauncherDrag(launcher, options) {
1963
2022
  launcher.addEventListener("blur", settle);
1964
2023
  }
1965
2024
 
2025
+ // src/ui/place_widget.ts
2026
+ function placeWidget(host, launcher, corner, viewport) {
2027
+ return {
2028
+ hostInset: inset({
2029
+ top: corner.y === "top" ? host.top : null,
2030
+ right: corner.x === "right" ? viewport.width - host.right : null,
2031
+ bottom: corner.y === "bottom" ? viewport.height - host.bottom : null,
2032
+ left: corner.x === "left" ? host.left : null
2033
+ }),
2034
+ launcherInset: inset({
2035
+ top: corner.y === "top" ? launcher.top - host.top : null,
2036
+ right: corner.x === "right" ? host.right - (launcher.left + launcher.width) : null,
2037
+ bottom: corner.y === "bottom" ? host.bottom - (launcher.top + launcher.height) : null,
2038
+ left: corner.x === "left" ? launcher.left - host.left : null
2039
+ })
2040
+ };
2041
+ }
2042
+ function inset(sides) {
2043
+ const side = (value) => value === null ? "auto" : `${Math.round(value)}px`;
2044
+ return `${side(sides.top)} ${side(sides.right)} ${side(sides.bottom)} ${side(sides.left)}`;
2045
+ }
2046
+
1966
2047
  // src/ui/launcher_placement.ts
1967
- var EDGE_MARGIN = 24;
1968
2048
  function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
1969
2049
  const roomRunningRight = viewport.width - launcher.left;
1970
2050
  const roomRunningLeft = launcher.left + launcher.width;
@@ -1976,34 +2056,17 @@ function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
1976
2056
  };
1977
2057
  const wantedLeft = corner.x === "left" ? launcher.left : launcher.left + launcher.width - panel.width;
1978
2058
  const wantedTop = corner.y === "top" ? launcher.top : launcher.top + launcher.height - panel.height;
1979
- const hostLeft = clamp(wantedLeft, margin, viewport.width - margin - panel.width);
1980
- const hostTop = clamp(wantedTop, margin, viewport.height - margin - panel.height);
1981
- const hostRight = hostLeft + panel.width;
1982
- const hostBottom = hostTop + panel.height;
1983
- return {
1984
- corner,
1985
- hostInset: inset({
1986
- top: corner.y === "top" ? hostTop : null,
1987
- right: corner.x === "right" ? viewport.width - hostRight : null,
1988
- bottom: corner.y === "bottom" ? viewport.height - hostBottom : null,
1989
- left: corner.x === "left" ? hostLeft : null
1990
- }),
1991
- // Measured from the host box's pinned corner to the launcher's matching
1992
- // corner, so the launcher lands exactly where it was dropped.
1993
- launcherInset: inset({
1994
- top: corner.y === "top" ? launcher.top - hostTop : null,
1995
- right: corner.x === "right" ? hostRight - (launcher.left + launcher.width) : null,
1996
- bottom: corner.y === "bottom" ? hostBottom - (launcher.top + launcher.height) : null,
1997
- left: corner.x === "left" ? launcher.left - hostLeft : null
1998
- })
1999
- };
2000
- }
2001
- function clamp(value, low, high) {
2002
- return Math.max(low, Math.min(value, high));
2003
- }
2004
- function inset(sides) {
2005
- const side = (value) => value === null ? "auto" : `${Math.round(value)}px`;
2006
- return `${side(sides.top)} ${side(sides.right)} ${side(sides.bottom)} ${side(sides.left)}`;
2059
+ const host = clampPanel(
2060
+ {
2061
+ left: wantedLeft,
2062
+ top: wantedTop,
2063
+ right: wantedLeft + panel.width,
2064
+ bottom: wantedTop + panel.height
2065
+ },
2066
+ viewport,
2067
+ margin
2068
+ );
2069
+ return { corner, ...placeWidget(host, launcher, corner, viewport) };
2007
2070
  }
2008
2071
 
2009
2072
  // src/ui/message_actions.ts
@@ -2374,6 +2437,55 @@ function place(button2, rect) {
2374
2437
  button2.style.left = `${Math.min(Math.max(centre, half), width - half)}px`;
2375
2438
  }
2376
2439
 
2440
+ // src/ui/panel_drag.ts
2441
+ var DRAG_THRESHOLD2 = 4;
2442
+ var CONTROLS = "button, a[href], input, select, textarea, [contenteditable]";
2443
+ function enablePanelDrag(handle, options) {
2444
+ handle.addEventListener("pointerdown", (event) => {
2445
+ if (event.button !== 0 || !options.enabled() || onControl(event, handle)) {
2446
+ return;
2447
+ }
2448
+ const start = options.rect();
2449
+ const originX = event.clientX;
2450
+ const originY = event.clientY;
2451
+ let dragging = false;
2452
+ const boxAt2 = (x2, y2) => {
2453
+ const dx = x2 - originX;
2454
+ const dy = y2 - originY;
2455
+ return {
2456
+ left: start.left + dx,
2457
+ top: start.top + dy,
2458
+ right: start.right + dx,
2459
+ bottom: start.bottom + dy
2460
+ };
2461
+ };
2462
+ const onMove = (move) => {
2463
+ if (!dragging && Math.hypot(move.clientX - originX, move.clientY - originY) < DRAG_THRESHOLD2) {
2464
+ return;
2465
+ }
2466
+ dragging = true;
2467
+ handle.setAttribute("data-dragging", "true");
2468
+ options.apply(boxAt2(move.clientX, move.clientY), start);
2469
+ };
2470
+ const onUp = (up) => {
2471
+ window.removeEventListener("pointermove", onMove);
2472
+ window.removeEventListener("pointerup", onUp);
2473
+ if (!dragging) {
2474
+ return;
2475
+ }
2476
+ handle.removeAttribute("data-dragging");
2477
+ options.commit(boxAt2(up.clientX, up.clientY), start);
2478
+ };
2479
+ event.preventDefault();
2480
+ window.addEventListener("pointermove", onMove);
2481
+ window.addEventListener("pointerup", onUp);
2482
+ });
2483
+ }
2484
+ function onControl(event, handle) {
2485
+ const path = event.composedPath();
2486
+ return path.slice(0, path.indexOf(handle)).some((node) => node instanceof Element && node.matches(CONTROLS));
2487
+ }
2488
+
2377
2489
  // src/ui/prettify_tool_name.ts
2378
2490
  function prettifyToolName(name) {
2379
2491
  const spaced = name.replace(/[._-]+/g, " ").trim();
@@ -2876,11 +2988,11 @@ function createDOMPurify() {
2876
2988
  const originalDocument = document2;
2877
2989
  const currentScript = originalDocument.currentScript;
2878
2990
  window2.DocumentFragment;
2879
- const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element = window2.Element, NodeFilter2 = window2.NodeFilter, _window$NamedNodeMap = window2.NamedNodeMap;
2991
+ const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element2 = window2.Element, NodeFilter2 = window2.NodeFilter, _window$NamedNodeMap = window2.NamedNodeMap;
2880
2992
  _window$NamedNodeMap === void 0 ? window2.NamedNodeMap || window2.MozNamedAttrMap : _window$NamedNodeMap;
2881
2993
  window2.HTMLFormElement;
2882
2994
  const DOMParser = window2.DOMParser, trustedTypes = window2.trustedTypes;
2883
- const ElementPrototype = Element.prototype;
2995
+ const ElementPrototype = Element2.prototype;
2884
2996
  const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
2885
2997
  const remove = lookupGetter(ElementPrototype, "remove");
2886
2998
  const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
@@ -5443,8 +5555,8 @@ function renderMarkdown(text4, options) {
5443
5555
  }
5444
5556
 
5445
5557
  // src/ui/resize_handle.ts
5446
- var MIN_WIDTH = 280;
5447
- var MIN_HEIGHT = 240;
5558
+ var MIN_WIDTH2 = 280;
5559
+ var MIN_HEIGHT2 = 240;
5448
5560
  var STEP2 = 16;
5449
5561
  var COARSE_STEP2 = 64;
5450
5562
  function createResizeHandle(grip, options) {
@@ -5526,11 +5638,11 @@ function movable(grip, axis) {
5526
5638
  return axis === "both" || grip.x !== void 0;
5527
5639
  }
5528
5640
  function boxAt(grip, axis, rect, x2, y2) {
5529
- const left = grip.x === "left" ? Math.min(x2, rect.right - MIN_WIDTH) : rect.left;
5530
- const right = grip.x === "right" ? Math.max(x2, rect.left + MIN_WIDTH) : rect.right;
5641
+ const left = grip.x === "left" ? Math.min(x2, rect.right - MIN_WIDTH2) : rect.left;
5642
+ const right = grip.x === "right" ? Math.max(x2, rect.left + MIN_WIDTH2) : rect.right;
5531
5643
  const vertical = axis === "both";
5532
- const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom - MIN_HEIGHT) : rect.top;
5533
- const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top + MIN_HEIGHT) : rect.bottom;
5644
+ const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom - MIN_HEIGHT2) : rect.top;
5645
+ const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top + MIN_HEIGHT2) : rect.bottom;
5534
5646
  return { left, top, right, bottom };
5535
5647
  }
5536
5648
 
@@ -5903,6 +6015,11 @@ var STYLES = `
5903
6015
  --_glyph-size: var(--ag-ui-glyph-size, 18px);
5904
6016
  --_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
5905
6017
 
6018
+ /* How wide a chart is allowed to get. A cap rather than a width: below it
6019
+ the chart fills its column, and above it a wider panel is just a wider
6020
+ panel. */
6021
+ --_chart-max-width: var(--ag-ui-chart-max-width, 480px);
6022
+
5906
6023
  /* Unread badge on the launcher. */
5907
6024
  --_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
5908
6025
  --_badge-fg: var(--ag-ui-badge-fg, #ffffff);
@@ -6293,6 +6410,41 @@ var STYLES = `
6293
6410
  color: var(--_header-fg);
6294
6411
  }
6295
6412
 
6413
+ /* The header is the panel's title bar: on the placements that let the widget
6414
+ be moved it drags the whole thing, matching the launcher's own drag. The
6415
+ selector lists those placements rather than asking JS to stamp an attribute,
6416
+ because a cursor that appears one frame late reads as a control that only
6417
+ sometimes exists. touch-action keeps a finger drag here moving the panel
6418
+ instead of scrolling the page behind it. */
6419
+ :host(:not([placement])) .header,
6420
+ :host([placement=""]) .header,
6421
+ :host([placement="floating"]) .header,
6422
+ :host([placement="bottom-left"]) .header {
6423
+ cursor: move;
6424
+ touch-action: none;
6425
+ }
6426
+
6427
+ /* A host that turned the drag off keeps a header that says so. */
6428
+ :host([data-launcher-drag="false"]) .header {
6429
+ cursor: auto;
6430
+ touch-action: auto;
6431
+ }
6432
+
6433
+ /* :host is carried for the specificity, not the scope: the placement rules
6434
+ above are a host selector plus a class plus an attribute, and a bare
6435
+ .header[data-dragging] loses to them -- silently, because the drag still
6436
+ works and only the cursor is wrong. */
6437
+ :host .header[data-dragging] {
6438
+ cursor: grabbing;
6439
+ }
6440
+
6441
+ /* The cursor inherits, so a control a host slots into the header would show
6442
+ the drag cursor over something the drag deliberately ignores. Named to
6443
+ match the controls panel_drag steps aside for. */
6444
+ .header ::slotted(:is(button, a, input, select, textarea)) {
6445
+ cursor: pointer;
6446
+ }
6447
+
6296
6448
  .header-title {
6297
6449
  flex: 1;
6298
6450
  min-width: 0;
@@ -7776,13 +7928,30 @@ var STYLES = `
7776
7928
  * values, and the axis furniture inherits currentColor at low opacity so it
7777
7929
  * reads correctly in either theme without a second palette.
7778
7930
  */
7931
+ /* A chart is sized like a message, not like a panel: it takes the width it
7932
+ needs and stops, rather than stretching into whatever room the transcript
7933
+ has. A message bubble caps at 80% for the same reason -- widening the panel
7934
+ should not resize what is already in it. The cap is a token so a host with a
7935
+ wide panel and a real reason can raise it; the default is the width this
7936
+ renderer has always drawn at. Below the cap the block still fills its column,
7937
+ which is what a narrow panel needs. */
7779
7938
  .chart-block {
7780
- align-self: stretch;
7781
- max-width: 100%;
7939
+ align-self: flex-start;
7940
+ width: 100%;
7941
+ max-width: var(--_chart-max-width);
7782
7942
  margin: 6px 0;
7783
7943
  color: var(--_fg);
7784
7944
  }
7785
7945
 
7946
+ /* The drawing fills the block's width and carries its own height in the
7947
+ viewBox, so it is laid out rather than magnified: block display keeps the
7948
+ inline baseline gap from adding a stripe under every chart. */
7949
+ .chart-block svg {
7950
+ display: block;
7951
+ width: 100%;
7952
+ height: auto;
7953
+ }
7954
+
7786
7955
  .chart-title {
7787
7956
  margin-bottom: 2px;
7788
7957
  font-size: 0.85em;
@@ -10698,6 +10867,13 @@ var COLLAPSED_KEY = "ag-ui-chat:collapsed";
10698
10867
  var SIZE_KEY = "ag-ui-chat:size";
10699
10868
  var THEME_KEY = "ag-ui-chat:theme";
10700
10869
  var LAUNCHER_KEY = "ag-ui-chat:launcher";
10870
+ function asPoint(value) {
10871
+ if (typeof value !== "object" || value === null) {
10872
+ return null;
10873
+ }
10874
+ const { left, top } = value;
10875
+ return typeof left === "number" && typeof top === "number" ? { left, top } : null;
10876
+ }
10701
10877
  var DRAGGABLE_PLACEMENTS = /* @__PURE__ */ new Set([null, "", "floating", "bottom-left"]);
10702
10878
  var RESIZE_GRIPS = [
10703
10879
  { y: "top" },
@@ -11126,6 +11302,18 @@ var AgUiChat = class extends HTMLElement {
11126
11302
  * position -- see #applyLauncherPlacement for what that costs the host.
11127
11303
  */
11128
11304
  #launcherPos = null;
11305
+ /**
11306
+ * Where the user dragged the *panel*, in viewport coordinates, or null while
11307
+ * its position is still derived from the launcher's.
11308
+ *
11309
+ * The two gestures state different things and are restored differently. A
11310
+ * launcher drag says where the bubble goes and leaves the panel to open into
11311
+ * whatever space the viewport has, so it is re-derived every time -- which is
11312
+ * what lets a widget re-decide its direction when the window changes under
11313
+ * it. A header drag states the panel's own position, and re-deriving that
11314
+ * from the launcher would move the panel the user just placed.
11315
+ */
11316
+ #panelPos = null;
11129
11317
  /**
11130
11318
  * The corner the panel opens away from, once this element is placing itself.
11131
11319
  * Null means the host's layout still decides, and the anchor is measured.
@@ -12493,6 +12681,7 @@ ${quoted}`;
12493
12681
  return;
12494
12682
  }
12495
12683
  this.#launcherPos = at2;
12684
+ this.#panelPos = null;
12496
12685
  const panel = this.getBoundingClientRect();
12497
12686
  const placement = launcherPlacement(
12498
12687
  this.#launcherBox(),
@@ -12514,13 +12703,137 @@ ${quoted}`;
12514
12703
  this.#moveLauncher(left, top);
12515
12704
  this.#storeLauncherPosition();
12516
12705
  }
12517
- /** Write the current launcher position, if this element owns one. */
12706
+ /**
12707
+ * Move the panel live during a header drag, without persisting.
12708
+ *
12709
+ * Only the host box is written, and that is the whole trick: the launcher is
12710
+ * positioned *inside* that box, so leaving its own inset alone carries it
12711
+ * along by exactly the distance the panel travelled -- which is what a person
12712
+ * dragging a window expects of the thing it collapses into. Placing it on the
12713
+ * panel's pinned corner instead, as an earlier version did, sent it leaping
12714
+ * across the panel the moment the drag re-picked that corner.
12715
+ *
12716
+ * The corner is therefore held for the length of the gesture. Both insets are
12717
+ * measured from it, and rewriting one of them from a new corner while the
12718
+ * other still names the old one would move the launcher for no reason.
12719
+ */
12720
+ #movePanel(box) {
12721
+ if (!this.#launcherDraggable()) {
12722
+ return box;
12723
+ }
12724
+ if (this.#launcherPos === null) {
12725
+ const resting = this.#launcherBox();
12726
+ this.#launcherPos = { left: resting.left, top: resting.top };
12727
+ }
12728
+ const held = clampPanel(box, this.#viewport());
12729
+ const corner = this.#expandCorner ?? this.#anchor;
12730
+ const viewport = this.#viewport();
12731
+ this.style.setProperty(
12732
+ "--ag-ui-inset",
12733
+ [
12734
+ corner.y === "top" ? `${Math.round(held.top)}px` : "auto",
12735
+ corner.x === "right" ? `${Math.round(viewport.width - held.right)}px` : "auto",
12736
+ corner.y === "bottom" ? `${Math.round(viewport.height - held.bottom)}px` : "auto",
12737
+ corner.x === "left" ? `${Math.round(held.left)}px` : "auto"
12738
+ ].join(" ")
12739
+ );
12740
+ this.#panelPos = { left: held.left, top: held.top };
12741
+ return held;
12742
+ }
12743
+ /**
12744
+ * Finish a header drag: settle where both halves ended up, and remember it.
12745
+ *
12746
+ * The launcher travels the distance the panel actually travelled, which is
12747
+ * the clamped distance rather than the pointer's -- a panel held against the
12748
+ * viewport margin stops, and so does the bubble attached to it. Measured from
12749
+ * the box the press started on, so a long drag cannot accumulate the rounding
12750
+ * each move writes into the inset.
12751
+ *
12752
+ * Only now is the corner re-picked, from where the launcher has ended up, so
12753
+ * the panel opens into clear space next time. Re-picking it moves nothing:
12754
+ * both insets are rewritten from positions that are already decided.
12755
+ */
12756
+ #commitPanel(box, from) {
12757
+ const held = this.#movePanel(box);
12758
+ const start = this.#launcherPos;
12759
+ if (!this.#launcherDraggable() || start === null) {
12760
+ return;
12761
+ }
12762
+ const carried = {
12763
+ ...this.#launcherBox(),
12764
+ left: start.left + (held.left - from.left),
12765
+ top: start.top + (held.top - from.top)
12766
+ };
12767
+ this.#placePanelAndLauncher(held, {
12768
+ ...carried,
12769
+ ...clampLauncher(carried, this.#viewport())
12770
+ });
12771
+ this.#storeLauncherPosition();
12772
+ }
12773
+ /**
12774
+ * Write both insets for a panel and launcher that are already positioned,
12775
+ * re-picking the corner they are measured from.
12776
+ */
12777
+ #placePanelAndLauncher(host, launcher) {
12778
+ const viewport = this.#viewport();
12779
+ const size = { width: host.right - host.left, height: host.bottom - host.top };
12780
+ const { corner } = launcherPlacement(launcher, size, viewport);
12781
+ const insets = placeWidget(host, launcher, corner, viewport);
12782
+ this.style.setProperty("--ag-ui-inset", insets.hostInset);
12783
+ this.style.setProperty("--ag-ui-launcher-inset", insets.launcherInset);
12784
+ this.#launcherPos = { left: launcher.left, top: launcher.top };
12785
+ this.#panelPos = { left: host.left, top: host.top };
12786
+ this.#expandCorner = corner;
12787
+ this.setAttribute("data-expand-corner", `${corner.y}-${corner.x}`);
12788
+ this.#syncResizeAnchor();
12789
+ }
12790
+ /**
12791
+ * Re-apply a panel position the user stated, against the current viewport.
12792
+ *
12793
+ * The launcher keeps its offset from the panel through the clamp -- it was
12794
+ * put where it is relative to the panel, and a viewport that has since shrunk
12795
+ * is no reason to move one without the other -- and is then held on screen in
12796
+ * its own right.
12797
+ */
12798
+ #restorePanelPosition(at2) {
12799
+ if (!this.#launcherDraggable()) {
12800
+ return;
12801
+ }
12802
+ const rect = this.getBoundingClientRect();
12803
+ const held = clampPanel(
12804
+ { left: at2.left, top: at2.top, right: at2.left + rect.width, bottom: at2.top + rect.height },
12805
+ this.#viewport()
12806
+ );
12807
+ const launcher = this.#launcherBox();
12808
+ const carried = {
12809
+ ...launcher,
12810
+ left: launcher.left + (held.left - at2.left),
12811
+ top: launcher.top + (held.top - at2.top)
12812
+ };
12813
+ this.#placePanelAndLauncher(held, {
12814
+ ...carried,
12815
+ ...clampLauncher(carried, this.#viewport())
12816
+ });
12817
+ }
12818
+ /**
12819
+ * Write the current position, if this element owns one.
12820
+ *
12821
+ * The panel's own position rides along only when the user stated it, because
12822
+ * its presence is what tells a restore which of the two gestures to honour:
12823
+ * with it, the panel goes back where it was put; without it, the panel is
12824
+ * re-derived from the launcher and opens into whatever room the viewport has
12825
+ * now.
12826
+ */
12518
12827
  #storeLauncherPosition() {
12519
12828
  const position = this.#launcherPos;
12520
12829
  if (position === null) {
12521
12830
  return;
12522
12831
  }
12523
- writeStoredItem(this.#storageKey(LAUNCHER_KEY), JSON.stringify(position));
12832
+ const panel = this.#panelPos;
12833
+ writeStoredItem(
12834
+ this.#storageKey(LAUNCHER_KEY),
12835
+ JSON.stringify(panel === null ? position : { ...position, panel })
12836
+ );
12524
12837
  }
12525
12838
  /**
12526
12839
  * Re-apply the dragged position against the current viewport: on connect,
@@ -12529,16 +12842,23 @@ ${quoted}`;
12529
12842
  * unreachable -- it is the only way back to a collapsed conversation.
12530
12843
  */
12531
12844
  #restoreLauncherPosition() {
12532
- const stored = this.#launcherPos ?? this.#readLauncherPosition();
12533
- if (stored === null) {
12845
+ const stored = this.#readLauncherPosition();
12846
+ const launcher = this.#launcherPos ?? stored;
12847
+ if (launcher === null) {
12848
+ return;
12849
+ }
12850
+ const panel = this.#panelPos ?? stored?.panel ?? null;
12851
+ if (panel !== null) {
12852
+ this.#launcherPos = { left: launcher.left, top: launcher.top };
12853
+ this.#restorePanelPosition(panel);
12534
12854
  return;
12535
12855
  }
12536
12856
  const box = this.#launcherBox();
12537
12857
  this.#applyLauncherPlacement(
12538
- clampLauncher({ ...box, left: stored.left, top: stored.top }, this.#viewport())
12858
+ clampLauncher({ ...box, left: launcher.left, top: launcher.top }, this.#viewport())
12539
12859
  );
12540
12860
  }
12541
- /** The persisted launcher position for this instance, or null. */
12861
+ /** The persisted position for this instance, or null. */
12542
12862
  #readLauncherPosition() {
12543
12863
  const raw = this.#readScopedItem(LAUNCHER_KEY);
12544
12864
  if (raw === null) {
@@ -12549,8 +12869,12 @@ ${quoted}`;
12549
12869
  if (typeof parsed !== "object" || parsed === null) {
12550
12870
  return null;
12551
12871
  }
12552
- const { left, top } = parsed;
12553
- return typeof left === "number" && typeof top === "number" ? { left, top } : null;
12872
+ const { left, top, panel } = parsed;
12873
+ if (typeof left !== "number" || typeof top !== "number") {
12874
+ return null;
12875
+ }
12876
+ const at2 = asPoint(panel);
12877
+ return at2 === null ? { left, top } : { left, top, panel: at2 };
12554
12878
  } catch {
12555
12879
  return null;
12556
12880
  }
@@ -12565,6 +12889,7 @@ ${quoted}`;
12565
12889
  return;
12566
12890
  }
12567
12891
  this.#launcherPos = null;
12892
+ this.#panelPos = null;
12568
12893
  this.#expandCorner = null;
12569
12894
  this.style.removeProperty("--ag-ui-inset");
12570
12895
  this.style.removeProperty("--ag-ui-launcher-inset");
@@ -12608,6 +12933,9 @@ ${quoted}`;
12608
12933
  top: anchor.y === "top" ? box.top : box.bottom - size
12609
12934
  };
12610
12935
  }
12936
+ if (this.#panelPos !== null) {
12937
+ this.#panelPos = { left: box.left, top: box.top };
12938
+ }
12611
12939
  }
12612
12940
  /** Finish a resize: keep the box, remember it, and re-read the pinned edges. */
12613
12941
  #commitResize(grip, box) {
@@ -13174,6 +13502,12 @@ ${quoted}`;
13174
13502
  }
13175
13503
  controls.append(collapse2);
13176
13504
  header.append(title, headerActions, controls);
13505
+ enablePanelDrag(header, {
13506
+ enabled: () => !this.collapsed && this.#launcherDraggable(),
13507
+ rect: () => this.getBoundingClientRect(),
13508
+ apply: (box) => this.#movePanel(box),
13509
+ commit: (box, from) => this.#commitPanel(box, from)
13510
+ });
13177
13511
  this.#messages.className = "messages";
13178
13512
  this.#messages.setAttribute("part", "messages");
13179
13513
  this.#messages.setAttribute("role", "log");
@@ -14643,7 +14977,7 @@ function setControlValue(el2, value) {
14643
14977
  }
14644
14978
 
14645
14979
  // src/version.ts
14646
- var VERSION = "0.33.1";
14980
+ var VERSION = "0.34.0";
14647
14981
  export {
14648
14982
  ATTACHMENT_EVENT,
14649
14983
  AgUiChat,