katalyst-tables 3.3.0 → 3.3.2
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/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
|
|