@tekkare/auriga 0.2.3 → 0.2.5

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/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.2.3"
7
+ "version": "0.2.5"
8
8
  }
@@ -75,7 +75,8 @@
75
75
  size="14"
76
76
  color="var(--medium)"
77
77
  />
78
- {{ sugg.text }}
78
+ <span v-if="!highlightInput"> {{ sugg.text }} </span>
79
+ <span v-else v-html="highlightSearchInput(sugg.text)" />
79
80
  </p>
80
81
  </div>
81
82
  <div v-else-if="!loadingSuggs" class="sugg_container">
@@ -268,6 +269,10 @@ export default defineComponent({
268
269
  loadingSuggs: {
269
270
  type: Boolean,
270
271
  default: false
272
+ },
273
+ highlightInput: {
274
+ type: Boolean,
275
+ default: false
271
276
  }
272
277
  },
273
278
  emits: ["update:Value", "onClose"],
@@ -396,6 +401,632 @@ export default defineComponent({
396
401
  searchedWords.value = props.defaultInputs;
397
402
  }
398
403
  });
404
+ function highlightSearchInput(suggestion) {
405
+ const escapedInput = value.value.replace(
406
+ /[-\/\\^$*+?.()|[\]{}]/g,
407
+ "\\<script lang="ts">
408
+ import {
409
+ onMounted,
410
+ onUpdated,
411
+ ref,
412
+ computed,
413
+ defineProps,
414
+ defineEmits,
415
+ watch,
416
+ defineComponent,
417
+ } from "vue";
418
+ import argIcon from "./argIcon.vue";
419
+ import { useFuse } from "@vueuse/integrations/useFuse";
420
+ interface Suggestion {
421
+ icon?: string;
422
+ text: string;
423
+ }
424
+
425
+ export default defineComponent({
426
+ name: "argAdvancedSearchBar",
427
+ components: { argIcon },
428
+ props: {
429
+ placeHolderValue: {
430
+ type: String,
431
+ default: "Type your search",
432
+ },
433
+ customShortcuts: {
434
+ type: Array<{ keyword: ""; value: ""; symbol: "" }>,
435
+ default: () => [],
436
+ },
437
+ defaultInputs: {
438
+ type: Array<{ type: ""; value: "" }>,
439
+ default: () => [],
440
+ },
441
+ suggestions: {
442
+ type: Array<Suggestion>,
443
+ default: null,
444
+ },
445
+ fuzzyThreshold: {
446
+ type: Number,
447
+ default: 0.5,
448
+ },
449
+ lang: {
450
+ type: String,
451
+ default: "en",
452
+ validator: (value: string) => {
453
+ return ["en", "fr", "es"].includes(value);
454
+ },
455
+ },
456
+ loadingSuggs: {
457
+ type: Boolean,
458
+ default: false,
459
+ },
460
+ highlightInput: {
461
+ type: Boolean,
462
+ default: false,
463
+ },
464
+ },
465
+ emits: ["update:Value", "onClose"],
466
+ setup(props, { emit }) {
467
+ const typing = ref(false);
468
+ const shortcuts = ref([
469
+ { keyword: "and", value: "add the word", symbol: "+" },
470
+ { keyword: "or", value: "matches at least one word", symbol: "/" },
471
+ { keyword: "not", value: "exclude the word", symbol: "!" },
472
+ ]);
473
+ const hoverShortcut = ref("" as string);
474
+ const selectedShortcut = ref("+" as string);
475
+ const searchedWords = ref([] as any);
476
+ const closeIndex = ref();
477
+ const value = ref("" as string);
478
+ const selectedTag = ref();
479
+ const duplicate = ref([]);
480
+ const searchBar = ref();
481
+ const tagsSection = ref();
482
+ const shortcutIndex = ref(-1);
483
+ const removeSelected = ref(false as boolean);
484
+ const hoverTag = ref();
485
+ const showInfo = ref(false as boolean);
486
+ const showChangeType = ref(null as any);
487
+ const regexSearch =
488
+ /^[\s\.\+\/\!]|[\+\/\!\s][\s\.]+|[\s\.]+[\+\/\!\s]$|\s+[\+\/]|\s+$/g;
489
+ // regex to check whitespaces / symbols in input value to avoid duplicate search
490
+ // each alternative is seperated by |
491
+ // 1st alternative: the word begins with a space or a symbol
492
+ // 2nd alternative: the word begins with a combination of one or more spaces and symbols
493
+ // 3rd alternative: the word ends with one or more spaces or symbols
494
+ // 4th alternative: the word contains one or more spaces followed by symbols
495
+ // 5th alternative: the word ends with one or more spaces
496
+
497
+ const suggOpen = ref(false);
498
+ const optionIndex = ref(0);
499
+
500
+ const iconColor = computed(() => {
501
+ if (typing.value === true) {
502
+ return "var(--primary)";
503
+ } else return "var(--medium)";
504
+ });
505
+
506
+ const hasShortcut = computed(() => {
507
+ if (selectedShortcut.value.length > 0 || hoverShortcut.value.length > 0) {
508
+ return true;
509
+ } else return false;
510
+ });
511
+
512
+ const infoHeight = computed(() => {
513
+ if (showInfo.value === true) {
514
+ const infoSection = document.getElementById("info");
515
+ const shortcutSection = document.getElementById("shortcuts");
516
+ if (infoSection !== null && shortcutSection !== null) {
517
+ if (infoSection.offsetHeight > shortcutSection.offsetHeight) {
518
+ return true;
519
+ } else return false;
520
+ } else return false;
521
+ } else return false;
522
+ });
523
+
524
+ const notFound = {
525
+ fr: "Aucune suggestion n'a été trouvée",
526
+ en: "No suggestions were found",
527
+ es: "No se encontraron sugerencias",
528
+ };
529
+ const loaderText = {
530
+ fr: "Plus de suggestions en chargement",
531
+ en: "Loading more suggestions",
532
+ es: "cargando más sugerencias",
533
+ };
534
+
535
+ watch(value, (new_value: string) => {
536
+ typing.value = false;
537
+ if (new_value === "") {
538
+ hoverShortcut.value = "";
539
+ suggOpen.value = false;
540
+ optionIndex.value = 0;
541
+ document.removeEventListener("click", closeSugg);
542
+ } else {
543
+ suggOpen.value = true;
544
+ setTimeout(() => {
545
+ document.addEventListener("click", closeSugg);
546
+ }, 100);
547
+ for (let i = 0; i < shortcuts.value.length; i++) {
548
+ if (new_value.startsWith(shortcuts.value[i].symbol)) {
549
+ selectedShortcut.value = shortcuts.value[i].symbol;
550
+ }
551
+ }
552
+ }
553
+ });
554
+
555
+ watch(
556
+ () => typing.value,
557
+ () => {
558
+ if (!typing.value) {
559
+ document.removeEventListener("click", close);
560
+ } else
561
+ setTimeout(() => {
562
+ document.addEventListener("click", close);
563
+ }, 100);
564
+ }
565
+ );
566
+
567
+ // watch(selectedShortcut, (new_shortcut: string) => {
568
+ // value.value = `${new_shortcut}`;
569
+ // });
570
+
571
+ watch(closeIndex, (new_index: number) => {
572
+ if (new_index > searchedWords.value.length) {
573
+ removeSelected.value = true;
574
+ } else removeSelected.value = false;
575
+ });
576
+
577
+ const wordsLength = computed(() => {
578
+ return searchedWords.value.length;
579
+ });
580
+
581
+ watch(wordsLength, (newSearched: []) => {
582
+ setTimeout(() => {
583
+ if (tagsSection.value.scrollWidth > 4) {
584
+ tagsSection.value.scrollLeft += tagsSection.value.scrollWidth;
585
+ }
586
+ }, 100);
587
+ });
588
+
589
+ onMounted(() => {
590
+ searchBar.value = document.getElementById("search_input");
591
+ tagsSection.value = document.getElementById("tags");
592
+ if (props.customShortcuts.length > 0) {
593
+ for (let i = 0; i < props.customShortcuts.length; i++) {
594
+ shortcuts.value.push(props.customShortcuts[i]);
595
+ }
596
+ }
597
+ if (props.defaultInputs.length > 0) {
598
+ for (let i = 0; i < props.defaultInputs.length; i++) {
599
+ addToSearch(
600
+ props.defaultInputs[i].type,
601
+ props.defaultInputs[i].value,
602
+ true
603
+ );
604
+ }
605
+ searchedWords.value = props.defaultInputs;
606
+ }
607
+ });
608
+ function highlightSearchInput(suggestion: string): string {
609
+ // Échappe les caractères spéciaux pour une utilisation dans une expression régulière
610
+ const escapedInput = value.value.replace(
611
+ /[-\/\\^$*+?.()|[\]{}]/g,
612
+ "\\$&"
613
+ );
614
+ // Crée une nouvelle RegExp avec la valeur de recherche, en ignorant la casse
615
+ const regex = new RegExp(escapedInput);
616
+ // Remplace la partie correspondante par un span avec du style
617
+ return suggestion.replace(
618
+ regex,
619
+ (match) =>
620
+ `<span style="color: var(--primary); font-weight: 700">${match}</span>`
621
+ );
622
+ }
623
+
624
+ function shortcutColor(shortcut: string) {
625
+ if (
626
+ shortcut === selectedShortcut.value ||
627
+ shortcut === hoverShortcut.value
628
+ ) {
629
+ return "var(--darkest)";
630
+ } else return "var(--white)";
631
+ }
632
+
633
+ function handleChangeType(searchIndex: number) {
634
+ showChangeType.value = searchIndex;
635
+ setTimeout(() => {
636
+ document.addEventListener("click", close);
637
+ }, 200);
638
+ }
639
+
640
+ function changeSearchType(searchIndex: number, option: string) {
641
+ searchedWords.value[searchIndex].type = option;
642
+ emit("update:Value", searchedWords.value);
643
+ showChangeType.value = null;
644
+ }
645
+
646
+ function shortcutSelect(dir: string) {
647
+ typing.value = true;
648
+ let index;
649
+ if (selectedShortcut.value.length > 0) {
650
+ index = shortcuts.value.findIndex(
651
+ (x) => x.symbol === selectedShortcut.value
652
+ );
653
+ } else {
654
+ index = shortcutIndex.value;
655
+ }
656
+ if (dir === "desc") {
657
+ if (index < shortcuts.value.length - 1) {
658
+ selectedShortcut.value = shortcuts.value[index + 1].symbol;
659
+ shortcutIndex.value += 1;
660
+ } else {
661
+ selectedShortcut.value = shortcuts.value[0].symbol;
662
+ shortcutIndex.value = 0;
663
+ }
664
+ } else {
665
+ if (index > 0) {
666
+ selectedShortcut.value = shortcuts.value[index - 1].symbol;
667
+ shortcutIndex.value -= 1;
668
+ } else {
669
+ selectedShortcut.value =
670
+ shortcuts.value[shortcuts.value.length - 1].symbol;
671
+ shortcutIndex.value = shortcuts.value.length - 1;
672
+ }
673
+ }
674
+ // searchBar.value.focus();
675
+ }
676
+
677
+ function addToSearch(
678
+ search: string,
679
+ textValue: string,
680
+ isDefault: boolean
681
+ ) {
682
+ searchedWords.value.push({ type: search, value: textValue });
683
+ closeIndex.value = searchedWords.value.length;
684
+ selectedTag.value = null;
685
+ if (!isDefault) {
686
+ emit("update:Value", searchedWords.value);
687
+ }
688
+ searchBar.value.focus();
689
+ resetSearch();
690
+ }
691
+ const options = computed(() => ({
692
+ fuseOptions: {
693
+ includeScore: true,
694
+ threshold: 0.5,
695
+ },
696
+ }));
697
+ const filterSuggestions = computed(() => {
698
+ if (value.value.replace(/[+/!]/g, "").length > 0) {
699
+ return props.suggestions
700
+ .filter((f) =>
701
+ useFuse(
702
+ value.value.replace(/[+/!]/g, ""),
703
+ props.suggestions.map((a) => a.text),
704
+ options.value
705
+ )
706
+ .results.value.map((a) => a.item)
707
+ .includes(f.text)
708
+ )
709
+ .slice(0, 50);
710
+ } else return props.suggestions.slice(0, 50);
711
+ });
712
+
713
+ function selectTag(index: number, direction: number) {
714
+ let nextTag;
715
+ if (direction === 0) {
716
+ nextTag = document.getElementById(`tag${index + 1}`);
717
+ } else {
718
+ nextTag = document.getElementById(`tag${index + 1}`);
719
+ }
720
+ if (direction === 1 && index === 0 && tagsSection.value.scrollLeft > 0) {
721
+ tagsSection.value.scrollLeft = 0;
722
+ }
723
+ if (direction === 0 && index === searchedWords.value.length - 1) {
724
+ tagsSection.value.scrollLeft += tagsSection.value.scrollWidth;
725
+ }
726
+
727
+ if (
728
+ searchedWords.value.length > 0 &&
729
+ index < searchedWords.value.length &&
730
+ index >= 0
731
+ ) {
732
+ selectedTag.value = index;
733
+ closeIndex.value = index;
734
+ if (direction > 0 && index > 2 && nextTag !== null) {
735
+ tagsSection.value.scrollLeft += nextTag.offsetWidth;
736
+ } else if (
737
+ direction === 0 &&
738
+ index < searchedWords.value.length - 2 &&
739
+ nextTag !== null
740
+ ) {
741
+ tagsSection.value.scrollLeft -= nextTag.offsetWidth;
742
+ }
743
+ }
744
+ if (index < 0) {
745
+ selectedTag.value = null;
746
+ closeIndex.value = searchedWords.value.length + 1;
747
+ }
748
+ if (index >= searchedWords.value.length) {
749
+ selectedTag.value = null;
750
+ closeIndex.value = index;
751
+ }
752
+ if (index > searchedWords.value.length + 1) {
753
+ selectedTag.value = null;
754
+ closeIndex.value = -1;
755
+ }
756
+ }
757
+
758
+ function resetSearch() {
759
+ value.value = "";
760
+ }
761
+
762
+ function getKeyword(keyword: string) {
763
+ let returnKeyword = "";
764
+ for (let i = 0; i < shortcuts.value.length; i++) {
765
+ if (keyword === shortcuts.value[i].symbol) {
766
+ returnKeyword = shortcuts.value[i].keyword;
767
+ }
768
+ }
769
+ return returnKeyword;
770
+ }
771
+
772
+ function addTag() {
773
+ suggOpen.value = false;
774
+ if (removeSelected.value === true) {
775
+ emptySearch();
776
+ } else if (
777
+ selectedTag.value !== null &&
778
+ selectedTag.value < searchedWords.value.length &&
779
+ selectedTag.value >= 0
780
+ ) {
781
+ removeTag(selectedTag.value);
782
+ } else {
783
+ if (optionIndex.value > 0) {
784
+ value.value = filterSuggestions.value[optionIndex.value - 1].text;
785
+ if (
786
+ searchedWords.value.filter(
787
+ (f: any) =>
788
+ f.value === filterSuggestions.value[optionIndex.value - 1].text
789
+ ).length > 0
790
+ ) {
791
+ setTimeout(() => {
792
+ suggOpen.value = false;
793
+ value.value = "";
794
+ });
795
+ }
796
+ // closeOnEnter.value = true;
797
+ }
798
+ let search: any;
799
+ let searchValue = "";
800
+ let arrayValues: any[];
801
+ arrayValues = [];
802
+ if (value.value.length > 0) {
803
+ searchValue = value.value.replace(regexSearch, "");
804
+ for (let i = 0; i < shortcuts.value.length; i++) {
805
+ if (value.value.startsWith(shortcuts.value[i].symbol)) {
806
+ search = shortcuts.value[i].keyword;
807
+ searchValue = value.value.replace(shortcuts.value[i].symbol, "");
808
+ }
809
+ }
810
+ if (selectedShortcut.value.length > 0 && search === undefined) {
811
+ search = getKeyword(selectedShortcut.value);
812
+ } else if (hoverShortcut.value.length > 0 && search === undefined) {
813
+ search = getKeyword(hoverShortcut.value);
814
+ } else if (search === undefined) {
815
+ search = "and";
816
+ }
817
+ if (value.value.includes(",")) {
818
+ arrayValues = value.value.split(",");
819
+ }
820
+ }
821
+ if (searchValue.length > 0 && arrayValues.length === 0) {
822
+ if (
823
+ searchedWords.value.filter((f: any) => f.value === searchValue)
824
+ .length === 0
825
+ ) {
826
+ addToSearch(search, searchValue, false);
827
+ } else {
828
+ handleDuplicate(searchValue);
829
+ value.value = "";
830
+ }
831
+ } else if (arrayValues.length > 0) {
832
+ for (let i = 0; i < arrayValues.length; i++) {
833
+ const arraySearchValue = arrayValues[i].replace(regexSearch, "");
834
+ if (arraySearchValue.length > 0) {
835
+ if (
836
+ searchedWords.value.filter(
837
+ (f: any) => f.value === arraySearchValue
838
+ ).length === 0
839
+ ) {
840
+ addToSearch(search, arraySearchValue, false);
841
+ } else {
842
+ handleDuplicate(arraySearchValue);
843
+ value.value = "";
844
+ }
845
+ }
846
+ }
847
+ }
848
+ }
849
+ }
850
+
851
+ function handleDuplicate(tag: any) {
852
+ duplicate.value.push(tag);
853
+ setTimeout(() => (duplicate.value = []), 1000);
854
+ }
855
+
856
+ function emptySearch() {
857
+ searchedWords.value = [];
858
+ removeSelected.value = false;
859
+ closeIndex.value = null;
860
+ value.value = "";
861
+ searchBar.value.focus();
862
+ emit("update:Value", searchedWords.value);
863
+ }
864
+ function selectSugg(sugg: Suggestion) {
865
+ // closeOnEnter.value = true;
866
+ if (
867
+ searchedWords.value.find(
868
+ (a: { type: string; value: string }) => a.value === sugg.text
869
+ ) === undefined
870
+ ) {
871
+ value.value = sugg.text;
872
+ addTag();
873
+ } else {
874
+ suggOpen.value = false;
875
+ handleDuplicate(sugg.text);
876
+ }
877
+ }
878
+
879
+ function selectSearch(item: any) {
880
+ const searchValue = value.value.replace(regexSearch, "");
881
+ selectedShortcut.value = item.symbol;
882
+ if (searchValue.length > 0) {
883
+ if (
884
+ searchedWords.value.filter((f: any) => f.value === searchValue)
885
+ .length === 0
886
+ ) {
887
+ addToSearch(getKeyword(item.symbol), searchValue, false);
888
+ } else {
889
+ handleDuplicate(searchValue);
890
+ value.value = "";
891
+ }
892
+ }
893
+ searchBar.value.focus();
894
+ typing.value = false;
895
+ }
896
+
897
+ function removeTag(tagIndex: any) {
898
+ searchedWords.value.splice(tagIndex, 1);
899
+ emit("update:Value", searchedWords.value);
900
+ if (tagIndex > 0 && value.value.length === 0) {
901
+ selectedTag.value -= 1;
902
+ } else {
903
+ selectedTag.value = searchedWords.value.length;
904
+ }
905
+
906
+ closeIndex.value = selectedTag.value;
907
+ searchBar.value.focus();
908
+ }
909
+
910
+ function handleDelete() {
911
+ if (value.value.length === 0) {
912
+ removeTag(searchedWords.value.length - 1);
913
+ }
914
+ }
915
+
916
+ function changeDisplay() {
917
+ if (typing.value === true) {
918
+ close();
919
+ } else {
920
+ typing.value = true;
921
+ setTimeout(() => {
922
+ document.addEventListener("click", close);
923
+ }, 100);
924
+ }
925
+ searchBar.value.focus();
926
+ }
927
+ function selectOption(isDown: boolean) {
928
+ if (isDown) {
929
+ optionIndex.value =
930
+ optionIndex.value === filterSuggestions.value.length
931
+ ? filterSuggestions.value.length
932
+ : optionIndex.value + 1;
933
+ } else {
934
+ optionIndex.value =
935
+ optionIndex.value === 0
936
+ ? filterSuggestions.value.length
937
+ : optionIndex.value - 1;
938
+ }
939
+ const suggContainer = document.querySelector(".search_suggestions");
940
+ const selectedOption =
941
+ suggContainer?.querySelectorAll("p")[optionIndex.value - 1];
942
+
943
+ if (selectedOption) {
944
+ const containerHeight = suggContainer?.clientHeight;
945
+ const containerScrollTop = suggContainer?.scrollTop;
946
+ const optionTop = selectedOption?.offsetTop;
947
+ const optionHeight = selectedOption?.offsetHeight * 2;
948
+
949
+ if (optionTop < containerScrollTop) {
950
+ // Scroll up to the selected option
951
+ suggContainer.scrollTop = optionTop;
952
+ } else if (
953
+ optionTop + optionHeight >
954
+ containerHeight + containerScrollTop
955
+ ) {
956
+ // Scroll down to the selected option
957
+ suggContainer.scrollTop = optionTop + optionHeight - containerHeight;
958
+ }
959
+ }
960
+ }
961
+
962
+ function close() {
963
+ emit("onClose");
964
+ typing.value = false;
965
+ showChangeType.value = null;
966
+ }
967
+
968
+ function closeSugg() {
969
+ suggOpen.value = false;
970
+ optionIndex.value = 0;
971
+ document.removeEventListener("click", close);
972
+ }
973
+
974
+ return {
975
+ close,
976
+ changeDisplay,
977
+ handleDelete,
978
+ removeTag,
979
+ selectSearch,
980
+ emptySearch,
981
+ getKeyword,
982
+ handleDuplicate,
983
+ addTag,
984
+ resetSearch,
985
+ selectTag,
986
+ addToSearch,
987
+ shortcutColor,
988
+ shortcutSelect,
989
+ typing,
990
+ shortcuts,
991
+ hoverShortcut,
992
+ selectedShortcut,
993
+ searchedWords,
994
+ closeIndex,
995
+ value,
996
+ selectedTag,
997
+ duplicate,
998
+ searchBar,
999
+ tagsSection,
1000
+ shortcutIndex,
1001
+ removeSelected,
1002
+ hoverTag,
1003
+ showInfo,
1004
+ regexSearch,
1005
+ iconColor,
1006
+ hasShortcut,
1007
+ infoHeight,
1008
+ suggOpen,
1009
+ filterSuggestions,
1010
+ selectOption,
1011
+ optionIndex,
1012
+ selectSugg,
1013
+ showChangeType,
1014
+ changeSearchType,
1015
+ notFound,
1016
+ loaderText,
1017
+ handleChangeType,
1018
+ highlightSearchInput,
1019
+ };
1020
+ },
1021
+ });
1022
+ </script>"
1023
+ );
1024
+ const regex = new RegExp(escapedInput);
1025
+ return suggestion.replace(
1026
+ regex,
1027
+ (match) => `<span style="color: var(--primary); font-weight: 700">${match}</span>`
1028
+ );
1029
+ }
399
1030
  function shortcutColor(shortcut) {
400
1031
  if (shortcut === selectedShortcut.value || shortcut === hoverShortcut.value) {
401
1032
  return "var(--darkest)";
@@ -718,7 +1349,8 @@ export default defineComponent({
718
1349
  changeSearchType,
719
1350
  notFound,
720
1351
  loaderText,
721
- handleChangeType
1352
+ handleChangeType,
1353
+ highlightSearchInput
722
1354
  };
723
1355
  }
724
1356
  });
@@ -113,6 +113,10 @@ declare const _default: import("vue").DefineComponent<{
113
113
  type: BooleanConstructor;
114
114
  default: boolean;
115
115
  };
116
+ highlightInput: {
117
+ type: BooleanConstructor;
118
+ default: boolean;
119
+ };
116
120
  }, {
117
121
  close: () => void;
118
122
  changeDisplay: () => void;
@@ -169,6 +173,7 @@ declare const _default: import("vue").DefineComponent<{
169
173
  es: string;
170
174
  };
171
175
  handleChangeType: (searchIndex: number) => void;
176
+ highlightSearchInput: (suggestion: string) => string;
172
177
  }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:Value" | "onClose")[], "update:Value" | "onClose", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
173
178
  placeHolderValue: {
174
179
  type: StringConstructor;
@@ -280,6 +285,10 @@ declare const _default: import("vue").DefineComponent<{
280
285
  type: BooleanConstructor;
281
286
  default: boolean;
282
287
  };
288
+ highlightInput: {
289
+ type: BooleanConstructor;
290
+ default: boolean;
291
+ };
283
292
  }>> & {
284
293
  "onUpdate:Value"?: ((...args: any[]) => any) | undefined;
285
294
  onOnClose?: ((...args: any[]) => any) | undefined;
@@ -298,5 +307,6 @@ declare const _default: import("vue").DefineComponent<{
298
307
  suggestions: Suggestion[];
299
308
  fuzzyThreshold: number;
300
309
  loadingSuggs: boolean;
310
+ highlightInput: boolean;
301
311
  }, {}>;
302
312
  export default _default;
@@ -55,7 +55,8 @@
55
55
  size="14"
56
56
  color="var(--medium)"
57
57
  />
58
- {{ sugg.text }}
58
+ <span v-if="!highlightInput"> {{ sugg.text }} </span>
59
+ <span v-else v-html="highlightSearchInput(sugg.text)" />
59
60
  </p>
60
61
  </div>
61
62
  <div v-else-if="!loadingSuggs" class="sugg_container">
@@ -104,9 +105,13 @@ export default defineComponent({
104
105
  loadingSuggs: {
105
106
  type: Boolean,
106
107
  default: false
108
+ },
109
+ highlightInput: {
110
+ type: Boolean,
111
+ default: false
107
112
  }
108
113
  },
109
- emits: ["update:Value", "submitSearch"],
114
+ emits: ["update:Value", "submitSearch", "selectSuggestion"],
110
115
  setup(props, { emit }) {
111
116
  const searchValue = ref("");
112
117
  const suggOpen = ref(false);
@@ -170,6 +175,256 @@ export default defineComponent({
170
175
  }
171
176
  }
172
177
  );
178
+ function highlightSearchInput(suggestion) {
179
+ const escapedInput = searchValue.value.replace(
180
+ /[-\/\\^$*+?.()|[\]{}]/g,
181
+ "\\<script lang="ts">
182
+ import argIcon from "./argIcon.vue";
183
+ import { useFuse } from "@vueuse/integrations/useFuse";
184
+ import {
185
+ onMounted,
186
+ ref,
187
+ computed,
188
+ watch,
189
+ defineProps,
190
+ defineEmits,
191
+ defineComponent,
192
+ } from "vue";
193
+ interface Suggestion {
194
+ icon?: string;
195
+ text: string;
196
+ }
197
+ export default defineComponent({
198
+ name: "argSearchInput",
199
+ components: { argIcon },
200
+ props: {
201
+ placeHolderValue: {
202
+ type: String,
203
+ default: "Search here...",
204
+ },
205
+ defaultValue: {
206
+ type: String,
207
+ default: "",
208
+ },
209
+ suggestions: {
210
+ type: Array<Suggestion>,
211
+ default: null,
212
+ },
213
+ fuzzyThreshold: {
214
+ type: Number,
215
+ default: 0.5,
216
+ },
217
+ lang: {
218
+ type: String,
219
+ default: "en",
220
+ validator: (value: string) => {
221
+ return ["en", "fr", "es"].includes(value);
222
+ },
223
+ },
224
+ loadingSuggs: {
225
+ type: Boolean,
226
+ default: false,
227
+ },
228
+ highlightInput: {
229
+ type: Boolean,
230
+ default: false,
231
+ },
232
+ },
233
+ emits: ["update:Value", "submitSearch", "selectSuggestion"],
234
+
235
+ setup(props, { emit }) {
236
+ const searchValue = ref("");
237
+ const suggOpen = ref(false);
238
+
239
+ const notFound = {
240
+ fr: "Aucune suggestion n'a été trouvée",
241
+ en: "No suggestions were found",
242
+ es: "No se encontraron sugerencias",
243
+ };
244
+ const loaderText = {
245
+ fr: "Plus de suggestions en chargement",
246
+ en: "Loading more suggestions",
247
+ es: "cargando más sugerencias",
248
+ };
249
+
250
+ const closeOnEnter = ref(false);
251
+
252
+ const optionIndex = ref(0);
253
+
254
+ function selectOption(isDown: boolean) {
255
+ if (isDown) {
256
+ optionIndex.value =
257
+ optionIndex.value === filterSuggestions.value.length
258
+ ? filterSuggestions.value.length
259
+ : optionIndex.value + 1;
260
+ } else {
261
+ optionIndex.value =
262
+ optionIndex.value === 0
263
+ ? filterSuggestions.value.length
264
+ : optionIndex.value - 1;
265
+ }
266
+ const suggContainer = document.querySelector(".search_suggestions");
267
+ const selectedOption =
268
+ suggContainer?.querySelectorAll("p")[optionIndex.value - 1];
269
+
270
+ if (selectedOption) {
271
+ const containerHeight = suggContainer?.clientHeight;
272
+ const containerScrollTop = suggContainer?.scrollTop;
273
+ const optionTop = selectedOption?.offsetTop;
274
+ const optionHeight = selectedOption?.offsetHeight * 2;
275
+
276
+ if (optionTop < containerScrollTop) {
277
+ // Scroll up to the selected option
278
+ suggContainer.scrollTop = optionTop;
279
+ } else if (
280
+ optionTop + optionHeight >
281
+ containerHeight + containerScrollTop
282
+ ) {
283
+ // Scroll down to the selected option
284
+ suggContainer.scrollTop = optionTop + optionHeight - containerHeight;
285
+ }
286
+ }
287
+ }
288
+
289
+ watch(
290
+ () => searchValue.value,
291
+ () => {
292
+ if (searchValue.value.length > 0 && props.suggestions !== null) {
293
+ if (!closeOnEnter.value) {
294
+ suggOpen.value = true;
295
+ setTimeout(() => {
296
+ document.addEventListener("click", closeSugg);
297
+ });
298
+ } else {
299
+ suggOpen.value = false;
300
+ document.removeEventListener("click", closeSugg);
301
+ closeOnEnter.value = false;
302
+ }
303
+ } else {
304
+ suggOpen.value = false;
305
+ document.removeEventListener("click", closeSugg);
306
+ }
307
+ }
308
+ );
309
+
310
+ watch(
311
+ () => suggOpen.value,
312
+ () => {
313
+ if (!suggOpen.value) {
314
+ optionIndex.value = 0;
315
+ }
316
+ }
317
+ );
318
+
319
+ function highlightSearchInput(suggestion: string): string {
320
+ // Échappe les caractères spéciaux pour une utilisation dans une expression régulière
321
+ const escapedInput = searchValue.value.replace(
322
+ /[-\/\\^$*+?.()|[\]{}]/g,
323
+ "\\$&"
324
+ );
325
+ // Crée une nouvelle RegExp avec la valeur de recherche, en ignorant la casse
326
+ const regex = new RegExp(escapedInput);
327
+ // Remplace la partie correspondante par un span avec du style
328
+ return suggestion.replace(
329
+ regex,
330
+ (match) =>
331
+ `<span style="color: var(--primary); font-weight: 700">${match}</span>`
332
+ );
333
+ }
334
+
335
+ const options = computed(() => ({
336
+ fuseOptions: {
337
+ includeScore: true,
338
+ threshold: props.fuzzyThreshold,
339
+ },
340
+ }));
341
+
342
+ const filterSuggestions = computed(() => {
343
+ return props.suggestions
344
+ .filter((f) =>
345
+ useFuse(
346
+ searchValue,
347
+ props.suggestions.map((a) => a.text),
348
+ options.value
349
+ )
350
+ .results.value.map((a) => a.item)
351
+ .includes(f.text)
352
+ )
353
+ .slice(0, 50);
354
+ });
355
+
356
+ const selectSugg = (sugg: Suggestion) => {
357
+ closeOnEnter.value = true;
358
+ searchValue.value = sugg.text;
359
+ emit("update:Value", searchValue.value);
360
+ emit("selectSuggestion", searchValue.value);
361
+ };
362
+
363
+ function closeSugg() {
364
+ suggOpen.value = false;
365
+ }
366
+
367
+ function openSugg() {
368
+ if (searchValue.value.length > 0) {
369
+ suggOpen.value = true;
370
+ }
371
+ }
372
+
373
+ onMounted(() => {
374
+ searchValue.value = props.defaultValue;
375
+ });
376
+
377
+ watch(
378
+ () => props.defaultValue,
379
+ (new_value: string) => {
380
+ searchValue.value = new_value;
381
+ }
382
+ );
383
+
384
+ function changeInput(event: any) {
385
+ searchValue.value = event.target.value;
386
+ emit("update:Value", searchValue.value);
387
+ }
388
+ function submitSearch() {
389
+ if (optionIndex.value > 0) {
390
+ closeOnEnter.value = true;
391
+ searchValue.value = filterSuggestions.value[optionIndex.value - 1].text;
392
+ emit("update:Value", searchValue.value);
393
+ emit("selectSuggestion", searchValue.value);
394
+ }
395
+ emit("submitSearch");
396
+ }
397
+
398
+ function clearValue() {
399
+ searchValue.value = "";
400
+ emit("update:Value", searchValue.value);
401
+ }
402
+
403
+ return {
404
+ searchValue,
405
+ changeInput,
406
+ submitSearch,
407
+ clearValue,
408
+ suggOpen,
409
+ openSugg,
410
+ filterSuggestions,
411
+ selectSugg,
412
+ selectOption,
413
+ optionIndex,
414
+ notFound,
415
+ loaderText,
416
+ highlightSearchInput,
417
+ };
418
+ },
419
+ });
420
+ </script>"
421
+ );
422
+ const regex = new RegExp(escapedInput);
423
+ return suggestion.replace(
424
+ regex,
425
+ (match) => `<span style="color: var(--primary); font-weight: 700">${match}</span>`
426
+ );
427
+ }
173
428
  const options = computed(() => ({
174
429
  fuseOptions: {
175
430
  includeScore: true,
@@ -189,6 +444,7 @@ export default defineComponent({
189
444
  closeOnEnter.value = true;
190
445
  searchValue.value = sugg.text;
191
446
  emit("update:Value", searchValue.value);
447
+ emit("selectSuggestion", searchValue.value);
192
448
  };
193
449
  function closeSugg() {
194
450
  suggOpen.value = false;
@@ -216,6 +472,7 @@ export default defineComponent({
216
472
  closeOnEnter.value = true;
217
473
  searchValue.value = filterSuggestions.value[optionIndex.value - 1].text;
218
474
  emit("update:Value", searchValue.value);
475
+ emit("selectSuggestion", searchValue.value);
219
476
  }
220
477
  emit("submitSearch");
221
478
  }
@@ -235,7 +492,8 @@ export default defineComponent({
235
492
  selectOption,
236
493
  optionIndex,
237
494
  notFound,
238
- loaderText
495
+ loaderText,
496
+ highlightSearchInput
239
497
  };
240
498
  }
241
499
  });
@@ -41,6 +41,10 @@ declare const _default: import("vue").DefineComponent<{
41
41
  type: BooleanConstructor;
42
42
  default: boolean;
43
43
  };
44
+ highlightInput: {
45
+ type: BooleanConstructor;
46
+ default: boolean;
47
+ };
44
48
  }, {
45
49
  searchValue: import("vue").Ref<string>;
46
50
  changeInput: (event: any) => void;
@@ -62,7 +66,8 @@ declare const _default: import("vue").DefineComponent<{
62
66
  en: string;
63
67
  es: string;
64
68
  };
65
- }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:Value" | "submitSearch")[], "update:Value" | "submitSearch", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
69
+ highlightSearchInput: (suggestion: string) => string;
70
+ }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:Value" | "submitSearch" | "selectSuggestion")[], "update:Value" | "submitSearch" | "selectSuggestion", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
66
71
  placeHolderValue: {
67
72
  type: StringConstructor;
68
73
  default: string;
@@ -101,15 +106,21 @@ declare const _default: import("vue").DefineComponent<{
101
106
  type: BooleanConstructor;
102
107
  default: boolean;
103
108
  };
109
+ highlightInput: {
110
+ type: BooleanConstructor;
111
+ default: boolean;
112
+ };
104
113
  }>> & {
105
114
  "onUpdate:Value"?: ((...args: any[]) => any) | undefined;
106
115
  onSubmitSearch?: ((...args: any[]) => any) | undefined;
116
+ onSelectSuggestion?: ((...args: any[]) => any) | undefined;
107
117
  }, {
108
118
  lang: string;
109
119
  placeHolderValue: string;
110
120
  suggestions: Suggestion[];
111
121
  fuzzyThreshold: number;
112
122
  loadingSuggs: boolean;
123
+ highlightInput: boolean;
113
124
  defaultValue: string;
114
125
  }, {}>;
115
126
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/auriga",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Aurgia is a UI kit developped by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",