@teammates/consolonia 0.4.0 → 0.5.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.
@@ -33,7 +33,7 @@ import type { DrawingContext, TextStyle } from "../drawing/context.js";
33
33
  import type { InputEvent } from "../input/events.js";
34
34
  import { Control } from "../layout/control.js";
35
35
  import type { Constraint, Rect, Size } from "../layout/types.js";
36
- import type { StyledSpan } from "../styled.js";
36
+ import { type StyledSpan } from "../styled.js";
37
37
  import { type StyledLine } from "./styled-text.js";
38
38
  import { type DeleteSizer, type InputColorizer, TextInput } from "./text-input.js";
39
39
  export interface DropdownItem {
@@ -93,8 +93,10 @@ export interface ChatViewOptions {
93
93
  dropdownLabelStyle?: TextStyle;
94
94
  /** Maximum number of lines the input box can grow to (default 1). */
95
95
  maxInputHeight?: number;
96
- /** Footer content shown below the input (StyledLine for mixed colors). */
96
+ /** Footer content shown below the input (StyledLine for mixed colors). Sets the left side. */
97
97
  footer?: StyledLine;
98
+ /** Right-aligned footer content. */
99
+ footerRight?: StyledLine;
98
100
  /** Style for footer text (used as default when footer is a plain string). */
99
101
  footerStyle?: TextStyle;
100
102
  /** Command history entries. */
@@ -117,6 +119,7 @@ export declare class ChatView extends Control {
117
119
  private _input;
118
120
  private _inputSeparator;
119
121
  private _footer;
122
+ private _footerRight;
120
123
  private _dropdownItems;
121
124
  private _dropdownIndex;
122
125
  private _feedStyle;
@@ -166,8 +169,10 @@ export declare class ChatView extends Control {
166
169
  set bannerWidget(widget: Control);
167
170
  /** Get the current banner widget. */
168
171
  get bannerWidget(): Control;
169
- /** Set footer content (plain string or StyledSpan for mixed colors). */
172
+ /** Set left-side footer content (plain string or StyledSpan for mixed colors). */
170
173
  setFooter(content: StyledLine): void;
174
+ /** Set right-side footer content (plain string or StyledSpan for mixed colors). */
175
+ setFooterRight(content: StyledLine): void;
171
176
  /** Append a line of plain text to the feed. Auto-scrolls to bottom. */
172
177
  appendToFeed(text: string, style?: TextStyle): void;
173
178
  /** Append a styled line (StyledSpan) to the feed. */
@@ -57,6 +57,7 @@ export class ChatView extends Control {
57
57
  _input;
58
58
  _inputSeparator;
59
59
  _footer;
60
+ _footerRight;
60
61
  _dropdownItems = [];
61
62
  _dropdownIndex = -1;
62
63
  // ── Configuration ──────────────────────────────────────────────
@@ -163,6 +164,13 @@ export class ChatView extends Control {
163
164
  wrap: false,
164
165
  });
165
166
  this.addChild(this._footer);
167
+ const footerRightLine = options.footerRight ?? "";
168
+ this._footerRight = new StyledText({
169
+ lines: [footerRightLine],
170
+ defaultStyle: this._footerStyle,
171
+ wrap: false,
172
+ });
173
+ this.addChild(this._footerRight);
166
174
  // Wire input events to ChatView events
167
175
  this._input.on("submit", (text) => this.emit("submit", text));
168
176
  this._input.on("change", (text) => this.emit("change", text));
@@ -221,11 +229,16 @@ export class ChatView extends Control {
221
229
  return this._banner;
222
230
  }
223
231
  // ── Public API: Footer ─────────────────────────────────────────
224
- /** Set footer content (plain string or StyledSpan for mixed colors). */
232
+ /** Set left-side footer content (plain string or StyledSpan for mixed colors). */
225
233
  setFooter(content) {
226
234
  this._footer.lines = [content];
227
235
  this.invalidate();
228
236
  }
237
+ /** Set right-side footer content (plain string or StyledSpan for mixed colors). */
238
+ setFooterRight(content) {
239
+ this._footerRight.lines = [content];
240
+ this.invalidate();
241
+ }
229
242
  // ── Public API: Feed ───────────────────────────────────────────
230
243
  /** Append a line of plain text to the feed. Auto-scrolls to bottom. */
231
244
  appendToFeed(text, style) {
@@ -335,6 +348,10 @@ export class ChatView extends Control {
335
348
  /** Scroll the feed by a delta (positive = down, negative = up). */
336
349
  scrollFeed(delta) {
337
350
  this._feedScrollOffset = Math.max(0, this._feedScrollOffset + delta);
351
+ // Clear selection when scrolling (unless actively drag-selecting)
352
+ if (!this._selecting && this._hasSelection()) {
353
+ this.clearSelection();
354
+ }
338
355
  this.invalidate();
339
356
  }
340
357
  // ── Public API: Input ──────────────────────────────────────────
@@ -446,6 +463,7 @@ export class ChatView extends Control {
446
463
  this._input.focusable = false;
447
464
  this._inputSeparator.visible = false;
448
465
  this._footer.visible = false;
466
+ this._footerRight.visible = false;
449
467
  }
450
468
  else {
451
469
  // Restore normal input chrome
@@ -454,6 +472,7 @@ export class ChatView extends Control {
454
472
  this._input.onFocus();
455
473
  this._inputSeparator.visible = true;
456
474
  this._footer.visible = true;
475
+ this._footerRight.visible = true;
457
476
  }
458
477
  this.invalidate();
459
478
  }
@@ -547,6 +566,8 @@ export class ChatView extends Control {
547
566
  const ratio = relY / this._feedH;
548
567
  this._feedScrollOffset = Math.round(ratio * this._maxScroll);
549
568
  this._feedScrollOffset = Math.max(0, Math.min(this._feedScrollOffset, this._maxScroll));
569
+ if (this._hasSelection())
570
+ this.clearSelection();
550
571
  this.invalidate();
551
572
  }
552
573
  return true;
@@ -558,6 +579,8 @@ export class ChatView extends Control {
558
579
  const clampedPos = Math.max(0, Math.min(newThumbPos, maxThumbPos));
559
580
  const ratio = maxThumbPos > 0 ? clampedPos / maxThumbPos : 0;
560
581
  this._feedScrollOffset = Math.round(ratio * this._maxScroll);
582
+ if (this._hasSelection())
583
+ this.clearSelection();
561
584
  this.invalidate();
562
585
  return true;
563
586
  }
@@ -631,6 +654,7 @@ export class ChatView extends Control {
631
654
  if (me.type === "release" && this._selecting) {
632
655
  this._selecting = false;
633
656
  this._stopSelScroll();
657
+ this._selEnd = { x: me.x, y: me.y };
634
658
  // If anchor == end (just a click, no drag), clear selection
635
659
  if (this._selAnchor &&
636
660
  this._selEnd &&
@@ -881,6 +905,7 @@ export class ChatView extends Control {
881
905
  this._renderDropdown(ctx, b.x, y, W, totalDropdownH);
882
906
  }
883
907
  else {
908
+ // Left footer
884
909
  this._footer.measure({
885
910
  minWidth: 0,
886
911
  maxWidth: W,
@@ -889,6 +914,23 @@ export class ChatView extends Control {
889
914
  });
890
915
  this._footer.arrange({ x: b.x, y, width: W, height: footerH });
891
916
  this._footer.render(ctx);
917
+ // Right footer (right-aligned on the same row)
918
+ const rightSize = this._footerRight.measure({
919
+ minWidth: 0,
920
+ maxWidth: W,
921
+ minHeight: 0,
922
+ maxHeight: 1,
923
+ });
924
+ if (rightSize.width > 0) {
925
+ const rightX = b.x + W - rightSize.width;
926
+ this._footerRight.arrange({
927
+ x: rightX,
928
+ y,
929
+ width: rightSize.width,
930
+ height: footerH,
931
+ });
932
+ this._footerRight.render(ctx);
933
+ }
892
934
  }
893
935
  }
894
936
  // ── Feed rendering ─────────────────────────────────────────────
@@ -983,6 +1025,9 @@ export class ChatView extends Control {
983
1025
  }
984
1026
  cy += item.height;
985
1027
  }
1028
+ // Always cache feed geometry for selection edge-detection
1029
+ this._feedY = y;
1030
+ this._feedH = height;
986
1031
  // Render scrollbar and cache geometry for hit-testing
987
1032
  if (height > 0 && totalContentH > height) {
988
1033
  const scrollX = x + width - 1;
@@ -994,8 +1039,6 @@ export class ChatView extends Control {
994
1039
  const thumbStyle = this._feedStyle;
995
1040
  // Cache for mouse interaction
996
1041
  this._scrollbarX = scrollX;
997
- this._feedY = y;
998
- this._feedH = height;
999
1042
  this._thumbPos = thumbPos;
1000
1043
  this._thumbSize = thumbSize;
1001
1044
  this._maxScroll = maxScroll;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/consolonia",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Terminal UI rendering engine inspired by Consolonia. Pixel-level compositing with ANSI output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",