katalyst-tables 3.5.5 → 3.6.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.
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) {