@lemonadejs/dropdown 5.0.0 → 5.8.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.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Implementar o page up and down
3
- * botao reset e done
4
- * traducoes
2
+ * Implement page up and down navigation
3
+ * Implement color attribute for items
5
4
  */
5
+
6
6
  if (!lemonade && typeof (require) === 'function') {
7
7
  var lemonade = require('lemonadejs');
8
8
  }
@@ -17,36 +17,80 @@ if (!Modal && typeof (require) === 'function') {
17
17
  global.Dropdown = factory();
18
18
  }(this, (function () {
19
19
 
20
+ class CustomEvents extends Event {
21
+ constructor(type, props, options) {
22
+ super(type, {
23
+ bubbles: true,
24
+ composed: true,
25
+ ...options,
26
+ });
27
+
28
+ if (props) {
29
+ for (const key in props) {
30
+ // Avoid assigning if property already exists anywhere on `this`
31
+ if (! (key in this)) {
32
+ this[key] = props[key];
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ // Dispatcher
40
+ const Dispatch = function(method, type, options) {
41
+ // Try calling the method directly if provided
42
+ if (typeof method === 'function') {
43
+ let a = Object.values(options);
44
+ return method(...a);
45
+ } else if (this.tagName) {
46
+ return this.dispatchEvent(new CustomEvents(type, options));
47
+ }
48
+ }
49
+
20
50
  // Default row height
21
51
  let defaultRowHeight = 24;
22
52
 
53
+ // Translations
54
+ const T = function(t) {
55
+ if (typeof(document) !== "undefined" && document.dictionary) {
56
+ return document.dictionary[t] || t;
57
+ } else {
58
+ return t;
59
+ }
60
+ }
61
+
62
+ const isEmpty = function(v) {
63
+ return v === '' || v === null || v === undefined || (Array.isArray(v) && v.length === 0);
64
+ }
65
+
23
66
  /**
24
- * Compare two arrays to see if contains exact the same elements
25
- * @param {number|number[]} a1
26
- * @param {number|number[]} a2
67
+ * Compare two values (arrays, strings, numbers, etc.)
68
+ * Returns true if both are equal or empty
69
+ * @param {*} a1
70
+ * @param {*} a2
27
71
  */
28
- const compareValues = function (a1, a2) {
72
+ const compareValues = function(a1, a2) {
73
+ if (a1 === a2 || (isEmpty(a1) && isEmpty(a2))) {
74
+ return true;
75
+ }
76
+
29
77
  if (!a1 || !a2) {
30
78
  return false;
31
79
  }
32
- if (!Array.isArray(a1) || !Array.isArray(a2)) {
33
- if (a1 === a2) {
34
- return true;
35
- } else {
36
- return false;
37
- }
38
- } else {
39
- let i = a1.length;
40
- if (i !== a2.length) {
80
+
81
+ if (Array.isArray(a1) && Array.isArray(a2)) {
82
+ if (a1.length !== a2.length) {
41
83
  return false;
42
84
  }
43
- while (i--) {
85
+ for (let i = 0; i < a1.length; i++) {
44
86
  if (a1[i] !== a2[i]) {
45
87
  return false;
46
88
  }
47
89
  }
90
+ return true;
48
91
  }
49
- return true;
92
+
93
+ return a1 === a2;
50
94
  }
51
95
 
52
96
  const lazyLoading = function (self) {
@@ -303,7 +347,11 @@ if (!Modal && typeof (require) === 'function') {
303
347
  return data;
304
348
  }
305
349
 
306
- const Dropdown = function (children) {
350
+ const isDOM = function(o) {
351
+ return (o instanceof Element || o instanceof HTMLDocument || o instanceof DocumentFragment);
352
+ }
353
+
354
+ const Dropdown = function (children, { onchange, onload }) {
307
355
  let self = this;
308
356
  // Data
309
357
  let data = [];
@@ -313,12 +361,12 @@ if (!Modal && typeof (require) === 'function') {
313
361
  let cursor = null;
314
362
  // Control events
315
363
  let ignoreEvents = false;
316
- // Default width
317
- if (! self.width) {
318
- self.width = 260;
319
- }
320
364
  // Lazy loading global instance
321
365
  let lazyloading = null;
366
+ // Tracking changes
367
+ let changesDetected = false;
368
+ // Debounce timer for search
369
+ let searchTimeout = null;
322
370
 
323
371
  // Data
324
372
  if (! Array.isArray(self.data)) {
@@ -332,16 +380,25 @@ if (!Modal && typeof (require) === 'function') {
332
380
  })
333
381
  }
334
382
 
383
+ // Decide the type based on the size of the screen
384
+ let autoType = self.type === 'auto';
385
+
335
386
  // Custom events defined by the user
336
- let onload = self.onload;
337
- let onchange = self.onchange;
387
+ let load = self.onload;
388
+ self.onload = null;
389
+ let change = self.onchange;
390
+ self.onchange = null;
391
+
392
+ // Compatibility
393
+ if (typeof self.newOptions !== 'undefined') {
394
+ self.insert = self.newOptions;
395
+ }
338
396
 
339
397
  // Cursor controllers
340
398
  const setCursor = function (index, force) {
341
399
  let item = self.rows[index];
342
-
343
400
  if (typeof (item) !== 'undefined') {
344
- // Set cursor number
401
+ // Set the cursor number
345
402
  cursor = index;
346
403
  // Set visual indication
347
404
  item.cursor = true;
@@ -407,9 +464,9 @@ if (!Modal && typeof (require) === 'function') {
407
464
 
408
465
  const adjustDimensions = function(data) {
409
466
  // Estimate width
410
- let width = self.width;
467
+ let width = self.width ?? 0;
411
468
  // Adjust the width
412
- let w = self.input.offsetWidth;
469
+ let w = getInput().offsetWidth;
413
470
  if (width < w) {
414
471
  width = w;
415
472
  }
@@ -425,13 +482,6 @@ if (!Modal && typeof (require) === 'function') {
425
482
  });
426
483
  // Min width for the container
427
484
  self.container.parentNode.style.width = (width - 2) + 'px';
428
- // Height
429
- self.height = 400;
430
-
431
- // Animation for mobile
432
- if (document.documentElement.clientWidth < 800) {
433
- self.animation = true;
434
- }
435
485
  }
436
486
 
437
487
  const setData = function () {
@@ -465,18 +515,18 @@ if (!Modal && typeof (require) === 'function') {
465
515
 
466
516
  const updateLabel = function () {
467
517
  if (value && value.length) {
468
- self.input.textContent = value.filter(v => v.selected).map(i => i.text).join('; ');
518
+ getInput().textContent = value.filter(v => v.selected).map(i => i.text).join('; ');
469
519
  } else {
470
- self.input.textContent = '';
520
+ getInput().textContent = '';
471
521
  }
472
522
  }
473
523
 
474
524
  const setValue = function (v, ignoreEvent) {
475
525
  // Values
476
526
  let newValue;
477
- if (!Array.isArray(v)) {
478
- if (typeof (v) === 'string') {
479
- newValue = v.split(';');
527
+ if (! Array.isArray(v)) {
528
+ if (typeof(v) === 'string') {
529
+ newValue = v.split(self.divisor ?? ';');
480
530
  } else {
481
531
  newValue = [v];
482
532
  }
@@ -489,22 +539,24 @@ if (!Modal && typeof (require) === 'function') {
489
539
 
490
540
  if (Array.isArray(data)) {
491
541
  data.map(function (s) {
492
- // Select values
493
- if (newValue.indexOf(s.value) !== -1) {
494
- s.selected = true;
542
+ s.selected = newValue.some(v => v == s.value);
543
+ if (s.selected) {
495
544
  value.push(s);
496
- } else {
497
- s.selected = false;
498
545
  }
499
546
  });
500
547
  }
501
548
 
502
549
  // Update label
503
- updateLabel();
550
+ if (self.isClosed()) {
551
+ updateLabel();
552
+ }
504
553
 
505
554
  // Component onchange
506
- if (! ignoreEvent && typeof(onchange) === 'function') {
507
- onchange.call(self, self, getValue());
555
+ if (! ignoreEvent) {
556
+ Dispatch.call(self, change, 'change', {
557
+ instance: self,
558
+ value: getValue(),
559
+ });
508
560
  }
509
561
  }
510
562
 
@@ -513,46 +565,13 @@ if (!Modal && typeof (require) === 'function') {
513
565
  if (value && value.length) {
514
566
  return value.filter(v => v.selected).map(i => i.value);
515
567
  }
516
- return [];
517
568
  } else {
518
569
  if (value && value.length) {
519
570
  return value[0].value;
520
- } else {
521
- return '';
522
571
  }
523
572
  }
524
- }
525
573
 
526
- const onclose = function () {
527
- // Cursor
528
- removeCursor(true);
529
- // Reset search
530
- if (self.autocomplete) {
531
- // Go to begin of the data
532
- self.rows = data;
533
- // Remove editable attribute
534
- self.input.removeAttribute('contenteditable');
535
- // Clear input
536
- self.input.textContent = '';
537
- }
538
-
539
- // Current value
540
- let newValue = getValue();
541
-
542
- // If that is different from the component value
543
- if (!compareValues(newValue, self.value)) {
544
- self.value = newValue;
545
- } else {
546
- // Update label
547
- updateLabel();
548
- }
549
-
550
- // Identify the new state of the dropdown
551
- self.state = false;
552
-
553
- if (typeof (self.onclose) === 'function') {
554
- self.onclose(self);
555
- }
574
+ return null;
556
575
  }
557
576
 
558
577
  const onopen = function () {
@@ -570,20 +589,82 @@ if (!Modal && typeof (require) === 'function') {
570
589
  }
571
590
  // Prepare search field
572
591
  if (self.autocomplete) {
573
- // Clear input
574
- self.input.textContent = '';
592
+ // Get the input
593
+ let input = getInput();
575
594
  // Editable
576
- self.input.setAttribute('contenteditable', true);
595
+ input.setAttribute('contenteditable', true);
596
+ // Clear input
597
+ input.textContent = '';
577
598
  // Focus on the item
578
- self.input.focus();
599
+ input.focus();
579
600
  }
601
+ // Adjust width and height
602
+ adjustDimensions(self.data);
603
+ // Open event
604
+ Dispatch.call(self, self.onopen, 'open', {
605
+ instance: self
606
+ });
607
+ }
580
608
 
581
- if (typeof (self.onopen) === 'function') {
582
- self.onopen(self);
609
+ const onclose = function (options, origin) {
610
+ // Cursor
611
+ removeCursor(true);
612
+ // Reset search
613
+ if (self.autocomplete) {
614
+ // Go to begin of the data
615
+ self.rows = data;
616
+ // Get the input
617
+ let input = getInput();
618
+ if (input) {
619
+ // Remove editable attribute
620
+ input.removeAttribute('contenteditable');
621
+ // Clear input
622
+ input.textContent = '';
623
+ }
624
+ }
625
+
626
+ if (origin === 'escape') {
627
+ // Cancel operation and keep the same previous value
628
+ setValue(self.value, true);
629
+ } else {
630
+ // Current value
631
+ let newValue = getValue();
632
+
633
+ // If that is different from the component value
634
+ if (changesDetected === true && ! compareValues(newValue, self.value)) {
635
+ self.value = newValue;
636
+ } else {
637
+ // Update label
638
+ updateLabel();
639
+ }
640
+ }
641
+
642
+ // Identify the new state of the dropdown
643
+ self.state = false;
644
+
645
+ // Close event
646
+ Dispatch.call(self, self.onclose, 'close', {
647
+ instance: self,
648
+ ...options
649
+ });
650
+ }
651
+
652
+ const normalizeData = function(result) {
653
+ if (result && result.length) {
654
+ return result.map((v) => {
655
+ if (typeof v === 'string' || typeof v === 'number') {
656
+ return { value: v, text: v };
657
+ } else if (typeof v === 'object' && v.hasOwnProperty('name')) {
658
+ return { value: v.id, text: v.name };
659
+ } else {
660
+ return v;
661
+ }
662
+ });
583
663
  }
584
664
  }
585
665
 
586
666
  const loadData = function(result) {
667
+ result = normalizeData(result);
587
668
  // Loading controls
588
669
  lazyloading = lazyLoading(self);
589
670
  // Loading new data from a remote source
@@ -598,152 +679,376 @@ if (!Modal && typeof (require) === 'function') {
598
679
  if (typeof(self.value) !== 'undefined') {
599
680
  setValue(self.value, true);
600
681
  }
601
- // Custom event by the developer
602
- if (typeof(onload) === 'function') {
603
- onload(self);
604
- }
682
+ // Onload method
683
+ Dispatch.call(self, load, 'load', {
684
+ instance: self
685
+ });
686
+ // Remove loading spin
687
+ self.input.classList.remove('lm-dropdown-loading');
605
688
  }
606
689
 
607
- self.add = async function (e) {
608
- if (!self.input.textContent) {
609
- return false;
690
+ const resetData = function(result) {
691
+ result = normalizeData(result);
692
+ // Reset cursor
693
+ removeCursor(true);
694
+ let r = data.filter(item => {
695
+ return item.selected === true;
696
+ });
697
+ // Loading new data from a remote source
698
+ if (result) {
699
+ result.forEach((v) => {
700
+ r.push(v);
701
+ });
610
702
  }
703
+ self.rows = r;
704
+ // Remove loading spin
705
+ self.input.classList.remove('lm-dropdown-loading');
611
706
 
612
- e.preventDefault();
707
+ // Event
708
+ Dispatch.call(self, self.onsearch, 'search', {
709
+ instance: self,
710
+ result: result,
711
+ });
712
+ }
613
713
 
614
- // New item
615
- let s = {
616
- text: self.input.textContent,
617
- value: self.input.textContent,
618
- }
714
+ const getInput = function() {
715
+ return self.input;
716
+ }
619
717
 
620
- // Event
621
- if (typeof (self.onbeforeinsert) === 'function') {
622
- let elClass = self.el.classList;
623
- elClass.add('lm-dropdown-loading');
624
- let ret = await self.onbeforeinsert(self, s);
625
- elClass.remove('lm-dropdown-loading');
626
- if (ret === false) {
627
- return;
628
- } else if (ret) {
629
- s = ret;
718
+ const search = function(query) {
719
+ if (! self.isClosed() && self.autocomplete) {
720
+
721
+ // Remote or normal search
722
+ if (self.remote === true) {
723
+ // Clear existing timeout
724
+ if (searchTimeout) {
725
+ clearTimeout(searchTimeout);
726
+ }
727
+ // Loading spin
728
+ self.input.classList.add('lm-dropdown-loading');
729
+ // Headers
730
+ let http = {
731
+ headers: {
732
+ 'Content-Type': 'text/json',
733
+ }
734
+ }
735
+ let ret = Dispatch.call(self, self.onbeforesearch, 'beforesearch', {
736
+ instance: self,
737
+ http: http,
738
+ query: query,
739
+ });
740
+
741
+ if (ret === false) {
742
+ return;
743
+ }
744
+
745
+ // Debounce the search with 300ms delay
746
+ searchTimeout = setTimeout(() => {
747
+ fetch(`${self.url}?q=${query}`, http).then(r => r.json()).then(resetData).catch((error) => {
748
+ resetData([]);
749
+ });
750
+ }, 300);
751
+ } else {
752
+ // Filter options
753
+ let temp;
754
+
755
+ const find = (prop) => {
756
+ if (prop) {
757
+ if (Array.isArray(prop)) {
758
+ // match if ANY element contains the query (case-insensitive)
759
+ return prop.some(v => v != null && v.toString().toLowerCase().includes(query));
760
+ }
761
+ // handle strings/numbers/others
762
+ return prop.toString().toLowerCase().includes(query);
763
+ }
764
+ return false;
765
+ };
766
+
767
+ if (! query) {
768
+ temp = data;
769
+ } else {
770
+ temp = data.filter(item => {
771
+ return item.selected === true || find(item.text) || find(item.group) || find(item.keywords) || find(item.synonym);
772
+ });
773
+ }
774
+
775
+ // Cursor
776
+ removeCursor(true);
777
+ // Update the data from the dropdown
778
+ self.rows = temp;
630
779
  }
631
780
  }
781
+ }
632
782
 
633
- // Process the data
634
- data.unshift(s);
635
- // Select the new item
636
- self.select(e, s);
637
- // Close dropdown
638
- self.search();
783
+ const events = {
784
+ focusout: (e) => {
785
+ if (self.modal) {
786
+ if (! (e.relatedTarget && self.el.contains(e.relatedTarget))) {
787
+ if (! self.isClosed()) {
788
+ self.close({ origin: 'focusout '});
789
+ }
790
+ }
791
+ }
792
+ },
793
+ keydown: (e) => {
794
+ if (! self.isClosed()) {
795
+ let prevent = false;
796
+ if (e.code === 'ArrowUp') {
797
+ moveCursor(-1);
798
+ prevent = true;
799
+ } else if (e.code === 'ArrowDown') {
800
+ moveCursor(1);
801
+ prevent = true;
802
+ } else if (e.code === 'Home') {
803
+ moveCursor(-1, true);
804
+ if (!self.autocomplete) {
805
+ prevent = true;
806
+ }
807
+ } else if (e.code === 'End') {
808
+ moveCursor(1, true);
809
+ if (!self.autocomplete) {
810
+ prevent = true;
811
+ }
812
+ } else if (e.code === 'Enter') {
813
+ if (e.target.tagName === 'BUTTON') {
814
+ e.target.click();
815
+ let input = getInput();
816
+ input.focus();
817
+ } else {
818
+ select(e, self.rows[cursor]);
819
+ }
820
+ prevent = true;
821
+ } else if (e.code === 'Escape') {
822
+ self.close({ origin: 'escape'});
823
+ prevent = true;
824
+ } else {
825
+ if (e.keyCode === 32 && !self.autocomplete) {
826
+ select(e, self.rows[cursor]);
827
+ }
828
+ }
639
829
 
640
- // Event
641
- if (typeof (self.oninsert) === 'function') {
642
- self.oninsert(self, s);
643
- }
830
+ if (prevent) {
831
+ e.preventDefault();
832
+ e.stopImmediatePropagation();
833
+ }
834
+ } else {
835
+ if (e.code === 'ArrowUp' || e.code === 'ArrowDown' || e.code === 'Enter') {
836
+ self.open();
837
+ e.preventDefault();
838
+ e.stopImmediatePropagation();
839
+ }
840
+ }
841
+ },
842
+ mousedown: (e) => {
843
+ if (e.target.classList.contains('lm-dropdown-input')) {
844
+ if (self.autocomplete) {
845
+ let x;
846
+ if (e.changedTouches && e.changedTouches[0]) {
847
+ x = e.changedTouches[0].clientX;
848
+ } else {
849
+ x = e.clientX;
850
+ }
851
+ if (e.target.offsetWidth - (x - e.target.offsetLeft) < 20) {
852
+ toggle();
853
+ } else {
854
+ self.open();
855
+ }
856
+ } else {
857
+ toggle();
858
+ }
859
+ }
860
+ },
861
+ paste: (e) => {
862
+ if (e.target.classList.contains('lm-dropdown-input')) {
863
+ let text;
864
+ if (e.clipboardData || e.originalEvent.clipboardData) {
865
+ text = (e.originalEvent || e).clipboardData.getData('text/plain');
866
+ } else if (window.clipboardData) {
867
+ text = window.clipboardData.getData('Text');
868
+ }
869
+ text = text.replace(/(\r\n|\n|\r)/gm, "");
870
+ document.execCommand('insertText', false, text)
871
+ e.preventDefault();
872
+ }
873
+ },
874
+ input: (e) => {
875
+ if (e.target.classList.contains('lm-dropdown-input')) {
876
+ search(e.target.textContent.toLowerCase());
877
+ }
878
+ },
644
879
  }
645
880
 
646
- self.search = function () {
647
- if (self.state && self.autocomplete) {
648
- // Filter options
649
- let temp;
650
- let value = self.input.textContent.toLowerCase()
651
- if (! value) {
652
- temp = data;
881
+ const selectItem = function(s) {
882
+ if (self.remote === true) {
883
+ if (data.indexOf(s) === -1) {
884
+ self.data.push(s);
885
+ data.push(s);
886
+ }
887
+ }
888
+
889
+ if (self.multiple === true) {
890
+ let position = value.indexOf(s);
891
+ if (position === -1) {
892
+ value.push(s);
893
+ s.selected = true;
653
894
  } else {
654
- temp = data.filter(item => {
655
- return item.selected === true ||
656
- (item.text.toLowerCase().includes(value)) ||
657
- (item.group && item.group.toLowerCase().includes(value)) ||
658
- (item.keywords.toLowerCase().includes(value));
659
- });
895
+ value.splice(position, 1);
896
+ s.selected = false;
897
+ }
898
+ } else {
899
+ if (value[0] === s) {
900
+ if (self.allowEmpty === false) {
901
+ s.selected = true;
902
+ } else {
903
+ s.selected = !s.selected;
904
+ }
905
+ } else {
906
+ if (value[0]) {
907
+ value[0].selected = false;
908
+ }
909
+ s.selected = true;
910
+ }
911
+ if (s.selected) {
912
+ value = [s];
913
+ } else {
914
+ value = [];
660
915
  }
661
- // Cursor
662
- removeCursor(true);
663
- // Update the data from the dropdown
664
- self.rows = temp;
665
916
  }
917
+
918
+ changesDetected = true;
666
919
  }
667
920
 
668
- self.open = function () {
669
- if (self.modal && self.modal.closed) {
670
- // Open the modal
671
- self.modal.closed = false;
921
+ const add = async function (e) {
922
+ let input = getInput();
923
+ let text = input.textContent;
924
+ if (! text) {
925
+ return false;
926
+ }
927
+
928
+ // New item
929
+ let s = {
930
+ text: text,
931
+ value: text,
672
932
  }
933
+
934
+ self.add(s);
935
+
936
+ e.preventDefault();
673
937
  }
674
938
 
675
- self.close = function () {
676
- // Close the modal
677
- if (self.modal) {
678
- self.modal.closed = true;
939
+ const select = function (e, s) {
940
+ if (s && s.disabled !== true) {
941
+ selectItem(s);
942
+ // Close the modal
943
+ if (self.multiple !== true) {
944
+ self.close({ origin: 'button' });
945
+ }
679
946
  }
680
947
  }
681
948
 
682
- self.toggle = function () {
949
+ const toggle = function () {
683
950
  if (self.modal) {
684
- if (self.modal.closed) {
951
+ if (self.isClosed()) {
685
952
  self.open();
686
953
  } else {
687
- self.close();
954
+ self.close({ origin: 'button' });
688
955
  }
689
956
  }
690
957
  }
691
958
 
692
- self.click = function (e) {
693
- if (self.autocomplete) {
694
- let x;
695
- if (e.changedTouches && e.changedTouches[0]) {
696
- x = e.changedTouches[0].clientX;
697
- } else {
698
- x = e.clientX;
699
- }
700
- if (e.target.offsetWidth - (x - e.target.offsetLeft) < 20) {
701
- self.toggle();
702
- } else {
703
- self.open();
959
+ self.add = async function (newItem) {
960
+ // Event
961
+ if (typeof(self.onbeforeinsert) === 'function') {
962
+ self.input.classList.add('lm-dropdown-loading');
963
+ let ret = await self.onbeforeinsert(self, newItem);
964
+ self.input.classList.remove('lm-dropdown-loading');
965
+ if (ret === false) {
966
+ return;
967
+ } else if (ret) {
968
+ newItem = ret;
704
969
  }
705
- } else {
706
- self.toggle();
707
970
  }
971
+ // Process the data
972
+ data.push(newItem);
973
+ self.data.push(newItem);
974
+ // Refresh screen
975
+ self.result.unshift(newItem);
976
+ self.rows.unshift(newItem);
977
+ self.refresh('result');
978
+
979
+ Dispatch.call(self, self.oninsert, 'insert', {
980
+ instance: self,
981
+ item: newItem,
982
+ });
708
983
  }
709
984
 
710
- self.select = function (e, s) {
711
- if (s) {
712
- if (self.multiple === true) {
713
- let position = value.indexOf(s);
714
- if (position === -1) {
715
- value.push(s);
716
- s.selected = true;
717
- } else {
718
- value.splice(position, 1);
719
- s.selected = false;
985
+ self.open = function () {
986
+ if (self.modal && ! self.disabled) {
987
+ if (self.isClosed()) {
988
+ if (autoType) {
989
+ self.type = window.innerWidth > 640 ? self.type = 'default' : (self.autocomplete ? 'searchbar' : 'picker');
720
990
  }
991
+ // Track
992
+ changesDetected = false;
993
+ // Open the modal
994
+ self.modal.open();
995
+ }
996
+ }
997
+ }
998
+
999
+ self.close = function (options) {
1000
+ if (self.modal) {
1001
+ if (options?.origin) {
1002
+ self.modal.close(options)
721
1003
  } else {
722
- if (value[0] === s) {
723
- if (self.allowempty === false) {
724
- s.selected = true;
725
- } else {
726
- s.selected = !s.selected;
727
- }
728
- } else {
729
- if (value[0]) {
730
- value[0].selected = false;
731
- }
732
- s.selected = true;
733
- }
734
- if (s.selected) {
735
- value = [s];
736
- } else {
737
- value = [];
738
- }
739
- // Close the modal
740
- self.close();
1004
+ self.modal.close({ origin: 'button' })
741
1005
  }
742
1006
  }
743
1007
  }
744
1008
 
745
- self.onload = function () {
746
- if (self.type !== "inline") {
1009
+ self.isClosed = function() {
1010
+ if (self.modal) {
1011
+ return self.modal.isClosed();
1012
+ }
1013
+ }
1014
+
1015
+ self.setData = function(data) {
1016
+ self.data = data;
1017
+ }
1018
+
1019
+ self.getData = function() {
1020
+ return self.data;
1021
+ }
1022
+
1023
+ self.getValue = function() {
1024
+ return self.value;
1025
+ }
1026
+
1027
+ self.setValue = function(v) {
1028
+ self.value = v;
1029
+ }
1030
+
1031
+ self.reset = function() {
1032
+ self.value = null;
1033
+ self.close({ origin: 'button' });
1034
+ }
1035
+
1036
+ self.onevent = function(e) {
1037
+ if (events[e.type]) {
1038
+ events[e.type](e);
1039
+ }
1040
+ }
1041
+
1042
+ // Init with a
1043
+ let input = self.input;
1044
+
1045
+ onload(() => {
1046
+ if (self.type === "inline") {
1047
+ // For inline dropdown
1048
+ self.el.setAttribute('tabindex', 0);
1049
+ // Remove search
1050
+ self.input.remove();
1051
+ } else {
747
1052
  // Create modal instance
748
1053
  self.modal = {
749
1054
  closed: true,
@@ -756,11 +1061,40 @@ if (!Modal && typeof (require) === 'function') {
756
1061
  };
757
1062
  // Generate modal
758
1063
  Modal(self.el.children[1], self.modal);
759
- } else {
760
- // For inline dropdown
761
- self.el.setAttribute('tabindex', 0);
1064
+ }
1065
+
1066
+ if (self.remote === 'true') {
1067
+ self.remote = true;
1068
+ }
1069
+
1070
+ if (self.autocomplete === 'true') {
1071
+ self.autocomplete = true;
1072
+ }
1073
+
1074
+ if (self.multiple === 'true') {
1075
+ self.multiple = true;
1076
+ }
1077
+
1078
+ if (self.insert === 'true') {
1079
+ self.insert = true;
1080
+ }
1081
+
1082
+ // Autocomplete will be forced to be true when insert action is active
1083
+ if ((self.insert === true || self.type === 'searchbar' || self.remote === true) && ! self.autocomplete) {
1084
+ self.autocomplete = true;
1085
+ }
1086
+
1087
+ if (typeof(input) !== 'undefined') {
1088
+ // Remove the native element
1089
+ if (isDOM(input)) {
1090
+ input.classList.add('lm-dropdown-input');
1091
+ }
762
1092
  // Remove search
763
1093
  self.input.remove();
1094
+ // New input
1095
+ self.input = input;
1096
+ } else {
1097
+ self.el.children[0].style.position = 'relative';
764
1098
  }
765
1099
 
766
1100
  // Default width
@@ -768,76 +1102,44 @@ if (!Modal && typeof (require) === 'function') {
768
1102
  // Dropdown
769
1103
  self.el.style.width = self.width + 'px';
770
1104
  }
771
- // Adjust width and height
772
- adjustDimensions(self.data);
773
1105
 
774
- // Focus out of the component
775
- self.el.addEventListener('focusout', function (e) {
776
- if (self.modal) {
777
- if (!(e.relatedTarget && self.el.contains(e.relatedTarget)) && !self.el.contains(e.relatedTarget)) {
778
- if (!self.modal.closed) {
779
- self.modal.closed = true;
780
- }
781
- }
782
- }
783
- });
1106
+ // Height
1107
+ self.height = 400;
784
1108
 
785
- // Key events
786
- self.el.addEventListener('keydown', function (e) {
787
- if (! self.modal.closed) {
788
- let prevent = false;
789
- if (e.key === 'ArrowUp') {
790
- moveCursor(-1);
791
- prevent = true;
792
- } else if (e.key === 'ArrowDown') {
793
- moveCursor(1);
794
- prevent = true;
795
- } else if (e.key === 'Home') {
796
- moveCursor(-1, true);
797
- if (!self.autocomplete) {
798
- prevent = true;
799
- }
800
- } else if (e.key === 'End') {
801
- moveCursor(1, true);
802
- if (!self.autocomplete) {
803
- prevent = true;
804
- }
805
- } else if (e.key === 'Enter') {
806
- self.select(e, self.rows[cursor]);
807
- prevent = true;
808
- } else if (e.key === 'Escape') {
809
- self.modal.closed = true;
810
- prevent = true;
811
- } else {
812
- if (e.keyCode === 32 && !self.autocomplete) {
813
- self.select(e, self.rows[cursor]);
814
- }
815
- }
1109
+ // Animation for mobile
1110
+ if (document.documentElement.clientWidth < 800) {
1111
+ self.animation = true;
1112
+ }
816
1113
 
817
- if (prevent) {
818
- e.preventDefault();
819
- e.stopImmediatePropagation();
820
- }
821
- } else {
822
- if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Enter') {
823
- self.modal.closed = false;
824
- }
825
- }
826
- });
1114
+ // Events
1115
+ self.el.addEventListener('focusout', events.focusout);
1116
+ self.el.addEventListener('keydown', events.keydown);
1117
+ self.el.addEventListener('mousedown', events.mousedown);
1118
+ self.el.addEventListener('paste', events.paste);
1119
+ self.el.addEventListener('input', events.input);
827
1120
 
828
1121
  // Load remote data
829
1122
  if (self.url) {
830
- fetch(self.url, {
831
- headers: {
832
- 'Content-Type': 'text/json',
833
- }
834
- }).then(r => r.json()).then(loadData);
1123
+ if (self.remote === true) {
1124
+ loadData();
1125
+ } else {
1126
+ // Loading spin
1127
+ self.input.classList.add('lm-dropdown-loading');
1128
+ // Load remote data
1129
+ fetch(self.url, {
1130
+ headers: {
1131
+ 'Content-Type': 'text/json',
1132
+ }
1133
+ }).then(r => r.json()).then(loadData).catch(() => {
1134
+ loadData();
1135
+ });
1136
+ }
835
1137
  } else {
836
1138
  loadData();
837
1139
  }
838
- }
1140
+ });
839
1141
 
840
- self.onchange = function (prop) {
1142
+ onchange(prop => {
841
1143
  if (prop === 'value') {
842
1144
  setValue(self.value);
843
1145
  } else if (prop === 'data') {
@@ -848,36 +1150,21 @@ if (!Modal && typeof (require) === 'function') {
848
1150
  if (typeof (lazyloading) === 'function') {
849
1151
  lazyloading(prop);
850
1152
  }
851
- }
852
-
853
- /**
854
- * Sanitize any HTML from be paste on the search
855
- * @param e
856
- */
857
- self.onpaste = function (e) {
858
- let text;
859
- if (e.clipboardData || e.originalEvent.clipboardData) {
860
- text = (e.originalEvent || e).clipboardData.getData('text/plain');
861
- } else if (window.clipboardData) {
862
- text = window.clipboardData.getData('Text');
863
- }
864
- text = text.replace(/(\r\n|\n|\r)/gm, "");
865
- document.execCommand('insertText', false, text)
866
- e.preventDefault();
867
- }
1153
+ });
868
1154
 
869
- return `<div class="lm-dropdown" data-insert="{{self.insert}}" data-type="{{self.type}}" data-state="{{self.state}}" :value="self.value" :data="self.data">
1155
+ return render => render`<div class="lm-dropdown" data-state="{{self.state}}" data-insert="{{self.insert}}" data-type="{{self.type}}" data-disabled="{{self.disabled}}" :value="self.value" :data="self.data">
870
1156
  <div class="lm-dropdown-header">
871
- <div class="lm-dropdown-input" onpaste="self.onpaste" oninput="self.search" onmousedown="self.click" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
872
- <div class="lm-dropdown-add" onmousedown="self.add"></div>
1157
+ <div class="lm-dropdown-input" placeholder="{{self.placeholder}}" :ref="self.input" tabindex="0"></div>
1158
+ <button class="lm-dropdown-add" onclick="${add}" tabindex="0"></button>
873
1159
  <div class="lm-dropdown-header-controls">
874
- <button onclick="self.close" class="lm-dropdown-done">Done</button>
1160
+ <button onclick="self.reset" class="lm-dropdown-done">${T('Reset')}</button>
1161
+ <button onclick="self.close" class="lm-dropdown-done">${T('Done')}</button>
875
1162
  </div>
876
1163
  </div>
877
1164
  <div class="lm-dropdown-content">
878
1165
  <div>
879
1166
  <div :loop="self.result" :ref="self.container" :rows="self.rows">
880
- <div class="lm-dropdown-item" onclick="self.parent.select" data-cursor="{{self.cursor}}" data-selected="{{self.selected}}" data-group="{{self.header}}">
1167
+ <div class="lm-dropdown-item" onclick="${select}" data-cursor="{{self.cursor}}" data-disabled="{{self.disabled}}" data-selected="{{self.selected}}" data-group="{{self.header}}">
881
1168
  <div><img :src="self.image" /> <div>{{self.text}}</div></div>
882
1169
  </div>
883
1170
  </div>