@nick-skriabin/glyph 0.1.23 → 0.1.25

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 CHANGED
@@ -18,6 +18,7 @@
18
18
  </p>
19
19
 
20
20
  <p align="center">
21
+ <a href="https://www.npmjs.com/package/@nick-skriabin/glyph"><img src="https://img.shields.io/npm/v/@nick-skriabin/glyph?color=crimson&logo=npm" alt="npm version"></a>
21
22
  <img src="https://img.shields.io/badge/React-18%2B-61dafb?logo=react&logoColor=white" alt="React 18+">
22
23
  <img src="https://img.shields.io/badge/Yoga-Flexbox-mediumpurple?logo=meta&logoColor=white" alt="Yoga Flexbox">
23
24
  <img src="https://img.shields.io/badge/TypeScript-First-3178c6?logo=typescript&logoColor=white" alt="TypeScript">
@@ -616,6 +617,24 @@ pnpm --filter showcase dev # Progress, Spinner, Toasts
616
617
 
617
618
  ---
618
619
 
620
+ ## Who Uses Glyph
621
+
622
+ <table>
623
+ <tr>
624
+ <td align="center">
625
+ <a href="https://github.com/nick-skriabin/aion">
626
+ <strong>Aion</strong>
627
+ </a>
628
+ <br>
629
+ <sub>Calendar & time management TUI</sub>
630
+ </td>
631
+ </tr>
632
+ </table>
633
+
634
+ <sub>Using Glyph in your project? <a href="https://github.com/nick-skriabin/glyph/issues">Let us know!</a></sub>
635
+
636
+ ---
637
+
619
638
  ## Architecture
620
639
 
621
640
  ```
package/dist/index.cjs CHANGED
@@ -348,9 +348,18 @@ var Terminal = class {
348
348
  resetStyles() {
349
349
  this.write(`${CSI}0m`);
350
350
  }
351
+ /** Enable kitty keyboard protocol for enhanced key detection */
352
+ enableKittyKeyboard() {
353
+ this.write(`${CSI}>1u`);
354
+ }
355
+ /** Disable kitty keyboard protocol */
356
+ disableKittyKeyboard() {
357
+ this.write(`${CSI}<u`);
358
+ }
351
359
  setup() {
352
360
  this.enterRawMode();
353
361
  this.enterAltScreen();
362
+ this.enableKittyKeyboard();
354
363
  this.hideCursor();
355
364
  this.clearScreen();
356
365
  this.attachStdinListener();
@@ -365,6 +374,7 @@ var Terminal = class {
365
374
  }
366
375
  this.resetStyles();
367
376
  this.resetCursorColor();
377
+ this.disableKittyKeyboard();
368
378
  this.showCursor();
369
379
  this.exitAltScreen();
370
380
  this.exitRawMode();
@@ -379,6 +389,7 @@ var Terminal = class {
379
389
  this.oscAccum = "";
380
390
  this.resetStyles();
381
391
  this.resetCursorColor();
392
+ this.disableKittyKeyboard();
382
393
  this.showCursor();
383
394
  this.exitAltScreen();
384
395
  this.exitRawMode();
@@ -387,6 +398,7 @@ var Terminal = class {
387
398
  resume() {
388
399
  this.enterRawMode();
389
400
  this.enterAltScreen();
401
+ this.enableKittyKeyboard();
390
402
  this.hideCursor();
391
403
  this.clearScreen();
392
404
  }
@@ -536,6 +548,211 @@ var Terminal = class {
536
548
  };
537
549
 
538
550
  // src/runtime/input.ts
551
+ function getKeyNameFromCode(code) {
552
+ switch (code) {
553
+ // Standard ASCII
554
+ case 9:
555
+ return "tab";
556
+ case 13:
557
+ return "return";
558
+ case 27:
559
+ return "escape";
560
+ case 32:
561
+ return " ";
562
+ case 127:
563
+ return "backspace";
564
+ // Kitty protocol special keys
565
+ case 57358:
566
+ return "capslock";
567
+ case 57359:
568
+ return "scrolllock";
569
+ case 57360:
570
+ return "numlock";
571
+ case 57361:
572
+ return "printscreen";
573
+ case 57362:
574
+ return "pause";
575
+ case 57363:
576
+ return "menu";
577
+ // Function keys (kitty uses these codes)
578
+ case 57364:
579
+ return "f13";
580
+ case 57365:
581
+ return "f14";
582
+ case 57366:
583
+ return "f15";
584
+ case 57367:
585
+ return "f16";
586
+ case 57368:
587
+ return "f17";
588
+ case 57369:
589
+ return "f18";
590
+ case 57370:
591
+ return "f19";
592
+ case 57371:
593
+ return "f20";
594
+ case 57372:
595
+ return "f21";
596
+ case 57373:
597
+ return "f22";
598
+ case 57374:
599
+ return "f23";
600
+ case 57375:
601
+ return "f24";
602
+ case 57376:
603
+ return "f25";
604
+ // Keypad keys
605
+ case 57399:
606
+ return "kp0";
607
+ case 57400:
608
+ return "kp1";
609
+ case 57401:
610
+ return "kp2";
611
+ case 57402:
612
+ return "kp3";
613
+ case 57403:
614
+ return "kp4";
615
+ case 57404:
616
+ return "kp5";
617
+ case 57405:
618
+ return "kp6";
619
+ case 57406:
620
+ return "kp7";
621
+ case 57407:
622
+ return "kp8";
623
+ case 57408:
624
+ return "kp9";
625
+ case 57409:
626
+ return "kpdecimal";
627
+ case 57410:
628
+ return "kpdivide";
629
+ case 57411:
630
+ return "kpmultiply";
631
+ case 57412:
632
+ return "kpminus";
633
+ case 57413:
634
+ return "kpplus";
635
+ case 57414:
636
+ return "kpenter";
637
+ case 57415:
638
+ return "kpequal";
639
+ // Navigation (kitty protocol)
640
+ case 57416:
641
+ return "kpleft";
642
+ case 57417:
643
+ return "kpright";
644
+ case 57418:
645
+ return "kpup";
646
+ case 57419:
647
+ return "kpdown";
648
+ case 57420:
649
+ return "kppageup";
650
+ case 57421:
651
+ return "kppagedown";
652
+ case 57422:
653
+ return "kphome";
654
+ case 57423:
655
+ return "kpend";
656
+ case 57424:
657
+ return "kpinsert";
658
+ case 57425:
659
+ return "kpdelete";
660
+ // Media keys
661
+ case 57428:
662
+ return "mediaplaypause";
663
+ case 57429:
664
+ return "mediastop";
665
+ case 57430:
666
+ return "mediaprev";
667
+ case 57431:
668
+ return "medianext";
669
+ case 57432:
670
+ return "mediarewind";
671
+ case 57433:
672
+ return "mediafastforward";
673
+ case 57434:
674
+ return "mediamute";
675
+ case 57435:
676
+ return "volumedown";
677
+ case 57436:
678
+ return "volumeup";
679
+ default:
680
+ if (code >= 32 && code <= 126) {
681
+ return String.fromCharCode(code).toLowerCase();
682
+ }
683
+ return "unknown";
684
+ }
685
+ }
686
+ function getTildeKeyName(param) {
687
+ const baseParam = param.split(";")[0];
688
+ switch (baseParam) {
689
+ case "1":
690
+ return "home";
691
+ case "2":
692
+ return "insert";
693
+ case "3":
694
+ return "delete";
695
+ case "4":
696
+ return "end";
697
+ case "5":
698
+ return "pageup";
699
+ case "6":
700
+ return "pagedown";
701
+ case "7":
702
+ return "home";
703
+ case "8":
704
+ return "end";
705
+ case "11":
706
+ return "f1";
707
+ case "12":
708
+ return "f2";
709
+ case "13":
710
+ return "f3";
711
+ case "14":
712
+ return "f4";
713
+ case "15":
714
+ return "f5";
715
+ case "17":
716
+ return "f6";
717
+ case "18":
718
+ return "f7";
719
+ case "19":
720
+ return "f8";
721
+ case "20":
722
+ return "f9";
723
+ case "21":
724
+ return "f10";
725
+ case "23":
726
+ return "f11";
727
+ case "24":
728
+ return "f12";
729
+ case "25":
730
+ return "f13";
731
+ case "26":
732
+ return "f14";
733
+ case "28":
734
+ return "f15";
735
+ case "29":
736
+ return "f16";
737
+ case "31":
738
+ return "f17";
739
+ case "32":
740
+ return "f18";
741
+ case "33":
742
+ return "f19";
743
+ case "34":
744
+ return "f20";
745
+ default:
746
+ return "unknown";
747
+ }
748
+ }
749
+ function applyModifiers(key, mod) {
750
+ const m = mod - 1;
751
+ if (m & 1) key.shift = true;
752
+ if (m & 2) key.alt = true;
753
+ if (m & 4) key.ctrl = true;
754
+ if (m & 8) key.meta = true;
755
+ }
539
756
  function parseKeySequence(data) {
540
757
  const keys = [];
541
758
  let i = 0;
@@ -551,9 +768,17 @@ function parseKeySequence(data) {
551
768
  continue;
552
769
  }
553
770
  }
771
+ if (data[i + 1] === "O") {
772
+ const seq = parseSs3Sequence(data, i);
773
+ if (seq) {
774
+ keys.push(seq.key);
775
+ i = seq.end;
776
+ continue;
777
+ }
778
+ }
554
779
  if (i + 1 < data.length && data.charCodeAt(i + 1) >= 32) {
555
780
  keys.push({
556
- name: data[i + 1],
781
+ name: data[i + 1].toLowerCase(),
557
782
  sequence: data.substring(i, i + 2),
558
783
  alt: true
559
784
  });
@@ -588,6 +813,102 @@ function parseKeySequence(data) {
588
813
  }
589
814
  return keys;
590
815
  }
816
+ function parseSs3Sequence(data, start) {
817
+ if (start + 2 >= data.length) return null;
818
+ const final = data[start + 2];
819
+ const sequence = data.substring(start, start + 3);
820
+ let key;
821
+ switch (final) {
822
+ // Arrow keys (some terminals)
823
+ case "A":
824
+ key = { name: "up", sequence };
825
+ break;
826
+ case "B":
827
+ key = { name: "down", sequence };
828
+ break;
829
+ case "C":
830
+ key = { name: "right", sequence };
831
+ break;
832
+ case "D":
833
+ key = { name: "left", sequence };
834
+ break;
835
+ // Home/End (some terminals)
836
+ case "H":
837
+ key = { name: "home", sequence };
838
+ break;
839
+ case "F":
840
+ key = { name: "end", sequence };
841
+ break;
842
+ // Function keys F1-F4
843
+ case "P":
844
+ key = { name: "f1", sequence };
845
+ break;
846
+ case "Q":
847
+ key = { name: "f2", sequence };
848
+ break;
849
+ case "R":
850
+ key = { name: "f3", sequence };
851
+ break;
852
+ case "S":
853
+ key = { name: "f4", sequence };
854
+ break;
855
+ // Keypad (application mode)
856
+ case "j":
857
+ key = { name: "kpmultiply", sequence };
858
+ break;
859
+ case "k":
860
+ key = { name: "kpplus", sequence };
861
+ break;
862
+ case "l":
863
+ key = { name: "kpcomma", sequence };
864
+ break;
865
+ case "m":
866
+ key = { name: "kpminus", sequence };
867
+ break;
868
+ case "n":
869
+ key = { name: "kpdecimal", sequence };
870
+ break;
871
+ case "o":
872
+ key = { name: "kpdivide", sequence };
873
+ break;
874
+ case "p":
875
+ key = { name: "kp0", sequence };
876
+ break;
877
+ case "q":
878
+ key = { name: "kp1", sequence };
879
+ break;
880
+ case "r":
881
+ key = { name: "kp2", sequence };
882
+ break;
883
+ case "s":
884
+ key = { name: "kp3", sequence };
885
+ break;
886
+ case "t":
887
+ key = { name: "kp4", sequence };
888
+ break;
889
+ case "u":
890
+ key = { name: "kp5", sequence };
891
+ break;
892
+ case "v":
893
+ key = { name: "kp6", sequence };
894
+ break;
895
+ case "w":
896
+ key = { name: "kp7", sequence };
897
+ break;
898
+ case "x":
899
+ key = { name: "kp8", sequence };
900
+ break;
901
+ case "y":
902
+ key = { name: "kp9", sequence };
903
+ break;
904
+ case "M":
905
+ key = { name: "kpenter", sequence };
906
+ break;
907
+ default:
908
+ return null;
909
+ }
910
+ return { key, end: start + 3 };
911
+ }
591
912
  function parseCsiSequence(data, start) {
592
913
  let i = start + 2;
593
914
  let params = "";
@@ -606,6 +927,7 @@ function parseCsiSequence(data, start) {
606
927
  i++;
607
928
  let key;
608
929
  switch (final) {
930
+ // Arrow keys
609
931
  case "A":
610
932
  key = { name: "up", sequence };
611
933
  break;
@@ -618,43 +940,73 @@ function parseCsiSequence(data, start) {
618
940
  case "D":
619
941
  key = { name: "left", sequence };
620
942
  break;
943
+ // Home/End
621
944
  case "H":
622
945
  key = { name: "home", sequence };
623
946
  break;
624
947
  case "F":
625
948
  key = { name: "end", sequence };
626
949
  break;
950
+ // Shift+Tab
627
951
  case "Z":
628
952
  key = { name: "tab", sequence, shift: true };
629
953
  break;
954
+ // Function keys (some terminals)
955
+ case "P":
956
+ key = { name: "f1", sequence };
957
+ break;
958
+ case "Q":
959
+ key = { name: "f2", sequence };
960
+ break;
961
+ case "R":
962
+ key = { name: "f3", sequence };
963
+ break;
964
+ case "S":
965
+ key = { name: "f4", sequence };
966
+ break;
967
+ // ~ terminated sequences (VT-style)
630
968
  case "~": {
631
- switch (params) {
632
- case "2":
633
- key = { name: "insert", sequence };
634
- break;
635
- case "3":
636
- key = { name: "delete", sequence };
637
- break;
638
- case "5":
639
- key = { name: "pageup", sequence };
640
- break;
641
- case "6":
642
- key = { name: "pagedown", sequence };
643
- break;
644
- default:
645
- key = { name: "unknown", sequence };
969
+ if (params.startsWith("27;")) {
970
+ const modParts = params.split(";");
971
+ const mod = parseInt(modParts[1] ?? "1", 10);
972
+ const keyCode = parseInt(modParts[2] ?? "0", 10);
973
+ key = { name: getKeyNameFromCode(keyCode), sequence };
974
+ applyModifiers(key, mod);
975
+ break;
976
+ }
977
+ key = { name: getTildeKeyName(params), sequence };
978
+ if (params.includes(";")) {
979
+ const parts = params.split(";");
980
+ const mod = parseInt(parts[1] ?? "1", 10);
981
+ applyModifiers(key, mod);
646
982
  }
647
983
  break;
648
984
  }
985
+ // Kitty keyboard protocol: CSI code;mod u
986
+ case "u": {
987
+ const parts = params.split(";");
988
+ const keyCode = parseInt(parts[0] ?? "0", 10);
989
+ const mod = parseInt(parts[1] ?? "1", 10);
990
+ key = { name: getKeyNameFromCode(keyCode), sequence };
991
+ applyModifiers(key, mod);
992
+ break;
993
+ }
994
+ // Focus events (if terminal reports them)
995
+ case "I":
996
+ key = { name: "focus", sequence };
997
+ break;
998
+ case "O":
999
+ key = { name: "blur", sequence };
1000
+ break;
649
1001
  default:
650
1002
  key = { name: "unknown", sequence };
651
1003
  }
652
- if (params.includes(";")) {
1004
+ if (params.includes(";") && !["~", "u"].includes(final)) {
653
1005
  const parts = params.split(";");
654
- const mod = parseInt(parts[1] ?? "1", 10) - 1;
655
- if (mod & 1) key.shift = true;
656
- if (mod & 2) key.alt = true;
657
- if (mod & 4) key.ctrl = true;
1006
+ const mod = parseInt(parts[parts.length - 1] ?? "1", 10);
1007
+ if (mod >= 1 && mod <= 16) {
1008
+ applyModifiers(key, mod);
1009
+ }
658
1010
  }
659
1011
  return { key, end: i };
660
1012
  }
@@ -2357,7 +2709,8 @@ function parseKeyDescriptor(descriptor) {
2357
2709
  name,
2358
2710
  ctrl: parts.includes("ctrl"),
2359
2711
  alt: parts.includes("alt"),
2360
- shift: parts.includes("shift")
2712
+ shift: parts.includes("shift"),
2713
+ meta: parts.includes("meta") || parts.includes("cmd") || parts.includes("super") || parts.includes("win")
2361
2714
  };
2362
2715
  }
2363
2716
  function matchesKey(matcher, key) {
@@ -2365,6 +2718,7 @@ function matchesKey(matcher, key) {
2365
2718
  if (matcher.ctrl !== !!key.ctrl) return false;
2366
2719
  if (matcher.alt !== !!key.alt) return false;
2367
2720
  if (matcher.shift !== !!key.shift) return false;
2721
+ if (matcher.meta !== !!key.meta) return false;
2368
2722
  return true;
2369
2723
  }
2370
2724
  function Keybind({