@particle-academy/fancy-code 0.15.1 → 0.17.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.cjs CHANGED
@@ -42,7 +42,8 @@ function CodeEditorPanel({ className }) {
42
42
  _diffAnnotations: diffAnnotations,
43
43
  _maxHeight,
44
44
  _minHeight,
45
- _highlightLines: highlightLines
45
+ _highlightLines: highlightLines,
46
+ _aria: aria
46
47
  } = useCodeEditor();
47
48
  const lineNumberElements = react.useMemo(() => {
48
49
  if (!engine || !lineNumbers) return null;
@@ -132,6 +133,7 @@ function CodeEditorPanel({ className }) {
132
133
  themeColors,
133
134
  handleKeyDown,
134
135
  handleInput,
136
+ handleBlur,
135
137
  handleScroll,
136
138
  handleSelect,
137
139
  scrollTop,
@@ -204,8 +206,12 @@ function CodeEditorPanel({ className }) {
204
206
  "textarea",
205
207
  {
206
208
  ref: textareaRef,
209
+ "aria-label": aria?.["aria-label"],
210
+ "aria-labelledby": aria?.["aria-labelledby"],
211
+ "aria-describedby": aria?.["aria-describedby"],
207
212
  className: reactFancy.cn(
208
- "relative m-0 block resize-none border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent outline-none",
213
+ "relative m-0 block resize-none border-none bg-transparent p-2.5 text-[13px] leading-[1.5] text-transparent",
214
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] dark:focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]",
209
215
  wordWrap ? "w-full" : "min-w-full"
210
216
  ),
211
217
  style: {
@@ -222,6 +228,7 @@ function CodeEditorPanel({ className }) {
222
228
  readOnly,
223
229
  onKeyDown: handleKeyDown,
224
230
  onInput: handleInput,
231
+ onBlur: handleBlur,
225
232
  onScroll: handleScroll,
226
233
  onSelect: handleSelect,
227
234
  onClick: handleSelect
@@ -268,864 +275,20 @@ function getRegisteredLanguages() {
268
275
  }
269
276
  return names;
270
277
  }
271
-
272
- // src/engine/tokenizers/javascript.ts
273
- var KEYWORDS = /* @__PURE__ */ new Set([
274
- "abstract",
275
- "arguments",
276
- "as",
277
- "async",
278
- "await",
279
- "boolean",
280
- "break",
281
- "byte",
282
- "case",
283
- "catch",
284
- "char",
285
- "class",
286
- "const",
287
- "continue",
288
- "debugger",
289
- "default",
290
- "delete",
291
- "do",
292
- "double",
293
- "else",
294
- "enum",
295
- "export",
296
- "extends",
297
- "false",
298
- "final",
299
- "finally",
300
- "float",
301
- "for",
302
- "from",
303
- "function",
304
- "get",
305
- "goto",
306
- "if",
307
- "implements",
308
- "import",
309
- "in",
310
- "instanceof",
311
- "int",
312
- "interface",
313
- "keyof",
314
- "let",
315
- "long",
316
- "native",
317
- "new",
318
- "null",
319
- "of",
320
- "package",
321
- "private",
322
- "protected",
323
- "public",
324
- "readonly",
325
- "return",
326
- "set",
327
- "short",
328
- "static",
329
- "super",
330
- "switch",
331
- "synchronized",
332
- "this",
333
- "throw",
334
- "throws",
335
- "transient",
336
- "true",
337
- "try",
338
- "type",
339
- "typeof",
340
- "undefined",
341
- "var",
342
- "void",
343
- "volatile",
344
- "while",
345
- "with",
346
- "yield"
347
- ]);
348
- var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
349
- "string",
350
- "number",
351
- "boolean",
352
- "any",
353
- "void",
354
- "never",
355
- "unknown",
356
- "object",
357
- "symbol",
358
- "bigint",
359
- "undefined",
360
- "null"
361
- ]);
362
- var tokenizeJavaScript = (source) => {
363
- const tokens = [];
364
- const len = source.length;
365
- let i = 0;
366
- while (i < len) {
367
- const ch = source[i];
368
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
369
- i++;
370
- continue;
371
- }
372
- if (ch === "/" && source[i + 1] === "/") {
373
- const pos = i;
374
- i += 2;
375
- while (i < len && source[i] !== "\n") i++;
376
- tokens.push({ type: "comment", start: pos, end: i });
377
- continue;
378
- }
379
- if (ch === "/" && source[i + 1] === "*") {
380
- const pos = i;
381
- i += 2;
382
- while (i < len && !(source[i] === "*" && source[i + 1] === "/")) i++;
383
- i += 2;
384
- tokens.push({ type: "comment", start: pos, end: i });
385
- continue;
386
- }
387
- if (ch === "`") {
388
- const pos = i;
389
- i++;
390
- while (i < len && source[i] !== "`") {
391
- if (source[i] === "\\") i++;
392
- i++;
393
- }
394
- i++;
395
- tokens.push({ type: "string", start: pos, end: i });
396
- continue;
397
- }
398
- if (ch === '"' || ch === "'") {
399
- const pos = i;
400
- const quote = ch;
401
- i++;
402
- while (i < len && source[i] !== quote) {
403
- if (source[i] === "\\") i++;
404
- i++;
405
- }
406
- i++;
407
- tokens.push({ type: "string", start: pos, end: i });
408
- continue;
409
- }
410
- if (ch >= "0" && ch <= "9" || ch === "." && source[i + 1] >= "0" && source[i + 1] <= "9") {
411
- const pos = i;
412
- if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
413
- i += 2;
414
- while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
415
- } else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
416
- i += 2;
417
- while (i < len && /[01_]/.test(source[i])) i++;
418
- } else {
419
- while (i < len && /[0-9_.]/.test(source[i])) i++;
420
- if (i < len && (source[i] === "e" || source[i] === "E")) {
421
- i++;
422
- if (i < len && (source[i] === "+" || source[i] === "-")) i++;
423
- while (i < len && source[i] >= "0" && source[i] <= "9") i++;
424
- }
425
- }
426
- if (i < len && source[i] === "n") i++;
427
- tokens.push({ type: "number", start: pos, end: i });
428
- continue;
429
- }
430
- if (/[a-zA-Z_$]/.test(ch)) {
431
- const pos = i;
432
- i++;
433
- while (i < len && /[a-zA-Z0-9_$]/.test(source[i])) i++;
434
- const word = source.slice(pos, i);
435
- let j = i;
436
- while (j < len && (source[j] === " " || source[j] === " ")) j++;
437
- if (TYPE_KEYWORDS.has(word)) {
438
- tokens.push({ type: "type", start: pos, end: i });
439
- } else if (KEYWORDS.has(word)) {
440
- tokens.push({ type: "keyword", start: pos, end: i });
441
- } else if (source[j] === "(") {
442
- tokens.push({ type: "function", start: pos, end: i });
443
- } else if (word[0] >= "A" && word[0] <= "Z") {
444
- tokens.push({ type: "type", start: pos, end: i });
445
- } else {
446
- tokens.push({ type: "variable", start: pos, end: i });
447
- }
448
- continue;
449
- }
450
- if ("+-*/%=<>!&|^~?:".includes(ch)) {
451
- const pos = i;
452
- i++;
453
- while (i < len && "+-*/%=<>!&|^~?:".includes(source[i])) i++;
454
- tokens.push({ type: "operator", start: pos, end: i });
455
- continue;
456
- }
457
- if ("(){}[];,.@#".includes(ch)) {
458
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
459
- i++;
460
- continue;
461
- }
462
- tokens.push({ type: "plain", start: i, end: i + 1 });
463
- i++;
464
- }
465
- return tokens;
466
- };
467
-
468
- // src/engine/tokenizers/php.ts
469
- var PHP_KEYWORDS = /* @__PURE__ */ new Set([
470
- "abstract",
471
- "and",
472
- "array",
473
- "as",
474
- "break",
475
- "callable",
476
- "case",
477
- "catch",
478
- "class",
479
- "clone",
480
- "const",
481
- "continue",
482
- "declare",
483
- "default",
484
- "do",
485
- "echo",
486
- "else",
487
- "elseif",
488
- "empty",
489
- "enddeclare",
490
- "endfor",
491
- "endforeach",
492
- "endif",
493
- "endswitch",
494
- "endwhile",
495
- "enum",
496
- "eval",
497
- "exit",
498
- "extends",
499
- "false",
500
- "final",
501
- "finally",
502
- "fn",
503
- "for",
504
- "foreach",
505
- "function",
506
- "global",
507
- "goto",
508
- "if",
509
- "implements",
510
- "include",
511
- "include_once",
512
- "instanceof",
513
- "insteadof",
514
- "interface",
515
- "isset",
516
- "list",
517
- "match",
518
- "namespace",
519
- "new",
520
- "null",
521
- "or",
522
- "print",
523
- "private",
524
- "protected",
525
- "public",
526
- "readonly",
527
- "require",
528
- "require_once",
529
- "return",
530
- "self",
531
- "static",
532
- "switch",
533
- "throw",
534
- "trait",
535
- "true",
536
- "try",
537
- "unset",
538
- "use",
539
- "var",
540
- "while",
541
- "xor",
542
- "yield"
543
- ]);
544
- function tokenizePhpBlock(source, offset) {
545
- const tokens = [];
546
- const len = source.length;
547
- let i = offset;
548
- while (i < len) {
549
- const ch = source[i];
550
- if (ch === "?" && source[i + 1] === ">") {
551
- return { tokens, end: i };
552
- }
553
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
554
- i++;
555
- continue;
556
- }
557
- if (ch === "/" && source[i + 1] === "/" || ch === "#") {
558
- const pos = i;
559
- while (i < len && source[i] !== "\n") i++;
560
- tokens.push({ type: "comment", start: pos, end: i });
561
- continue;
562
- }
563
- if (ch === "/" && source[i + 1] === "*") {
564
- const pos = i;
565
- i += 2;
566
- while (i < len && !(source[i] === "*" && source[i + 1] === "/")) i++;
567
- i += 2;
568
- tokens.push({ type: "comment", start: pos, end: i });
569
- continue;
570
- }
571
- if (ch === "$") {
572
- const pos = i;
573
- i++;
574
- while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
575
- tokens.push({ type: "variable", start: pos, end: i });
576
- continue;
577
- }
578
- if (ch === '"' || ch === "'") {
579
- const pos = i;
580
- const quote = ch;
581
- i++;
582
- while (i < len && source[i] !== quote) {
583
- if (source[i] === "\\") i++;
584
- i++;
585
- }
586
- i++;
587
- tokens.push({ type: "string", start: pos, end: i });
588
- continue;
589
- }
590
- if (ch >= "0" && ch <= "9") {
591
- const pos = i;
592
- while (i < len && /[0-9_.]/.test(source[i])) i++;
593
- if (i < len && (source[i] === "e" || source[i] === "E")) {
594
- i++;
595
- if (i < len && (source[i] === "+" || source[i] === "-")) i++;
596
- while (i < len && source[i] >= "0" && source[i] <= "9") i++;
597
- }
598
- tokens.push({ type: "number", start: pos, end: i });
599
- continue;
600
- }
601
- if (/[a-zA-Z_]/.test(ch)) {
602
- const pos = i;
603
- i++;
604
- while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
605
- const word = source.slice(pos, i);
606
- let j = i;
607
- while (j < len && (source[j] === " " || source[j] === " ")) j++;
608
- if (PHP_KEYWORDS.has(word.toLowerCase())) {
609
- tokens.push({ type: "keyword", start: pos, end: i });
610
- } else if (source[j] === "(") {
611
- tokens.push({ type: "function", start: pos, end: i });
612
- } else if (word[0] >= "A" && word[0] <= "Z") {
613
- tokens.push({ type: "type", start: pos, end: i });
614
- } else {
615
- tokens.push({ type: "variable", start: pos, end: i });
616
- }
617
- continue;
618
- }
619
- if ("+-*/%=<>!&|^~?:.".includes(ch)) {
620
- const pos = i;
621
- i++;
622
- while (i < len && "+-*/%=<>!&|^~?:.".includes(source[i])) i++;
623
- tokens.push({ type: "operator", start: pos, end: i });
624
- continue;
625
- }
626
- if ("(){}[];,@\\".includes(ch)) {
627
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
628
- i++;
629
- continue;
630
- }
631
- tokens.push({ type: "plain", start: i, end: i + 1 });
632
- i++;
633
- }
634
- return { tokens, end: i };
635
- }
636
- var tokenizePhp = (source) => {
637
- const tokens = [];
638
- const len = source.length;
639
- let i = 0;
640
- let htmlChunkStart = 0;
641
- while (i < len) {
642
- if (source[i] === "<" && source[i + 1] === "?") {
643
- if (i > htmlChunkStart) {
644
- const htmlChunk = source.slice(htmlChunkStart, i);
645
- const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
646
- for (const t of htmlTokens) {
647
- tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
648
- }
649
- }
650
- const tagStart = i;
651
- i += 2;
652
- if (i < len && source.slice(i, i + 3).toLowerCase() === "php") {
653
- i += 3;
654
- } else if (i < len && source[i] === "=") {
655
- i++;
656
- }
657
- tokens.push({ type: "keyword", start: tagStart, end: i });
658
- const result = tokenizePhpBlock(source, i);
659
- tokens.push(...result.tokens);
660
- i = result.end;
661
- if (i < len && source[i] === "?" && source[i + 1] === ">") {
662
- tokens.push({ type: "keyword", start: i, end: i + 2 });
663
- i += 2;
664
- }
665
- htmlChunkStart = i;
666
- continue;
667
- }
668
- i++;
669
- }
670
- if (htmlChunkStart < len) {
671
- const htmlChunk = source.slice(htmlChunkStart);
672
- const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
673
- for (const t of htmlTokens) {
674
- tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
675
- }
676
- }
677
- return tokens;
678
- };
679
-
680
- // src/engine/tokenizers/python.ts
681
- var KEYWORDS2 = /* @__PURE__ */ new Set([
682
- "and",
683
- "as",
684
- "assert",
685
- "async",
686
- "await",
687
- "break",
688
- "class",
689
- "continue",
690
- "def",
691
- "del",
692
- "elif",
693
- "else",
694
- "except",
695
- "finally",
696
- "for",
697
- "from",
698
- "global",
699
- "if",
700
- "import",
701
- "in",
702
- "is",
703
- "lambda",
704
- "nonlocal",
705
- "not",
706
- "or",
707
- "pass",
708
- "raise",
709
- "return",
710
- "try",
711
- "while",
712
- "with",
713
- "yield",
714
- "True",
715
- "False",
716
- "None"
717
- ]);
718
- var TYPES = /* @__PURE__ */ new Set([
719
- "int",
720
- "str",
721
- "float",
722
- "bool",
723
- "list",
724
- "dict",
725
- "tuple",
726
- "set",
727
- "bytes",
728
- "bytearray",
729
- "memoryview",
730
- "range",
731
- "frozenset",
732
- "complex",
733
- "type",
734
- "object",
735
- "property",
736
- "classmethod",
737
- "staticmethod",
738
- "Any",
739
- "Optional",
740
- "Union",
741
- "Callable",
742
- "List",
743
- "Dict",
744
- "Tuple",
745
- "Set",
746
- "Sequence",
747
- "Mapping",
748
- "Iterator",
749
- "Generator",
750
- "Coroutine"
751
- ]);
752
- var tokenizePython = (source) => {
753
- const tokens = [];
754
- const len = source.length;
755
- let i = 0;
756
- while (i < len) {
757
- const ch = source[i];
758
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
759
- i++;
760
- continue;
761
- }
762
- if (ch === "#") {
763
- const pos = i;
764
- i++;
765
- while (i < len && source[i] !== "\n") i++;
766
- tokens.push({ type: "comment", start: pos, end: i });
767
- continue;
768
- }
769
- if (ch === "@" && i + 1 < len && /[a-zA-Z_]/.test(source[i + 1])) {
770
- const pos = i;
771
- i++;
772
- while (i < len && /[a-zA-Z0-9_.]/.test(source[i])) i++;
773
- tokens.push({ type: "keyword", start: pos, end: i });
774
- continue;
775
- }
776
- if (ch === '"' || ch === "'" || (ch === "f" || ch === "r" || ch === "b" || ch === "F" || ch === "R" || ch === "B") && (source[i + 1] === '"' || source[i + 1] === "'")) {
777
- const pos = i;
778
- if (ch !== '"' && ch !== "'") i++;
779
- const quote = source[i];
780
- if (source[i + 1] === quote && source[i + 2] === quote) {
781
- const triple = quote + quote + quote;
782
- i += 3;
783
- while (i < len && source.slice(i, i + 3) !== triple) {
784
- if (source[i] === "\\") i++;
785
- i++;
786
- }
787
- i += 3;
788
- tokens.push({ type: "string", start: pos, end: i });
789
- continue;
790
- }
791
- i++;
792
- while (i < len && source[i] !== quote && source[i] !== "\n") {
793
- if (source[i] === "\\") i++;
794
- i++;
795
- }
796
- if (i < len && source[i] === quote) i++;
797
- tokens.push({ type: "string", start: pos, end: i });
798
- continue;
799
- }
800
- if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
801
- const pos = i;
802
- if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
803
- i += 2;
804
- while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
805
- } else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
806
- i += 2;
807
- while (i < len && /[01_]/.test(source[i])) i++;
808
- } else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
809
- i += 2;
810
- while (i < len && /[0-7_]/.test(source[i])) i++;
811
- } else {
812
- while (i < len && /[0-9_.]/.test(source[i])) i++;
813
- if (i < len && (source[i] === "e" || source[i] === "E")) {
814
- i++;
815
- if (i < len && (source[i] === "+" || source[i] === "-")) i++;
816
- while (i < len && source[i] >= "0" && source[i] <= "9") i++;
817
- }
818
- }
819
- if (i < len && source[i] === "j") i++;
820
- tokens.push({ type: "number", start: pos, end: i });
821
- continue;
822
- }
823
- if (/[a-zA-Z_]/.test(ch)) {
824
- const pos = i;
825
- i++;
826
- while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
827
- const word = source.slice(pos, i);
828
- let j = i;
829
- while (j < len && (source[j] === " " || source[j] === " ")) j++;
830
- if (KEYWORDS2.has(word)) {
831
- tokens.push({ type: "keyword", start: pos, end: i });
832
- } else if (TYPES.has(word)) {
833
- tokens.push({ type: "type", start: pos, end: i });
834
- } else if (source[j] === "(") {
835
- tokens.push({ type: "function", start: pos, end: i });
836
- } else if (word[0] >= "A" && word[0] <= "Z") {
837
- tokens.push({ type: "type", start: pos, end: i });
838
- } else {
839
- tokens.push({ type: "variable", start: pos, end: i });
840
- }
841
- continue;
842
- }
843
- if ("+-*/%=<>!&|^~:@".includes(ch)) {
844
- const pos = i;
845
- i++;
846
- while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
847
- tokens.push({ type: "operator", start: pos, end: i });
848
- continue;
849
- }
850
- if ("()[]{},.;\\".includes(ch)) {
851
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
852
- i++;
853
- continue;
854
- }
855
- tokens.push({ type: "plain", start: i, end: i + 1 });
856
- i++;
857
- }
858
- return tokens;
859
- };
860
-
861
- // src/engine/tokenizers/go.ts
862
- var KEYWORDS3 = /* @__PURE__ */ new Set([
863
- "break",
864
- "case",
865
- "chan",
866
- "const",
867
- "continue",
868
- "default",
869
- "defer",
870
- "else",
871
- "fallthrough",
872
- "for",
873
- "func",
874
- "go",
875
- "goto",
876
- "if",
877
- "import",
878
- "interface",
879
- "map",
880
- "package",
881
- "range",
882
- "return",
883
- "select",
884
- "struct",
885
- "switch",
886
- "type",
887
- "var",
888
- "nil",
889
- "true",
890
- "false",
891
- "iota"
892
- ]);
893
- var TYPES2 = /* @__PURE__ */ new Set([
894
- "bool",
895
- "byte",
896
- "complex64",
897
- "complex128",
898
- "error",
899
- "float32",
900
- "float64",
901
- "int",
902
- "int8",
903
- "int16",
904
- "int32",
905
- "int64",
906
- "rune",
907
- "string",
908
- "uint",
909
- "uint8",
910
- "uint16",
911
- "uint32",
912
- "uint64",
913
- "uintptr",
914
- "any"
915
- ]);
916
- var BUILTINS = /* @__PURE__ */ new Set([
917
- "make",
918
- "len",
919
- "cap",
920
- "new",
921
- "append",
922
- "copy",
923
- "close",
924
- "delete",
925
- "complex",
926
- "real",
927
- "imag",
928
- "panic",
929
- "recover",
930
- "print",
931
- "println"
932
- ]);
933
- var tokenizeGo = (source) => {
934
- const tokens = [];
935
- const len = source.length;
936
- let i = 0;
937
- while (i < len) {
938
- const ch = source[i];
939
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
940
- i++;
941
- continue;
942
- }
943
- if (ch === "/" && source[i + 1] === "/") {
944
- const pos = i;
945
- i += 2;
946
- while (i < len && source[i] !== "\n") i++;
947
- tokens.push({ type: "comment", start: pos, end: i });
948
- continue;
949
- }
950
- if (ch === "/" && source[i + 1] === "*") {
951
- const pos = i;
952
- i += 2;
953
- while (i < len && !(source[i] === "*" && source[i + 1] === "/")) i++;
954
- i += 2;
955
- tokens.push({ type: "comment", start: pos, end: i });
956
- continue;
957
- }
958
- if (ch === "`") {
959
- const pos = i;
960
- i++;
961
- while (i < len && source[i] !== "`") i++;
962
- i++;
963
- tokens.push({ type: "string", start: pos, end: i });
964
- continue;
965
- }
966
- if (ch === '"') {
967
- const pos = i;
968
- i++;
969
- while (i < len && source[i] !== '"' && source[i] !== "\n") {
970
- if (source[i] === "\\") i++;
971
- i++;
972
- }
973
- if (i < len && source[i] === '"') i++;
974
- tokens.push({ type: "string", start: pos, end: i });
975
- continue;
976
- }
977
- if (ch === "'") {
978
- const pos = i;
979
- i++;
980
- while (i < len && source[i] !== "'" && source[i] !== "\n") {
981
- if (source[i] === "\\") i++;
982
- i++;
983
- }
984
- if (i < len && source[i] === "'") i++;
985
- tokens.push({ type: "string", start: pos, end: i });
986
- continue;
987
- }
988
- if (ch >= "0" && ch <= "9" || ch === "." && i + 1 < len && source[i + 1] >= "0" && source[i + 1] <= "9") {
989
- const pos = i;
990
- if (ch === "0" && (source[i + 1] === "x" || source[i + 1] === "X")) {
991
- i += 2;
992
- while (i < len && /[0-9a-fA-F_]/.test(source[i])) i++;
993
- } else if (ch === "0" && (source[i + 1] === "b" || source[i + 1] === "B")) {
994
- i += 2;
995
- while (i < len && /[01_]/.test(source[i])) i++;
996
- } else if (ch === "0" && (source[i + 1] === "o" || source[i + 1] === "O")) {
997
- i += 2;
998
- while (i < len && /[0-7_]/.test(source[i])) i++;
999
- } else {
1000
- while (i < len && /[0-9_.]/.test(source[i])) i++;
1001
- if (i < len && (source[i] === "e" || source[i] === "E")) {
1002
- i++;
1003
- if (i < len && (source[i] === "+" || source[i] === "-")) i++;
1004
- while (i < len && source[i] >= "0" && source[i] <= "9") i++;
1005
- }
1006
- }
1007
- if (i < len && source[i] === "i") i++;
1008
- tokens.push({ type: "number", start: pos, end: i });
1009
- continue;
1010
- }
1011
- if (/[a-zA-Z_]/.test(ch)) {
1012
- const pos = i;
1013
- i++;
1014
- while (i < len && /[a-zA-Z0-9_]/.test(source[i])) i++;
1015
- const word = source.slice(pos, i);
1016
- let j = i;
1017
- while (j < len && (source[j] === " " || source[j] === " ")) j++;
1018
- if (KEYWORDS3.has(word)) {
1019
- tokens.push({ type: "keyword", start: pos, end: i });
1020
- } else if (TYPES2.has(word)) {
1021
- tokens.push({ type: "type", start: pos, end: i });
1022
- } else if (BUILTINS.has(word) && source[j] === "(") {
1023
- tokens.push({ type: "function", start: pos, end: i });
1024
- } else if (source[j] === "(") {
1025
- tokens.push({ type: "function", start: pos, end: i });
1026
- } else if (word[0] >= "A" && word[0] <= "Z") {
1027
- tokens.push({ type: "type", start: pos, end: i });
1028
- } else {
1029
- tokens.push({ type: "variable", start: pos, end: i });
1030
- }
1031
- continue;
1032
- }
1033
- if ("+-*/%=<>!&|^~:".includes(ch)) {
1034
- const pos = i;
1035
- i++;
1036
- while (i < len && "+-*/%=<>!&|^~:".includes(source[i])) i++;
1037
- tokens.push({ type: "operator", start: pos, end: i });
1038
- continue;
1039
- }
1040
- if ("(){}[];,.".includes(ch)) {
1041
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
1042
- i++;
1043
- continue;
1044
- }
1045
- tokens.push({ type: "plain", start: i, end: i + 1 });
1046
- i++;
1047
- }
1048
- return tokens;
1049
- };
1050
-
1051
- // src/engine/tokenizers/markdown.ts
1052
- function tokenizeMarkdown(source) {
1053
- const tokens = [];
1054
- const lines = source.split("\n");
1055
- let offset = 0;
1056
- let inFence = false;
1057
- for (const line of lines) {
1058
- const base = offset;
1059
- const len = line.length;
1060
- offset += len + 1;
1061
- if (/^\s*(```|~~~)/.test(line)) {
1062
- tokens.push(fancyFileCommons.tok("string", base, base + len));
1063
- inFence = !inFence;
1064
- continue;
1065
- }
1066
- if (inFence) {
1067
- tokens.push(fancyFileCommons.tok("string", base, base + len));
1068
- continue;
1069
- }
1070
- if (/^#{1,6}\s/.test(line)) {
1071
- tokens.push(fancyFileCommons.tok("keyword", base, base + len));
1072
- continue;
1073
- }
1074
- if (/^\s*>/.test(line)) {
1075
- tokens.push(fancyFileCommons.tok("comment", base, base + len));
1076
- continue;
1077
- }
1078
- const list = line.match(/^(\s*)([-*+]|\d+\.)\s/);
1079
- if (list) {
1080
- const start = base + list[1].length;
1081
- tokens.push(fancyFileCommons.tok("operator", start, start + list[2].length));
1082
- }
1083
- pushInline(line, base, tokens);
1084
- }
1085
- return tokens;
1086
- }
1087
- function pushInline(line, base, out) {
1088
- if (line.length > 5e3) return;
1089
- const spans = [];
1090
- const add = (type, start, end) => {
1091
- spans.push(fancyFileCommons.tok(type, base + start, base + end));
1092
- };
1093
- let m;
1094
- const code = /`[^`]+`/g;
1095
- while (m = code.exec(line)) add("string", m.index, m.index + m[0].length);
1096
- const emph = /(\*\*|__)(?=\S)([\s\S]*?\S)\1|(\*|_)(?=\S)([\s\S]*?\S)\3/g;
1097
- while (m = emph.exec(line)) add("type", m.index, m.index + m[0].length);
1098
- const link = /!?\[[^\]]*\]\(([^)]+)\)/g;
1099
- while (m = link.exec(line)) {
1100
- add("tag", m.index, m.index + m[0].length - m[1].length - 1);
1101
- const urlStart = m.index + m[0].length - m[1].length - 1;
1102
- add("attributeValue", urlStart, urlStart + m[1].length);
1103
- }
1104
- spans.sort((a, b) => a.start - b.start || b.end - a.end);
1105
- let lastEnd = -1;
1106
- for (const s of spans) {
1107
- if (s.start >= lastEnd) {
1108
- out.push(s);
1109
- lastEnd = s.end;
1110
- }
1111
- }
1112
- }
1113
-
1114
- // src/languages/builtin.ts
1115
278
  registerLanguage({
1116
279
  name: "Markdown",
1117
280
  aliases: ["md", "markdown", "mkd"],
1118
- tokenize: tokenizeMarkdown
281
+ tokenize: fancyFileCommons.tokenizeMarkdown
1119
282
  });
1120
283
  registerLanguage({
1121
284
  name: "JavaScript",
1122
285
  aliases: ["js", "javascript", "jsx"],
1123
- tokenize: tokenizeJavaScript
286
+ tokenize: fancyFileCommons.tokenizeJavaScript
1124
287
  });
1125
288
  registerLanguage({
1126
289
  name: "TypeScript",
1127
290
  aliases: ["ts", "typescript", "tsx"],
1128
- tokenize: tokenizeJavaScript
291
+ tokenize: fancyFileCommons.tokenizeJavaScript
1129
292
  // Same tokenizer — JS/TS syntax is identical for highlighting
1130
293
  });
1131
294
  registerLanguage({
@@ -1136,17 +299,27 @@ registerLanguage({
1136
299
  registerLanguage({
1137
300
  name: "PHP",
1138
301
  aliases: ["php"],
1139
- tokenize: tokenizePhp
302
+ tokenize: fancyFileCommons.tokenizePhp
1140
303
  });
1141
304
  registerLanguage({
1142
305
  name: "Python",
1143
306
  aliases: ["py", "python"],
1144
- tokenize: tokenizePython
307
+ tokenize: fancyFileCommons.tokenizePython
1145
308
  });
1146
309
  registerLanguage({
1147
310
  name: "Go",
1148
311
  aliases: ["go", "golang"],
1149
- tokenize: tokenizeGo
312
+ tokenize: fancyFileCommons.tokenizeGo
313
+ });
314
+ registerLanguage({
315
+ name: "Shell",
316
+ aliases: ["sh", "bash", "zsh", "shell"],
317
+ tokenize: fancyFileCommons.tokenizeShell
318
+ });
319
+ registerLanguage({
320
+ name: "Dotenv",
321
+ aliases: ["env", "dotenv"],
322
+ tokenize: fancyFileCommons.tokenizeDotenv
1150
323
  });
1151
324
  var iconBtnClass = "inline-flex items-center justify-center rounded-md p-1 text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-200";
1152
325
  function LanguageSelector() {
@@ -1317,6 +490,7 @@ function useEditorEngine({
1317
490
  const [scrollTop, setScrollTop] = react.useState(0);
1318
491
  const [scrollLeft, setScrollLeft] = react.useState(0);
1319
492
  const [activeLine, setActiveLine] = react.useState(1);
493
+ const tabArmedRef = react.useRef(false);
1320
494
  const themeColors = react.useMemo(() => {
1321
495
  const def = getTheme(theme);
1322
496
  return def?.colors ?? DEFAULT_COLORS;
@@ -1377,6 +551,7 @@ function useEditorEngine({
1377
551
  const handleInput = react.useCallback(() => {
1378
552
  const ta = textareaRef.current;
1379
553
  if (!ta) return;
554
+ tabArmedRef.current = true;
1380
555
  onChangeRef.current?.(ta.value);
1381
556
  autoResize();
1382
557
  updateCursorInfo();
@@ -1412,14 +587,19 @@ function useEditorEngine({
1412
587
  },
1413
588
  [updateCursorInfo]
1414
589
  );
590
+ const handleBlur = react.useCallback(() => {
591
+ tabArmedRef.current = false;
592
+ }, []);
1415
593
  const handleKeyDown = react.useCallback(
1416
594
  (e) => {
1417
595
  if (readOnly) {
1418
- if (e.key === "c" && (e.metaKey || e.ctrlKey)) return;
1419
- if (e.key === "a" && (e.metaKey || e.ctrlKey)) return;
1420
- e.preventDefault();
1421
596
  return;
1422
597
  }
598
+ if (e.key === "Escape") {
599
+ tabArmedRef.current = false;
600
+ return;
601
+ }
602
+ if (e.key === "Tab" && !tabArmedRef.current) return;
1423
603
  if (e.key === "Tab") {
1424
604
  e.preventDefault();
1425
605
  const ta = e.currentTarget;
@@ -1468,6 +648,7 @@ function useEditorEngine({
1468
648
  }
1469
649
  if (e.key === "Enter") {
1470
650
  e.preventDefault();
651
+ tabArmedRef.current = true;
1471
652
  const ta = e.currentTarget;
1472
653
  const start = ta.selectionStart;
1473
654
  const end = ta.selectionEnd;
@@ -1496,6 +677,7 @@ function useEditorEngine({
1496
677
  themeColors,
1497
678
  handleKeyDown,
1498
679
  handleInput,
680
+ handleBlur,
1499
681
  handleScroll,
1500
682
  handleSelect,
1501
683
  revealLine,
@@ -1536,8 +718,19 @@ function CodeEditorRoot({
1536
718
  cursorLine,
1537
719
  cursorColumn,
1538
720
  diffBase,
1539
- highlightLines
721
+ highlightLines,
722
+ "aria-label": ariaLabel,
723
+ "aria-labelledby": ariaLabelledBy,
724
+ "aria-describedby": ariaDescribedBy
1540
725
  }) {
726
+ const aria = react.useMemo(
727
+ () => ({
728
+ "aria-label": ariaLabelledBy ? void 0 : ariaLabel ?? "Code editor",
729
+ "aria-labelledby": ariaLabelledBy,
730
+ "aria-describedby": ariaDescribedBy
731
+ }),
732
+ [ariaLabel, ariaLabelledBy, ariaDescribedBy]
733
+ );
1541
734
  const [currentValue, setCurrentValue] = reactFancy.useControllableState(valueProp, defaultValue, onChange);
1542
735
  const diffAnnotations = react.useMemo(
1543
736
  () => diffBase == null ? null : fancyFileCommons.annotateLines(diffBase, currentValue),
@@ -1630,9 +823,10 @@ function CodeEditorRoot({
1630
823
  _engineReturn: engineReturn,
1631
824
  _minHeight: minHeight,
1632
825
  _maxHeight: maxHeight,
1633
- _highlightLines: highlightLines
826
+ _highlightLines: highlightLines,
827
+ _aria: aria
1634
828
  }),
1635
- [engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, diffAnnotations, minHeight, maxHeight, highlightLines]
829
+ [engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, diffAnnotations, minHeight, maxHeight, highlightLines, aria]
1636
830
  );
1637
831
  return /* @__PURE__ */ jsxRuntime.jsx(CodeEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
1638
832
  "div",
@@ -1668,7 +862,10 @@ function MarkdownEditor({
1668
862
  minHeight,
1669
863
  maxHeight,
1670
864
  className,
1671
- renderPreview
865
+ renderPreview,
866
+ "aria-label": ariaLabel,
867
+ "aria-labelledby": ariaLabelledBy,
868
+ "aria-describedby": ariaDescribedBy
1672
869
  }) {
1673
870
  const [content, setContent] = reactFancy.useControllableState(value, defaultValue, onValueChange);
1674
871
  const showEditor = mode !== "preview";
@@ -1693,6 +890,9 @@ function MarkdownEditor({
1693
890
  placeholder,
1694
891
  minHeight,
1695
892
  maxHeight,
893
+ "aria-label": ariaLabel ?? "Markdown editor",
894
+ "aria-labelledby": ariaLabelledBy,
895
+ "aria-describedby": ariaDescribedBy,
1696
896
  children: /* @__PURE__ */ jsxRuntime.jsx(CodeEditor.Panel, {})
1697
897
  }
1698
898
  ) }),
@@ -1794,7 +994,10 @@ function FileViewer({
1794
994
  frameProps,
1795
995
  document,
1796
996
  className,
1797
- style
997
+ style,
998
+ "aria-label": ariaLabel,
999
+ "aria-labelledby": ariaLabelledBy,
1000
+ "aria-describedby": ariaDescribedBy
1798
1001
  }) {
1799
1002
  const resolved = fileKind ?? resolveFileKind({ filename, mime });
1800
1003
  if (resolved.kind === "document" && view === "rendered" && readOnly && line == null) {
@@ -1888,6 +1091,9 @@ function FileViewer({
1888
1091
  cursorLine: line,
1889
1092
  cursorColumn: column,
1890
1093
  highlightLines: highlightLines ?? (line != null ? [line] : void 0),
1094
+ "aria-label": ariaLabel ?? filename ?? "File contents",
1095
+ "aria-labelledby": ariaLabelledBy,
1096
+ "aria-describedby": ariaDescribedBy,
1891
1097
  children: /* @__PURE__ */ jsxRuntime.jsx(CodeEditor.Panel, {})
1892
1098
  }
1893
1099
  )
@@ -2081,6 +1287,10 @@ Object.defineProperty(exports, "languageFromFilename", {
2081
1287
  enumerable: true,
2082
1288
  get: function () { return fancyFileCommons.languageFromFilename; }
2083
1289
  });
1290
+ Object.defineProperty(exports, "tokenizeMarkdown", {
1291
+ enumerable: true,
1292
+ get: function () { return fancyFileCommons.tokenizeMarkdown; }
1293
+ });
2084
1294
  exports.CodeEditor = CodeEditor;
2085
1295
  exports.CodeEditorInputTag = CodeEditorInputTag;
2086
1296
  exports.FileViewer = FileViewer;
@@ -2100,7 +1310,6 @@ exports.registerOfficeViewer = registerOfficeViewer;
2100
1310
  exports.registerTheme = registerTheme;
2101
1311
  exports.renderMarkdown = renderMarkdown;
2102
1312
  exports.resolveFileKind = resolveFileKind;
2103
- exports.tokenizeMarkdown = tokenizeMarkdown;
2104
1313
  exports.useCodeEditor = useCodeEditor;
2105
1314
  exports.useCodeEditorInputTagAdapter = useCodeEditorInputTagAdapter;
2106
1315
  //# sourceMappingURL=index.cjs.map