katalyst-tables 3.5.5 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5b39c8b9f9bd09482c418688e2bc279c52107b21015979c3ced2a6b175e544d
4
- data.tar.gz: 924bdf02fb5791051967b2ce39e59360c47398a518a53401747ee4e6f02ce728
3
+ metadata.gz: fe9631a56d02759f79b16bc7f4e84e8f6514eaaafdbe867edcf3141fe5747807
4
+ data.tar.gz: 4bbf49bcc6afbd93f52252f7d4ec5e8060e7fe807778627d5b5feec72e75dcd3
5
5
  SHA512:
6
- metadata.gz: 2d809af4b9c2d75d14af780f2223450b852260e96dab3b2ab362d7d5ce4a15a67a1aafd38613da25262e6f7b63903fc1890f6e53bbb19034a17fbb0a72b33623
7
- data.tar.gz: d7edbcc6ea97a412ec13a24084fe2d324d8643d7a4b7f8515d1b7d6e7656a12dd4efb7c0e96082eaa97491672fd3e5ada979eb9fde11da52ba6d31fdb574eb2e
6
+ metadata.gz: 250ec84f3123b2c36a98deb34fb7ee66adabf7cbbe38effb9516dee0d5f4249a338faf08b9af9f5fe6d6f8e5c0297e184ec39a18d8559472bcdc198c38b4253c
7
+ data.tar.gz: 0664eabd232457bb9e0145566680c78d6f33e5db8970c63ff34f64b2ead49874399bd11cd1d20ebb7156f9968b80efd7a3ab7cf5c750c9d7a6ba139e1adf678e
@@ -725,6 +725,7 @@ class QueryController extends Controller {
725
725
 
726
726
  closeModal() {
727
727
  delete this.modalTarget.dataset.open;
728
+ this.query.setAttribute("aria-expanded", false);
728
729
 
729
730
  if (document.activeElement === this.query) document.activeElement.blur();
730
731
 
@@ -733,6 +734,7 @@ class QueryController extends Controller {
733
734
 
734
735
  openModal() {
735
736
  this.modalTarget.dataset.open = true;
737
+ this.query.setAttribute("aria-expanded", true);
736
738
 
737
739
  document.addEventListener("selectionchange", this.selection);
738
740
  }
@@ -761,25 +763,26 @@ class QueryController extends Controller {
761
763
  delete this.pending;
762
764
  }
763
765
 
764
- // prevent an unnecessary `?q=` parameter from appearing in the URL
766
+ // add/remove current cursor position
767
+ if (hasFocus && this.query.value !== "") {
768
+ this.position.value = position;
769
+ this.position.disabled = false;
770
+ } else {
771
+ this.position.value = "";
772
+ this.position.disabled = true;
773
+ }
774
+
775
+ // prevent an unnecessary `?q=&p=0` parameter from appearing in the URL
765
776
  if (this.query.value === "") {
766
777
  this.query.disabled = true;
767
778
 
768
779
  // restore input and focus after form submission
769
780
  setTimeout(() => {
770
781
  this.query.disabled = false;
782
+ this.position.disabled = false;
771
783
  if (hasFocus) this.query.focus();
772
784
  }, 0);
773
785
  }
774
-
775
- // add/remove current cursor position
776
- if (hasFocus && position) {
777
- this.position.value = position;
778
- this.position.disabled = false;
779
- } else {
780
- this.position.value = "";
781
- this.position.disabled = true;
782
- }
783
786
  }
784
787
 
785
788
  update = () => {
@@ -801,8 +804,85 @@ class QueryController extends Controller {
801
804
  }
802
805
  }
803
806
 
807
+ moveToPreviousSuggestion() {
808
+ const prev = this.previousSuggestion || this.lastSuggestion;
809
+
810
+ if (prev) this.makeSuggestionActive(prev);
811
+ }
812
+
813
+ moveToNextSuggestion() {
814
+ const next = this.nextSuggestion || this.firstSuggestion;
815
+
816
+ if (next) this.makeSuggestionActive(next);
817
+ }
818
+
819
+ selectFirstSuggestion(e) {
820
+ // This is caused by pressing the tab key. We don't want to move focus.
821
+ // Ideally we don't want to always prevent the user from tabbing. We will address this later
822
+ e.preventDefault();
823
+
824
+ this.firstSuggestion?.dispatchEvent(new CustomEvent("query:select"));
825
+ }
826
+
827
+ selectActiveSuggestion() {
828
+ if (!this.activeSuggestion) {
829
+ this.closeModal();
830
+ return;
831
+ }
832
+
833
+ this.activeSuggestion.dispatchEvent(new CustomEvent("query:select"));
834
+ }
835
+
836
+ selectSuggestion(e) {
837
+ this.query.dispatchEvent(
838
+ new CustomEvent("replaceToken", {
839
+ detail: { token: e.params.value, position: this.query.selectionStart },
840
+ }),
841
+ );
842
+
843
+ this.clearActiveSuggestion();
844
+ }
845
+
846
+ makeSuggestionActive(node) {
847
+ if (this.activeSuggestion) {
848
+ this.activeSuggestion.setAttribute("aria-selected", "false");
849
+ }
850
+
851
+ this.query.setAttribute("aria-activedescendant", node.id);
852
+ node.setAttribute("aria-selected", "true");
853
+ }
854
+
855
+ clearActiveSuggestion() {
856
+ if (this.activeSuggestion) {
857
+ this.activeSuggestion.setAttribute("aria-selected", "false");
858
+ this.query.removeAttribute("aria-activedescendant");
859
+ }
860
+ }
861
+
862
+ get activeSuggestion() {
863
+ return this.modalTarget.querySelector(
864
+ `#${this.query.getAttribute("aria-activedescendant")}`,
865
+ );
866
+ }
867
+
868
+ get previousSuggestion() {
869
+ return this.activeSuggestion?.previousElementSibling;
870
+ }
871
+
872
+ get nextSuggestion() {
873
+ return this.activeSuggestion?.nextElementSibling;
874
+ }
875
+
876
+ get firstSuggestion() {
877
+ return this.modalTarget.querySelector("#suggestions li:first-of-type");
878
+ }
879
+
880
+ get lastSuggestion() {
881
+ return this.modalTarget.querySelector("#suggestions li:last-of-type");
882
+ }
883
+
804
884
  get query() {
805
- return this.element.querySelector("[role=searchbox]");
885
+ return this.element.querySelector("[role=combobox]");
806
886
  }
807
887
 
808
888
  get position() {
@@ -838,6 +918,49 @@ class QueryInputController extends Controller {
838
918
  this.highlightTarget.appendChild(token.render());
839
919
  });
840
920
  }
921
+
922
+ replaceToken(e) {
923
+ let tokenToAdd = e.detail.token.toString();
924
+
925
+ // wrap in quotes if it contains a spaces or special characters
926
+ if (/\s/.exec(tokenToAdd)) {
927
+ tokenToAdd = `"${tokenToAdd}"`;
928
+ }
929
+
930
+ const indexPosition = e.detail.position;
931
+ let caretPosition = indexPosition + tokenToAdd.length;
932
+ let sliceStart = indexPosition;
933
+ let sliceEnd = indexPosition;
934
+
935
+ // detect if position has a token already, if so, replace it
936
+ const existingToken = new Parser()
937
+ .parse(this.queryValue)
938
+ .tokenAtPosition(indexPosition);
939
+ if (existingToken) {
940
+ // We don't want to include the trailing space as we are replacing an existing value
941
+ tokenToAdd = tokenToAdd.trim();
942
+
943
+ // Slice up to the beginning of the tokens value (not the initial caret position)
944
+ sliceStart = existingToken.startOfValue();
945
+
946
+ // Slice after the end of the tokens value
947
+ sliceEnd = existingToken.endOfValue();
948
+
949
+ // The end position of the newly added token
950
+ caretPosition = sliceStart + tokenToAdd.length;
951
+ }
952
+
953
+ // Replace any text within sliceStart and sliceEnd with tokenToAdd
954
+ this.inputTarget.value =
955
+ this.queryValue.slice(0, sliceStart) +
956
+ tokenToAdd +
957
+ this.queryValue.slice(sliceEnd);
958
+
959
+ // Re focus the input at the end of the newly added token
960
+ this.update();
961
+ this.inputTarget.focus();
962
+ this.inputTarget.setSelectionRange(caretPosition, caretPosition);
963
+ }
841
964
  }
842
965
 
843
966
  class Parser {
@@ -871,13 +994,13 @@ class Parser {
871
994
  skipWhitespace(query) {
872
995
  if (!query.scan(/\s+/)) return;
873
996
 
874
- return new Token(query.matched());
997
+ return new Token(query.matched(), query.position);
875
998
  }
876
999
 
877
1000
  takeUntagged(query) {
878
1001
  if (!query.scan(/\S+/)) return;
879
1002
 
880
- return new Untagged(query.matched());
1003
+ return new Untagged(query.matched(), query.position);
881
1004
  }
882
1005
 
883
1006
  takeTagged(query) {
@@ -889,13 +1012,13 @@ class Parser {
889
1012
  const value =
890
1013
  this.takeArrayValue(query) || this.takeSingleValue(query) || new Token();
891
1014
 
892
- return new Tagged(key, separator, value);
1015
+ return new Tagged(key, separator, value, query.position);
893
1016
  }
894
1017
 
895
1018
  takeArrayValue(query) {
896
1019
  if (!query.scan(/\[\s*/)) return;
897
1020
 
898
- const start = new Token(query.matched());
1021
+ const start = new Token(query.matched(), query.position);
899
1022
  const values = (this.values = []);
900
1023
 
901
1024
  while (!query.isEos()) {
@@ -904,17 +1027,17 @@ class Parser {
904
1027
  }
905
1028
 
906
1029
  query.scan(/\s*]/);
907
- const end = new Token(query.matched());
1030
+ const end = new Token(query.matched(), query.position);
908
1031
 
909
1032
  this.values = null;
910
1033
 
911
- return new Array$1(start, values, end);
1034
+ return new ArrayToken(start, values, end);
912
1035
  }
913
1036
 
914
1037
  takeDelimiter(query) {
915
1038
  if (!query.scan(/\s*,\s*/)) return;
916
1039
 
917
- return new Token(query.matched());
1040
+ return new Token(query.matched(), query.position);
918
1041
  }
919
1042
 
920
1043
  takeSingleValue(query) {
@@ -924,24 +1047,49 @@ class Parser {
924
1047
  takeQuotedValue(query) {
925
1048
  if (!query.scan(/"([^"]*)"/)) return;
926
1049
 
927
- return new Value(query.matched());
1050
+ return new Value(query.matched(), query.position);
928
1051
  }
929
1052
 
930
1053
  takeUnquotedValue(query) {
931
1054
  if (!query.scan(/[^ \],]*/)) return;
932
1055
 
933
- return new Value(query.matched());
1056
+ return new Value(query.matched(), query.position);
1057
+ }
1058
+
1059
+ tokenAtPosition(position) {
1060
+ return this.tokens
1061
+ .filter((t) => t instanceof Tagged || t instanceof Untagged)
1062
+ .find((t) => t.range.includes(position));
934
1063
  }
935
1064
  }
936
1065
 
937
1066
  class Token {
938
- constructor(value = "") {
1067
+ constructor(value = "", position) {
939
1068
  this.value = value;
1069
+ this.length = this.value.length;
1070
+ this.start = position - this.length;
1071
+ this.end = this.start + this.length;
1072
+ this.range = this.arrayRange(this.start, this.end);
940
1073
  }
941
1074
 
942
1075
  render() {
943
1076
  return document.createTextNode(this.value);
944
1077
  }
1078
+
1079
+ arrayRange(start, stop) {
1080
+ return Array.from(
1081
+ { length: stop - start + 1 },
1082
+ (value, index) => start + index,
1083
+ );
1084
+ }
1085
+
1086
+ startOfValue() {
1087
+ return this.start;
1088
+ }
1089
+
1090
+ endOfValue() {
1091
+ return this.end;
1092
+ }
945
1093
  }
946
1094
 
947
1095
  class Value extends Token {
@@ -955,12 +1103,16 @@ class Value extends Token {
955
1103
  }
956
1104
 
957
1105
  class Tagged extends Token {
958
- constructor(key, separator, value) {
1106
+ constructor(key, separator, value, position) {
959
1107
  super();
960
1108
 
961
1109
  this.key = key;
962
1110
  this.separator = separator;
963
1111
  this.value = value;
1112
+ this.length = key.length + separator.length + value.value.length;
1113
+ this.start = position - this.length;
1114
+ this.end = this.start + this.length;
1115
+ this.range = this.arrayRange(this.start, this.end);
964
1116
  }
965
1117
 
966
1118
  render() {
@@ -977,6 +1129,14 @@ class Tagged extends Token {
977
1129
 
978
1130
  return span;
979
1131
  }
1132
+
1133
+ startOfValue() {
1134
+ return this.value.startOfValue();
1135
+ }
1136
+
1137
+ endOfValue() {
1138
+ return this.value.endOfValue();
1139
+ }
980
1140
  }
981
1141
 
982
1142
  class Untagged extends Token {
@@ -988,13 +1148,18 @@ class Untagged extends Token {
988
1148
  }
989
1149
  }
990
1150
 
991
- let Array$1 = class Array extends Token {
1151
+ class ArrayToken extends Token {
992
1152
  constructor(start, values, end) {
993
1153
  super();
994
1154
 
995
1155
  this.start = start;
996
1156
  this.values = values;
997
1157
  this.end = end;
1158
+ this.range = this.arrayRange(start.start, end.range[end.length]);
1159
+ this.length =
1160
+ start.length +
1161
+ values.reduce((length, value) => length + value.length, 0) +
1162
+ end.length;
998
1163
  }
999
1164
 
1000
1165
  render() {
@@ -1012,7 +1177,15 @@ let Array$1 = class Array extends Token {
1012
1177
 
1013
1178
  return array;
1014
1179
  }
1015
- };
1180
+
1181
+ startOfValue() {
1182
+ return this.start.start;
1183
+ }
1184
+
1185
+ endOfValue() {
1186
+ return this.end.end;
1187
+ }
1188
+ }
1016
1189
 
1017
1190
  class StringScanner {
1018
1191
  constructor(input) {