@teammates/consolonia 0.7.1 → 0.7.3

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.
@@ -534,7 +534,10 @@ describe("detectTerminal", () => {
534
534
  }
535
535
  Object.assign(process.env, origEnv);
536
536
  Object.defineProperty(process, "platform", { value: origPlatform });
537
- Object.defineProperty(process.stdout, "isTTY", { value: origIsTTY, writable: true });
537
+ Object.defineProperty(process.stdout, "isTTY", {
538
+ value: origIsTTY,
539
+ writable: true,
540
+ });
538
541
  });
539
542
  function setEnv(overrides) {
540
543
  for (const [k, v] of Object.entries(overrides)) {
@@ -545,7 +548,10 @@ describe("detectTerminal", () => {
545
548
  }
546
549
  }
547
550
  it("returns pipe caps when stdout is not a TTY", () => {
548
- Object.defineProperty(process.stdout, "isTTY", { value: false, writable: true });
551
+ Object.defineProperty(process.stdout, "isTTY", {
552
+ value: false,
553
+ writable: true,
554
+ });
549
555
  const caps = detectTerminal();
550
556
  expect(caps.isTTY).toBe(false);
551
557
  expect(caps.mouse).toBe(false);
@@ -554,7 +560,10 @@ describe("detectTerminal", () => {
554
560
  });
555
561
  it("detects Windows Terminal via WT_SESSION", () => {
556
562
  Object.defineProperty(process, "platform", { value: "win32" });
557
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
563
+ Object.defineProperty(process.stdout, "isTTY", {
564
+ value: true,
565
+ writable: true,
566
+ });
558
567
  setEnv({ WT_SESSION: "some-guid" });
559
568
  const caps = detectTerminal();
560
569
  expect(caps.name).toBe("windows-terminal");
@@ -563,7 +572,10 @@ describe("detectTerminal", () => {
563
572
  });
564
573
  it("detects VS Code terminal on Windows", () => {
565
574
  Object.defineProperty(process, "platform", { value: "win32" });
566
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
575
+ Object.defineProperty(process.stdout, "isTTY", {
576
+ value: true,
577
+ writable: true,
578
+ });
567
579
  setEnv({ WT_SESSION: undefined, TERM_PROGRAM: "vscode" });
568
580
  const caps = detectTerminal();
569
581
  expect(caps.name).toBe("vscode");
@@ -571,21 +583,40 @@ describe("detectTerminal", () => {
571
583
  });
572
584
  it("detects ConEmu on Windows", () => {
573
585
  Object.defineProperty(process, "platform", { value: "win32" });
574
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
575
- setEnv({ WT_SESSION: undefined, TERM_PROGRAM: undefined, ConEmuPID: "1234" });
586
+ Object.defineProperty(process.stdout, "isTTY", {
587
+ value: true,
588
+ writable: true,
589
+ });
590
+ setEnv({
591
+ WT_SESSION: undefined,
592
+ TERM_PROGRAM: undefined,
593
+ ConEmuPID: "1234",
594
+ });
576
595
  const caps = detectTerminal();
577
596
  expect(caps.name).toBe("conemu");
578
597
  });
579
598
  it("detects mintty via TERM + MSYSTEM", () => {
580
599
  Object.defineProperty(process, "platform", { value: "win32" });
581
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
582
- setEnv({ WT_SESSION: undefined, TERM_PROGRAM: undefined, ConEmuPID: undefined, TERM: "xterm-256color", MSYSTEM: "MINGW64" });
600
+ Object.defineProperty(process.stdout, "isTTY", {
601
+ value: true,
602
+ writable: true,
603
+ });
604
+ setEnv({
605
+ WT_SESSION: undefined,
606
+ TERM_PROGRAM: undefined,
607
+ ConEmuPID: undefined,
608
+ TERM: "xterm-256color",
609
+ MSYSTEM: "MINGW64",
610
+ });
583
611
  const caps = detectTerminal();
584
612
  expect(caps.name).toBe("mintty");
585
613
  });
586
614
  it("detects tmux on Unix", () => {
587
615
  Object.defineProperty(process, "platform", { value: "linux" });
588
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
616
+ Object.defineProperty(process.stdout, "isTTY", {
617
+ value: true,
618
+ writable: true,
619
+ });
589
620
  setEnv({ TMUX: "/tmp/tmux-1000/default,1234,0", TERM: "screen-256color" });
590
621
  const caps = detectTerminal();
591
622
  expect(caps.name).toBe("tmux");
@@ -593,8 +624,16 @@ describe("detectTerminal", () => {
593
624
  });
594
625
  it("detects GNU screen with limited caps", () => {
595
626
  Object.defineProperty(process, "platform", { value: "linux" });
596
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
597
- setEnv({ TMUX: undefined, TERM: "screen", TERM_PROGRAM: undefined, ITERM_SESSION_ID: undefined });
627
+ Object.defineProperty(process.stdout, "isTTY", {
628
+ value: true,
629
+ writable: true,
630
+ });
631
+ setEnv({
632
+ TMUX: undefined,
633
+ TERM: "screen",
634
+ TERM_PROGRAM: undefined,
635
+ ITERM_SESSION_ID: undefined,
636
+ });
598
637
  const caps = detectTerminal();
599
638
  expect(caps.name).toBe("screen");
600
639
  expect(caps.sgrMouse).toBe(false);
@@ -602,24 +641,48 @@ describe("detectTerminal", () => {
602
641
  });
603
642
  it("detects iTerm2", () => {
604
643
  Object.defineProperty(process, "platform", { value: "darwin" });
605
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
606
- setEnv({ TMUX: undefined, TERM: "xterm-256color", TERM_PROGRAM: "iTerm.app" });
644
+ Object.defineProperty(process.stdout, "isTTY", {
645
+ value: true,
646
+ writable: true,
647
+ });
648
+ setEnv({
649
+ TMUX: undefined,
650
+ TERM: "xterm-256color",
651
+ TERM_PROGRAM: "iTerm.app",
652
+ });
607
653
  const caps = detectTerminal();
608
654
  expect(caps.name).toBe("iterm2");
609
655
  expect(caps.truecolor).toBe(true);
610
656
  });
611
657
  it("detects xterm-compatible with COLORTERM truecolor", () => {
612
658
  Object.defineProperty(process, "platform", { value: "linux" });
613
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
614
- setEnv({ TMUX: undefined, TERM: "xterm-256color", TERM_PROGRAM: undefined, ITERM_SESSION_ID: undefined, COLORTERM: "truecolor" });
659
+ Object.defineProperty(process.stdout, "isTTY", {
660
+ value: true,
661
+ writable: true,
662
+ });
663
+ setEnv({
664
+ TMUX: undefined,
665
+ TERM: "xterm-256color",
666
+ TERM_PROGRAM: undefined,
667
+ ITERM_SESSION_ID: undefined,
668
+ COLORTERM: "truecolor",
669
+ });
615
670
  const caps = detectTerminal();
616
671
  expect(caps.truecolor).toBe(true);
617
672
  expect(caps.color256).toBe(true);
618
673
  });
619
674
  it("detects dumb terminal with minimal caps", () => {
620
675
  Object.defineProperty(process, "platform", { value: "linux" });
621
- Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true });
622
- setEnv({ TMUX: undefined, TERM: "dumb", TERM_PROGRAM: undefined, ITERM_SESSION_ID: undefined });
676
+ Object.defineProperty(process.stdout, "isTTY", {
677
+ value: true,
678
+ writable: true,
679
+ });
680
+ setEnv({
681
+ TMUX: undefined,
682
+ TERM: "dumb",
683
+ TERM_PROGRAM: undefined,
684
+ ITERM_SESSION_ID: undefined,
685
+ });
623
686
  const caps = detectTerminal();
624
687
  expect(caps.name).toBe("dumb");
625
688
  expect(caps.mouse).toBe(false);
@@ -667,7 +730,10 @@ describe("esc environment-aware sequences", () => {
667
730
  describe("initSequence", () => {
668
731
  it("includes all features for a full-caps terminal", () => {
669
732
  const caps = makeCaps();
670
- const seq = esc.initSequence(caps, { alternateScreen: true, mouse: true });
733
+ const seq = esc.initSequence(caps, {
734
+ alternateScreen: true,
735
+ mouse: true,
736
+ });
671
737
  expect(seq).toContain(esc.alternateScreenOn);
672
738
  expect(seq).toContain(esc.hideCursor);
673
739
  expect(seq).toContain(esc.bracketedPasteOn);
@@ -676,34 +742,52 @@ describe("esc environment-aware sequences", () => {
676
742
  });
677
743
  it("skips alternate screen when not supported", () => {
678
744
  const caps = makeCaps({ alternateScreen: false });
679
- const seq = esc.initSequence(caps, { alternateScreen: true, mouse: false });
745
+ const seq = esc.initSequence(caps, {
746
+ alternateScreen: true,
747
+ mouse: false,
748
+ });
680
749
  expect(seq).not.toContain(esc.alternateScreenOn);
681
750
  });
682
751
  it("skips alternate screen when app opts out", () => {
683
752
  const caps = makeCaps();
684
- const seq = esc.initSequence(caps, { alternateScreen: false, mouse: false });
753
+ const seq = esc.initSequence(caps, {
754
+ alternateScreen: false,
755
+ mouse: false,
756
+ });
685
757
  expect(seq).not.toContain(esc.alternateScreenOn);
686
758
  });
687
759
  it("skips bracketed paste when not supported", () => {
688
760
  const caps = makeCaps({ bracketedPaste: false });
689
- const seq = esc.initSequence(caps, { alternateScreen: false, mouse: false });
761
+ const seq = esc.initSequence(caps, {
762
+ alternateScreen: false,
763
+ mouse: false,
764
+ });
690
765
  expect(seq).not.toContain(esc.bracketedPasteOn);
691
766
  });
692
767
  it("skips mouse when app opts out even if caps support it", () => {
693
768
  const caps = makeCaps();
694
- const seq = esc.initSequence(caps, { alternateScreen: false, mouse: false });
769
+ const seq = esc.initSequence(caps, {
770
+ alternateScreen: false,
771
+ mouse: false,
772
+ });
695
773
  expect(seq).not.toContain("?1000h");
696
774
  });
697
775
  it("returns empty string for non-TTY", () => {
698
776
  const caps = makeCaps({ isTTY: false });
699
- const seq = esc.initSequence(caps, { alternateScreen: true, mouse: true });
777
+ const seq = esc.initSequence(caps, {
778
+ alternateScreen: true,
779
+ mouse: true,
780
+ });
700
781
  expect(seq).toBe("");
701
782
  });
702
783
  });
703
784
  describe("restoreSequence", () => {
704
785
  it("includes all restore features for a full-caps terminal", () => {
705
786
  const caps = makeCaps();
706
- const seq = esc.restoreSequence(caps, { alternateScreen: true, mouse: true });
787
+ const seq = esc.restoreSequence(caps, {
788
+ alternateScreen: true,
789
+ mouse: true,
790
+ });
707
791
  expect(seq).toContain(esc.reset);
708
792
  expect(seq).toContain(esc.mouseTrackingOff);
709
793
  expect(seq).toContain(esc.bracketedPasteOff);
@@ -712,14 +796,20 @@ describe("esc environment-aware sequences", () => {
712
796
  });
713
797
  it("mirrors initSequence — skips what init skipped", () => {
714
798
  const caps = makeCaps({ bracketedPaste: false, alternateScreen: false });
715
- const seq = esc.restoreSequence(caps, { alternateScreen: true, mouse: false });
799
+ const seq = esc.restoreSequence(caps, {
800
+ alternateScreen: true,
801
+ mouse: false,
802
+ });
716
803
  expect(seq).not.toContain(esc.bracketedPasteOff);
717
804
  expect(seq).not.toContain(esc.alternateScreenOff);
718
805
  expect(seq).not.toContain(esc.mouseTrackingOff);
719
806
  });
720
807
  it("returns empty string for non-TTY", () => {
721
808
  const caps = makeCaps({ isTTY: false });
722
- const seq = esc.restoreSequence(caps, { alternateScreen: true, mouse: true });
809
+ const seq = esc.restoreSequence(caps, {
810
+ alternateScreen: true,
811
+ mouse: true,
812
+ });
723
813
  expect(seq).toBe("");
724
814
  });
725
815
  });
@@ -296,6 +296,44 @@ describe("symbol", () => {
296
296
  // U+1F600 = grinning face (in emoji range 0x1f000-0x1faff)
297
297
  expect(charWidth(0x1f600)).toBe(2);
298
298
  });
299
+ it("text-presentation symbols that remain width 1", () => {
300
+ // These render as 1 cell even on Windows Terminal
301
+ expect(charWidth(0x2713)).toBe(1); // ✓ Check Mark
302
+ expect(charWidth(0x2702)).toBe(1); // ✂ Scissors
303
+ expect(charWidth(0x00a9)).toBe(1); // © Copyright
304
+ expect(charWidth(0x00ae)).toBe(1); // ® Registered
305
+ });
306
+ it("emoji-presentation symbols are width 2", () => {
307
+ // These have Emoji_Presentation=Yes and render as 2 cells
308
+ expect(charWidth(0x2614)).toBe(2); // ☔ Umbrella with Rain Drops
309
+ expect(charWidth(0x2615)).toBe(2); // ☕ Hot Beverage
310
+ expect(charWidth(0x2705)).toBe(2); // ✅ White Heavy Check Mark
311
+ expect(charWidth(0x274c)).toBe(2); // ❌ Cross Mark
312
+ expect(charWidth(0x2757)).toBe(2); // ❗ Heavy Exclamation Mark
313
+ expect(charWidth(0x26bd)).toBe(2); // ⚽ Soccer Ball
314
+ expect(charWidth(0x26fd)).toBe(2); // ⛽ Fuel Pump
315
+ expect(charWidth(0x2b50)).toBe(2); // ⭐ White Medium Star
316
+ });
317
+ it("Windows Terminal wide symbols are width 2", () => {
318
+ // Text-presentation chars that Windows Terminal renders as double-width
319
+ expect(charWidth(0x2139)).toBe(2); // ℹ Information Source
320
+ expect(charWidth(0x2605)).toBe(2); // ★ Black Star
321
+ expect(charWidth(0x2606)).toBe(2); // ☆ White Star
322
+ expect(charWidth(0x2660)).toBe(2); // ♠ Black Spade Suit
323
+ expect(charWidth(0x2663)).toBe(2); // ♣ Black Club Suit
324
+ expect(charWidth(0x2665)).toBe(2); // ♥ Black Heart Suit
325
+ expect(charWidth(0x2666)).toBe(2); // ♦ Black Diamond Suit
326
+ expect(charWidth(0x2690)).toBe(2); // ⚐ White Flag
327
+ expect(charWidth(0x2691)).toBe(2); // ⚑ Black Flag
328
+ expect(charWidth(0x2699)).toBe(2); // ⚙ Gear
329
+ expect(charWidth(0x26a0)).toBe(2); // ⚠ Warning Sign
330
+ expect(charWidth(0x2714)).toBe(2); // ✔ Heavy Check Mark
331
+ expect(charWidth(0x2716)).toBe(2); // ✖ Heavy Multiplication X
332
+ expect(charWidth(0x279c)).toBe(2); // ➜ Heavy Round-Tipped Arrow
333
+ expect(charWidth(0x27a4)).toBe(2); // ➤ Black Right Arrowhead
334
+ expect(charWidth(0x25b6)).toBe(2); // ▶ Black Right Triangle
335
+ expect(charWidth(0x23f1)).toBe(2); // ⏱ Stopwatch
336
+ });
299
337
  });
300
338
  describe("sym() factory", () => {
301
339
  it("creates a symbol with auto-detected width 1 for ASCII", () => {
@@ -80,7 +80,7 @@ describe("concat", () => {
80
80
  const s = concat(pen.green("✔ "), pen.white("done"));
81
81
  expect(s).toHaveLength(2);
82
82
  expect(spanText(s)).toBe("✔ done");
83
- expect(spanLength(s)).toBe(7); // ✔ is width 2 (dingbat emoji)
83
+ expect(spanLength(s)).toBe(7); // ✔ is width 2 (renders wide on Windows Terminal)
84
84
  });
85
85
  it("accepts plain strings", () => {
86
86
  const s = concat("hello ", pen.cyan("world"));
package/dist/app.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import * as esc from "./ansi/esc.js";
9
9
  import { AnsiOutput } from "./ansi/output.js";
10
- import { detectTerminal, } from "./ansi/terminal-env.js";
10
+ import { detectTerminal } from "./ansi/terminal-env.js";
11
11
  import { DrawingContext } from "./drawing/context.js";
12
12
  import { createInputProcessor } from "./input/processor.js";
13
13
  import { disableRawMode, enableRawMode } from "./input/raw-mode.js";
@@ -99,8 +99,7 @@ export class App {
99
99
  enableRawMode();
100
100
  // 2. Create ANSI output
101
101
  this._output = new AnsiOutput(stdout);
102
- // 3. Prepare terminal (custom sequence instead of prepareTerminal()
103
- // so we can conditionally enable mouse tracking)
102
+ // 3. Prepare terminal (ANSI sequences for alternate screen, mouse, etc.)
104
103
  this._prepareTerminal();
105
104
  // 4. Set terminal title
106
105
  if (this._title) {
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export type { Constraint, Point, Rect, Size, } from "./layout/types.js";
16
16
  export * as esc from "./ansi/esc.js";
17
17
  export { AnsiOutput } from "./ansi/output.js";
18
18
  export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
19
- export { type TerminalCaps, detectTerminal, } from "./ansi/terminal-env.js";
19
+ export { detectTerminal, type TerminalCaps, } from "./ansi/terminal-env.js";
20
20
  export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
21
21
  export { RenderTarget } from "./render/render-target.js";
22
22
  export { EscapeMatcher } from "./input/escape-matcher.js";
@@ -114,49 +114,115 @@ export function charWidth(codePoint) {
114
114
  // CJK Compatibility Ideographs Supplement
115
115
  if (codePoint >= 0x2f800 && codePoint <= 0x2fa1f)
116
116
  return 2;
117
- // ── Emoji ranges (rendered as width 2 on modern terminals) ─────
118
- // Hourglass + Watch
117
+ // ── Emoji with Emoji_Presentation=Yes (rendered as 2 cells by default) ──
118
+ // Only characters that terminals render as wide WITHOUT a variation selector.
119
+ // ── Text-presentation characters that Windows Terminal renders as wide ──
120
+ // These have Emoji_Presentation=No in Unicode but modern terminals (Windows
121
+ // Terminal, VS Code integrated terminal) render them as double-width emoji.
122
+ if (codePoint === 0x2139)
123
+ return 2; // ℹ Information Source
124
+ if (codePoint === 0x2605 || codePoint === 0x2606)
125
+ return 2; // ★☆ Stars
126
+ if (codePoint === 0x2660)
127
+ return 2; // ♠ Black Spade Suit
128
+ if (codePoint === 0x2663)
129
+ return 2; // ♣ Black Club Suit
130
+ if (codePoint === 0x2665)
131
+ return 2; // ♥ Black Heart Suit
132
+ if (codePoint === 0x2666)
133
+ return 2; // ♦ Black Diamond Suit
134
+ if (codePoint === 0x2690 || codePoint === 0x2691)
135
+ return 2; // ⚐⚑ Flags
136
+ if (codePoint === 0x2699)
137
+ return 2; // ⚙ Gear
138
+ if (codePoint === 0x26a0)
139
+ return 2; // ⚠ Warning Sign
140
+ if (codePoint === 0x2714)
141
+ return 2; // ✔ Heavy Check Mark
142
+ if (codePoint === 0x2716)
143
+ return 2; // ✖ Heavy Multiplication X
144
+ if (codePoint === 0x279c)
145
+ return 2; // ➜ Heavy Round-Tipped Arrow
146
+ if (codePoint === 0x27a4)
147
+ return 2; // ➤ Black Right Arrowhead
148
+ if (codePoint === 0x25b6)
149
+ return 2; // ▶ Black Right Triangle
150
+ if (codePoint === 0x23f1)
151
+ return 2; // ⏱ Stopwatch
152
+ // Hourglass + Watch (⌚⌛)
119
153
  if (codePoint === 0x231a || codePoint === 0x231b)
120
154
  return 2;
121
- // Player controls (⏩-⏳)
122
- if (codePoint >= 0x23e9 && codePoint <= 0x23f3)
123
- return 2;
124
- // Media controls (⏸-⏺)
125
- if (codePoint >= 0x23f8 && codePoint <= 0x23fa)
126
- return 2;
127
- // Play / reverse play buttons
128
- if (codePoint === 0x25b6 || codePoint === 0x25c0)
129
- return 2;
130
- // Geometric shapes used as emoji (◻◼◽◾)
131
- if (codePoint >= 0x25fb && codePoint <= 0x25fe)
132
- return 2;
133
- // Miscellaneous Symbols — most have emoji presentation (☀-⛿)
134
- if (codePoint >= 0x2600 && codePoint <= 0x26ff)
135
- return 2;
136
- // Dingbats with emoji presentation (✂-➿)
137
- if (codePoint >= 0x2702 && codePoint <= 0x27b0)
138
- return 2;
139
- // Curly loop
140
- if (codePoint === 0x27bf)
141
- return 2;
142
- // Supplemental arrows used as emoji
143
- if (codePoint === 0x2934 || codePoint === 0x2935)
144
- return 2;
145
- // Misc symbols used as emoji (⬛⬜⭐⭕)
146
- if (codePoint >= 0x2b05 && codePoint <= 0x2b07)
147
- return 2;
155
+ // Fast-forward through rewind (⏩⏪⏫⏬)
156
+ if (codePoint >= 0x23e9 && codePoint <= 0x23ec)
157
+ return 2;
158
+ // Alarm clock ()
159
+ if (codePoint === 0x23f0)
160
+ return 2;
161
+ // Hourglass flowing (⏳)
162
+ if (codePoint === 0x23f3)
163
+ return 2;
164
+ // White/black medium small square with emoji pres (◽◾)
165
+ if (codePoint === 0x25fd || codePoint === 0x25fe)
166
+ return 2;
167
+ // Misc Symbols — only Emoji_Presentation=Yes entries
168
+ if (codePoint === 0x2614 || codePoint === 0x2615)
169
+ return 2; // ☔☕
170
+ if (codePoint >= 0x2648 && codePoint <= 0x2653)
171
+ return 2; // ♈-♓
172
+ if (codePoint === 0x267f)
173
+ return 2; //
174
+ if (codePoint === 0x2693)
175
+ return 2; // ⚓
176
+ if (codePoint === 0x26a1)
177
+ return 2; //
178
+ if (codePoint === 0x26aa || codePoint === 0x26ab)
179
+ return 2; // ⚪⚫
180
+ if (codePoint === 0x26bd || codePoint === 0x26be)
181
+ return 2; // ⚽⚾
182
+ if (codePoint === 0x26c4 || codePoint === 0x26c5)
183
+ return 2; // ⛄⛅
184
+ if (codePoint === 0x26ce)
185
+ return 2; // ⛎
186
+ if (codePoint === 0x26d4)
187
+ return 2; // ⛔
188
+ if (codePoint === 0x26ea)
189
+ return 2; // ⛪
190
+ if (codePoint === 0x26f2 || codePoint === 0x26f3)
191
+ return 2; // ⛲⛳
192
+ if (codePoint === 0x26f5)
193
+ return 2; // ⛵
194
+ if (codePoint === 0x26fa)
195
+ return 2; // ⛺
196
+ if (codePoint === 0x26fd)
197
+ return 2; // ⛽
198
+ // Dingbats — only Emoji_Presentation=Yes entries
199
+ if (codePoint === 0x2705)
200
+ return 2; // ✅
201
+ if (codePoint === 0x270a || codePoint === 0x270b)
202
+ return 2; // ✊✋
203
+ if (codePoint === 0x2728)
204
+ return 2; // ✨
205
+ if (codePoint === 0x274c)
206
+ return 2; // ❌
207
+ if (codePoint === 0x274e)
208
+ return 2; // ❎
209
+ if (codePoint >= 0x2753 && codePoint <= 0x2755)
210
+ return 2; // ❓❔❕
211
+ if (codePoint === 0x2757)
212
+ return 2; // ❗
213
+ if (codePoint === 0x2764)
214
+ return 2; // ❤
215
+ if (codePoint >= 0x2795 && codePoint <= 0x2797)
216
+ return 2; // ➕➖➗
217
+ // Curly loop / double curly loop (➰➿)
218
+ if (codePoint === 0x27b0 || codePoint === 0x27bf)
219
+ return 2;
220
+ // Black large square/circle, star, hollow circle (⬛⬜⭐⭕)
148
221
  if (codePoint === 0x2b1b || codePoint === 0x2b1c)
149
222
  return 2;
150
223
  if (codePoint === 0x2b50 || codePoint === 0x2b55)
151
224
  return 2;
152
- // Copyright / Registered / TM (when emoji-styled)
153
- if (codePoint === 0x00a9 || codePoint === 0x00ae)
154
- return 2;
155
- // Wavy dash, part alternation mark
156
- if (codePoint === 0x3030 || codePoint === 0x303d)
157
- return 2;
158
225
  // SMP Emoji: Mahjong through Symbols & Pictographs Extended-A
159
- // Covers emoticons, transport, flags, supplemental symbols, etc.
160
226
  if (codePoint >= 0x1f000 && codePoint <= 0x1faff)
161
227
  return 2;
162
228
  return 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/consolonia",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
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",