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 +4 -4
- data/app/assets/builds/katalyst/tables.esm.js +197 -24
- data/app/assets/builds/katalyst/tables.js +197 -24
- data/app/assets/builds/katalyst/tables.min.js +1 -1
- data/app/assets/builds/katalyst/tables.min.js.map +1 -1
- data/app/assets/stylesheets/katalyst/tables/_query.scss +37 -3
- data/app/components/katalyst/tables/query/input_component.html.erb +6 -1
- data/app/components/katalyst/tables/query/input_component.rb +5 -1
- data/app/components/katalyst/tables/query/modal_component.html.erb +2 -2
- data/app/components/katalyst/tables/query/modal_component.rb +7 -2
- data/app/components/katalyst/tables/query/suggestion_component.rb +21 -1
- data/app/components/katalyst/tables/query_component.rb +1 -0
- data/app/javascript/tables/query_controller.js +91 -11
- data/app/javascript/tables/query_input_controller.js +105 -12
- data/config/locales/tables.en.yml +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe9631a56d02759f79b16bc7f4e84e8f6514eaaafdbe867edcf3141fe5747807
|
4
|
+
data.tar.gz: 4bbf49bcc6afbd93f52252f7d4ec5e8060e7fe807778627d5b5feec72e75dcd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
//
|
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=
|
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
|
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
|
-
|
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) {
|