katalyst-tables 3.3.0 → 3.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/builds/katalyst/tables.esm.js +332 -6
- data/app/assets/builds/katalyst/tables.js +332 -6
- 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/_index.scss +1 -1
- data/app/assets/stylesheets/katalyst/tables/_query.scss +109 -0
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_date.scss +1 -1
- data/app/assets/stylesheets/katalyst/tables/typed-columns/_datetime.scss +1 -1
- data/app/components/katalyst/table_component.rb +13 -2
- data/app/components/katalyst/tables/cells/currency_component.rb +21 -2
- data/app/components/katalyst/tables/cells/number_component.rb +31 -1
- data/app/components/katalyst/tables/query/input_component.html.erb +12 -0
- data/app/components/katalyst/tables/query/input_component.rb +46 -0
- data/app/components/katalyst/tables/query/modal_component.html.erb +33 -0
- data/app/components/katalyst/tables/query/modal_component.rb +89 -0
- data/app/components/katalyst/tables/query_component.html.erb +8 -0
- data/app/components/katalyst/tables/{filter_component.rb → query_component.rb} +37 -30
- data/app/controllers/concerns/katalyst/tables/backend.rb +14 -0
- data/app/helpers/katalyst/tables/frontend.rb +14 -8
- data/app/javascript/tables/application.js +8 -3
- data/app/javascript/tables/query_controller.js +108 -0
- data/app/javascript/tables/query_input_controller.js +228 -0
- data/app/models/concerns/katalyst/tables/collection/core.rb +5 -3
- data/app/models/concerns/katalyst/tables/collection/query/array_value_parser.rb +13 -26
- data/app/models/concerns/katalyst/tables/collection/query/parser.rb +16 -13
- data/app/models/concerns/katalyst/tables/collection/query/single_value_parser.rb +11 -3
- data/app/models/concerns/katalyst/tables/collection/query/value_parser.rb +10 -5
- data/app/models/concerns/katalyst/tables/collection/query.rb +44 -6
- data/app/models/concerns/katalyst/tables/collection/sorting.rb +11 -1
- data/app/models/katalyst/tables/collection/base.rb +10 -0
- data/app/models/katalyst/tables/collection/filter.rb +10 -0
- data/app/models/katalyst/tables/collection/type/boolean.rb +11 -1
- data/app/models/katalyst/tables/collection/type/date.rb +19 -22
- data/app/models/katalyst/tables/collection/type/enum.rb +11 -0
- data/app/models/katalyst/tables/collection/type/float.rb +3 -39
- data/app/models/katalyst/tables/collection/type/helpers/delegate.rb +2 -22
- data/app/models/katalyst/tables/collection/type/helpers/extensions.rb +14 -0
- data/app/models/katalyst/tables/collection/type/helpers/multiple.rb +30 -0
- data/app/models/katalyst/tables/collection/type/helpers/range.rb +59 -0
- data/app/models/katalyst/tables/collection/type/integer.rb +3 -39
- data/app/models/katalyst/tables/collection/type/value.rb +22 -2
- metadata +12 -8
- data/app/assets/stylesheets/katalyst/tables/_filter.scss +0 -43
- data/app/components/katalyst/tables/filter/modal_component.html.erb +0 -25
- data/app/components/katalyst/tables/filter/modal_component.rb +0 -112
- data/app/components/katalyst/tables/filter_component.html.erb +0 -18
- data/app/javascript/tables/filter/modal_controller.js +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15f5912b36b82347b774b75d9339a7f41cff2c6d9973c51e8f5142fd829b7814
|
4
|
+
data.tar.gz: da4fadb4364f0c2bf2d19a75a992ef7d00d912ca5c27d2d851fb96c32eaa7fbc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74661bd722d18e4fdac80a0979a2516b500b6ad4402d03a396ce53b3b6cee2edb5c2ad9aa70086969033189a3a4622cb4ddd907269670228fc1e2838d5973189
|
7
|
+
data.tar.gz: 0262fadd6c9840db3a91575bb0ebfed71891abb00ecfcc6a73e0a87a8c666f7da85a5e60f26dc424f2e9388097812546b5900245d3f63377c524489b19272c5a
|
data/README.md
CHANGED
@@ -227,7 +227,7 @@ query expressions such as `first_name:Aaron` or `created_at:>2024-01-01` and the
|
|
227
227
|
will be automatically parsed and applied to the collection attribute, and the collection
|
228
228
|
will automatically generate and apply ActiveRecord conditions to filter the given scope.
|
229
229
|
|
230
|
-
There's also a frontend utility, `
|
230
|
+
There's also a frontend utility, `table_query_with(collection:)` that will generate the form
|
231
231
|
and show a modal that helps users to interact with the query interface. More details available
|
232
232
|
in the [query](docs/query.md) documentation.
|
233
233
|
|
@@ -533,15 +533,337 @@ class SelectionItemController extends Controller {
|
|
533
533
|
}
|
534
534
|
}
|
535
535
|
|
536
|
-
class
|
536
|
+
class QueryController extends Controller {
|
537
537
|
static targets = ["modal"];
|
538
538
|
|
539
|
-
|
539
|
+
disconnect() {
|
540
|
+
delete this.pending;
|
541
|
+
|
542
|
+
document.removeEventListener("selectionchange", this.selection);
|
543
|
+
}
|
544
|
+
|
545
|
+
focus() {
|
546
|
+
if (document.activeElement === this.query) return;
|
547
|
+
|
548
|
+
this.query.addEventListener(
|
549
|
+
"focusin",
|
550
|
+
(e) => {
|
551
|
+
e.target.setSelectionRange(-1, -1);
|
552
|
+
},
|
553
|
+
{ once: true },
|
554
|
+
);
|
555
|
+
|
556
|
+
this.query.focus();
|
557
|
+
}
|
558
|
+
|
559
|
+
closeModal() {
|
540
560
|
delete this.modalTarget.dataset.open;
|
561
|
+
|
562
|
+
if (document.activeElement === this.query) document.activeElement.blur();
|
563
|
+
|
564
|
+
document.removeEventListener("selectionchange", this.selection);
|
565
|
+
}
|
566
|
+
|
567
|
+
openModal() {
|
568
|
+
this.modalTarget.dataset.open = true;
|
569
|
+
|
570
|
+
document.addEventListener("selectionchange", this.selection);
|
571
|
+
}
|
572
|
+
|
573
|
+
clear() {
|
574
|
+
if (this.query.value === "") {
|
575
|
+
// if the user presses escape once, browser clears the input
|
576
|
+
// if the user presses escape again, get them out of here
|
577
|
+
this.closeModal();
|
578
|
+
}
|
579
|
+
}
|
580
|
+
|
581
|
+
submit() {
|
582
|
+
const hasFocus = this.isFocused;
|
583
|
+
const position = hasFocus && this.query.selectionStart;
|
584
|
+
|
585
|
+
if (this.pending) {
|
586
|
+
clearTimeout(this.pending);
|
587
|
+
delete this.pending;
|
588
|
+
}
|
589
|
+
|
590
|
+
// prevent an unnecessary `?q=` parameter from appearing in the URL
|
591
|
+
if (this.query.value === "") {
|
592
|
+
this.query.disabled = true;
|
593
|
+
|
594
|
+
// restore input and focus after form submission
|
595
|
+
setTimeout(() => {
|
596
|
+
this.query.disabled = false;
|
597
|
+
if (hasFocus) this.query.focus();
|
598
|
+
}, 0);
|
599
|
+
}
|
600
|
+
|
601
|
+
// add/remove current cursor position
|
602
|
+
if (hasFocus && position) {
|
603
|
+
this.position.value = position;
|
604
|
+
this.position.disabled = false;
|
605
|
+
} else {
|
606
|
+
this.position.value = "";
|
607
|
+
this.position.disabled = true;
|
608
|
+
}
|
609
|
+
}
|
610
|
+
|
611
|
+
update = () => {
|
612
|
+
if (this.pending) clearTimeout(this.pending);
|
613
|
+
this.pending = setTimeout(() => {
|
614
|
+
this.element.requestSubmit();
|
615
|
+
}, 300);
|
616
|
+
};
|
617
|
+
|
618
|
+
selection = () => {
|
619
|
+
if (this.isFocused) this.update();
|
620
|
+
};
|
621
|
+
|
622
|
+
beforeMorphAttribute(e) {
|
623
|
+
switch (e.detail.attributeName) {
|
624
|
+
case "data-open":
|
625
|
+
e.preventDefault();
|
626
|
+
break;
|
627
|
+
}
|
541
628
|
}
|
542
629
|
|
543
|
-
|
544
|
-
this.
|
630
|
+
get query() {
|
631
|
+
return this.element.querySelector("input[type=search]");
|
632
|
+
}
|
633
|
+
|
634
|
+
get position() {
|
635
|
+
return this.element.querySelector("input[name=p]");
|
636
|
+
}
|
637
|
+
|
638
|
+
get isFocused() {
|
639
|
+
return this.query === document.activeElement;
|
640
|
+
}
|
641
|
+
}
|
642
|
+
|
643
|
+
class QueryInputController extends Controller {
|
644
|
+
static targets = ["input", "highlight"];
|
645
|
+
static values = { query: String };
|
646
|
+
|
647
|
+
connect() {
|
648
|
+
this.queryValue = this.inputTarget.value;
|
649
|
+
}
|
650
|
+
|
651
|
+
update() {
|
652
|
+
this.queryValue = this.inputTarget.value;
|
653
|
+
}
|
654
|
+
|
655
|
+
queryValueChanged(query) {
|
656
|
+
this.highlightTarget.innerHTML = "";
|
657
|
+
|
658
|
+
new Parser().parse(query).tokens.forEach((token) => {
|
659
|
+
this.highlightTarget.appendChild(token.render());
|
660
|
+
});
|
661
|
+
}
|
662
|
+
}
|
663
|
+
|
664
|
+
class Parser {
|
665
|
+
constructor() {
|
666
|
+
this.tokens = [];
|
667
|
+
this.values = null;
|
668
|
+
}
|
669
|
+
|
670
|
+
parse(input) {
|
671
|
+
const query = new StringScanner(input);
|
672
|
+
|
673
|
+
while (!query.isEos()) {
|
674
|
+
this.push(this.skipWhitespace(query));
|
675
|
+
|
676
|
+
const value = this.takeTagged(query) || this.takeUntagged(query);
|
677
|
+
|
678
|
+
if (!this.push(value)) break;
|
679
|
+
}
|
680
|
+
|
681
|
+
return this;
|
682
|
+
}
|
683
|
+
|
684
|
+
push(token) {
|
685
|
+
if (token) {
|
686
|
+
this.values ? this.values.push(token) : this.tokens.push(token);
|
687
|
+
}
|
688
|
+
|
689
|
+
return !!token;
|
690
|
+
}
|
691
|
+
|
692
|
+
skipWhitespace(query) {
|
693
|
+
if (!query.scan(/\s+/)) return;
|
694
|
+
|
695
|
+
return new Token(query.matched());
|
696
|
+
}
|
697
|
+
|
698
|
+
takeUntagged(query) {
|
699
|
+
if (!query.scan(/\S+/)) return;
|
700
|
+
|
701
|
+
return new Untagged(query.matched());
|
702
|
+
}
|
703
|
+
|
704
|
+
takeTagged(query) {
|
705
|
+
if (!query.scan(/(\w+(?:\.\w+)?)(:\s*)/)) return;
|
706
|
+
|
707
|
+
const key = query.valueAt(1);
|
708
|
+
const separator = query.valueAt(2);
|
709
|
+
|
710
|
+
const value =
|
711
|
+
this.takeArrayValue(query) || this.takeSingleValue(query) || new Token();
|
712
|
+
|
713
|
+
return new Tagged(key, separator, value);
|
714
|
+
}
|
715
|
+
|
716
|
+
takeArrayValue(query) {
|
717
|
+
if (!query.scan(/\[\s*/)) return;
|
718
|
+
|
719
|
+
const start = new Token(query.matched());
|
720
|
+
const values = (this.values = []);
|
721
|
+
|
722
|
+
while (!query.isEos()) {
|
723
|
+
if (!this.push(this.takeSingleValue(query))) break;
|
724
|
+
if (!this.push(this.takeDelimiter(query))) break;
|
725
|
+
}
|
726
|
+
|
727
|
+
query.scan(/\s*]/);
|
728
|
+
const end = new Token(query.matched());
|
729
|
+
|
730
|
+
this.values = null;
|
731
|
+
|
732
|
+
return new Array$1(start, values, end);
|
733
|
+
}
|
734
|
+
|
735
|
+
takeDelimiter(query) {
|
736
|
+
if (!query.scan(/\s*,\s*/)) return;
|
737
|
+
|
738
|
+
return new Token(query.matched());
|
739
|
+
}
|
740
|
+
|
741
|
+
takeSingleValue(query) {
|
742
|
+
return this.takeQuotedValue(query) || this.takeUnquotedValue(query);
|
743
|
+
}
|
744
|
+
|
745
|
+
takeQuotedValue(query) {
|
746
|
+
if (!query.scan(/"([^"]*)"/)) return;
|
747
|
+
|
748
|
+
return new Value(query.matched());
|
749
|
+
}
|
750
|
+
|
751
|
+
takeUnquotedValue(query) {
|
752
|
+
if (!query.scan(/[^ \],]*/)) return;
|
753
|
+
|
754
|
+
return new Value(query.matched());
|
755
|
+
}
|
756
|
+
}
|
757
|
+
|
758
|
+
class Token {
|
759
|
+
constructor(value = "") {
|
760
|
+
this.value = value;
|
761
|
+
}
|
762
|
+
|
763
|
+
render() {
|
764
|
+
return document.createTextNode(this.value);
|
765
|
+
}
|
766
|
+
}
|
767
|
+
|
768
|
+
class Value extends Token {
|
769
|
+
render() {
|
770
|
+
const span = document.createElement("span");
|
771
|
+
span.className = "value";
|
772
|
+
span.innerText = this.value;
|
773
|
+
|
774
|
+
return span;
|
775
|
+
}
|
776
|
+
}
|
777
|
+
|
778
|
+
class Tagged extends Token {
|
779
|
+
constructor(key, separator, value) {
|
780
|
+
super();
|
781
|
+
|
782
|
+
this.key = key;
|
783
|
+
this.separator = separator;
|
784
|
+
this.value = value;
|
785
|
+
}
|
786
|
+
|
787
|
+
render() {
|
788
|
+
const span = document.createElement("span");
|
789
|
+
span.className = "tag";
|
790
|
+
|
791
|
+
const key = document.createElement("span");
|
792
|
+
key.className = "key";
|
793
|
+
key.innerText = this.key;
|
794
|
+
|
795
|
+
span.appendChild(key);
|
796
|
+
span.appendChild(document.createTextNode(this.separator));
|
797
|
+
span.appendChild(this.value.render());
|
798
|
+
|
799
|
+
return span;
|
800
|
+
}
|
801
|
+
}
|
802
|
+
|
803
|
+
class Untagged extends Token {
|
804
|
+
render() {
|
805
|
+
const span = document.createElement("span");
|
806
|
+
span.className = "untagged";
|
807
|
+
span.innerText = this.value;
|
808
|
+
return span;
|
809
|
+
}
|
810
|
+
}
|
811
|
+
|
812
|
+
let Array$1 = class Array extends Token {
|
813
|
+
constructor(start, values, end) {
|
814
|
+
super();
|
815
|
+
|
816
|
+
this.start = start;
|
817
|
+
this.values = values;
|
818
|
+
this.end = end;
|
819
|
+
}
|
820
|
+
|
821
|
+
render() {
|
822
|
+
const array = document.createElement("span");
|
823
|
+
array.className = "array-values";
|
824
|
+
array.appendChild(this.start.render());
|
825
|
+
|
826
|
+
this.values.forEach((value) => {
|
827
|
+
const span = document.createElement("span");
|
828
|
+
span.appendChild(value.render());
|
829
|
+
array.appendChild(span);
|
830
|
+
});
|
831
|
+
|
832
|
+
array.appendChild(this.end.render());
|
833
|
+
|
834
|
+
return array;
|
835
|
+
}
|
836
|
+
};
|
837
|
+
|
838
|
+
class StringScanner {
|
839
|
+
constructor(input) {
|
840
|
+
this.input = input;
|
841
|
+
this.position = 0;
|
842
|
+
this.last = null;
|
843
|
+
}
|
844
|
+
|
845
|
+
isEos() {
|
846
|
+
return this.position >= this.input.length;
|
847
|
+
}
|
848
|
+
|
849
|
+
scan(regex) {
|
850
|
+
const match = regex.exec(this.input.substring(this.position));
|
851
|
+
if (match?.index === 0) {
|
852
|
+
this.last = match;
|
853
|
+
this.position += match[0].length;
|
854
|
+
return true;
|
855
|
+
} else {
|
856
|
+
this.last = {};
|
857
|
+
return false;
|
858
|
+
}
|
859
|
+
}
|
860
|
+
|
861
|
+
matched() {
|
862
|
+
return this.last && this.last[0];
|
863
|
+
}
|
864
|
+
|
865
|
+
valueAt(index) {
|
866
|
+
return this.last && this.last[index];
|
545
867
|
}
|
546
868
|
}
|
547
869
|
|
@@ -567,8 +889,12 @@ const Definitions = [
|
|
567
889
|
controllerConstructor: SelectionItemController,
|
568
890
|
},
|
569
891
|
{
|
570
|
-
identifier: "tables--
|
571
|
-
controllerConstructor:
|
892
|
+
identifier: "tables--query",
|
893
|
+
controllerConstructor: QueryController,
|
894
|
+
},
|
895
|
+
{
|
896
|
+
identifier: "tables--query-input",
|
897
|
+
controllerConstructor: QueryInputController,
|
572
898
|
},
|
573
899
|
];
|
574
900
|
|