@artooi/ag-ui-web-component 0.33.0 → 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.
@@ -0,0 +1,56 @@
1
+ import type { ExpandCorner, Extent, LauncherBox } from "./launcher_placement.js";
2
+ import type { PanelRect } from "./resize_handle.js";
3
+
4
+ /** Where to put the host box, and where the launcher sits inside it. */
5
+ export interface WidgetInsets {
6
+ /** An `inset` shorthand for the host box. */
7
+ readonly hostInset: string;
8
+ /** An `inset` shorthand for the launcher, relative to the host box. */
9
+ readonly launcherInset: string;
10
+ }
11
+
12
+ /**
13
+ * Express a panel and its launcher, both already positioned, as the two
14
+ * `inset` shorthands the element writes.
15
+ *
16
+ * Both are measured from the **same** corner, which is what stops a later
17
+ * resize from dragging the launcher: the pinned corner is the one edge a
18
+ * resize cannot move. Which corner that is says nothing about where either box
19
+ * ends up -- the positions are absolute and arrive decided -- so re-picking it
20
+ * moves nothing, and the launcher may sit outside its own host box. Nothing
21
+ * clips it there, and that is what lets a launcher be flush to a screen corner
22
+ * while the panel it opens keeps its margin.
23
+ */
24
+ export function placeWidget(
25
+ host: PanelRect,
26
+ launcher: LauncherBox,
27
+ corner: ExpandCorner,
28
+ viewport: Extent,
29
+ ): WidgetInsets {
30
+ return {
31
+ hostInset: inset({
32
+ top: corner.y === "top" ? host.top : null,
33
+ right: corner.x === "right" ? viewport.width - host.right : null,
34
+ bottom: corner.y === "bottom" ? viewport.height - host.bottom : null,
35
+ left: corner.x === "left" ? host.left : null,
36
+ }),
37
+ launcherInset: inset({
38
+ top: corner.y === "top" ? launcher.top - host.top : null,
39
+ right: corner.x === "right" ? host.right - (launcher.left + launcher.width) : null,
40
+ bottom: corner.y === "bottom" ? host.bottom - (launcher.top + launcher.height) : null,
41
+ left: corner.x === "left" ? launcher.left - host.left : null,
42
+ }),
43
+ };
44
+ }
45
+
46
+ /** An `inset` shorthand; a null side is `auto`, so the opposite one pins it. */
47
+ function inset(sides: {
48
+ top: number | null;
49
+ right: number | null;
50
+ bottom: number | null;
51
+ left: number | null;
52
+ }): string {
53
+ const side = (value: number | null): string =>
54
+ value === null ? "auto" : `${Math.round(value)}px`;
55
+ return `${side(sides.top)} ${side(sides.right)} ${side(sides.bottom)} ${side(sides.left)}`;
56
+ }
package/src/ui/styles.ts CHANGED
@@ -109,6 +109,11 @@ export const STYLES = `
109
109
  --_glyph-size: var(--ag-ui-glyph-size, 18px);
110
110
  --_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
111
111
 
112
+ /* How wide a chart is allowed to get. A cap rather than a width: below it
113
+ the chart fills its column, and above it a wider panel is just a wider
114
+ panel. */
115
+ --_chart-max-width: var(--ag-ui-chart-max-width, 480px);
116
+
112
117
  /* Unread badge on the launcher. */
113
118
  --_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
114
119
  --_badge-fg: var(--ag-ui-badge-fg, #ffffff);
@@ -499,6 +504,41 @@ export const STYLES = `
499
504
  color: var(--_header-fg);
500
505
  }
501
506
 
507
+ /* The header is the panel's title bar: on the placements that let the widget
508
+ be moved it drags the whole thing, matching the launcher's own drag. The
509
+ selector lists those placements rather than asking JS to stamp an attribute,
510
+ because a cursor that appears one frame late reads as a control that only
511
+ sometimes exists. touch-action keeps a finger drag here moving the panel
512
+ instead of scrolling the page behind it. */
513
+ :host(:not([placement])) .header,
514
+ :host([placement=""]) .header,
515
+ :host([placement="floating"]) .header,
516
+ :host([placement="bottom-left"]) .header {
517
+ cursor: move;
518
+ touch-action: none;
519
+ }
520
+
521
+ /* A host that turned the drag off keeps a header that says so. */
522
+ :host([data-launcher-drag="false"]) .header {
523
+ cursor: auto;
524
+ touch-action: auto;
525
+ }
526
+
527
+ /* :host is carried for the specificity, not the scope: the placement rules
528
+ above are a host selector plus a class plus an attribute, and a bare
529
+ .header[data-dragging] loses to them -- silently, because the drag still
530
+ works and only the cursor is wrong. */
531
+ :host .header[data-dragging] {
532
+ cursor: grabbing;
533
+ }
534
+
535
+ /* The cursor inherits, so a control a host slots into the header would show
536
+ the drag cursor over something the drag deliberately ignores. Named to
537
+ match the controls panel_drag steps aside for. */
538
+ .header ::slotted(:is(button, a, input, select, textarea)) {
539
+ cursor: pointer;
540
+ }
541
+
502
542
  .header-title {
503
543
  flex: 1;
504
544
  min-width: 0;
@@ -808,7 +848,22 @@ export const STYLES = `
808
848
  border-radius: var(--_msg-radius);
809
849
  line-height: 1.4;
810
850
  white-space: pre-wrap;
811
- word-break: break-word;
851
+ /* overflow-wrap, not word-break, and the difference is the whole reason a
852
+ markdown table used to be unreadable.
853
+
854
+ Both stop one long unbroken token blowing out the bubble, which is all this
855
+ is for. But word-break: break-word is the legacy spelling of "break
856
+ anywhere", and breaking anywhere drops the *min-content* width of every
857
+ descendant to one character. A table's column algorithm takes min-content
858
+ as an input, so the table always fitted max-width: 100%, the overflow-x:
859
+ auto below it never had anything to scroll, and the columns absorbed the
860
+ pressure by rendering one letter per line instead. A seven-column header
861
+ came out 162px tall.
862
+
863
+ overflow-wrap: break-word breaks a word only when it would otherwise
864
+ overflow its line, and leaves min-content alone -- so the bubble is still
865
+ protected and the table is free to be wider than the panel and scroll. */
866
+ overflow-wrap: break-word;
812
867
  }
813
868
 
814
869
  /* ── Incoming-text animations (data-text-animation) ─────────────────────── */
@@ -1967,13 +2022,30 @@ export const STYLES = `
1967
2022
  * values, and the axis furniture inherits currentColor at low opacity so it
1968
2023
  * reads correctly in either theme without a second palette.
1969
2024
  */
2025
+ /* A chart is sized like a message, not like a panel: it takes the width it
2026
+ needs and stops, rather than stretching into whatever room the transcript
2027
+ has. A message bubble caps at 80% for the same reason -- widening the panel
2028
+ should not resize what is already in it. The cap is a token so a host with a
2029
+ wide panel and a real reason can raise it; the default is the width this
2030
+ renderer has always drawn at. Below the cap the block still fills its column,
2031
+ which is what a narrow panel needs. */
1970
2032
  .chart-block {
1971
- align-self: stretch;
1972
- max-width: 100%;
2033
+ align-self: flex-start;
2034
+ width: 100%;
2035
+ max-width: var(--_chart-max-width);
1973
2036
  margin: 6px 0;
1974
2037
  color: var(--_fg);
1975
2038
  }
1976
2039
 
2040
+ /* The drawing fills the block's width and carries its own height in the
2041
+ viewBox, so it is laid out rather than magnified: block display keeps the
2042
+ inline baseline gap from adding a stripe under every chart. */
2043
+ .chart-block svg {
2044
+ display: block;
2045
+ width: 100%;
2046
+ height: auto;
2047
+ }
2048
+
1977
2049
  .chart-title {
1978
2050
  margin-bottom: 2px;
1979
2051
  font-size: 0.85em;
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION: string = "0.33.0";
1
+ export const VERSION: string = "0.34.0";